diff options
| author | John "Lameguy" Wilbert Villamor <lameguy64@gmail.com> | 2022-01-18 08:31:14 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-01-18 08:31:14 +0800 |
| commit | 05d44488bd5587786f4bd0286fc0f555c79aa46a (patch) | |
| tree | 5740f396d10a9580c3a39ca536544436898ff1b6 /doc | |
| parent | 08de895e8582dbc70b639ae5f511ab9ebfb4d68a (diff) | |
| parent | e9475e283a82665fe6c19bebc3318b5084f15a2e (diff) | |
| download | psn00bsdk-05d44488bd5587786f4bd0286fc0f555c79aa46a.tar.gz | |
Merge pull request #44 from spicyjpeg/actions
GitHub Actions CI, psxcd and libc fixes, new examples
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/cmake_reference.md | 77 | ||||
| -rw-r--r-- | doc/dev notes.txt | 249 | ||||
| -rw-r--r-- | doc/dev_notes.md | 280 | ||||
| -rw-r--r-- | doc/installation.md | 153 | ||||
| -rw-r--r-- | doc/known_bugs.md | 52 | ||||
| -rw-r--r-- | doc/toolchain.md | 224 |
6 files changed, 762 insertions, 273 deletions
diff --git a/doc/cmake_reference.md b/doc/cmake_reference.md index 3b586ab..3c89da3 100644 --- a/doc/cmake_reference.md +++ b/doc/cmake_reference.md @@ -10,15 +10,44 @@ can be done on the command line (`-DCMAKE_TOOLCHAIN_FILE=...`), in `CMakeLists.txt` (before calling `project()`) or using a [preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html). -It's recommended to put this snippet in `CMakeLists.txt` to automatically set -the toolchain file according to the `PSN00BSDK_LIBS` environment variable: - -```cmake -if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS}) - set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake) -endif() +It's suggested to have a default preset that sets `CMAKE_TOOLCHAIN_FILE` to +`$env{PSN00BSDK_LIBS}/cmake/sdk.cmake`, so the `PSN00BSDK_LIBS` environment +variable (used by former PSn00bSDK versions) is respected. Such a preset can be +created by placing a `CMakePresets.json` file in the project's root with the +following contents: + +```json +{ + "version": 2, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "displayName": "Default configuration", + "description": "Use this preset to build the project using PSn00bSDK.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": "$env{PSN00BSDK_LIBS}/cmake/sdk.cmake", + "PSN00BSDK_TC": "", + "PSN00BSDK_TARGET": "mipsel-none-elf" + } + } + ] +} ``` +To avoid having to pass variables to CMake each time the project is built, a +second presets file named `CMakeUserPresets.json` can be created and populated +with hardcoded values in the `cacheVariables` section. This file can be kept +private (e.g. by adding it to `.gitignore`); CMake will automatically load +presets from it instead of `CMakePresets.json` if it exists. + See the [template](../template/CMakeLists.txt) for an example CMake script showing how to build a simple project. @@ -130,28 +159,24 @@ build script, from the CMake command line when configuring the project - `PSN00BSDK_TARGET` (`STRING`) The GCC toolchain's target triplet. PSn00bSDK assumes the toolchain targets - `mipsel-unknown-elf` by default, however this can be changed to e.g. use a - MIPS toolchain that was compiled for a slightly-different-but-equivalent - target. + `mipsel-none-elf` by default, however this can be changed to e.g. use a MIPS + toolchain that was compiled for a slightly-different-but-equivalent target. The following GCC target triplets have been confirmed to work with PSn00bSDK: - - `mipsel-unknown-elf` - `mipsel-none-elf` + - `mipsel-unknown-elf` + - ~~`mipsel-linux-gnu`~~ (has issues with linking) - `PSN00BSDK_TC` (`PATH`) - Path to the GCC toolchain's installation prefix/directory. By default this is - initialized to the value of the `PSN00BSDK_TC` environment variable (if set). - Note that modifying the environment variable after the project has been - configured will *NOT* update this cache entry unless the project's cache is - cleared manually. - - If not set, CMake will attempt to find the toolchain in the `PATH` - environment variable and store its path in this variable (so the search does - not have to be repeated). + Path to the GCC toolchain's installation prefix/directory. If not set, CMake + will attempt to find the toolchain in the `PATH` environment variable and + store its path in the project's variable cache (so the search does not have + to be repeated). It is recommended to add the toolchain's `bin` subfolder to + `PATH` rather than setting this variable. - **IMPORTANT**: if the toolchain's target is not `mipsel-unknown-elf`, + **IMPORTANT**: if the toolchain's target is not `mipsel-none-elf`, `PSN00BSDK_TARGET` must be set regardless of whether or not `PSN00BSDK_TC` is also set. @@ -182,9 +207,13 @@ the build script. ## Read-only variables -- `PSN00BSDK_VERSION` +- `PSN00BSDK_VERSION`, `PSN00BSDK_BUILD_DATE`, `PSN00BSDK_GIT_TAG`, + `PSN00BSDK_GIT_COMMIT` - The SDK's version number (`major.minor.patch`). + These variables are loaded from `lib/libpsn00b/build.json` and contain + information about the SDK's version. Note that `PSN00BSDK_GIT_TAG` and + `PSN00BSDK_GIT_COMMIT` are not populated by default when building PSn00bSDK + manually from source, so they might be empty strings. - `PSN00BSDK_TOOLS`, `PSN00BSDK_INCLUDE`, `PSN00BSDK_LDSCRIPTS` @@ -204,4 +233,4 @@ the build script. LZP archives as part of the build pipeline. ----------------------------------------- -_Last updated on 2021-09-27 by spicyjpeg_ +_Last updated on 2021-12-29 by spicyjpeg_ diff --git a/doc/dev notes.txt b/doc/dev notes.txt deleted file mode 100644 index 3aa2db5..0000000 --- a/doc/dev notes.txt +++ /dev/null @@ -1,249 +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 (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. - -* 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/doc/installation.md b/doc/installation.md new file mode 100644 index 0000000..1646653 --- /dev/null +++ b/doc/installation.md @@ -0,0 +1,153 @@ + +# Getting started with PSn00bSDK + +## Building and installing + +The instructions below are for Windows and Linux. Building on macOS hasn't been +tested extensively yet, however it should work once the GCC toolchain is built +and installed properly. + +1. Install prerequisites and a host compiler toolchain. On Linux (most distros) + install the following packages from your distro's package manager: + + - `git` + - `build-essential`, `base-devel` or similar + - `make` or `ninja-build` + - `cmake` (3.20+ is required, download it from + [here](https://cmake.org/download) if your package manager only provides + older versions) + + On Windows you can obtain these dependencies by installing + [MSys2](https://www.msys2.org), opening the "MSys2 MSYS" shell and running: + + ```bash + pacman -Syu git mingw-w64-x86_64-make mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc + ``` + + If you are prompted to close the shell, you may have to reopen it afterwards + and rerun the command to finish installation. + **Do not use the MSys2 shell for the next steps**, use a regular command + prompt or PowerShell instead. + + Add these directories (replace `C:\msys64` if you installed MSys2 to a + different location) to the `PATH` environment variable using System + Properties: + + - `C:\msys64\mingw64\bin` + - `C:\msys64\usr\bin` + +2. Download a precompiled copy of the GCC toolchain for `mipsel-none-elf` from + the releases page and extract it into one of the directories listed in + step 3. If you want to build the toolchain yourself, see + [toolchain.md](toolchain.md). + + **NOTE**: PSn00bSDK is also compatible with toolchains that target + `mipsel-unknown-elf`. If you already have such a toolchain, you can use it + by passing `-DPSN00BSDK_TARGET=mipsel-unknown-elf` to CMake when configuring + the SDK (see step 5). + +3. If you chose a non-standard install location for the toolchain, add the + `bin` subfolder (inside the top-level toolchain directory) to the `PATH` + environment variable. This step is unnecessary if you installed/extracted + the toolchain into any of these directories: + + - `C:\Program Files\mipsel-none-elf` + - `C:\Program Files (x86)\mipsel-none-elf` + - `C:\mipsel-none-elf` + - `/usr/local/mipsel-none-elf` + - `/usr/mipsel-none-elf` + - `/opt/mipsel-none-elf` + +4. Clone the PSn00bSDK repo, then run the following command from the PSn00bSDK + repository to download additional dependencies: + + ```bash + git submodule update --init --recursive + ``` + +5. Compile the libraries, tools and examples using CMake: + + ```bash + cmake --preset default . + cmake --build ./build + ``` + + If you want to install the SDK to a custom location rather than the default + one (`C:\Program Files\PSn00bSDK` or `/usr/local` depending on your OS), add + `--install-prefix <INSTALL_PATH>` to the first command. Remember to add + `-DPSN00BSDK_TARGET=mipsel-unknown-elf` if necessary. + + **NOTE**: Ninja is used by default to build the SDK. If you can't get it to + work or don't have it installed, pass `-G "Unix Makefiles"` (or + `-G "MSYS Makefiles"` on Windows) to the first command to build using `make` + instead. + +6. Install the SDK to the path you chose (add `sudo` or run it from a command + prompt with admin privileges if necessary): + + ```bash + cmake --install ./build + ``` + + This will create and populate the following directories: + + - `<INSTALL_PATH>/bin` + - `<INSTALL_PATH>/lib/libpsn00b` + - `<INSTALL_PATH>/share/psn00bsdk` + +7. You may optionally set the `PSN00BSDK_LIBS` environment variable to point to + the `lib/libpsn00b` subfolder inside the install directory. You might also + want to add the `bin` folder to `PATH` if it's not listed already. + +Although not strictly required, you'll probably want to install a PS1 emulator +with debugging capabilities such as [no$psx](https://problemkaputt.de/psx.htm) +(Windows only), [DuckStation](https://github.com/stenzek/duckstation) or +[pcsx-redux](https://github.com/grumpycoders/pcsx-redux). +**Avoid ePSXe and anything based on MAME** as they are inaccurate. + +## Building installer packages + +CPack can be used to build NSIS-based installers, DEB/RPM packages and zipped +releases that include built SDK libraries, headers as well as the GCC toolchain. +Distributing prebuilt releases is however discouraged since PSn00bSDK is still +far from being feature-complete. + +1. Follow steps 1-4 above to set up the toolchain, then install + [NSIS](https://nsis.sourceforge.io/Download) on Windows or `dpkg` and `rpm` + on Linux. + +2. Run the following commands from the PSn00bSDK directory (pass the + appropriate options to the first command if necessary): + + ```bash + cmake --preset package . + cmake --build ./build -t package + ``` + + All built packages will be copied to the `build/packages` folder. + +## Creating a project + +1. Copy the contents of `<INSTALL_PATH>/share/psn00bsdk/template` (or the + `template` folder within the repo) to your new project's root directory. + +2. If you haven't set the `PSN00BSDK_LIBS` environment variable previously or + if you want to use a different PSn00bSDK installation for the project, edit + `CMakePresets.json` to set the path you installed the SDK to. See the + [setup guide](cmake_reference.md#setup) for details. + +3. Configure and build the template by running: + + ```bash + cmake --preset default . + cmake --build ./build + ``` + + If you did everything correctly there should be a `template.bin` CD image in + the `build` folder. Test it in an emulator to ensure it works. + +The toolchain script defines a few CMake macros to create PS1 executables, DLLs +and CD images. See the [reference](cmake_reference.md) for details. + +----------------------------------------- +_Last updated on 2021-12-29 by spicyjpeg_ diff --git a/doc/known_bugs.md b/doc/known_bugs.md new file mode 100644 index 0000000..2af9e3f --- /dev/null +++ b/doc/known_bugs.md @@ -0,0 +1,52 @@ + +# Known PSn00bSDK bugs + +This is an incomplete list of things that are currently broken (or not behaving +as they should, or untested on real hardware) and haven't yet been fixed. + +## Libraries + +`psxspu`: + +- Calls to `SpuSetTransferMode()` are ignored. SPU transfers are always + performed using DMA, which imposes limitations such as the data length having + to be a multiple of 64 bytes. + +`psxetc`: + +- `DL_LoadSymbolMapFromFile()`, `DL_LoadDLLFromFile()` and `dlopen()` have been + disabled due to bugs in the BIOS file APIs. The dynamic linker can still be + used by loading DLL binaries into RAM manually and calling `DL_CreateDLL()` + on them (see the `system/dynlink` example). + +## Tools + +- The `mkpsxiso` submodule is temporarily set to point to a fork of `mkpsxiso` + with bugfixed CMake scripts (the main repo is broken to the point it fails to + build). There is [another fork](https://github.com/CookiePLMonster/mkpsxiso) + which is currently work-in-progress and includes more fixes as well as a tool + to dump existing CD images: PSn00bSDK will switch back to the main `mkpsxiso` + repo once the changes get upstreamed. + +## Examples + +- `cdrom/cdxa` and `sound/spustream` demonstrate how to stream an audio file + from CD-ROM. Such a file isn't provided however, as PSn00bSDK does not yet + come with the tooling required for transcoding audio from a source file. In + order to run these examples you'll have to provide your own audio files, + convert them and build the CD image manually. + +- `demos/n00bdemo` suffers from flickering on real hardware, especially when + masking/stencil buffering is used. + +- `graphics/render2tex` gets stuck after initialization on real hardware. + +- `io/pads` seems to work on real hardware, but fails to automatically enable + analog mode on DualShock controllers. This example needs more testing with + official and unofficial controllers. + +- `io/system573` hasn't been tested on a real Konami System 573. It runs on + MAME, however MAME's System 573 emulation is *very* inaccurate. + +----------------------------------------- +_Last updated on 2021-12-30 by spicyjpeg_ diff --git a/doc/toolchain.md b/doc/toolchain.md new file mode 100644 index 0000000..8e28c24 --- /dev/null +++ b/doc/toolchain.md @@ -0,0 +1,224 @@ + +# Building the GCC toolchain + +If you wish to build the toolchain yourself, beware that this process can get +pretty tedious if your machine is not fairly recent. Ensure you have at least a +quad-core processor and 4 GB of free space before continuing. + +You'll need a Linux environment, even if you want to build a Windows toolchain +(as GCC is basically impossible to build under Windows but can be cross-compiled +via MinGW). Due to how the GCC build process works, you'll have to build a Linux +version of the toolchain first to be able to compile it for Windows. This +basically means you will have to build the whole toolchain twice if you want to +target Windows. + +These instructions are for Debian/Ubuntu, however it should be relatively easy +to follow them if you are using another distro. If you do not have access to a +Linux system already, consider spinning up a VM (a headless Debian or Ubuntu +Server install is recommended) or using WSL, whose setup is out of the scope of +this guide. + +## Choosing a GCC version + +PSn00bSDK *should* work with any GCC version. In most cases you'll want to get +the latest stable release of GCC and binutils. If for some reason you are having +problems you may try building one of the following versions, which have been +tested extensively: + +- ~~GCC 7.4.0 with binutils 2.31~~ (the linker fails to build PS1 DLLs) +- GCC **11.1.0** with binutils **2.36** +- GCC **11.2.0** with binutils **2.37** + +If you wish to pick an older GCC release but don't know which binutils version +it requires, see [here](https://wiki.osdev.org/Cross-Compiler_Successful_Builds) +for a compatibility table. + +## Downloading GCC + +1. Run the following commands to install a host toolchain and prerequisites + (adapt them for non-Debian distros if necessary): + + ```bash + sudo apt update + sudo apt install -y build-essential make wget + ``` + +2. Create an empty directory to store build artifacts in. You'll be able to + delete it once the toolchain is installed. + +3. Download the GCC and binutils source packages from + [here](https://ftpmirror.gnu.org/gnu) and unzip them into the folder you + created, or run the following commands to do the same (replace `<VERSION>` + with the versions you chose): + + ```bash + wget https://ftpmirror.gnu.org/gnu/binutils/binutils-<VERSION>.tar.xz + wget https://ftpmirror.gnu.org/gnu/gcc/gcc-<VERSION>/gcc-<VERSION>.tar.xz + tar xvf binutils-<VERSION>.tar.xz + tar xvf gcc-<VERSION>.tar.xz + rm -f *.tar.xz + ``` + +4. From the extracted GCC directory run the `download_prerequisites` script to + download additional dependencies: + + ```bash + cd gcc-<VERSION> + ./contrib/download_prerequisites + ``` + +## Building binutils + +1. Go back to the folder you made earlier and create a new subdirectory to build + binutils in (don't create it inside the extracted binutils source directory). + Call it `binutils-build` or whatever. + +2. Run the binutils configuration script from that folder: + + ```bash + ../binutils-<VERSION>/configure \ + --prefix=/usr/local/mipsel-none-elf --target=mipsel-none-elf \ + --disable-docs --disable-nls --with-float=soft + ``` + + Replace `<VERSION>` as usual. If you don't want to install the toolchain into + `/usr/local/mipsel-none-elf` you can change the `--prefix` option. + +3. Compile and install binutils (this will take a few minutes to finish): + + ```bash + make -j 4 + sudo make install-strip + ``` + + Increase `-j 4` to speed up the build if your machine or VM has more than 4 + CPU cores. + + **NOTE**: if the build fails with a "`uint` undeclared" or similar error, try + editing the source file that caused the error and pasting this line at the + beginning: + + ```c + typedef unsigned int uint; + ``` + + Rerun `make` to resume the build after saving the file. + +## Building GCC + +The process is mostly the same as binutils, just with different configuration +options. + +1. Go back to the main directory and create an empty `gcc-build` (or whatever) + subfolder. + +2. Run the GCC configuration script from there: + + ```bash + ../gcc-<VERSION>/configure \ + --prefix=/usr/local/mipsel-none-elf --target=mipsel-none-elf \ + --disable-docs --disable-nls --disable-libada --disable-libssp \ + --disable-libquadmath --disable-libstdc++-v3 --with-float=soft \ + --enable-languages=c,c++ --with-gnu-as --with-gnu-ld + ``` + + If you previously set a custom installation path, remember to set it here as + well (it must be the same). + +3. Compile and install GCC (will take a long time, usually around half an hour): + + ```bash + make -j 4 + sudo make install-strip + ``` + + Increase `-j 4` to speed up the build if your machine or VM has more than 4 + threads. + +4. Add the toolchain to the `PATH` environment variable. This is required to + rebuild the toolchain for Windows (see below), but it will also allow + PSn00bSDK to find the toolchain if you installed it in a custom location. + + Edit the `.bashrc` or `.bash_profile` file in your home directory and add + this line at the end (replace the path with the install location you chose + earlier, but keep the `/bin` at the end): + + ```bash + export PATH=$PATH:/usr/local/mipsel-none-elf/bin + ``` + + Restart the shell by closing and reopening the terminal window or SSH + connection afterwards. + +## Rebuilding for Windows + +At this point you should be able to build and install PSn00bSDK on your Linux +system. The instructions below are for building a second copy of the toolchain +that runs on Windows. + +1. Install the MinGW host toolchain: + + ```bash + sudo apt install -y g++-mingw-w64-x86-64 + ``` + +2. Create two new `binutils-win` and `gcc-win` folders in the directory you + extracted/built everything in. + +3. From the `binutils-win` directory, rerun the binutils configuration script + with the following options (do not change the installation path): + + ```bash + ../binutils-<VERSION>/configure \ + --build=x86_64-linux-gnu --host=x86_64-w64-mingw32 \ + --prefix=/tmp/mipsel-none-elf --target=mipsel-none-elf \ + --disable-docs --disable-nls --with-float=soft + ``` + + Then build binutils again: + + ```bash + make -j 4 + make install-strip + ``` + +4. Do the same for GCC from the `gcc-win` directory: + + ```bash + ../gcc-<VERSION>/configure \ + --build=x86_64-linux-gnu --host=x86_64-w64-mingw32 \ + --prefix=/tmp/mipsel-none-elf --target=mipsel-none-elf \ + --disable-docs --disable-nls --disable-libada --disable-libssp \ + --disable-libquadmath --disable-libstdc++-v3 --with-float=soft \ + --enable-languages=c,c++ --with-gnu-as --with-gnu-ld + ``` + + And build it as usual: + + ```bash + make -j 4 + make install-strip + ``` + +5. Copy the entire `/tmp/mipsel-none-elf` directory over to your Windows + machine using VM shared folders, a network share, `scp` or whichever method + you prefer. It's recommended to put the toolchain in + `C:\Program Files\mipsel-none-elf` or `C:\mipsel-none-elf`. + +6. If you want to keep the toolchain in another location and/or use it from the + command line, add the `bin` subdirectory inside `mipsel-none-elf` to the + `PATH` environment variable (as you did on Linux) using System Properties. + +## Note regarding C++ support + +C++ support in PSn00bSDK, besides compile-time features like `constexpr`, only +goes as far as basic classes, namespaces and the ability to dynamically create +and delete class objects at any point of the program. The required dependencies +(which are just wrappers around `malloc()` and `free()`) are supplied by `libc`. + +Standard C++ libraries are not implemented and likely never going to be +implemented due to bloat concerns that it may introduce. Besides, the official +SDK lacks full C++ support as well. + +----------------------------------------- +_Last updated on 2021-11-23 by spicyjpeg_ |
