From a75b3fa4b1a1b882c33f533645ddae75c09dd697 Mon Sep 17 00:00:00 2001
From: spicyjpeg <88942473+spicyjpeg@users.noreply.github.com>
Date: Sun, 28 Nov 2021 18:15:14 +0100
Subject: Switch to mipsel-none-elf, move docs, add template presets
---
doc/dev notes.txt | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
(limited to 'doc/dev notes.txt')
diff --git a/doc/dev notes.txt b/doc/dev notes.txt
index 3aa2db5..65a3976 100644
--- a/doc/dev notes.txt
+++ b/doc/dev notes.txt
@@ -109,13 +109,14 @@ your exception handler must do the following:
controller- or interrupt-related function is being called. This is necessary
(although ugly) as libpsn00b often calls such functions internally.
-* For some reason mipsel-unknown-elf-nm (symbol map generator) insists on
-outputting 64-bit addresses (with the top 32 bits set, e.g. FFFFFFFF80010000)
-even when feeding it a regular 32-bit MIPS executable, while the standard x86
-nm tool that ships with most GCC packages prints the proper 32-bit address.
-Unclear whether this is a bug, intended behavior or the result of some ancient
-ELF ABI flag crap. DL_ParseSymbolMap() will ignore the top 32 bits, so this
-should only bother you if you're implementing your own symbol map parser.
+* For some reason mipsel-unknown-elf-nm and mipsel-none-elf-nm (symbol map
+generators) insist on outputting 64-bit addresses (with the top 32 bits set,
+e.g. FFFFFFFF80010000) even when feeding it a regular 32-bit MIPS executable,
+while the standard x86 nm tool that ships with most GCC packages prints the
+proper 32-bit address. Unclear whether this is a bug, intended behavior or the
+result of some ancient ELF ABI flag crap. DL_ParseSymbolMap() will ignore the
+top 32 bits, so this should only bother you if you're implementing your own
+symbol map parser.
* I haven't worked on psxspu but, for those willing to write some code, this is
the formula to calculate SPU pitch values for playing musical notes ("^" is the
--
cgit v1.2.3
From 31fba94b8e8c15c1d127925f01a38efc68933fd1 Mon Sep 17 00:00:00 2001
From: spicyjpeg <88942473+spicyjpeg@users.noreply.github.com>
Date: Mon, 29 Nov 2021 00:17:28 +0100
Subject: Add io/system573 example, rewrite dev notes, fix CI
---
.github/workflows/build.yml | 27 ++-
CHANGELOG.md | 14 +-
README.md | 87 ++++----
doc/dev notes.txt | 250 -----------------------
doc/dev_notes.md | 280 ++++++++++++++++++++++++++
examples/io/system573/CMakeLists.txt | 27 +++
examples/io/system573/iso.xml | 34 ++++
examples/io/system573/main.c | 371 +++++++++++++++++++++++++++++++++++
8 files changed, 785 insertions(+), 305 deletions(-)
delete mode 100644 doc/dev notes.txt
create mode 100644 doc/dev_notes.md
create mode 100644 examples/io/system573/CMakeLists.txt
create mode 100644 examples/io/system573/iso.xml
create mode 100644 examples/io/system573/main.c
(limited to 'doc/dev notes.txt')
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d6746b0..8c16ac3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -181,22 +181,38 @@ jobs:
needs: [ build-sdk-windows, build-sdk-linux ]
steps:
+ - name: Initialize toolchain cache
+ if: ${{ github.ref_type == 'tag' }}
+ uses: actions/cache@v2
+ with:
+ key: gcc-${{ env.GCC_TARGET }}-${{ env.GCC_VERSION }}
+ path: gcc
+
+ - name: Package GCC toolchains
+ if: ${{ github.ref_type == 'tag' }}
+ run: |
+ cd gcc/windows
+ zip -9 -q -r ../../gcc-${{ env.GCC_TARGET }}-${{ env.GCC_VERSION }}-windows.zip .
+ cd ../linux
+ zip -9 -q -r ../../gcc-${{ env.GCC_TARGET }}-${{ env.GCC_VERSION }}-linux.zip .
+
- name: Fetch repo contents
+ if: ${{ github.ref_type == 'tag' }}
uses: actions/checkout@v2
with:
path: sdk
+ - name: Generate release notes
+ if: ${{ github.ref_type == 'tag' }}
+ run: |
+ python3 sdk/.github/scripts/generate_release_notes.py -v ${{ github.ref_name }} -o release.md sdk/CHANGELOG.md
+
- name: Fetch build artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/download-artifact@v2
with:
path: .
- - name: Generate release notes
- if: ${{ github.ref_type == 'tag' }}
- run: |
- sdk/.github/scripts/generate_release_notes.py -v ${{ github.ref_name }} -o release.md sdk/CHANGELOG.md
-
- name: Publish release
if: ${{ github.ref_type == 'tag' }}
uses: softprops/action-gh-release@v1
@@ -204,5 +220,6 @@ jobs:
fail_on_unmatched_files: true
body_path: release.md
files: |
+ *.zip
psn00bsdk-windows/*
psn00bsdk-linux/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c05105e..9463b83 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,7 +17,7 @@ contributing to PSn00bSDK, add a new block at the top following this template:
You may run `.github/scripts/generate_release_notes.py CHANGELOG.md` afterwards
to ensure the changelog can be parsed correctly.
---------
+-------------------------------------------------------------------------------
## 2021-11-28: 0.18
@@ -25,13 +25,13 @@ spicyjpeg:
- libc: Removed `STACK_MAX_SIZE` and added `_mem_init()` back. RAM and stack
size can now be set by calling `_mem_init()` manually before allocating any
- memory.
+ memory (however this seems to be currently broken).
- libc: `sprintf()` now supports fixed padding when using the `%@` (binary
integer) format specifier.
-- psxcd: File paths with forward slashes instead of backslashes are now
- accepted.
+- psxcd: File paths with forward slashes instead of backslashes, as well as
+ paths containing both slash types, are now accepted.
- psxapi: Added wrapper around BIOS function `GetSystemInfo()`.
@@ -44,8 +44,10 @@ spicyjpeg:
- Deprecated `malloc.h` and removed all references to it (`stdlib.h` should be
used instead). Moved `int*_t` and `uint*_t` types to `stdint.h`.
-- Fixed file permission errors when attempting to install the SDK on macOS and
- made some small updates to `INSTALL.md`, which is now `doc/installation.md`.
+- Fixed file permission errors when attempting to install the SDK on macOS.
+
+- Cleaned up, updated and moved all documentation to the `doc` folder.
+ Rewritten this changelog and added a script to generate release notes.
## 2021-10-31
diff --git a/README.md b/README.md
index 6fd4cd1..13ab7d1 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+
# PSn00bSDK
PSn00bSDK is a 100% free and open source SDK project for the original Sony
@@ -5,33 +6,32 @@ PlayStation for developing homebrew applications and games for the console
100% freely. This SDK can be used for freeware, commercial, and open source
homebrew projects.
-The SDK is composed mainly of libraries (libpsn00b) and some utilities that
-provide a basic framework for developing software for the PlayStation
-hardware, the compiler is separate (GCC) and should be acquired from GNU.
-The library API is intentionally written to resemble the library API of the
-official libraries as closely as possible. This design decision is not only
-for familiarity reasons to experienced programmers, but also so that existing
-sample code and tutorials would still apply to this SDK, as well as making
-the process of porting over existing homebrew originally made with official
-SDKs easier with minimal modification, provided it doesn't use libgs.
+The SDK is composed mainly of libraries (`libpsn00b`) and some utilities that
+provide a basic framework for developing software for the PlayStation hardware,
+the compiler is separate (GCC) and should be acquired from GNU. The library API
+is intentionally written to resemble the library API of the official libraries
+as closely as possible. This design decision is not only for familiarity
+reasons to experienced programmers, but also so that existing sample code and
+tutorials would still apply to this SDK, as well as making the process of
+porting over existing homebrew originally made with official SDKs easier with
+minimal modification, provided it doesn't use `libgs`.
PSn00bSDK is currently a work in progress and cannot really be considered
-production ready, but what is currently implemented should be enough to
-produce some interesting homebrew with the SDK, especially with its extensive
-support for the GPU and GTE hardware. There's no reason not to fully support
-hardware features of a target platform when said hardware features have been
-fully documented for years (nocash's PSX specs document in this case).
+production ready, but what is currently implemented should be enough to produce
+some interesting homebrew with the SDK, especially with its extensive support
+for the GPU and GTE hardware. There's no reason not to fully support hardware
+features of a target platform when said hardware features have been fully
+documented for years (nocash's PSX specs document in this case).
-Most of libpsn00b is written mostly in MIPS assembly, moreso functions that
+Most of `libpsn00b` is written mostly in MIPS assembly, moreso functions that
interface with the hardware. Many of the standard C functions are implemented
in custom MIPS assembly instead of equivalents found in the BIOS ROM, for both
-stability (the BIOS libc implementation of the PlayStation is actually buggy)
+stability (the BIOS `libc` implementation of the PlayStation is actually buggy)
and performance reasons.
-
## Notable features
-As of August 16, 2021
+As of November 28, 2021
* Extensive GPU support with polygon, line and sprite primitives, high-speed
DMA transfers for VRAM data and ordering tables. All video modes for both
@@ -76,28 +76,25 @@ As of August 16, 2021
* Fully expandable and customizable to your heart's content.
-
## Obtaining PSn00bSDK
PSn00bSDK has switched to a CMake-based build and installation system. See
[installation.md](doc/installation.md) for details.
-Because PSn00bSDK is updated semi-regularly due to this project being in
-a work-in-progress state, it is better to obtain this SDK from source and
-building it yourself in the long run. Pre-compiled packages for Debian and
-Msys2 are being planned however (it is already possible to build installers,
-DEB and RPM packages through CPack so it's only a matter of time).
+Prebuilt SDK packages and versions of the GCC toolchain for Windows and Linux
+(DEB/RPM) are available through GitHub Actions. Stable releases haven't yet
+been published however, due to this project being in a work-in-progress state.
+It is still recommended to build the SDK from source for the time being.
## Examples
-There are a few examples and complete source code of n00bdemo included in
-the examples directory. More example programs may be added in the future
-and contributed example programs are welcome.
-
-There's also Lameguy's PlayStation Programming Tutorial Series at
-http://lameguy64.net/tutorials/pstutorials/ for learning how to program
-for the PlayStation. The tutorials should still apply to PSn00bSDK.
+There are a few examples and complete source code of `n00bdemo` included in the
+`examples` directory. More example programs may be added in the future and
+contributed example programs are welcome.
+There's also [Lameguy's PlayStation Programming Tutorial Series](http://lameguy64.net/tutorials/pstutorials)
+for learning how to program for the PlayStation. The tutorials should still
+apply to PSn00bSDK.
## To-do List
@@ -121,33 +118,35 @@ for the PlayStation. The tutorials should still apply to PSn00bSDK.
## Usage terms (or lack thereof)
-PSn00bSDK falls under the terms and conditions of the Mozilla Public
-License. A quick summary of this license is that PSn00bSDK can be used
-freely in both free and open source projects and commercial closed source
-projects as projects using PSn00bSDK does not necessarily have to follow
-the MPL as well.
+PSn00bSDK falls under the terms and conditions of the Mozilla Public License. A
+quick summary of this license is that PSn00bSDK can be used freely in both free
+and open source projects and commercial closed source projects as projects
+using PSn00bSDK does not necessarily have to follow the MPL as well.
If modifications to the SDK were made as part of the development of such
-projects that enhance its functionality, such changes must be contributed
-back in return.
+projects that enhance its functionality, such changes must be contributed back
+in return.
Homebrew made with PSn00bSDK may not be released under 'annoyingmous'. Although
there's nothing that would enforce it, this term may as well be ignored despite
it annoying this SDK's author.
-
## Credits
-Main developer:
-* Lameguy64
+Main developers:
+
+* **Lameguy64**
+* **spicyjpeg**: dynamic linker, CMake scripts, some docs and examples
Honorable mentions:
-* ijacquez - helpful suggestions for getting C++ working.
-* NicolasNoble - his OpenBIOS project gave insight to how the BIOS works
+
+* **ijacquez**: helpful suggestions for getting C++ working.
+* **Nicolas Noble**: his OpenBIOS project gave insight to how the BIOS works
internally.
Helpful contributors can be found in the changelog.
References used:
-* nocash's PlayStation specs document (http://problemkaputt.de/psx-spx.htm)
+
+* [nocash's PlayStation specs document](http://problemkaputt.de/psx-spx.htm)
* Tails92's PSXSDK project (during PSn00bSDK's infancy).
diff --git a/doc/dev notes.txt b/doc/dev notes.txt
deleted file mode 100644
index 65a3976..0000000
--- a/doc/dev notes.txt
+++ /dev/null
@@ -1,250 +0,0 @@
-These are some development notes I've put together that would be of great
-aid to those willing to contribute to the PSn00bSDK project. Many of these
-came from my own experience dealing with the PS1 at low-level when I ran
-into some unexplained quirks while some are from disassembly observations
-and clarification of existing documents. More entries will be added when
-I run into more previously undocumented quirks in the future.
- - Lameguy64
-
-* When calling C functions (ie. BIOS functions) from assembly code you'll
-need to allocate N words on the stack first when calling a function that has
-N arguments (addiu $sp, -(4*N) where N = number of arguments of the function
-being called) even if the arguments are on registers a0 to a3 and the C
-functions don't always use the space allotted in stack. When calling a
-function with a variable number of arguments (printf) always allocate
-16 bytes of stack.
-
-* Hooking a custom handler using BIOS function HookEntryInt (B(19h), known
-as SetCustomExitFromException in nocash docs) is only triggered when there's
-an IRQ that is not yet acknowledged by previous IRQ handlers built into the
-kernel. This is also the best point to acknowledge any IRQs without breaking
-compatibility with built-in BIOS IRQ handlers and is what the official SDK
-uses to handle IRQs. To make sure this handler is triggered on every interrupt
-you must call ChangeClearPad(0) and ChangeClearRCnt(3, 0) (which are functions
-(B(5Bh)) and C(0Ah) respectively) otherwise the pad and root counter handlers
-in the kernel will acknowledge the interrupt before your handler, preventing
-you from handling them yourself.
-
-* It is not advisable to handle interrupts using event handlers like in
-PSXSDK as it breaks BIOS features that depend on interrupts. Clearing the
-VBlank IRQ in a event handler for example prevents the BIOS controller
-functions from working as it depends on the VBlank IRQ to determine when to
-query controllers. Acknowledge interrupts using a custom handler set by BIOS
-function HookEntryInt (B(19h), known as SetCustomExitFromException in nocash
-docs).
-
-* When running in high resolution mode you must additionally wait for bit 31
-in GPUSTAT (1F801814h) to alternate on every frame (frame 0: wait until 0,
-frame 1: wait until 1, frame 2: wait until 0) before waiting for VSync
-otherwise the GPU will only draw the first field if you don't have drawing
-to displayed area enabled. Performing this check in a low resolution/non
-interlaced mode is harmless.
-
-* There's a hardware bug in the GPU FillVRAM command GP0(02h) where if you
-set the height to 512 pixels the primitive is processed with a height of 0
-as the hardware does not appear to interpret the last bit of the height
-value. This is most apparent when putting a DRAWENV with the height of 512
-pixels (for PAL for example) and background clearing is enabled, hence why
-DRAWENV.isbg is not effective in the official SDK.
-
-* In the official SDK, DMA IRQs appear to be enabled only when a callback
-function is set (ie. DrawSyncCallback() enables IRQ for DMA channel 2). DMA
-IRQs are only triggered on transfer completion.
-
-Additional notes by spicyjpeg:
-
-* The controller/memory card SPI interface is poorly implemented in most
-emulators, making custom controller polling code insanely hard to write and
-debug. The only emulator that comes close to real hardware is no$psx, which
-seems to correctly implement all features of the SPI port (even those not used
-by the BIOS pad driver, such as TX/RX interrupts). DuckStation only emulates
-the bare minimum required by the BIOS and Sony libraries, and pcsx-redux has
-major bugs that break most custom polling implementations. This pretty much
-means TX/RX IRQs and many flags in the JOY_* registers should not be used
-unless you are willing to break compatibility with emulators.
-
-* As if communicating with controllers wasn't difficult enough already,
-DualShock pads also have a built-in watchdog timer that gets enabled when first
-putting them in configuration mode (and is NOT disabled after exiting config
-mode). If no polling commands are sent to the controller for about 1 second,
-vibration motors are switched off and analog mode is disabled; the same happens
-if the analog button is pressed while in analog mode. In order to always keep
-the pad in analog mode you must:
-
- * Poll both controller ports at least once per frame by sending command 42h.
- Polling at a higher rate might be desirable in some cases (such as rhythm
- games) to increase timing accuracy.
- * If a digital pad response (type = 4) is received from a port that hasn't
- previously been flagged as digital-only, attempt to put the pad into config
- mode using command 43h *twice* (as the proper response is delayed).
- * If the pad doesn't recognize the config command, flag it as digital-only
- and treat all further digital pad responses from it as valid.
- * If the pad recognizes the command, it will reply by identifying as an
- analog pad in config mode (type = 15). Send command 44h immediately
- (without sending 42h first, otherwise the pad will exit config mode) to
- enable analog mode and turn on the LED.
- * Pressing the analog button will result in the controller identifying as
- digital even though it is not flagged as such, thus re-triggering the
- configuration process and putting it back into analog mode.
- * All analog pad responses (type = 7) can be treated as valid, as they will
- come from controllers that have already been configured.
- * If no valid response is received, assume no controller is connected and
- reset the port's digital-only flag.
-
-* The SDK provides no support yet for replacing the BIOS exception handler with
-a custom one, however it can be done (if you are ok with losing all BIOS
-controller, memory card and file functionality). In order not to break anything
-your exception handler must do the following:
-
- * prevent GTE opcodes from being executed twice due to a hardware glitch (the
- nocash docs explain how to do this);
- * define _irq_func_table[12] as an *extern* array of function pointers, call
- the appropriate entry when an IRQ occurs and clear the respective flag in
- register 1F801070h;
- * handle syscalls 01h-02h, i.e. EnterCriticalSection and ExitCriticalSection
- properly. You should also handle syscall FF00h (invalid API usage), as well
- as breaks 1800h and 1C00h (division errors injected by GCC), by locking up
- and maybe showing a BSOD or similar;
- * overwrite the default BIOS API vectors with a passthrough that checks no
- controller- or interrupt-related function is being called. This is necessary
- (although ugly) as libpsn00b often calls such functions internally.
-
-* For some reason mipsel-unknown-elf-nm and mipsel-none-elf-nm (symbol map
-generators) insist on outputting 64-bit addresses (with the top 32 bits set,
-e.g. FFFFFFFF80010000) even when feeding it a regular 32-bit MIPS executable,
-while the standard x86 nm tool that ships with most GCC packages prints the
-proper 32-bit address. Unclear whether this is a bug, intended behavior or the
-result of some ancient ELF ABI flag crap. DL_ParseSymbolMap() will ignore the
-top 32 bits, so this should only bother you if you're implementing your own
-symbol map parser.
-
-* I haven't worked on psxspu but, for those willing to write some code, this is
-the formula to calculate SPU pitch values for playing musical notes ("^" is the
-power operator, not xor):
-
- frequency = (ref / 32) * (2 ^ ((note - 9) / 12))
- spu_pitch = frequency / 44100 * 4096
-
- ref = frequency the sample should be played at to play a middle A (MIDI note 69)
- note = MIDI note number (usually 0-127, 60 is middle C)
-
-* If you are overriding any of the memory allocation functions, DO NOT ENABLE
-LINK-TIME OPTIMIZATION. GCC has a long-standing bug with LTO and weak functions
-written in assembly, also LTO hasn't been tested at all yet.
-
-Obscure CMake issues and related stuff:
-
-* Toolchain files are loaded "early" according to the CMake docs. What this
-means in practice is that a lot of commands, such as find_*(), won't work
-properly in a toolchain script as they rely on variables initialized by the
-project() command. The poorly documented solution to this is to move such
-commands to a separate file and set CMAKE_PROJECT_INCLUDE to point to it, so
-project() will execute it immediately after initialization.
-
-* After executing the toolchain file, CMake generates and attempts to build
-several dummy projects to test the compiler. Each of these projects re-includes
-the toolchain script (which is why you'll see commands executed multiple times)
-and uses the same variable values as the main project, however CMake will *NOT*
-pass custom variables through by default. If your toolchain script has options
-that can be set via custom variables (like PSN00BSDK_TC and PSN00BSDK_PREFIX in
-PSn00bSDK), you'll have to set CMAKE_TRY_COMPILE_PLATFORM_VARIABLES to a list
-of variable names to be exported to generated dummy projects.
-
-* There is no way to use multiple toolchains (PS1 + host) in a single project,
-even if you use add_subdirectory() to execute multiple project files (which,
-confusingly, adds their targets to the parent project rather than treating them
-as separate projects). Thankfully though CMake provides support for automating
-the build process of independent CMake projects via the ExternalProject module.
-Which brings me to the next issue...
-
-* If you run CPack on a "superbuild" project (i.e. a project that calls
-ExternalProject_Add() to configure, compile and install subprojects at build
-time), you'll likely run into a weird issue with CPack bundling folders from
-your build directory into DEB and RPM packages. This is caused by the DEB/RPM
-generators running "cmake --install" in a chroot/fakeroot to prepare the files
-to be packaged, which seems to interfere with absolute paths in the project
-cache or something like that (?). The only workaround I know of is to use
-CPACK_PRE_BUILD_SCRIPTS to trigger a custom script that deletes anything other
-than the actual files to be packaged (see cpack/fakeroot_fix.cmake).
-
-* Project installation might fail on macOS (and possibly some Linux distros) if
-CMake attempts to set permissions on system directories such as /usr/local.
-This is usually caused by install() commands that copy files to the root
-installation directory rather than to a subfolder, like this:
-
- install(
- DIRECTORY install_tree/
- DESTINATION .
- USE_SOURCE_PERMISSIONS
- )
-
-If the USE_SOURCE_PERMISSIONS flag is specified CMake will attempt to set
-permissions on the DESTINATION folder, which in this case would be the root
-prefix (/usr/local by default on macOS), to match the source directory. This
-will however fail as macOS restricts top-level system directories from having
-their permissions changed. The simplest workaround is to avoid using
-"DESTINATION ." and install each subdirectory explicitly instead, like this:
-
- foreach(
- _dir IN ITEMS
- ${CMAKE_INSTALL_BINDIR}
- ${CMAKE_INSTALL_LIBDIR}
- ${CMAKE_INSTALL_INCLUDEDIR}
- ${CMAKE_INSTALL_DATADIR}
- )
- install(
- DIRECTORY install_tree/${_dir}/
- DESTINATION ${_dir}
- USE_SOURCE_PERMISSIONS
- )
- endforeach()
-
-* Depending on how you find external dependencies (find_package(), vcpkg,
-pkg-config...), CMake may end up outputting an executable that relies on a DLL
-installed system-wide. To correctly install the DLL alongside the executable
-you have to specify a regex as follows:
-
- install(
- TARGETS my_executable
- RUNTIME_DEPENDENCIES
- PRE_EXCLUDE_REGEXES ".*"
- PRE_INCLUDE_REGEXES "tinyxml2"
- )
-
-CMake will scan the executable at install time and copy all the required DLLs
-that match the second regex. If no regex is specified CMake will also copy OS
-DLLs like libc or msvcrt, which usually isn't the desired behavior.
-
-* Using interface targets to set include directories can be finicky. Not only
-do you have to use generator expressions to conditionally use different paths
-depending on whether the targets are installed, but CMake can get confused on
-which options to pass to the compiler. I spent hours trying to get CMake to use
--I rather than -isystem for include directories (for some reason GCC would
-ignore -isystem completely). I eventually gave up and just set the include
-directories manually for each target, and for some reason CMake actually
-started passing -I instead of -isystem to GCC.
-
-* When using CPack with NSIS, all CPACK_NSIS_* variables are passed to NSIS
-verbatim, i.e. without the usual slash-to-backslash path conversion that CMake
-does on Windows. Most Windows programs accept paths with slashes without issue,
-unfortunately the NSIS builder is not one of those. To add insult to injury,
-CMake doesn't even escape backslashes by default when quoting strings in the
-generated CPack config file! So you have to convert the paths manually *and*
-tell CMake to enable escaping by setting CPACK_VERBATIM_VARIABLES, like this:
-
- set(CPACK_VERBATIM_VARIABLES ON)
- foreach(
- _var IN ITEMS
- CPACK_NSIS_MUI_ICON
- CPACK_NSIS_MUI_UNIICON
- CPACK_NSIS_MUI_HEADERIMAGE
- CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP
- CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP
- )
- cmake_path(NATIVE_PATH ${_var} ${_var})
- endforeach()
-
-* Not a CMake/CPack bug per se, but NSIS is picky about the banner and header
-images shown in generated installers. They must be Windows BMP version 3 files
-with no alpha channel, no compression and no metadata. They can either be
-24-bit RGB or indexed, though it's common to use indexed colors to save space.
diff --git a/doc/dev_notes.md b/doc/dev_notes.md
new file mode 100644
index 0000000..3aa2304
--- /dev/null
+++ b/doc/dev_notes.md
@@ -0,0 +1,280 @@
+
+# Development notes
+
+These are some development notes I've put together that would be of great aid
+to those willing to contribute to the PSn00bSDK project. Many of these came
+from my own experience dealing with the PS1 at low-level when I ran into some
+unexplained quirks while some are from disassembly observations and
+clarification of existing documents. More entries will be added when I run into
+more previously undocumented quirks in the future. _- Lameguy64_
+
+Porting PSn00bSDK to CMake also uncovered a lot of bugs and undocumented
+behavior in CMake itself. I documented [below](#cmake) all issues I ran into.
+_- spicyjpeg_
+
+## MIPS ABI / compiler
+
+- When calling C functions (ie. BIOS functions) from assembly code you'll need
+ to allocate N words on the stack first when calling a function that has N
+ arguments (`addiu $sp, -(4*N)` where N = number of arguments of the function
+ being called) even if the arguments are on registers `a0` to `a3` and the C
+ functions don't always use the space allotted in stack. When calling a
+ function with a variable number of arguments (`printf`) always allocate 16
+ bytes of stack.
+
+- For some reason `mipsel-unknown-elf-nm` and `mipsel-none-elf-nm` (symbol map
+ generators) insist on outputting 64-bit addresses (with the top 32 bits set,
+ e.g. `FFFFFFFF80010000`) even when feeding it a regular 32-bit MIPS
+ executable, while the standard x86 nm tool that ships with most GCC packages
+ prints the proper 32-bit address. Unclear whether this is a bug, intended
+ behavior or the result of some ancient ELF ABI flag crap.
+ `DL_ParseSymbolMap()` will ignore the top 32 bits, so this should only bother
+ you if you're implementing your own symbol map parser.
+
+- If you are overriding any of the memory allocation functions,
+ **DO NOT ENABLE LINK-TIME OPTIMIZATION**. GCC has a long-standing bug with
+ LTO and weak functions written in assembly, also LTO hasn't been tested at
+ all yet.
+
+## BIOS and interrupts
+
+- Hooking a custom handler using BIOS function `HookEntryInt` (`B(19h)`, known
+ as `SetCustomExitFromException` in nocash docs) is only triggered when
+ there's an IRQ that is not yet acknowledged by previous IRQ handlers built
+ into the kernel. This is also the best point to acknowledge any IRQs without
+ breaking compatibility with built-in BIOS IRQ handlers and is what the
+ official SDK uses to handle IRQs. To make sure this handler is triggered on
+ every interrupt you must call `ChangeClearPad(0)` and `ChangeClearRCnt(3, 0)`
+ (which are functions `B(5Bh)` and `C(0Ah)` respectively) otherwise the pad
+ and root counter handlers in the kernel will acknowledge the interrupt before
+ your handler, preventing you from handling them yourself.
+
+- It is not advisable to handle interrupts using event handlers like in PSXSDK
+ as it breaks BIOS features that depend on interrupts. Clearing the VBlank IRQ
+ in a event handler for example prevents the BIOS controller functions from
+ working as it depends on the VBlank IRQ to determine when to query
+ controllers. Acknowledge interrupts using a custom handler set by BIOS
+ function `HookEntryInt` (`B(19h)`, known as `SetCustomExitFromException` in
+ nocash docs).
+
+- In the official SDK, DMA IRQs appear to be enabled only when a callback
+ function is set (ie. `DrawSyncCallback()` enables IRQ for DMA channel 2). DMA
+ IRQs are only triggered on transfer completion.
+
+- PSn00bSDK provides no support yet for replacing the BIOS exception handler
+ with a custom one, however it can be done (if you are ok with losing all BIOS
+ controller, memory card and file functionality). In order not to break
+ anything your exception handler must do the following:
+
+ - prevent GTE opcodes from being executed twice due to a hardware glitch (the
+ nocash docs explain how to do this);
+ - define `_irq_func_table[12]` as an *extern* array of function pointers,
+ call the appropriate entry when an IRQ occurs and clear the respective flag
+ in register `1F801070h`;
+ - handle syscalls `01h`-`02h`, i.e. `EnterCriticalSection` and
+ `ExitCriticalSection`, properly. You should also handle syscall `FF00h`
+ (invalid API usage), as well as breaks `1800h` and `1C00h` (division errors
+ injected by GCC), by locking up and maybe showing a BSOD or similar;
+ - overwrite the default BIOS API vectors with a passthrough that checks no
+ controller- or interrupt-related function is being called. This is necessary
+ (although ugly) as `libpsn00b` often calls such functions internally.
+
+## Hardware
+
+- When running in high resolution mode you must additionally wait for bit 31 in
+ `GPUSTAT` (`1F801814h`) to alternate on every frame (frame 0: wait until 0,
+ frame 1: wait until 1, frame 2: wait until 0) before waiting for VSync
+ otherwise the GPU will only draw the first field if you don't have drawing to
+ displayed area enabled. Performing this check in a low resolution/non
+ interlaced mode is harmless.
+
+- There's a hardware bug in the GPU `FillVRAM` command `GP0(02h)` where if you
+ set the height to 512 pixels the primitive is processed with a height of 0 as
+ the hardware does not appear to interpret the last bit of the height value.
+ This is most apparent when putting a DRAWENV with the height of 512 pixels
+ (for PAL for example) and background clearing is enabled, hence why
+ `DRAWENV.isbg` is not effective in the official SDK.
+
+- The controller/memory card SPI interface is poorly implemented in most
+ emulators, making custom controller polling code insanely hard to write and
+ debug. The only emulator that comes close to real hardware is no$psx, which
+ seems to correctly implement all features of the SPI port (even those not
+ used by the BIOS pad driver, such as TX/RX interrupts). DuckStation only
+ emulates the bare minimum required by the BIOS and Sony libraries, and
+ pcsx-redux has major bugs that break most custom polling implementations.
+ This pretty much means TX/RX IRQs and many flags in the `JOY_*` registers
+ should **not** be used unless you are willing to break compatibility with
+ emulators.
+
+- As if communicating with controllers wasn't difficult enough already,
+ DualShock pads also have a built-in watchdog timer that gets enabled when
+ first putting them in configuration mode (and is **not** disabled after
+ exiting config mode). If no polling commands are sent to the controller for
+ about 1 second, vibration motors are switched off and analog mode is
+ disabled; the same happens if the analog button is pressed while in analog
+ mode. In order to always keep the pad in analog mode you must:
+
+ 1. Poll both controller ports at least once per frame by sending command
+ `42h`. Polling at a higher rate might be desirable in some cases (such as
+ rhythm games) to increase timing accuracy.
+ 2. If a digital pad response (type = 4) is received from a port that hasn't
+ previously been flagged as digital-only, attempt to put the pad into
+ config mode using command `43h` *twice* (as the proper response is
+ delayed).
+ - If the pad doesn't recognize the config command, flag it as digital-only
+ and treat all further digital pad responses from it as valid.
+ - If the pad recognizes the command, it will reply by identifying as an
+ analog pad in config mode (type = 15). Send command `44h` immediately
+ (without sending `42h` first, otherwise the pad will exit config mode)
+ to enable analog mode and turn on the LED.
+ - Pressing the analog button will result in the controller identifying as
+ digital even though it is not flagged as such, thus re-triggering the
+ configuration process and putting it back into analog mode.
+ 3. All analog pad responses (type = 7) can be treated as valid, as they will
+ come from controllers that have already been configured.
+ 4. If no valid response is received, assume no controller is connected and
+ reset the port's digital-only flag.
+
+- I haven't worked on `psxspu` but, for those willing to write some code, this
+ is the formula to calculate SPU pitch values for playing musical notes (`^`
+ is the power operator, not xor):
+
+ ```
+ frequency = (ref / 32) * (2 ^ ((note - 9) / 12))
+ spu_pitch = frequency / 44100 * 4096
+
+ ref = frequency the sample should be played at to play a middle A (MIDI note 69)
+ note = MIDI note number (usually 0-127, 60 is middle C)
+ ```
+
+## CMake
+
+- Toolchain files are loaded "early" according to the CMake docs. What this
+ means in practice is that a lot of commands, such as `find_*()`, won't work
+ properly in a toolchain script as they rely on variables initialized by the
+ `project()` command. The poorly documented solution to this is to move such
+ commands to a separate file and set `CMAKE_PROJECT_INCLUDE` to point to it,
+ so `project()` will execute it immediately after initialization.
+
+- After executing the toolchain file, CMake generates and attempts to build
+ several dummy projects to test the compiler. Each of these projects
+ re-includes the toolchain script (which is why you'll see commands executed
+ multiple times) and uses the same variable values as the main project,
+ however CMake will *not* pass custom variables through by default. If your
+ toolchain script has options that can be set via custom variables (like
+ `PSN00BSDK_TC` and `PSN00BSDK_PREFIX` in PSn00bSDK), you'll have to set
+ `CMAKE_TRY_COMPILE_PLATFORM_VARIABLES` to a list of variable names to be
+ exported to generated dummy projects.
+
+- There is no way to use multiple toolchains (PS1 + host) in a single project,
+ even if you use `add_subdirectory()` to execute multiple project files
+ (which, confusingly, adds their targets to the parent project rather than
+ treating them as separate projects). Thankfully though CMake provides support
+ for automating the build process of independent CMake projects via the
+ `ExternalProject` module. Which brings me to the next issue...
+
+- If you run CPack on a "superbuild" project (i.e. a project that calls
+ `ExternalProject_Add()` to configure, compile and install subprojects at
+ build time), you'll likely run into a weird issue with CPack bundling folders
+ from your build directory into DEB and RPM packages. This is caused by the
+ DEB/RPM generators running `cmake --install` in a chroot/fakeroot to prepare
+ the files to be packaged, which seems to interfere with absolute paths in the
+ project cache or something like that (?). The only workaround I know of is to
+ use `CPACK_PRE_BUILD_SCRIPTS` to trigger a custom script that deletes
+ anything other than the actual files to be packaged (see
+ `cpack/fakeroot_fix.cmake`).
+
+- Project installation might fail on macOS (and possibly some Linux distros) if
+ CMake attempts to set permissions on system directories such as `/usr/local`.
+ This is usually caused by `install()` commands that copy files to the root
+ installation directory rather than to a subfolder, like this:
+
+ ```cmake
+ install(
+ DIRECTORY install_tree/
+ DESTINATION .
+ USE_SOURCE_PERMISSIONS
+ )
+ ```
+
+ If the `USE_SOURCE_PERMISSIONS` flag is specified CMake will attempt to set
+ permissions on the `DESTINATION` folder, which in this case would be the root
+ prefix (`/usr/local` by default on macOS), to match the source directory.
+ This will however fail as macOS restricts top-level system directories from
+ having their permissions changed. The simplest workaround is to avoid using
+ `DESTINATION .` and install each subdirectory explicitly instead, like this:
+
+ ```cmake
+ foreach(
+ _dir IN ITEMS
+ ${CMAKE_INSTALL_BINDIR}
+ ${CMAKE_INSTALL_LIBDIR}
+ ${CMAKE_INSTALL_INCLUDEDIR}
+ ${CMAKE_INSTALL_DATADIR}
+ )
+ install(
+ DIRECTORY install_tree/${_dir}/
+ DESTINATION ${_dir}
+ USE_SOURCE_PERMISSIONS
+ )
+ endforeach()
+ ```
+
+- Depending on how you find external dependencies (`find_package()`, vcpkg,
+ pkg-config...), CMake may end up outputting an executable that relies on a
+ DLL installed system-wide. To correctly install the DLL alongside the
+ executable you have to specify a regex as follows:
+
+ ```cmake
+ install(
+ TARGETS my_executable
+ RUNTIME_DEPENDENCIES
+ PRE_EXCLUDE_REGEXES ".*"
+ PRE_INCLUDE_REGEXES "tinyxml2"
+ )
+ ```
+
+ CMake will scan the executable at install time and copy all the required DLLs
+ that match the second regex. If no regex is specified CMake will also copy OS
+ DLLs like `libc` or `msvcrt`, which usually isn't the desired behavior.
+
+- Using interface targets to set include directories can be finicky. Not only
+ do you have to use generator expressions to conditionally use different paths
+ depending on whether the targets are installed, but CMake can get confused on
+ which options to pass to the compiler. I spent hours trying to get CMake to
+ use `-I` rather than `-isystem` for include directories (for some reason GCC
+ would ignore `-isystem` completely). I eventually gave up and just set the
+ include directories manually for each target, and for some reason CMake
+ actually started passing `-I` instead of `-isystem` to GCC.
+
+- When using CPack with NSIS, all `CPACK_NSIS_*` variables are passed to NSIS
+ verbatim, i.e. without the usual slash-to-backslash path conversion that
+ CMake does on Windows. Most Windows programs accept paths with slashes
+ without issue, unfortunately the NSIS builder is not one of those. To add
+ insult to injury, CMake doesn't even escape backslashes by default when
+ quoting strings in the generated CPack config file! So you have to convert
+ the paths manually *and* tell CMake to enable escaping by setting
+ `CPACK_VERBATIM_VARIABLES`, like this:
+
+ ```cmake
+ set(CPACK_VERBATIM_VARIABLES ON)
+ foreach(
+ _var IN ITEMS
+ CPACK_NSIS_MUI_ICON
+ CPACK_NSIS_MUI_UNIICON
+ CPACK_NSIS_MUI_HEADERIMAGE
+ CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP
+ CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP
+ )
+ cmake_path(NATIVE_PATH ${_var} ${_var})
+ endforeach()
+ ```
+
+- Not a CMake/CPack bug per se, but NSIS is picky about the banner and header
+ images shown in generated installers. They must be Windows BMP version 3
+ files with no alpha channel, no compression and no metadata. They can either
+ be 24-bit RGB or indexed, though it's common to use indexed colors to save
+ space.
+
+-----------------------------------------
+_Last updated on 2021-11-28 by spicyjpeg_
diff --git a/examples/io/system573/CMakeLists.txt b/examples/io/system573/CMakeLists.txt
new file mode 100644
index 0000000..34c9153
--- /dev/null
+++ b/examples/io/system573/CMakeLists.txt
@@ -0,0 +1,27 @@
+# PSn00bSDK example CMake script
+# (C) 2021 spicyjpeg - MPL licensed
+
+cmake_minimum_required(VERSION 3.21)
+
+if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
+ set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
+endif()
+
+project(
+ system573
+ LANGUAGES C ASM
+ VERSION 1.0.0
+ DESCRIPTION "PSn00bSDK Konami System 573 example"
+ HOMEPAGE_URL "http://lameguy64.net/?page=psn00bsdk"
+)
+
+file(GLOB _sources *.c *.s)
+psn00bsdk_add_executable(system573 STATIC ${_sources})
+psn00bsdk_add_cd_image(system573_iso system573 iso.xml DEPENDS system573)
+
+install(
+ FILES
+ ${PROJECT_BINARY_DIR}/system573.bin
+ ${PROJECT_BINARY_DIR}/system573.cue
+ TYPE BIN
+)
diff --git a/examples/io/system573/iso.xml b/examples/io/system573/iso.xml
new file mode 100644
index 0000000..09b4d85
--- /dev/null
+++ b/examples/io/system573/iso.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
diff --git a/examples/io/system573/main.c b/examples/io/system573/main.c
new file mode 100644
index 0000000..67a98da
--- /dev/null
+++ b/examples/io/system573/main.c
@@ -0,0 +1,371 @@
+/*
+ * PSn00bSDK Konami System 573 example
+ * (C) 2021 spicyjpeg - MPL licensed
+ *
+ * This is a minimal example demonstrating how to target the Konami System 573
+ * using PSn00bSDK. The System 573 is a PS1-based arcade motherboard that
+ * powered various Konami arcade games throughout the late 1990s, most notably
+ * Dance Dance Revolution and other Bemani rhythm games. It came in several
+ * configurations, with slightly different I/O connectors depending on the game
+ * and two optional add-on modules (known as the "analog I/O" and "digital I/O"
+ * boards respectively) providing light control outputs and, in the case of the
+ * digital I/O board, MP3 audio playback.
+ *
+ * Unlike other arcade systems based on PS1 hardware, the 573 is mostly
+ * identical to a regular PS1, with almost all custom extensions mapped into
+ * the expansion port region at 0x1f000000. The major differences are:
+ *
+ * - RAM is 4 MB instead of 2, and VRAM is 2 MB instead of 1. It is recommended
+ * *not* to use the additional memory to preserve PS1 compatibility.
+ *
+ * - The CD drive is replaced by a standard IDE/ATAPI drive (which most of the
+ * time is going to be an aftermarket DVD drive, as the original drives the
+ * system shipped with were prone to failure and couldn't read CD-Rs). This
+ * also means the 573 has no support at all for XA audio playback, as XA is
+ * not part of the CD-ROM specification implemented by IDE drives. CD audio
+ * is supported by most IDE drives, but 573 units with the digital I/O board
+ * installed have the 4-pin audio cable plugged into that instead of the
+ * drive. The IDE bus is connected to IRQ10 and DMA5 (expansion port) instead
+ * of IRQ2 and DMA3, which go unused.
+ *
+ * - The BIOS seems to have most file I/O APIs removed and exposes no functions
+ * whatsoever for accessing the IDE drive or the filesystem on the disc. The
+ * launcher/shell is completely different from Sony's shell and is capable of
+ * loading an executable from the CD drive, a PCMCIA memory-mapped flash card
+ * or the internal 16 MB flash memory.
+ *
+ * - The SPI controller bus seems to be left unconnected. Inputs are routed to
+ * a JAMMA PCB edge connector and handled through two custom/relabeled chips,
+ * which expose the inputs as memory-mapped registers. There is also a JVS
+ * port (i.e. RS-485 serial bus wired to a USB-A connector, commonly used for
+ * daisy-chaining peripherals in arcade cabinets) managed by a
+ * microcontroller.
+ *
+ * - There is a "security cartridge" slot, which breaks out the serial port as
+ * well as several GPIO pins. All security cartridge communication and DRM is
+ * handled by games rather than by the BIOS, so a security cartridge is *not*
+ * required to boot homebrew. Each game came with a different cartridge type;
+ * many of them expose the serial port or provide additional game-specific
+ * I/O connectors.
+ *
+ * Currently the only publicly available documentation for the custom registers
+ * is the System 573 MAME driver. Also keep in mind that the psxcd library does
+ * not yet support IDE drives, so the 573's drive can only be accessed by
+ * writing a custom ATAPI driver and ISO9660 parser (which is out of the scope
+ * of this example).
+ *
+ * https://github.com/mamedev/mame/blob/master/src/mame/drivers/ksys573.cpp
+ * https://github.com/mamedev/mame/blob/master/src/mame/machine/k573dio.cpp
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* Register definitions */
+
+#define EXP1_ADDR *((volatile uint32_t *) 0x1f801000)
+#define EXP1_CTRL *((volatile uint32_t *) 0x1f801008)
+
+#define K573_IN0 *((volatile uint16_t *) 0x1f400000)
+#define K573_IN1_L *((volatile uint16_t *) 0x1f400004)
+#define K573_IN1_H *((volatile uint16_t *) 0x1f400006)
+#define K573_IN2 *((volatile uint16_t *) 0x1f400008)
+#define K573_IN3_L *((volatile uint16_t *) 0x1f40000c)
+#define K573_IN3_H *((volatile uint16_t *) 0x1f40000e)
+#define K573_BANK *((volatile uint16_t *) 0x1f500000)
+#define K573_WATCHDOG *((volatile uint16_t *) 0x1f5c0000)
+
+#define K573_IDE_CS0 ((volatile uint16_t *) 0x1f480000)
+#define K573_IDE_CS1 ((volatile uint16_t *) 0x1f4c0000)
+#define K573_RTC ((volatile uint16_t *) 0x1f620000)
+#define K573_IO_BOARD ((volatile uint16_t *) 0x1f640000)
+
+typedef enum {
+ ANALOG_IO_LIGHTS0 = 0x20,
+ ANALOG_IO_LIGHTS1 = 0x22,
+ ANALOG_IO_LIGHTS2 = 0x24,
+ ANALOG_IO_LIGHTS3 = 0x26,
+
+ // The digital I/O board has a lot more registers than these, but there
+ // seems to be no DIGITAL_IO_LIGHTS6 register. WTF
+ DIGITAL_IO_LIGHTS1 = 0x70,
+ DIGITAL_IO_LIGHTS0 = 0x71,
+ DIGITAL_IO_LIGHTS3 = 0x72,
+ DIGITAL_IO_LIGHTS7 = 0x73,
+ DIGITAL_IO_LIGHTS4 = 0x7d,
+ DIGITAL_IO_LIGHTS5 = 0x7e,
+ DIGITAL_IO_LIGHTS2 = 0x7f
+} IO_BOARD_REG;
+
+// The 573's real-time clock chip is an M48T58, which behaves like a standard
+// 8 KB battery-backed SRAM with a bunch of special registers. Official games
+// store highscores and settings in RTC RAM.
+typedef enum {
+ RTC_CTRL = 0x1ff8,
+ RTC_SECONDS = 0x1ff9,
+ RTC_MINUTES = 0x1ffa,
+ RTC_HOURS = 0x1ffb,
+ RTC_DAY_OF_WEEK = 0x1ffc,
+ RTC_DAY_OF_MONTH = 0x1ffd,
+ RTC_MONTH = 0x1ffe,
+ RTC_YEAR = 0x1fff
+} RTC_REG;
+
+#define btoi(x) ((((x) >> 4) & 0xf) * 10 + ((x) & 0xf))
+
+/* Display/GPU context utilities */
+
+#define SCREEN_XRES 320
+#define SCREEN_YRES 240
+
+#define BGCOLOR_R 48
+#define BGCOLOR_G 24
+#define BGCOLOR_B 0
+
+typedef struct {
+ DISPENV disp;
+ DRAWENV draw;
+} DB;
+
+typedef struct {
+ DB db[2];
+ uint32_t db_active;
+} CONTEXT;
+
+void init_context(CONTEXT *ctx) {
+ DB *db;
+
+ ResetGraph(0);
+ ctx->db_active = 0;
+
+ db = &(ctx->db[0]);
+ SetDefDispEnv(&(db->disp), 0, 0, SCREEN_XRES, SCREEN_YRES);
+ SetDefDrawEnv(&(db->draw), SCREEN_XRES, 0, SCREEN_XRES, SCREEN_YRES);
+ setRGB0(&(db->draw), BGCOLOR_R, BGCOLOR_G, BGCOLOR_B);
+ db->draw.isbg = 1;
+ db->draw.dtd = 1;
+
+ db = &(ctx->db[1]);
+ SetDefDispEnv(&(db->disp), SCREEN_XRES, 0, SCREEN_XRES, SCREEN_YRES);
+ SetDefDrawEnv(&(db->draw), 0, 0, SCREEN_XRES, SCREEN_YRES);
+ setRGB0(&(db->draw), BGCOLOR_R, BGCOLOR_G, BGCOLOR_B);
+ db->draw.isbg = 1;
+ db->draw.dtd = 1;
+
+ PutDrawEnv(&(db->draw));
+ //PutDispEnv(&(db->disp));
+
+ // Create a text stream at the top of the screen.
+ FntLoad(960, 0);
+ FntOpen(8, 16, 304, 208, 2, 512);
+}
+
+void display(CONTEXT *ctx) {
+ DB *db;
+
+ DrawSync(0);
+ VSync(0);
+ ctx->db_active ^= 1;
+
+ db = &(ctx->db[ctx->db_active]);
+ PutDrawEnv(&(db->draw));
+ PutDispEnv(&(db->disp));
+ SetDispMask(1);
+}
+
+/* Input polling utilities */
+
+typedef struct {
+ uint8_t p1_joy, p1_btn;
+ uint8_t p2_joy, p2_btn;
+ uint8_t coin, dip_sw;
+} JAMMA_INPUTS;
+
+void get_jamma_inputs(JAMMA_INPUTS *output) {
+ uint16_t in1l = K573_IN1_L;
+ uint16_t in1h = K573_IN1_H;
+ uint16_t in2 = K573_IN2;
+ uint16_t in3l = K573_IN3_L;
+ uint16_t in3h = K573_IN3_H;
+ uint8_t p1_btn, p2_btn, coin;
+
+ // Rearrange the bits read from the input register into something that's
+ // easier to parse and display. Refer to MAME for information on what each
+ // bit in the IN* registers does.
+ p1_btn = ((in2 >> 15) & 0x0001); // Bit 0 = start button
+ p1_btn |= ((in2 >> 8) & 0x0007) << 1; // Bit 1-3 = buttons 1-3
+ p1_btn |= ((in3l >> 8) & 0x0003) << 4; // Bit 4-5 = buttons 4-5
+ p1_btn |= ((in3l >> 11) & 0x0001) << 6; // Bit 6 = button 6
+ p2_btn = ((in2 >> 7) & 0x0001); // Bit 0 = start button
+ p2_btn |= ((in2 >> 4) & 0x0007) << 1; // Bit 1-3 = buttons 1-3
+ p2_btn |= ((in3h >> 8) & 0x0003) << 4; // Bit 4-5 = buttons 4-5
+ p2_btn |= ((in3h >> 11) & 0x0001) << 6; // Bit 6 = button 6
+ coin = ((in1h >> 8) & 0x0003); // Bit 0-1 = coin switches
+ coin |= ((in1h >> 12) & 0x0001) << 2; // Bit 2 = service button
+ coin |= ((in3l >> 10) & 0x0001) << 3; // Bit 3 = test button
+ coin |= ((in1h >> 10) & 0x0003) << 4; // Bit 4-5 = PCMCIA cards
+
+ output->p1_joy = (in2 >> 8) & 0x000f;
+ output->p1_btn = p1_btn;
+ output->p2_joy = in2 & 0x000f;
+ output->p2_btn = p2_btn;
+ output->coin = coin;
+ output->dip_sw = in1l & 0x000f;
+}
+
+/* I/O board (light control) utilities */
+
+// This function controls light outputs on analog I/O boards.
+void set_lights_analog(uint32_t lights) {
+ uint32_t bits;
+
+ bits = (lights & 0x01010101) << 7; // Lamp 0 -> bit 7
+ bits |= (lights & 0x02020202) << 5; // Lamp 1 -> bit 6
+ bits |= (lights & 0x04040404) >> 1; // Lamp 2 -> bit 1
+ bits |= (lights & 0x08080808) >> 3; // Lamp 3 -> bit 0
+ bits |= (lights & 0x10101010) << 1; // Lamp 4 -> bit 5
+ bits |= (lights & 0x20202020) >> 1; // Lamp 5 -> bit 4
+ bits |= (lights & 0x40404040) >> 3; // Lamp 6 -> bit 3
+ bits |= (lights & 0x80808080) >> 5; // Lamp 7 -> bit 2
+
+ K573_IO_BOARD[ANALOG_IO_LIGHTS0] = (bits) & 0xff;
+ K573_IO_BOARD[ANALOG_IO_LIGHTS1] = (bits >> 8) & 0xff;
+ K573_IO_BOARD[ANALOG_IO_LIGHTS2] = (bits >> 16) & 0xff;
+ K573_IO_BOARD[ANALOG_IO_LIGHTS3] = (bits >> 24) & 0xff;
+}
+
+// This function controls light outputs on digital I/O boards (i.e. the ones
+// that include MP3 playback hardware in addition to the light control).
+// TODO: test this on real hardware -- it might not work if lights are handled
+// by the board's FPGA, which requires a binary blob...
+void set_lights_digital(uint32_t lights) {
+ uint32_t bits;
+
+ bits = (lights & 0x11111111); // Lamp 0 -> bit 0
+ bits |= (lights & 0x22222222) << 1; // Lamp 1 -> bit 2
+ bits |= (lights & 0x44444444) << 1; // Lamp 2 -> bit 3
+ bits |= (lights & 0x88888888) >> 2; // Lamp 3 -> bit 1
+
+ K573_IO_BOARD[DIGITAL_IO_LIGHTS0] = ((bits) & 0xf) << 12;
+ K573_IO_BOARD[DIGITAL_IO_LIGHTS1] = ((bits >> 4) & 0xf) << 12;
+ K573_IO_BOARD[DIGITAL_IO_LIGHTS2] = ((bits >> 8) & 0xf) << 12;
+ K573_IO_BOARD[DIGITAL_IO_LIGHTS3] = ((bits >> 12) & 0xf) << 12;
+ K573_IO_BOARD[DIGITAL_IO_LIGHTS4] = ((bits >> 16) & 0xf) << 12;
+ K573_IO_BOARD[DIGITAL_IO_LIGHTS5] = ((bits >> 20) & 0xf) << 12;
+ //K573_IO_BOARD[DIGITAL_IO_LIGHTS6] = ((bits >> 24) & 0xf) << 12;
+ K573_IO_BOARD[DIGITAL_IO_LIGHTS7] = ((bits >> 28) & 0xf) << 12;
+}
+
+/* Main */
+
+static CONTEXT ctx;
+
+#define SHOW_STATUS(...) { FntPrint(-1, __VA_ARGS__); FntFlush(-1); display(&ctx); }
+#define SHOW_ERROR(...) { SHOW_STATUS(__VA_ARGS__); while (1) __asm__("nop"); }
+
+int main(int argc, const char* argv[]) {
+ // Reinitialize the heap and relocate the stack to allow the 573's full 4
+ // MB of RAM to be used. This isn't strictly required; executables designed
+ // for 2 MB of RAM will also run fine on the 573 (obviously).
+ // FIXME: this seems to be broken currently
+ //__asm__ volatile("li $sp, 0x803fffe0");
+ //_mem_init(0x400000, 0x20000);
+
+ EXP1_ADDR = 0x1f000000;
+ EXP1_CTRL = 0x24173f47; // 573 BIOS uses this value
+ K573_WATCHDOG = 0;
+
+ init_context(&ctx);
+
+ // Determine whether we are running on a 573 by fetching the version string
+ // from the BIOS.
+ const char *const version = (const char *const) GetSystemInfo(0x02);
+ //if (strncmp(version, "Konami OS", 9))
+ //SHOW_ERROR("ERROR: NOT RUNNING ON A SYSTEM 573!\n\n[%s]\n", version);
+
+ uint32_t counter = 0;
+ uint8_t last_joystick = 0xff;
+ uint8_t last_buttons = 0xff;
+ uint32_t current_light = 0;
+ uint32_t is_digital = 0;
+
+ while (1) {
+ FntPrint(-1, "COUNTER=%d\n", counter++);
+
+ JAMMA_INPUTS inputs;
+ get_jamma_inputs(&inputs);
+
+ FntPrint(-1, "\nJAMMA INPUTS:\n");
+ FntPrint(-1, " P1 JOYSTICK =%04@\n", inputs.p1_joy);
+ FntPrint(-1, " P1 BUTTONS =%07@\n", inputs.p1_btn);
+ FntPrint(-1, " P2 JOYSTICK =%04@\n", inputs.p2_joy);
+ FntPrint(-1, " P2 BUTTONS =%07@\n", inputs.p2_btn);
+ FntPrint(-1, " COIN/SERVICE=%04@\n", inputs.coin);
+ FntPrint(-1, " DIP SWITCHES=%04@\n", inputs.dip_sw);
+
+ FntPrint(-1, "\nCABINET LIGHTS:\n");
+ FntPrint(-1, " BOARD=%s I/O\n", is_digital ? "DIGITAL" : "ANALOG");
+ FntPrint(-1, " LIGHT=%d\n\n", current_light);
+ FntPrint(-1, " [START] CHANGE BOARD TYPE\n");
+ FntPrint(-1, " [LEFT/RIGHT] SELECT LIGHT TO TEST\n");
+
+ // Request the current date/time from the RTC and display it.
+ K573_RTC[RTC_CTRL] |= 0x40;
+ FntPrint(-1, "\nRTC:\n");
+ FntPrint(
+ -1,
+ " %02d-%02d-%02d %02d:%02d:%02d\n",
+ btoi(K573_RTC[RTC_YEAR]),
+ btoi(K573_RTC[RTC_MONTH]),
+ btoi(K573_RTC[RTC_DAY_OF_MONTH] & 0x3f),
+ btoi(K573_RTC[RTC_HOURS]),
+ btoi(K573_RTC[RTC_MINUTES]),
+ btoi(K573_RTC[RTC_SECONDS] & 0x7f)
+ );
+
+ FntPrint(-1, "\nSYSTEM:\n");
+ FntPrint(-1, " KERNEL=%s\n", version);
+ FntPrint(-1, " PCMCIA=%02@\n", inputs.coin >> 4);
+
+ FntFlush(-1);
+ display(&ctx);
+
+ // Reset the watchdog. This must be done at least once per frame to
+ // prevent the 573 from rebooting.
+ K573_WATCHDOG = 0;
+
+ if (is_digital)
+ set_lights_digital(1 << current_light);
+ else
+ set_lights_analog(1 << current_light);
+
+ // Handle inputs.
+ if ((last_joystick & 0x01) && !(inputs.p1_joy & 0x01)) // Left
+ current_light--;
+ if ((last_joystick & 0x02) && !(inputs.p1_joy & 0x02)) // Right
+ current_light++;
+ if ((last_buttons & 0x02) && !(inputs.p1_btn & 0x02)) // Button 1
+ current_light--;
+ if ((last_buttons & 0x04) && !(inputs.p1_btn & 0x04)) // Button 2
+ current_light++;
+ if ((last_buttons & 0x01) && !(inputs.p1_btn & 0x01)) { // Start
+ is_digital = !is_digital;
+ if (is_digital)
+ set_lights_analog(0);
+ else
+ set_lights_digital(0);
+ }
+
+ current_light %= 32;
+ last_joystick = inputs.p1_joy;
+ last_buttons = inputs.p1_btn;
+ }
+
+ return 0;
+}
--
cgit v1.2.3