diff --git a/cmake/targets/psp.cmake b/cmake/targets/psp.cmake index 5512444..2131c69 100644 --- a/cmake/targets/psp.cmake +++ b/cmake/targets/psp.cmake @@ -12,6 +12,17 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC mbedcrypto lzma m + pspdebug + pspdisplay + pspge + pspctrl + pspgu + pspaudio + pspaudiolib + psputility + pspvfpu + pspvram + psphprm ) target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE @@ -24,6 +35,8 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC DUSK_PSP DUSK_INPUT_GAMEPAD DUSK_PLATFORM_ENDIAN_LITTLE + DUSK_DISPLAY_WIDTH=480 + DUSK_DISPLAY_HEIGHT=272 ) # Postbuild, create .pbp file for PSP. diff --git a/docker/psp/Dockerfile b/docker/psp/Dockerfile index de09ebf..dff1cb8 100644 --- a/docker/psp/Dockerfile +++ b/docker/psp/Dockerfile @@ -2,6 +2,6 @@ FROM pspdev/pspdev:latest WORKDIR /workdir RUN apk add --no-cache \ python3 \ - py3-pip -VOLUME ["/workdir"] -CMD [ "/bin/bash", "-c", "./scripts/build-psp.sh" ] \ No newline at end of file + py3-pip \ + py3-dotenv +VOLUME ["/workdir"] \ No newline at end of file diff --git a/scripts/build-psp-docker.sh b/scripts/build-psp-docker.sh index f825cee..ecd103e 100755 --- a/scripts/build-psp-docker.sh +++ b/scripts/build-psp-docker.sh @@ -1,3 +1,3 @@ #!/bin/bash docker build -t dusk-psp -f docker/psp/Dockerfile . -docker run --rm -v $(pwd):/workdir dusk-psp \ No newline at end of file +docker run --rm -v $(pwd):/workdir dusk-psp /bin/bash -c "./scripts/build-psp.sh" \ No newline at end of file diff --git a/scripts/build-psp.sh b/scripts/build-psp.sh index f7a887e..c9901d4 100755 --- a/scripts/build-psp.sh +++ b/scripts/build-psp.sh @@ -6,5 +6,6 @@ fi mkdir -p build-psp cd build-psp -cmake .. -DDUSK_TARGET_SYSTEM=psp -DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake -make -j$(nproc) \ No newline at end of file +psp-cmake -DDUSK_TARGET_SYSTEM=psp -DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake -DBUILD_PRX=1 -DCMAKE_BUILD_TYPE=Debug .. +make +# make -j$(nproc) \ No newline at end of file diff --git a/src/dusk/display/display.h b/src/dusk/display/display.h index d6775f7..b75f954 100644 --- a/src/dusk/display/display.h +++ b/src/dusk/display/display.h @@ -17,8 +17,8 @@ #ifndef DUSK_DISPLAY_HEIGHT #error "DUSK_DISPLAY_HEIGHT must be defined" #endif - #define DISPLAY_WINDOW_WIDTH_DEFAULT DUSK_DISPLAY_WIDTH - #define DISPLAY_WINDOW_HEIGHT_DEFAULT DUSK_DISPLAY_HEIGHT + #define DUSK_DISPLAY_WIDTH_DEFAULT DUSK_DISPLAY_WIDTH + #define DUSK_DISPLAY_HEIGHT_DEFAULT DUSK_DISPLAY_HEIGHT #else #ifndef DUSK_DISPLAY_WIDTH_DEFAULT #error "DUSK_DISPLAY_WIDTH_DEFAULT must be defined." diff --git a/src/duskpsp/CMakeLists.txt b/src/duskpsp/CMakeLists.txt index 7407c40..1606ee9 100644 --- a/src/duskpsp/CMakeLists.txt +++ b/src/duskpsp/CMakeLists.txt @@ -9,5 +9,13 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME} ${CMAKE_CURRENT_LIST_DIR} ) +# Sources +target_sources(${DUSK_BINARY_TARGET_NAME} + PUBLIC + # hello.c +) + # Subdirs -add_subdirectory(debug) \ No newline at end of file +add_subdirectory(debug) +add_subdirectory(asset) +add_subdirectory(input) \ No newline at end of file diff --git a/src/duskpsp/asset/CMakeLists.txt b/src/duskpsp/asset/CMakeLists.txt new file mode 100644 index 0000000..412f7a8 --- /dev/null +++ b/src/duskpsp/asset/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + assetpbp.c + assetpsp.c +) \ No newline at end of file diff --git a/src/duskpsp/asset/assetpbp.c b/src/duskpsp/asset/assetpbp.c index e242056..5b970e0 100644 --- a/src/duskpsp/asset/assetpbp.c +++ b/src/duskpsp/asset/assetpbp.c @@ -1,87 +1,113 @@ -assertTrue(ENGINE.argc >= 1, "PSP requires launch argument."); +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ - // PSP is given either the prx OR the PBP file. - // In the format of "ms0:/PSP/GAME/DUSK/EBOOT.PBP" or "host0:/Dusk.prx" - // IF the file is the PBP file, we are loading directly on the PSP itself. - // IF the file is the .prx then we are debugging and fopen will return - // relative filepaths correctly, e.g. host0:/dusk.dsk will be on host. - if( - stringEndsWithCaseInsensitive(ENGINE.argv[0], ".pbp") || - ASSET_PBP_READ_PBP_FROM_HOST - ) { - const char_t *pbpPath = ( - ASSET_PBP_READ_PBP_FROM_HOST ? "./EBOOT.PBP" : ENGINE.argv[0] - ); - ASSET.pbpFile = fopen(pbpPath, "rb"); - if(ASSET.pbpFile == NULL) { - errorThrow("Failed to open PBP file: %s", pbpPath); - } +#include "asset/asset.h" +#include "assert/assert.h" +#include "util/memory.h" - // Get size of PBP file. - if(fseek(ASSET.pbpFile, 0, SEEK_END) != 0) { - fclose(ASSET.pbpFile); - errorThrow("Failed to seek to end of PBP file : %s", pbpPath); - } - size_t pbpSize = ftell(ASSET.pbpFile); +errorret_t assetInitPBP(const char_t *pbpPath) { + assertNotNull(pbpPath, "PBP path cannot be null."); + assertStrLenMin(pbpPath, 1, "PBP path cannot be empty."); + assertStrLenMax(pbpPath, FILENAME_MAX, "PBP path is too long."); - // Rewind to start - if(fseek(ASSET.pbpFile, 0, SEEK_SET) != 0) { - fclose(ASSET.pbpFile); - errorThrow("Failed to seek to start of PBP file : %s", pbpPath); - } + ASSET.platform.pbpFile = fopen(pbpPath, "rb"); + if(ASSET.platform.pbpFile == NULL) { + errorThrow("Failed to open PBP file: %s", pbpPath); + } - // Read the PBP header - size_t read = fread( - &ASSET.pbpHeader, - 1, - sizeof(assetpbp_t), - ASSET.pbpFile - ); - if(read != sizeof(assetpbp_t)) { - fclose(ASSET.pbpFile); - errorThrow("Failed to read PBP header", pbpPath); - } - if(memoryCompare( - ASSET.pbpHeader.signature, - ASSET_PBP_SIGNATURE, - sizeof(ASSET_PBP_SIGNATURE) - ) != 0) { - fclose(ASSET.pbpFile); - errorThrow("Invalid PBP signature in file: %s", pbpPath); - } + // Get size of PBP file. + if(fseek(ASSET.platform.pbpFile, 0, SEEK_END) != 0) { + fclose(ASSET.platform.pbpFile); + errorThrow("Failed to seek to end of PBP file : %s", pbpPath); + } - // If we seek to the PSAR offset, we can read the WAD file from there - if(fseek(ASSET.pbpFile, ASSET.pbpHeader.psarOffset, SEEK_SET) != 0) { - fclose(ASSET.pbpFile); - errorThrow("Failed to seek to PSAR offset in PBP file: %s", pbpPath); - } + size_t pbpSize = ftell(ASSET.platform.pbpFile); + if(pbpSize == -1L) { + fclose(ASSET.platform.pbpFile); + errorThrow("Failed to get size of PBP file : %s", pbpPath); + } - zip_uint64_t zipPsarOffset = (zip_uint64_t)ASSET.pbpHeader.psarOffset; - zip_int64_t zipPsarSize = (zip_int64_t)( - pbpSize - ASSET.pbpHeader.psarOffset - ); + if(pbpSize < sizeof(assetpbpheader_t)) { + fclose(ASSET.platform.pbpFile); + errorThrow("PBP file is too small to be valid: %s", pbpPath); + } - zip_source_t *psarSource = zip_source_filep_create( - ASSET.pbpFile, - zipPsarOffset, - zipPsarSize, - NULL - ); - if(psarSource == NULL) { - fclose(ASSET.pbpFile); - errorThrow("Failed to create zip source in PBP file: %s", pbpPath); - } - - ASSET.zip = zip_open_from_source( - psarSource, - ZIP_RDONLY, - NULL - ); - if(ASSET.zip == NULL) { - zip_source_free(psarSource); - fclose(ASSET.pbpFile); - errorThrow("Failed to open zip from PBP file: %s", pbpPath); - } + // Rewind to start + if(fseek(ASSET.platform.pbpFile, 0, SEEK_SET) != 0) { + fclose(ASSET.platform.pbpFile); + errorThrow("Failed to seek to start of PBP file : %s", pbpPath); + } - errorOk(); - } \ No newline at end of file + // Read the PBP header + size_t read = fread( + &ASSET.platform.pbpHeader, + 1, + sizeof(assetpbpheader_t), + ASSET.platform.pbpFile + ); + if(read != sizeof(assetpbpheader_t)) { + fclose(ASSET.platform.pbpFile); + errorThrow("Failed to read PBP header", pbpPath); + } + + if(memoryCompare( + ASSET.platform.pbpHeader.signature, + ASSET_PBP_SIGNATURE, + sizeof(ASSET_PBP_SIGNATURE) + ) != 0) { + fclose(ASSET.platform.pbpFile); + errorThrow("Invalid PBP signature in file: %s", pbpPath); + } + + // If we seek to the PSAR offset, we can read the WAD file from there. + // I'm not sure what PSAR was intended for, but it holds any user data we + // want, so I shoved the entire dusk wad there. + if(fseek( + ASSET.platform.pbpFile, ASSET.platform.pbpHeader.psarOffset, SEEK_SET + ) != 0) { + fclose(ASSET.platform.pbpFile); + errorThrow("Failed to seek to PSAR offset in PBP file: %s", pbpPath); + } + + zip_uint64_t zipPsarOffset = (zip_uint64_t)ASSET.platform.pbpHeader.psarOffset; + zip_int64_t zipPsarSize = (zip_int64_t)( + pbpSize - ASSET.platform.pbpHeader.psarOffset + ); + + zip_source_t *psarSource = zip_source_filep_create( + ASSET.platform.pbpFile, + zipPsarOffset, + zipPsarSize, + NULL + ); + if(psarSource == NULL) { + fclose(ASSET.platform.pbpFile); + errorThrow("Failed to create zip source in PBP file: %s", pbpPath); + } + + ASSET.zip = zip_open_from_source( + psarSource, + ZIP_RDONLY, + NULL + ); + if(ASSET.zip == NULL) { + zip_source_free(psarSource); + fclose(ASSET.platform.pbpFile); + errorThrow("Failed to open zip from PBP file: %s", pbpPath); + } + + errorOk(); +} + +errorret_t assetDisposePBP(void) { + if(ASSET.platform.pbpFile != NULL) { + fclose(ASSET.platform.pbpFile); + ASSET.platform.pbpFile = NULL; + } + + errorOk(); +} \ No newline at end of file diff --git a/src/duskpsp/asset/assetpbp.h b/src/duskpsp/asset/assetpbp.h index 512251d..88ca078 100644 --- a/src/duskpsp/asset/assetpbp.h +++ b/src/duskpsp/asset/assetpbp.h @@ -6,9 +6,8 @@ */ #pragma once -#include "dusk.h" +#include "error/error.h" -#define ASSET_PBP_READ_PBP_FROM_HOST 0 #define ASSET_PBP_SIGNATURE_SIZE 4 #define ASSET_PBP_SIGNATURE "\0PBP" @@ -28,4 +27,19 @@ typedef struct { typedef struct { FILE *pbpFile; assetpbpheader_t pbpHeader; -} assetpbp_t; \ No newline at end of file +} assetpbp_t; + +/** + * Initializes the PBP style asset system. + * + * @param pbpPath The file path to the PBP file to read. + * @returns An errorret_t indicating success or failure of the operation. + */ +errorret_t assetInitPBP(const char_t *pbpPath); + +/** + * Disposes the PBP style asset system. + * + * @returns An errorret_t indicating success or failure of the operation. + */ +errorret_t assetDisposePBP(void); \ No newline at end of file diff --git a/src/duskpsp/asset/assetplatform.h b/src/duskpsp/asset/assetplatform.h new file mode 100644 index 0000000..b7440a8 --- /dev/null +++ b/src/duskpsp/asset/assetplatform.h @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "assetpsp.h" + +typedef assetpsp_t assetplatform_t; +#define assetInitPlatform assetInitPSP +#define assetDisposePlatform assetDisposePSP \ No newline at end of file diff --git a/src/duskpsp/asset/assetpsp.c b/src/duskpsp/asset/assetpsp.c new file mode 100644 index 0000000..0d983bf --- /dev/null +++ b/src/duskpsp/asset/assetpsp.c @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "asset/asset.h" +#include "engine/engine.h" +#include "assert/assert.h" +#include "util/string.h" + +errorret_t assetInitPSP(void) { + assertTrue(ENGINE.argc >= 1, "PSP requires launch argument."); + + // PSP is given either the prx OR the PBP file. + // In the format of "ms0:/PSP/GAME/DUSK/EBOOT.PBP" or "host0:/Dusk.prx" + // IF the file is the PBP file, we are loading directly on the PSP itself. + // IF the file is the .prx then we are debugging and fopen will return + // relative filepaths correctly, e.g. host0:/dusk.dsk will be on host. + + // Originally I was going to allow host to read the .dsk directly but then + // I'd have to maintain two file implementations. + + const char_t *pbpPath; + if(stringEndsWithCaseInsensitive(ENGINE.argv[0], ASSET_PSP_EXTENSION_PBP)) { + pbpPath = ENGINE.argv[0]; + } else { + // In Debugging this would be next to host0:/Dusk.prx + pbpPath = "./EBOOT.PBP"; + } + + errorChain(assetInitPBP(pbpPath)); + errorOk(); +} + +errorret_t assetDisposePSP(void) { + errorChain(assetDisposePBP()); + errorOk(); +} \ No newline at end of file diff --git a/src/duskpsp/asset/assetpsp.h b/src/duskpsp/asset/assetpsp.h new file mode 100644 index 0000000..badcd31 --- /dev/null +++ b/src/duskpsp/asset/assetpsp.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "assetpbp.h" + +#define ASSET_PSP_EXTENSION_PBP ".pbp" + +typedef assetpbp_t assetpsp_t; + +/** + * Initializes the PSP style asset system. + * + * @returns An errorret_t indicating success or failure of the operation. + */ +errorret_t assetInitPSP(void); + +/** + * Disposes the PSP style asset system. + * + * @returns An errorret_t indicating success or failure of the operation. + */ +errorret_t assetDisposePSP(void); \ No newline at end of file diff --git a/src/duskpsp/debug/debug.c b/src/duskpsp/debug/debug.c index ef19a85..fbb6732 100644 --- a/src/duskpsp/debug/debug.c +++ b/src/duskpsp/debug/debug.c @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT */ -#include "psp.h" +#include "debug/debug.h" void debugPrint(const char_t *message, ...) { FILE *file = fopen("ms0:/PSP/GAME/Dusk/debug.log", "a"); diff --git a/src/duskpsp/display/displayplatform.h b/src/duskpsp/display/displayplatform.h new file mode 100644 index 0000000..b193667 --- /dev/null +++ b/src/duskpsp/display/displayplatform.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "display/displaysdl2.h" + +typedef displaysdl2_t displayplatform_t; + +#define displayPlatformInit displaySDL2Init +#define displayPlatformUpdate displaySDL2Update +#define displayPlatformSwap displaySDL2Swap +#define displayPlatformDispose displaySDL2Dispose \ No newline at end of file diff --git a/src/duskpsp/duskplatform.h b/src/duskpsp/duskplatform.h index 4a0dde2..96ea9ac 100644 --- a/src/duskpsp/duskplatform.h +++ b/src/duskpsp/duskplatform.h @@ -10,4 +10,5 @@ #include #include #include -#include \ No newline at end of file +#include +#include "dusksdl2.h" \ No newline at end of file diff --git a/src/duskpsp/hello.c b/src/duskpsp/hello.c new file mode 100644 index 0000000..e097812 --- /dev/null +++ b/src/duskpsp/hello.c @@ -0,0 +1,41 @@ +#include +#include +#include + +// PSP_MODULE_INFO is required +PSP_MODULE_INFO("Hello World", 0, 1, 0); +PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER); + +int exit_callback(int arg1, int arg2, void *common) { + sceKernelExitGame(); + return 0; +} + +int callback_thread(SceSize args, void *argp) { + int cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL); + sceKernelRegisterExitCallback(cbid); + sceKernelSleepThreadCB(); + return 0; +} + +int setup_callbacks(void) { + int thid = sceKernelCreateThread("update_thread", callback_thread, 0x11, 0xFA0, 0, 0); + if(thid >= 0) + sceKernelStartThread(thid, 0, 0); + return thid; +} + +int main(void) { + // Use above functions to make exiting possible + setup_callbacks(); + + // Print Hello World! on a debug screen on a loop + pspDebugScreenInit(); + while(1) { + pspDebugScreenSetXY(0, 0); + pspDebugScreenPrintf("Hello World!"); + sceDisplayWaitVblankStart(); + } + + return 0; +} diff --git a/src/duskpsp/input/CMakeLists.txt b/src/duskpsp/input/CMakeLists.txt new file mode 100644 index 0000000..6fdee7f --- /dev/null +++ b/src/duskpsp/input/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + inputpsp.c +) \ No newline at end of file diff --git a/src/duskpsp/input/inputpsp.c b/src/duskpsp/input/inputpsp.c new file mode 100644 index 0000000..3d14b85 --- /dev/null +++ b/src/duskpsp/input/inputpsp.c @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "input/input.h" + +inputbuttondata_t INPUT_BUTTON_DATA[] = { + { .name = "triangle", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_Y } }, + { .name = "cross", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_A } }, + { .name = "circle", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_B } }, + { .name = "square", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_X } }, + { .name = "start", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_START } }, + { .name = "select", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_BACK } }, + { .name = "up", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_UP } }, + { .name = "down", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_DOWN } }, + { .name = "left", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_LEFT } }, + { .name = "right", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_RIGHT } }, + { .name = "l", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_LEFTSHOULDER } }, + { .name = "r", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER } }, + + { .name = "lstick_down", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTX, .positive = true } } }, + { .name = "lstick_up", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTX, .positive = false } } }, + { .name = "lstick_right", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTY, .positive = true } } }, + { .name = "lstick_left", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTY, .positive = false } } }, + + { .name = NULL } +}; \ No newline at end of file