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 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). * 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. * 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.