aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorJohn "Lameguy" Wilbert Villamor <lameguy64@gmail.com>2022-01-18 08:31:14 +0800
committerGitHub <noreply@github.com>2022-01-18 08:31:14 +0800
commit05d44488bd5587786f4bd0286fc0f555c79aa46a (patch)
tree5740f396d10a9580c3a39ca536544436898ff1b6 /examples
parent08de895e8582dbc70b639ae5f511ab9ebfb4d68a (diff)
parente9475e283a82665fe6c19bebc3318b5084f15a2e (diff)
downloadpsn00bsdk-05d44488bd5587786f4bd0286fc0f555c79aa46a.tar.gz
Merge pull request #44 from spicyjpeg/actions
GitHub Actions CI, psxcd and libc fixes, new examples
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md82
-rw-r--r--examples/beginner/cppdemo/CMakeLists.txt4
-rw-r--r--examples/beginner/hello/CMakeLists.txt4
-rw-r--r--examples/cdrom/cdbrowse/CMakeLists.txt4
-rw-r--r--examples/cdrom/cdxa/CMakeLists.txt4
-rw-r--r--examples/cdrom/cdxa/main.c2
-rw-r--r--examples/demos/n00bdemo/CMakeLists.txt4
-rw-r--r--examples/graphics/balls/CMakeLists.txt4
-rw-r--r--examples/graphics/billboard/CMakeLists.txt4
-rw-r--r--examples/graphics/fpscam/CMakeLists.txt4
-rw-r--r--examples/graphics/gte/CMakeLists.txt4
-rw-r--r--examples/graphics/hdtv/CMakeLists.txt4
-rw-r--r--examples/graphics/render2tex/CMakeLists.txt4
-rw-r--r--examples/graphics/rgb24/CMakeLists.txt4
-rw-r--r--examples/graphics/tilesasm/CMakeLists.txt12
-rw-r--r--examples/graphics/tilesasm/data.s.template (renamed from examples/graphics/tilesasm/data.s)69
-rw-r--r--examples/io/pads/CMakeLists.txt4
-rw-r--r--examples/io/pads/main.c37
-rw-r--r--examples/io/pads/spi.c50
-rw-r--r--examples/io/pads/spi.h25
-rw-r--r--examples/io/system573/CMakeLists.txt23
-rw-r--r--examples/io/system573/iso.xml34
-rw-r--r--examples/io/system573/main.c371
-rw-r--r--examples/lowlevel/cartrom/CMakeLists.txt4
-rw-r--r--examples/readme.txt64
-rw-r--r--examples/sound/spustream/CMakeLists.txt25
-rw-r--r--examples/sound/spustream/convert_stream.py112
-rw-r--r--examples/sound/spustream/iso.xml29
-rw-r--r--examples/sound/spustream/main.c456
-rw-r--r--examples/sound/spustream/system.cnf4
-rw-r--r--examples/sound/vagsample/0proyt.h2
-rw-r--r--examples/sound/vagsample/CMakeLists.txt4
-rw-r--r--examples/sound/vagsample/threedeeffeggzz.h2
-rw-r--r--examples/system/childexec/CMakeLists.txt4
-rw-r--r--examples/system/console/CMakeLists.txt4
-rw-r--r--examples/system/dynlink/CMakeLists.txt4
-rw-r--r--examples/system/dynlink/display.c74
-rw-r--r--examples/system/dynlink/main.c148
-rw-r--r--examples/system/timer/CMakeLists.txt4
-rw-r--r--examples/system/tty/CMakeLists.txt4
40 files changed, 1358 insertions, 343 deletions
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..dfea465
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,82 @@
+
+# PSn00bSDK Example Programs
+
+## Examples summary
+
+The following list is a brief summary of all the example programs included.
+Additional information may be found in the source code of each example.
+
+| Path | Description | Type | Notes |
+| :--------------------------------------------- | :---------------------------------------------------- | :--: | :---: |
+| [`beginner/cppdemo`](./beginner/cppdemo) | Simple demonstration of (dynamic) C++ classes | EXE | |
+| [`beginner/hello`](./beginner/hello) | The obligatory "Hello World" example program | EXE | |
+| [`cdrom/cdbrowse`](./cdrom/cdbrowse) | File browser using libpsxcd's directory functions | CD | |
+| [`cdrom/cdxa`](./cdrom/cdxa) | CD-XA ADPCM audio player | CD | 1 |
+| [`demos/n00bdemo`](./demos/n00bdemo) | The premiere demonstration program of PSn00bSDK | EXE | |
+| [`graphics/balls`](./graphics/balls) | Draws colored balls bouncing around the screen | EXE | |
+| [`graphics/billboard`](./graphics/billboard) | Demonstrates how to draw 2D sprites in a 3D space | EXE | |
+| [`graphics/fpscam`](./graphics/fpscam) | First-person perspective camera with look-at | EXE | |
+| [`graphics/gte`](./graphics/gte) | Displays a rotating cube using GTE macros | EXE | |
+| [`graphics/hdtv`](./graphics/hdtv) | Demonstrates anamorphic widescreen at 704x480 | EXE | |
+| [`graphics/render2tex`](./graphics/render2tex) | Procedural texture effects using off-screen drawing | EXE | |
+| [`graphics/rgb24`](./graphics/rgb24) | Displays a 640x480 24-bit RGB image | EXE | |
+| [`graphics/tilesasm`](./graphics/tilesasm) | Drawing a tile-map with assembly language | EXE | |
+| [`io/pads`](./io/pads) | Demonstrates reading controllers via low-level access | EXE | |
+| [`io/system573`](./io/system573) | Konami System 573 (PS1-based arcade board) example | CD | |
+| [`lowlevel/cartrom`](./lowlevel/cartrom) | ROM firmware for cheat devices written using GNU GAS | ROM | 2 |
+| [`sound/spustream`](./sound/spustream) | Custom (non XA) CD-ROM audio streaming using the SPU | CD | 1 |
+| [`sound/vagsample`](./sound/vagsample) | Demonstrates playing VAG sound files with the SPU | EXE | |
+| [`system/childexec`](./system/childexec) | Loading a child program and returning to parent | EXE | |
+| [`system/console`](./system/console) | TTY based text console that interrupts gameplay | EXE | |
+| [`system/dynlink`](./system/dynlink) | Demonstrates dynamically linked libraries | CD | |
+| [`system/timer`](./system/timer) | Demonstrates using hardware timers with interrupts | EXE | |
+| [`system/tty`](./system/tty) | Using TTY as a remote text console interface | EXE | |
+
+Notes:
+
+1. `cdrom/cdxa` and `sound/spustream` do not come with example audio files. In
+ order to run these examples you'll have to provide your own files (and, in
+ the case of `spustream`, convert them using the included Python script) and
+ build the CD image manually.
+2. The `lowlevel/cartrom` example is outdated and does not use SDK libraries.
+ It is kept for reference purposes only.
+
+## Building the examples
+
+The instructions below assume that PSn00bSDK, CMake 3.20+ and a GCC toolchain
+are already installed. Refer to the [installation guide](../doc/installation.md)
+for details.
+
+**NOTE**: all examples are compiled by default when building the PSn00bSDK
+libraries and tools (check the `build/examples` directory). These instructions
+are for rebuilding the examples *after* the SDK has been installed.
+
+1. Copy the contents of this directory (`share/psn00bsdk/examples` within the
+ PSn00bSDK installation directory) to your home directory or to another
+ folder you have write access to.
+
+2. Configure and build the examples by running:
+
+ ```bash
+ cmake -S . -B ./build -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=<INSTALL_PATH>/lib/libpsn00b/cmake/sdk.cmake
+ cmake --build ./build
+ ```
+
+ Replace `<INSTALL_PATH>` with the installation prefix you chose when
+ installing the SDK (usually `C:\Program Files\PSn00bSDK` or `/usr/local`,
+ so the full path to `sdk.cmake` would be
+ `C:\Program Files\PSn00bSDK\lib\libpsn00b\cmake\sdk.cmake` or
+ `/usr/local/lib/libpsn00b/cmake/sdk.cmake` respectively).
+
+ Add `-DPSN00BSDK_TARGET=mipsel-unknown-elf` to the first command if your
+ toolchain targets `mipsel-unknown-elf` rather than `mipsel-none-elf`. If you
+ can't get Ninja to work or don't have it installed, you can also replace
+ `-G "Ninja"` with `-G "Unix Makefiles"` (`-G "MSYS Makefiles"` on Windows)
+ to build using `make` instead.
+
+ This should create a `build` directory whose structure mirrors the one of
+ the parent directory, with each subfolder containing built executables and
+ CD images for each example.
+
+-----------------------------------------
+_Last updated on 2022-01-17 by spicyjpeg_
diff --git a/examples/beginner/cppdemo/CMakeLists.txt b/examples/beginner/cppdemo/CMakeLists.txt
index becf464..c43d4a1 100644
--- a/examples/beginner/cppdemo/CMakeLists.txt
+++ b/examples/beginner/cppdemo/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
cppdemo
LANGUAGES CXX
diff --git a/examples/beginner/hello/CMakeLists.txt b/examples/beginner/hello/CMakeLists.txt
index 7fb7c22..d8297c5 100644
--- a/examples/beginner/hello/CMakeLists.txt
+++ b/examples/beginner/hello/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
hello
LANGUAGES C
diff --git a/examples/cdrom/cdbrowse/CMakeLists.txt b/examples/cdrom/cdbrowse/CMakeLists.txt
index e5ec759..e36407d 100644
--- a/examples/cdrom/cdbrowse/CMakeLists.txt
+++ b/examples/cdrom/cdbrowse/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
cdbrowse
LANGUAGES C
diff --git a/examples/cdrom/cdxa/CMakeLists.txt b/examples/cdrom/cdxa/CMakeLists.txt
index 18dcc69..7b90f59 100644
--- a/examples/cdrom/cdxa/CMakeLists.txt
+++ b/examples/cdrom/cdxa/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
cdxa
LANGUAGES C
diff --git a/examples/cdrom/cdxa/main.c b/examples/cdrom/cdxa/main.c
index 5f11d8d..284b92f 100644
--- a/examples/cdrom/cdxa/main.c
+++ b/examples/cdrom/cdxa/main.c
@@ -199,7 +199,7 @@ void xa_callback(int intr, unsigned char *result)
if (intr == CdlDataReady)
{
/* Fetch data sector */
- CdGetSector((u_long*)&xa_sector_buff, 2048);
+ CdGetSector((u_long*)&xa_sector_buff, 512);
/* Quirk: This CdGetSector() implementation must fetch 2048 bytes */
/* or more otherwise the following sectors will be read in an */
diff --git a/examples/demos/n00bdemo/CMakeLists.txt b/examples/demos/n00bdemo/CMakeLists.txt
index c62c4ef..1c211b3 100644
--- a/examples/demos/n00bdemo/CMakeLists.txt
+++ b/examples/demos/n00bdemo/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
n00bdemo
LANGUAGES C ASM
diff --git a/examples/graphics/balls/CMakeLists.txt b/examples/graphics/balls/CMakeLists.txt
index 5886484..f5297c3 100644
--- a/examples/graphics/balls/CMakeLists.txt
+++ b/examples/graphics/balls/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
balls
LANGUAGES C
diff --git a/examples/graphics/billboard/CMakeLists.txt b/examples/graphics/billboard/CMakeLists.txt
index 8cd31a9..1b417d2 100644
--- a/examples/graphics/billboard/CMakeLists.txt
+++ b/examples/graphics/billboard/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
billboard
LANGUAGES C ASM
diff --git a/examples/graphics/fpscam/CMakeLists.txt b/examples/graphics/fpscam/CMakeLists.txt
index 791f6c2..cb0c086 100644
--- a/examples/graphics/fpscam/CMakeLists.txt
+++ b/examples/graphics/fpscam/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
fpscam
LANGUAGES C
diff --git a/examples/graphics/gte/CMakeLists.txt b/examples/graphics/gte/CMakeLists.txt
index 85b2942..f95c5ff 100644
--- a/examples/graphics/gte/CMakeLists.txt
+++ b/examples/graphics/gte/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
gte
LANGUAGES C
diff --git a/examples/graphics/hdtv/CMakeLists.txt b/examples/graphics/hdtv/CMakeLists.txt
index f92faeb..804b096 100644
--- a/examples/graphics/hdtv/CMakeLists.txt
+++ b/examples/graphics/hdtv/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
hdtv
LANGUAGES C
diff --git a/examples/graphics/render2tex/CMakeLists.txt b/examples/graphics/render2tex/CMakeLists.txt
index 360840d..70e489e 100644
--- a/examples/graphics/render2tex/CMakeLists.txt
+++ b/examples/graphics/render2tex/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
render2tex
LANGUAGES C ASM
diff --git a/examples/graphics/rgb24/CMakeLists.txt b/examples/graphics/rgb24/CMakeLists.txt
index bf8a8fa..449981a 100644
--- a/examples/graphics/rgb24/CMakeLists.txt
+++ b/examples/graphics/rgb24/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
rgb24
LANGUAGES C ASM
diff --git a/examples/graphics/tilesasm/CMakeLists.txt b/examples/graphics/tilesasm/CMakeLists.txt
index 59ef665..3384875 100644
--- a/examples/graphics/tilesasm/CMakeLists.txt
+++ b/examples/graphics/tilesasm/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
tilesasm
LANGUAGES C ASM
@@ -15,8 +11,14 @@ project(
HOMEPAGE_URL "http://lameguy64.net/?page=psn00bsdk"
)
+configure_file(data.s.template data.s)
+
file(GLOB _sources *.s *.c)
-psn00bsdk_add_executable(tilesasm STATIC ${_sources})
+psn00bsdk_add_executable(
+ tilesasm STATIC
+ ${_sources}
+ ${PROJECT_BINARY_DIR}/data.s
+)
#psn00bsdk_add_cd_image(tilesasm_iso tilesasm iso.xml DEPENDS tilesasm)
install(FILES ${PROJECT_BINARY_DIR}/tilesasm.exe TYPE BIN)
diff --git a/examples/graphics/tilesasm/data.s b/examples/graphics/tilesasm/data.s.template
index c64ebbc..1c4b01e 100644
--- a/examples/graphics/tilesasm/data.s
+++ b/examples/graphics/tilesasm/data.s.template
@@ -1,35 +1,34 @@
-#
-# LibPSn00b Example Programs
-#
-# Drawing Tile-maps with Assembler Routines
-# 2022 Meido-Tek Productions / PSn00bSDK Project
-#
-# Example by John "Lameguy" Wilbert Villamor (Lameguy64)
-#
-# This assembler file is used to include the file tiles.tim as an array named
-# 'tim_tileset' for use in this example program. Note how the variable name
-# itself is leading with an underscore (_) in this file. This is because
-# GNU C requires leading underscores for global variables, perhaps to prevent
-# function names and variable names from mixing up during the linking stage.
-
-# Tell assembler that the contents that follow must be in the .data section
-.section .data
-
-# This directive define the 'tim_tileset' label as a global symbol so that
-# main.c and other program modules can see this symbol during linking
-.global tim_tileset
-
-# This directive is not really required, but its best to define symbols
-# not pointing to program code as an object to help identify it as a
-# variable in debuggers
-.type tim_tileset, @object
-
-# The following line defines the variable 'tim_tileset' itself filled with the
-# contents of the file 'tiles.tim' by using the .incbin directive
-#
-# Remember the variable type of a symbol is always governed by how it is
-# declared in the C code
-#
-tim_tileset:
- .incbin "../tiles_256.tim"
- \ No newline at end of file
+#
+# LibPSn00b Example Programs
+#
+# Drawing Tile-maps with Assembler Routines
+# 2022 Meido-Tek Productions / PSn00bSDK Project
+#
+# Example by John "Lameguy" Wilbert Villamor (Lameguy64)
+#
+# This assembler file is used to include the file tiles.tim as an array named
+# 'tim_tileset' for use in this example program. Note how the variable name
+# itself is leading with an underscore (_) in this file. This is because
+# GNU C requires leading underscores for global variables, perhaps to prevent
+# function names and variable names from mixing up during the linking stage.
+
+# Tell assembler that the contents that follow must be in the .data section
+.section .data
+
+# This directive define the 'tim_tileset' label as a global symbol so that
+# main.c and other program modules can see this symbol during linking
+.global tim_tileset
+
+# This directive is not really required, but its best to define symbols
+# not pointing to program code as an object to help identify it as a
+# variable in debuggers
+.type tim_tileset, @object
+
+# The following line defines the variable 'tim_tileset' itself filled with the
+# contents of the file 'tiles.tim' by using the .incbin directive
+#
+# Remember the variable type of a symbol is always governed by how it is
+# declared in the C code
+#
+tim_tileset:
+ .incbin "${PROJECT_SOURCE_DIR}/tiles_256.tim"
diff --git a/examples/io/pads/CMakeLists.txt b/examples/io/pads/CMakeLists.txt
index 5bd7f5d..cf5f817 100644
--- a/examples/io/pads/CMakeLists.txt
+++ b/examples/io/pads/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
pads
LANGUAGES C ASM
diff --git a/examples/io/pads/main.c b/examples/io/pads/main.c
index 92beb1c..d100482 100644
--- a/examples/io/pads/main.c
+++ b/examples/io/pads/main.c
@@ -118,16 +118,16 @@ static volatile uint8_t pad_buff[2][34];
static volatile size_t pad_buff_len[2];
static volatile uint32_t pad_digital_only[2] = { 0, 0 };
-// Just a wrapper around spi_new_request(). This does not send the command
+// Just a wrapper around SPI_CreateRequest(). This does not send the command
// immediately but adds it to the driver's request queue.
void send_pad_cmd(
- uint32_t port,
- PAD_COMMAND cmd,
- uint8_t arg1,
- uint8_t arg2,
- SPICALLBACK callback
+ uint32_t port,
+ PadCommand cmd,
+ uint8_t arg1,
+ uint8_t arg2,
+ SPI_Callback callback
) {
- SPIREQUEST *req = spi_new_request();
+ SPI_Request *req = SPI_CreateRequest();
req->len = 9;
req->port = port;
@@ -150,12 +150,12 @@ void send_pad_cmd(
// actually a DualShock in digital mode by checking if it started identifying
// as CONFIG_MODE after receiving a configuration command.
void dualshock_init_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_len) {
- PADTYPE *pad = (PADTYPE *) buff;
+ PadResponse *pad = (PadResponse *) buff;
if (
(rx_len < 2) ||
- (pad->raw.prefix != 0x5a) ||
- (pad->raw.type != PAD_ID_CONFIG_MODE)
+ (pad->prefix != 0x5a) ||
+ (pad->type != PAD_ID_CONFIG_MODE)
) {
printf("no, pad is digital-only (len = %d)\n", rx_len);
@@ -187,7 +187,7 @@ void poll_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_len) {
if (rx_len)
memcpy((void *) pad_buff[port], (void *) buff, rx_len);
- PADTYPE *pad = (PADTYPE *) buff;
+ PadResponse *pad = (PadResponse *) buff;
// If this pad identifies as a digital pad and hasn't been flagged as a
// digital-only pad already, attempt to put it into analog mode by entering
@@ -196,8 +196,8 @@ void poll_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_len) {
// returning digital pad responses.
if (
rx_len &&
- (pad->raw.prefix == 0x5a) &&
- (pad->raw.type == PAD_ID_DIGITAL)
+ (pad->prefix == 0x5a) &&
+ (pad->type == PAD_ID_DIGITAL)
) {
if (!pad_digital_only[port]) {
printf("Detecting if pad %d supports config mode... ", port + 1);
@@ -221,7 +221,7 @@ static CONTEXT ctx;
int main(int argc, const char* argv[]) {
init_context(&ctx);
- spi_init(&poll_cb);
+ SPI_Init(&poll_cb);
uint32_t counter = 0;
@@ -238,15 +238,14 @@ int main(int argc, const char* argv[]) {
continue;
}
- PADTYPE *pad = (PADTYPE *) pad_buff[port];
- PAD_TYPEID type = pad->raw.type;
+ PadResponse *pad = (PadResponse *) pad_buff[port];
// According to nocash docs, there is a hardware bug in DualShock
// controllers that causes the prefix byte (normally 0x5a) to turn
// into 0x00 if the analog button is pressed after configuration
// commands have been used. Thus making sure the prefix is 0x5a
// isn't enough to reliably detect pads.
- /*if ((pad->raw.prefix != 0x5a) && (type != PAD_ID_ANALOG)) {
+ /*if ((pad->prefix != 0x5a) && (pad->type != PAD_ID_ANALOG)) {
FntPrint(-1, "\n\nPORT %d: INVALID RESPONSE\n", port + 1);
if ((counter % 64) < 32)
FntPrint(-1, " CHECK CONNECTION...");
@@ -258,8 +257,8 @@ int main(int argc, const char* argv[]) {
-1,
"\n\nPORT %d: %s (TYPE=%d)\n",
port + 1,
- PAD_TYPEIDS[type],
- type
+ PAD_TYPEIDS[pad->type],
+ pad->type
);
// Print a hexdump of the payload returned by the pad.
diff --git a/examples/io/pads/spi.c b/examples/io/pads/spi.c
index e01b3f6..ef75ffc 100644
--- a/examples/io/pads/spi.c
+++ b/examples/io/pads/spi.c
@@ -54,21 +54,21 @@
/* Internal structures and globals */
-typedef struct _SPICONTEXT {
- uint8_t tx_buff[SPI_BUFF_LEN];
- uint8_t rx_buff[SPI_BUFF_LEN];
- uint32_t tx_len, rx_len, port;
- SPICALLBACK callback;
-} SPICONTEXT;
+typedef struct _SPI_CONTEXT {
+ uint8_t tx_buff[SPI_BUFF_LEN];
+ uint8_t rx_buff[SPI_BUFF_LEN];
+ uint32_t tx_len, rx_len, port;
+ SPI_Callback callback;
+} SPI_Context;
-static volatile SPICONTEXT ctx;
-static volatile SPIREQUEST volatile *current_req;
-static SPICALLBACK default_cb;
+static volatile SPI_Context ctx;
+static volatile SPI_Request volatile *current_req;
+static SPI_Callback default_cb;
/* Request queue management */
-static void prepare_poll_req(void) {
- PADREQUEST *req = (PADREQUEST *) ctx.tx_buff;
+static void _spi_create_poll_req(void) {
+ PadRequest *req = (PadRequest *) ctx.tx_buff;
req->addr = 0x01;
req->cmd = PAD_CMD_READ;
@@ -82,7 +82,7 @@ static void prepare_poll_req(void) {
ctx.callback = default_cb;
}
-static void prepare_next_req(void) {
+static void _spi_next_req(void) {
// Copy the contents of the first request in the queue into the TX buffer.
memcpy((void *) ctx.tx_buff, (void *) current_req->data, current_req->len);
@@ -93,7 +93,7 @@ static void prepare_next_req(void) {
// Pop the first request from the queue by deallocating it and adjusting
// the pointer to the first queue item.
- SPIREQUEST *next = current_req->next;
+ SPI_Request *next = current_req->next;
free((void *) current_req);
current_req = next;
@@ -101,7 +101,7 @@ static void prepare_next_req(void) {
/* Interrupt handlers */
-static void poll_timer_tick(void) {
+static void _spi_poll_irq_handler(void) {
// Fetch the last response byte, which wasn't followed by a pulse on /ACK,
// from the RX FIFO.
if (JOY_STAT & 0x0002)
@@ -112,9 +112,9 @@ static void poll_timer_tick(void) {
// If the request queue is empty, create a pad polling request.
if (current_req)
- prepare_next_req();
+ _spi_next_req();
else
- prepare_poll_req();
+ _spi_create_poll_req();
// Prepare the SPI port by clearing any pending IRQ, pulling /CS high and
// enabling the /ACK IRQ. In order to communicate with controllers, /CS has
@@ -132,7 +132,7 @@ static void poll_timer_tick(void) {
JOY_TXRX = ctx.tx_buff[0];
}
-static void spi_ack_handler(void) {
+static void _spi_ack_irq_handler(void) {
// Wait until /ACK is pulled up by the controller before sending the next
// byte. According to nocash docs, this has to be done before resetting the
// IRQ.
@@ -166,8 +166,8 @@ static void spi_ack_handler(void) {
/* Public API */
-SPIREQUEST *spi_new_request(void) {
- SPIREQUEST *req = malloc(sizeof(SPIREQUEST));
+SPI_Request *SPI_CreateRequest(void) {
+ SPI_Request *req = malloc(sizeof(SPI_Request));
req->len = 0;
req->port = 0;
@@ -179,7 +179,7 @@ SPIREQUEST *spi_new_request(void) {
if (!current_req) {
current_req = req;
} else {
- volatile SPIREQUEST *volatile last = current_req;
+ volatile SPI_Request *volatile last = current_req;
while (last->next)
last = last->next;
@@ -189,7 +189,7 @@ SPIREQUEST *spi_new_request(void) {
return req;
}
-void spi_set_poll_rate(uint32_t value) {
+void SPI_SetPollRate(uint32_t value) {
TIM_CTRL(2) = 0x0258; // CLK/8 input, IRQ on reload, disable one-shot IRQ
if (value < 65)
@@ -198,21 +198,21 @@ void spi_set_poll_rate(uint32_t value) {
TIM_RELOAD(2) = (F_CPU / 8) / value;
}
-void spi_init(SPICALLBACK callback) {
+void SPI_Init(SPI_Callback callback) {
// Disable the BIOS timer handler (which for some stupid reason is enabled
// by default, even though it does nothing) and set up custom interrupt
// handlers.
EnterCriticalSection();
ChangeClearRCnt(2, 0);
- InterruptCallback(6, &poll_timer_tick);
- InterruptCallback(7, &spi_ack_handler);
+ InterruptCallback(6, &_spi_poll_irq_handler);
+ InterruptCallback(7, &_spi_ack_irq_handler);
ExitCriticalSection();
JOY_CTRL = 0x0040; // Reset all registers
JOY_MODE = 0x000d; // 1x multiplier, 8 data bits, no parity
JOY_BAUD = 0x0088; // 250000 bps
- spi_set_poll_rate(250);
+ SPI_SetPollRate(250);
current_req = 0;
default_cb = callback;
}
diff --git a/examples/io/pads/spi.h b/examples/io/pads/spi.h
index 1c473cd..c50e065 100644
--- a/examples/io/pads/spi.h
+++ b/examples/io/pads/spi.h
@@ -9,23 +9,24 @@
#include <stdint.h>
#include <psxpad.h>
+// Maximum request/response length (34 bytes for pads, 140 for memory cards)
//#define SPI_BUFF_LEN 34
#define SPI_BUFF_LEN 140
/* Request structures */
-typedef void (*SPICALLBACK)(uint32_t port, const volatile uint8_t *buff, size_t rx_len);
+typedef void (*SPI_Callback)(uint32_t port, const volatile uint8_t *buff, size_t rx_len);
-typedef struct _SPIREQUEST {
+typedef struct _SPI_Request {
union {
- uint8_t data[SPI_BUFF_LEN];
- PADREQUEST pad_req;
- MCDREQUEST mcd_req;
+ uint8_t data[SPI_BUFF_LEN];
+ PadRequest pad_req;
+ MemCardRequest mcd_req;
};
- uint32_t len, port;
- SPICALLBACK callback;
- struct _SPIREQUEST *next;
-} SPIREQUEST;
+ uint32_t len, port;
+ SPI_Callback callback;
+ struct _SPI_Request *next;
+} SPI_Request;
/* Public API */
@@ -34,7 +35,7 @@ typedef struct _SPIREQUEST {
* object must be populated afterwards by setting the length, callback and
* filling in the TX data buffer.
*/
-SPIREQUEST *spi_new_request(void);
+SPI_Request *SPI_CreateRequest(void);
/**
* @brief Changes the controller polling rate. The lowest supported rate is 65
@@ -43,7 +44,7 @@ SPIREQUEST *spi_new_request(void);
*
* @param value
*/
-void spi_set_poll_rate(uint32_t value);
+void SPI_SetPollRate(uint32_t value);
/**
* @brief Installs the SPI and timer 2 interrupt handlers and starts the poll
@@ -56,6 +57,6 @@ void spi_set_poll_rate(uint32_t value);
*
* @param callback
*/
-void spi_init(SPICALLBACK callback);
+void SPI_Init(SPI_Callback callback);
#endif
diff --git a/examples/io/system573/CMakeLists.txt b/examples/io/system573/CMakeLists.txt
new file mode 100644
index 0000000..1c74347
--- /dev/null
+++ b/examples/io/system573/CMakeLists.txt
@@ -0,0 +1,23 @@
+# PSn00bSDK example CMake script
+# (C) 2021 spicyjpeg - MPL licensed
+
+cmake_minimum_required(VERSION 3.21)
+
+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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<iso_project
+ image_name="${CD_IMAGE_NAME}.bin"
+ cue_sheet="${CD_IMAGE_NAME}.cue"
+>
+ <track type="data">
+ <identifiers
+ system ="PLAYSTATION"
+ volume ="SYSTEM573"
+ volume_set ="SYSTEM573"
+ publisher ="MEIDOTEK"
+ data_preparer ="PSN00BSDK ${PSN00BSDK_VERSION}"
+ application ="PLAYSTATION"
+ copyright ="README.TXT;1"
+ />
+
+ <directory_tree>
+ <!--
+ The System 573 BIOS does not parse SYSTEM.CNF, it always looks
+ for an executable named PSX.EXE. Note that this behavior can be
+ abused to make multi-system CDs with different executables for
+ PS1 and 573 (i.e. have both PSX.EXE and SYSTEM.CNF pointing to
+ a different executable).
+ -->
+ <!--<file name="SYSTEM.CNF" type="data" source="${PROJECT_SOURCE_DIR}/system.cnf" />-->
+ <file name="PSX.EXE" type="data" source="system573.exe" />
+ <file name="PSX.MAP" type="data" source="system573.map" />
+
+ <dummy sectors="1024"/>
+ </directory_tree>
+ </track>
+
+ <!--<track type="audio" source="track2.wav" />-->
+</iso_project>
diff --git a/examples/io/system573/main.c b/examples/io/system573/main.c
new file mode 100644
index 0000000..a06c4e5
--- /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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <psxetc.h>
+#include <psxapi.h>
+#include <psxgpu.h>
+#include <psxpad.h>
+
+/* 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;
+} JAMMAInputs;
+
+void get_jamma_inputs(JAMMAInputs *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 n*8+0 -> bit n*8+7
+ bits |= (lights & 0x02020202) << 5; // Lamp n*8+1 -> bit n*8+6
+ bits |= (lights & 0x04040404) >> 1; // Lamp n*8+2 -> bit n*8+1
+ bits |= (lights & 0x08080808) >> 3; // Lamp n*8+3 -> bit n*8+0
+ bits |= (lights & 0x10101010) << 1; // Lamp n*8+4 -> bit n*8+5
+ bits |= (lights & 0x20202020) >> 1; // Lamp n*8+5 -> bit n*8+4
+ bits |= (lights & 0x40404040) >> 3; // Lamp n*8+6 -> bit n*8+3
+ bits |= (lights & 0x80808080) >> 5; // Lamp n*8+7 -> bit n*8+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 n*4+0 -> bit n*4+0
+ bits |= (lights & 0x22222222) << 1; // Lamp n*4+1 -> bit n*4+2
+ bits |= (lights & 0x44444444) << 1; // Lamp n*4+2 -> bit n*4+3
+ bits |= (lights & 0x88888888) >> 2; // Lamp n*4+3 -> bit n*4+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++);
+
+ JAMMAInputs 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 & 0xf);
+ 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;
+}
diff --git a/examples/lowlevel/cartrom/CMakeLists.txt b/examples/lowlevel/cartrom/CMakeLists.txt
index 3e807a3..107cc3d 100644
--- a/examples/lowlevel/cartrom/CMakeLists.txt
+++ b/examples/lowlevel/cartrom/CMakeLists.txt
@@ -3,10 +3,6 @@
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(
cartrom
LANGUAGES C ASM
diff --git a/examples/readme.txt b/examples/readme.txt
deleted file mode 100644
index 80747de..0000000
--- a/examples/readme.txt
+++ /dev/null
@@ -1,64 +0,0 @@
-PSn00bSDK Example Programs
-2019 - 2022 Meido-Tek Productions / PSn00bSDK Project
-
-## Building the examples ##
-
-The instructions below assumes that a mipsel-unknown-elf or mipsel-none-elf GNU
-toolchain is installed, CMake and Ninja or make is installed and the PSn00bSDK
-libraries and tools are compiled and installed. CMake version must be at least
-version 3.20 or newer.
-
-1. If the examples are in /usr/local/share, copy the directory into your home
- directory.
-
-2. Configure the examples with CMake by running:
-
- cmake -S . -B ./build -DCMAKE_TOOLCHAIN_FILE=<path to sdk.cmake>
-
- <path to sdk.cmake> must point to the sdk.cmake file provided by the SDK.
- Unless you've installed the SDK with a custom path, normally this file is
- located in /usr/local/lib/libpsn00b/cmake on *nix style systems or
- C:\Program Files\PSn00bSDK\lib\libpsn00b\cmake in Windows.
-
- If the mipsel toolchain has a different prefix (ie. mipsel-none-elf), specify
- -DPSN00BSDK_TARGET=<prefix> to override the default toolchain prefix.
-
- If Ninja does not work for you or don't have it installed, pass
- -G "Unix Makefiles" (or -G "MSys Makefiles" on Windows) to build using make
- instead.
-
-3. Build the example programs by running:
-
- cmake --build ./build
-
- This should create a build directory with a directory structure that mirrors
- the parent directory. The directories should contain compiled versions of the
- example programs as a PS-EXE or ISO file.
-
-
-## Examples summary ##
-
-The following list is a brief summary of all the example programs included.
-Additional information may be found in the source code of each example.
-
- beginner/cppdemo Simple demonstration of (dynamic) C++ classes
- beginner/hello The obligatory "Hello World" example program
- cdrom/cdbrowse File browser using libpsxcd's directory functions
- cdrom/cdxa Plays CD-XA audio (XA audio not included)
- demos/n00bdemo The premiere demonstration program of PSn00bSDK
- graphics/balls Draws colored balls bouncing around the screen
- graphics/billboard Demonstrates how to draw 2D sprites in a 3D space
- graphics/fpscam First-person perspective camera with look-at
- graphics/gte Displays a rotating cube using GTE macros
- graphics/hdtv Demonstrates anamorphic widescreen at 704x480
- graphics/render2tex Procedural texture effects using off-screen drawing
- graphics/rgb24 Displays a 640x480 24-bit RGB image
- graphics/tilesasm Drawing a tile-map with assembly language
- io/pads Demonstrates reading controllers via low-level access
- lowlevel/cartrom ROM firmware for cheat devices written using GNU GAS
- sound/vagsample Demonstrates playing VAG sound files with the SPU
- system/childexec Loading a child program and returning to parent
- system/console TTY based text console that interrupts gameplay
- system/dynlink Demonstrates dynamically linked libraries
- system/timer Demonstrates using hardware timers with interrupts
- system/tty Using TTY as a remote text console interface \ No newline at end of file
diff --git a/examples/sound/spustream/CMakeLists.txt b/examples/sound/spustream/CMakeLists.txt
new file mode 100644
index 0000000..9e84fa3
--- /dev/null
+++ b/examples/sound/spustream/CMakeLists.txt
@@ -0,0 +1,25 @@
+# PSn00bSDK example CMake script
+# (C) 2021 spicyjpeg - MPL licensed
+
+cmake_minimum_required(VERSION 3.20)
+
+project(
+ spustream
+ LANGUAGES C
+ VERSION 1.0.0
+ DESCRIPTION "PSn00bSDK SPU custom streaming example"
+ HOMEPAGE_URL "http://lameguy64.net/?page=psn00bsdk"
+)
+
+# TODO: add rules to actually generate a valid STREAM.BIN file
+file(GLOB _sources *.c)
+psn00bsdk_add_executable(spustream STATIC ${_sources})
+#psn00bsdk_add_cd_image(spustream_iso spustream iso.xml DEPENDS spustream)
+
+install(
+ FILES
+ #${PROJECT_BINARY_DIR}/spustream.bin
+ #${PROJECT_BINARY_DIR}/spustream.cue
+ ${PROJECT_BINARY_DIR}/spustream.exe
+ TYPE BIN
+)
diff --git a/examples/sound/spustream/convert_stream.py b/examples/sound/spustream/convert_stream.py
new file mode 100644
index 0000000..1b1696f
--- /dev/null
+++ b/examples/sound/spustream/convert_stream.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+# Simple .VAG to STREAM.BIN interleaving tool
+# (C) 2021 spicyjpeg - MPL licensed
+
+import sys
+from warnings import warn
+from struct import Struct
+from itertools import zip_longest
+from argparse import ArgumentParser, FileType
+
+VAG_HEADER = Struct("> 4s I 4x 2I 12x 16s")
+VAG_MAGIC = b"VAGp"
+SAMPLE_RATE = 44100
+BUFFER_SIZE = 26624 # (26624 / 16 * 28) / 44100 = 1.05 seconds
+ALIGN_SIZE = 2048
+
+## Helpers
+
+def align(data, size):
+ chunks = (len(data) + size - 1) // size
+
+ return data.ljust(chunks * size, b"\x00")
+
+def set_loop_flag(data):
+ last_block = bytearray(data[-16:])
+ last_block[1] = 0x03 # Jump to loop point + sustain
+
+ return data[:-16] + last_block
+
+## .VAG file reader
+
+def read_vag(_file, chunk_size):
+ with _file:
+ header = _file.read(VAG_HEADER.size)
+ (
+ magic,
+ version,
+ size,
+ sample_rate,
+ name
+ ) = VAG_HEADER.unpack(header)
+
+ #if magic != VAG_MAGIC:
+ #raise RuntimeError(f"{_file.name} is not a valid .VAG file")
+ if sample_rate != SAMPLE_RATE:
+ warn(RuntimeWarning(f"{_file.name} sample rate is not {SAMPLE_RATE} Hz"))
+
+ for i in range(0, size, chunk_size):
+ chunk = _file.read(chunk_size)
+
+ if len(chunk) % 16:
+ warn(RuntimeWarning(f"{_file.name} is not 16-byte aligned, trimming"))
+ chunk = chunk[0:len(chunk) // 16 * 16]
+
+ chunk = set_loop_flag(chunk)
+
+ yield chunk.ljust(chunk_size, b"\x00")
+
+## Main
+
+def get_args():
+ parser = ArgumentParser(
+ description = "Generates interleaved stream data from one or more .VAG files."
+ )
+ parser.add_argument(
+ "input_file",
+ nargs = "+",
+ type = FileType("rb"),
+ help = f"mono input files for each channel (must be {SAMPLE_RATE} Hz .VAG)"
+ )
+ parser.add_argument(
+ "-o", "--output",
+ type = FileType("wb"),
+ default = "stream.bin",
+ help = "where to output converted stream data (stream.bin by default)",
+ metavar = "file"
+ )
+ parser.add_argument(
+ "-b", "--buffer-size",
+ type = int,
+ default = BUFFER_SIZE,
+ help = f"size of each interleaved chunk (one per channel, default {BUFFER_SIZE})",
+ metavar = "bytes"
+ )
+ parser.add_argument(
+ "-a", "--align",
+ type = int,
+ default = ALIGN_SIZE,
+ help = f"align each group of chunks to N bytes (default {ALIGN_SIZE})",
+ metavar = "bytes"
+ )
+
+ return parser.parse_args()
+
+def main():
+ args = get_args()
+ if args.buffer_size % 16:
+ raise ValueError("buffer size must be a multiple of 16 bytes")
+
+ interleave = zip_longest(
+ *( read_vag(_file, args.buffer_size) for _file in args.input_file ),
+ fillvalue = b"\x00" * args.buffer_size
+ )
+
+ with args.output as _file:
+ for chunks in interleave:
+ data = b"".join(chunks)
+
+ _file.write(align(data, args.align))
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/sound/spustream/iso.xml b/examples/sound/spustream/iso.xml
new file mode 100644
index 0000000..3807046
--- /dev/null
+++ b/examples/sound/spustream/iso.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<iso_project
+ image_name="${CD_IMAGE_NAME}.bin"
+ cue_sheet="${CD_IMAGE_NAME}.cue"
+>
+ <track type="data">
+ <identifiers
+ system ="PLAYSTATION"
+ volume ="SPUSTREAM"
+ volume_set ="SPUSTREAM"
+ publisher ="MEIDOTEK"
+ data_preparer ="PSN00BSDK ${PSN00BSDK_VERSION}"
+ application ="PLAYSTATION"
+ copyright ="README.TXT;1"
+ />
+
+ <directory_tree>
+ <file name="SYSTEM.CNF" type="data" source="${PROJECT_SOURCE_DIR}/system.cnf" />
+ <file name="SPUSTRM.EXE" type="data" source="spustream.exe" />
+ <file name="SPUSTRM.MAP" type="data" source="spustream.map" />
+
+ <file name="STREAM.BIN" type="data" source="${PROJECT_SOURCE_DIR}/stream.bin" />
+
+ <dummy sectors="1024"/>
+ </directory_tree>
+ </track>
+
+ <!--<track type="audio" source="track2.wav" />-->
+</iso_project>
diff --git a/examples/sound/spustream/main.c b/examples/sound/spustream/main.c
new file mode 100644
index 0000000..be095cb
--- /dev/null
+++ b/examples/sound/spustream/main.c
@@ -0,0 +1,456 @@
+/*
+ * PSn00bSDK SPU audio streaming example
+ * (C) 2021 spicyjpeg - MPL licensed
+ *
+ * This example demonstrates how to play a large multi-channel audio file
+ * "manually" by streaming it through the SPU, without having to rely on the CD
+ * drive's ability to play audio tracks or XA files.
+ *
+ * The way this works is by splitting the audio file into a series of ~1 second
+ * "chunks", each of which in turn is an array of concatenated buffers holding
+ * SPU ADPCM data (one for each channel, so a stereo stream would have 2
+ * buffers per chunk). All buffers in a chunk are played simultaneously using
+ * multiple SPU channels; each buffer has the loop flag set at the end, so each
+ * channel will jump to its loop address (SPU_CHANNELS[n].loop_addr) once the
+ * chunk is played.
+ *
+ * Since the loop point doesn't necessarily have to be within the chunk itself,
+ * we can abuse it to "queue" another set of buffers to be played immediately
+ * after the currently playing chunk. This allows us to fetch a chunk from the
+ * CD, upload it to SPU RAM (2048 bytes at a time to avoid having to keep
+ * another large buffer in main RAM) and queue it for playback while a
+ * previously buffered chunk is playing in the background. SPU RAM always holds
+ * two chunks, one of which is played while the other one is buffered. This is
+ * the layout used in this example:
+ *
+ * /================================================\
+ * | /==================\ |
+ * v Loop point | v Loop point |
+ * +-------+----------------+----------------+----------------+----------------+
+ * | Dummy | Left buffer 0 | Right buffer 0 | Left buffer 1 | Right buffer 1 |
+ * +-------+----------------+----------------+----------------+----------------+
+ * \____________Chunk 0____________/ \____________Chunk 1____________/
+ *
+ * It's pretty much the same thing as GPU double buffering (aka page flipping),
+ * just with chunks instead of framebuffers.
+ *
+ * We need to know when the chunk we've buffered actually starts playing in
+ * order to start buffering the next one. The SPU can be configured to trigger
+ * an interrupt whenever a specific address in SPU RAM is read by a channel, so
+ * we can just point it to the beginning of the buffered chunk's first buffer.
+ * The interrupt callback will then kick off CD reading and adjust the loop/IRQ
+ * addresses to the ones of the chunk that is going to be buffered next.
+ *
+ * Chunks are read from a STREAM.BIN file which is just a series of sector
+ * aligned chunks, arranged as follows:
+ *
+ * +--Sector--+--Sector--+--Sector--+--Sector--+--Sector--+--Sector--+----
+ * | +--------------------------+--------------------------+ |
+ * | | Left channel data | Right channel data | Padding | ...
+ * | +--------------------------+--------------------------+ |
+ * +----------+----------+----------+----------+----------+----------+----
+ * \________________________Chunk________________________/
+ *
+ * Such file isn't provided as PSn00bSDK doesn't yet have a tool for audio
+ * transcoding. A Python script is included to generate STREAM.BIN from one or
+ * more SPU ADPCM (.VAG) files, one for each channel (the .VAG format only
+ * supports mono).
+ *
+ * Of course SPU streaming isn't the only way to play music, as the CD drive
+ * can play CD-DA tracks and XA files natively with zero CPU overhead. However
+ * streaming has a number of advantages over CD audio or XA:
+ *
+ * - Any sample rate up to 44.1 kHz can be used. The sample rate can also be
+ * changed on-the-fly to play the stream at different speeds and pitches (as
+ * long as the CD drive can keep up of course), or even interpolated for
+ * effects like tape stops or DJ scratches.
+ * - Manual streaming is not limited to mono or stereo but can be expanded to
+ * as many channels as needed, only limited by the amount of SPU RAM required
+ * for chunks and CD bandwidth. Having more than 2 channels can be useful for
+ * e.g. crossfading between tracks (not possible with XA) or controlling
+ * volume and panning of each individual instrument.
+ * - Depending on how streaming/interleaving is implemented it is possible to
+ * have 500-1000ms idle periods during which the CD drive isn't buffering the
+ * stream, that can be used to read small amounts of other data without ever
+ * interrupting playback. This is different from XA-style interleaving as the
+ * drive is free to seek to *any* region of the disc during these periods
+ * (it must seek back to the stream's next chunk afterwards though).
+ * - Thanks to the idle periods it is possible to seek back to the beginning of
+ * the stream and preload the first chunk before the end is reached, allowing
+ * the track to be looped seamlessly without having to resort to tricks like
+ * filler samples.
+ * - Unlike XA, SPU streaming can be used on some PS1-based arcade boards such
+ * as the Konami System 573. These systems usually use IDE/SCSI CD drives or
+ * flash memory, neither of which supports XA playback.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <psxetc.h>
+#include <psxapi.h>
+#include <psxgpu.h>
+#include <psxpad.h>
+#include <psxspu.h>
+#include <psxcd.h>
+
+// To maximize STREAM.BIN packing efficiency and get rid of padding between
+// chunks, buffer size should be a multiple of sector size (2048 bytes). Buffer
+// size can be increased to get more idle time between CD reads, however it is
+// usually best to keep it to 1-2 seconds as SPU RAM is only 512 KB.
+#define SAMPLE_RATE 0x1000 // 44100 Hz
+#define BUFFER_SIZE 26624 // (26624 / 16 * 28) / 44100 = 1.05 seconds
+
+#define NUM_CHANNELS 2
+#define CHANNEL_MASK 0x03
+
+/* Register definitions */
+
+// For some reason SpuVoiceRaw doesn't actually match the layout of SPU
+// registers, so here we go.
+typedef struct {
+ uint16_t vol_left;
+ uint16_t vol_right;
+ uint16_t freq;
+ uint16_t addr;
+ uint32_t adsr_param;
+ uint16_t _reserved;
+ uint16_t loop_addr;
+} SPUChannel;
+
+#define SPU_CTRL *((volatile uint16_t *) 0x1f801daa)
+#define SPU_IRQ_ADDR *((volatile uint16_t *) 0x1f801da4)
+#define SPU_KEY_ON *((volatile uint32_t *) 0x1f801d88)
+#define SPU_KEY_OFF *((volatile uint32_t *) 0x1f801d8c)
+
+// SPU RAM is addressed in 8-byte units, using 16-bit pointers.
+#define SPU_CHANNELS ((volatile SPUChannel *) 0x1f801c00)
+#define SPU_RAM_ADDR(x) ((uint16_t) (((uint32_t) (x)) >> 3))
+
+/* 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);
+}
+
+/* Stream interrupt handlers */
+
+// This is a silent looping sample used to keep unused SPU channels busy,
+// preventing them from accidentally triggering the SPU RAM interrupt and
+// throwing off the timing (all channels are always reading sample data, even
+// when "stopped"). It is 64 bytes as that is the minimum size for SPU DMA
+// transfers, however only the first 16 bytes are kept. The rest is going to be
+// overwritten by chunks.
+// https://problemkaputt.de/psx-spx.htm#spuinterrupt
+const uint8_t SPU_DUMMY_BLOCK[] = {
+ 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+// The first 4 KB of SPU RAM are reserved for capture buffers, so we have to
+// place stream buffers after those. Sony's SPU library additionally places a
+// dummy sample at 0x1000; we are going to do the same with the block above.
+#define DUMMY_BLOCK_ADDR 0x1000
+#define BUFFER_START_ADDR 0x1010
+#define CHUNK_SIZE (BUFFER_SIZE * NUM_CHANNELS)
+
+typedef struct {
+ uint32_t lba;
+ uint32_t length;
+ uint32_t pos;
+
+ uint32_t spu_addr;
+ uint32_t spu_pos;
+ uint32_t db_active;
+} StreamContext;
+
+static volatile StreamContext str_ctx;
+
+// This buffer is used by cd_event_handler() as a temporary area for sectors
+// read from the CD and uploaded to SPU RAM. Due to DMA limitations it can't be
+// allocated on the stack (especially not in the interrupt callbacks' stack,
+// whose size is very limited).
+static uint8_t sector_buffer[2048];
+
+void spu_irq_handler(void) {
+ // Acknowledge the interrupt to ensure it can be triggered again. The only
+ // way to do this is actually to disable the interrupt entirely; we'll
+ // enable it again once the buffer is ready.
+ SPU_CTRL &= 0xffbf;
+
+ str_ctx.db_active ^= 1;
+ str_ctx.spu_pos = 0;
+
+ // Align the sector counter to the size of a chunk (to prevent glitches
+ // after seeking) and reset it if it exceeds the stream's length.
+ str_ctx.pos %= str_ctx.length;
+ str_ctx.pos -= str_ctx.pos % ((CHUNK_SIZE + 2047) / 2048);
+
+ // Configure to SPU to trigger an IRQ once the buffer that is going to be
+ // filled now starts playing (so the next buffer can be loaded) and
+ // override both channels' loop addresses to make them "jump" to the new
+ // buffer rather than actually looping when they encounter the loop flag at
+ // the end of the currently playing buffer.
+ str_ctx.spu_addr = BUFFER_START_ADDR + CHUNK_SIZE * str_ctx.db_active;
+ SPU_IRQ_ADDR = SPU_RAM_ADDR(str_ctx.spu_addr);
+
+ for (uint32_t i = 0; i < NUM_CHANNELS; i++)
+ SPU_CHANNELS[i].loop_addr = SPU_RAM_ADDR(str_ctx.spu_addr + BUFFER_SIZE * i);
+
+ // Start loading the next chunk. cd_event_handler() will be called
+ // repeatedly for each sector until the entire chunk is read.
+ CdlLOC pos;
+ CdIntToPos(str_ctx.lba + str_ctx.pos, &pos);
+ CdControlF(CdlReadN, &pos);
+}
+
+void cd_event_handler(int32_t event, uint8_t *payload) {
+ // Ignore all events other than a sector being ready.
+ // TODO: read errors should be handled properly
+ if (event != CdlDataReady)
+ return;
+
+ // Fetch the sector that has been read from the drive.
+ CdGetSector(sector_buffer, 512);
+ str_ctx.pos++;
+
+ // Set loop flags to make sure the buffer will loop (actually jump to the
+ // other buffer, as we're overriding loop addresses) at the end.
+ // NOTE: this isn't actually necessary here as the stream converter script
+ // already sets these flags in the file.
+ /*for (uint32_t i = 0; i < NUM_CHANNELS; i++) {
+ if (
+ str_ctx.spu_pos >= (BUFFER_SIZE * i - 2048) &&
+ str_ctx.spu_pos < (BUFFER_SIZE * i)
+ )
+ sector[(BUFFER_SIZE * i - str_ctx.spu_pos) - 15] = 0x03;
+ }*/
+
+ // Copy the sector to SPU RAM, appending it to the buffer that is not
+ // playing currently. As the left and right buffers are adjacent, we can
+ // just treat the chunk as a single blob of data and copy it as-is; we only
+ // have to trim the padding at the end (if any) to avoid overwriting other
+ // data in SPU RAM.
+ uint32_t length = CHUNK_SIZE - str_ctx.spu_pos;
+ if (length > 2048)
+ length = 2048;
+
+ SpuSetTransferStartAddr(str_ctx.spu_addr + str_ctx.spu_pos);
+ SpuWrite(sector_buffer, length);
+ str_ctx.spu_pos += length;
+
+ // If the buffer has been filled completely, stop reading and re-enable the
+ // SPU IRQ.
+ // TODO TODO: preload first sector
+ if (str_ctx.spu_pos >= CHUNK_SIZE) {
+ CdControlF(CdlPause, 0);
+ SPU_CTRL |= 0x0040;
+ }
+}
+
+/* Stream helpers */
+
+void init_spu_channels(void) {
+ // Upload the dummy block to the SPU and play it on all channels, locking
+ // them up and stopping them from messing with the SPU interrupt.
+ // TODO: is this really necessary? (needs testing on real hardware)
+ SpuSetTransferStartAddr(DUMMY_BLOCK_ADDR);
+ SpuWrite(SPU_DUMMY_BLOCK, 64);
+
+ SPU_KEY_OFF = 0x00ffffff;
+
+ for (uint32_t i = 0; i < 24; i++)
+ SPU_CHANNELS[i].addr = SPU_RAM_ADDR(DUMMY_BLOCK_ADDR);
+
+ SPU_KEY_ON = 0x00ffffff;
+}
+
+void init_stream(CdlFILE *file) {
+ EnterCriticalSection();
+ InterruptCallback(9, &spu_irq_handler);
+ CdReadyCallback(&cd_event_handler);
+ ExitCriticalSection();
+
+ // Set the initial LBA of the stream file, which is going to be incremented
+ // as the stream is played.
+ str_ctx.lba = CdPosToInt(&(file->pos));
+ str_ctx.length = file->size / 2048;
+ str_ctx.pos = 0;
+
+ // Ensure at least one chunk is in SPU RAM by invoking the SPU IRQ handler
+ // manually and blocking until the chunk has loaded.
+ str_ctx.db_active = 1;
+ spu_irq_handler();
+
+ while (str_ctx.spu_pos < CHUNK_SIZE)
+ __asm__("nop");
+}
+
+void start_stream(void) {
+ SPU_KEY_OFF = CHANNEL_MASK;
+
+ for (uint32_t i = 0; i < NUM_CHANNELS; i++) {
+ SPU_CHANNELS[i].addr = SPU_RAM_ADDR(BUFFER_START_ADDR + BUFFER_SIZE * i);
+ SPU_CHANNELS[i].freq = SAMPLE_RATE;
+ SPU_CHANNELS[i].adsr_param = 0x1fee80ff; // or 0x9fc080ff, 0xdff18087
+ }
+
+ // Unmute the channels and route them for stereo output. You'll want to
+ // edit this if you are using more than 2 channels, and/or if you want to
+ // provide an option to output mono audio instead of stereo.
+ SPU_CHANNELS[0].vol_left = 0x3fff;
+ SPU_CHANNELS[0].vol_right = 0x0000;
+ SPU_CHANNELS[1].vol_left = 0x0000;
+ SPU_CHANNELS[1].vol_right = 0x3fff;
+
+ SPU_KEY_ON = CHANNEL_MASK;
+ spu_irq_handler();
+}
+
+/* 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[]) {
+ init_context(&ctx);
+
+ SHOW_STATUS("INITIALIZING\n");
+ SpuInit();
+ CdInit();
+ init_spu_channels();
+
+ SHOW_STATUS("LOCATING STREAM FILE\n");
+
+ CdlFILE file;
+ if (!CdSearchFile(&file, "\\STREAM.BIN"))
+ SHOW_ERROR("FAILED TO FIND STREAM.BIN\n");
+
+ SHOW_STATUS("BUFFERING STREAM\n");
+ init_stream(&file);
+ start_stream();
+
+ // Set up controller polling.
+ uint8_t pad_buff[2][34];
+ InitPAD(pad_buff[0], 34, pad_buff[1], 34);
+ StartPAD();
+ ChangeClearPAD(0);
+
+ uint16_t sample_rate = SAMPLE_RATE;
+ uint16_t last_buttons = 0xffff;
+
+ while (1) {
+ FntPrint(-1, "PLAYING SPU STREAM\n");
+ if (str_ctx.spu_pos >= CHUNK_SIZE)
+ FntPrint(-1, "STATUS: IDLE\n\n");
+ else if (!str_ctx.spu_pos)
+ FntPrint(-1, "STATUS: SEEKING\n\n");
+ else
+ FntPrint(-1, "STATUS: BUFFERING\n\n");
+
+ FntPrint(-1, "POSITION=%5d/%5d\n", str_ctx.pos, str_ctx.length);
+ FntPrint(-1, "BUFFERED=%5d/%5d\n", str_ctx.spu_pos, CHUNK_SIZE);
+ FntPrint(-1, "SMP RATE=%5d HZ\n\n", (sample_rate * 44100) >> 12);
+
+ FntPrint(-1, "[LEFT/RIGHT] SEEK\n");
+ FntPrint(-1, "[O] RESET POSITION\n");
+ FntPrint(-1, "[UP/DOWN] CHANGE SAMPLE RATE\n");
+ FntPrint(-1, "[X] RESET SAMPLE RATE\n");
+
+ FntFlush(-1);
+ display(&ctx);
+
+ // Check if a compatible controller is connected and handle button
+ // presses.
+ PADTYPE *pad = (PADTYPE *) pad_buff[0];
+ if (pad->stat)
+ continue;
+ if ((pad->type != 4) && (pad->type != 5) && (pad->type != 7))
+ continue;
+
+ // Seeking by an arbitrary number of sectors isn't a problem as
+ // spu_irq_handler() always realigns the counter.
+ if (!(pad->btn & PAD_LEFT))
+ str_ctx.pos -= 16;
+ if (!(pad->btn & PAD_RIGHT))
+ str_ctx.pos += 16;
+ if ((last_buttons & PAD_CIRCLE) && !(pad->btn & PAD_CIRCLE))
+ str_ctx.pos = 0;
+
+ if (!(pad->btn & PAD_DOWN) && (sample_rate > 0x400))
+ sample_rate -= 0x40;
+ if (!(pad->btn & PAD_UP) && (sample_rate < 0x2000))
+ sample_rate += 0x40;
+ if ((last_buttons & PAD_CROSS) && !(pad->btn & PAD_CROSS))
+ sample_rate = SAMPLE_RATE;
+
+ // Only set the sample rate registers if necessary.
+ if (pad->btn != 0xffff) {
+ for (uint32_t i = 0; i < NUM_CHANNELS; i++)
+ SPU_CHANNELS[i].freq = sample_rate;
+ }
+
+ last_buttons = pad->btn;
+ }
+
+ return 0;
+}
diff --git a/examples/sound/spustream/system.cnf b/examples/sound/spustream/system.cnf
new file mode 100644
index 0000000..0c4561a
--- /dev/null
+++ b/examples/sound/spustream/system.cnf
@@ -0,0 +1,4 @@
+BOOT=cdrom:\spustrm.exe;1
+TCB=4
+EVENT=10
+STACK=801FFFF0
diff --git a/examples/sound/vagsample/0proyt.h b/examples/sound/vagsample/0proyt.h
index 4402b9a..73629f9 100644
--- a/examples/sound/vagsample/0proyt.h
+++ b/examples/sound/vagsample/0proyt.h
@@ -12616,6 +12616,6 @@ unsigned char proyt[] = {
0xf2,0xf0,0xf2,0x15,0xc2,0x1b,0x00,0x00,0x01,0xe3,0x31,0x13,0xf3,0x1f,0xe2,
0x2f,0x13,0xd6,0x20,0x6e,0x2d,0x1b,0x00,0x03,0x42,0xc3,0x14,0x30,0x20,0x21,
0x21,0x32,0x0e,0x32,0x12,0x42,0xf0,0x39,0x01,0x13,0x12,0x04,0xb3,0x06,0x1f,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x77,0x77,0x77,0x77,0x77,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x05,0x77,0x77,0x77,0x77,0x77,
0x77,0x77,0x77,0x77,0x77,0x77,0x77,0x77,0x77
};
diff --git a/examples/sound/vagsample/CMakeLists.txt b/examples/sound/vagsample/CMakeLists.txt
index 1a15d9c..f37be97 100644
--- a/examples/sound/vagsample/CMakeLists.txt
+++ b/examples/sound/vagsample/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
vagsample
LANGUAGES C
diff --git a/examples/sound/vagsample/threedeeffeggzz.h b/examples/sound/vagsample/threedeeffeggzz.h
index ed0e098..f0815aa 100644
--- a/examples/sound/vagsample/threedeeffeggzz.h
+++ b/examples/sound/vagsample/threedeeffeggzz.h
@@ -15194,6 +15194,6 @@ unsigned char tdfx[] = {
0x0b,0x1e,0x21,0xa0,0x4f,0xe2,0x2d,0x17,0x00,0xd2,0x02,0x1d,0xd1,0xf0,0x21,
0xfa,0x2e,0xc4,0x1e,0xfe,0xf2,0x2f,0x24,0x17,0x00,0xef,0xee,0x1f,0xf2,0xce,
0x0d,0x25,0x01,0x3e,0xcb,0xe1,0x30,0xeb,0xc4,0x17,0x01,0x1f,0x1f,0xaf,0x22,
-0xfd,0xe0,0xf3,0x0b,0x2e,0xdd,0xc3,0xde,0x12,0x2d,0x07,0x00,0x77,0x77,0x77,
+0xfd,0xe0,0xf3,0x0b,0x2e,0xdd,0xc3,0xde,0x12,0x2d,0x07,0x05,0x77,0x77,0x77,
0x77,0x77,0x77,0x77,0x77,0x77,0x77,0x77,0x77,0x77,0x77
};
diff --git a/examples/system/childexec/CMakeLists.txt b/examples/system/childexec/CMakeLists.txt
index 88168e0..ca0c110 100644
--- a/examples/system/childexec/CMakeLists.txt
+++ b/examples/system/childexec/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
childexec
LANGUAGES C ASM
diff --git a/examples/system/console/CMakeLists.txt b/examples/system/console/CMakeLists.txt
index 6dc6154..d58f212 100644
--- a/examples/system/console/CMakeLists.txt
+++ b/examples/system/console/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
console
LANGUAGES C
diff --git a/examples/system/dynlink/CMakeLists.txt b/examples/system/dynlink/CMakeLists.txt
index 5834647..aae3bb3 100644
--- a/examples/system/dynlink/CMakeLists.txt
+++ b/examples/system/dynlink/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
dynlink
LANGUAGES C
diff --git a/examples/system/dynlink/display.c b/examples/system/dynlink/display.c
deleted file mode 100644
index 573f17c..0000000
--- a/examples/system/dynlink/display.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * PSn00bSDK dynamic linker example (utilities)
- * (C) 2021 spicyjpeg - MPL licensed
- */
-
-#include <psxgpu.h>
-
-#include "library/dll_common.h"
-
-#define SCREEN_XRES 320
-#define SCREEN_YRES 240
-
-#define BGCOLOR_R 48
-#define BGCOLOR_G 24
-#define BGCOLOR_B 0
-
-/* Display/GPU context utilities */
-
-void init_context(CONTEXT *ctx) {
- DB *db;
-
- ResetGraph(0);
- ctx->xres = SCREEN_XRES;
- ctx->yres = SCREEN_YRES;
- 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;
-
- // Set up the ordering tables and primitive buffers.
- db = &(ctx->db[0]);
- ctx->db_nextpri = db->p;
- ClearOTagR((u_long *) db->ot, OT_LEN);
-
- PutDrawEnv(&(db->draw));
- //PutDispEnv(&(db->disp));
-
- db = &(ctx->db[1]);
- ClearOTagR((u_long *) db->ot, OT_LEN);
-
- // Create a text stream at the top of the screen.
- FntLoad(960, 0);
- FntOpen(4, 12, 312, 32, 2, 256);
-}
-
-void display(CONTEXT *ctx) {
- DB *db;
-
- DrawSync(0);
- VSync(0);
- ctx->db_active ^= 1;
-
- db = &(ctx->db[ctx->db_active]);
- ctx->db_nextpri = db->p;
- ClearOTagR((u_long *) db->ot, OT_LEN);
-
- PutDrawEnv(&(db->draw));
- PutDispEnv(&(db->disp));
- SetDispMask(1);
-
- db = &(ctx->db[!ctx->db_active]);
- DrawOTag((u_long *) &(db->ot[OT_LEN - 1]));
-}
diff --git a/examples/system/dynlink/main.c b/examples/system/dynlink/main.c
index 6d93e71..690371e 100644
--- a/examples/system/dynlink/main.c
+++ b/examples/system/dynlink/main.c
@@ -11,14 +11,14 @@
* however this may be expanded in the future.
*
* Being able to introspect local symbols at runtime, in turn, allows us to use
- * the dl*() set of APIs to load, link and execute code from an external file
+ * another set of APIs to load, link and execute code from an external file
* (compiled with the dll.ld linker script). A dynamically-loaded library can
* reference and access any non-static function or variable within the main
* executable (and the libraries the main executable has been compiled with);
* the dynamic linker will automatically patch the DLL's code and resolve these
* references so that they point to the addresses listed in the map file. DLLs
* also have their own symbol tables, and any symbol in a DLL is accessible to
- * the main executable through dlsym().
+ * the main executable through DL_GetDLLSymbol().
*
* This example shows how DLLs can be loaded and unloaded at any time. Pressing
* START will unload the current DLL and load an alternate one on-the-fly. A
@@ -47,6 +47,7 @@
#include <psxgte.h>
#include <psxgpu.h>
#include <psxpad.h>
+#include <psxcd.h>
#include "library/dll_common.h"
@@ -67,14 +68,77 @@ const void *const DO_NOT_STRIP[] __attribute__((section(".dummy"))) = {
};
static const char *const DLL_FILENAMES[] = {
- "cdrom:CUBE.DLL;1",
- "cdrom:BALLS.DLL;1"
+ "\\CUBE.DLL;1",
+ "\\BALLS.DLL;1"
};
#define DLL_COUNT 2
-void init_context(CONTEXT *ctx);
-void display(CONTEXT *ctx);
+/* Display/GPU context utilities */
+
+#define SCREEN_XRES 320
+#define SCREEN_YRES 240
+
+#define BGCOLOR_R 48
+#define BGCOLOR_G 24
+#define BGCOLOR_B 0
+
+void init_context(CONTEXT *ctx) {
+ DB *db;
+
+ ResetGraph(0);
+ ctx->xres = SCREEN_XRES;
+ ctx->yres = SCREEN_YRES;
+ 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;
+
+ // Set up the ordering tables and primitive buffers.
+ db = &(ctx->db[0]);
+ ctx->db_nextpri = db->p;
+ ClearOTagR((u_long *) db->ot, OT_LEN);
+
+ PutDrawEnv(&(db->draw));
+ //PutDispEnv(&(db->disp));
+
+ db = &(ctx->db[1]);
+ ClearOTagR((u_long *) db->ot, OT_LEN);
+
+ // Create a text stream at the top of the screen.
+ FntLoad(960, 0);
+ FntOpen(4, 12, 312, 32, 2, 256);
+}
+
+void display(CONTEXT *ctx) {
+ DB *db;
+
+ DrawSync(0);
+ VSync(0);
+ ctx->db_active ^= 1;
+
+ db = &(ctx->db[ctx->db_active]);
+ ctx->db_nextpri = db->p;
+ ClearOTagR((u_long *) db->ot, OT_LEN);
+
+ PutDrawEnv(&(db->draw));
+ PutDispEnv(&(db->disp));
+ SetDispMask(1);
+
+ db = &(ctx->db[!ctx->db_active]);
+ DrawOTag((u_long *) &(db->ot[OT_LEN - 1]));
+}
/* Symbol overriding example */
@@ -118,8 +182,8 @@ void *custom_resolver(DLL *dll, const char *name) {
// Define a struct to store pointers to a DLL's functions into. This is not
// strictly required, however looking up symbols is a relatively slow operation
-// and the pointers returned by dlsym() should be saved and reused as much as
-// possible.
+// and the pointers returned by DL_GetDLLSymbol() should be saved and reused as
+// much as possible.
typedef struct {
void (*init)(CONTEXT *);
void (*render)(CONTEXT *, uint16_t buttons);
@@ -134,47 +198,79 @@ 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"); }
+// This is a simple function to read a CD-ROM file into memory (the dynamic
+// linker no longer provides APIs that take file paths directly). You might
+// want to replace this with code that e.g. loads the DLL from a compressed
+// archive or even from the serial port.
+size_t load_file(const char *filename, void **ptr) {
+ SHOW_STATUS("LOADING %s\n", filename);
+
+ CdlFILE file;
+ if (!CdSearchFile(&file, filename))
+ SHOW_ERROR("FAILED TO FIND %s\n", filename);
+
+ // Round up the file size so it matches the number of sectors occupied by
+ // the file (as CdRead() can only return entire sectors).
+ size_t len = (file.size + 2047) & 0xfffff800;
+ void *_ptr = malloc(len);
+ if (!_ptr)
+ SHOW_ERROR("FAILED TO ALLOCATE %d BYTES\n", len);
+
+ CdControl(CdlSetloc, &(file.pos), 0);
+ CdRead(len / 2048, _ptr, CdlModeSpeed);
+ if (CdReadSync(0, 0) < 0)
+ SHOW_ERROR("FAILED TO READ %s\n", filename);
+
+ *ptr = _ptr;
+ return file.size;
+}
+
void load_dll(const char *filename) {
+ // As we're passing RTLD_FREE_ON_DESTROY to DL_CreateDLL(), calling
+ // DL_DestroyDLL() will also deallocate the buffer the DLL was loaded into.
if (dll)
- dlclose(dll);
+ DL_DestroyDLL(dll);
- SHOW_STATUS("LOADING %s\n", filename);
+ void *ptr;
+ size_t len = load_file(filename, &ptr);
- dll = dlopen(filename, RTLD_LAZY);
+ dll = DL_CreateDLL(ptr, len, RTLD_LAZY | RTLD_FREE_ON_DESTROY);
if (!dll)
- SHOW_ERROR("FAILED TO LOAD %s\nERROR=%d\n", filename, (int32_t) dlerror());
+ SHOW_ERROR("FAILED TO PARSE %s\nERROR=%d\n", filename, (int32_t) DL_GetLastError());
- dll_api.init = dlsym(dll, "init");
- dll_api.render = dlsym(dll, "render");
+ dll_api.init = DL_GetDLLSymbol(dll, "init");
+ dll_api.render = DL_GetDLLSymbol(dll, "render");
printf("DLL init() @ %08x, render() @ %08x\n", dll_api.init, dll_api.render);
// Unfortunately, due to how position-independent code works, function
- // pointers returned by dlsym() can't be called directly. We have to use
- // the DL_CALL() macro instead, which sets up register $t9 to ensure the
- // function can locate and reference the DLL's relocation table.
+ // pointers returned by DL_GetDLLSymbol() can't be called directly. We have
+ // to use the DL_CALL() macro instead, which sets up register $t9 to ensure
+ // the function can locate and reference the DLL's relocation table.
DL_CALL(dll_api.init, &ctx);
}
int main(int argc, const char* argv[]) {
- // As DL_LoadSymbolMap() and dlopen() rely on BIOS file APIs, the BIOS CD
- // driver must be initialized by calling _InitCd() prior to loading the
- // symbol map (but after setting up the GPU, for some reason).
init_context(&ctx);
SHOW_STATUS("INITIALIZING CD\n");
- _InitCd();
+ CdInit();
+
+ // Load the symbol map file, let the dynamic linker parse it and then
+ // unload it.
+ void *ptr;
+ size_t len = load_file("\\MAIN.MAP;1", &ptr);
- SHOW_STATUS("LOADING SYMBOL MAP\n");
+ if (!DL_ParseSymbolMap(ptr, len))
+ SHOW_ERROR("FAILED TO PARSE SYMBOL MAP\nERROR=%d\n", (int32_t) DL_GetLastError());
- if (!DL_LoadSymbolMap("cdrom:MAIN.MAP;1"))
- SHOW_ERROR("FAILED TO LOAD SYMBOL MAP\nERROR=%d\n", (int32_t) dlerror());
+ free(ptr);
// Try to obtain a reference to a local function.
void (*_display)() = DL_GetSymbolByName("display");
if (!_display)
- SHOW_ERROR("FAILED TO LOOK UP LOCAL FUNCTION\nERROR=%d\n", (int32_t) dlerror());
+ SHOW_ERROR("FAILED TO LOOK UP LOCAL FUNCTION\nERROR=%d\n", (int32_t) DL_GetLastError());
printf("Symbol map test, display() @ %08x\n", _display);
@@ -220,7 +316,7 @@ int main(int argc, const char* argv[]) {
last_buttons = pad->btn;
}
- //dlclose(dll);
+ //DL_DestroyDLL(dll);
//DL_UnloadSymbolMap();
return 0;
}
diff --git a/examples/system/timer/CMakeLists.txt b/examples/system/timer/CMakeLists.txt
index 58daf9b..328e07e 100644
--- a/examples/system/timer/CMakeLists.txt
+++ b/examples/system/timer/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
timer
LANGUAGES C
diff --git a/examples/system/tty/CMakeLists.txt b/examples/system/tty/CMakeLists.txt
index 4e0ca36..0664502 100644
--- a/examples/system/tty/CMakeLists.txt
+++ b/examples/system/tty/CMakeLists.txt
@@ -3,10 +3,6 @@
cmake_minimum_required(VERSION 3.20)
-if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS})
- set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake)
-endif()
-
project(
tty
LANGUAGES C