From da54226f8a52a6d13e797b58bdfe288aab1ed38f Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Fri, 18 Jul 2025 03:21:54 -0300 Subject: [PATCH 01/49] Implement controller pak functions --- librecomp/src/pak.cpp | 269 ++++++++++++++++++++-- ultramodern/include/ultramodern/ultra64.h | 7 + 2 files changed, 255 insertions(+), 21 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 0be1513..df86301 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -1,51 +1,278 @@ +#include +#include #include "ultramodern/ultra64.h" #include "ultramodern/ultramodern.hpp" #include "recomp.h" #include "helpers.hpp" -extern "C" void osPfsInitPak_recomp(uint8_t * rdram, recomp_context* ctx) { - ctx->r2 = 1; // PFS_ERR_NOPACK +typedef struct ControllerPak { + OSPfsState state; + FILE* file; +} ControllerPak; + +static ControllerPak sControllerPak[16]; +static int sFileSelect = 0; + +void ByteSwapFile(u8* buffer, size_t size) { + uint8_t c0, c1, c2, c3; + + for (size_t i = 0; i < size; i += 4) { + c0 = buffer[i + 0]; + c1 = buffer[i + 1]; + c2 = buffer[i + 2]; + c3 = buffer[i + 3]; + + buffer[i + 3] = c0; + buffer[i + 2] = c1; + buffer[i + 1] = c2; + buffer[i + 0] = c3; + } } -extern "C" void osPfsFreeBlocks_recomp(uint8_t * rdram, recomp_context * ctx) { - ctx->r2 = 1; // PFS_ERR_NOPACK +// s32 osPfsIsPlug(OSMesgQueue* mq, u8* pattern) +extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { + MEM_B(0, ctx->r5) = 1; // *pattern = 1; + ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsAllocateFile_recomp(uint8_t * rdram, recomp_context * ctx) { - ctx->r2 = 1; // PFS_ERR_NOPACK +// s32 osPfsInit(OSMesgQueue* queue, OSPfs* pfs, int channel) { +extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { + OSMesgQueue* queue = _arg<0, OSMesgQueue*>(rdram, ctx); + OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); + s32 channel = _arg<2, s32>(rdram, ctx); + + pfs->queue = (int32_t) queue; + pfs->channel = channel; + pfs->status = 0x1; + + ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsDeleteFile_recomp(uint8_t * rdram, recomp_context * ctx) { - ctx->r2 = 1; // PFS_ERR_NOPACK +// s32 osPfsInitPak(OSMesgQueue* queue, OSPfs* pfs, int channel); +extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { + OSMesgQueue* queue = _arg<0, OSMesgQueue*>(rdram, ctx); + OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); + s32 channel = _arg<2, s32>(rdram, ctx); + + pfs->queue = (int32_t) queue; + pfs->channel = channel; + pfs->status = 0x1; + + ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsFileState_recomp(uint8_t * rdram, recomp_context * ctx) { - ctx->r2 = 1; // PFS_ERR_NOPACK +// s32 osPfsFreeBlocks(OSPfs* pfs, s32* bytes_not_used); +extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { + s32* bytes_not_used = _arg<1, s32*>(rdram, ctx); + + *bytes_not_used = 123 << 8; + + ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsFindFile_recomp(uint8_t * rdram, recomp_context * ctx) { - ctx->r2 = 1; // PFS_ERR_NOPACK +// TODO: VALIDATE +// Can't really be validated without a valid controller pak file system header, which is only used +// by the controller pak manager present in games by holding START on boot and not necessary for recomp. +// s32 osPfsAllocateFile(OSPfs* pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, +// int file_size_in_bytes, s32* file_no); +extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { + u16 company_code = _arg<1, u16>(rdram, ctx); + u32 game_code = _arg<2, u32>(rdram, ctx); + u8* game_name = _arg<3, u8*>(rdram, ctx); + u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + s32 file_size_in_bytes = (s32) MEM_W(0x14, ctx->r29); + s32* file_no = TO_PTR(s32, MEM_W(0x18, ctx->r29)); + + ControllerPak* pak = &sControllerPak[*file_no]; + + printf("osPfsAllocateFile_recomp:\n"); + + *file_no = sFileSelect++; + + char filename[100]; + sprintf(filename, "controllerPak_file_%d.sav", *file_no); + pak->file = fopen(filename, "wb+"); + + file_size_in_bytes = (file_size_in_bytes + 31) & ~31; + + u8* zero_block = (u8*) malloc(file_size_in_bytes); + memset(zero_block, 0, file_size_in_bytes); + fwrite(zero_block, file_size_in_bytes, 1, pak->file); + + pak->state.company_code = company_code; + pak->state.game_code = game_code; + strcpy(pak->state.game_name, (const char*) game_name); + strcpy(pak->state.ext_name, (const char*) ext_name); + + ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsReadWriteFile_recomp(uint8_t * rdram, recomp_context * ctx) { - ctx->r2 = 1; // PFS_ERR_NOPACK +// TODO: VALIDATE +// Can't really be validated without a valid controller pak file system header, which is only used +// by the controller pak manager present in games by holding START on boot and not necessary for recomp. +// s32 osPfsDeleteFile(OSPfs* pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name); +extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { + u16 company_code = _arg<1, u16>(rdram, ctx); + u32 game_code = _arg<2, u32>(rdram, ctx); + u8* game_name = _arg<3, u8*>(rdram, ctx); + u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + s32 file_no; + + printf("osPfsDeleteFile_recomp:\n"); + + for (size_t i = 0; i < ARRAYSIZE(sControllerPak); i++) { + if ((sControllerPak[i].state.company_code = company_code) && (sControllerPak[i].state.game_code == game_code) && + (strcmp(sControllerPak[i].state.game_name, (const char*) game_name) == 0) && + strcmp(sControllerPak[i].state.ext_name, (const char*) ext_name) == 0) { + file_no = i; + + char filename[100]; + sprintf(filename, "controllerPak_file_%d.sav", file_no); + remove(filename); + + ctx->r2 = 0; // PFS_NO_ERROR + printf("Returned 0: FILE DELETED\n\n"); + return; + } else { + ctx->r2 = 1; // PFS_ERR_NOPACK + printf("Returned 1: FILE NOT FOUND\n\n"); + } + } } -extern "C" void osPfsChecker_recomp(uint8_t * rdram, recomp_context * ctx) { - ctx->r2 = 1; // PFS_ERR_NOPACK +bool IsFileEmpty(FILE* file, s32 file_no) { + u8 data_buffer[256]; + + fseek(file, 0, SEEK_SET); + fread(data_buffer, 0x100, 1, file); + + long current = ftell(file); + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, current, SEEK_SET); + + return size == 0; } -extern "C" void osPfsNumFiles_recomp(uint8_t * rdram, recomp_context * ctx) { +// s32 osPfsFileState(OSPfs* pfs, s32 file_no, OSPfsState* state); +extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { + s32 file_no = _arg<1, s32>(rdram, ctx); + OSPfsState* state = _arg<2, OSPfsState*>(rdram, ctx); + + ControllerPak* pak = &sControllerPak[file_no]; + + char filename[100]; + sprintf(filename, "controllerPak_file_%d.sav", file_no); + pak->file = fopen(filename, "rb+"); + if (pak->file == NULL) { + pak->file = fopen(filename, "wb+"); + } + + if (!IsFileEmpty(pak->file, file_no)) { + state->file_size = pak->state.file_size; + state->company_code = pak->state.company_code; + state->game_code = pak->state.game_code; + + for (size_t j = 0; j < ARRAYSIZE(pak->state.game_name); j++) { + state->game_name[j] = pak->state.game_name[j]; + } + for (size_t j = 0; j < ARRAYSIZE(pak->state.ext_name); j++) { + state->ext_name[j] = pak->state.ext_name[j]; + } + + fclose(pak->file); + ctx->r2 = 0; // PFS_NO_ERROR + } else { + fclose(pak->file); + remove(filename); + ctx->r2 = 1; // PFS_ERR_NOPACK + } +} + +// s32 osPfsFindFile(OSPfs* pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, s32* file_no); +extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { + u16 company_code = _arg<1, u16>(rdram, ctx); + u32 game_code = _arg<2, u32>(rdram, ctx); + u8* game_name = _arg<3, u8*>(rdram, ctx); + u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + s32* file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); + + for (size_t i = 0; i < ARRAYSIZE(sControllerPak); i++) { + if ((sControllerPak[i].state.game_code == game_code) && + (sControllerPak[i].state.company_code == company_code) && + (strcmp(sControllerPak[i].state.game_name, (const char*) game_name) == 0) && + strcmp(sControllerPak[i].state.ext_name, (const char*) ext_name) == 0) { + // File found + *file_no = i; + ctx->r2 = 0; // PFS_NO_ERROR + return; + } + } + + // Open file + *file_no = sFileSelect++; + + char filename[100]; + sprintf(filename, "controllerPak_file_%d.sav", *file_no); + sControllerPak[*file_no].file = fopen(filename, "rb+"); + if (sControllerPak[*file_no].file == NULL) { + sControllerPak[*file_no].file = fopen(filename, "wb+"); + } + + sControllerPak[*file_no].state.company_code = company_code; + sControllerPak[*file_no].state.game_code = game_code; + strcpy(sControllerPak[*file_no].state.game_name, (const char*) game_name); + strcpy(sControllerPak[*file_no].state.ext_name, (const char*) ext_name); + + ctx->r2 = 0; // PFS_NO_ERROR +} + +// s32 osPfsReadWriteFile(OSPfs* pfs, s32 file_no, u8 flag, int offset, int size_in_bytes, u8* data_buffer); +extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { + s32 file_no = _arg<1, s32>(rdram, ctx); + u8 flag = _arg<2, u8>(rdram, ctx); + s32 offset = _arg<3, s32>(rdram, ctx); + s32 size_in_bytes = (s32) MEM_W(0x10, ctx->r29); + u8* data_buffer = TO_PTR(u8, MEM_W(0x14, ctx->r29)); + + ControllerPak* pak = &sControllerPak[file_no]; + + if (flag == 0) { + fseek(pak->file, offset, SEEK_SET); + fread(data_buffer, size_in_bytes, 1, pak->file); + ByteSwapFile(data_buffer, size_in_bytes); + } else { + ByteSwapFile(data_buffer, size_in_bytes); + fseek(pak->file, offset, SEEK_SET); + fwrite(data_buffer, size_in_bytes, 1, pak->file); + } + + ctx->r2 = 0; // PFS_NO_ERROR +} + +extern "C" void osPfsChecker_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = 0; // PFS_NO_ERROR +} + +// s32 osPfsNumFiles(OSPfs* pfs, s32* max_files, s32* files_used); +extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { s32* max_files = _arg<1, s32*>(rdram, ctx); s32* files_used = _arg<2, s32*>(rdram, ctx); - *max_files = 0; - *files_used = 0; + u8 files = 0; + for (size_t i = 0; i < ARRAYSIZE(sControllerPak); i++) { + if ((sControllerPak[i].state.company_code != 0) && (sControllerPak[i].state.game_code != 0)) { + files++; + } + } - _return(ctx, 1); // PFS_ERR_NOPACK + *files_used = files; + *max_files = ARRAYSIZE(sControllerPak); + + ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsRepairId_recomp(uint8_t * rdram, recomp_context * ctx) { +extern "C" void osPfsRepairId_recomp(uint8_t* rdram, recomp_context* ctx) { _return(ctx, 1); // PFS_ERR_NOPACK } diff --git a/ultramodern/include/ultramodern/ultra64.h b/ultramodern/include/ultramodern/ultra64.h index 71a412f..8b136cf 100644 --- a/ultramodern/include/ultramodern/ultra64.h +++ b/ultramodern/include/ultramodern/ultra64.h @@ -235,6 +235,13 @@ typedef struct { u8 banks; } OSPfs; +typedef struct { + /* 0x00 */ u32 file_size; /* bytes */ + /* 0x04 */ u32 game_code; + /* 0x08 */ u16 company_code; + /* 0x0C */ char ext_name[4]; + /* 0x10 */ char game_name[16]; +} OSPfsState; // size = 0x20 // Controller From ed7db194c4af11cda4dd714e90eca8644c0663f1 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Fri, 18 Jul 2025 03:45:47 -0300 Subject: [PATCH 02/49] get rid of file_no in IsFileEmpty() --- librecomp/src/pak.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index df86301..9301286 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -141,7 +141,7 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { } } -bool IsFileEmpty(FILE* file, s32 file_no) { +bool IsFileEmpty(FILE* file) { u8 data_buffer[256]; fseek(file, 0, SEEK_SET); @@ -169,7 +169,7 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { pak->file = fopen(filename, "wb+"); } - if (!IsFileEmpty(pak->file, file_no)) { + if (!IsFileEmpty(pak->file)) { state->file_size = pak->state.file_size; state->company_code = pak->state.company_code; state->game_code = pak->state.game_code; From 7e4a78680a97e36344338a251f8a7518b7bb129b Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Sat, 19 Jul 2025 23:22:06 -0300 Subject: [PATCH 03/49] WIP C++ changes --- librecomp/src/pak.cpp | 86 ++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 9301286..ab5202d 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -8,7 +8,7 @@ typedef struct ControllerPak { OSPfsState state; - FILE* file; + std::fstream file; } ControllerPak; static ControllerPak sControllerPak[16]; @@ -84,6 +84,9 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { s32 file_size_in_bytes = (s32) MEM_W(0x14, ctx->r29); s32* file_no = TO_PTR(s32, MEM_W(0x18, ctx->r29)); + printf("osPfsAllocateFile_recomp was called!\n"); + assert(false); + #if 0 ControllerPak* pak = &sControllerPak[*file_no]; printf("osPfsAllocateFile_recomp:\n"); @@ -92,7 +95,8 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", *file_no); - pak->file = fopen(filename, "wb+"); + // pak->file = fopen(filename, "wb+"); + //pak->file.open() file_size_in_bytes = (file_size_in_bytes + 31) & ~31; @@ -104,7 +108,7 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { pak->state.game_code = game_code; strcpy(pak->state.game_name, (const char*) game_name); strcpy(pak->state.ext_name, (const char*) ext_name); - + #endif ctx->r2 = 0; // PFS_NO_ERROR } @@ -141,50 +145,48 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { } } -bool IsFileEmpty(FILE* file) { - u8 data_buffer[256]; - - fseek(file, 0, SEEK_SET); - fread(data_buffer, 0x100, 1, file); - - long current = ftell(file); - fseek(file, 0, SEEK_END); - long size = ftell(file); - fseek(file, current, SEEK_SET); +bool IsFileEmpty(std::fstream& file) { + file.seekg(0, std::ios::end); + long size = file.tellg(); + file.seekg(0, std::ios::beg); return size == 0; } - +// f.read(ptr, size) and f.write(ptr, size); // s32 osPfsFileState(OSPfs* pfs, s32 file_no, OSPfsState* state); extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { s32 file_no = _arg<1, s32>(rdram, ctx); OSPfsState* state = _arg<2, OSPfsState*>(rdram, ctx); - ControllerPak* pak = &sControllerPak[file_no]; + ControllerPak& pak = sControllerPak[file_no]; char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", file_no); - pak->file = fopen(filename, "rb+"); - if (pak->file == NULL) { - pak->file = fopen(filename, "wb+"); + // pak.file = fopen(filename, "rb+"); + pak.file.open(filename, std::ios::binary | std::ios::app); + if (!pak.file.good()) { + printf("File's not good\n"); + assert(false); } - if (!IsFileEmpty(pak->file)) { - state->file_size = pak->state.file_size; - state->company_code = pak->state.company_code; - state->game_code = pak->state.game_code; + if (!IsFileEmpty(pak.file)) { + state->file_size = pak.state.file_size; + state->company_code = pak.state.company_code; + state->game_code = pak.state.game_code; - for (size_t j = 0; j < ARRAYSIZE(pak->state.game_name); j++) { - state->game_name[j] = pak->state.game_name[j]; + for (size_t j = 0; j < ARRAYSIZE(pak.state.game_name); j++) { + state->game_name[j] = pak.state.game_name[j]; } - for (size_t j = 0; j < ARRAYSIZE(pak->state.ext_name); j++) { - state->ext_name[j] = pak->state.ext_name[j]; + for (size_t j = 0; j < ARRAYSIZE(pak.state.ext_name); j++) { + state->ext_name[j] = pak.state.ext_name[j]; } - fclose(pak->file); + // fclose(pak.file); + pak.file.close(); ctx->r2 = 0; // PFS_NO_ERROR } else { - fclose(pak->file); + // fclose(pak.file); + pak.file.close(); remove(filename); ctx->r2 = 1; // PFS_ERR_NOPACK } @@ -215,9 +217,13 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", *file_no); - sControllerPak[*file_no].file = fopen(filename, "rb+"); - if (sControllerPak[*file_no].file == NULL) { - sControllerPak[*file_no].file = fopen(filename, "wb+"); + // sControllerPak[*file_no].file = fopen(filename, "rb+"); + // if (sControllerPak[*file_no].file == NULL) { + // sControllerPak[*file_no].file = fopen(filename, "wb+"); + // } + sControllerPak[*file_no].file.open(filename, std::ios::binary | std::ios::app); + if (!sControllerPak[*file_no].file.good()) { + assert(false); } sControllerPak[*file_no].state.company_code = company_code; @@ -236,16 +242,22 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { s32 size_in_bytes = (s32) MEM_W(0x10, ctx->r29); u8* data_buffer = TO_PTR(u8, MEM_W(0x14, ctx->r29)); - ControllerPak* pak = &sControllerPak[file_no]; + ControllerPak& pak = sControllerPak[file_no]; if (flag == 0) { - fseek(pak->file, offset, SEEK_SET); - fread(data_buffer, size_in_bytes, 1, pak->file); + // fseek(pak.file, offset, SEEK_SET); + pak.file.seekg(offset, std::ios::beg); + // fread(data_buffer, size_in_bytes, 1, pak.file); + pak.file.read((char*) data_buffer, size_in_bytes); + // TODO: use a separate buffer for holding the swapped memory ByteSwapFile(data_buffer, size_in_bytes); } else { - ByteSwapFile(data_buffer, size_in_bytes); - fseek(pak->file, offset, SEEK_SET); - fwrite(data_buffer, size_in_bytes, 1, pak->file); + // TODO: use a separate buffer for holding the swapped memory + ByteSwapFile(data_buffer, size_in_bytes); + // fseek(pak.file, offset, SEEK_SET); + pak.file.seekg(offset, std::ios::beg); + // fwrite(data_buffer, size_in_bytes, 1, pak.file); + pak.file.write((char*) data_buffer, size_in_bytes); } ctx->r2 = 0; // PFS_NO_ERROR From 118ad933de78563919f2c5d1378175824ea0e956 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Sat, 19 Jul 2025 23:49:59 -0300 Subject: [PATCH 04/49] WIP C++ changes --- librecomp/src/pak.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index ab5202d..394ee3e 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -86,7 +86,7 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { printf("osPfsAllocateFile_recomp was called!\n"); assert(false); - #if 0 +#if 0 ControllerPak* pak = &sControllerPak[*file_no]; printf("osPfsAllocateFile_recomp:\n"); @@ -108,7 +108,7 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { pak->state.game_code = game_code; strcpy(pak->state.game_name, (const char*) game_name); strcpy(pak->state.ext_name, (const char*) ext_name); - #endif +#endif ctx->r2 = 0; // PFS_NO_ERROR } @@ -163,7 +163,7 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", file_no); // pak.file = fopen(filename, "rb+"); - pak.file.open(filename, std::ios::binary | std::ios::app); + pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); if (!pak.file.good()) { printf("File's not good\n"); assert(false); @@ -221,7 +221,7 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { // if (sControllerPak[*file_no].file == NULL) { // sControllerPak[*file_no].file = fopen(filename, "wb+"); // } - sControllerPak[*file_no].file.open(filename, std::ios::binary | std::ios::app); + sControllerPak[*file_no].file.open(filename, std::ios::binary | std::ios::in | std::ios::out); if (!sControllerPak[*file_no].file.good()) { assert(false); } @@ -253,7 +253,7 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { ByteSwapFile(data_buffer, size_in_bytes); } else { // TODO: use a separate buffer for holding the swapped memory - ByteSwapFile(data_buffer, size_in_bytes); + ByteSwapFile(data_buffer, size_in_bytes); // fseek(pak.file, offset, SEEK_SET); pak.file.seekg(offset, std::ios::beg); // fwrite(data_buffer, size_in_bytes, 1, pak.file); From b18f6b3ff895dbf6e050ef17f38a73b778007fc7 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Sun, 20 Jul 2025 19:30:14 -0300 Subject: [PATCH 05/49] Refactor. State: OK --- librecomp/src/pak.cpp | 491 +++++++++++++++++++++++++++++++++--------- 1 file changed, 384 insertions(+), 107 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 394ee3e..6014714 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -6,12 +6,13 @@ #include "recomp.h" #include "helpers.hpp" +#define MAX_FILES 16 typedef struct ControllerPak { OSPfsState state; std::fstream file; + std::fstream header; } ControllerPak; -static ControllerPak sControllerPak[16]; static int sFileSelect = 0; void ByteSwapFile(u8* buffer, size_t size) { @@ -30,6 +31,9 @@ void ByteSwapFile(u8* buffer, size_t size) { } } +void Pak_WriteHeader(OSPfsState state, s32 file_no) { +} + // s32 osPfsIsPlug(OSMesgQueue* mq, u8* pattern) extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { MEM_B(0, ctx->r5) = 1; // *pattern = 1; @@ -46,6 +50,13 @@ extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { pfs->channel = channel; pfs->status = 0x1; + ControllerPak pak; + + if (!std::filesystem::exists("controllerPak_header.sav")) { + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); + pak.header.close(); + } + ctx->r2 = 0; // PFS_NO_ERROR } @@ -66,7 +77,51 @@ extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { s32* bytes_not_used = _arg<1, s32*>(rdram, ctx); - *bytes_not_used = 123 << 8; + // locals: + s32 usedSpace = 0; + + ControllerPak pak; + + // *bytes_not_used = 123 << 8; + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + printf("file is not good!\n"); + assert(false); + } + if (!pak.header.is_open()) { + printf("file isn't open!\n"); + assert(false); + } + + for (size_t i = 0; i < 16; i++) { + u32 seek = i * sizeof(OSPfsState); + s32 file_size_in_bytes; + u32 game_code; + u16 company_code; + + // file_size + pak.header.seekg(seek + 0x0, std::ios::beg); + pak.header.read((char*) &file_size_in_bytes, sizeof(file_size_in_bytes)); + // game_code + pak.header.seekg(seek + 0x4, std::ios::beg); + pak.header.read((char*) &game_code, sizeof(game_code)); + // company_code + pak.header.seekg(seek + 0x08, std::ios::beg); + pak.header.read((char*) &company_code, sizeof(company_code)); + + if ((company_code == 0) || (game_code == 0)) { + continue; + } else { + usedSpace += file_size_in_bytes >> 8; + file_size_in_bytes = game_code = company_code = 0; + } + } + + pak.header.close(); + + *bytes_not_used = (123 - usedSpace) << 8; ctx->r2 = 0; // PFS_NO_ERROR } @@ -84,112 +139,177 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { s32 file_size_in_bytes = (s32) MEM_W(0x14, ctx->r29); s32* file_no = TO_PTR(s32, MEM_W(0x18, ctx->r29)); - printf("osPfsAllocateFile_recomp was called!\n"); - assert(false); + if ((company_code == 0) || (game_code == 0)) { + ctx->r2 = 5; // PFS_ERR_INVALID + return; + } + + ControllerPak pak; + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + printf("file is not good!\n"); + assert(false); + } + if (!pak.header.is_open()) { + printf("file isn't open!\n"); + assert(false); + } + u8 freeFileIndex = 0; #if 0 - ControllerPak* pak = &sControllerPak[*file_no]; + /* Search for a free slot */ + for (size_t i = 0; i < 16; i++) { + u32 seek = i * sizeof(OSPfsState); + u32 game_code_; + u16 company_code_; - printf("osPfsAllocateFile_recomp:\n"); + // game_code + pak.header.seekg(seek + 0x4, std::ios::beg); + pak.header.read((char*) &game_code_, sizeof(game_code_)); + // company_code + pak.header.seekg(seek + 0x08, std::ios::beg); + pak.header.read((char*) &company_code_, sizeof(company_code_)); - *file_no = sFileSelect++; + if ((company_code_ == 0) || (game_code_ == 0)) { + freeFileIndex = i; + break; + } + + game_code_ = company_code_ = 0; + } +#endif + /* Set file parameters to header */ + freeFileIndex = 0; + u32 seek = freeFileIndex * 0x20; + + // file_size + pak.header.seekp(seek + 0x0, std::ios::beg); + pak.header.write((char*) &file_size_in_bytes, sizeof(file_size_in_bytes)); + // game_code + pak.header.seekp(seek + 0x4, std::ios::beg); + pak.header.write((char*) &game_code, sizeof(game_code)); + // company_code + pak.header.seekp(seek + 0x08, std::ios::beg); + pak.header.write((char*) &company_code, sizeof(company_code)); + // ext_name + pak.header.seekp(seek + 0x0C, std::ios::beg); + pak.header.write((char*) ext_name, 4); + // game_name + pak.header.seekp(seek + 0x10, std::ios::beg); + pak.header.write((char*) game_name, 16); + + pak.header.close(); + + /* Create empty file */ char filename[100]; - sprintf(filename, "controllerPak_file_%d.sav", *file_no); - // pak->file = fopen(filename, "wb+"); - //pak->file.open() + sprintf(filename, "controllerPak_file_%d.sav", freeFileIndex); + pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); file_size_in_bytes = (file_size_in_bytes + 31) & ~31; u8* zero_block = (u8*) malloc(file_size_in_bytes); memset(zero_block, 0, file_size_in_bytes); - fwrite(zero_block, file_size_in_bytes, 1, pak->file); - pak->state.company_code = company_code; - pak->state.game_code = game_code; - strcpy(pak->state.game_name, (const char*) game_name); - strcpy(pak->state.ext_name, (const char*) ext_name); -#endif + pak.file.seekp(0, std::ios::beg); + pak.file.write((char*) zero_block, file_size_in_bytes); + + free(zero_block); + + pak.file.close(); + + *file_no = freeFileIndex; + ctx->r2 = 0; // PFS_NO_ERROR } -// TODO: VALIDATE -// Can't really be validated without a valid controller pak file system header, which is only used -// by the controller pak manager present in games by holding START on boot and not necessary for recomp. -// s32 osPfsDeleteFile(OSPfs* pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name); -extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { - u16 company_code = _arg<1, u16>(rdram, ctx); - u32 game_code = _arg<2, u32>(rdram, ctx); - u8* game_name = _arg<3, u8*>(rdram, ctx); - u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - s32 file_no; - - printf("osPfsDeleteFile_recomp:\n"); - - for (size_t i = 0; i < ARRAYSIZE(sControllerPak); i++) { - if ((sControllerPak[i].state.company_code = company_code) && (sControllerPak[i].state.game_code == game_code) && - (strcmp(sControllerPak[i].state.game_name, (const char*) game_name) == 0) && - strcmp(sControllerPak[i].state.ext_name, (const char*) ext_name) == 0) { - file_no = i; - - char filename[100]; - sprintf(filename, "controllerPak_file_%d.sav", file_no); - remove(filename); - - ctx->r2 = 0; // PFS_NO_ERROR - printf("Returned 0: FILE DELETED\n\n"); - return; - } else { - ctx->r2 = 1; // PFS_ERR_NOPACK - printf("Returned 1: FILE NOT FOUND\n\n"); - } - } -} - bool IsFileEmpty(std::fstream& file) { + + if (!file.good()) { + return 1; + } + file.seekg(0, std::ios::end); long size = file.tellg(); file.seekg(0, std::ios::beg); return size == 0; } -// f.read(ptr, size) and f.write(ptr, size); + // s32 osPfsFileState(OSPfs* pfs, s32 file_no, OSPfsState* state); extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { s32 file_no = _arg<1, s32>(rdram, ctx); OSPfsState* state = _arg<2, OSPfsState*>(rdram, ctx); - ControllerPak& pak = sControllerPak[file_no]; + // locals: + s32 file_size_in_bytes; + u32 game_code; + u16 company_code; + char ext_name[4]; + char game_name[16]; + + // should pass the state of the requested file_no to the incoming state pointer, + // games call this function 16 times, once per bank + // fills the incoming state with the information inside the header of the pak. + + /* Read game info from pak */ + ControllerPak pak; char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", file_no); - // pak.file = fopen(filename, "rb+"); pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - if (!pak.file.good()) { - printf("File's not good\n"); + + if (IsFileEmpty(pak.file)) { + pak.file.close(); + ctx->r2 = 1; // PFS_NO_ERROR + return; + } + pak.file.close(); + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + printf("file is not good!\n"); + assert(false); + } + if (!pak.header.is_open()) { + printf("file isn't open!\n"); assert(false); } - if (!IsFileEmpty(pak.file)) { - state->file_size = pak.state.file_size; - state->company_code = pak.state.company_code; - state->game_code = pak.state.game_code; + u32 seek = file_no * sizeof(OSPfsState); - for (size_t j = 0; j < ARRAYSIZE(pak.state.game_name); j++) { - state->game_name[j] = pak.state.game_name[j]; - } - for (size_t j = 0; j < ARRAYSIZE(pak.state.ext_name); j++) { - state->ext_name[j] = pak.state.ext_name[j]; - } + // file_size + pak.header.seekg(seek + 0x0, std::ios::beg); + pak.header.read((char*) &file_size_in_bytes, sizeof(file_size_in_bytes)); + // game_code + pak.header.seekg(seek + 0x4, std::ios::beg); + pak.header.read((char*) &game_code, sizeof(game_code)); + // company_code + pak.header.seekg(seek + 0x08, std::ios::beg); + pak.header.read((char*) &company_code, sizeof(company_code)); + // ext_name + pak.header.seekg(seek + 0x0C, std::ios::beg); + pak.header.read((char*) ext_name, ARRAYSIZE(ext_name)); + // game_name + pak.header.seekg(seek + 0x10, std::ios::beg); + pak.header.read((char*) game_name, ARRAYSIZE(game_name)); - // fclose(pak.file); - pak.file.close(); - ctx->r2 = 0; // PFS_NO_ERROR - } else { - // fclose(pak.file); - pak.file.close(); - remove(filename); - ctx->r2 = 1; // PFS_ERR_NOPACK + pak.header.close(); + + state->file_size = file_size_in_bytes; + state->company_code = game_code; + state->game_code = game_code; + + for (size_t j = 0; j < ARRAYSIZE(game_name); j++) { + state->game_name[j] = game_name[j]; } + for (size_t j = 0; j < ARRAYSIZE(ext_name); j++) { + state->ext_name[j] = ext_name[j]; + } + + ctx->r2 = 0; // PFS_NO_ERROR } // s32 osPfsFindFile(OSPfs* pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, s32* file_no); @@ -198,40 +318,73 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { u32 game_code = _arg<2, u32>(rdram, ctx); u8* game_name = _arg<3, u8*>(rdram, ctx); u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - s32* file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); + s32* file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); // we should return the index of the file found here - for (size_t i = 0; i < ARRAYSIZE(sControllerPak); i++) { - if ((sControllerPak[i].state.game_code == game_code) && - (sControllerPak[i].state.company_code == company_code) && - (strcmp(sControllerPak[i].state.game_name, (const char*) game_name) == 0) && - strcmp(sControllerPak[i].state.ext_name, (const char*) ext_name) == 0) { - // File found - *file_no = i; - ctx->r2 = 0; // PFS_NO_ERROR - return; - } - } - - // Open file - *file_no = sFileSelect++; + ControllerPak pak; char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", *file_no); - // sControllerPak[*file_no].file = fopen(filename, "rb+"); - // if (sControllerPak[*file_no].file == NULL) { - // sControllerPak[*file_no].file = fopen(filename, "wb+"); - // } - sControllerPak[*file_no].file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - if (!sControllerPak[*file_no].file.good()) { + pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); + + if (!std::filesystem::exists(filename) || IsFileEmpty(pak.file)) { + pak.file.close(); + ctx->r2 = 5; // PFS_ERR_INVALID + return; + } + pak.file.close(); + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + printf("file is not good!\n"); + assert(false); + } + if (!pak.header.is_open()) { + printf("file isn't open!\n"); assert(false); } - sControllerPak[*file_no].state.company_code = company_code; - sControllerPak[*file_no].state.game_code = game_code; - strcpy(sControllerPak[*file_no].state.game_name, (const char*) game_name); - strcpy(sControllerPak[*file_no].state.ext_name, (const char*) ext_name); + for (size_t i = 0; i < 16; i++) { + // locals: + u32 game_code_; + u16 company_code_; + char ext_name_[4]; + char game_name_[16]; + u32 seek = i * sizeof(OSPfsState); - ctx->r2 = 0; // PFS_NO_ERROR + // game_code + pak.header.seekg(seek + 0x4, std::ios::beg); + pak.header.read((char*) &game_code_, sizeof(game_code_)); + // company_code + pak.header.seekg(seek + 0x08, std::ios::beg); + pak.header.read((char*) &company_code_, sizeof(company_code_)); + // ext_name + pak.header.seekg(seek + 0x0C, std::ios::beg); + pak.header.read((char*) ext_name_, 4); + // game_name + pak.header.seekg(seek + 0x10, std::ios::beg); + pak.header.read((char*) game_name_, 16); + + + + if ((company_code_ == 0) || (game_code_ == 0)) { + continue; + } else { + if ((game_code == game_code_) && (company_code == company_code_) && + (strcmp((const char*) game_name, (const char*) game_name_) == 0) && + strcmp((const char*) ext_name, (const char*) ext_name_) == 0) { + // File found + *file_no = i; + pak.header.close(); + ctx->r2 = 0; // PFS_NO_ERROR + return; + } + } + } + + pak.header.close(); + // File not found + ctx->r2 = 5; // PFS_ERR_INVALID } // s32 osPfsReadWriteFile(OSPfs* pfs, s32 file_no, u8 flag, int offset, int size_in_bytes, u8* data_buffer); @@ -242,24 +395,39 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { s32 size_in_bytes = (s32) MEM_W(0x10, ctx->r29); u8* data_buffer = TO_PTR(u8, MEM_W(0x14, ctx->r29)); - ControllerPak& pak = sControllerPak[file_no]; + ControllerPak pak; + + char filename[100]; + sprintf(filename, "controllerPak_file_%d.sav", file_no); + pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.file.good()) { + printf("file is not good!\n"); + ctx->r2 = 5; // PFS_ERR_INVALID // VALIDATE + return; + // assert(false); + } + if (!pak.file.is_open()) { + printf("file isn't open!\n"); + ctx->r2 = 5; // PFS_ERR_INVALID // VALIDATE + return; + // assert(false); + } if (flag == 0) { - // fseek(pak.file, offset, SEEK_SET); pak.file.seekg(offset, std::ios::beg); - // fread(data_buffer, size_in_bytes, 1, pak.file); pak.file.read((char*) data_buffer, size_in_bytes); // TODO: use a separate buffer for holding the swapped memory ByteSwapFile(data_buffer, size_in_bytes); } else { // TODO: use a separate buffer for holding the swapped memory ByteSwapFile(data_buffer, size_in_bytes); - // fseek(pak.file, offset, SEEK_SET); - pak.file.seekg(offset, std::ios::beg); - // fwrite(data_buffer, size_in_bytes, 1, pak.file); + pak.file.seekp(offset, std::ios::beg); pak.file.write((char*) data_buffer, size_in_bytes); } + pak.file.close(); + ctx->r2 = 0; // PFS_NO_ERROR } @@ -272,19 +440,128 @@ extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { s32* max_files = _arg<1, s32*>(rdram, ctx); s32* files_used = _arg<2, s32*>(rdram, ctx); + ControllerPak pak; + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + printf("file is not good!\n"); + assert(false); + } + if (!pak.header.is_open()) { + printf("file isn't open!\n"); + assert(false); + } + u8 files = 0; - for (size_t i = 0; i < ARRAYSIZE(sControllerPak); i++) { - if ((sControllerPak[i].state.company_code != 0) && (sControllerPak[i].state.game_code != 0)) { + for (size_t i = 0; i < 16; i++) { + u32 seek = i * sizeof(OSPfsState); + u32 game_code = 0; + u16 company_code = 0; + + // game_code + pak.header.seekg(seek + 0x4, std::ios::beg); + pak.header.read((char*) &game_code, sizeof(game_code)); + // company_code + pak.header.seekg(seek + 0x08, std::ios::beg); + pak.header.read((char*) &company_code, sizeof(company_code)); + + if ((company_code != 0) || (game_code != 0)) { files++; } } + pak.header.close(); + *files_used = files; - *max_files = ARRAYSIZE(sControllerPak); + *max_files = MAX_FILES; ctx->r2 = 0; // PFS_NO_ERROR } +// TODO: Doesn't work, the game is sending NULL game_name, ext_name and company_code for some reason? +// s32 osPfsDeleteFile(OSPfs* pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name); +extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { + u16 company_code = _arg<1, u16>(rdram, ctx); + u32 game_code = _arg<2, u32>(rdram, ctx); + u8* game_name = _arg<3, u8*>(rdram, ctx); + u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + + printf("osPfsDeleteFile_recomp:\n"); + + if (company_code == 0 || game_code == 0) { + ctx->r2 = 5; // PFS_ERR_INVALID + return; + } + + ControllerPak pak; + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + printf("file is not good!\n"); + assert(false); + } + if (!pak.header.is_open()) { + printf("file isn't open!\n"); + assert(false); + } + + for (int i = 0; i < MAX_FILES; i++) { + // locals: + u32 game_code_; + u16 company_code_; + char ext_name_[4]; + char game_name_[16]; + u32 seek = i * sizeof(OSPfsState); + + // game_code + pak.header.seekg(seek + 0x4, std::ios::beg); + pak.header.read((char*) &game_code_, sizeof(game_code_)); + // company_code + pak.header.seekg(seek + 0x08, std::ios::beg); + pak.header.read((char*) &company_code_, sizeof(company_code_)); + // ext_name + pak.header.seekg(seek + 0x0C, std::ios::beg); + pak.header.read((char*) ext_name_, 4); + // game_name + pak.header.seekg(seek + 0x10, std::ios::beg); + pak.header.read((char*) game_name_, 16); + + if ((company_code_ == 0) || (game_code_ == 0)) { + continue; + } else { + if ((game_code == game_code_) && (company_code == company_code_) && + (strcmp((const char*) game_name, (const char*) game_name_) == 0) && + strcmp((const char*) ext_name, (const char*) ext_name_) == 0) { + // File found + + // Zero out the header for this file. + u8* zero_block = (u8*) malloc(sizeof(OSPfsState)); + memset(zero_block, 0, sizeof(OSPfsState)); + pak.header.seekp(seek + 0x0, std::ios::beg); + pak.header.write((char*) &zero_block, sizeof(OSPfsState)); + free(zero_block); + + pak.header.close(); + + char filename[100]; + sprintf(filename, "controllerPak_file_%d.sav", i); + remove(filename); + + ctx->r2 = 0; // PFS_NO_ERROR + return; + } + } + } + + pak.header.close(); + + // File not found + ctx->r2 = 5; // PFS_ERR_INVALID + return; +} + extern "C" void osPfsRepairId_recomp(uint8_t* rdram, recomp_context* ctx) { _return(ctx, 1); // PFS_ERR_NOPACK } From c964f29220a540f074a41fd9d036b6a929039651 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Sun, 20 Jul 2025 23:00:08 -0300 Subject: [PATCH 06/49] quick fix --- librecomp/src/pak.cpp | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 6014714..c24469d 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -157,12 +157,12 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { assert(false); } u8 freeFileIndex = 0; -#if 0 +#if 0 /// THIS IS BROKEN /* Search for a free slot */ - for (size_t i = 0; i < 16; i++) { + for (size_t i = 0; i < MAX_FILES; i++) { u32 seek = i * sizeof(OSPfsState); - u32 game_code_; - u16 company_code_; + u32 game_code_ = 0; + u16 company_code_ = 0; // game_code pak.header.seekg(seek + 0x4, std::ios::beg); @@ -175,12 +175,10 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { freeFileIndex = i; break; } - - game_code_ = company_code_ = 0; } #endif /* Set file parameters to header */ - freeFileIndex = 0; + // freeFileIndex = 0; u32 seek = freeFileIndex * 0x20; // file_size @@ -322,17 +320,6 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { ControllerPak pak; - char filename[100]; - sprintf(filename, "controllerPak_file_%d.sav", *file_no); - pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - - if (!std::filesystem::exists(filename) || IsFileEmpty(pak.file)) { - pak.file.close(); - ctx->r2 = 5; // PFS_ERR_INVALID - return; - } - pak.file.close(); - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); if (!pak.header.good()) { @@ -365,8 +352,6 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { pak.header.seekg(seek + 0x10, std::ios::beg); pak.header.read((char*) game_name_, 16); - - if ((company_code_ == 0) || (game_code_ == 0)) { continue; } else { From e48e7b51375b83f236ff69b0292588106c29edb8 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Mon, 21 Jul 2025 03:58:09 -0300 Subject: [PATCH 07/49] fully working and tested with mk64 and --- librecomp/src/pak.cpp | 140 +++++++++++++----------------------------- 1 file changed, 44 insertions(+), 96 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index c24469d..626fa2b 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -7,15 +7,13 @@ #include "helpers.hpp" #define MAX_FILES 16 + typedef struct ControllerPak { - OSPfsState state; - std::fstream file; std::fstream header; + std::fstream file; } ControllerPak; -static int sFileSelect = 0; - -void ByteSwapFile(u8* buffer, size_t size) { +void Pfs_ByteSwapFile(u8* buffer, size_t size) { uint8_t c0, c1, c2, c3; for (size_t i = 0; i < size; i += 4) { @@ -31,16 +29,11 @@ void ByteSwapFile(u8* buffer, size_t size) { } } -void Pak_WriteHeader(OSPfsState state, s32 file_no) { -} - -// s32 osPfsIsPlug(OSMesgQueue* mq, u8* pattern) extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { - MEM_B(0, ctx->r5) = 1; // *pattern = 1; - ctx->r2 = 0; // PFS_NO_ERROR + MEM_B(0, ctx->r5) = 0b0001; // *pattern = 1; + ctx->r2 = 0; // PFS_NO_ERROR } -// s32 osPfsInit(OSMesgQueue* queue, OSPfs* pfs, int channel) { extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { OSMesgQueue* queue = _arg<0, OSMesgQueue*>(rdram, ctx); OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); @@ -52,6 +45,7 @@ extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { ControllerPak pak; + // If a header file doesn't exist, create it. if (!std::filesystem::exists("controllerPak_header.sav")) { pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); pak.header.close(); @@ -60,7 +54,6 @@ extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -// s32 osPfsInitPak(OSMesgQueue* queue, OSPfs* pfs, int channel); extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { OSMesgQueue* queue = _arg<0, OSMesgQueue*>(rdram, ctx); OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); @@ -70,36 +63,37 @@ extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { pfs->channel = channel; pfs->status = 0x1; + ControllerPak pak; + + // If a header file doesn't exist, create it. + if (!std::filesystem::exists("controllerPak_header.sav")) { + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); + pak.header.close(); + } + ctx->r2 = 0; // PFS_NO_ERROR } -// s32 osPfsFreeBlocks(OSPfs* pfs, s32* bytes_not_used); extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { s32* bytes_not_used = _arg<1, s32*>(rdram, ctx); - // locals: - s32 usedSpace = 0; - ControllerPak pak; - // *bytes_not_used = 123 << 8; - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); if (!pak.header.good()) { - printf("file is not good!\n"); assert(false); } if (!pak.header.is_open()) { - printf("file isn't open!\n"); assert(false); } - for (size_t i = 0; i < 16; i++) { + s32 usedSpace = 0; + for (size_t i = 0; i < MAX_FILES; i++) { u32 seek = i * sizeof(OSPfsState); - s32 file_size_in_bytes; - u32 game_code; - u16 company_code; + s32 file_size_in_bytes = 0; + u32 game_code = 0; + u16 company_code = 0; // file_size pak.header.seekg(seek + 0x0, std::ios::beg); @@ -115,7 +109,6 @@ extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { continue; } else { usedSpace += file_size_in_bytes >> 8; - file_size_in_bytes = game_code = company_code = 0; } } @@ -126,11 +119,6 @@ extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -// TODO: VALIDATE -// Can't really be validated without a valid controller pak file system header, which is only used -// by the controller pak manager present in games by holding START on boot and not necessary for recomp. -// s32 osPfsAllocateFile(OSPfs* pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, -// int file_size_in_bytes, s32* file_no); extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); @@ -149,16 +137,14 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); if (!pak.header.good()) { - printf("file is not good!\n"); assert(false); } if (!pak.header.is_open()) { - printf("file isn't open!\n"); assert(false); } - u8 freeFileIndex = 0; -#if 0 /// THIS IS BROKEN + /* Search for a free slot */ + u8 freeFileIndex = 0; for (size_t i = 0; i < MAX_FILES; i++) { u32 seek = i * sizeof(OSPfsState); u32 game_code_ = 0; @@ -176,10 +162,13 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { break; } } -#endif + + pak.header.close(); + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + /* Set file parameters to header */ - // freeFileIndex = 0; - u32 seek = freeFileIndex * 0x20; + u32 seek = freeFileIndex * sizeof(OSPfsState); // file_size pak.header.seekp(seek + 0x0, std::ios::beg); @@ -222,20 +211,6 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -bool IsFileEmpty(std::fstream& file) { - - if (!file.good()) { - return 1; - } - - file.seekg(0, std::ios::end); - long size = file.tellg(); - file.seekg(0, std::ios::beg); - - return size == 0; -} - -// s32 osPfsFileState(OSPfs* pfs, s32 file_no, OSPfsState* state); extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { s32 file_no = _arg<1, s32>(rdram, ctx); OSPfsState* state = _arg<2, OSPfsState*>(rdram, ctx); @@ -248,31 +223,18 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { char game_name[16]; // should pass the state of the requested file_no to the incoming state pointer, - // games call this function 16 times, once per bank + // games call this function 16 times, once per file // fills the incoming state with the information inside the header of the pak. /* Read game info from pak */ ControllerPak pak; - char filename[100]; - sprintf(filename, "controllerPak_file_%d.sav", file_no); - pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - - if (IsFileEmpty(pak.file)) { - pak.file.close(); - ctx->r2 = 1; // PFS_NO_ERROR - return; - } - pak.file.close(); - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); if (!pak.header.good()) { - printf("file is not good!\n"); assert(false); } if (!pak.header.is_open()) { - printf("file isn't open!\n"); assert(false); } @@ -310,29 +272,25 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -// s32 osPfsFindFile(OSPfs* pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, s32* file_no); extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); u8* game_name = _arg<3, u8*>(rdram, ctx); u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - s32* file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); // we should return the index of the file found here + s32* file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); ControllerPak pak; pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); if (!pak.header.good()) { - printf("file is not good!\n"); assert(false); } if (!pak.header.is_open()) { - printf("file isn't open!\n"); assert(false); } - for (size_t i = 0; i < 16; i++) { - // locals: + for (size_t i = 0; i < MAX_FILES; i++) { u32 game_code_; u16 company_code_; char ext_name_[4]; @@ -368,11 +326,11 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { } pak.header.close(); + // File not found ctx->r2 = 5; // PFS_ERR_INVALID } -// s32 osPfsReadWriteFile(OSPfs* pfs, s32 file_no, u8 flag, int offset, int size_in_bytes, u8* data_buffer); extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { s32 file_no = _arg<1, s32>(rdram, ctx); u8 flag = _arg<2, u8>(rdram, ctx); @@ -386,27 +344,27 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { sprintf(filename, "controllerPak_file_%d.sav", file_no); pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - if (!pak.file.good()) { - printf("file is not good!\n"); - ctx->r2 = 5; // PFS_ERR_INVALID // VALIDATE + if (!std::filesystem::exists(filename)) { + ctx->r2 = 5; // PFS_ERR_INVALID + return; + } + if (!pak.file.good()) { + ctx->r2 = 5; // PFS_ERR_INVALID return; - // assert(false); } if (!pak.file.is_open()) { - printf("file isn't open!\n"); - ctx->r2 = 5; // PFS_ERR_INVALID // VALIDATE + ctx->r2 = 5; // PFS_ERR_INVALID return; - // assert(false); } if (flag == 0) { pak.file.seekg(offset, std::ios::beg); pak.file.read((char*) data_buffer, size_in_bytes); // TODO: use a separate buffer for holding the swapped memory - ByteSwapFile(data_buffer, size_in_bytes); + Pfs_ByteSwapFile(data_buffer, size_in_bytes); } else { // TODO: use a separate buffer for holding the swapped memory - ByteSwapFile(data_buffer, size_in_bytes); + Pfs_ByteSwapFile(data_buffer, size_in_bytes); pak.file.seekp(offset, std::ios::beg); pak.file.write((char*) data_buffer, size_in_bytes); } @@ -420,7 +378,6 @@ extern "C" void osPfsChecker_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -// s32 osPfsNumFiles(OSPfs* pfs, s32* max_files, s32* files_used); extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { s32* max_files = _arg<1, s32*>(rdram, ctx); s32* files_used = _arg<2, s32*>(rdram, ctx); @@ -430,16 +387,14 @@ extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); if (!pak.header.good()) { - printf("file is not good!\n"); assert(false); } if (!pak.header.is_open()) { - printf("file isn't open!\n"); assert(false); } u8 files = 0; - for (size_t i = 0; i < 16; i++) { + for (size_t i = 0; i < MAX_FILES; i++) { u32 seek = i * sizeof(OSPfsState); u32 game_code = 0; u16 company_code = 0; @@ -464,16 +419,12 @@ extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -// TODO: Doesn't work, the game is sending NULL game_name, ext_name and company_code for some reason? -// s32 osPfsDeleteFile(OSPfs* pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name); extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); u8* game_name = _arg<3, u8*>(rdram, ctx); u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - printf("osPfsDeleteFile_recomp:\n"); - if (company_code == 0 || game_code == 0) { ctx->r2 = 5; // PFS_ERR_INVALID return; @@ -484,16 +435,13 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); if (!pak.header.good()) { - printf("file is not good!\n"); assert(false); } if (!pak.header.is_open()) { - printf("file isn't open!\n"); assert(false); } for (int i = 0; i < MAX_FILES; i++) { - // locals: u32 game_code_; u16 company_code_; char ext_name_[4]; @@ -508,10 +456,10 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { pak.header.read((char*) &company_code_, sizeof(company_code_)); // ext_name pak.header.seekg(seek + 0x0C, std::ios::beg); - pak.header.read((char*) ext_name_, 4); + pak.header.read((char*) ext_name_, sizeof(ext_name_)); // game_name pak.header.seekg(seek + 0x10, std::ios::beg); - pak.header.read((char*) game_name_, 16); + pak.header.read((char*) game_name_, sizeof(game_name_)); if ((company_code_ == 0) || (game_code_ == 0)) { continue; @@ -548,5 +496,5 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsRepairId_recomp(uint8_t* rdram, recomp_context* ctx) { - _return(ctx, 1); // PFS_ERR_NOPACK + _return(ctx, 0); // PFS_NO_ERROR } From bf84e072d1be917b9dfb54c11f3ba3a897ff602b Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Mon, 21 Jul 2025 04:14:15 -0300 Subject: [PATCH 08/49] add padding to OSPfsState --- ultramodern/include/ultramodern/ultra64.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ultramodern/include/ultramodern/ultra64.h b/ultramodern/include/ultramodern/ultra64.h index 8b136cf..7a5dedf 100644 --- a/ultramodern/include/ultramodern/ultra64.h +++ b/ultramodern/include/ultramodern/ultra64.h @@ -239,6 +239,7 @@ typedef struct { /* 0x00 */ u32 file_size; /* bytes */ /* 0x04 */ u32 game_code; /* 0x08 */ u16 company_code; + /* 0x0A */ char pad_0A[2]; /* 0x0C */ char ext_name[4]; /* 0x10 */ char game_name[16]; } OSPfsState; // size = 0x20 From 7361eba6e4cf9b603c77a2ecb390e8bc36f17146 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Mon, 21 Jul 2025 18:38:28 -0300 Subject: [PATCH 09/49] forgot this check --- librecomp/src/pak.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 626fa2b..9e74723 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -226,6 +226,13 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { // games call this function 16 times, once per file // fills the incoming state with the information inside the header of the pak. + char filename[100]; + sprintf(filename, "controllerPak_file_%d.sav", file_no); + if (!std::filesystem::exists(filename)) { + ctx->r2 = 5; // PFS_ERR_INVALID + return; + } + /* Read game info from pak */ ControllerPak pak; From 19d26be0aeb7d0946f0a86f821b090f5ab08d398 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Tue, 22 Jul 2025 06:07:41 -0300 Subject: [PATCH 10/49] simplify code and fix DeleteFile bug --- librecomp/src/pak.cpp | 318 ++++++++++++++++++------------------------ 1 file changed, 138 insertions(+), 180 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 9e74723..c08b1c0 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "ultramodern/ultra64.h" #include "ultramodern/ultramodern.hpp" @@ -13,6 +14,79 @@ typedef struct ControllerPak { std::fstream file; } ControllerPak; +// extern std::filesystem::path config_path; +// const std::u8string save_folder = u8"saves"; +// std::filesystem::path save_folder_path = config_path / save_folder; + +void Pfs_PakHeader_Write(u32* file_size, u32* game_code, u16* company_code, u8* ext_name, u8* game_name, u8 fileIndex) { + ControllerPak pak; + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + assert(false); + } + if (!pak.header.is_open()) { + assert(false); + } + + /* Set file parameters to header */ + u32 seek = fileIndex * sizeof(OSPfsState); + + // file_size + pak.header.seekp(seek + 0x0, std::ios::beg); + pak.header.write((char*) file_size, 4); + // game_code + pak.header.seekp(seek + 0x4, std::ios::beg); + pak.header.write((char*) game_code, 4); + // company_code + pak.header.seekp(seek + 0x08, std::ios::beg); + pak.header.write((char*) company_code, 2); + // ext_name + pak.header.seekp(seek + 0x0C, std::ios::beg); + pak.header.write((char*) ext_name, 4); + // game_name + pak.header.seekp(seek + 0x10, std::ios::beg); + pak.header.write((char*) game_name, 16); + + pak.header.close(); +} + +void Pfs_PakHeader_Read(u32* file_size, u32* game_code, u16* company_code, char* ext_name, char* game_name, + u8 fileIndex) { + ControllerPak pak; + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + assert(false); + } + if (!pak.header.is_open()) { + assert(false); + } + + /* Set file parameters to header */ + u32 seek = fileIndex * sizeof(OSPfsState); + + // file_size + pak.header.seekg(seek + 0x0, std::ios::beg); + pak.header.read((char*) file_size, 4); + // game_code + pak.header.seekg(seek + 0x4, std::ios::beg); + pak.header.read((char*) game_code, 4); + // company_code + pak.header.seekg(seek + 0x08, std::ios::beg); + pak.header.read((char*) company_code, 2); + // ext_name + pak.header.seekg(seek + 0x0C, std::ios::beg); + pak.header.read((char*) ext_name, 4); + // game_name + pak.header.seekg(seek + 0x10, std::ios::beg); + pak.header.read((char*) game_name, 16); + + pak.header.close(); +} + void Pfs_ByteSwapFile(u8* buffer, size_t size) { uint8_t c0, c1, c2, c3; @@ -29,6 +103,12 @@ void Pfs_ByteSwapFile(u8* buffer, size_t size) { } } +void ByteSwapCopy(uint8_t* dst, uint8_t* src, size_t size_bytes) { + for (size_t i = 0; i < size_bytes; i++) { + dst[i] = src[i ^ 3]; + } +} + extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { MEM_B(0, ctx->r5) = 0b0001; // *pattern = 1; ctx->r2 = 0; // PFS_NO_ERROR @@ -90,25 +170,18 @@ extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { s32 usedSpace = 0; for (size_t i = 0; i < MAX_FILES; i++) { - u32 seek = i * sizeof(OSPfsState); - s32 file_size_in_bytes = 0; + u32 file_size = 0; u32 game_code = 0; u16 company_code = 0; + char ext_name[4] = { 0 }; + char game_name[16] = { 0 }; - // file_size - pak.header.seekg(seek + 0x0, std::ios::beg); - pak.header.read((char*) &file_size_in_bytes, sizeof(file_size_in_bytes)); - // game_code - pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char*) &game_code, sizeof(game_code)); - // company_code - pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char*) &company_code, sizeof(company_code)); + Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, i); if ((company_code == 0) || (game_code == 0)) { continue; } else { - usedSpace += file_size_in_bytes >> 8; + usedSpace += file_size >> 8; } } @@ -124,7 +197,7 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { u32 game_code = _arg<2, u32>(rdram, ctx); u8* game_name = _arg<3, u8*>(rdram, ctx); u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - s32 file_size_in_bytes = (s32) MEM_W(0x14, ctx->r29); + u32 file_size = (s32) MEM_W(0x14, ctx->r29); s32* file_no = TO_PTR(s32, MEM_W(0x18, ctx->r29)); if ((company_code == 0) || (game_code == 0)) { @@ -134,28 +207,16 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { ControllerPak pak; - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - /* Search for a free slot */ u8 freeFileIndex = 0; for (size_t i = 0; i < MAX_FILES; i++) { - u32 seek = i * sizeof(OSPfsState); + u32 file_size_ = 0; u32 game_code_ = 0; u16 company_code_ = 0; + char ext_name_[4] = { 0 }; + char game_name_[16] = { 0 }; - // game_code - pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char*) &game_code_, sizeof(game_code_)); - // company_code - pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char*) &company_code_, sizeof(company_code_)); + Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); if ((company_code_ == 0) || (game_code_ == 0)) { freeFileIndex = i; @@ -163,30 +224,7 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { } } - pak.header.close(); - - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - /* Set file parameters to header */ - u32 seek = freeFileIndex * sizeof(OSPfsState); - - // file_size - pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char*) &file_size_in_bytes, sizeof(file_size_in_bytes)); - // game_code - pak.header.seekp(seek + 0x4, std::ios::beg); - pak.header.write((char*) &game_code, sizeof(game_code)); - // company_code - pak.header.seekp(seek + 0x08, std::ios::beg); - pak.header.write((char*) &company_code, sizeof(company_code)); - // ext_name - pak.header.seekp(seek + 0x0C, std::ios::beg); - pak.header.write((char*) ext_name, 4); - // game_name - pak.header.seekp(seek + 0x10, std::ios::beg); - pak.header.write((char*) game_name, 16); - - pak.header.close(); + Pfs_PakHeader_Write(&file_size, &game_code, &company_code, ext_name, game_name, freeFileIndex); /* Create empty file */ @@ -194,13 +232,13 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { sprintf(filename, "controllerPak_file_%d.sav", freeFileIndex); pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - file_size_in_bytes = (file_size_in_bytes + 31) & ~31; + file_size = (file_size + 31) & ~31; - u8* zero_block = (u8*) malloc(file_size_in_bytes); - memset(zero_block, 0, file_size_in_bytes); + u8* zero_block = (u8*) malloc(file_size); + memset(zero_block, 0, file_size); pak.file.seekp(0, std::ios::beg); - pak.file.write((char*) zero_block, file_size_in_bytes); + pak.file.write((char*) zero_block, file_size); free(zero_block); @@ -215,57 +253,27 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { s32 file_no = _arg<1, s32>(rdram, ctx); OSPfsState* state = _arg<2, OSPfsState*>(rdram, ctx); - // locals: - s32 file_size_in_bytes; - u32 game_code; - u16 company_code; - char ext_name[4]; - char game_name[16]; + u32 file_size = 0; + u32 game_code = 0; + u16 company_code = 0; + char ext_name[4] = { 0 }; + char game_name[16] = { 0 }; // should pass the state of the requested file_no to the incoming state pointer, // games call this function 16 times, once per file // fills the incoming state with the information inside the header of the pak. - + // If a header file doesn't exist, create it. char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", file_no); if (!std::filesystem::exists(filename)) { - ctx->r2 = 5; // PFS_ERR_INVALID + ctx->r2 = 5; return; } /* Read game info from pak */ - ControllerPak pak; + Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, file_no); - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - - u32 seek = file_no * sizeof(OSPfsState); - - // file_size - pak.header.seekg(seek + 0x0, std::ios::beg); - pak.header.read((char*) &file_size_in_bytes, sizeof(file_size_in_bytes)); - // game_code - pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char*) &game_code, sizeof(game_code)); - // company_code - pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char*) &company_code, sizeof(company_code)); - // ext_name - pak.header.seekg(seek + 0x0C, std::ios::beg); - pak.header.read((char*) ext_name, ARRAYSIZE(ext_name)); - // game_name - pak.header.seekg(seek + 0x10, std::ios::beg); - pak.header.read((char*) game_name, ARRAYSIZE(game_name)); - - pak.header.close(); - - state->file_size = file_size_in_bytes; + state->file_size = file_size; state->company_code = game_code; state->game_code = game_code; @@ -288,34 +296,14 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { ControllerPak pak; - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - for (size_t i = 0; i < MAX_FILES; i++) { - u32 game_code_; - u16 company_code_; - char ext_name_[4]; - char game_name_[16]; - u32 seek = i * sizeof(OSPfsState); + u32 file_size_ = 0; + u32 game_code_ = 0; + u16 company_code_ = 0; + char ext_name_[4] = { 0 }; + char game_name_[16] = { 0 }; - // game_code - pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char*) &game_code_, sizeof(game_code_)); - // company_code - pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char*) &company_code_, sizeof(company_code_)); - // ext_name - pak.header.seekg(seek + 0x0C, std::ios::beg); - pak.header.read((char*) ext_name_, 4); - // game_name - pak.header.seekg(seek + 0x10, std::ios::beg); - pak.header.read((char*) game_name_, 16); + Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); if ((company_code_ == 0) || (game_code_ == 0)) { continue; @@ -325,15 +313,12 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { strcmp((const char*) ext_name, (const char*) ext_name_) == 0) { // File found *file_no = i; - pak.header.close(); ctx->r2 = 0; // PFS_NO_ERROR return; } } } - pak.header.close(); - // File not found ctx->r2 = 5; // PFS_ERR_INVALID } @@ -364,17 +349,17 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { return; } + u8* swapBuffer = (u8*) malloc(size_in_bytes); if (flag == 0) { pak.file.seekg(offset, std::ios::beg); - pak.file.read((char*) data_buffer, size_in_bytes); - // TODO: use a separate buffer for holding the swapped memory - Pfs_ByteSwapFile(data_buffer, size_in_bytes); + pak.file.read((char*) swapBuffer, size_in_bytes); + ByteSwapCopy(data_buffer, swapBuffer, size_in_bytes); } else { - // TODO: use a separate buffer for holding the swapped memory - Pfs_ByteSwapFile(data_buffer, size_in_bytes); + ByteSwapCopy(swapBuffer, data_buffer, size_in_bytes); pak.file.seekp(offset, std::ios::beg); - pak.file.write((char*) data_buffer, size_in_bytes); + pak.file.write((char*) swapBuffer, size_in_bytes); } + free(swapBuffer); pak.file.close(); @@ -389,37 +374,21 @@ extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { s32* max_files = _arg<1, s32*>(rdram, ctx); s32* files_used = _arg<2, s32*>(rdram, ctx); - ControllerPak pak; - - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - u8 files = 0; for (size_t i = 0; i < MAX_FILES; i++) { - u32 seek = i * sizeof(OSPfsState); + u32 file_size = 0; u32 game_code = 0; u16 company_code = 0; + char ext_name[4] = { 0 }; + char game_name[16] = { 0 }; - // game_code - pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char*) &game_code, sizeof(game_code)); - // company_code - pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char*) &company_code, sizeof(company_code)); + Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, i); if ((company_code != 0) || (game_code != 0)) { files++; } } - pak.header.close(); - *files_used = files; *max_files = MAX_FILES; @@ -439,34 +408,14 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { ControllerPak pak; - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - for (int i = 0; i < MAX_FILES; i++) { - u32 game_code_; - u16 company_code_; - char ext_name_[4]; - char game_name_[16]; - u32 seek = i * sizeof(OSPfsState); + u32 file_size_ = 0; + u32 game_code_ = 0; + u16 company_code_ = 0; + char ext_name_[4] = { 0 }; + char game_name_[16] = { 0 }; - // game_code - pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char*) &game_code_, sizeof(game_code_)); - // company_code - pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char*) &company_code_, sizeof(company_code_)); - // ext_name - pak.header.seekg(seek + 0x0C, std::ios::beg); - pak.header.read((char*) ext_name_, sizeof(ext_name_)); - // game_name - pak.header.seekg(seek + 0x10, std::ios::beg); - pak.header.read((char*) game_name_, sizeof(game_name_)); + Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); if ((company_code_ == 0) || (game_code_ == 0)) { continue; @@ -476,11 +425,22 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { strcmp((const char*) ext_name, (const char*) ext_name_) == 0) { // File found + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + assert(false); + } + if (!pak.header.is_open()) { + assert(false); + } + + u32 seek = i * sizeof(OSPfsState); + // Zero out the header for this file. u8* zero_block = (u8*) malloc(sizeof(OSPfsState)); memset(zero_block, 0, sizeof(OSPfsState)); pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char*) &zero_block, sizeof(OSPfsState)); + pak.header.write((char*) zero_block, sizeof(OSPfsState)); free(zero_block); pak.header.close(); @@ -495,8 +455,6 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { } } - pak.header.close(); - // File not found ctx->r2 = 5; // PFS_ERR_INVALID return; From 9a658b406fd48dd0c3d539c0af3c9bc048024d6f Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Tue, 22 Jul 2025 07:08:31 -0300 Subject: [PATCH 11/49] ARRAY_COUNT --- librecomp/src/pak.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index c08b1c0..5911f7c 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -7,6 +7,7 @@ #include "recomp.h" #include "helpers.hpp" +#define ARRAY_COUNT(arr) (s32)(sizeof(arr) / sizeof(arr[0])) #define MAX_FILES 16 typedef struct ControllerPak { @@ -277,10 +278,10 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { state->company_code = game_code; state->game_code = game_code; - for (size_t j = 0; j < ARRAYSIZE(game_name); j++) { + for (size_t j = 0; j < ARRAY_COUNT(game_name); j++) { state->game_name[j] = game_name[j]; } - for (size_t j = 0; j < ARRAYSIZE(ext_name); j++) { + for (size_t j = 0; j < ARRAY_COUNT(ext_name); j++) { state->ext_name[j] = ext_name[j]; } From c2a525ed2d1f8c6eb60f10b01d90b93ac01ce0fa Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Tue, 22 Jul 2025 14:45:37 -0300 Subject: [PATCH 12/49] Pak: int32_t queue --- librecomp/src/pak.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 5911f7c..56cc7cd 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -116,7 +116,7 @@ extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { - OSMesgQueue* queue = _arg<0, OSMesgQueue*>(rdram, ctx); + int32_t* queue = _arg<0, int32_t*>(rdram, ctx); OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); s32 channel = _arg<2, s32>(rdram, ctx); From 33228560d58e1472e06bd39d832bead25a0750c4 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Tue, 22 Jul 2025 15:15:36 -0300 Subject: [PATCH 13/49] pak queue as int32_t --- librecomp/src/pak.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 56cc7cd..57687ba 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -116,11 +116,11 @@ extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { - int32_t* queue = _arg<0, int32_t*>(rdram, ctx); + int32_t queue = _arg<0, int32_t>(rdram, ctx); OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); s32 channel = _arg<2, s32>(rdram, ctx); - pfs->queue = (int32_t) queue; + pfs->queue = queue; pfs->channel = channel; pfs->status = 0x1; From ece48700a849a5a9cb47cef7951774362211aa25 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Tue, 22 Jul 2025 15:23:10 -0300 Subject: [PATCH 14/49] forgot to fix osPfsInitPak_recomp --- librecomp/src/pak.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 57687ba..f5751da 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -136,11 +136,11 @@ extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { - OSMesgQueue* queue = _arg<0, OSMesgQueue*>(rdram, ctx); + int32_t queue = _arg<0, int32_t>(rdram, ctx); OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); s32 channel = _arg<2, s32>(rdram, ctx); - pfs->queue = (int32_t) queue; + pfs->queue = queue; pfs->channel = channel; pfs->status = 0x1; From 1834cef9c9582f093a28560f33145797d2f86664 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Tue, 22 Jul 2025 19:24:12 -0300 Subject: [PATCH 15/49] Pak.cpp: cleanup --- librecomp/src/pak.cpp | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index f5751da..e77fb1c 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -158,17 +158,6 @@ extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { s32* bytes_not_used = _arg<1, s32*>(rdram, ctx); - ControllerPak pak; - - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - s32 usedSpace = 0; for (size_t i = 0; i < MAX_FILES; i++) { u32 file_size = 0; @@ -186,8 +175,6 @@ extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { } } - pak.header.close(); - *bytes_not_used = (123 - usedSpace) << 8; ctx->r2 = 0; // PFS_NO_ERROR @@ -206,8 +193,6 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { return; } - ControllerPak pak; - /* Search for a free slot */ u8 freeFileIndex = 0; for (size_t i = 0; i < MAX_FILES; i++) { @@ -229,6 +214,8 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { /* Create empty file */ + ControllerPak pak; + char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", freeFileIndex); pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); @@ -263,7 +250,7 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { // should pass the state of the requested file_no to the incoming state pointer, // games call this function 16 times, once per file // fills the incoming state with the information inside the header of the pak. - // If a header file doesn't exist, create it. + char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", file_no); if (!std::filesystem::exists(filename)) { @@ -295,8 +282,6 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); s32* file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); - ControllerPak pak; - for (size_t i = 0; i < MAX_FILES; i++) { u32 file_size_ = 0; u32 game_code_ = 0; From 381e0704b923c021c25ed7e4e39fae2d2af2338e Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Wed, 23 Jul 2025 05:50:10 -0300 Subject: [PATCH 16/49] osPfsAllocateFile_recomp: add check for max files --- librecomp/src/pak.cpp | 263 ++++++++++++++++++++++++++---------------- 1 file changed, 162 insertions(+), 101 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index e77fb1c..5643a9e 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -10,7 +10,8 @@ #define ARRAY_COUNT(arr) (s32)(sizeof(arr) / sizeof(arr[0])) #define MAX_FILES 16 -typedef struct ControllerPak { +typedef struct ControllerPak +{ std::fstream header; std::fstream file; } ControllerPak; @@ -19,15 +20,18 @@ typedef struct ControllerPak { // const std::u8string save_folder = u8"saves"; // std::filesystem::path save_folder_path = config_path / save_folder; -void Pfs_PakHeader_Write(u32* file_size, u32* game_code, u16* company_code, u8* ext_name, u8* game_name, u8 fileIndex) { +void Pfs_PakHeader_Write(u32 *file_size, u32 *game_code, u16 *company_code, u8 *ext_name, u8 *game_name, u8 fileIndex) +{ ControllerPak pak; pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - if (!pak.header.good()) { + if (!pak.header.good()) + { assert(false); } - if (!pak.header.is_open()) { + if (!pak.header.is_open()) + { assert(false); } @@ -36,33 +40,36 @@ void Pfs_PakHeader_Write(u32* file_size, u32* game_code, u16* company_code, u8* // file_size pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char*) file_size, 4); + pak.header.write((char *)file_size, 4); // game_code pak.header.seekp(seek + 0x4, std::ios::beg); - pak.header.write((char*) game_code, 4); + pak.header.write((char *)game_code, 4); // company_code pak.header.seekp(seek + 0x08, std::ios::beg); - pak.header.write((char*) company_code, 2); + pak.header.write((char *)company_code, 2); // ext_name pak.header.seekp(seek + 0x0C, std::ios::beg); - pak.header.write((char*) ext_name, 4); + pak.header.write((char *)ext_name, 4); // game_name pak.header.seekp(seek + 0x10, std::ios::beg); - pak.header.write((char*) game_name, 16); + pak.header.write((char *)game_name, 16); pak.header.close(); } -void Pfs_PakHeader_Read(u32* file_size, u32* game_code, u16* company_code, char* ext_name, char* game_name, - u8 fileIndex) { +void Pfs_PakHeader_Read(u32 *file_size, u32 *game_code, u16 *company_code, char *ext_name, char *game_name, + u8 fileIndex) +{ ControllerPak pak; pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - if (!pak.header.good()) { + if (!pak.header.good()) + { assert(false); } - if (!pak.header.is_open()) { + if (!pak.header.is_open()) + { assert(false); } @@ -71,27 +78,29 @@ void Pfs_PakHeader_Read(u32* file_size, u32* game_code, u16* company_code, char* // file_size pak.header.seekg(seek + 0x0, std::ios::beg); - pak.header.read((char*) file_size, 4); + pak.header.read((char *)file_size, 4); // game_code pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char*) game_code, 4); + pak.header.read((char *)game_code, 4); // company_code pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char*) company_code, 2); + pak.header.read((char *)company_code, 2); // ext_name pak.header.seekg(seek + 0x0C, std::ios::beg); - pak.header.read((char*) ext_name, 4); + pak.header.read((char *)ext_name, 4); // game_name pak.header.seekg(seek + 0x10, std::ios::beg); - pak.header.read((char*) game_name, 16); + pak.header.read((char *)game_name, 16); pak.header.close(); } -void Pfs_ByteSwapFile(u8* buffer, size_t size) { +void Pfs_ByteSwapFile(u8 *buffer, size_t size) +{ uint8_t c0, c1, c2, c3; - for (size_t i = 0; i < size; i += 4) { + for (size_t i = 0; i < size; i += 4) + { c0 = buffer[i + 0]; c1 = buffer[i + 1]; c2 = buffer[i + 2]; @@ -104,20 +113,24 @@ void Pfs_ByteSwapFile(u8* buffer, size_t size) { } } -void ByteSwapCopy(uint8_t* dst, uint8_t* src, size_t size_bytes) { - for (size_t i = 0; i < size_bytes; i++) { +void ByteSwapCopy(uint8_t *dst, uint8_t *src, size_t size_bytes) +{ + for (size_t i = 0; i < size_bytes; i++) + { dst[i] = src[i ^ 3]; } } -extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsIsPlug_recomp(uint8_t *rdram, recomp_context *ctx) +{ MEM_B(0, ctx->r5) = 0b0001; // *pattern = 1; ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsInit_recomp(uint8_t *rdram, recomp_context *ctx) +{ int32_t queue = _arg<0, int32_t>(rdram, ctx); - OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); + OSPfs *pfs = _arg<1, OSPfs *>(rdram, ctx); s32 channel = _arg<2, s32>(rdram, ctx); pfs->queue = queue; @@ -127,7 +140,8 @@ extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { ControllerPak pak; // If a header file doesn't exist, create it. - if (!std::filesystem::exists("controllerPak_header.sav")) { + if (!std::filesystem::exists("controllerPak_header.sav")) + { pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); pak.header.close(); } @@ -135,9 +149,10 @@ extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsInitPak_recomp(uint8_t *rdram, recomp_context *ctx) +{ int32_t queue = _arg<0, int32_t>(rdram, ctx); - OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); + OSPfs *pfs = _arg<1, OSPfs *>(rdram, ctx); s32 channel = _arg<2, s32>(rdram, ctx); pfs->queue = queue; @@ -147,7 +162,8 @@ extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { ControllerPak pak; // If a header file doesn't exist, create it. - if (!std::filesystem::exists("controllerPak_header.sav")) { + if (!std::filesystem::exists("controllerPak_header.sav")) + { pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); pak.header.close(); } @@ -155,22 +171,27 @@ extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { - s32* bytes_not_used = _arg<1, s32*>(rdram, ctx); +extern "C" void osPfsFreeBlocks_recomp(uint8_t *rdram, recomp_context *ctx) +{ + s32 *bytes_not_used = _arg<1, s32 *>(rdram, ctx); s32 usedSpace = 0; - for (size_t i = 0; i < MAX_FILES; i++) { + for (size_t i = 0; i < MAX_FILES; i++) + { u32 file_size = 0; u32 game_code = 0; u16 company_code = 0; - char ext_name[4] = { 0 }; - char game_name[16] = { 0 }; + char ext_name[4] = {0}; + char game_name[16] = {0}; Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, i); - if ((company_code == 0) || (game_code == 0)) { + if ((company_code == 0) || (game_code == 0)) + { continue; - } else { + } + else + { usedSpace += file_size >> 8; } } @@ -180,36 +201,45 @@ extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsAllocateFile_recomp(uint8_t *rdram, recomp_context *ctx) +{ u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); - u8* game_name = _arg<3, u8*>(rdram, ctx); - u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - u32 file_size = (s32) MEM_W(0x14, ctx->r29); - s32* file_no = TO_PTR(s32, MEM_W(0x18, ctx->r29)); + u8 *game_name = _arg<3, u8 *>(rdram, ctx); + u8 *ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + u32 file_size = (s32)MEM_W(0x14, ctx->r29); + s32 *file_no = TO_PTR(s32, MEM_W(0x18, ctx->r29)); - if ((company_code == 0) || (game_code == 0)) { + if ((company_code == 0) || (game_code == 0)) + { ctx->r2 = 5; // PFS_ERR_INVALID return; } /* Search for a free slot */ u8 freeFileIndex = 0; - for (size_t i = 0; i < MAX_FILES; i++) { + for (size_t i = 0; i < MAX_FILES; i++) + { u32 file_size_ = 0; u32 game_code_ = 0; u16 company_code_ = 0; - char ext_name_[4] = { 0 }; - char game_name_[16] = { 0 }; + char ext_name_[4] = {0}; + char game_name_[16] = {0}; Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); - if ((company_code_ == 0) || (game_code_ == 0)) { + if ((company_code_ == 0) || (game_code_ == 0)) + { freeFileIndex = i; break; } } + if (freeFileIndex == MAX_FILES) { + ctx->r2 = 8; // PFS_DIR_FULL + return; + } + Pfs_PakHeader_Write(&file_size, &game_code, &company_code, ext_name, game_name, freeFileIndex); /* Create empty file */ @@ -222,11 +252,11 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { file_size = (file_size + 31) & ~31; - u8* zero_block = (u8*) malloc(file_size); + u8 *zero_block = (u8 *)malloc(file_size); memset(zero_block, 0, file_size); pak.file.seekp(0, std::ios::beg); - pak.file.write((char*) zero_block, file_size); + pak.file.write((char *)zero_block, file_size); free(zero_block); @@ -237,15 +267,16 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsFileState_recomp(uint8_t *rdram, recomp_context *ctx) +{ s32 file_no = _arg<1, s32>(rdram, ctx); - OSPfsState* state = _arg<2, OSPfsState*>(rdram, ctx); + OSPfsState *state = _arg<2, OSPfsState *>(rdram, ctx); u32 file_size = 0; u32 game_code = 0; u16 company_code = 0; - char ext_name[4] = { 0 }; - char game_name[16] = { 0 }; + char ext_name[4] = {0}; + char game_name[16] = {0}; // should pass the state of the requested file_no to the incoming state pointer, // games call this function 16 times, once per file @@ -253,7 +284,8 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", file_no); - if (!std::filesystem::exists(filename)) { + if (!std::filesystem::exists(filename)) + { ctx->r2 = 5; return; } @@ -265,38 +297,46 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { state->company_code = game_code; state->game_code = game_code; - for (size_t j = 0; j < ARRAY_COUNT(game_name); j++) { + for (size_t j = 0; j < ARRAY_COUNT(game_name); j++) + { state->game_name[j] = game_name[j]; } - for (size_t j = 0; j < ARRAY_COUNT(ext_name); j++) { + for (size_t j = 0; j < ARRAY_COUNT(ext_name); j++) + { state->ext_name[j] = ext_name[j]; } ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsFindFile_recomp(uint8_t *rdram, recomp_context *ctx) +{ u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); - u8* game_name = _arg<3, u8*>(rdram, ctx); - u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - s32* file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); + u8 *game_name = _arg<3, u8 *>(rdram, ctx); + u8 *ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + s32 *file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); - for (size_t i = 0; i < MAX_FILES; i++) { + for (size_t i = 0; i < MAX_FILES; i++) + { u32 file_size_ = 0; u32 game_code_ = 0; u16 company_code_ = 0; - char ext_name_[4] = { 0 }; - char game_name_[16] = { 0 }; + char ext_name_[4] = {0}; + char game_name_[16] = {0}; Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); - if ((company_code_ == 0) || (game_code_ == 0)) { + if ((company_code_ == 0) || (game_code_ == 0)) + { continue; - } else { + } + else + { if ((game_code == game_code_) && (company_code == company_code_) && - (strcmp((const char*) game_name, (const char*) game_name_) == 0) && - strcmp((const char*) ext_name, (const char*) ext_name_) == 0) { + (strcmp((const char *)game_name, (const char *)game_name_) == 0) && + strcmp((const char *)ext_name, (const char *)ext_name_) == 0) + { // File found *file_no = i; ctx->r2 = 0; // PFS_NO_ERROR @@ -309,12 +349,13 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 5; // PFS_ERR_INVALID } -extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsReadWriteFile_recomp(uint8_t *rdram, recomp_context *ctx) +{ s32 file_no = _arg<1, s32>(rdram, ctx); u8 flag = _arg<2, u8>(rdram, ctx); s32 offset = _arg<3, s32>(rdram, ctx); - s32 size_in_bytes = (s32) MEM_W(0x10, ctx->r29); - u8* data_buffer = TO_PTR(u8, MEM_W(0x14, ctx->r29)); + s32 size_in_bytes = (s32)MEM_W(0x10, ctx->r29); + u8 *data_buffer = TO_PTR(u8, MEM_W(0x14, ctx->r29)); ControllerPak pak; @@ -322,28 +363,34 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { sprintf(filename, "controllerPak_file_%d.sav", file_no); pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - if (!std::filesystem::exists(filename)) { + if (!std::filesystem::exists(filename)) + { ctx->r2 = 5; // PFS_ERR_INVALID return; } - if (!pak.file.good()) { + if (!pak.file.good()) + { ctx->r2 = 5; // PFS_ERR_INVALID return; } - if (!pak.file.is_open()) { + if (!pak.file.is_open()) + { ctx->r2 = 5; // PFS_ERR_INVALID return; } - u8* swapBuffer = (u8*) malloc(size_in_bytes); - if (flag == 0) { + u8 *swapBuffer = (u8 *)malloc(size_in_bytes); + if (flag == 0) + { pak.file.seekg(offset, std::ios::beg); - pak.file.read((char*) swapBuffer, size_in_bytes); + pak.file.read((char *)swapBuffer, size_in_bytes); ByteSwapCopy(data_buffer, swapBuffer, size_in_bytes); - } else { + } + else + { ByteSwapCopy(swapBuffer, data_buffer, size_in_bytes); pak.file.seekp(offset, std::ios::beg); - pak.file.write((char*) swapBuffer, size_in_bytes); + pak.file.write((char *)swapBuffer, size_in_bytes); } free(swapBuffer); @@ -352,25 +399,29 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsChecker_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsChecker_recomp(uint8_t *rdram, recomp_context *ctx) +{ ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { - s32* max_files = _arg<1, s32*>(rdram, ctx); - s32* files_used = _arg<2, s32*>(rdram, ctx); +extern "C" void osPfsNumFiles_recomp(uint8_t *rdram, recomp_context *ctx) +{ + s32 *max_files = _arg<1, s32 *>(rdram, ctx); + s32 *files_used = _arg<2, s32 *>(rdram, ctx); u8 files = 0; - for (size_t i = 0; i < MAX_FILES; i++) { + for (size_t i = 0; i < MAX_FILES; i++) + { u32 file_size = 0; u32 game_code = 0; u16 company_code = 0; - char ext_name[4] = { 0 }; - char game_name[16] = { 0 }; + char ext_name[4] = {0}; + char game_name[16] = {0}; Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, i); - if ((company_code != 0) || (game_code != 0)) { + if ((company_code != 0) || (game_code != 0)) + { files++; } } @@ -381,52 +432,61 @@ extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsDeleteFile_recomp(uint8_t *rdram, recomp_context *ctx) +{ u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); - u8* game_name = _arg<3, u8*>(rdram, ctx); - u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + u8 *game_name = _arg<3, u8 *>(rdram, ctx); + u8 *ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - if (company_code == 0 || game_code == 0) { + if (company_code == 0 || game_code == 0) + { ctx->r2 = 5; // PFS_ERR_INVALID return; } ControllerPak pak; - for (int i = 0; i < MAX_FILES; i++) { + for (int i = 0; i < MAX_FILES; i++) + { u32 file_size_ = 0; u32 game_code_ = 0; u16 company_code_ = 0; - char ext_name_[4] = { 0 }; - char game_name_[16] = { 0 }; + char ext_name_[4] = {0}; + char game_name_[16] = {0}; Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); - if ((company_code_ == 0) || (game_code_ == 0)) { + if ((company_code_ == 0) || (game_code_ == 0)) + { continue; - } else { + } + else + { if ((game_code == game_code_) && (company_code == company_code_) && - (strcmp((const char*) game_name, (const char*) game_name_) == 0) && - strcmp((const char*) ext_name, (const char*) ext_name_) == 0) { + (strcmp((const char *)game_name, (const char *)game_name_) == 0) && + strcmp((const char *)ext_name, (const char *)ext_name_) == 0) + { // File found pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - if (!pak.header.good()) { + if (!pak.header.good()) + { assert(false); } - if (!pak.header.is_open()) { + if (!pak.header.is_open()) + { assert(false); } u32 seek = i * sizeof(OSPfsState); // Zero out the header for this file. - u8* zero_block = (u8*) malloc(sizeof(OSPfsState)); + u8 *zero_block = (u8 *)malloc(sizeof(OSPfsState)); memset(zero_block, 0, sizeof(OSPfsState)); pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char*) zero_block, sizeof(OSPfsState)); + pak.header.write((char *)zero_block, sizeof(OSPfsState)); free(zero_block); pak.header.close(); @@ -446,6 +506,7 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { return; } -extern "C" void osPfsRepairId_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPfsRepairId_recomp(uint8_t *rdram, recomp_context *ctx) +{ _return(ctx, 0); // PFS_NO_ERROR } From 257b5db9d6a38c5f9b96fb67ad4f5abc69809b77 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 10 Aug 2025 01:00:36 -0400 Subject: [PATCH 17/49] Rewrite VI functionality for higher accuracy --- .../include/ultramodern/renderer_context.hpp | 19 +- ultramodern/src/events.cpp | 310 ++++++++++-------- 2 files changed, 198 insertions(+), 131 deletions(-) diff --git a/ultramodern/include/ultramodern/renderer_context.hpp b/ultramodern/include/ultramodern/renderer_context.hpp index b2da932..43032ea 100644 --- a/ultramodern/include/ultramodern/renderer_context.hpp +++ b/ultramodern/include/ultramodern/renderer_context.hpp @@ -28,6 +28,23 @@ struct SDL_Window; namespace ultramodern { namespace renderer { + struct ViRegs { + unsigned int VI_STATUS_REG; + unsigned int VI_ORIGIN_REG; + unsigned int VI_WIDTH_REG; + unsigned int VI_INTR_REG; + unsigned int VI_V_CURRENT_LINE_REG; + unsigned int VI_TIMING_REG; + unsigned int VI_V_SYNC_REG; + unsigned int VI_H_SYNC_REG; + unsigned int VI_LEAP_REG; + unsigned int VI_H_START_REG; + unsigned int VI_V_START_REG; + unsigned int VI_V_BURST_REG; + unsigned int VI_X_SCALE_REG; + unsigned int VI_Y_SCALE_REG; + }; + ViRegs* get_vi_regs(); #if defined(_WIN32) // Native HWND handle to the target window. @@ -67,7 +84,7 @@ namespace ultramodern { virtual void enable_instant_present() = 0; virtual void send_dl(const OSTask* task) = 0; - virtual void update_screen(uint32_t vi_origin) = 0; + virtual void update_screen() = 0; virtual void shutdown() = 0; virtual uint32_t get_display_framerate() const = 0; virtual float get_resolution_scale() const = 0; diff --git a/ultramodern/src/events.cpp b/ultramodern/src/events.cpp index fed835d..7fb890b 100644 --- a/ultramodern/src/events.cpp +++ b/ultramodern/src/events.cpp @@ -27,23 +27,82 @@ struct SpTaskAction { OSTask task; }; -struct SwapBuffersAction { - uint32_t origin; +struct ScreenUpdateAction { }; struct UpdateConfigAction { }; -using Action = std::variant; +using Action = std::variant; + +struct ViState { + const OSViMode* mode; + PTR(void) framebuffer; + PTR(OSMesg) mq; + OSMesg msg; + uint32_t state; + uint32_t control; + int retrace_count = 1; +}; + +#define VI_STATE_BLACK 0x20 +#define VI_STATE_REPEATLINE 0x40 static struct { struct { std::thread thread; - PTR(OSMesgQueue) mq = NULLPTR; - PTR(void) current_buffer = NULLPTR; - PTR(void) next_buffer = NULLPTR; - OSMesg msg = (OSMesg)0; - int retrace_count = 1; + int cur_state; + int field; + ViState states[2]; + ultramodern::renderer::ViRegs regs; + + ViState* get_next_state() { + return &states[cur_state ^ 1]; + } + ViState* get_cur_state() { + return &states[cur_state]; + } + void update_vi() { + ViState* next_state = get_next_state(); + const OSViMode* next_mode = next_state->mode; + const OSViCommonRegs* common_regs = &next_mode->comRegs; + const OSViFieldRegs* field_regs = &next_mode->fldRegs[field]; + PTR(void) framebuffer = osVirtualToPhysical(next_state->framebuffer); + PTR(void) origin = framebuffer + field_regs->origin; + + // Process the VI state flags. + uint32_t hStart = common_regs->hStart; + if (next_state->state & VI_STATE_BLACK) { + hStart = 0; + } + + uint32_t yScale = field_regs->yScale; + if (next_state->state & VI_STATE_REPEATLINE) { + yScale = 0; + origin = framebuffer; + } + + // TODO implement osViFade + + // Update VI registers. + regs.VI_ORIGIN_REG = origin; + regs.VI_WIDTH_REG = common_regs->width; + regs.VI_TIMING_REG = common_regs->burst; + regs.VI_V_SYNC_REG = common_regs->vSync; + regs.VI_H_SYNC_REG = common_regs->hSync; + regs.VI_LEAP_REG = common_regs->leap; + regs.VI_H_START_REG = hStart; + regs.VI_V_START_REG = field_regs->vStart; // TODO implement osViExtendVStart + regs.VI_V_BURST_REG = field_regs->vBurst; + regs.VI_INTR_REG = field_regs->vIntr; + regs.VI_X_SCALE_REG = common_regs->xScale; // TODO implement osViSetXScale + regs.VI_Y_SCALE_REG = yScale; // TODO implement osViSetYScale + regs.VI_STATUS_REG = next_state->control; + + // Swap VI states. + cur_state ^= 1; + *get_next_state() = *get_cur_state(); + } } vi; struct { std::thread gfx_thread; @@ -71,8 +130,11 @@ static struct { moodycamel::ConcurrentQueue deleted_threads{}; } events_context{}; +ultramodern::renderer::ViRegs* ultramodern::renderer::get_vi_regs() { + return &events_context.vi.regs; +} + extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) { - OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_); std::lock_guard lock{ events_context.message_mutex }; switch (event_id) { @@ -96,9 +158,10 @@ extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 retrace_count) { std::lock_guard lock{ events_context.message_mutex }; - events_context.vi.mq = mq_; - events_context.vi.msg = msg; - events_context.vi.retrace_count = retrace_count; + ViState* next_state = events_context.vi.get_next_state(); + next_state->mq = mq_; + next_state->msg = msg; + next_state->retrace_count = retrace_count; } uint64_t total_vis = 0; @@ -107,7 +170,7 @@ uint64_t total_vis = 0; extern std::atomic_bool exited; extern moodycamel::LightweightSemaphore graphics_shutdown_ready; -void set_dummy_vi(); +void set_dummy_vi(bool odd); void vi_thread_func() { ultramodern::set_native_thread_name("VI Thread"); @@ -116,7 +179,7 @@ void vi_thread_func() { ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Critical); using namespace std::chrono_literals; - int remaining_retraces = events_context.vi.retrace_count; + int remaining_retraces = 1; while (!exited) { // Determine the next VI time (more accurate than adding 16ms each VI interrupt) @@ -142,32 +205,34 @@ void vi_thread_func() { } total_vis = new_total_vis; - remaining_retraces--; + // If the game hasn't started yet, set a dummy VI mode and origin. + if (!ultramodern::is_game_started()) { + static bool odd = false; + set_dummy_vi(odd); + odd = !odd; + } - { - std::lock_guard lock{ events_context.message_mutex }; + // Update VI registers and swap VI modes. + events_context.vi.update_vi(); + + // Queue a screen update for the graphics thread. + events_context.action_queue.enqueue(ScreenUpdateAction{ }); + + // If the game has started, handle sending VI and AI events. + if (ultramodern::is_game_started()) { + remaining_retraces--; + uint8_t* rdram = events_context.rdram; + std::lock_guard lock{ events_context.message_mutex }; + ViState* cur_state = events_context.vi.get_cur_state(); if (remaining_retraces == 0) { - remaining_retraces = events_context.vi.retrace_count; + remaining_retraces = cur_state->retrace_count; - if (ultramodern::is_game_started()) { - if (events_context.vi.mq != NULLPTR) { - if (osSendMesg(PASS_RDRAM events_context.vi.mq, events_context.vi.msg, OS_MESG_NOBLOCK) == -1) { - //printf("Game skipped a VI frame!\n"); - } + if (cur_state->mq != NULLPTR) { + if (osSendMesg(PASS_RDRAM cur_state->mq, cur_state->msg, OS_MESG_NOBLOCK) == -1) { + //printf("Game skipped a VI frame!\n"); } } - else { - set_dummy_vi(); - static bool swap = false; - uint32_t vi_origin = 0x400 + 0x280; // Skip initial RDRAM contents and add the usual origin offset - // Offset by one FB every other frame so RT64 continues drawing - if (swap) { - vi_origin += 0x25800; - } - osViSwapBuffer(rdram, vi_origin); - swap = !swap; - } } if (events_context.ai.mq != NULLPTR) { if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) { @@ -298,19 +363,20 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re sp_complete(); ultramodern::measure_input_latency(); - auto renderer_start = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto renderer_start = std::chrono::high_resolution_clock::now(); renderer_context->send_dl(&task_action->task); - auto renderer_end = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto renderer_end = std::chrono::high_resolution_clock::now(); dp_complete(); // printf("Renderer ProcessDList time: %d us\n", static_cast(std::chrono::duration_cast(renderer_end - renderer_start).count())); } - else if (const auto* swap_action = std::get_if(&action)) { - events_context.vi.current_buffer = events_context.vi.next_buffer; - renderer_context->update_screen(swap_action->origin); + else if (const auto* screen_update_action = std::get_if(&action)) { + (void)screen_update_action; + renderer_context->update_screen(); display_refresh_rate = renderer_context->get_display_framerate(); resolution_scale = renderer_context->get_resolution_scale(); } else if (const auto* config_action = std::get_if(&action)) { + (void)config_action; auto new_config = ultramodern::renderer::get_graphics_config(); if (renderer_context->update_config(old_config, new_config)) { old_config = new_config; @@ -323,79 +389,6 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re renderer_context->shutdown(); } -extern unsigned int VI_STATUS_REG; -extern unsigned int VI_ORIGIN_REG; -extern unsigned int VI_WIDTH_REG; -extern unsigned int VI_INTR_REG; -extern unsigned int VI_V_CURRENT_LINE_REG; -extern unsigned int VI_TIMING_REG; -extern unsigned int VI_V_SYNC_REG; -extern unsigned int VI_H_SYNC_REG; -extern unsigned int VI_LEAP_REG; -extern unsigned int VI_H_START_REG; -extern unsigned int VI_V_START_REG; -extern unsigned int VI_V_BURST_REG; -extern unsigned int VI_X_SCALE_REG; -extern unsigned int VI_Y_SCALE_REG; - -#define VI_STATE_BLACK 0x20 -#define VI_STATE_REPEATLINE 0x40 - -uint32_t hstart = 0; -uint32_t vi_origin_offset = 320 * sizeof(uint16_t); -static uint16_t vi_state = 0; - -void set_dummy_vi() { - VI_STATUS_REG = 0x311E; - VI_WIDTH_REG = 0x140; - VI_V_SYNC_REG = 0x20D; - VI_H_SYNC_REG = 0xC15; - VI_LEAP_REG = 0x0C150C15; - hstart = 0x006C02EC; - VI_X_SCALE_REG = 0x200; - VI_V_CURRENT_LINE_REG = 0x0; - vi_origin_offset = 0x280; - VI_Y_SCALE_REG = 0x400; - VI_V_START_REG = 0x2501FF; - VI_V_BURST_REG = 0xE0204; - VI_INTR_REG = 0x2; -} - -extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) { - VI_H_START_REG = hstart; - if (vi_state & VI_STATE_BLACK) { - VI_H_START_REG = 0; - } - - if (vi_state & VI_STATE_REPEATLINE) { - VI_Y_SCALE_REG = 0; - VI_ORIGIN_REG = osVirtualToPhysical(frameBufPtr); - } - - events_context.vi.next_buffer = frameBufPtr; - events_context.action_queue.enqueue(SwapBuffersAction{ osVirtualToPhysical(frameBufPtr) + vi_origin_offset }); -} - -extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) { - OSViMode* mode = TO_PTR(OSViMode, mode_); - VI_STATUS_REG = mode->comRegs.ctrl; - VI_WIDTH_REG = mode->comRegs.width; - // burst - VI_V_SYNC_REG = mode->comRegs.vSync; - VI_H_SYNC_REG = mode->comRegs.hSync; - VI_LEAP_REG = mode->comRegs.leap; - hstart = mode->comRegs.hStart; - VI_X_SCALE_REG = mode->comRegs.xScale; - VI_V_CURRENT_LINE_REG = mode->comRegs.vCurrent; - - // TODO swap these every VI to account for fields changing - vi_origin_offset = mode->fldRegs[0].origin; - VI_Y_SCALE_REG = mode->fldRegs[0].yScale; - VI_V_START_REG = mode->fldRegs[0].vStart; - VI_V_BURST_REG = mode->fldRegs[0].vBurst; - VI_INTR_REG = mode->fldRegs[0].vIntr; -} - #define VI_CTRL_TYPE_16 0x00002 #define VI_CTRL_TYPE_32 0x00003 #define VI_CTRL_GAMMA_DITHER_ON 0x00004 @@ -412,6 +405,54 @@ extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) { #define VI_CTRL_PIXEL_ADV_3 0x03000 #define VI_CTRL_DITHER_FILTER_ON 0x10000 +static const OSViMode dummy_mode = []() { + OSViMode ret{}; + + ret.type = 2; + ret.comRegs.ctrl = VI_CTRL_TYPE_16 | VI_CTRL_GAMMA_DITHER_ON | VI_CTRL_GAMMA_ON | VI_CTRL_DIVOT_ON | VI_CTRL_ANTIALIAS_MODE_1 | VI_CTRL_PIXEL_ADV_3; + ret.comRegs.width = 0x140; + ret.comRegs.burst = 0x03E52239; + ret.comRegs.vSync = 0x20D; + ret.comRegs.hSync = 0xC15; + ret.comRegs.leap = 0x0C150C15; + ret.comRegs.hStart = 0x006C02EC; + ret.comRegs.xScale = 0x200; + ret.comRegs.vCurrent = 0x0; + + for (int field = 0; field < 2; field++) { + ret.fldRegs[field].origin = 0x280; + ret.fldRegs[field].yScale = 0x400; + ret.fldRegs[field].vStart = 0x2501FF; + ret.fldRegs[field].vBurst = 0xE0204; + ret.fldRegs[field].vIntr = 0x2; + } + + return ret; +}(); + +void set_dummy_vi(bool odd) { + ViState* next_state = events_context.vi.get_next_state(); + next_state->mode = &dummy_mode; + // Set up a dummy framebuffer. + next_state->framebuffer = 0x80700000; + if (odd) { + next_state->framebuffer += 0x25800; + } +} + +extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) { + std::lock_guard lock{ events_context.message_mutex }; + events_context.vi.get_next_state()->framebuffer = frameBufPtr; +} + +extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) { + std::lock_guard lock{ events_context.message_mutex }; + OSViMode* mode = TO_PTR(OSViMode, mode_); + ViState* next_state = events_context.vi.get_next_state(); + next_state->mode = mode; + next_state->control = next_state->mode->comRegs.ctrl; +} + #define OS_VI_GAMMA_ON 0x0001 #define OS_VI_GAMMA_OFF 0x0002 #define OS_VI_GAMMA_DITHER_ON 0x0004 @@ -422,54 +463,63 @@ extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) { #define OS_VI_DITHER_FILTER_OFF 0x0080 extern "C" void osViSetSpecialFeatures(uint32_t func) { + std::lock_guard lock{ events_context.message_mutex }; + ViState* next_state = events_context.vi.get_next_state(); + uint32_t* control_out = &next_state->control; if ((func & OS_VI_GAMMA_ON) != 0) { - VI_STATUS_REG |= VI_CTRL_GAMMA_ON; + *control_out |= VI_CTRL_GAMMA_ON; } if ((func & OS_VI_GAMMA_OFF) != 0) { - VI_STATUS_REG &= ~VI_CTRL_GAMMA_ON; + *control_out &= ~VI_CTRL_GAMMA_ON; } if ((func & OS_VI_GAMMA_DITHER_ON) != 0) { - VI_STATUS_REG |= VI_CTRL_GAMMA_DITHER_ON; + *control_out |= VI_CTRL_GAMMA_DITHER_ON; } if ((func & OS_VI_GAMMA_DITHER_OFF) != 0) { - VI_STATUS_REG &= ~VI_CTRL_GAMMA_DITHER_ON; + *control_out &= ~VI_CTRL_GAMMA_DITHER_ON; } if ((func & OS_VI_DIVOT_ON) != 0) { - VI_STATUS_REG |= VI_CTRL_DIVOT_ON; + *control_out |= VI_CTRL_DIVOT_ON; } if ((func & OS_VI_DIVOT_OFF) != 0) { - VI_STATUS_REG &= ~VI_CTRL_DIVOT_ON; + *control_out &= ~VI_CTRL_DIVOT_ON; } if ((func & OS_VI_DITHER_FILTER_ON) != 0) { - VI_STATUS_REG |= VI_CTRL_DITHER_FILTER_ON; - VI_STATUS_REG &= ~VI_CTRL_ANTIALIAS_MASK; + *control_out |= VI_CTRL_DITHER_FILTER_ON; + *control_out &= ~VI_CTRL_ANTIALIAS_MASK; } if ((func & OS_VI_DITHER_FILTER_OFF) != 0) { - VI_STATUS_REG &= ~VI_CTRL_DITHER_FILTER_ON; - //VI_STATUS_REG |= __osViNext->modep->comRegs.ctrl & VI_CTRL_ANTIALIAS_MASK; + *control_out &= ~VI_CTRL_DITHER_FILTER_ON; + *control_out |= next_state->mode->comRegs.ctrl & VI_CTRL_ANTIALIAS_MASK; } } extern "C" void osViBlack(uint8_t active) { + std::lock_guard lock{ events_context.message_mutex }; + ViState* next_state = events_context.vi.get_next_state(); + uint32_t* state_out = &next_state->state; if (active) { - vi_state |= VI_STATE_BLACK; + *state_out |= VI_STATE_BLACK; } else { - vi_state &= ~VI_STATE_BLACK; + *state_out &= ~VI_STATE_BLACK; } } extern "C" void osViRepeatLine(uint8_t active) { + std::lock_guard lock{ events_context.message_mutex }; + ViState* next_state = events_context.vi.get_next_state(); + uint32_t* state_out = &next_state->state; if (active) { - vi_state |= VI_STATE_REPEATLINE; + *state_out |= VI_STATE_REPEATLINE; } else { - vi_state &= ~VI_STATE_REPEATLINE; + *state_out &= ~VI_STATE_REPEATLINE; } } @@ -486,11 +536,11 @@ extern "C" void osViSetYScale(float scale) { } extern "C" PTR(void) osViGetNextFramebuffer() { - return events_context.vi.next_buffer; + return events_context.vi.get_next_state()->framebuffer; } extern "C" PTR(void) osViGetCurrentFramebuffer() { - return events_context.vi.current_buffer; + return events_context.vi.get_cur_state()->framebuffer; } void ultramodern::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) { @@ -523,7 +573,7 @@ void ultramodern::init_events(RDRAM_ARG ultramodern::renderer::WindowHandle wind task_thread_ready.wait(); ultramodern::renderer::SetupResult setup_result = renderer_setup_result.load(); - if (renderer_setup_result != ultramodern::renderer::SetupResult::Success) { + if (setup_result != ultramodern::renderer::SetupResult::Success) { auto show_renderer_error = [](const std::string& msg) { std::string error_msg = "An error has been encountered on startup: " + msg; @@ -531,7 +581,7 @@ void ultramodern::init_events(RDRAM_ARG ultramodern::renderer::WindowHandle wind }; const std::string driver_os_suffix = "\nPlease make sure your GPU drivers and your OS are up to date."; - switch (renderer_setup_result) { + switch (setup_result) { case ultramodern::renderer::SetupResult::Success: break; case ultramodern::renderer::SetupResult::DynamicLibrariesNotFound: From d34934aa7eb7a424fb5c0186b7a22d8e5ce130e4 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 11 Aug 2025 01:28:41 -0400 Subject: [PATCH 18/49] Implement osSetTime and move update screen to before VI update --- librecomp/src/ultra_translation.cpp | 9 +++++++++ ultramodern/include/ultramodern/ultra64.h | 2 ++ ultramodern/src/events.cpp | 19 +++++++++++-------- ultramodern/src/timer.cpp | 12 +++++++++++- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/librecomp/src/ultra_translation.cpp b/librecomp/src/ultra_translation.cpp index 24ea0bd..e0d367e 100644 --- a/librecomp/src/ultra_translation.cpp +++ b/librecomp/src/ultra_translation.cpp @@ -73,12 +73,21 @@ extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) { ctx->r2 = osGetCount(); } +extern "C" void osSetCount_recomp(uint8_t * rdram, recomp_context * ctx) { + osSetCount(ctx->r4); +} + extern "C" void osGetTime_recomp(uint8_t * rdram, recomp_context * ctx) { uint64_t total_count = osGetTime(); ctx->r2 = (int32_t)(total_count >> 32); ctx->r3 = (int32_t)(total_count >> 0); } +extern "C" void osSetTime_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t t = ((uint64_t)(ctx->r4) << 32) | ((ctx->r5) & 0xFFFFFFFFu); + osSetTime(t); +} + extern "C" void osSetTimer_recomp(uint8_t * rdram, recomp_context * ctx) { uint64_t countdown = ((uint64_t)(ctx->r6) << 32) | ((ctx->r7) & 0xFFFFFFFFu); uint64_t interval = load_doubleword(rdram, ctx->r29, 0x10); diff --git a/ultramodern/include/ultramodern/ultra64.h b/ultramodern/include/ultramodern/ultra64.h index 71a412f..31eb106 100644 --- a/ultramodern/include/ultramodern/ultra64.h +++ b/ultramodern/include/ultramodern/ultra64.h @@ -290,7 +290,9 @@ void osViSetYScale(float scale); PTR(void) osViGetNextFramebuffer(); PTR(void) osViGetCurrentFramebuffer(); u32 osGetCount(); +void osSetCount(u32 count); OSTime osGetTime(); +void osSetTime(OSTime t); int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg); int osStopTimer(RDRAM_ARG PTR(OSTimer) timer); u32 osVirtualToPhysical(PTR(void) addr); diff --git a/ultramodern/src/events.cpp b/ultramodern/src/events.cpp index 7fb890b..46f63f1 100644 --- a/ultramodern/src/events.cpp +++ b/ultramodern/src/events.cpp @@ -28,6 +28,7 @@ struct SpTaskAction { }; struct ScreenUpdateAction { + ultramodern::renderer::ViRegs regs; }; struct UpdateConfigAction { @@ -55,6 +56,7 @@ static struct { int field; ViState states[2]; ultramodern::renderer::ViRegs regs; + ultramodern::renderer::ViRegs update_screen_regs; ViState* get_next_state() { return &states[cur_state ^ 1]; @@ -131,7 +133,7 @@ static struct { } events_context{}; ultramodern::renderer::ViRegs* ultramodern::renderer::get_vi_regs() { - return &events_context.vi.regs; + return &events_context.vi.update_screen_regs; } extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) { @@ -198,8 +200,9 @@ void vi_thread_func() { next = std::chrono::high_resolution_clock::now(); } ultramodern::sleep_until(next); + auto time_now = ultramodern::time_since_start(); // Calculate how many VIs have passed - uint64_t new_total_vis = (ultramodern::time_since_start() * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1; + uint64_t new_total_vis = (time_now * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1; if (new_total_vis > total_vis + 1) { //printf("Skipped % " PRId64 " frames in VI interupt thread!\n", new_total_vis - total_vis - 1); } @@ -212,12 +215,13 @@ void vi_thread_func() { odd = !odd; } + // Queue a screen update for the graphics thread with the current VI register state. + // Doing this before the VI update is equivalent to updating the screen after the previous frame's scanout finished. + events_context.action_queue.enqueue(ScreenUpdateAction{ events_context.vi.regs }); + // Update VI registers and swap VI modes. events_context.vi.update_vi(); - // Queue a screen update for the graphics thread. - events_context.action_queue.enqueue(ScreenUpdateAction{ }); - // If the game has started, handle sending VI and AI events. if (ultramodern::is_game_started()) { remaining_retraces--; @@ -226,13 +230,12 @@ void vi_thread_func() { std::lock_guard lock{ events_context.message_mutex }; ViState* cur_state = events_context.vi.get_cur_state(); if (remaining_retraces == 0) { - remaining_retraces = cur_state->retrace_count; - if (cur_state->mq != NULLPTR) { if (osSendMesg(PASS_RDRAM cur_state->mq, cur_state->msg, OS_MESG_NOBLOCK) == -1) { //printf("Game skipped a VI frame!\n"); } } + remaining_retraces = cur_state->retrace_count; } if (events_context.ai.mq != NULLPTR) { if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) { @@ -370,7 +373,7 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re // printf("Renderer ProcessDList time: %d us\n", static_cast(std::chrono::duration_cast(renderer_end - renderer_start).count())); } else if (const auto* screen_update_action = std::get_if(&action)) { - (void)screen_update_action; + events_context.vi.update_screen_regs = screen_update_action->regs; renderer_context->update_screen(); display_refresh_rate = renderer_context->get_display_framerate(); resolution_scale = renderer_context->get_resolution_scale(); diff --git a/ultramodern/src/timer.cpp b/ultramodern/src/timer.cpp index 1b4e9ae..d36ffcc 100644 --- a/ultramodern/src/timer.cpp +++ b/ultramodern/src/timer.cpp @@ -13,6 +13,8 @@ // Start time for the program static std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); +// Offset of the duration since program start used to calculate the value for osGetTime. +static int64_t ostime_offset = 0; // Game speed multiplier (1 means no speedup) constexpr uint32_t speed_multiplier = 1; // N64 CPU counter ticks per millisecond @@ -162,12 +164,20 @@ extern "C" u32 osGetCount() { return (uint32_t)total_count; } +extern "C" void osSetCount(u32 count) { + assert(false); +} + extern "C" OSTime osGetTime() { - uint64_t total_count = time_now(); + uint64_t total_count = time_now() - ostime_offset; return total_count; } +extern "C" void osSetTime(OSTime t) { + ostime_offset = time_now() - t; +} + extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) { OSTimer* t = TO_PTR(OSTimer, t_); From c93aadd261c59826615362493019304964a6b20d Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Mon, 11 Aug 2025 23:18:56 -0300 Subject: [PATCH 19/49] update to vi-fixes --- N64Recomp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/N64Recomp b/N64Recomp index c1a6dc9..8781eb4 160000 --- a/N64Recomp +++ b/N64Recomp @@ -1 +1 @@ -Subproject commit c1a6dc93bfa5977de0ea256562058be4f1b73353 +Subproject commit 8781eb44acbf55cb6a109d2aa5529aadb95a419d From ff5daef97fa7623a722dd0adb6adfdb4e6c3b3c5 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Mon, 11 Aug 2025 23:33:06 -0300 Subject: [PATCH 20/49] update n64recomp --- N64Recomp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/N64Recomp b/N64Recomp index 8781eb4..a13e5cf 160000 --- a/N64Recomp +++ b/N64Recomp @@ -1 +1 @@ -Subproject commit 8781eb44acbf55cb6a109d2aa5529aadb95a419d +Subproject commit a13e5cff96686776b0e03baf23923e3c1927b770 From 48d4f287378e61d4be8cedd8d47e10c681be988d Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Sat, 1 Nov 2025 18:26:31 -0300 Subject: [PATCH 21/49] Fix duke nukem zero hour --- librecomp/src/pak.cpp | 417 +++++++++++++++++++++++++----------------- 1 file changed, 253 insertions(+), 164 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index 5643a9e..ff4bc48 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -7,11 +7,11 @@ #include "recomp.h" #include "helpers.hpp" +#define PAK_DEBUG 0 #define ARRAY_COUNT(arr) (s32)(sizeof(arr) / sizeof(arr[0])) #define MAX_FILES 16 -typedef struct ControllerPak -{ +typedef struct ControllerPak { std::fstream header; std::fstream file; } ControllerPak; @@ -20,56 +20,49 @@ typedef struct ControllerPak // const std::u8string save_folder = u8"saves"; // std::filesystem::path save_folder_path = config_path / save_folder; -void Pfs_PakHeader_Write(u32 *file_size, u32 *game_code, u16 *company_code, u8 *ext_name, u8 *game_name, u8 fileIndex) -{ +void Pfs_PakHeader_Write(u32* file_size, u32* game_code, u16* company_code, u8* ext_name, u8* game_name, u8 fileIndex) { ControllerPak pak; pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - if (!pak.header.good()) - { + if (!pak.header.good()) { assert(false); } - if (!pak.header.is_open()) - { + if (!pak.header.is_open()) { assert(false); } /* Set file parameters to header */ - u32 seek = fileIndex * sizeof(OSPfsState); + u32 seek = fileIndex * 0x20; // file_size pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char *)file_size, 4); + pak.header.write((char*) file_size, 4); // game_code pak.header.seekp(seek + 0x4, std::ios::beg); - pak.header.write((char *)game_code, 4); + pak.header.write((char*) game_code, 4); // company_code pak.header.seekp(seek + 0x08, std::ios::beg); - pak.header.write((char *)company_code, 2); + pak.header.write((char*) company_code, 2); // ext_name - pak.header.seekp(seek + 0x0C, std::ios::beg); - pak.header.write((char *)ext_name, 4); + pak.header.seekp(seek + 0xC, std::ios::beg); + pak.header.write((char*) ext_name, 4); // game_name pak.header.seekp(seek + 0x10, std::ios::beg); - pak.header.write((char *)game_name, 16); + pak.header.write((char*) game_name, 16); pak.header.close(); } -void Pfs_PakHeader_Read(u32 *file_size, u32 *game_code, u16 *company_code, char *ext_name, char *game_name, - u8 fileIndex) -{ +void Pfs_PakHeader_Read(u32* file_size, u32* game_code, u16* company_code, u8* ext_name, u8* game_name, u8 fileIndex) { ControllerPak pak; pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - if (!pak.header.good()) - { + if (!pak.header.good()) { assert(false); } - if (!pak.header.is_open()) - { + if (!pak.header.is_open()) { assert(false); } @@ -78,29 +71,27 @@ void Pfs_PakHeader_Read(u32 *file_size, u32 *game_code, u16 *company_code, char // file_size pak.header.seekg(seek + 0x0, std::ios::beg); - pak.header.read((char *)file_size, 4); + pak.header.read((char*) file_size, 4); // game_code pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char *)game_code, 4); + pak.header.read((char*) game_code, 4); // company_code pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char *)company_code, 2); + pak.header.read((char*) company_code, 2); // ext_name - pak.header.seekg(seek + 0x0C, std::ios::beg); - pak.header.read((char *)ext_name, 4); + pak.header.seekg(seek + 0xC, std::ios::beg); + pak.header.read((char*) ext_name, 4); // game_name pak.header.seekg(seek + 0x10, std::ios::beg); - pak.header.read((char *)game_name, 16); + pak.header.read((char*) game_name, 16); pak.header.close(); } -void Pfs_ByteSwapFile(u8 *buffer, size_t size) -{ +void Pfs_ByteSwapFile(u8* buffer, size_t size) { uint8_t c0, c1, c2, c3; - for (size_t i = 0; i < size; i += 4) - { + for (size_t i = 0; i < size; i += 4) { c0 = buffer[i + 0]; c1 = buffer[i + 1]; c2 = buffer[i + 2]; @@ -113,24 +104,20 @@ void Pfs_ByteSwapFile(u8 *buffer, size_t size) } } -void ByteSwapCopy(uint8_t *dst, uint8_t *src, size_t size_bytes) -{ - for (size_t i = 0; i < size_bytes; i++) - { +void ByteSwapCopy(uint8_t* dst, uint8_t* src, size_t size_bytes) { + for (size_t i = 0; i < size_bytes; i++) { dst[i] = src[i ^ 3]; } } -extern "C" void osPfsIsPlug_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { MEM_B(0, ctx->r5) = 0b0001; // *pattern = 1; ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsInit_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { int32_t queue = _arg<0, int32_t>(rdram, ctx); - OSPfs *pfs = _arg<1, OSPfs *>(rdram, ctx); + OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); s32 channel = _arg<2, s32>(rdram, ctx); pfs->queue = queue; @@ -140,8 +127,7 @@ extern "C" void osPfsInit_recomp(uint8_t *rdram, recomp_context *ctx) ControllerPak pak; // If a header file doesn't exist, create it. - if (!std::filesystem::exists("controllerPak_header.sav")) - { + if (!std::filesystem::exists("controllerPak_header.sav")) { pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); pak.header.close(); } @@ -149,10 +135,9 @@ extern "C" void osPfsInit_recomp(uint8_t *rdram, recomp_context *ctx) ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsInitPak_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { int32_t queue = _arg<0, int32_t>(rdram, ctx); - OSPfs *pfs = _arg<1, OSPfs *>(rdram, ctx); + OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); s32 channel = _arg<2, s32>(rdram, ctx); pfs->queue = queue; @@ -162,36 +147,60 @@ extern "C" void osPfsInitPak_recomp(uint8_t *rdram, recomp_context *ctx) ControllerPak pak; // If a header file doesn't exist, create it. - if (!std::filesystem::exists("controllerPak_header.sav")) - { + if (!std::filesystem::exists("controllerPak_header.sav")) { pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); pak.header.close(); } +// Test +#if PAK_DEBUG + u32 file_size_ = 0x01020304; + u32 game_code_ = 0x05060708; + u16 company_code_ = 0x0910; + u8 ext_name_[4] = { 0x11, 0x12, 0x13, 0x14 }; + u8 game_name_[16] = { 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24 }; + + Pfs_PakHeader_Write(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, 0); + + Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, 0); + + printf("TEST\n"); + printf("file_size_: %x\n", file_size_); + printf("game_code: %x\n", game_code_); + printf("company_code: %x\n", company_code_); + + printf("ext_name: "); + for (size_t i = 0; i < 4; i++) { + printf("%x", ext_name_[i]); + } + printf("\n"); + + printf("game_name: "); + for (size_t i = 0; i < 16; i++) { + printf("%x", game_name_[i]); + } + printf("\n"); +#endif + ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsFreeBlocks_recomp(uint8_t *rdram, recomp_context *ctx) -{ - s32 *bytes_not_used = _arg<1, s32 *>(rdram, ctx); +extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { + s32* bytes_not_used = _arg<1, s32*>(rdram, ctx); s32 usedSpace = 0; - for (size_t i = 0; i < MAX_FILES; i++) - { + for (size_t i = 0; i < MAX_FILES; i++) { u32 file_size = 0; u32 game_code = 0; u16 company_code = 0; - char ext_name[4] = {0}; - char game_name[16] = {0}; + u8 ext_name[4] = { 0 }; + u8 game_name[16] = { 0 }; Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, i); - if ((company_code == 0) || (game_code == 0)) - { + if ((company_code == 0) || (game_code == 0)) { continue; - } - else - { + } else { usedSpace += file_size >> 8; } } @@ -201,35 +210,31 @@ extern "C" void osPfsFreeBlocks_recomp(uint8_t *rdram, recomp_context *ctx) ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsAllocateFile_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); - u8 *game_name = _arg<3, u8 *>(rdram, ctx); - u8 *ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - u32 file_size = (s32)MEM_W(0x14, ctx->r29); - s32 *file_no = TO_PTR(s32, MEM_W(0x18, ctx->r29)); + u8* game_name = _arg<3, u8*>(rdram, ctx); + u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + u32 file_size = (s32) MEM_W(0x14, ctx->r29); + s32* file_no = TO_PTR(s32, MEM_W(0x18, ctx->r29)); - if ((company_code == 0) || (game_code == 0)) - { + if ((company_code == 0) || (game_code == 0)) { ctx->r2 = 5; // PFS_ERR_INVALID return; } /* Search for a free slot */ u8 freeFileIndex = 0; - for (size_t i = 0; i < MAX_FILES; i++) - { + for (size_t i = 0; i < MAX_FILES; i++) { u32 file_size_ = 0; u32 game_code_ = 0; u16 company_code_ = 0; - char ext_name_[4] = {0}; - char game_name_[16] = {0}; + u8 ext_name_[4] = { 0 }; + u8 game_name_[16] = { 0 }; Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); - if ((company_code_ == 0) || (game_code_ == 0)) - { + if ((company_code_ == 0) || (game_code_ == 0)) { freeFileIndex = i; break; } @@ -252,11 +257,11 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t *rdram, recomp_context *ctx) file_size = (file_size + 31) & ~31; - u8 *zero_block = (u8 *)malloc(file_size); + u8* zero_block = (u8*) malloc(file_size); memset(zero_block, 0, file_size); pak.file.seekp(0, std::ios::beg); - pak.file.write((char *)zero_block, file_size); + pak.file.write((char*) zero_block, file_size); free(zero_block); @@ -267,16 +272,15 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t *rdram, recomp_context *ctx) ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsFileState_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { s32 file_no = _arg<1, s32>(rdram, ctx); - OSPfsState *state = _arg<2, OSPfsState *>(rdram, ctx); + OSPfsState* state = _arg<2, OSPfsState*>(rdram, ctx); u32 file_size = 0; u32 game_code = 0; u16 company_code = 0; - char ext_name[4] = {0}; - char game_name[16] = {0}; + u8 ext_name[4] = { 0 }; + u8 game_name[16] = { 0 }; // should pass the state of the requested file_no to the incoming state pointer, // games call this function 16 times, once per file @@ -284,8 +288,7 @@ extern "C" void osPfsFileState_recomp(uint8_t *rdram, recomp_context *ctx) char filename[100]; sprintf(filename, "controllerPak_file_%d.sav", file_no); - if (!std::filesystem::exists(filename)) - { + if (!std::filesystem::exists(filename)) { ctx->r2 = 5; return; } @@ -293,51 +296,102 @@ extern "C" void osPfsFileState_recomp(uint8_t *rdram, recomp_context *ctx) /* Read game info from pak */ Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, file_no); + // Test code +#if 0 + for (size_t i = 0; i < 5; i++) { + ext_name[i] = i + 1; + } + + for (size_t i = 0; i < 16; i++) { + game_name[i] = i + 1; + } +#endif + +#if 1 state->file_size = file_size; - state->company_code = game_code; + state->company_code = company_code; state->game_code = game_code; - for (size_t j = 0; j < ARRAY_COUNT(game_name); j++) - { + for (size_t j = 0; j < ARRAY_COUNT(game_name); j++) { state->game_name[j] = game_name[j]; } - for (size_t j = 0; j < ARRAY_COUNT(ext_name); j++) - { + for (size_t j = 0; j < ARRAY_COUNT(ext_name); j++) { state->ext_name[j] = ext_name[j]; } +#else + u8* ptr = (u8*) state; + *(u32*) &ptr[0] = file_size; + *(u32*) &ptr[0x4] = game_code; + *(u16*) &ptr[0xA] = company_code; + + // printf("*(u16*) &ptr[0x8] = company_code; : %x\n", ptr[0xA]); + + char* ext_name_ = (char*) &ptr[0xC]; + char* game_name_ = (char*) &ptr[0x10]; + + for (size_t j = 0; j < 4; j++) { + ext_name_[j] = ext_name[j]; + } + for (size_t j = 0; j < 16; j++) { + game_name_[j] = game_name[j]; + } +#endif + +#if PAK_DEBUG + if (file_no == 0) { + printf("osPfsFileState_recomp: STORE IN GAME PTR\n"); + printf("file_size: %x\n", state->file_size); + printf("company_code: %x\n", state->company_code); + printf("game_code: %x\n", state->game_code); + + printf("game_name: "); + for (size_t i = 0; i < 16; i++) { + printf("%x", game_name[i]); + } + printf("\n"); + + printf("ext_name: "); + for (size_t i = 0; i < 4; i++) { + printf("%x", ext_name[i]); + } + printf("\n"); + } +#endif ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsFindFile_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); - u8 *game_name = _arg<3, u8 *>(rdram, ctx); - u8 *ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - s32 *file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); + u8* game_name = _arg<3, u8*>(rdram, ctx); + u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + s32* file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); - for (size_t i = 0; i < MAX_FILES; i++) - { + for (size_t i = 0; i < MAX_FILES; i++) { u32 file_size_ = 0; u32 game_code_ = 0; u16 company_code_ = 0; - char ext_name_[4] = {0}; - char game_name_[16] = {0}; + u8 ext_name_[4] = { 0 }; + u8 game_name_[16] = { 0 }; Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); - if ((company_code_ == 0) || (game_code_ == 0)) - { + if ((company_code_ == 0) || (game_code_ == 0)) { continue; - } - else - { - if ((game_code == game_code_) && (company_code == company_code_) && - (strcmp((const char *)game_name, (const char *)game_name_) == 0) && - strcmp((const char *)ext_name, (const char *)ext_name_) == 0) - { - // File found + } else { + if ((game_code == game_code_) && (company_code == company_code_)) { + for (size_t i = 0; i < ARRAY_COUNT(game_name_); i++) { + if (game_name[i] != game_name_[i]) { + break; + } + } + for (size_t i = 0; i < ARRAY_COUNT(ext_name_); i++) { + if (ext_name[i] != ext_name_[i]) { + break; + } + } + // File found *file_no = i; ctx->r2 = 0; // PFS_NO_ERROR return; @@ -349,13 +403,12 @@ extern "C" void osPfsFindFile_recomp(uint8_t *rdram, recomp_context *ctx) ctx->r2 = 5; // PFS_ERR_INVALID } -extern "C" void osPfsReadWriteFile_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { s32 file_no = _arg<1, s32>(rdram, ctx); u8 flag = _arg<2, u8>(rdram, ctx); s32 offset = _arg<3, s32>(rdram, ctx); - s32 size_in_bytes = (s32)MEM_W(0x10, ctx->r29); - u8 *data_buffer = TO_PTR(u8, MEM_W(0x14, ctx->r29)); + s32 size_in_bytes = (s32) MEM_W(0x10, ctx->r29); + u8* data_buffer = TO_PTR(u8, MEM_W(0x14, ctx->r29)); ControllerPak pak; @@ -363,34 +416,28 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t *rdram, recomp_context *ctx) sprintf(filename, "controllerPak_file_%d.sav", file_no); pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - if (!std::filesystem::exists(filename)) - { + if (!std::filesystem::exists(filename)) { ctx->r2 = 5; // PFS_ERR_INVALID return; } - if (!pak.file.good()) - { + if (!pak.file.good()) { ctx->r2 = 5; // PFS_ERR_INVALID return; } - if (!pak.file.is_open()) - { + if (!pak.file.is_open()) { ctx->r2 = 5; // PFS_ERR_INVALID return; } - u8 *swapBuffer = (u8 *)malloc(size_in_bytes); - if (flag == 0) - { + u8* swapBuffer = (u8*) malloc(size_in_bytes); + if (flag == 0) { pak.file.seekg(offset, std::ios::beg); - pak.file.read((char *)swapBuffer, size_in_bytes); + pak.file.read((char*) swapBuffer, size_in_bytes); ByteSwapCopy(data_buffer, swapBuffer, size_in_bytes); - } - else - { + } else { ByteSwapCopy(swapBuffer, data_buffer, size_in_bytes); pak.file.seekp(offset, std::ios::beg); - pak.file.write((char *)swapBuffer, size_in_bytes); + pak.file.write((char*) swapBuffer, size_in_bytes); } free(swapBuffer); @@ -399,29 +446,25 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t *rdram, recomp_context *ctx) ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsChecker_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsChecker_recomp(uint8_t* rdram, recomp_context* ctx) { ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsNumFiles_recomp(uint8_t *rdram, recomp_context *ctx) -{ - s32 *max_files = _arg<1, s32 *>(rdram, ctx); - s32 *files_used = _arg<2, s32 *>(rdram, ctx); +extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { + s32* max_files = _arg<1, s32*>(rdram, ctx); + s32* files_used = _arg<2, s32*>(rdram, ctx); u8 files = 0; - for (size_t i = 0; i < MAX_FILES; i++) - { + for (size_t i = 0; i < MAX_FILES; i++) { u32 file_size = 0; u32 game_code = 0; u16 company_code = 0; - char ext_name[4] = {0}; - char game_name[16] = {0}; + u8 ext_name[4] = { 0 }; + u8 game_name[16] = { 0 }; Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, i); - if ((company_code != 0) || (game_code != 0)) - { + if ((company_code != 0) || (game_code != 0)) { files++; } } @@ -432,61 +475,108 @@ extern "C" void osPfsNumFiles_recomp(uint8_t *rdram, recomp_context *ctx) ctx->r2 = 0; // PFS_NO_ERROR } -extern "C" void osPfsDeleteFile_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); - u8 *game_name = _arg<3, u8 *>(rdram, ctx); - u8 *ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); + u8* game_name = _arg<3, u8*>(rdram, ctx); + u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - if (company_code == 0 || game_code == 0) - { + // WORKAROUND! This shouldn't be necessary, there's something wrong with the OSPfsState struct + game_name += 2; + ext_name += 2; + +#if PAK_DEBUG + printf("osPfsDeleteFile_recomp: REQUEST\n"); + printf(" company_code: %x\n", company_code); + printf(" game_code: %x\n", game_code); + + printf(" ext_name: "); + for (size_t i = 0; i < 4; i++) { + printf("%x", ext_name[i]); + } + printf("\n"); + + printf(" game_name: "); + for (size_t i = 0; i < 16; i++) { + printf("%x", game_name[i]); + } + printf("\n"); +#endif + + if (company_code == 0 || game_code == 0) { ctx->r2 = 5; // PFS_ERR_INVALID return; } ControllerPak pak; - for (int i = 0; i < MAX_FILES; i++) - { + for (int i = 0; i < MAX_FILES; i++) { u32 file_size_ = 0; u32 game_code_ = 0; u16 company_code_ = 0; - char ext_name_[4] = {0}; - char game_name_[16] = {0}; + u8 ext_name_[4] = { 0 }; + u8 game_name_[16] = { 0 }; Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); - if ((company_code_ == 0) || (game_code_ == 0)) - { - continue; +#if PAK_DEBUG + if (i == 0) { + printf("osPfsDeleteFile_recomp: READ\n"); + printf("company_code: %x\n", company_code_); + printf("game_code: %x\n", game_code_); + + printf("ext_name_: "); + for (size_t i = 0; i < 4; i++) { + printf("%x", ext_name_[i]); + } + printf("\n"); + + printf("game_name_: "); + for (size_t i = 0; i < 16; i++) { + printf("%x", game_name_[i]); + } + printf("\n"); } - else - { - if ((game_code == game_code_) && (company_code == company_code_) && - (strcmp((const char *)game_name, (const char *)game_name_) == 0) && - strcmp((const char *)ext_name, (const char *)ext_name_) == 0) - { +#endif + + if ((company_code_ == 0) || (game_code_ == 0)) { + continue; + } else { + if ((game_code == game_code_) && (company_code == company_code_)) { + int gncount = 0; + int encount = 0; + + for (size_t i = 0; i < ARRAY_COUNT(game_name_); i++) { + if (game_name[i] == game_name_[i]) { + gncount++; + } + } + for (size_t i = 0; i < ARRAY_COUNT(ext_name_); i++) { + if (ext_name[i] == ext_name_[i]) { + encount++; + } + } + if ((gncount != 16) || encount != 4) { + continue; + } // File found pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - if (!pak.header.good()) - { + if (!pak.header.good()) { assert(false); } - if (!pak.header.is_open()) - { + if (!pak.header.is_open()) { assert(false); } u32 seek = i * sizeof(OSPfsState); // Zero out the header for this file. - u8 *zero_block = (u8 *)malloc(sizeof(OSPfsState)); + u8* zero_block = (u8*) malloc(sizeof(OSPfsState)); memset(zero_block, 0, sizeof(OSPfsState)); pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char *)zero_block, sizeof(OSPfsState)); + pak.header.write((char*) zero_block, sizeof(OSPfsState)); free(zero_block); pak.header.close(); @@ -506,7 +596,6 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t *rdram, recomp_context *ctx) return; } -extern "C" void osPfsRepairId_recomp(uint8_t *rdram, recomp_context *ctx) -{ +extern "C" void osPfsRepairId_recomp(uint8_t* rdram, recomp_context* ctx) { _return(ctx, 0); // PFS_NO_ERROR } From 212c0cd304862b961f0bb0a9a3ed1ca045ed8358 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Sat, 1 Nov 2025 18:27:15 -0300 Subject: [PATCH 22/49] fix endianess in the OSPfsState struct --- ultramodern/include/ultramodern/ultra64.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultramodern/include/ultramodern/ultra64.h b/ultramodern/include/ultramodern/ultra64.h index acd5235..4c5d432 100644 --- a/ultramodern/include/ultramodern/ultra64.h +++ b/ultramodern/include/ultramodern/ultra64.h @@ -238,8 +238,8 @@ typedef struct { typedef struct { /* 0x00 */ u32 file_size; /* bytes */ /* 0x04 */ u32 game_code; - /* 0x08 */ u16 company_code; /* 0x0A */ char pad_0A[2]; + /* 0x08 */ u16 company_code; /* 0x0C */ char ext_name[4]; /* 0x10 */ char game_name[16]; } OSPfsState; // size = 0x20 From 9818d28894df6d2991fcc639887a1dcbf9b93918 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Thu, 15 Jan 2026 16:05:33 -0800 Subject: [PATCH 23/49] WAR performance issue for now --- librecomp/src/pi.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index 62656ad..4d8b249 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -275,7 +275,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ recomp::do_rom_read(rdram, rdram_address, physical_addr, size); // Send a message to the mq to indicate that the transfer completed - ultramodern::enqueue_external_message(mq, 0, false, true); + ultramodern::enqueue_external_message(mq, 0, false, false); } else if (physical_addr >= recomp::sram_base) { if (!recomp::sram_allowed()) { ultramodern::error_handling::message_box("Attempted to use SRAM saving with other save type"); @@ -285,7 +285,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ save_read(rdram, rdram_address, physical_addr - recomp::sram_base, size); // Send a message to the mq to indicate that the transfer completed - ultramodern::enqueue_external_message(mq, 0, false, true); + ultramodern::enqueue_external_message(mq, 0, false, false); } else { fprintf(stderr, "[WARN] PI DMA read from unknown region, phys address 0x%08X\n", physical_addr); } @@ -302,7 +302,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ save_write(rdram, rdram_address, physical_addr - recomp::sram_base, size); // Send a message to the mq to indicate that the transfer completed - ultramodern::enqueue_external_message(mq, 0, false, true); + ultramodern::enqueue_external_message(mq, 0, false, false); } else { fprintf(stderr, "[WARN] PI DMA write to unknown region, phys address 0x%08X\n", physical_addr); } From 0fb6b85828a0c55fc06d5596be633a4160dc8220 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Thu, 15 Jan 2026 16:07:32 -0800 Subject: [PATCH 24/49] rumblepak state tracking --- ultramodern/include/ultramodern/input.hpp | 2 +- ultramodern/src/input.cpp | 37 ++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/ultramodern/include/ultramodern/input.hpp b/ultramodern/include/ultramodern/input.hpp index 1dab1b8..0e7866f 100644 --- a/ultramodern/include/ultramodern/input.hpp +++ b/ultramodern/include/ultramodern/input.hpp @@ -15,7 +15,7 @@ namespace ultramodern { enum class Pak { None, RumblePak, - // ControllerPak, + ControllerPak, // TransferPak }; diff --git a/ultramodern/src/input.cpp b/ultramodern/src/input.cpp index c48db1b..1d8cf70 100644 --- a/ultramodern/src/input.cpp +++ b/ultramodern/src/input.cpp @@ -4,6 +4,17 @@ #include "ultramodern/ultra64.h" #include "ultramodern/ultramodern.hpp" +#define PFS_ERR_NOPACK 1 // no device inserted +#define PFS_ERR_CONTRFAIL 4 // data transmission failure +#define PFS_ERR_INVALID 5 // invalid parameter or invalid file +#define PFS_ERR_DEVICE 11 // different type of device inserted + +#define PFS_INITIALIZED 1 +#define PFS_CORRUPTED 2 +#define PFS_ID_BROKEN 4 +#define PFS_MOTOR_INITIALIZED 8 +#define PFS_GBPAK_INITIALIZED 16 + static ultramodern::input::callbacks_t input_callbacks {}; void ultramodern::input::set_callbacks(const callbacks_t& callbacks) { @@ -154,8 +165,28 @@ extern "C" void osContGetReadData(OSContPad *data) { s32 osMotorInit(RDRAM_ARG PTR(OSMesgQueue) mq, PTR(OSPfs) pfs_, int channel) { OSPfs *pfs = TO_PTR(OSPfs, pfs_); + // basic initialization performed regardless of connected/disconnected status + pfs->queue = mq; pfs->channel = channel; + pfs->activebank = 0xFF; + pfs->status = 0; + ultramodern::input::connected_device_info_t device_info{}; + if (input_callbacks.get_connected_device_info != nullptr) { + device_info = input_callbacks.get_connected_device_info(channel); + } + + if (device_info.connected_device != ultramodern::input::Device::Controller) { + return PFS_ERR_CONTRFAIL; + } + if (device_info.connected_pak != ultramodern::input::Pak::None) { + return PFS_ERR_NOPACK; + } + if (device_info.connected_pak != ultramodern::input::Pak::RumblePak) { + return PFS_ERR_DEVICE; + } + + pfs->status = PFS_MOTOR_INITIALIZED; return 0; } @@ -170,10 +201,14 @@ s32 osMotorStart(RDRAM_ARG PTR(OSPfs) pfs) { s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs) pfs_, s32 flag) { OSPfs *pfs = TO_PTR(OSPfs, pfs_); + if (!(pfs->status & PFS_MOTOR_INITIALIZED)) { + return PFS_ERR_INVALID; + } + if (input_callbacks.set_rumble != nullptr) { - // TODO: Should we check if the Rumble Pak is connected? Or just rumble regardless of the connected Pak? input_callbacks.set_rumble(pfs->channel, flag); } return 0; } + From f5ba39ec85a8ce5098b9bf60a0b0d20ec443e1b1 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Thu, 15 Jan 2026 16:14:50 -0800 Subject: [PATCH 25/49] stash work --- librecomp/src/pak.cpp | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index ff4bc48..af3b138 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -11,6 +11,12 @@ #define ARRAY_COUNT(arr) (s32)(sizeof(arr) / sizeof(arr[0])) #define MAX_FILES 16 +#if PAK_DEBUG +#define TRACE_ENTRY() fprintf(stderr, "PAK_ENTRY(%s)\n", __func__); +#else +#define TRACE_ENTRY() +#endif + typedef struct ControllerPak { std::fstream header; std::fstream file; @@ -110,12 +116,24 @@ void ByteSwapCopy(uint8_t* dst, uint8_t* src, size_t size_bytes) { } } -extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { - MEM_B(0, ctx->r5) = 0b0001; // *pattern = 1; +extern "C" void osPfsGetLabel_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() ctx->r2 = 0; // PFS_NO_ERROR } +extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() + + PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx); + PTR(u8) bitpattern = _arg<1, PTR(u8)>(rdram, ctx); + + MEM_B(0, bitpattern) = 0b0001; + + _return(ctx, 0); +} + extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() int32_t queue = _arg<0, int32_t>(rdram, ctx); OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); s32 channel = _arg<2, s32>(rdram, ctx); @@ -136,6 +154,7 @@ extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() int32_t queue = _arg<0, int32_t>(rdram, ctx); OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); s32 channel = _arg<2, s32>(rdram, ctx); @@ -186,6 +205,7 @@ extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() s32* bytes_not_used = _arg<1, s32*>(rdram, ctx); s32 usedSpace = 0; @@ -211,6 +231,7 @@ extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); u8* game_name = _arg<3, u8*>(rdram, ctx); @@ -273,6 +294,7 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() s32 file_no = _arg<1, s32>(rdram, ctx); OSPfsState* state = _arg<2, OSPfsState*>(rdram, ctx); @@ -362,6 +384,7 @@ extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); u8* game_name = _arg<3, u8*>(rdram, ctx); @@ -404,6 +427,7 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() s32 file_no = _arg<1, s32>(rdram, ctx); u8 flag = _arg<2, u8>(rdram, ctx); s32 offset = _arg<3, s32>(rdram, ctx); @@ -447,10 +471,12 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsChecker_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() ctx->r2 = 0; // PFS_NO_ERROR } extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() s32* max_files = _arg<1, s32*>(rdram, ctx); s32* files_used = _arg<2, s32*>(rdram, ctx); @@ -476,6 +502,7 @@ extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() u16 company_code = _arg<1, u16>(rdram, ctx); u32 game_code = _arg<2, u32>(rdram, ctx); u8* game_name = _arg<3, u8*>(rdram, ctx); @@ -597,5 +624,7 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osPfsRepairId_recomp(uint8_t* rdram, recomp_context* ctx) { + TRACE_ENTRY() _return(ctx, 0); // PFS_NO_ERROR } + From 89923edcd683914269fb880dac24e0849ca499d1 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Thu, 15 Jan 2026 19:05:41 -0800 Subject: [PATCH 26/49] track state properly --- ultramodern/src/input.cpp | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/ultramodern/src/input.cpp b/ultramodern/src/input.cpp index c48db1b..2cfa0dd 100644 --- a/ultramodern/src/input.cpp +++ b/ultramodern/src/input.cpp @@ -4,6 +4,17 @@ #include "ultramodern/ultra64.h" #include "ultramodern/ultramodern.hpp" +#define PFS_ERR_NOPACK 1 // no device inserted +#define PFS_ERR_CONTRFAIL 4 // data transmission failure +#define PFS_ERR_INVALID 5 // invalid parameter or invalid file +#define PFS_ERR_DEVICE 11 // different type of device inserted + +#define PFS_INITIALIZED 1 +#define PFS_CORRUPTED 2 +#define PFS_ID_BROKEN 4 +#define PFS_MOTOR_INITIALIZED 8 +#define PFS_GBPAK_INITIALIZED 16 + static ultramodern::input::callbacks_t input_callbacks {}; void ultramodern::input::set_callbacks(const callbacks_t& callbacks) { @@ -154,8 +165,28 @@ extern "C" void osContGetReadData(OSContPad *data) { s32 osMotorInit(RDRAM_ARG PTR(OSMesgQueue) mq, PTR(OSPfs) pfs_, int channel) { OSPfs *pfs = TO_PTR(OSPfs, pfs_); + // basic initialization performed regardless of connected/disconnected status + pfs->queue = mq; pfs->channel = channel; + pfs->activebank = 0xFF; + pfs->status = 0; + ultramodern::input::connected_device_info_t device_info{}; + if (input_callbacks.get_connected_device_info != nullptr) { + device_info = input_callbacks.get_connected_device_info(channel); + } + + if (device_info.connected_device != ultramodern::input::Device::Controller) { + return PFS_ERR_CONTRFAIL; + } + if (device_info.connected_pak == ultramodern::input::Pak::None) { + return PFS_ERR_NOPACK; + } + if (device_info.connected_pak != ultramodern::input::Pak::RumblePak) { + return PFS_ERR_DEVICE; + } + + pfs->status = PFS_MOTOR_INITIALIZED; return 0; } @@ -170,10 +201,14 @@ s32 osMotorStart(RDRAM_ARG PTR(OSPfs) pfs) { s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs) pfs_, s32 flag) { OSPfs *pfs = TO_PTR(OSPfs, pfs_); + if (!(pfs->status & PFS_MOTOR_INITIALIZED)) { + return PFS_ERR_INVALID; + } + if (input_callbacks.set_rumble != nullptr) { - // TODO: Should we check if the Rumble Pak is connected? Or just rumble regardless of the connected Pak? input_callbacks.set_rumble(pfs->channel, flag); } return 0; } + From 1c798b13d28ed806c0b39e6eddc2faf399824d6b Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sat, 17 Jan 2026 22:10:40 -0800 Subject: [PATCH 27/49] move file APIs without changes --- .../librecomp => ultramodern/include/ultramodern}/files.hpp | 0 {librecomp => ultramodern}/src/files.cpp | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {librecomp/include/librecomp => ultramodern/include/ultramodern}/files.hpp (100%) rename {librecomp => ultramodern}/src/files.cpp (100%) diff --git a/librecomp/include/librecomp/files.hpp b/ultramodern/include/ultramodern/files.hpp similarity index 100% rename from librecomp/include/librecomp/files.hpp rename to ultramodern/include/ultramodern/files.hpp diff --git a/librecomp/src/files.cpp b/ultramodern/src/files.cpp similarity index 100% rename from librecomp/src/files.cpp rename to ultramodern/src/files.cpp From b42d7bd2e4dab31f4e29dc60700a5d0a6509f5bc Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sat, 17 Jan 2026 22:13:33 -0800 Subject: [PATCH 28/49] move save APIs without changes --- librecomp/include/librecomp/game.hpp | 12 +- librecomp/src/pi.cpp | 182 ---------------------- ultramodern/include/ultramodern/save.hpp | 20 +++ ultramodern/src/save.cpp | 184 +++++++++++++++++++++++ 4 files changed, 205 insertions(+), 193 deletions(-) create mode 100644 ultramodern/include/ultramodern/save.hpp create mode 100644 ultramodern/src/save.cpp diff --git a/librecomp/include/librecomp/game.hpp b/librecomp/include/librecomp/game.hpp index 6df55ee..9e55b72 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -9,21 +9,12 @@ #include namespace recomp { - enum class SaveType { - None, - Eep4k, - Eep16k, - Sram, - Flashram, - AllowAll, // Allows all save types to work and reports eeprom size as 16kbit. - }; - struct GameEntry { uint64_t rom_hash; std::string internal_name; std::u8string game_id; std::string mod_game_id; - SaveType save_type = SaveType::None; + ultramodern::SaveType save_type = ultramodern::SaveType::None; bool is_enabled; // Only needed for mod function hooking support, not needed if `has_compressed_code` is false. std::vector (*decompression_routine)(std::span compressed_rom) = nullptr; @@ -109,7 +100,6 @@ namespace recomp { /// void start(const Configuration& cfg); - SaveType get_save_type(); bool eeprom_allowed(); bool sram_allowed(); bool flashram_allowed(); diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index 43509ba..546aa63 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -84,188 +84,6 @@ void recomp::do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr) MEM_B(3, ram_address) = *rom_addr++; } -struct { - std::vector save_buffer; - std::thread saving_thread; - std::filesystem::path save_file_path; - moodycamel::LightweightSemaphore write_sempahore; - // Used to tell the saving thread that a file swap is pending. - moodycamel::LightweightSemaphore swap_file_pending_sempahore; - // Used to tell the consumer thread that the saving thread is ready for a file swap. - moodycamel::LightweightSemaphore swap_file_ready_sempahore; - std::mutex save_buffer_mutex; -} save_context; - -const std::u8string save_folder = u8"saves"; - -extern std::filesystem::path config_path; - -std::filesystem::path ultramodern::get_save_file_path() { - return save_context.save_file_path; -} - -void set_save_file_path(const std::u8string& subfolder, const std::u8string& name) { - std::filesystem::path save_folder_path = config_path / save_folder; - if (!subfolder.empty()) { - save_folder_path = save_folder_path / subfolder; - } - save_context.save_file_path = save_folder_path / (name + u8".bin"); -} - -void update_save_file() { - bool saving_failed = false; - { - std::ofstream save_file = recomp::open_output_file_with_backup(ultramodern::get_save_file_path(), std::ios_base::binary); - - if (save_file.good()) { - std::lock_guard lock{ save_context.save_buffer_mutex }; - save_file.write(save_context.save_buffer.data(), save_context.save_buffer.size()); - } - else { - saving_failed = true; - } - } - if (!saving_failed) { - saving_failed = !recomp::finalize_output_file_with_backup(ultramodern::get_save_file_path()); - } - if (saving_failed) { - ultramodern::error_handling::message_box("Failed to write to the save file. Check your file permissions and whether the save folder has been moved to Dropbox or similar, as this can cause issues."); - } -} - -extern std::atomic_bool exited; - -void saving_thread_func(RDRAM_ARG1) { - while (!exited) { - bool save_buffer_updated = false; - // Repeatedly wait for a new action to be sent. - constexpr int64_t wait_time_microseconds = 10000; - constexpr int max_actions = 128; - int num_actions = 0; - - // Wait up to the given timeout for a write to come in. Allow multiple writes to coalesce together into a single save. - // Cap the number of coalesced writes to guarantee that the save buffer eventually gets written out to the file even if the game - // is constantly sending writes. - while (save_context.write_sempahore.wait(wait_time_microseconds) && num_actions < max_actions) { - save_buffer_updated = true; - num_actions++; - } - - // If an action came through that affected the save file, save the updated contents. - if (save_buffer_updated) { - update_save_file(); - } - - if (save_context.swap_file_pending_sempahore.tryWait()) { - save_context.swap_file_ready_sempahore.signal(); - } - } -} - -void save_write_ptr(const void* in, uint32_t offset, uint32_t count) { - assert(offset + count <= save_context.save_buffer.size()); - - { - std::lock_guard lock { save_context.save_buffer_mutex }; - memcpy(&save_context.save_buffer[offset], in, count); - } - - save_context.write_sempahore.signal(); -} - -void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { - assert(offset + count <= save_context.save_buffer.size()); - - { - std::lock_guard lock { save_context.save_buffer_mutex }; - for (gpr i = 0; i < count; i++) { - save_context.save_buffer[offset + i] = MEM_B(i, rdram_address); - } - } - - save_context.write_sempahore.signal(); -} - -void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { - assert(offset + count <= save_context.save_buffer.size()); - - std::lock_guard lock { save_context.save_buffer_mutex }; - for (gpr i = 0; i < count; i++) { - MEM_B(i, rdram_address) = save_context.save_buffer[offset + i]; - } -} - -void save_clear(uint32_t start, uint32_t size, char value) { - assert(start + size < save_context.save_buffer.size()); - - { - std::lock_guard lock { save_context.save_buffer_mutex }; - std::fill_n(save_context.save_buffer.begin() + start, size, value); - } - - save_context.write_sempahore.signal(); -} - -size_t get_save_size(recomp::SaveType save_type) { - switch (save_type) { - case recomp::SaveType::AllowAll: - case recomp::SaveType::Flashram: - return 0x20000; - case recomp::SaveType::Sram: - return 0x8000; - case recomp::SaveType::Eep16k: - return 0x800; - case recomp::SaveType::Eep4k: - return 0x200; - case recomp::SaveType::None: - return 0; - } - return 0; -} - -void read_save_file() { - std::filesystem::path save_file_path = ultramodern::get_save_file_path(); - - // Ensure the save file directory exists. - std::filesystem::create_directories(save_file_path.parent_path()); - - // Read the save file if it exists. - std::ifstream save_file = recomp::open_input_file_with_backup(save_file_path, std::ios_base::binary); - if (save_file.good()) { - save_file.read(save_context.save_buffer.data(), save_context.save_buffer.size()); - } - else { - // Otherwise clear the save file to all zeroes. - std::fill(save_context.save_buffer.begin(), save_context.save_buffer.end(), 0); - } -} - -void ultramodern::init_saving(RDRAM_ARG1) { - set_save_file_path(u8"", recomp::current_game_id()); - - save_context.save_buffer.resize(get_save_size(recomp::get_save_type())); - - read_save_file(); - - save_context.saving_thread = std::thread{saving_thread_func, PASS_RDRAM}; -} - -void ultramodern::change_save_file(const std::u8string& subfolder, const std::u8string& name) { - // Tell the saving thread that a file swap is pending. - save_context.swap_file_pending_sempahore.signal(); - // Wait until the saving thread indicates it's ready to swap files. - save_context.swap_file_ready_sempahore.wait(); - // Perform the save file swap. - set_save_file_path(subfolder, name); - read_save_file(); -} - -void ultramodern::join_saving_thread() { - if (save_context.saving_thread.joinable()) { - save_context.saving_thread.join(); - } -} - void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) { // TODO asynchronous transfer // TODO implement unaligned DMA correctly diff --git a/ultramodern/include/ultramodern/save.hpp b/ultramodern/include/ultramodern/save.hpp new file mode 100644 index 0000000..d813443 --- /dev/null +++ b/ultramodern/include/ultramodern/save.hpp @@ -0,0 +1,20 @@ +#ifndef __ULTRAMODERN_SAVE_HPP__ +#define __ULTRAMODERN_SAVE_HPP__ + +#include + +namespace ultramodern { + enum class SaveType { + None, + Eep4k, + Eep16k, + Sram, + Flashram, + AllowAll, // Allows all save types to work and reports eeprom size as 16kbit. + }; + + SaveType get_save_type(); +} + +#endif // __ULTRAMODERN_SAVE_HPP__ + diff --git a/ultramodern/src/save.cpp b/ultramodern/src/save.cpp new file mode 100644 index 0000000..c24c98b --- /dev/null +++ b/ultramodern/src/save.cpp @@ -0,0 +1,184 @@ +#include + +struct { + std::vector save_buffer; + std::thread saving_thread; + std::filesystem::path save_file_path; + moodycamel::LightweightSemaphore write_sempahore; + // Used to tell the saving thread that a file swap is pending. + moodycamel::LightweightSemaphore swap_file_pending_sempahore; + // Used to tell the consumer thread that the saving thread is ready for a file swap. + moodycamel::LightweightSemaphore swap_file_ready_sempahore; + std::mutex save_buffer_mutex; +} save_context; + +const std::u8string save_folder = u8"saves"; + +extern std::filesystem::path config_path; + +std::filesystem::path ultramodern::get_save_file_path() { + return save_context.save_file_path; +} + +void set_save_file_path(const std::u8string& subfolder, const std::u8string& name) { + std::filesystem::path save_folder_path = config_path / save_folder; + if (!subfolder.empty()) { + save_folder_path = save_folder_path / subfolder; + } + save_context.save_file_path = save_folder_path / (name + u8".bin"); +} + +void update_save_file() { + bool saving_failed = false; + { + std::ofstream save_file = recomp::open_output_file_with_backup(ultramodern::get_save_file_path(), std::ios_base::binary); + + if (save_file.good()) { + std::lock_guard lock{ save_context.save_buffer_mutex }; + save_file.write(save_context.save_buffer.data(), save_context.save_buffer.size()); + } + else { + saving_failed = true; + } + } + if (!saving_failed) { + saving_failed = !recomp::finalize_output_file_with_backup(ultramodern::get_save_file_path()); + } + if (saving_failed) { + ultramodern::error_handling::message_box("Failed to write to the save file. Check your file permissions and whether the save folder has been moved to Dropbox or similar, as this can cause issues."); + } +} + +extern std::atomic_bool exited; + +void saving_thread_func(RDRAM_ARG1) { + while (!exited) { + bool save_buffer_updated = false; + // Repeatedly wait for a new action to be sent. + constexpr int64_t wait_time_microseconds = 10000; + constexpr int max_actions = 128; + int num_actions = 0; + + // Wait up to the given timeout for a write to come in. Allow multiple writes to coalesce together into a single save. + // Cap the number of coalesced writes to guarantee that the save buffer eventually gets written out to the file even if the game + // is constantly sending writes. + while (save_context.write_sempahore.wait(wait_time_microseconds) && num_actions < max_actions) { + save_buffer_updated = true; + num_actions++; + } + + // If an action came through that affected the save file, save the updated contents. + if (save_buffer_updated) { + update_save_file(); + } + + if (save_context.swap_file_pending_sempahore.tryWait()) { + save_context.swap_file_ready_sempahore.signal(); + } + } +} + +void save_write_ptr(const void* in, uint32_t offset, uint32_t count) { + assert(offset + count <= save_context.save_buffer.size()); + + { + std::lock_guard lock { save_context.save_buffer_mutex }; + memcpy(&save_context.save_buffer[offset], in, count); + } + + save_context.write_sempahore.signal(); +} + +void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { + assert(offset + count <= save_context.save_buffer.size()); + + { + std::lock_guard lock { save_context.save_buffer_mutex }; + for (gpr i = 0; i < count; i++) { + save_context.save_buffer[offset + i] = MEM_B(i, rdram_address); + } + } + + save_context.write_sempahore.signal(); +} + +void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { + assert(offset + count <= save_context.save_buffer.size()); + + std::lock_guard lock { save_context.save_buffer_mutex }; + for (gpr i = 0; i < count; i++) { + MEM_B(i, rdram_address) = save_context.save_buffer[offset + i]; + } +} + +void save_clear(uint32_t start, uint32_t size, char value) { + assert(start + size < save_context.save_buffer.size()); + + { + std::lock_guard lock { save_context.save_buffer_mutex }; + std::fill_n(save_context.save_buffer.begin() + start, size, value); + } + + save_context.write_sempahore.signal(); +} + +size_t get_save_size(recomp::SaveType save_type) { + switch (save_type) { + case recomp::SaveType::AllowAll: + case recomp::SaveType::Flashram: + return 0x20000; + case recomp::SaveType::Sram: + return 0x8000; + case recomp::SaveType::Eep16k: + return 0x800; + case recomp::SaveType::Eep4k: + return 0x200; + case recomp::SaveType::None: + return 0; + } + return 0; +} + +void read_save_file() { + std::filesystem::path save_file_path = ultramodern::get_save_file_path(); + + // Ensure the save file directory exists. + std::filesystem::create_directories(save_file_path.parent_path()); + + // Read the save file if it exists. + std::ifstream save_file = recomp::open_input_file_with_backup(save_file_path, std::ios_base::binary); + if (save_file.good()) { + save_file.read(save_context.save_buffer.data(), save_context.save_buffer.size()); + } + else { + // Otherwise clear the save file to all zeroes. + std::fill(save_context.save_buffer.begin(), save_context.save_buffer.end(), 0); + } +} + +void ultramodern::init_saving(RDRAM_ARG1) { + set_save_file_path(u8"", recomp::current_game_id()); + + save_context.save_buffer.resize(get_save_size(recomp::get_save_type())); + + read_save_file(); + + save_context.saving_thread = std::thread{saving_thread_func, PASS_RDRAM}; +} + +void ultramodern::change_save_file(const std::u8string& subfolder, const std::u8string& name) { + // Tell the saving thread that a file swap is pending. + save_context.swap_file_pending_sempahore.signal(); + // Wait until the saving thread indicates it's ready to swap files. + save_context.swap_file_ready_sempahore.wait(); + // Perform the save file swap. + set_save_file_path(subfolder, name); + read_save_file(); +} + +void ultramodern::join_saving_thread() { + if (save_context.saving_thread.joinable()) { + save_context.saving_thread.join(); + } +} + From 496639b8c1ea290d845c23353a0ef9c753e47e22 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sat, 17 Jan 2026 22:51:05 -0800 Subject: [PATCH 29/49] update code to reflect relocation --- librecomp/CMakeLists.txt | 1 - librecomp/include/librecomp/game.hpp | 1 + librecomp/src/eep.cpp | 30 +++++----- librecomp/src/flash.cpp | 44 +++++++------- librecomp/src/mod_manifest.cpp | 4 +- librecomp/src/mods.cpp | 12 ++-- librecomp/src/pi.cpp | 6 +- librecomp/src/recomp.cpp | 28 +-------- ultramodern/CMakeLists.txt | 2 + ultramodern/include/ultramodern/files.hpp | 11 ++-- ultramodern/include/ultramodern/save.hpp | 29 +++++++++ ultramodern/src/files.cpp | 10 ++-- ultramodern/src/save.cpp | 71 +++++++++++++++++------ 13 files changed, 142 insertions(+), 107 deletions(-) diff --git a/librecomp/CMakeLists.txt b/librecomp/CMakeLists.txt index fcc3337..60541ad 100644 --- a/librecomp/CMakeLists.txt +++ b/librecomp/CMakeLists.txt @@ -13,7 +13,6 @@ add_library(librecomp STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/eep.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/euc-jp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/extensions.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/files.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/flash.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/heap.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/math_routines.cpp" diff --git a/librecomp/include/librecomp/game.hpp b/librecomp/include/librecomp/game.hpp index 9e55b72..64254a2 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -7,6 +7,7 @@ #include "recomp.h" #include "rsp.hpp" #include +#include namespace recomp { struct GameEntry { diff --git a/librecomp/src/eep.cpp b/librecomp/src/eep.cpp index 048246e..6cf55fb 100644 --- a/librecomp/src/eep.cpp +++ b/librecomp/src/eep.cpp @@ -1,20 +1,18 @@ #include "recomp.h" #include "librecomp/game.hpp" -#include "ultramodern/ultra64.h" - -void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); -void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); +#include +#include constexpr int eeprom_block_size = 8; extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) { - switch (recomp::get_save_type()) { - case recomp::SaveType::AllowAll: - case recomp::SaveType::Eep16k: + switch (ultramodern::get_save_type()) { + case ultramodern::SaveType::AllowAll: + case ultramodern::SaveType::Eep16k: ctx->r2 = 0x02; // EEPROM_TYPE_16K break; - case recomp::SaveType::Eep4k: + case ultramodern::SaveType::Eep4k: ctx->r2 = 0x01; // EEPROM_TYPE_4K break; default: @@ -24,7 +22,7 @@ extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) { - if (!recomp::eeprom_allowed()) { + if (!ultramodern::eeprom_allowed()) { ultramodern::error_handling::message_box("Attempted to use EEPROM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -33,13 +31,13 @@ extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) { gpr buffer = ctx->r6; int32_t nbytes = eeprom_block_size; - save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes); + ultramodern::save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes); ctx->r2 = 0; } extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) { - if (!recomp::eeprom_allowed()) { + if (!ultramodern::eeprom_allowed()) { ultramodern::error_handling::message_box("Attempted to use EEPROM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -50,13 +48,13 @@ extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) { assert((nbytes % eeprom_block_size) == 0); - save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes); + ultramodern::save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes); ctx->r2 = 0; } extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) { - if (!recomp::eeprom_allowed()) { + if (!ultramodern::eeprom_allowed()) { ultramodern::error_handling::message_box("Attempted to use EEPROM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -65,13 +63,13 @@ extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) { gpr buffer = ctx->r6; int32_t nbytes = eeprom_block_size; - save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes); + ultramodern::save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes); ctx->r2 = 0; } extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) { - if (!recomp::eeprom_allowed()) { + if (!ultramodern::eeprom_allowed()) { ultramodern::error_handling::message_box("Attempted to use EEPROM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -82,7 +80,7 @@ extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) { assert((nbytes % eeprom_block_size) == 0); - save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes); + ultramodern::save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes); ctx->r2 = 0; } diff --git a/librecomp/src/flash.cpp b/librecomp/src/flash.cpp index f46261c..e0de47c 100644 --- a/librecomp/src/flash.cpp +++ b/librecomp/src/flash.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include "recomp.h" @@ -15,15 +16,10 @@ constexpr uint32_t page_count = flash_size / page_size; constexpr uint32_t sector_size = page_size * pages_per_sector; constexpr uint32_t sector_count = flash_size / sector_size; -void save_write_ptr(const void* in, uint32_t offset, uint32_t count); -void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); -void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); -void save_clear(uint32_t start, uint32_t size, char value); - std::array write_buffer; extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -32,7 +28,7 @@ extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) { } extern "C" void osFlashReadStatus_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -43,7 +39,7 @@ extern "C" void osFlashReadStatus_recomp(uint8_t * rdram, recomp_context * ctx) } extern "C" void osFlashReadId_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -57,7 +53,7 @@ extern "C" void osFlashReadId_recomp(uint8_t * rdram, recomp_context * ctx) { } extern "C" void osFlashClearStatus_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -66,30 +62,30 @@ extern "C" void osFlashClearStatus_recomp(uint8_t * rdram, recomp_context * ctx) } extern "C" void osFlashAllErase_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } - save_clear(0, ultramodern::save_size, 0xFF); + ultramodern::save_clear(0, ultramodern::save_size, 0xFF); ctx->r2 = 0; } extern "C" void osFlashAllEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } - save_clear(0, ultramodern::save_size, 0xFF); + ultramodern::save_clear(0, ultramodern::save_size, 0xFF); ctx->r2 = 0; } // This function is named sector but really means page. extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -102,14 +98,14 @@ extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) return; } - save_clear(page_num * page_size, page_size, 0xFF); + ultramodern::save_clear(page_num * page_size, page_size, 0xFF); ctx->r2 = 0; } // Same naming issue as above. extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -122,13 +118,13 @@ extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context return; } - save_clear(page_num * page_size, page_size, 0xFF); + ultramodern::save_clear(page_num * page_size, page_size, 0xFF); ctx->r2 = 0; } extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -138,7 +134,7 @@ extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ct } extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -160,7 +156,7 @@ extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) } extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -168,13 +164,13 @@ extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx) uint32_t page_num = ctx->r4; // Copy the write buffer into the save file - save_write_ptr(write_buffer.data(), page_num * page_size, page_size); + ultramodern::save_write_ptr(write_buffer.data(), page_num * page_size, page_size); ctx->r2 = 0; } extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -190,7 +186,7 @@ extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) { uint32_t count = n_pages * page_size; // Read from the save file into the provided buffer - save_read(PASS_RDRAM dramAddr, offset, count); + ultramodern::save_read(PASS_RDRAM dramAddr, offset, count); // Send the message indicating read completion ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); @@ -199,7 +195,7 @@ extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) { } extern "C" void osFlashChange_recomp(uint8_t * rdram, recomp_context * ctx) { - if (!recomp::flashram_allowed()) { + if (!ultramodern::flashram_allowed()) { ultramodern::error_handling::message_box("Attempted to use FlashRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index a75d253..b9b5134 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -3,8 +3,8 @@ #include "json/json.hpp" #include "recompiler/context.h" -#include "librecomp/files.hpp" #include "librecomp/mods.hpp" +#include static bool read_json(std::ifstream input_file, nlohmann::json &json_out) { if (!input_file.good()) { @@ -27,7 +27,7 @@ static bool read_json_with_backups(const std::filesystem::path &path, nlohmann:: } // Try reading and parsing the backup file. - if (read_json(recomp::open_input_backup_file(path), json_out)) { + if (read_json(ultramodern::open_input_backup_file(path), json_out)) { return true; } diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index ff40212..e4ef3c8 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -3,7 +3,7 @@ #include #include -#include "librecomp/files.hpp" +#include #include "librecomp/mods.hpp" #include "librecomp/overlays.hpp" #include "librecomp/game.hpp" @@ -32,7 +32,7 @@ static bool read_json_with_backups(const std::filesystem::path &path, nlohmann:: } // Try reading and parsing the backup file. - if (read_json(recomp::open_input_backup_file(path), json_out)) { + if (read_json(ultramodern::open_input_backup_file(path), json_out)) { return true; } @@ -679,7 +679,7 @@ bool save_mod_config_storage(const std::filesystem::path &path, const std::strin } } - std::ofstream output_file = recomp::open_output_file_with_backup(path); + std::ofstream output_file = ultramodern::open_output_file_with_backup(path); if (!output_file.good()) { return false; } @@ -687,7 +687,7 @@ bool save_mod_config_storage(const std::filesystem::path &path, const std::strin output_file << std::setw(4) << config_json; output_file.close(); - return recomp::finalize_output_file_with_backup(path); + return ultramodern::finalize_output_file_with_backup(path); } bool parse_mods_config(const std::filesystem::path &path, std::unordered_set &enabled_mods, std::vector &mod_order) { @@ -720,7 +720,7 @@ bool save_mods_config(const std::filesystem::path &path, const std::unordered_se config_json["enabled_mods"] = enabled_mods; config_json["mod_order"] = mod_order; - std::ofstream output_file = recomp::open_output_file_with_backup(path); + std::ofstream output_file = ultramodern::open_output_file_with_backup(path); if (!output_file.good()) { return false; } @@ -728,7 +728,7 @@ bool save_mods_config(const std::filesystem::path &path, const std::unordered_se output_file << std::setw(4) << config_json; output_file.close(); - return recomp::finalize_output_file_with_backup(path); + return ultramodern::finalize_output_file_with_backup(path); } void recomp::mods::ModContext::dirty_mod_configuration_thread_process() { diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index 546aa63..378d3d8 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -7,7 +7,7 @@ #include "recomp.h" #include "librecomp/addresses.hpp" #include "librecomp/game.hpp" -#include "librecomp/files.hpp" +#include #include #include @@ -100,7 +100,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ ULTRAMODERN_QUICK_EXIT(); } // read sram - save_read(rdram, rdram_address, physical_addr - recomp::sram_base, size); + ultramodern::save_read(rdram, rdram_address, physical_addr - recomp::sram_base, size); // Send a message to the mq to indicate that the transfer completed ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); @@ -117,7 +117,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ ULTRAMODERN_QUICK_EXIT(); } // write sram - save_write(rdram, rdram_address, physical_addr - recomp::sram_base, size); + ultramodern::save_write(rdram, rdram_address, physical_addr - recomp::sram_base, size); // Send a message to the mq to indicate that the transfer completed ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index ea17675..e48d85e 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -21,6 +21,7 @@ #include "xxHash/xxh3.h" #include "ultramodern/ultramodern.hpp" #include "ultramodern/error_handling.hpp" +#include "ultramodern/save.hpp" #include "librecomp/addresses.hpp" #include "librecomp/mods.hpp" #include "recompiler/live_recompiler.h" @@ -57,8 +58,6 @@ std::unordered_map game_roms {}; std::unique_ptr mod_context = std::make_unique(); // The project's version. recomp::Version project_version; -// The current game's save type. -recomp::SaveType save_type = recomp::SaveType::None; std::u8string recomp::GameEntry::stored_filename() const { return game_id + u8".z64"; @@ -687,7 +686,7 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { recomp::init_heap(rdram, recomp::mod_rdram_start + mod_ram_used); - save_type = game_entry.save_type; + ultramodern::set_save_type(game_entry.save_type); ultramodern::init_saving(rdram); try { @@ -706,29 +705,6 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { } } -recomp::SaveType recomp::get_save_type() { - return save_type; -} - -bool recomp::eeprom_allowed() { - return - save_type == SaveType::Eep4k || - save_type == SaveType::Eep16k || - save_type == SaveType::AllowAll; -} - -bool recomp::sram_allowed() { - return - save_type == SaveType::Sram || - save_type == SaveType::AllowAll; -} - -bool recomp::flashram_allowed() { - return - save_type == SaveType::Flashram || - save_type == SaveType::AllowAll; -} - void recomp::start(const recomp::Configuration& cfg) { project_version = cfg.project_version; recomp::check_all_stored_roms(); diff --git a/ultramodern/CMakeLists.txt b/ultramodern/CMakeLists.txt index 5593172..0c844b4 100644 --- a/ultramodern/CMakeLists.txt +++ b/ultramodern/CMakeLists.txt @@ -10,11 +10,13 @@ add_library(ultramodern STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/error_handling.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/events.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/extensions.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/files.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/input.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/mesgqueue.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/misc_ultra.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/renderer_context.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rsp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/save.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/scheduling.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/task_win32.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/threadqueue.cpp" diff --git a/ultramodern/include/ultramodern/files.hpp b/ultramodern/include/ultramodern/files.hpp index 63e3e9d..497d6d9 100644 --- a/ultramodern/include/ultramodern/files.hpp +++ b/ultramodern/include/ultramodern/files.hpp @@ -1,14 +1,15 @@ -#ifndef __RECOMP_FILES_H__ -#define __RECOMP_FILES_H__ +#ifndef __ULTRAMODERN_FILES_HPP__ +#define __ULTRAMODERN_FILES_HPP__ #include #include -namespace recomp { +namespace ultramodern { std::ifstream open_input_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode = std::ios_base::in); std::ifstream open_input_backup_file(const std::filesystem::path& filepath, std::ios_base::openmode mode = std::ios_base::in); std::ofstream open_output_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode = std::ios_base::out); bool finalize_output_file_with_backup(const std::filesystem::path& filepath); -}; +} + +#endif // __ULTRAMODERN_FILES_HPP__ -#endif diff --git a/ultramodern/include/ultramodern/save.hpp b/ultramodern/include/ultramodern/save.hpp index d813443..c0489f1 100644 --- a/ultramodern/include/ultramodern/save.hpp +++ b/ultramodern/include/ultramodern/save.hpp @@ -1,6 +1,7 @@ #ifndef __ULTRAMODERN_SAVE_HPP__ #define __ULTRAMODERN_SAVE_HPP__ +#include #include namespace ultramodern { @@ -13,7 +14,35 @@ namespace ultramodern { AllowAll, // Allows all save types to work and reports eeprom size as 16kbit. }; + void set_save_type(SaveType type); + + void set_save_file_path(const std::u8string& subfolder, const std::u8string& name); + + void init_saving(RDRAM_ARG1); + + void change_save_file(const std::u8string& subfolder, const std::u8string& name); + + void join_saving_thread(); + + void save_write_ptr(const void* in, uint32_t offset, uint32_t count); + + void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); + + void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); + + void save_clear(uint32_t start, uint32_t size, char value); + SaveType get_save_type(); + + size_t get_save_size(SaveType save_type); + + std::filesystem::path get_save_file_path(); + + bool eeprom_allowed(); + + bool sram_allowed(); + + bool flashram_allowed(); } #endif // __ULTRAMODERN_SAVE_HPP__ diff --git a/ultramodern/src/files.cpp b/ultramodern/src/files.cpp index af6f18d..0dd1a2f 100644 --- a/ultramodern/src/files.cpp +++ b/ultramodern/src/files.cpp @@ -1,15 +1,15 @@ -#include "files.hpp" +#include constexpr std::u8string_view backup_suffix = u8".bak"; constexpr std::u8string_view temp_suffix = u8".temp"; -std::ifstream recomp::open_input_backup_file(const std::filesystem::path& filepath, std::ios_base::openmode mode) { +std::ifstream ultramodern::open_input_backup_file(const std::filesystem::path& filepath, std::ios_base::openmode mode) { std::filesystem::path backup_path{filepath}; backup_path += backup_suffix; return std::ifstream{backup_path, mode}; } -std::ifstream recomp::open_input_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) { +std::ifstream ultramodern::open_input_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) { std::ifstream ret{filepath, mode}; // Check if the file failed to open and open the corresponding backup file instead if so. @@ -20,7 +20,7 @@ std::ifstream recomp::open_input_file_with_backup(const std::filesystem::path& f return ret; } -std::ofstream recomp::open_output_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) { +std::ofstream ultramodern::open_output_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) { std::filesystem::path temp_path{filepath}; temp_path += temp_suffix; std::ofstream temp_file_out{ temp_path, mode }; @@ -28,7 +28,7 @@ std::ofstream recomp::open_output_file_with_backup(const std::filesystem::path& return temp_file_out; } -bool recomp::finalize_output_file_with_backup(const std::filesystem::path& filepath) { +bool ultramodern::finalize_output_file_with_backup(const std::filesystem::path& filepath) { std::filesystem::path backup_path{filepath}; backup_path += backup_suffix; diff --git a/ultramodern/src/save.cpp b/ultramodern/src/save.cpp index c24c98b..951dc8e 100644 --- a/ultramodern/src/save.cpp +++ b/ultramodern/src/save.cpp @@ -1,4 +1,9 @@ +#include +#include +#include +#include #include +#include struct { std::vector save_buffer; @@ -12,15 +17,43 @@ struct { std::mutex save_buffer_mutex; } save_context; +// The current game's save directory within the config path. const std::u8string save_folder = u8"saves"; +// The current game's config directory path. extern std::filesystem::path config_path; +// The current game's save type. +ultramodern::SaveType save_type = ultramodern::SaveType::None; + +ultramodern::SaveType ultramodern::get_save_type() { + return save_type; +} + +bool ultramodern::eeprom_allowed() { + return + save_type == SaveType::Eep4k || + save_type == SaveType::Eep16k || + save_type == SaveType::AllowAll; +} + +bool ultramodern::sram_allowed() { + return + save_type == SaveType::Sram || + save_type == SaveType::AllowAll; +} + +bool ultramodern::flashram_allowed() { + return + save_type == SaveType::Flashram || + save_type == SaveType::AllowAll; +} + std::filesystem::path ultramodern::get_save_file_path() { return save_context.save_file_path; } -void set_save_file_path(const std::u8string& subfolder, const std::u8string& name) { +void ultramodern::set_save_file_path(const std::u8string& subfolder, const std::u8string& name) { std::filesystem::path save_folder_path = config_path / save_folder; if (!subfolder.empty()) { save_folder_path = save_folder_path / subfolder; @@ -31,7 +64,7 @@ void set_save_file_path(const std::u8string& subfolder, const std::u8string& nam void update_save_file() { bool saving_failed = false; { - std::ofstream save_file = recomp::open_output_file_with_backup(ultramodern::get_save_file_path(), std::ios_base::binary); + std::ofstream save_file = ultramodern::open_output_file_with_backup(ultramodern::get_save_file_path(), std::ios_base::binary); if (save_file.good()) { std::lock_guard lock{ save_context.save_buffer_mutex }; @@ -42,7 +75,7 @@ void update_save_file() { } } if (!saving_failed) { - saving_failed = !recomp::finalize_output_file_with_backup(ultramodern::get_save_file_path()); + saving_failed = !ultramodern::finalize_output_file_with_backup(ultramodern::get_save_file_path()); } if (saving_failed) { ultramodern::error_handling::message_box("Failed to write to the save file. Check your file permissions and whether the save folder has been moved to Dropbox or similar, as this can cause issues."); @@ -78,7 +111,7 @@ void saving_thread_func(RDRAM_ARG1) { } } -void save_write_ptr(const void* in, uint32_t offset, uint32_t count) { +void ultramodern::save_write_ptr(const void* in, uint32_t offset, uint32_t count) { assert(offset + count <= save_context.save_buffer.size()); { @@ -89,12 +122,12 @@ void save_write_ptr(const void* in, uint32_t offset, uint32_t count) { save_context.write_sempahore.signal(); } -void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { +void ultramodern::save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { assert(offset + count <= save_context.save_buffer.size()); { std::lock_guard lock { save_context.save_buffer_mutex }; - for (gpr i = 0; i < count; i++) { + for (uint32_t i = 0; i < count; i++) { save_context.save_buffer[offset + i] = MEM_B(i, rdram_address); } } @@ -102,16 +135,16 @@ void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t cou save_context.write_sempahore.signal(); } -void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { +void ultramodern::save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { assert(offset + count <= save_context.save_buffer.size()); std::lock_guard lock { save_context.save_buffer_mutex }; - for (gpr i = 0; i < count; i++) { + for (uint32_t i = 0; i < count; i++) { MEM_B(i, rdram_address) = save_context.save_buffer[offset + i]; } } -void save_clear(uint32_t start, uint32_t size, char value) { +void ultramodern::save_clear(uint32_t start, uint32_t size, char value) { assert(start + size < save_context.save_buffer.size()); { @@ -122,18 +155,18 @@ void save_clear(uint32_t start, uint32_t size, char value) { save_context.write_sempahore.signal(); } -size_t get_save_size(recomp::SaveType save_type) { +size_t ultramodern::get_save_size(ultramodern::SaveType save_type) { switch (save_type) { - case recomp::SaveType::AllowAll: - case recomp::SaveType::Flashram: + case ultramodern::SaveType::AllowAll: + case ultramodern::SaveType::Flashram: return 0x20000; - case recomp::SaveType::Sram: + case ultramodern::SaveType::Sram: return 0x8000; - case recomp::SaveType::Eep16k: + case ultramodern::SaveType::Eep16k: return 0x800; - case recomp::SaveType::Eep4k: + case ultramodern::SaveType::Eep4k: return 0x200; - case recomp::SaveType::None: + case ultramodern::SaveType::None: return 0; } return 0; @@ -146,7 +179,7 @@ void read_save_file() { std::filesystem::create_directories(save_file_path.parent_path()); // Read the save file if it exists. - std::ifstream save_file = recomp::open_input_file_with_backup(save_file_path, std::ios_base::binary); + std::ifstream save_file = ultramodern::open_input_file_with_backup(save_file_path, std::ios_base::binary); if (save_file.good()) { save_file.read(save_context.save_buffer.data(), save_context.save_buffer.size()); } @@ -157,9 +190,9 @@ void read_save_file() { } void ultramodern::init_saving(RDRAM_ARG1) { - set_save_file_path(u8"", recomp::current_game_id()); + set_save_file_path(u8"", ultramodern::current_game_id()); - save_context.save_buffer.resize(get_save_size(recomp::get_save_type())); + save_context.save_buffer.resize(get_save_size(ultramodern::get_save_type())); read_save_file(); From 8eaa83204af6f3c46468947186cd89751881be31 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sat, 17 Jan 2026 23:08:44 -0800 Subject: [PATCH 30/49] pass gameid to init_saving() --- librecomp/include/librecomp/game.hpp | 4 ---- librecomp/src/pi.cpp | 4 ++-- librecomp/src/recomp.cpp | 2 +- ultramodern/include/ultramodern/save.hpp | 2 +- ultramodern/src/save.cpp | 4 ++-- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/librecomp/include/librecomp/game.hpp b/librecomp/include/librecomp/game.hpp index 64254a2..da59743 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -101,10 +101,6 @@ namespace recomp { /// void start(const Configuration& cfg); - bool eeprom_allowed(); - bool sram_allowed(); - bool flashram_allowed(); - void start_game(const std::u8string& game_id); std::u8string current_game_id(); std::string current_mod_game_id(); diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index 378d3d8..d1d9dab 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -95,7 +95,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ // Send a message to the mq to indicate that the transfer completed ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); } else if (physical_addr >= recomp::sram_base) { - if (!recomp::sram_allowed()) { + if (!ultramodern::sram_allowed()) { ultramodern::error_handling::message_box("Attempted to use SRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } @@ -112,7 +112,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ // write cart rom throw std::runtime_error("ROM DMA write unimplemented"); } else if (physical_addr >= recomp::sram_base) { - if (!recomp::sram_allowed()) { + if (!ultramodern::sram_allowed()) { ultramodern::error_handling::message_box("Attempted to use SRAM saving with other save type"); ULTRAMODERN_QUICK_EXIT(); } diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index e48d85e..a166c85 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -687,7 +687,7 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { recomp::init_heap(rdram, recomp::mod_rdram_start + mod_ram_used); ultramodern::set_save_type(game_entry.save_type); - ultramodern::init_saving(rdram); + ultramodern::init_saving(rdram, recomp::current_game_id()); try { game_entry.entrypoint(rdram, context); diff --git a/ultramodern/include/ultramodern/save.hpp b/ultramodern/include/ultramodern/save.hpp index c0489f1..74dc683 100644 --- a/ultramodern/include/ultramodern/save.hpp +++ b/ultramodern/include/ultramodern/save.hpp @@ -18,7 +18,7 @@ namespace ultramodern { void set_save_file_path(const std::u8string& subfolder, const std::u8string& name); - void init_saving(RDRAM_ARG1); + void init_saving(RDRAM_ARG const std::u8string& name); void change_save_file(const std::u8string& subfolder, const std::u8string& name); diff --git a/ultramodern/src/save.cpp b/ultramodern/src/save.cpp index 951dc8e..c1199cb 100644 --- a/ultramodern/src/save.cpp +++ b/ultramodern/src/save.cpp @@ -189,8 +189,8 @@ void read_save_file() { } } -void ultramodern::init_saving(RDRAM_ARG1) { - set_save_file_path(u8"", ultramodern::current_game_id()); +void ultramodern::init_saving(RDRAM_ARG const std::u8string& name) { + set_save_file_path(u8"", name); save_context.save_buffer.resize(get_save_size(ultramodern::get_save_type())); From d990ef0bfcc700f604083e438b0e7e3642ed38cc Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sun, 18 Jan 2026 01:37:18 -0800 Subject: [PATCH 31/49] fix memory copies --- librecomp/src/eep.cpp | 25 ++++++++++++++++++++---- librecomp/src/flash.cpp | 7 ++++++- librecomp/src/pi.cpp | 13 ++++++++++-- ultramodern/include/ultramodern/save.hpp | 4 +--- ultramodern/src/save.cpp | 19 ++---------------- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/librecomp/src/eep.cpp b/librecomp/src/eep.cpp index 6cf55fb..94fedf6 100644 --- a/librecomp/src/eep.cpp +++ b/librecomp/src/eep.cpp @@ -5,6 +5,7 @@ #include constexpr int eeprom_block_size = 8; +static std::vector save_buffer; extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) { switch (ultramodern::get_save_type()) { @@ -31,7 +32,11 @@ extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) { gpr buffer = ctx->r6; int32_t nbytes = eeprom_block_size; - ultramodern::save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes); + save_buffer.resize(nbytes); + for (uint32_t i = 0; i < nbytes; i++) { + save_buffer[i] = MEM_B(i, buffer); + } + ultramodern::save_write_ptr(save_buffer.data(), eep_address * eeprom_block_size, nbytes); ctx->r2 = 0; } @@ -48,7 +53,11 @@ extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) { assert((nbytes % eeprom_block_size) == 0); - ultramodern::save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes); + save_buffer.resize(nbytes); + for (uint32_t i = 0; i < nbytes; i++) { + save_buffer[i] = MEM_B(i, buffer); + } + ultramodern::save_write_ptr(save_buffer.data(), eep_address * eeprom_block_size, nbytes); ctx->r2 = 0; } @@ -63,7 +72,11 @@ extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) { gpr buffer = ctx->r6; int32_t nbytes = eeprom_block_size; - ultramodern::save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes); + save_buffer.resize(nbytes); + ultramodern::save_read_ptr(save_buffer.data(), eep_address * eeprom_block_size, nbytes); + for (uint32_t i = 0; i < nbytes; i++) { + MEM_B(i, buffer) = save_buffer[i]; + } ctx->r2 = 0; } @@ -80,7 +93,11 @@ extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) { assert((nbytes % eeprom_block_size) == 0); - ultramodern::save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes); + save_buffer.resize(nbytes); + ultramodern::save_read_ptr(save_buffer.data(), eep_address * eeprom_block_size, nbytes); + for (uint32_t i = 0; i < nbytes; i++) { + MEM_B(i, buffer) = save_buffer[i]; + } ctx->r2 = 0; } diff --git a/librecomp/src/flash.cpp b/librecomp/src/flash.cpp index e0de47c..9a877aa 100644 --- a/librecomp/src/flash.cpp +++ b/librecomp/src/flash.cpp @@ -17,6 +17,7 @@ constexpr uint32_t sector_size = page_size * pages_per_sector; constexpr uint32_t sector_count = flash_size / sector_size; std::array write_buffer; +std::vector save_buffer; extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) { if (!ultramodern::flashram_allowed()) { @@ -186,7 +187,11 @@ extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) { uint32_t count = n_pages * page_size; // Read from the save file into the provided buffer - ultramodern::save_read(PASS_RDRAM dramAddr, offset, count); + save_buffer.resize(count); + ultramodern::save_read_ptr(save_buffer.data(), offset, count); + for (uint32_t i = 0; i < count; i++) { + MEM_B(i, dramAddr) = save_buffer[i]; + } // Send the message indicating read completion ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index d1d9dab..67cd86d 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -12,6 +12,7 @@ #include static std::vector rom; +static std::vector save_buffer; bool recomp::is_rom_loaded() { return !rom.empty(); @@ -100,7 +101,11 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ ULTRAMODERN_QUICK_EXIT(); } // read sram - ultramodern::save_read(rdram, rdram_address, physical_addr - recomp::sram_base, size); + save_buffer.resize(size); + ultramodern::save_read_ptr(save_buffer.data(), physical_addr - recomp::sram_base, size); + for (uint32_t i = 0; i < size; i++) { + MEM_B(i, rdram_address) = save_buffer[i]; + } // Send a message to the mq to indicate that the transfer completed ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); @@ -117,7 +122,11 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ ULTRAMODERN_QUICK_EXIT(); } // write sram - ultramodern::save_write(rdram, rdram_address, physical_addr - recomp::sram_base, size); + save_buffer.resize(size); + for (uint32_t i = 0; i < size; i++) { + save_buffer[i] = MEM_B(i, rdram_address); + } + ultramodern::save_write_ptr(save_buffer.data(), physical_addr - recomp::sram_base, size); // Send a message to the mq to indicate that the transfer completed ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); diff --git a/ultramodern/include/ultramodern/save.hpp b/ultramodern/include/ultramodern/save.hpp index 74dc683..568b900 100644 --- a/ultramodern/include/ultramodern/save.hpp +++ b/ultramodern/include/ultramodern/save.hpp @@ -26,9 +26,7 @@ namespace ultramodern { void save_write_ptr(const void* in, uint32_t offset, uint32_t count); - void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); - - void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); + void save_read_ptr(void *out, uint32_t offset, uint32_t count); void save_clear(uint32_t start, uint32_t size, char value); diff --git a/ultramodern/src/save.cpp b/ultramodern/src/save.cpp index c1199cb..51a3bd8 100644 --- a/ultramodern/src/save.cpp +++ b/ultramodern/src/save.cpp @@ -122,26 +122,11 @@ void ultramodern::save_write_ptr(const void* in, uint32_t offset, uint32_t count save_context.write_sempahore.signal(); } -void ultramodern::save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { - assert(offset + count <= save_context.save_buffer.size()); - - { - std::lock_guard lock { save_context.save_buffer_mutex }; - for (uint32_t i = 0; i < count; i++) { - save_context.save_buffer[offset + i] = MEM_B(i, rdram_address); - } - } - - save_context.write_sempahore.signal(); -} - -void ultramodern::save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { +void ultramodern::save_read_ptr(void *out, uint32_t offset, uint32_t count) { assert(offset + count <= save_context.save_buffer.size()); std::lock_guard lock { save_context.save_buffer_mutex }; - for (uint32_t i = 0; i < count; i++) { - MEM_B(i, rdram_address) = save_context.save_buffer[offset + i]; - } + std::memcpy(out, &save_context.save_buffer[offset], count); } void ultramodern::save_clear(uint32_t start, uint32_t size, char value) { From af3e3aaea07a0bec1cd2037f0d7f2cd9bb47c34d Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sun, 18 Jan 2026 02:43:56 -0800 Subject: [PATCH 32/49] add missing ultramodern::set_save_type --- librecomp/include/librecomp/game.hpp | 3 ++- ultramodern/src/save.cpp | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/librecomp/include/librecomp/game.hpp b/librecomp/include/librecomp/game.hpp index da59743..1695765 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -10,12 +10,13 @@ #include namespace recomp { + using SaveType = ultramodern::SaveType; struct GameEntry { uint64_t rom_hash; std::string internal_name; std::u8string game_id; std::string mod_game_id; - ultramodern::SaveType save_type = ultramodern::SaveType::None; + SaveType save_type = SaveType::None; bool is_enabled; // Only needed for mod function hooking support, not needed if `has_compressed_code` is false. std::vector (*decompression_routine)(std::span compressed_rom) = nullptr; diff --git a/ultramodern/src/save.cpp b/ultramodern/src/save.cpp index 51a3bd8..5b1b69e 100644 --- a/ultramodern/src/save.cpp +++ b/ultramodern/src/save.cpp @@ -26,6 +26,10 @@ extern std::filesystem::path config_path; // The current game's save type. ultramodern::SaveType save_type = ultramodern::SaveType::None; +void ultramodern::set_save_type(ultramodern::SaveType type) { + save_type = type; +} + ultramodern::SaveType ultramodern::get_save_type() { return save_type; } From 127ac961d2fb293ef07499b3b2e6d6bf590eb64a Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sun, 18 Jan 2026 12:01:53 -0800 Subject: [PATCH 33/49] include to fix windows build --- ultramodern/src/save.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ultramodern/src/save.cpp b/ultramodern/src/save.cpp index 5b1b69e..74f6927 100644 --- a/ultramodern/src/save.cpp +++ b/ultramodern/src/save.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include From b7f1e9cce39f0cdc3c05473f9f36f17b4867d191 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sun, 18 Jan 2026 14:34:20 -0800 Subject: [PATCH 34/49] support args >= 4 via the stack --- librecomp/include/librecomp/helpers.hpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/librecomp/include/librecomp/helpers.hpp b/librecomp/include/librecomp/helpers.hpp index d8f5afd..e51e68c 100644 --- a/librecomp/include/librecomp/helpers.hpp +++ b/librecomp/include/librecomp/helpers.hpp @@ -7,8 +7,7 @@ #include template -T _arg(uint8_t* rdram, recomp_context* ctx) { - static_assert(index < 4, "Only args 0 through 3 supported"); +T _arg(uint8_t* rdram, recomp_context* ctx) requires(index < 4) { gpr raw_arg = (&ctx->r4)[index]; if constexpr (std::is_same_v) { if constexpr (index < 2) { @@ -38,6 +37,25 @@ T _arg(uint8_t* rdram, recomp_context* ctx) { } } +template +T _arg(uint8_t* rdram, recomp_context* ctx) requires(index >= 4) { + const auto raw_arg = MEM_W(0x10 + index * 4, ctx->r29); + if constexpr (std::is_pointer_v) { + static_assert (!std::is_pointer_v>, "Double pointers not supported"); + return TO_PTR(std::remove_pointer_t, raw_arg); + } + else if constexpr (std::is_integral_v) { + static_assert(sizeof(T) <= 4, "64-bit args not supported"); + return static_cast(raw_arg); + } + else { + // static_assert in else workaround + [] () { + static_assert(flag, "Unsupported type"); + }(); + } +} + inline float _arg_float_a1(uint8_t* rdram, recomp_context* ctx) { (void)rdram; union { From 6b5fd295937fb6575bd908b4980d3db32da541c5 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sun, 18 Jan 2026 17:15:08 -0800 Subject: [PATCH 35/49] thunks and declarations --- librecomp/src/pak.cpp | 797 +++++----------------- ultramodern/include/ultramodern/ultra64.h | 29 + 2 files changed, 207 insertions(+), 619 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index af3b138..f8d42c3 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -1,630 +1,189 @@ -#include -#include -#include -#include "ultramodern/ultra64.h" #include "ultramodern/ultramodern.hpp" -#include "recomp.h" #include "helpers.hpp" -#define PAK_DEBUG 0 -#define ARRAY_COUNT(arr) (s32)(sizeof(arr) / sizeof(arr[0])) -#define MAX_FILES 16 - -#if PAK_DEBUG -#define TRACE_ENTRY() fprintf(stderr, "PAK_ENTRY(%s)\n", __func__); -#else -#define TRACE_ENTRY() -#endif - -typedef struct ControllerPak { - std::fstream header; - std::fstream file; -} ControllerPak; - -// extern std::filesystem::path config_path; -// const std::u8string save_folder = u8"saves"; -// std::filesystem::path save_folder_path = config_path / save_folder; - -void Pfs_PakHeader_Write(u32* file_size, u32* game_code, u16* company_code, u8* ext_name, u8* game_name, u8 fileIndex) { - ControllerPak pak; - - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - - /* Set file parameters to header */ - u32 seek = fileIndex * 0x20; - - // file_size - pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char*) file_size, 4); - // game_code - pak.header.seekp(seek + 0x4, std::ios::beg); - pak.header.write((char*) game_code, 4); - // company_code - pak.header.seekp(seek + 0x08, std::ios::beg); - pak.header.write((char*) company_code, 2); - // ext_name - pak.header.seekp(seek + 0xC, std::ios::beg); - pak.header.write((char*) ext_name, 4); - // game_name - pak.header.seekp(seek + 0x10, std::ios::beg); - pak.header.write((char*) game_name, 16); - - pak.header.close(); -} - -void Pfs_PakHeader_Read(u32* file_size, u32* game_code, u16* company_code, u8* ext_name, u8* game_name, u8 fileIndex) { - ControllerPak pak; - - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - - /* Set file parameters to header */ - u32 seek = fileIndex * sizeof(OSPfsState); - - // file_size - pak.header.seekg(seek + 0x0, std::ios::beg); - pak.header.read((char*) file_size, 4); - // game_code - pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char*) game_code, 4); - // company_code - pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char*) company_code, 2); - // ext_name - pak.header.seekg(seek + 0xC, std::ios::beg); - pak.header.read((char*) ext_name, 4); - // game_name - pak.header.seekg(seek + 0x10, std::ios::beg); - pak.header.read((char*) game_name, 16); - - pak.header.close(); -} - -void Pfs_ByteSwapFile(u8* buffer, size_t size) { - uint8_t c0, c1, c2, c3; - - for (size_t i = 0; i < size; i += 4) { - c0 = buffer[i + 0]; - c1 = buffer[i + 1]; - c2 = buffer[i + 2]; - c3 = buffer[i + 3]; - - buffer[i + 3] = c0; - buffer[i + 2] = c1; - buffer[i + 1] = c2; - buffer[i + 0] = c3; - } -} - -void ByteSwapCopy(uint8_t* dst, uint8_t* src, size_t size_bytes) { - for (size_t i = 0; i < size_bytes; i++) { - dst[i] = src[i ^ 3]; - } -} - -extern "C" void osPfsGetLabel_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - ctx->r2 = 0; // PFS_NO_ERROR -} - -extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - - PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx); - PTR(u8) bitpattern = _arg<1, PTR(u8)>(rdram, ctx); - - MEM_B(0, bitpattern) = 0b0001; - - _return(ctx, 0); -} - -extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - int32_t queue = _arg<0, int32_t>(rdram, ctx); - OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); - s32 channel = _arg<2, s32>(rdram, ctx); - - pfs->queue = queue; - pfs->channel = channel; - pfs->status = 0x1; - - ControllerPak pak; - - // If a header file doesn't exist, create it. - if (!std::filesystem::exists("controllerPak_header.sav")) { - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - pak.header.close(); - } - - ctx->r2 = 0; // PFS_NO_ERROR -} - extern "C" void osPfsInitPak_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - int32_t queue = _arg<0, int32_t>(rdram, ctx); - OSPfs* pfs = _arg<1, OSPfs*>(rdram, ctx); - s32 channel = _arg<2, s32>(rdram, ctx); + PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx); + PTR(OSPfs) pfs = _arg<1, PTR(OSPfs)>(rdram, ctx); + int channel = _arg<2, int>(rdram, ctx); - pfs->queue = queue; - pfs->channel = channel; - pfs->status = 0x1; - - ControllerPak pak; - - // If a header file doesn't exist, create it. - if (!std::filesystem::exists("controllerPak_header.sav")) { - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - pak.header.close(); - } - -// Test -#if PAK_DEBUG - u32 file_size_ = 0x01020304; - u32 game_code_ = 0x05060708; - u16 company_code_ = 0x0910; - u8 ext_name_[4] = { 0x11, 0x12, 0x13, 0x14 }; - u8 game_name_[16] = { 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24 }; - - Pfs_PakHeader_Write(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, 0); - - Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, 0); - - printf("TEST\n"); - printf("file_size_: %x\n", file_size_); - printf("game_code: %x\n", game_code_); - printf("company_code: %x\n", company_code_); - - printf("ext_name: "); - for (size_t i = 0; i < 4; i++) { - printf("%x", ext_name_[i]); - } - printf("\n"); - - printf("game_name: "); - for (size_t i = 0; i < 16; i++) { - printf("%x", game_name_[i]); - } - printf("\n"); -#endif - - ctx->r2 = 0; // PFS_NO_ERROR -} - -extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - s32* bytes_not_used = _arg<1, s32*>(rdram, ctx); - - s32 usedSpace = 0; - for (size_t i = 0; i < MAX_FILES; i++) { - u32 file_size = 0; - u32 game_code = 0; - u16 company_code = 0; - u8 ext_name[4] = { 0 }; - u8 game_name[16] = { 0 }; - - Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, i); - - if ((company_code == 0) || (game_code == 0)) { - continue; - } else { - usedSpace += file_size >> 8; - } - } - - *bytes_not_used = (123 - usedSpace) << 8; - - ctx->r2 = 0; // PFS_NO_ERROR -} - -extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - u16 company_code = _arg<1, u16>(rdram, ctx); - u32 game_code = _arg<2, u32>(rdram, ctx); - u8* game_name = _arg<3, u8*>(rdram, ctx); - u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - u32 file_size = (s32) MEM_W(0x14, ctx->r29); - s32* file_no = TO_PTR(s32, MEM_W(0x18, ctx->r29)); - - if ((company_code == 0) || (game_code == 0)) { - ctx->r2 = 5; // PFS_ERR_INVALID - return; - } - - /* Search for a free slot */ - u8 freeFileIndex = 0; - for (size_t i = 0; i < MAX_FILES; i++) { - u32 file_size_ = 0; - u32 game_code_ = 0; - u16 company_code_ = 0; - u8 ext_name_[4] = { 0 }; - u8 game_name_[16] = { 0 }; - - Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); - - if ((company_code_ == 0) || (game_code_ == 0)) { - freeFileIndex = i; - break; - } - } - - if (freeFileIndex == MAX_FILES) { - ctx->r2 = 8; // PFS_DIR_FULL - return; - } - - Pfs_PakHeader_Write(&file_size, &game_code, &company_code, ext_name, game_name, freeFileIndex); - - /* Create empty file */ - - ControllerPak pak; - - char filename[100]; - sprintf(filename, "controllerPak_file_%d.sav", freeFileIndex); - pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - - file_size = (file_size + 31) & ~31; - - u8* zero_block = (u8*) malloc(file_size); - memset(zero_block, 0, file_size); - - pak.file.seekp(0, std::ios::beg); - pak.file.write((char*) zero_block, file_size); - - free(zero_block); - - pak.file.close(); - - *file_no = freeFileIndex; - - ctx->r2 = 0; // PFS_NO_ERROR -} - -extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - s32 file_no = _arg<1, s32>(rdram, ctx); - OSPfsState* state = _arg<2, OSPfsState*>(rdram, ctx); - - u32 file_size = 0; - u32 game_code = 0; - u16 company_code = 0; - u8 ext_name[4] = { 0 }; - u8 game_name[16] = { 0 }; - - // should pass the state of the requested file_no to the incoming state pointer, - // games call this function 16 times, once per file - // fills the incoming state with the information inside the header of the pak. - - char filename[100]; - sprintf(filename, "controllerPak_file_%d.sav", file_no); - if (!std::filesystem::exists(filename)) { - ctx->r2 = 5; - return; - } - - /* Read game info from pak */ - Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, file_no); - - // Test code -#if 0 - for (size_t i = 0; i < 5; i++) { - ext_name[i] = i + 1; - } - - for (size_t i = 0; i < 16; i++) { - game_name[i] = i + 1; - } -#endif - -#if 1 - state->file_size = file_size; - state->company_code = company_code; - state->game_code = game_code; - - for (size_t j = 0; j < ARRAY_COUNT(game_name); j++) { - state->game_name[j] = game_name[j]; - } - for (size_t j = 0; j < ARRAY_COUNT(ext_name); j++) { - state->ext_name[j] = ext_name[j]; - } -#else - u8* ptr = (u8*) state; - *(u32*) &ptr[0] = file_size; - *(u32*) &ptr[0x4] = game_code; - *(u16*) &ptr[0xA] = company_code; - - // printf("*(u16*) &ptr[0x8] = company_code; : %x\n", ptr[0xA]); - - char* ext_name_ = (char*) &ptr[0xC]; - char* game_name_ = (char*) &ptr[0x10]; - - for (size_t j = 0; j < 4; j++) { - ext_name_[j] = ext_name[j]; - } - for (size_t j = 0; j < 16; j++) { - game_name_[j] = game_name[j]; - } -#endif - -#if PAK_DEBUG - if (file_no == 0) { - printf("osPfsFileState_recomp: STORE IN GAME PTR\n"); - printf("file_size: %x\n", state->file_size); - printf("company_code: %x\n", state->company_code); - printf("game_code: %x\n", state->game_code); - - printf("game_name: "); - for (size_t i = 0; i < 16; i++) { - printf("%x", game_name[i]); - } - printf("\n"); - - printf("ext_name: "); - for (size_t i = 0; i < 4; i++) { - printf("%x", ext_name[i]); - } - printf("\n"); - } -#endif - - ctx->r2 = 0; // PFS_NO_ERROR -} - -extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - u16 company_code = _arg<1, u16>(rdram, ctx); - u32 game_code = _arg<2, u32>(rdram, ctx); - u8* game_name = _arg<3, u8*>(rdram, ctx); - u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - s32* file_no = TO_PTR(s32, MEM_W(0x14, ctx->r29)); - - for (size_t i = 0; i < MAX_FILES; i++) { - u32 file_size_ = 0; - u32 game_code_ = 0; - u16 company_code_ = 0; - u8 ext_name_[4] = { 0 }; - u8 game_name_[16] = { 0 }; - - Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); - - if ((company_code_ == 0) || (game_code_ == 0)) { - continue; - } else { - if ((game_code == game_code_) && (company_code == company_code_)) { - for (size_t i = 0; i < ARRAY_COUNT(game_name_); i++) { - if (game_name[i] != game_name_[i]) { - break; - } - } - for (size_t i = 0; i < ARRAY_COUNT(ext_name_); i++) { - if (ext_name[i] != ext_name_[i]) { - break; - } - } - // File found - *file_no = i; - ctx->r2 = 0; // PFS_NO_ERROR - return; - } - } - } - - // File not found - ctx->r2 = 5; // PFS_ERR_INVALID -} - -extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - s32 file_no = _arg<1, s32>(rdram, ctx); - u8 flag = _arg<2, u8>(rdram, ctx); - s32 offset = _arg<3, s32>(rdram, ctx); - s32 size_in_bytes = (s32) MEM_W(0x10, ctx->r29); - u8* data_buffer = TO_PTR(u8, MEM_W(0x14, ctx->r29)); - - ControllerPak pak; - - char filename[100]; - sprintf(filename, "controllerPak_file_%d.sav", file_no); - pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - - if (!std::filesystem::exists(filename)) { - ctx->r2 = 5; // PFS_ERR_INVALID - return; - } - if (!pak.file.good()) { - ctx->r2 = 5; // PFS_ERR_INVALID - return; - } - if (!pak.file.is_open()) { - ctx->r2 = 5; // PFS_ERR_INVALID - return; - } - - u8* swapBuffer = (u8*) malloc(size_in_bytes); - if (flag == 0) { - pak.file.seekg(offset, std::ios::beg); - pak.file.read((char*) swapBuffer, size_in_bytes); - ByteSwapCopy(data_buffer, swapBuffer, size_in_bytes); - } else { - ByteSwapCopy(swapBuffer, data_buffer, size_in_bytes); - pak.file.seekp(offset, std::ios::beg); - pak.file.write((char*) swapBuffer, size_in_bytes); - } - free(swapBuffer); - - pak.file.close(); - - ctx->r2 = 0; // PFS_NO_ERROR -} - -extern "C" void osPfsChecker_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - ctx->r2 = 0; // PFS_NO_ERROR -} - -extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - s32* max_files = _arg<1, s32*>(rdram, ctx); - s32* files_used = _arg<2, s32*>(rdram, ctx); - - u8 files = 0; - for (size_t i = 0; i < MAX_FILES; i++) { - u32 file_size = 0; - u32 game_code = 0; - u16 company_code = 0; - u8 ext_name[4] = { 0 }; - u8 game_name[16] = { 0 }; - - Pfs_PakHeader_Read(&file_size, &game_code, &company_code, ext_name, game_name, i); - - if ((company_code != 0) || (game_code != 0)) { - files++; - } - } - - *files_used = files; - *max_files = MAX_FILES; - - ctx->r2 = 0; // PFS_NO_ERROR -} - -extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - u16 company_code = _arg<1, u16>(rdram, ctx); - u32 game_code = _arg<2, u32>(rdram, ctx); - u8* game_name = _arg<3, u8*>(rdram, ctx); - u8* ext_name = TO_PTR(u8, MEM_W(0x10, ctx->r29)); - - // WORKAROUND! This shouldn't be necessary, there's something wrong with the OSPfsState struct - game_name += 2; - ext_name += 2; - -#if PAK_DEBUG - printf("osPfsDeleteFile_recomp: REQUEST\n"); - printf(" company_code: %x\n", company_code); - printf(" game_code: %x\n", game_code); - - printf(" ext_name: "); - for (size_t i = 0; i < 4; i++) { - printf("%x", ext_name[i]); - } - printf("\n"); - - printf(" game_name: "); - for (size_t i = 0; i < 16; i++) { - printf("%x", game_name[i]); - } - printf("\n"); -#endif - - if (company_code == 0 || game_code == 0) { - ctx->r2 = 5; // PFS_ERR_INVALID - return; - } - - ControllerPak pak; - - for (int i = 0; i < MAX_FILES; i++) { - u32 file_size_ = 0; - u32 game_code_ = 0; - u16 company_code_ = 0; - u8 ext_name_[4] = { 0 }; - u8 game_name_[16] = { 0 }; - - Pfs_PakHeader_Read(&file_size_, &game_code_, &company_code_, ext_name_, game_name_, i); - -#if PAK_DEBUG - if (i == 0) { - printf("osPfsDeleteFile_recomp: READ\n"); - printf("company_code: %x\n", company_code_); - printf("game_code: %x\n", game_code_); - - printf("ext_name_: "); - for (size_t i = 0; i < 4; i++) { - printf("%x", ext_name_[i]); - } - printf("\n"); - - printf("game_name_: "); - for (size_t i = 0; i < 16; i++) { - printf("%x", game_name_[i]); - } - printf("\n"); - } -#endif - - if ((company_code_ == 0) || (game_code_ == 0)) { - continue; - } else { - if ((game_code == game_code_) && (company_code == company_code_)) { - int gncount = 0; - int encount = 0; - - for (size_t i = 0; i < ARRAY_COUNT(game_name_); i++) { - if (game_name[i] == game_name_[i]) { - gncount++; - } - } - for (size_t i = 0; i < ARRAY_COUNT(ext_name_); i++) { - if (ext_name[i] == ext_name_[i]) { - encount++; - } - } - if ((gncount != 16) || encount != 4) { - continue; - } - // File found - - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - - u32 seek = i * sizeof(OSPfsState); - - // Zero out the header for this file. - u8* zero_block = (u8*) malloc(sizeof(OSPfsState)); - memset(zero_block, 0, sizeof(OSPfsState)); - pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char*) zero_block, sizeof(OSPfsState)); - free(zero_block); - - pak.header.close(); - - char filename[100]; - sprintf(filename, "controllerPak_file_%d.sav", i); - remove(filename); - - ctx->r2 = 0; // PFS_NO_ERROR - return; - } - } - } - - // File not found - ctx->r2 = 5; // PFS_ERR_INVALID - return; + s32 ret = osPfsInitPak(PASS_RDRAM mq, pfs, channel); + _return(ctx, ret); } extern "C" void osPfsRepairId_recomp(uint8_t* rdram, recomp_context* ctx) { - TRACE_ENTRY() - _return(ctx, 0); // PFS_NO_ERROR + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + + s32 ret = osPfsRepairId(PASS_RDRAM pfs); + _return(ctx, ret); +} + +extern "C" void osPfsInit_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx); + PTR(OSPfs) pfs = _arg<1, PTR(OSPfs)>(rdram, ctx); + int channel = _arg<2, int>(rdram, ctx); + + s32 ret = osPfsInit(PASS_RDRAM mq, pfs, channel); + _return(ctx, ret); +} + +extern "C" void osPfsReFormat_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + PTR(OSMesgQueue) mq = _arg<1, PTR(OSMesgQueue)>(rdram, ctx); + int channel = _arg<2, int>(rdram, ctx); + + s32 ret = osPfsReFormat(PASS_RDRAM pfs, mq, channel); + _return(ctx, ret); +} + +extern "C" void osPfsChecker_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + + s32 ret = osPfsChecker(PASS_RDRAM pfs); + _return(ctx, ret); +} + +extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + u16 company_code = _arg<1, u16>(rdram, ctx); + u32 game_code = _arg<2, u32>(rdram, ctx); + PTR(u8) game_name = _arg<3, PTR(u8)>(rdram, ctx); + PTR(u8) ext_name = _arg<4, PTR(u8)>(rdram, ctx); + int file_size = _arg<5, int>(rdram, ctx); + PTR(s32) file_no = _arg<6, PTR(s32)>(rdram, ctx); + u8 game_name_proxy[PFS_FILE_NAME_LEN]; + u8 ext_name_proxy[PFS_FILE_EXT_LEN]; + + for (uint32_t i = 0; i < PFS_FILE_NAME_LEN; i++) { + game_name_proxy[i] = MEM_B(i, game_name); + } + for (uint32_t i = 0; i < PFS_FILE_EXT_LEN; i++) { + ext_name_proxy[i] = MEM_B(i, ext_name); + } + s32 ret = osPfsAllocateFile(PASS_RDRAM pfs, company_code, game_code, game_name_proxy, ext_name_proxy, file_size, file_no); + _return(ctx, ret); +} + +extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + u16 company_code = _arg<1, u16>(rdram, ctx); + u32 game_code = _arg<2, u32>(rdram, ctx); + PTR(u8) game_name = _arg<3, PTR(u8)>(rdram, ctx); + PTR(u8) ext_name = _arg<4, PTR(u8)>(rdram, ctx); + PTR(s32) file_no = _arg<5, PTR(s32)>(rdram, ctx); + u8 game_name_proxy[PFS_FILE_NAME_LEN]; + u8 ext_name_proxy[PFS_FILE_EXT_LEN]; + + for (uint32_t i = 0; i < PFS_FILE_NAME_LEN; i++) { + game_name_proxy[i] = MEM_B(i, game_name); + } + for (uint32_t i = 0; i < PFS_FILE_EXT_LEN; i++) { + ext_name_proxy[i] = MEM_B(i, ext_name); + } + s32 ret = osPfsFindFile(PASS_RDRAM pfs, company_code, game_code, game_name_proxy, ext_name_proxy, file_no); + _return(ctx, ret); +} + +extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + u16 company_code = _arg<1, u16>(rdram, ctx); + u32 game_code = _arg<2, u32>(rdram, ctx); + PTR(u8) game_name = _arg<3, PTR(u8)>(rdram, ctx); + PTR(u8) ext_name = _arg<4, PTR(u8)>(rdram, ctx); + u8 game_name_proxy[PFS_FILE_NAME_LEN]; + u8 ext_name_proxy[PFS_FILE_EXT_LEN]; + + for (uint32_t i = 0; i < PFS_FILE_NAME_LEN; i++) { + game_name_proxy[i] = MEM_B(i, game_name); + } + for (uint32_t i = 0; i < PFS_FILE_EXT_LEN; i++) { + ext_name_proxy[i] = MEM_B(i, ext_name); + } + s32 ret = osPfsDeleteFile(PASS_RDRAM pfs, company_code, game_code, game_name_proxy, ext_name_proxy); + _return(ctx, ret); +} + +extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + s32 file_no = _arg<1, s32>(rdram, ctx); + u8 flag = _arg<2, u8>(rdram, ctx); + int offset = _arg<3, int>(rdram, ctx); + int nbytes = _arg<4, int>(rdram, ctx); + PTR(u8) data_buffer = _arg<5, PTR(u8)>(rdram, ctx); + std::vector data_buffer_proxy(nbytes); + + if (flag == PFS_WRITE) { + for (uint32_t i = 0; i < nbytes; i++) { + data_buffer_proxy[i] = MEM_B(i, data_buffer); + } + } + s32 ret = osPfsReadWriteFile(PASS_RDRAM pfs, file_no, flag, offset, nbytes, data_buffer_proxy.data()); + if (flag == PFS_READ) { + for (uint32_t i = 0; i < nbytes; i++) { + MEM_B(i, data_buffer) = data_buffer_proxy[i]; + } + } + _return(ctx, ret); +} + +extern "C" void osPfsFileState_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + s32 file_no = _arg<1, s32>(rdram, ctx); + PTR(OSPfsState) state = _arg<2, PTR(OSPfsState)>(rdram, ctx); + + s32 ret = osPfsFileState(PASS_RDRAM pfs, file_no, state); + _return(ctx, ret); +} + +extern "C" void osPfsGetLabel_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + PTR(u8) label = _arg<1, PTR(u8)>(rdram, ctx); + PTR(int) len = _arg<2, PTR(int)>(rdram, ctx); + u8 label_proxy[32]; + + s32 ret = osPfsGetLabel(PASS_RDRAM pfs, label_proxy, len); + for (uint32_t i = 0; i < 32; i++) { + MEM_B(i, label) = label_proxy[i]; + } + _return(ctx, ret); +} + +extern "C" void osPfsSetLabel_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + PTR(u8) label = _arg<1, PTR(u8)>(rdram, ctx); + u8 label_proxy[32]; + + for (uint32_t i = 0; i < 32; i++) { + label_proxy[i] = MEM_B(i, label); + } + s32 ret = osPfsSetLabel(PASS_RDRAM pfs, label_proxy); + _return(ctx, ret); +} + +extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx); + PTR(u8) pattern = _arg<1, PTR(u8)>(rdram, ctx); + u8 pattern_proxy = 0; + + s32 ret = osPfsIsPlug(PASS_RDRAM mq, &pattern_proxy); + MEM_B(0, pattern) = pattern_proxy; + _return(ctx, ret); +} + +extern "C" void osPfsFreeBlocks_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + PTR(s32) bytes_not_used = _arg<1, PTR(s32)>(rdram, ctx); + + s32 ret = osPfsFreeBlocks(PASS_RDRAM pfs, bytes_not_used); + _return(ctx, ret); +} + +extern "C" void osPfsNumFiles_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx); + PTR(s32) max_files = _arg<1, PTR(s32)>(rdram, ctx); + PTR(s32) files_used = _arg<2, PTR(s32)>(rdram, ctx); + + s32 ret = osPfsNumFiles(PASS_RDRAM pfs, max_files, files_used); + _return(ctx, ret); } diff --git a/ultramodern/include/ultramodern/ultra64.h b/ultramodern/include/ultramodern/ultra64.h index 4c5d432..8c743e7 100644 --- a/ultramodern/include/ultramodern/ultra64.h +++ b/ultramodern/include/ultramodern/ultra64.h @@ -74,6 +74,17 @@ typedef u64 OSTime; #define OS_EVENT_THREADSTATUS 13 /* CPU thread status: used by rmon */ #define OS_EVENT_PRENMI 14 /* Pre NMI interrupt */ +#define PFS_INODE_SIZE_PER_PAGE 128 +#define PFS_FILE_NAME_LEN 16 +#define PFS_FILE_EXT_LEN 4 +#define PFS_BLOCKSIZE 32 /* bytes */ +#define PFS_ONE_PAGE 8 /* blocks */ +#define PFS_MAX_BANKS 62 + +#define PFS_READ 0 +#define PFS_WRITE 1 +#define PFS_CREATE 2 + #define M_GFXTASK 1 #define M_AUDTASK 2 #define M_VIDTASK 3 @@ -322,6 +333,24 @@ s32 osMotorStop(RDRAM_ARG PTR(OSPfs)); s32 osMotorStart(RDRAM_ARG PTR(OSPfs)); s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs), s32); +/* Controller PAK interface */ + +s32 osPfsInitPak(RDRAM_ARG PTR(OSMesgQueue) queue, PTR(OSPfs) pfs, int channel); +s32 osPfsRepairId(RDRAM_ARG PTR(OSPfs) pfs); +s32 osPfsInit(RDRAM_ARG PTR(OSMesgQueue) queue, PTR(OSPfs) pfs, int channel); +s32 osPfsReFormat(RDRAM_ARG PTR(OSPfs) pfs, PTR(OSMesgQueue) queue, int channel); +s32 osPfsChecker(RDRAM_ARG PTR(OSPfs) pfs); +s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, int file_size_in_bytes, PTR(s32) file_no); +s32 osPfsFindFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, PTR(s32) file_no); +s32 osPfsDeleteFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name); +s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs, s32 file_no, u8 flag, int offset, int size_in_bytes, u8* data_buffer); +s32 osPfsFileState(RDRAM_ARG PTR(OSPfs) pfs, s32 file_no, PTR(OSPfsState) state); +s32 osPfsGetLabel(RDRAM_ARG PTR(OSPfs) pfs, u8* label, PTR(int) len); +s32 osPfsSetLabel(RDRAM_ARG PTR(OSPfs) pfs, u8* label); +s32 osPfsIsPlug(RDRAM_ARG PTR(OSMesgQueue) mq, u8* pattern); +s32 osPfsFreeBlocks(RDRAM_ARG PTR(OSPfs) pfs, PTR(s32) bytes_not_used); +s32 osPfsNumFiles(RDRAM_ARG PTR(OSPfs) pfs, PTR(s32) max_files, PTR(s32) files_used); + #ifdef __cplusplus } // extern "C" #endif From b05625b2788f8b36adaf1975360041670b4195a5 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sun, 18 Jan 2026 17:20:58 -0800 Subject: [PATCH 36/49] rename to nbytes for consistency --- librecomp/src/pak.cpp | 4 ++-- ultramodern/include/ultramodern/ultra64.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index f8d42c3..eac5192 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -49,7 +49,7 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { u32 game_code = _arg<2, u32>(rdram, ctx); PTR(u8) game_name = _arg<3, PTR(u8)>(rdram, ctx); PTR(u8) ext_name = _arg<4, PTR(u8)>(rdram, ctx); - int file_size = _arg<5, int>(rdram, ctx); + int nbytes = _arg<5, int>(rdram, ctx); PTR(s32) file_no = _arg<6, PTR(s32)>(rdram, ctx); u8 game_name_proxy[PFS_FILE_NAME_LEN]; u8 ext_name_proxy[PFS_FILE_EXT_LEN]; @@ -60,7 +60,7 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { for (uint32_t i = 0; i < PFS_FILE_EXT_LEN; i++) { ext_name_proxy[i] = MEM_B(i, ext_name); } - s32 ret = osPfsAllocateFile(PASS_RDRAM pfs, company_code, game_code, game_name_proxy, ext_name_proxy, file_size, file_no); + s32 ret = osPfsAllocateFile(PASS_RDRAM pfs, company_code, game_code, game_name_proxy, ext_name_proxy, nbytes, file_no); _return(ctx, ret); } diff --git a/ultramodern/include/ultramodern/ultra64.h b/ultramodern/include/ultramodern/ultra64.h index 8c743e7..7cb288d 100644 --- a/ultramodern/include/ultramodern/ultra64.h +++ b/ultramodern/include/ultramodern/ultra64.h @@ -340,10 +340,10 @@ s32 osPfsRepairId(RDRAM_ARG PTR(OSPfs) pfs); s32 osPfsInit(RDRAM_ARG PTR(OSMesgQueue) queue, PTR(OSPfs) pfs, int channel); s32 osPfsReFormat(RDRAM_ARG PTR(OSPfs) pfs, PTR(OSMesgQueue) queue, int channel); s32 osPfsChecker(RDRAM_ARG PTR(OSPfs) pfs); -s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, int file_size_in_bytes, PTR(s32) file_no); +s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, int nbytes, PTR(s32) file_no); s32 osPfsFindFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, PTR(s32) file_no); s32 osPfsDeleteFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name); -s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs, s32 file_no, u8 flag, int offset, int size_in_bytes, u8* data_buffer); +s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs, s32 file_no, u8 flag, int offset, int nbytes, u8* data_buffer); s32 osPfsFileState(RDRAM_ARG PTR(OSPfs) pfs, s32 file_no, PTR(OSPfsState) state); s32 osPfsGetLabel(RDRAM_ARG PTR(OSPfs) pfs, u8* label, PTR(int) len); s32 osPfsSetLabel(RDRAM_ARG PTR(OSPfs) pfs, u8* label); From 69a2a781031e6f23b6554eab32697c6892587726 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sun, 18 Jan 2026 22:49:07 -0800 Subject: [PATCH 37/49] fix offset --- librecomp/include/librecomp/helpers.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/librecomp/include/librecomp/helpers.hpp b/librecomp/include/librecomp/helpers.hpp index e51e68c..eb9f86f 100644 --- a/librecomp/include/librecomp/helpers.hpp +++ b/librecomp/include/librecomp/helpers.hpp @@ -39,7 +39,7 @@ T _arg(uint8_t* rdram, recomp_context* ctx) requires(index < 4) { template T _arg(uint8_t* rdram, recomp_context* ctx) requires(index >= 4) { - const auto raw_arg = MEM_W(0x10 + index * 4, ctx->r29); + const auto raw_arg = MEM_W(index * 4, ctx->r29); if constexpr (std::is_pointer_v) { static_assert (!std::is_pointer_v>, "Double pointers not supported"); return TO_PTR(std::remove_pointer_t, raw_arg); From 1760d27a537897f05131314f230346eaa5502111 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sun, 18 Jan 2026 22:52:45 -0800 Subject: [PATCH 38/49] add cast to gpr --- librecomp/src/pak.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp index eac5192..2145056 100644 --- a/librecomp/src/pak.cpp +++ b/librecomp/src/pak.cpp @@ -55,10 +55,10 @@ extern "C" void osPfsAllocateFile_recomp(uint8_t* rdram, recomp_context* ctx) { u8 ext_name_proxy[PFS_FILE_EXT_LEN]; for (uint32_t i = 0; i < PFS_FILE_NAME_LEN; i++) { - game_name_proxy[i] = MEM_B(i, game_name); + game_name_proxy[i] = MEM_B(i, (gpr)game_name); } for (uint32_t i = 0; i < PFS_FILE_EXT_LEN; i++) { - ext_name_proxy[i] = MEM_B(i, ext_name); + ext_name_proxy[i] = MEM_B(i, (gpr)ext_name); } s32 ret = osPfsAllocateFile(PASS_RDRAM pfs, company_code, game_code, game_name_proxy, ext_name_proxy, nbytes, file_no); _return(ctx, ret); @@ -75,10 +75,10 @@ extern "C" void osPfsFindFile_recomp(uint8_t* rdram, recomp_context* ctx) { u8 ext_name_proxy[PFS_FILE_EXT_LEN]; for (uint32_t i = 0; i < PFS_FILE_NAME_LEN; i++) { - game_name_proxy[i] = MEM_B(i, game_name); + game_name_proxy[i] = MEM_B(i, (gpr)game_name); } for (uint32_t i = 0; i < PFS_FILE_EXT_LEN; i++) { - ext_name_proxy[i] = MEM_B(i, ext_name); + ext_name_proxy[i] = MEM_B(i, (gpr)ext_name); } s32 ret = osPfsFindFile(PASS_RDRAM pfs, company_code, game_code, game_name_proxy, ext_name_proxy, file_no); _return(ctx, ret); @@ -94,10 +94,10 @@ extern "C" void osPfsDeleteFile_recomp(uint8_t* rdram, recomp_context* ctx) { u8 ext_name_proxy[PFS_FILE_EXT_LEN]; for (uint32_t i = 0; i < PFS_FILE_NAME_LEN; i++) { - game_name_proxy[i] = MEM_B(i, game_name); + game_name_proxy[i] = MEM_B(i, (gpr)game_name); } for (uint32_t i = 0; i < PFS_FILE_EXT_LEN; i++) { - ext_name_proxy[i] = MEM_B(i, ext_name); + ext_name_proxy[i] = MEM_B(i, (gpr)ext_name); } s32 ret = osPfsDeleteFile(PASS_RDRAM pfs, company_code, game_code, game_name_proxy, ext_name_proxy); _return(ctx, ret); @@ -114,13 +114,13 @@ extern "C" void osPfsReadWriteFile_recomp(uint8_t* rdram, recomp_context* ctx) { if (flag == PFS_WRITE) { for (uint32_t i = 0; i < nbytes; i++) { - data_buffer_proxy[i] = MEM_B(i, data_buffer); + data_buffer_proxy[i] = MEM_B(i, (gpr)data_buffer); } } s32 ret = osPfsReadWriteFile(PASS_RDRAM pfs, file_no, flag, offset, nbytes, data_buffer_proxy.data()); if (flag == PFS_READ) { for (uint32_t i = 0; i < nbytes; i++) { - MEM_B(i, data_buffer) = data_buffer_proxy[i]; + MEM_B(i, (gpr)data_buffer) = data_buffer_proxy[i]; } } _return(ctx, ret); @@ -143,7 +143,7 @@ extern "C" void osPfsGetLabel_recomp(uint8_t* rdram, recomp_context* ctx) { s32 ret = osPfsGetLabel(PASS_RDRAM pfs, label_proxy, len); for (uint32_t i = 0; i < 32; i++) { - MEM_B(i, label) = label_proxy[i]; + MEM_B(i, (gpr)label) = label_proxy[i]; } _return(ctx, ret); } @@ -154,7 +154,7 @@ extern "C" void osPfsSetLabel_recomp(uint8_t* rdram, recomp_context* ctx) { u8 label_proxy[32]; for (uint32_t i = 0; i < 32; i++) { - label_proxy[i] = MEM_B(i, label); + label_proxy[i] = MEM_B(i, (gpr)label); } s32 ret = osPfsSetLabel(PASS_RDRAM pfs, label_proxy); _return(ctx, ret); @@ -166,7 +166,7 @@ extern "C" void osPfsIsPlug_recomp(uint8_t* rdram, recomp_context* ctx) { u8 pattern_proxy = 0; s32 ret = osPfsIsPlug(PASS_RDRAM mq, &pattern_proxy); - MEM_B(0, pattern) = pattern_proxy; + MEM_B(0, (gpr)pattern) = pattern_proxy; _return(ctx, ret); } From cb0a22cdaf59b0f8163daa7f40581f7317004010 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Mon, 19 Jan 2026 22:52:21 -0800 Subject: [PATCH 39/49] modified controllerpak code --- ultramodern/src/controller_pak.hpp | 116 +++++++++ ultramodern/src/input.cpp | 401 ++++++++++++++++++++++++++++- 2 files changed, 511 insertions(+), 6 deletions(-) create mode 100644 ultramodern/src/controller_pak.hpp diff --git a/ultramodern/src/controller_pak.hpp b/ultramodern/src/controller_pak.hpp new file mode 100644 index 0000000..472bd0e --- /dev/null +++ b/ultramodern/src/controller_pak.hpp @@ -0,0 +1,116 @@ +#ifndef __ULTRAMODERN_PFS_HPP__ +#define __ULTRAMODERN_PFS_HPP__ + +#include + +typedef struct ControllerPak { + std::fstream header; + std::fstream file; +} ControllerPak; + +typedef struct ControllerPakHdr { + int file_size; + u32 game_code; + u16 company_code; + u8 ext_name[4]; + u8 game_name[16]; +} ControllerPakHdr; + +// extern std::filesystem::path config_path; +// const std::u8string save_folder = u8"saves"; +// std::filesystem::path save_folder_path = config_path / save_folder; + +void Pfs_PakHeader_Write(int file_size, u32 game_code, u16 company_code, const u8* ext_name, const u8* game_name, u8 file_index) { + ControllerPak pak; + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + assert(false); + } + if (!pak.header.is_open()) { + assert(false); + } + + /* Set file parameters to header */ + u32 seek = file_index * 0x20; + + // file_size + pak.header.seekp(seek + 0x0, std::ios::beg); + pak.header.write((const char*)&file_size, 4); + // game_code + pak.header.seekp(seek + 0x4, std::ios::beg); + pak.header.write((const char*)&game_code, 4); + // company_code + pak.header.seekp(seek + 0x08, std::ios::beg); + pak.header.write((const char*)&company_code, 2); + // ext_name + pak.header.seekp(seek + 0xC, std::ios::beg); + pak.header.write((const char*)ext_name, 4); + // game_name + pak.header.seekp(seek + 0x10, std::ios::beg); + pak.header.write((const char*)game_name, 16); + + pak.header.close(); +} + +void Pfs_PakHeader_Write(const ControllerPakHdr& hdr, u8 file_index) { + Pfs_PakHeader_Write(hdr.file_size, hdr.game_code, hdr.company_code, hdr.ext_name, hdr.game_name, file_index); +} + +void Pfs_PakHeader_Read(ControllerPakHdr& hdr, u8 file_index) { + ControllerPak pak; + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + assert(false); + } + if (!pak.header.is_open()) { + assert(false); + } + + /* Set file parameters to header */ + u32 seek = file_index * sizeof(OSPfsState); + + // file_size + pak.header.seekg(seek + 0x0, std::ios::beg); + pak.header.read((char*)&hdr.file_size, 4); + // game_code + pak.header.seekg(seek + 0x4, std::ios::beg); + pak.header.read((char*)&hdr.game_code, 4); + // company_code + pak.header.seekg(seek + 0x08, std::ios::beg); + pak.header.read((char*)&hdr.company_code, 2); + // ext_name + pak.header.seekg(seek + 0xC, std::ios::beg); + pak.header.read((char*)hdr.ext_name, 4); + // game_name + pak.header.seekg(seek + 0x10, std::ios::beg); + pak.header.read((char*)hdr.game_name, 16); + + pak.header.close(); +} + +void Pfs_ByteSwapFile(u8* buffer, size_t size) { + uint8_t c0, c1, c2, c3; + + for (size_t i = 0; i < size; i += 4) { + c0 = buffer[i + 0]; + c1 = buffer[i + 1]; + c2 = buffer[i + 2]; + c3 = buffer[i + 3]; + + buffer[i + 3] = c0; + buffer[i + 2] = c1; + buffer[i + 1] = c2; + buffer[i + 0] = c3; + } +} + +void ByteSwapCopy(uint8_t* dst, uint8_t* src, size_t size_bytes) { + for (size_t i = 0; i < size_bytes; i++) { + dst[i] = src[i ^ 3]; + } +} + +#endif // __ULTRAMODERN_PFS_HPP__ + diff --git a/ultramodern/src/input.cpp b/ultramodern/src/input.cpp index 8e86cb1..e2b9fce 100644 --- a/ultramodern/src/input.cpp +++ b/ultramodern/src/input.cpp @@ -1,13 +1,25 @@ #include +#include +#include #include "ultramodern/input.hpp" #include "ultramodern/ultra64.h" #include "ultramodern/ultramodern.hpp" +#include "controller_pak.hpp" #define PFS_ERR_NOPACK 1 // no device inserted +#define PFS_ERR_NEW_PACK 2 /* ram pack has been changed to a different one */ +#define PFS_ERR_INCONSISTENT 3 /* need to run Pfschecker*/ #define PFS_ERR_CONTRFAIL 4 // data transmission failure #define PFS_ERR_INVALID 5 // invalid parameter or invalid file +#define PFS_ERR_BAD_DATA 6 /* the data read from pack are bad*/ +#define PFS_DATA_FULL 7 /* no free pages on ram pack*/ +#define PFS_DIR_FULL 8 /* no free directories on ram pack*/ +#define PFS_ERR_EXIST 9 /* file exists*/ +#define PFS_ERR_ID_FATAL 10 /* dead ram pack */ #define PFS_ERR_DEVICE 11 // different type of device inserted +#define PFS_ERR_NO_GBCART 12 /* no gb cartridge (64GB-PAK) */ +#define PFS_ERR_NEW_GBCART 13 /* gb cartridge may be changed */ #define PFS_INITIALIZED 1 #define PFS_CORRUPTED 2 @@ -15,6 +27,10 @@ #define PFS_MOTOR_INITIALIZED 8 #define PFS_GBPAK_INITIALIZED 16 +#define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) +#define MAX_FILES 16 +#define TRACE_ENTRY() fprintf(stderr, "PAK_ENTRY(%s)\n", __func__); + static ultramodern::input::callbacks_t input_callbacks {}; void ultramodern::input::set_callbacks(const callbacks_t& callbacks) { @@ -160,13 +176,13 @@ extern "C" void osContGetReadData(OSContPad *data) { } } -/* Rumble */ +/* RumblePak */ -s32 osMotorInit(RDRAM_ARG PTR(OSMesgQueue) mq, PTR(OSPfs) pfs_, int channel) { +extern "C" s32 osMotorInit(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int channel) { OSPfs *pfs = TO_PTR(OSPfs, pfs_); // basic initialization performed regardless of connected/disconnected status - pfs->queue = mq; + pfs->queue = mq_; pfs->channel = channel; pfs->activebank = 0xFF; pfs->status = 0; @@ -190,15 +206,15 @@ s32 osMotorInit(RDRAM_ARG PTR(OSMesgQueue) mq, PTR(OSPfs) pfs_, int channel) { return 0; } -s32 osMotorStop(RDRAM_ARG PTR(OSPfs) pfs) { +extern "C" s32 osMotorStop(RDRAM_ARG PTR(OSPfs) pfs) { return __osMotorAccess(PASS_RDRAM pfs, false); } -s32 osMotorStart(RDRAM_ARG PTR(OSPfs) pfs) { +extern "C" s32 osMotorStart(RDRAM_ARG PTR(OSPfs) pfs) { return __osMotorAccess(PASS_RDRAM pfs, true); } -s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs) pfs_, s32 flag) { +extern "C" s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs) pfs_, s32 flag) { OSPfs *pfs = TO_PTR(OSPfs, pfs_); if (!(pfs->status & PFS_MOTOR_INITIALIZED)) { @@ -212,3 +228,376 @@ s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs) pfs_, s32 flag) { return 0; } +/* ControllerPak */ + +static s32 __osPfsGetStatus(RDRAM_ARG PTR(OSMesgQueue) queue, int channel) { + ultramodern::input::connected_device_info_t device_info{}; + if (input_callbacks.get_connected_device_info != nullptr) { + device_info = input_callbacks.get_connected_device_info(channel); + } + + if (device_info.connected_device != ultramodern::input::Device::Controller) { + return PFS_ERR_CONTRFAIL; + } + if (device_info.connected_pak == ultramodern::input::Pak::None) { + return PFS_ERR_NOPACK; + } + if (device_info.connected_pak != ultramodern::input::Pak::ControllerPak) { + return PFS_ERR_DEVICE; + } + + // If a header file doesn't exist, create it. + ControllerPak pak; + if (!std::filesystem::exists("controllerPak_header.sav")) { + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); + pak.header.close(); + } + + fprintf(stderr, "__osPfsGetStatus OK\n"); + return 0; +} + +extern "C" s32 osPfsInitPak(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int channel) { + TRACE_ENTRY() + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + + const auto status = __osPfsGetStatus(PASS_RDRAM mq_, channel); + if (status != 0) { + return status; + } + + pfs->queue = mq_; + pfs->channel = channel; + pfs->status = 0; + + const s32 ret = osPfsChecker(PASS_RDRAM pfs_); + pfs->status |= PFS_INITIALIZED; + return 0; +} + +extern "C" s32 osPfsRepairId(RDRAM_ARG PTR(OSPfs) pfs) { + TRACE_ENTRY() + return 0; +} + +extern "C" s32 osPfsInit(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int channel) { + TRACE_ENTRY() + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + + const auto status = __osPfsGetStatus(PASS_RDRAM mq_, channel); + if (status != 0) { + return status; + } + + pfs->queue = mq_; + pfs->channel = channel; + pfs->status = 0; + pfs->activebank = -1; + + const s32 ret = osPfsChecker(PASS_RDRAM pfs_); + pfs->status |= PFS_INITIALIZED; + return 0; +} + +extern "C" s32 osPfsReFormat(RDRAM_ARG PTR(OSPfs) pfs, PTR(OSMesgQueue) mq_, int channel) { + TRACE_ENTRY() + return 0; +} + +extern "C" s32 osPfsChecker(RDRAM_ARG PTR(OSPfs) pfs) { + TRACE_ENTRY() + return 0; +} + +extern "C" s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, int nbytes, PTR(s32) file_no_) { + TRACE_ENTRY() + s32* file_no = TO_PTR(s32, file_no_); + + if ((company_code == 0) || (game_code == 0)) { + return PFS_ERR_INVALID; + } + + /* Search for a free slot */ + u8 free_file_index = 0; + for (size_t i = 0; i < MAX_FILES; i++) { + ControllerPakHdr hdr{}; + Pfs_PakHeader_Read(hdr, i); + + if ((hdr.company_code == 0) || (hdr.game_code == 0)) { + free_file_index = i; + break; + } + } + + if (free_file_index == MAX_FILES) { + return PFS_DIR_FULL; + } + + Pfs_PakHeader_Write(nbytes, game_code, company_code, ext_name, game_name, free_file_index); + + /* Create empty file */ + + ControllerPak pak; + + const auto filename = std::format("controllerPak_file_{}.sav", free_file_index); + pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); + + nbytes = (nbytes + 31) & ~31; + + std::vector zero_block(nbytes, 0); + pak.file.seekp(0, std::ios::beg); + pak.file.write((char*)zero_block.data(), nbytes); + pak.file.close(); + + *file_no = free_file_index; + return 0; +} + +extern "C" s32 osPfsFindFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, PTR(s32) file_no_) { + TRACE_ENTRY() + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + s32* file_no = TO_PTR(s32, file_no_); + + for (size_t i = 0; i < MAX_FILES; i++) { + ControllerPakHdr hdr{}; + Pfs_PakHeader_Read(hdr, i); + + if ((hdr.company_code == 0) || (hdr.game_code == 0)) { + continue; + } else { + if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { + for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { + if (game_name[i] != hdr.game_name[i]) { + break; + } + } + for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { + if (ext_name[i] != hdr.ext_name[i]) { + break; + } + } + // File found + *file_no = i; + return 0; + } + } + } + + return PFS_ERR_INVALID; +} + +extern "C" s32 osPfsDeleteFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 game_code, u8* game_name, u8* ext_name) { + TRACE_ENTRY() + + if (company_code == 0 || game_code == 0) { + return PFS_ERR_INVALID; + } + + ControllerPak pak; + for (int i = 0; i < MAX_FILES; i++) { + ControllerPakHdr hdr{}; + Pfs_PakHeader_Read(hdr, i); + + if ((hdr.company_code == 0) || (hdr.game_code == 0)) { + continue; + } else { + if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { + int gncount = 0; + int encount = 0; + + for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { + if (game_name[i] == hdr.game_name[i]) { + gncount++; + } + } + for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { + if (ext_name[i] == hdr.ext_name[i]) { + encount++; + } + } + if ((gncount != 16) || encount != 4) { + continue; + } + // File found + + pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + assert(false); + } + if (!pak.header.is_open()) { + assert(false); + } + + u32 seek = i * sizeof(OSPfsState); + + // Zero out the header for this file. + std::vector zero_block(sizeof(OSPfsState), 0); + pak.header.seekp(seek + 0x0, std::ios::beg); + pak.header.write((char*)zero_block.data(), sizeof(OSPfsState)); + pak.header.close(); + + std::filesystem::remove(std::format("controllerPak_file_{}.sav", i)); + return 0; + } + } + } + + return PFS_ERR_INVALID; +} + +extern "C" s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, u8 flag, int offset, int size_in_bytes, u8* data_buffer) { + TRACE_ENTRY() + + ControllerPak pak; + + const auto filename = std::format("controllerPak_file_{}.sav", file_no); + pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); + + if (!std::filesystem::exists(filename)) { + return PFS_ERR_INVALID; + } + if (!pak.file.good()) { + return PFS_ERR_INVALID; + } + if (!pak.file.is_open()) { + return PFS_ERR_INVALID; + } + + std::vector swap_buffer(size_in_bytes); + if (flag == 0) { + pak.file.seekg(offset, std::ios::beg); + pak.file.read((char*)swap_buffer.data(), size_in_bytes); + ByteSwapCopy(data_buffer, swap_buffer.data(), size_in_bytes); + } else { + ByteSwapCopy(swap_buffer.data(), data_buffer, size_in_bytes); + pak.file.seekp(offset, std::ios::beg); + pak.file.write((char*)swap_buffer.data(), size_in_bytes); + } + + pak.file.close(); + return 0; +} + +extern "C" s32 osPfsFileState(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, PTR(OSPfsState) state_) { + TRACE_ENTRY() + OSPfsState *state = TO_PTR(OSPfsState, state_); + + // should pass the state of the requested file_no to the incoming state pointer, + // games call this function 16 times, once per file + // fills the incoming state with the information inside the header of the pak. + + const auto filename = std::format("controllerPak_file_{}.sav", file_no); + if (!std::filesystem::exists(filename)) { + return PFS_ERR_INVALID; + } + + /* Read game info from pak */ + ControllerPakHdr hdr{}; + Pfs_PakHeader_Read(hdr, file_no); + + state->file_size = hdr.file_size; + state->company_code = hdr.company_code; + state->game_code = hdr.game_code; + + for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { + state->game_name[i] = hdr.game_name[i]; + } + for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { + state->ext_name[i] = hdr.ext_name[i]; + } + + return 0; +} + +extern "C" s32 osPfsGetLabel(RDRAM_ARG PTR(OSPfs) pfs_, u8* label, PTR(int) len_) { + TRACE_ENTRY() + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + int* len = TO_PTR(int, len_); + + if (label == NULL) { + return PFS_ERR_INVALID; + } +// if (__osCheckId(pfs) == PFS_ERR_NEW_PACK) { +// return PFS_ERR_NEW_PACK; +// } + + int i; + for (i = 0; i < ARRLEN(pfs->label); i++) { + if (pfs->label[i] == 0) { + break; + } + *label++ = pfs->label[i]; + } + *len = i; + return 0; +} + +extern "C" s32 osPfsSetLabel(RDRAM_ARG PTR(OSPfs) pfs_, u8* label) { + TRACE_ENTRY() + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + + if (label != NULL) { + for (int i = 0; i < ARRLEN(pfs->label); i++) { + if (*label == 0) { + break; + } + pfs->label[i] = *label++; + } + } + return 0; +} + +extern "C" s32 osPfsIsPlug(RDRAM_ARG PTR(OSMesgQueue) mq_, u8* pattern) { + TRACE_ENTRY() + u8 bits = 0; + + for (int channel = 0; channel < max_controllers; channel++) { + if (__osPfsGetStatus(PASS_RDRAM mq_, channel) == 0) { + bits |= (1 << channel); + } + } + *pattern = bits; + return 0; +} + +extern "C" s32 osPfsFreeBlocks(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) bytes_not_used_) { + TRACE_ENTRY() + OSPfs *pfs = TO_PTR(OSPfs, pfs_); + s32 *bytes_not_used = TO_PTR(s32, bytes_not_used_); + + s32 bytes_used = 0; + for (size_t i = 0; i < MAX_FILES; i++) { + ControllerPakHdr hdr{}; + Pfs_PakHeader_Read(hdr, i); + + if ((hdr.company_code != 0) && (hdr.game_code != 0)) { + bytes_used += hdr.file_size >> 8; + } + } + + *bytes_not_used = (123 - bytes_used) << 8; + return 0; +} + +extern "C" s32 osPfsNumFiles(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) max_files_, PTR(s32) files_used_) { + TRACE_ENTRY() + OSPfs *pfs = TO_PTR(OSPfs, pfs_); + s32 *max_files = TO_PTR(s32, max_files_); + s32 *files_used = TO_PTR(s32, files_used_); + + u8 num_files = 0; + for (size_t i = 0; i < MAX_FILES; i++) { + ControllerPakHdr hdr{}; + Pfs_PakHeader_Read(hdr, i); + + if ((hdr.company_code != 0) && (hdr.game_code != 0)) { + num_files++; + } + } + + *max_files = MAX_FILES; + *files_used = num_files; + return 0; +} + From 8d997fcb86ce2dd3db60ea065df37aee1b0500a3 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Thu, 22 Jan 2026 22:09:31 -0800 Subject: [PATCH 40/49] cleanup + write pfs files to save directory --- ultramodern/CMakeLists.txt | 1 + ultramodern/include/ultramodern/input.hpp | 5 + ultramodern/include/ultramodern/save.hpp | 2 + ultramodern/include/ultramodern/ultra64.h | 42 ++ ultramodern/src/input.cpp | 435 +----------------- ultramodern/src/pfs.cpp | 355 ++++++++++++++ .../src/{controller_pak.hpp => pfs.hpp} | 49 +- ultramodern/src/save.cpp | 11 +- 8 files changed, 461 insertions(+), 439 deletions(-) create mode 100644 ultramodern/src/pfs.cpp rename ultramodern/src/{controller_pak.hpp => pfs.hpp} (62%) diff --git a/ultramodern/CMakeLists.txt b/ultramodern/CMakeLists.txt index 0c844b4..54cdd9f 100644 --- a/ultramodern/CMakeLists.txt +++ b/ultramodern/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(ultramodern STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/input.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/mesgqueue.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/misc_ultra.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/pfs.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/renderer_context.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rsp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/save.cpp" diff --git a/ultramodern/include/ultramodern/input.hpp b/ultramodern/include/ultramodern/input.hpp index 0e7866f..abb8e76 100644 --- a/ultramodern/include/ultramodern/input.hpp +++ b/ultramodern/include/ultramodern/input.hpp @@ -58,6 +58,11 @@ namespace ultramodern { void set_callbacks(const callbacks_t& callbacks); } + + input::connected_device_info_t get_connected_device_info(int channel); + + int get_max_controllers(); } #endif + diff --git a/ultramodern/include/ultramodern/save.hpp b/ultramodern/include/ultramodern/save.hpp index 568b900..576c17c 100644 --- a/ultramodern/include/ultramodern/save.hpp +++ b/ultramodern/include/ultramodern/save.hpp @@ -34,6 +34,8 @@ namespace ultramodern { size_t get_save_size(SaveType save_type); + std::filesystem::path get_save_base_path(); + std::filesystem::path get_save_file_path(); bool eeprom_allowed(); diff --git a/ultramodern/include/ultramodern/ultra64.h b/ultramodern/include/ultramodern/ultra64.h index 7cb288d..aee0c19 100644 --- a/ultramodern/include/ultramodern/ultra64.h +++ b/ultramodern/include/ultramodern/ultra64.h @@ -74,6 +74,38 @@ typedef u64 OSTime; #define OS_EVENT_THREADSTATUS 13 /* CPU thread status: used by rmon */ #define OS_EVENT_PRENMI 14 /* Pre NMI interrupt */ +/* Controller errors */ + +#define CONT_NO_RESPONSE_ERROR 0x8 +#define CONT_OVERRUN_ERROR 0x4 +#define CONT_RANGE_ERROR -1 +#define CONT_FRAME_ERROR 0x2 +#define CONT_COLLISION_ERROR 0x1 + +/* Controller type */ + +#define CONT_TYPE_NORMAL 0x0005 +#define CONT_TYPE_MOUSE 0x0002 +#define CONT_TYPE_VOICE 0x0100 + +/* File System error number */ + +#define PFS_ERR_NOPACK 1 /* no memory card is plugged or */ +#define PFS_ERR_NEW_PACK 2 /* ram pack has been changed to a different one */ +#define PFS_ERR_INCONSISTENT 3 /* need to run Pfschecker*/ +#define PFS_ERR_CONTRFAIL CONT_OVERRUN_ERROR +#define PFS_ERR_INVALID 5 /* invalid parameter or file not exist*/ +#define PFS_ERR_BAD_DATA 6 /* the data read from pack are bad*/ +#define PFS_DATA_FULL 7 /* no free pages on ram pack*/ +#define PFS_DIR_FULL 8 /* no free directories on ram pack*/ +#define PFS_ERR_EXIST 9 /* file exists*/ +#define PFS_ERR_ID_FATAL 10 /* dead ram pack */ +#define PFS_ERR_DEVICE 11 /* wrong device type*/ +#define PFS_ERR_NO_GBCART 12 /* no gb cartridge (64GB-PAK) */ +#define PFS_ERR_NEW_GBCART 13 /* gb cartridge may be changed */ + +/* File System size */ + #define PFS_INODE_SIZE_PER_PAGE 128 #define PFS_FILE_NAME_LEN 16 #define PFS_FILE_EXT_LEN 4 @@ -81,10 +113,20 @@ typedef u64 OSTime; #define PFS_ONE_PAGE 8 /* blocks */ #define PFS_MAX_BANKS 62 +/* File System flag */ + #define PFS_READ 0 #define PFS_WRITE 1 #define PFS_CREATE 2 +/* File System status */ + +#define PFS_INITIALIZED 0x1 +#define PFS_CORRUPTED 0x2 +#define PFS_ID_BROKEN 0x4 +#define PFS_MOTOR_INITIALIZED 0x8 +#define PFS_GBPAK_INITIALIZED 0x10 + #define M_GFXTASK 1 #define M_AUDTASK 2 #define M_VIDTASK 3 diff --git a/ultramodern/src/input.cpp b/ultramodern/src/input.cpp index e2b9fce..5d723c5 100644 --- a/ultramodern/src/input.cpp +++ b/ultramodern/src/input.cpp @@ -1,42 +1,31 @@ #include -#include -#include #include "ultramodern/input.hpp" #include "ultramodern/ultra64.h" #include "ultramodern/ultramodern.hpp" -#include "controller_pak.hpp" -#define PFS_ERR_NOPACK 1 // no device inserted -#define PFS_ERR_NEW_PACK 2 /* ram pack has been changed to a different one */ -#define PFS_ERR_INCONSISTENT 3 /* need to run Pfschecker*/ -#define PFS_ERR_CONTRFAIL 4 // data transmission failure -#define PFS_ERR_INVALID 5 // invalid parameter or invalid file -#define PFS_ERR_BAD_DATA 6 /* the data read from pack are bad*/ -#define PFS_DATA_FULL 7 /* no free pages on ram pack*/ -#define PFS_DIR_FULL 8 /* no free directories on ram pack*/ -#define PFS_ERR_EXIST 9 /* file exists*/ -#define PFS_ERR_ID_FATAL 10 /* dead ram pack */ -#define PFS_ERR_DEVICE 11 // different type of device inserted -#define PFS_ERR_NO_GBCART 12 /* no gb cartridge (64GB-PAK) */ -#define PFS_ERR_NEW_GBCART 13 /* gb cartridge may be changed */ +#define MAXCONTROLLERS 4 -#define PFS_INITIALIZED 1 -#define PFS_CORRUPTED 2 -#define PFS_ID_BROKEN 4 -#define PFS_MOTOR_INITIALIZED 8 -#define PFS_GBPAK_INITIALIZED 16 - -#define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) -#define MAX_FILES 16 -#define TRACE_ENTRY() fprintf(stderr, "PAK_ENTRY(%s)\n", __func__); +static int max_controllers = 0; static ultramodern::input::callbacks_t input_callbacks {}; +int ultramodern::get_max_controllers() { + return max_controllers; +} + void ultramodern::input::set_callbacks(const callbacks_t& callbacks) { input_callbacks = callbacks; } +ultramodern::input::connected_device_info_t ultramodern::get_connected_device_info(int channel) { + ultramodern::input::connected_device_info_t device_info{}; + if (input_callbacks.get_connected_device_info != nullptr) { + device_info = input_callbacks.get_connected_device_info(channel); + } + return device_info; +} + static std::chrono::high_resolution_clock::time_point input_poll_time; static void update_poll_time() { @@ -49,16 +38,6 @@ void ultramodern::measure_input_latency() { #endif } -#define MAXCONTROLLERS 4 - -#define CONT_NO_RESPONSE_ERROR 0x8 - -#define CONT_TYPE_NORMAL 0x0005 -#define CONT_TYPE_MOUSE 0x0002 -#define CONT_TYPE_VOICE 0x0100 - -static int max_controllers = 0; - /* Plain controller */ static u16 get_controller_type(ultramodern::input::Device device_type) { @@ -85,12 +64,7 @@ static void __osContGetInitData(u8* pattern, OSContStatus *data) { *pattern = 0x00; for (int controller = 0; controller < max_controllers; controller++) { - ultramodern::input::connected_device_info_t device_info{}; - - if (input_callbacks.get_connected_device_info != nullptr) { - device_info = input_callbacks.get_connected_device_info(controller); - } - + const auto device_info = ultramodern::get_connected_device_info(controller); if (device_info.connected_device != ultramodern::input::Device::None) { // Mark controller as present @@ -187,11 +161,7 @@ extern "C" s32 osMotorInit(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int pfs->activebank = 0xFF; pfs->status = 0; - ultramodern::input::connected_device_info_t device_info{}; - if (input_callbacks.get_connected_device_info != nullptr) { - device_info = input_callbacks.get_connected_device_info(channel); - } - + const auto device_info = ultramodern::get_connected_device_info(channel); if (device_info.connected_device != ultramodern::input::Device::Controller) { return PFS_ERR_CONTRFAIL; } @@ -228,376 +198,3 @@ extern "C" s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs) pfs_, s32 flag) { return 0; } -/* ControllerPak */ - -static s32 __osPfsGetStatus(RDRAM_ARG PTR(OSMesgQueue) queue, int channel) { - ultramodern::input::connected_device_info_t device_info{}; - if (input_callbacks.get_connected_device_info != nullptr) { - device_info = input_callbacks.get_connected_device_info(channel); - } - - if (device_info.connected_device != ultramodern::input::Device::Controller) { - return PFS_ERR_CONTRFAIL; - } - if (device_info.connected_pak == ultramodern::input::Pak::None) { - return PFS_ERR_NOPACK; - } - if (device_info.connected_pak != ultramodern::input::Pak::ControllerPak) { - return PFS_ERR_DEVICE; - } - - // If a header file doesn't exist, create it. - ControllerPak pak; - if (!std::filesystem::exists("controllerPak_header.sav")) { - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - pak.header.close(); - } - - fprintf(stderr, "__osPfsGetStatus OK\n"); - return 0; -} - -extern "C" s32 osPfsInitPak(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int channel) { - TRACE_ENTRY() - OSPfs* pfs = TO_PTR(OSPfs, pfs_); - - const auto status = __osPfsGetStatus(PASS_RDRAM mq_, channel); - if (status != 0) { - return status; - } - - pfs->queue = mq_; - pfs->channel = channel; - pfs->status = 0; - - const s32 ret = osPfsChecker(PASS_RDRAM pfs_); - pfs->status |= PFS_INITIALIZED; - return 0; -} - -extern "C" s32 osPfsRepairId(RDRAM_ARG PTR(OSPfs) pfs) { - TRACE_ENTRY() - return 0; -} - -extern "C" s32 osPfsInit(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int channel) { - TRACE_ENTRY() - OSPfs* pfs = TO_PTR(OSPfs, pfs_); - - const auto status = __osPfsGetStatus(PASS_RDRAM mq_, channel); - if (status != 0) { - return status; - } - - pfs->queue = mq_; - pfs->channel = channel; - pfs->status = 0; - pfs->activebank = -1; - - const s32 ret = osPfsChecker(PASS_RDRAM pfs_); - pfs->status |= PFS_INITIALIZED; - return 0; -} - -extern "C" s32 osPfsReFormat(RDRAM_ARG PTR(OSPfs) pfs, PTR(OSMesgQueue) mq_, int channel) { - TRACE_ENTRY() - return 0; -} - -extern "C" s32 osPfsChecker(RDRAM_ARG PTR(OSPfs) pfs) { - TRACE_ENTRY() - return 0; -} - -extern "C" s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, int nbytes, PTR(s32) file_no_) { - TRACE_ENTRY() - s32* file_no = TO_PTR(s32, file_no_); - - if ((company_code == 0) || (game_code == 0)) { - return PFS_ERR_INVALID; - } - - /* Search for a free slot */ - u8 free_file_index = 0; - for (size_t i = 0; i < MAX_FILES; i++) { - ControllerPakHdr hdr{}; - Pfs_PakHeader_Read(hdr, i); - - if ((hdr.company_code == 0) || (hdr.game_code == 0)) { - free_file_index = i; - break; - } - } - - if (free_file_index == MAX_FILES) { - return PFS_DIR_FULL; - } - - Pfs_PakHeader_Write(nbytes, game_code, company_code, ext_name, game_name, free_file_index); - - /* Create empty file */ - - ControllerPak pak; - - const auto filename = std::format("controllerPak_file_{}.sav", free_file_index); - pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - - nbytes = (nbytes + 31) & ~31; - - std::vector zero_block(nbytes, 0); - pak.file.seekp(0, std::ios::beg); - pak.file.write((char*)zero_block.data(), nbytes); - pak.file.close(); - - *file_no = free_file_index; - return 0; -} - -extern "C" s32 osPfsFindFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, PTR(s32) file_no_) { - TRACE_ENTRY() - OSPfs* pfs = TO_PTR(OSPfs, pfs_); - s32* file_no = TO_PTR(s32, file_no_); - - for (size_t i = 0; i < MAX_FILES; i++) { - ControllerPakHdr hdr{}; - Pfs_PakHeader_Read(hdr, i); - - if ((hdr.company_code == 0) || (hdr.game_code == 0)) { - continue; - } else { - if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { - for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { - if (game_name[i] != hdr.game_name[i]) { - break; - } - } - for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { - if (ext_name[i] != hdr.ext_name[i]) { - break; - } - } - // File found - *file_no = i; - return 0; - } - } - } - - return PFS_ERR_INVALID; -} - -extern "C" s32 osPfsDeleteFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 game_code, u8* game_name, u8* ext_name) { - TRACE_ENTRY() - - if (company_code == 0 || game_code == 0) { - return PFS_ERR_INVALID; - } - - ControllerPak pak; - for (int i = 0; i < MAX_FILES; i++) { - ControllerPakHdr hdr{}; - Pfs_PakHeader_Read(hdr, i); - - if ((hdr.company_code == 0) || (hdr.game_code == 0)) { - continue; - } else { - if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { - int gncount = 0; - int encount = 0; - - for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { - if (game_name[i] == hdr.game_name[i]) { - gncount++; - } - } - for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { - if (ext_name[i] == hdr.ext_name[i]) { - encount++; - } - } - if ((gncount != 16) || encount != 4) { - continue; - } - // File found - - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - - u32 seek = i * sizeof(OSPfsState); - - // Zero out the header for this file. - std::vector zero_block(sizeof(OSPfsState), 0); - pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char*)zero_block.data(), sizeof(OSPfsState)); - pak.header.close(); - - std::filesystem::remove(std::format("controllerPak_file_{}.sav", i)); - return 0; - } - } - } - - return PFS_ERR_INVALID; -} - -extern "C" s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, u8 flag, int offset, int size_in_bytes, u8* data_buffer) { - TRACE_ENTRY() - - ControllerPak pak; - - const auto filename = std::format("controllerPak_file_{}.sav", file_no); - pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - - if (!std::filesystem::exists(filename)) { - return PFS_ERR_INVALID; - } - if (!pak.file.good()) { - return PFS_ERR_INVALID; - } - if (!pak.file.is_open()) { - return PFS_ERR_INVALID; - } - - std::vector swap_buffer(size_in_bytes); - if (flag == 0) { - pak.file.seekg(offset, std::ios::beg); - pak.file.read((char*)swap_buffer.data(), size_in_bytes); - ByteSwapCopy(data_buffer, swap_buffer.data(), size_in_bytes); - } else { - ByteSwapCopy(swap_buffer.data(), data_buffer, size_in_bytes); - pak.file.seekp(offset, std::ios::beg); - pak.file.write((char*)swap_buffer.data(), size_in_bytes); - } - - pak.file.close(); - return 0; -} - -extern "C" s32 osPfsFileState(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, PTR(OSPfsState) state_) { - TRACE_ENTRY() - OSPfsState *state = TO_PTR(OSPfsState, state_); - - // should pass the state of the requested file_no to the incoming state pointer, - // games call this function 16 times, once per file - // fills the incoming state with the information inside the header of the pak. - - const auto filename = std::format("controllerPak_file_{}.sav", file_no); - if (!std::filesystem::exists(filename)) { - return PFS_ERR_INVALID; - } - - /* Read game info from pak */ - ControllerPakHdr hdr{}; - Pfs_PakHeader_Read(hdr, file_no); - - state->file_size = hdr.file_size; - state->company_code = hdr.company_code; - state->game_code = hdr.game_code; - - for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { - state->game_name[i] = hdr.game_name[i]; - } - for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { - state->ext_name[i] = hdr.ext_name[i]; - } - - return 0; -} - -extern "C" s32 osPfsGetLabel(RDRAM_ARG PTR(OSPfs) pfs_, u8* label, PTR(int) len_) { - TRACE_ENTRY() - OSPfs* pfs = TO_PTR(OSPfs, pfs_); - int* len = TO_PTR(int, len_); - - if (label == NULL) { - return PFS_ERR_INVALID; - } -// if (__osCheckId(pfs) == PFS_ERR_NEW_PACK) { -// return PFS_ERR_NEW_PACK; -// } - - int i; - for (i = 0; i < ARRLEN(pfs->label); i++) { - if (pfs->label[i] == 0) { - break; - } - *label++ = pfs->label[i]; - } - *len = i; - return 0; -} - -extern "C" s32 osPfsSetLabel(RDRAM_ARG PTR(OSPfs) pfs_, u8* label) { - TRACE_ENTRY() - OSPfs* pfs = TO_PTR(OSPfs, pfs_); - - if (label != NULL) { - for (int i = 0; i < ARRLEN(pfs->label); i++) { - if (*label == 0) { - break; - } - pfs->label[i] = *label++; - } - } - return 0; -} - -extern "C" s32 osPfsIsPlug(RDRAM_ARG PTR(OSMesgQueue) mq_, u8* pattern) { - TRACE_ENTRY() - u8 bits = 0; - - for (int channel = 0; channel < max_controllers; channel++) { - if (__osPfsGetStatus(PASS_RDRAM mq_, channel) == 0) { - bits |= (1 << channel); - } - } - *pattern = bits; - return 0; -} - -extern "C" s32 osPfsFreeBlocks(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) bytes_not_used_) { - TRACE_ENTRY() - OSPfs *pfs = TO_PTR(OSPfs, pfs_); - s32 *bytes_not_used = TO_PTR(s32, bytes_not_used_); - - s32 bytes_used = 0; - for (size_t i = 0; i < MAX_FILES; i++) { - ControllerPakHdr hdr{}; - Pfs_PakHeader_Read(hdr, i); - - if ((hdr.company_code != 0) && (hdr.game_code != 0)) { - bytes_used += hdr.file_size >> 8; - } - } - - *bytes_not_used = (123 - bytes_used) << 8; - return 0; -} - -extern "C" s32 osPfsNumFiles(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) max_files_, PTR(s32) files_used_) { - TRACE_ENTRY() - OSPfs *pfs = TO_PTR(OSPfs, pfs_); - s32 *max_files = TO_PTR(s32, max_files_); - s32 *files_used = TO_PTR(s32, files_used_); - - u8 num_files = 0; - for (size_t i = 0; i < MAX_FILES; i++) { - ControllerPakHdr hdr{}; - Pfs_PakHeader_Read(hdr, i); - - if ((hdr.company_code != 0) && (hdr.game_code != 0)) { - num_files++; - } - } - - *max_files = MAX_FILES; - *files_used = num_files; - return 0; -} - diff --git a/ultramodern/src/pfs.cpp b/ultramodern/src/pfs.cpp new file mode 100644 index 0000000..a8f2c1c --- /dev/null +++ b/ultramodern/src/pfs.cpp @@ -0,0 +1,355 @@ +#include +#include +#include + +#include +#include "pfs.hpp" + +#define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) +#define MAX_FILES 16 + +/* ControllerPak */ + +static s32 __osPfsGetStatus(RDRAM_ARG PTR(OSMesgQueue) queue, int channel) { + const auto device_info = ultramodern::get_connected_device_info(channel); + if (device_info.connected_device != ultramodern::input::Device::Controller) { + return PFS_ERR_CONTRFAIL; + } + if (device_info.connected_pak == ultramodern::input::Pak::None) { + return PFS_ERR_NOPACK; + } + if (device_info.connected_pak != ultramodern::input::Pak::ControllerPak) { + return PFS_ERR_DEVICE; + } + + pfs_header_alloc(); + return 0; +} + +extern "C" s32 osPfsInitPak(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int channel) { + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + + const auto status = __osPfsGetStatus(PASS_RDRAM mq_, channel); + if (status != 0) { + return status; + } + + pfs->queue = mq_; + pfs->channel = channel; + pfs->status = 0; + + const s32 ret = osPfsChecker(PASS_RDRAM pfs_); + pfs->status |= PFS_INITIALIZED; + return 0; +} + +extern "C" s32 osPfsRepairId(RDRAM_ARG PTR(OSPfs) pfs) { + return 0; +} + +extern "C" s32 osPfsInit(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int channel) { + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + + const auto status = __osPfsGetStatus(PASS_RDRAM mq_, channel); + if (status != 0) { + return status; + } + + pfs->queue = mq_; + pfs->channel = channel; + pfs->status = 0; + pfs->activebank = -1; + + const s32 ret = osPfsChecker(PASS_RDRAM pfs_); + pfs->status |= PFS_INITIALIZED; + return 0; +} + +extern "C" s32 osPfsReFormat(RDRAM_ARG PTR(OSPfs) pfs, PTR(OSMesgQueue) mq_, int channel) { + return 0; +} + +extern "C" s32 osPfsChecker(RDRAM_ARG PTR(OSPfs) pfs) { + return 0; +} + +extern "C" s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, int nbytes, PTR(s32) file_no_) { + s32* file_no = TO_PTR(s32, file_no_); + + if ((company_code == 0) || (game_code == 0)) { + return PFS_ERR_INVALID; + } + + /* Search for a free slot */ + u8 free_file_index = 0; + for (size_t i = 0; i < MAX_FILES; i++) { + pfs_header_t hdr{}; + pfs_header_read(hdr, i); + + if ((hdr.company_code == 0) || (hdr.game_code == 0)) { + free_file_index = i; + break; + } + } + + if (free_file_index == MAX_FILES) { + return PFS_DIR_FULL; + } + + pfs_header_write(nbytes, game_code, company_code, ext_name, game_name, free_file_index); + + /* Create empty file */ + + pfs_state_t pak; + + const auto filename = pfs_file_path(free_file_index); + pak.file.open(filename, std::ios::binary | std::ios::out | std::ios::trunc); + + nbytes = (nbytes + 31) & ~31; + + std::vector zero_block(nbytes, 0); + pak.file.seekp(0, std::ios::beg); + pak.file.write((char*)zero_block.data(), nbytes); + pak.file.close(); + + *file_no = free_file_index; + return 0; +} + +extern "C" s32 osPfsFindFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, PTR(s32) file_no_) { + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + s32* file_no = TO_PTR(s32, file_no_); + + for (size_t i = 0; i < MAX_FILES; i++) { + pfs_header_t hdr{}; + pfs_header_read(hdr, i); + + if ((hdr.company_code == 0) || (hdr.game_code == 0)) { + continue; + } else { + if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { + for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { + if (game_name[i] != hdr.game_name[i]) { + break; + } + } + for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { + if (ext_name[i] != hdr.ext_name[i]) { + break; + } + } + // File found + *file_no = i; + return 0; + } + } + } + + return PFS_ERR_INVALID; +} + +extern "C" s32 osPfsDeleteFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 game_code, u8* game_name, u8* ext_name) { + if (company_code == 0 || game_code == 0) { + return PFS_ERR_INVALID; + } + + pfs_state_t pak; + for (int i = 0; i < MAX_FILES; i++) { + pfs_header_t hdr{}; + pfs_header_read(hdr, i); + + if ((hdr.company_code == 0) || (hdr.game_code == 0)) { + continue; + } else { + if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { + int gncount = 0; + int encount = 0; + + for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { + if (game_name[i] == hdr.game_name[i]) { + gncount++; + } + } + for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { + if (ext_name[i] == hdr.ext_name[i]) { + encount++; + } + } + if ((gncount != 16) || encount != 4) { + continue; + } + + // File found + pak.header.open(pfs_header_path(), std::ios::binary | std::ios::in | std::ios::out); + + if (!pak.header.good()) { + assert(false); + } + if (!pak.header.is_open()) { + assert(false); + } + + u32 seek = i * sizeof(OSPfsState); + + // Zero out the header for this file. + std::vector zero_block(sizeof(OSPfsState), 0); + pak.header.seekp(seek + 0x0, std::ios::beg); + pak.header.write((char*)zero_block.data(), sizeof(OSPfsState)); + pak.header.close(); + + std::filesystem::remove(pfs_file_path(i)); + return 0; + } + } + } + + return PFS_ERR_INVALID; +} + +extern "C" s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, u8 flag, int offset, int size_in_bytes, u8* data_buffer) { + pfs_state_t pak; + + const auto filename = pfs_file_path(file_no); + pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); + + if (!std::filesystem::exists(filename)) { + return PFS_ERR_INVALID; + } + if (!pak.file.good()) { + return PFS_ERR_INVALID; + } + if (!pak.file.is_open()) { + return PFS_ERR_INVALID; + } + + std::vector swap_buffer(size_in_bytes); + if (flag == 0) { + pak.file.seekg(offset, std::ios::beg); + pak.file.read((char*)swap_buffer.data(), size_in_bytes); + ByteSwapCopy(data_buffer, swap_buffer.data(), size_in_bytes); + } else { + ByteSwapCopy(swap_buffer.data(), data_buffer, size_in_bytes); + pak.file.seekp(offset, std::ios::beg); + pak.file.write((char*)swap_buffer.data(), size_in_bytes); + } + + pak.file.close(); + return 0; +} + +extern "C" s32 osPfsFileState(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, PTR(OSPfsState) state_) { + OSPfsState *state = TO_PTR(OSPfsState, state_); + + // should pass the state of the requested file_no to the incoming state pointer, + // games call this function 16 times, once per file + // fills the incoming state with the information inside the header of the pak. + + const auto filename = pfs_file_path(file_no); + if (!std::filesystem::exists(filename)) { + return PFS_ERR_INVALID; + } + + /* Read game info from pak */ + pfs_header_t hdr{}; + pfs_header_read(hdr, file_no); + + state->file_size = hdr.file_size; + state->company_code = hdr.company_code; + state->game_code = hdr.game_code; + + for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { + state->game_name[i] = hdr.game_name[i]; + } + for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { + state->ext_name[i] = hdr.ext_name[i]; + } + + return 0; +} + +extern "C" s32 osPfsGetLabel(RDRAM_ARG PTR(OSPfs) pfs_, u8* label, PTR(int) len_) { + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + int* len = TO_PTR(int, len_); + + if (label == NULL) { + return PFS_ERR_INVALID; + } +// if (__osCheckId(pfs) == PFS_ERR_NEW_PACK) { +// return PFS_ERR_NEW_PACK; +// } + + int i; + for (i = 0; i < ARRLEN(pfs->label); i++) { + if (pfs->label[i] == 0) { + break; + } + *label++ = pfs->label[i]; + } + *len = i; + return 0; +} + +extern "C" s32 osPfsSetLabel(RDRAM_ARG PTR(OSPfs) pfs_, u8* label) { + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + + if (label != NULL) { + for (int i = 0; i < ARRLEN(pfs->label); i++) { + if (*label == 0) { + break; + } + pfs->label[i] = *label++; + } + } + return 0; +} + +extern "C" s32 osPfsIsPlug(RDRAM_ARG PTR(OSMesgQueue) mq_, u8* pattern) { + u8 bits = 0; + + for (int channel = 0; channel < ultramodern::get_max_controllers(); channel++) { + if (__osPfsGetStatus(PASS_RDRAM mq_, channel) == 0) { + bits |= (1 << channel); + } + } + *pattern = bits; + return 0; +} + +extern "C" s32 osPfsFreeBlocks(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) bytes_not_used_) { + OSPfs *pfs = TO_PTR(OSPfs, pfs_); + s32 *bytes_not_used = TO_PTR(s32, bytes_not_used_); + + s32 bytes_used = 0; + for (size_t i = 0; i < MAX_FILES; i++) { + pfs_header_t hdr{}; + pfs_header_read(hdr, i); + + if ((hdr.company_code != 0) && (hdr.game_code != 0)) { + bytes_used += hdr.file_size >> 8; + } + } + + *bytes_not_used = (123 - bytes_used) << 8; + return 0; +} + +extern "C" s32 osPfsNumFiles(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) max_files_, PTR(s32) files_used_) { + OSPfs *pfs = TO_PTR(OSPfs, pfs_); + s32 *max_files = TO_PTR(s32, max_files_); + s32 *files_used = TO_PTR(s32, files_used_); + + u8 num_files = 0; + for (size_t i = 0; i < MAX_FILES; i++) { + pfs_header_t hdr{}; + pfs_header_read(hdr, i); + + if ((hdr.company_code != 0) && (hdr.game_code != 0)) { + num_files++; + } + } + + *max_files = MAX_FILES; + *files_used = num_files; + return 0; +} + diff --git a/ultramodern/src/controller_pak.hpp b/ultramodern/src/pfs.hpp similarity index 62% rename from ultramodern/src/controller_pak.hpp rename to ultramodern/src/pfs.hpp index 472bd0e..46891c2 100644 --- a/ultramodern/src/controller_pak.hpp +++ b/ultramodern/src/pfs.hpp @@ -2,27 +2,42 @@ #define __ULTRAMODERN_PFS_HPP__ #include +#include -typedef struct ControllerPak { +typedef struct pfs_state { std::fstream header; std::fstream file; -} ControllerPak; +} pfs_state_t; -typedef struct ControllerPakHdr { +typedef struct pfs_header { int file_size; u32 game_code; u16 company_code; u8 ext_name[4]; u8 game_name[16]; -} ControllerPakHdr; +} pfs_header_t; -// extern std::filesystem::path config_path; -// const std::u8string save_folder = u8"saves"; -// std::filesystem::path save_folder_path = config_path / save_folder; +inline std::filesystem::path pfs_header_path() { + const auto filename = "controllerpak_header.bin"; + return ultramodern::get_save_base_path() / filename; +} -void Pfs_PakHeader_Write(int file_size, u32 game_code, u16 company_code, const u8* ext_name, const u8* game_name, u8 file_index) { - ControllerPak pak; - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); +inline std::filesystem::path pfs_file_path(size_t fileno) { + const auto filename = std::format("controllerpak_file_{}.bin", fileno); + return ultramodern::get_save_base_path() / filename; +} + +void pfs_header_alloc() { + pfs_state_t pak; + if (!std::filesystem::exists(pfs_header_path())) { + pak.header.open(pfs_header_path(), std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); + pak.header.close(); + } +} + +void pfs_header_write(int file_size, u32 game_code, u16 company_code, const u8* ext_name, const u8* game_name, u8 file_index) { + pfs_state_t pak; + pak.header.open(pfs_header_path(), std::ios::binary | std::ios::out); if (!pak.header.good()) { assert(false); @@ -53,13 +68,13 @@ void Pfs_PakHeader_Write(int file_size, u32 game_code, u16 company_code, const u pak.header.close(); } -void Pfs_PakHeader_Write(const ControllerPakHdr& hdr, u8 file_index) { - Pfs_PakHeader_Write(hdr.file_size, hdr.game_code, hdr.company_code, hdr.ext_name, hdr.game_name, file_index); +void pfs_header_write(const pfs_header_t& hdr, u8 file_index) { + pfs_header_write(hdr.file_size, hdr.game_code, hdr.company_code, hdr.ext_name, hdr.game_name, file_index); } -void Pfs_PakHeader_Read(ControllerPakHdr& hdr, u8 file_index) { - ControllerPak pak; - pak.header.open("controllerPak_header.sav", std::ios::binary | std::ios::in | std::ios::out); +void pfs_header_read(pfs_header_t& hdr, u8 file_index) { + pfs_state_t pak; + pak.header.open(pfs_header_path(), std::ios::binary | std::ios::in); if (!pak.header.good()) { assert(false); @@ -106,8 +121,8 @@ void Pfs_ByteSwapFile(u8* buffer, size_t size) { } } -void ByteSwapCopy(uint8_t* dst, uint8_t* src, size_t size_bytes) { - for (size_t i = 0; i < size_bytes; i++) { +void ByteSwapCopy(uint8_t* dst, const uint8_t* src, size_t nbytes) { + for (size_t i = 0; i < nbytes; i++) { dst[i] = src[i ^ 3]; } } diff --git a/ultramodern/src/save.cpp b/ultramodern/src/save.cpp index 74f6927..9a216f4 100644 --- a/ultramodern/src/save.cpp +++ b/ultramodern/src/save.cpp @@ -9,6 +9,7 @@ struct { std::vector save_buffer; std::thread saving_thread; + std::filesystem::path save_base_path; std::filesystem::path save_file_path; moodycamel::LightweightSemaphore write_sempahore; // Used to tell the saving thread that a file swap is pending. @@ -54,16 +55,20 @@ bool ultramodern::flashram_allowed() { save_type == SaveType::AllowAll; } +std::filesystem::path ultramodern::get_save_base_path() { + return save_context.save_base_path; +} + std::filesystem::path ultramodern::get_save_file_path() { return save_context.save_file_path; } void ultramodern::set_save_file_path(const std::u8string& subfolder, const std::u8string& name) { - std::filesystem::path save_folder_path = config_path / save_folder; + save_context.save_base_path = config_path / save_folder; if (!subfolder.empty()) { - save_folder_path = save_folder_path / subfolder; + save_context.save_base_path = save_context.save_base_path / subfolder; } - save_context.save_file_path = save_folder_path / (name + u8".bin"); + save_context.save_file_path = save_context.save_base_path / (name + u8".bin"); } void update_save_file() { From 7081cbb0026f73bb335701b9d34d0c01b4e9de44 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Thu, 22 Jan 2026 23:29:57 -0800 Subject: [PATCH 41/49] initialize other OSPfs fields --- ultramodern/src/pfs.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/ultramodern/src/pfs.cpp b/ultramodern/src/pfs.cpp index a8f2c1c..f76741f 100644 --- a/ultramodern/src/pfs.cpp +++ b/ultramodern/src/pfs.cpp @@ -6,6 +6,7 @@ #include "pfs.hpp" #define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) +#define DEF_DIR_PAGES 2 #define MAX_FILES 16 /* ControllerPak */ @@ -26,6 +27,24 @@ static s32 __osPfsGetStatus(RDRAM_ARG PTR(OSMesgQueue) queue, int channel) { return 0; } +static s32 __osGetId(RDRAM_ARG PTR(OSPfs) pfs_) { + OSPfs* pfs = TO_PTR(OSPfs, pfs_); + + // we don't implement the real filesystem, so just mimic initialization + pfs->version = 0; + pfs->banks = 1; + pfs->activebank = 0; + pfs->inode_start_page = 1 + DEF_DIR_PAGES + (2 * pfs->banks); + pfs->dir_size = DEF_DIR_PAGES * PFS_ONE_PAGE; + pfs->inode_table = 1 * PFS_ONE_PAGE; + pfs->minode_table = (1 + pfs->banks) * PFS_ONE_PAGE; + pfs->dir_table = pfs->minode_table + (pfs->banks * PFS_ONE_PAGE); + + std::memset(pfs->id, 0, ARRLEN(pfs->id)); + std::memset(pfs->label, 0, ARRLEN(pfs->label)); + return 0; +} + extern "C" s32 osPfsInitPak(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int channel) { OSPfs* pfs = TO_PTR(OSPfs, pfs_); @@ -37,10 +56,11 @@ extern "C" s32 osPfsInitPak(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int pfs->queue = mq_; pfs->channel = channel; pfs->status = 0; + __osGetId(PASS_RDRAM pfs_); const s32 ret = osPfsChecker(PASS_RDRAM pfs_); pfs->status |= PFS_INITIALIZED; - return 0; + return ret; } extern "C" s32 osPfsRepairId(RDRAM_ARG PTR(OSPfs) pfs) { @@ -59,10 +79,11 @@ extern "C" s32 osPfsInit(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSPfs) pfs_, int ch pfs->channel = channel; pfs->status = 0; pfs->activebank = -1; + __osGetId(PASS_RDRAM pfs_); const s32 ret = osPfsChecker(PASS_RDRAM pfs_); pfs->status |= PFS_INITIALIZED; - return 0; + return ret; } extern "C" s32 osPfsReFormat(RDRAM_ARG PTR(OSPfs) pfs, PTR(OSMesgQueue) mq_, int channel) { From 15ff1c050751e1ffc5e3f5a97139d7cdf7f4bfc3 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Thu, 22 Jan 2026 23:30:56 -0800 Subject: [PATCH 42/49] rename --- librecomp/CMakeLists.txt | 2 +- librecomp/src/{pak.cpp => pfs.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename librecomp/src/{pak.cpp => pfs.cpp} (100%) diff --git a/librecomp/CMakeLists.txt b/librecomp/CMakeLists.txt index 60541ad..5e000ed 100644 --- a/librecomp/CMakeLists.txt +++ b/librecomp/CMakeLists.txt @@ -22,8 +22,8 @@ add_library(librecomp STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_manifest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_config_api.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/patcher.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/pfs.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/pi.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/print.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/recomp.cpp" diff --git a/librecomp/src/pak.cpp b/librecomp/src/pfs.cpp similarity index 100% rename from librecomp/src/pak.cpp rename to librecomp/src/pfs.cpp From 315c69638b9a7887011580a4ae88a6cc716d7d7d Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Thu, 22 Jan 2026 23:54:32 -0800 Subject: [PATCH 43/49] remove byteswap in ultramodern --- ultramodern/src/pfs.cpp | 11 ++++------- ultramodern/src/pfs.hpp | 22 ---------------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/ultramodern/src/pfs.cpp b/ultramodern/src/pfs.cpp index f76741f..6ba363d 100644 --- a/ultramodern/src/pfs.cpp +++ b/ultramodern/src/pfs.cpp @@ -227,7 +227,7 @@ extern "C" s32 osPfsDeleteFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 return PFS_ERR_INVALID; } -extern "C" s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, u8 flag, int offset, int size_in_bytes, u8* data_buffer) { +extern "C" s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, u8 flag, int offset, int nbytes, u8* data_buffer) { pfs_state_t pak; const auto filename = pfs_file_path(file_no); @@ -243,15 +243,12 @@ extern "C" s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, u8 fla return PFS_ERR_INVALID; } - std::vector swap_buffer(size_in_bytes); - if (flag == 0) { + if (flag == PFS_READ) { pak.file.seekg(offset, std::ios::beg); - pak.file.read((char*)swap_buffer.data(), size_in_bytes); - ByteSwapCopy(data_buffer, swap_buffer.data(), size_in_bytes); + pak.file.read((char*)data_buffer, nbytes); } else { - ByteSwapCopy(swap_buffer.data(), data_buffer, size_in_bytes); pak.file.seekp(offset, std::ios::beg); - pak.file.write((char*)swap_buffer.data(), size_in_bytes); + pak.file.write((const char*)data_buffer, nbytes); } pak.file.close(); diff --git a/ultramodern/src/pfs.hpp b/ultramodern/src/pfs.hpp index 46891c2..5a83fea 100644 --- a/ultramodern/src/pfs.hpp +++ b/ultramodern/src/pfs.hpp @@ -105,27 +105,5 @@ void pfs_header_read(pfs_header_t& hdr, u8 file_index) { pak.header.close(); } -void Pfs_ByteSwapFile(u8* buffer, size_t size) { - uint8_t c0, c1, c2, c3; - - for (size_t i = 0; i < size; i += 4) { - c0 = buffer[i + 0]; - c1 = buffer[i + 1]; - c2 = buffer[i + 2]; - c3 = buffer[i + 3]; - - buffer[i + 3] = c0; - buffer[i + 2] = c1; - buffer[i + 1] = c2; - buffer[i + 0] = c3; - } -} - -void ByteSwapCopy(uint8_t* dst, const uint8_t* src, size_t nbytes) { - for (size_t i = 0; i < nbytes; i++) { - dst[i] = src[i ^ 3]; - } -} - #endif // __ULTRAMODERN_PFS_HPP__ From e47b14e418a5a1acf46313b006eddefd6f8cb1e3 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Fri, 23 Jan 2026 15:36:35 -0800 Subject: [PATCH 44/49] cleanup --- ultramodern/src/pfs.cpp | 246 ++++++++++++++++++++-------------------- ultramodern/src/pfs.hpp | 109 ------------------ 2 files changed, 121 insertions(+), 234 deletions(-) delete mode 100644 ultramodern/src/pfs.hpp diff --git a/ultramodern/src/pfs.cpp b/ultramodern/src/pfs.cpp index 6ba363d..a68e580 100644 --- a/ultramodern/src/pfs.cpp +++ b/ultramodern/src/pfs.cpp @@ -1,14 +1,93 @@ -#include -#include +#include #include - +#include +#include #include -#include "pfs.hpp" #define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) #define DEF_DIR_PAGES 2 #define MAX_FILES 16 +/* PFS Context */ + +inline std::filesystem::path pfs_header_path() { + const auto filename = "controllerpak_header.bin"; + return ultramodern::get_save_base_path() / filename; +} + +inline std::filesystem::path pfs_file_path(size_t file_no) { + const auto filename = std::format("controllerpak_file_{}.bin", file_no); + return ultramodern::get_save_base_path() / filename; +} + +inline bool pfs_header_alloc() { + if (!std::filesystem::exists(pfs_header_path())) { + std::ofstream out(pfs_header_path(), std::ios::binary | std::ios::out | std::ios::trunc); + return out.good(); + } + return true; +} + +inline bool pfs_header_write(int file_no, u32 file_size, u32 game_code, u16 company_code, u8* ext_name, u8* game_name) { + std::ofstream out(pfs_header_path(), std::ios::binary | std::ios::in); + if (out.is_open() && out.good()) { + u16 padding = 0; + out.seekp(file_no * sizeof(OSPfsState), std::ios::beg); + out.write((const char*)&file_size, 4); + out.write((const char*)&game_code, 4); + out.write((const char*)&company_code, 2); + out.write((const char*)&padding, 2); + out.write((const char*)ext_name, 4); + out.write((const char*)game_name, 16); + } + return out.good(); +} + +inline bool pfs_header_write(int file_no, const OSPfsState& hdr) { + return pfs_header_write(file_no, hdr.file_size, hdr.game_code, hdr.company_code, (u8*)hdr.ext_name, (u8*)hdr.game_name); +} + +inline bool pfs_header_read(int file_no, OSPfsState& hdr) { + std::ifstream in(pfs_header_path(), std::ios::binary | std::ios::in); + if (in.is_open() && in.good()) { + in.seekg(file_no * sizeof(OSPfsState), std::ios::beg); + in.read((char*)&hdr.file_size, 4); + in.read((char*)&hdr.game_code, 4); + in.read((char*)&hdr.company_code, 2); + in.read((char*)&hdr.pad_0A, 2); + in.read((char*)&hdr.ext_name[0], 4); + in.read((char*)&hdr.game_name[0], 16); + } + return in.good(); +} + +inline bool pfs_file_alloc(int file_no, int nbytes) { + std::vector zero_block((nbytes + 31) & ~31, 0); + std::ofstream out(pfs_file_path(file_no), std::ios::binary | std::ios::out | std::ios::trunc); + if (out.is_open() && out.good()) { + out.write((const char*)zero_block.data(), zero_block.size()); + } + return out.good(); +} + +inline bool pfs_file_write(int file_no, int offset, const char* data_buffer, int nbytes) { + std::ofstream out(pfs_file_path(file_no), std::ios::binary | std::ios::out); + if (out.is_open() && out.good()) { + out.seekp(offset, std::ios::beg); + out.write((const char*)data_buffer, nbytes); + } + return out.good(); +} + +inline bool pfs_file_read(int file_no, int offset, char* data_buffer, int nbytes) { + std::ifstream in(pfs_file_path(file_no), std::ios::binary | std::ios::in); + if (in.is_open() && in.good()) { + in.seekg(offset, std::ios::beg); + in.read((char*)data_buffer, nbytes); + } + return in.good(); +} + /* ControllerPak */ static s32 __osPfsGetStatus(RDRAM_ARG PTR(OSMesgQueue) queue, int channel) { @@ -101,11 +180,10 @@ extern "C" s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 return PFS_ERR_INVALID; } - /* Search for a free slot */ u8 free_file_index = 0; for (size_t i = 0; i < MAX_FILES; i++) { - pfs_header_t hdr{}; - pfs_header_read(hdr, i); + OSPfsState hdr{}; + pfs_header_read(i, hdr); if ((hdr.company_code == 0) || (hdr.game_code == 0)) { free_file_index = i; @@ -116,23 +194,12 @@ extern "C" s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 if (free_file_index == MAX_FILES) { return PFS_DIR_FULL; } - - pfs_header_write(nbytes, game_code, company_code, ext_name, game_name, free_file_index); - - /* Create empty file */ - - pfs_state_t pak; - - const auto filename = pfs_file_path(free_file_index); - pak.file.open(filename, std::ios::binary | std::ios::out | std::ios::trunc); - - nbytes = (nbytes + 31) & ~31; - - std::vector zero_block(nbytes, 0); - pak.file.seekp(0, std::ios::beg); - pak.file.write((char*)zero_block.data(), nbytes); - pak.file.close(); - + if (!pfs_header_write(free_file_index, nbytes, game_code, company_code, ext_name, game_name)) { + return PFS_ERR_INVALID; + } + if (!pfs_file_alloc(free_file_index, nbytes)) { + return PFS_ERR_INVALID; + } *file_no = free_file_index; return 0; } @@ -141,31 +208,23 @@ extern "C" s32 osPfsFindFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 ga OSPfs* pfs = TO_PTR(OSPfs, pfs_); s32* file_no = TO_PTR(s32, file_no_); - for (size_t i = 0; i < MAX_FILES; i++) { - pfs_header_t hdr{}; - pfs_header_read(hdr, i); + if (company_code == 0 || game_code == 0) { + return PFS_ERR_INVALID; + } - if ((hdr.company_code == 0) || (hdr.game_code == 0)) { - continue; - } else { - if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { - for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { - if (game_name[i] != hdr.game_name[i]) { - break; - } - } - for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { - if (ext_name[i] != hdr.ext_name[i]) { - break; - } - } - // File found + for (size_t i = 0; i < MAX_FILES; i++) { + OSPfsState hdr{}; + pfs_header_read(i, hdr); + + if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { + const auto gn_match = !std::memcmp(game_name, hdr.game_name, sizeof(hdr.game_name)); + const auto en_match = !std::memcmp(ext_name, hdr.ext_name, sizeof(hdr.ext_name)); + if (gn_match && en_match) { *file_no = i; return 0; } } } - return PFS_ERR_INVALID; } @@ -174,114 +233,51 @@ extern "C" s32 osPfsDeleteFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 return PFS_ERR_INVALID; } - pfs_state_t pak; for (int i = 0; i < MAX_FILES; i++) { - pfs_header_t hdr{}; - pfs_header_read(hdr, i); - - if ((hdr.company_code == 0) || (hdr.game_code == 0)) { - continue; - } else { - if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { - int gncount = 0; - int encount = 0; - - for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { - if (game_name[i] == hdr.game_name[i]) { - gncount++; - } - } - for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { - if (ext_name[i] == hdr.ext_name[i]) { - encount++; - } - } - if ((gncount != 16) || encount != 4) { - continue; - } - - // File found - pak.header.open(pfs_header_path(), std::ios::binary | std::ios::in | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - - u32 seek = i * sizeof(OSPfsState); - - // Zero out the header for this file. - std::vector zero_block(sizeof(OSPfsState), 0); - pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((char*)zero_block.data(), sizeof(OSPfsState)); - pak.header.close(); + OSPfsState hdr{}; + pfs_header_read(i, hdr); + if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { + const auto gn_match = !std::memcmp(game_name, hdr.game_name, sizeof(hdr.game_name)); + const auto en_match = !std::memcmp(ext_name, hdr.ext_name, sizeof(hdr.ext_name)); + if (gn_match && en_match) { + pfs_header_write(i, OSPfsState{}); std::filesystem::remove(pfs_file_path(i)); return 0; } } } - return PFS_ERR_INVALID; } extern "C" s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, u8 flag, int offset, int nbytes, u8* data_buffer) { - pfs_state_t pak; - - const auto filename = pfs_file_path(file_no); - pak.file.open(filename, std::ios::binary | std::ios::in | std::ios::out); - - if (!std::filesystem::exists(filename)) { + if (!std::filesystem::exists(pfs_file_path(file_no))) { return PFS_ERR_INVALID; } - if (!pak.file.good()) { + else if ((flag == PFS_READ) && !pfs_file_read(file_no, offset, (char*)data_buffer, nbytes)) { return PFS_ERR_INVALID; } - if (!pak.file.is_open()) { + else if ((flag == PFS_WRITE) && !pfs_file_write(file_no, offset, (const char*)data_buffer, nbytes)) { return PFS_ERR_INVALID; } - - if (flag == PFS_READ) { - pak.file.seekg(offset, std::ios::beg); - pak.file.read((char*)data_buffer, nbytes); - } else { - pak.file.seekp(offset, std::ios::beg); - pak.file.write((const char*)data_buffer, nbytes); - } - - pak.file.close(); return 0; } extern "C" s32 osPfsFileState(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, PTR(OSPfsState) state_) { OSPfsState *state = TO_PTR(OSPfsState, state_); - // should pass the state of the requested file_no to the incoming state pointer, - // games call this function 16 times, once per file - // fills the incoming state with the information inside the header of the pak. - - const auto filename = pfs_file_path(file_no); - if (!std::filesystem::exists(filename)) { + if (!std::filesystem::exists(pfs_file_path(file_no))) { return PFS_ERR_INVALID; } - /* Read game info from pak */ - pfs_header_t hdr{}; - pfs_header_read(hdr, file_no); + OSPfsState hdr{}; + pfs_header_read(file_no, hdr); state->file_size = hdr.file_size; state->company_code = hdr.company_code; state->game_code = hdr.game_code; - - for (size_t i = 0; i < ARRLEN(hdr.game_name); i++) { - state->game_name[i] = hdr.game_name[i]; - } - for (size_t i = 0; i < ARRLEN(hdr.ext_name); i++) { - state->ext_name[i] = hdr.ext_name[i]; - } - + std::memcpy(state->game_name, hdr.game_name, sizeof(hdr.game_name)); + std::memcpy(state->ext_name, hdr.ext_name, sizeof(hdr.ext_name)); return 0; } @@ -339,8 +335,8 @@ extern "C" s32 osPfsFreeBlocks(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) bytes_not_use s32 bytes_used = 0; for (size_t i = 0; i < MAX_FILES; i++) { - pfs_header_t hdr{}; - pfs_header_read(hdr, i); + OSPfsState hdr{}; + pfs_header_read(i, hdr); if ((hdr.company_code != 0) && (hdr.game_code != 0)) { bytes_used += hdr.file_size >> 8; @@ -358,8 +354,8 @@ extern "C" s32 osPfsNumFiles(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) max_files_, PTR u8 num_files = 0; for (size_t i = 0; i < MAX_FILES; i++) { - pfs_header_t hdr{}; - pfs_header_read(hdr, i); + OSPfsState hdr{}; + pfs_header_read(i, hdr); if ((hdr.company_code != 0) && (hdr.game_code != 0)) { num_files++; diff --git a/ultramodern/src/pfs.hpp b/ultramodern/src/pfs.hpp deleted file mode 100644 index 5a83fea..0000000 --- a/ultramodern/src/pfs.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef __ULTRAMODERN_PFS_HPP__ -#define __ULTRAMODERN_PFS_HPP__ - -#include -#include - -typedef struct pfs_state { - std::fstream header; - std::fstream file; -} pfs_state_t; - -typedef struct pfs_header { - int file_size; - u32 game_code; - u16 company_code; - u8 ext_name[4]; - u8 game_name[16]; -} pfs_header_t; - -inline std::filesystem::path pfs_header_path() { - const auto filename = "controllerpak_header.bin"; - return ultramodern::get_save_base_path() / filename; -} - -inline std::filesystem::path pfs_file_path(size_t fileno) { - const auto filename = std::format("controllerpak_file_{}.bin", fileno); - return ultramodern::get_save_base_path() / filename; -} - -void pfs_header_alloc() { - pfs_state_t pak; - if (!std::filesystem::exists(pfs_header_path())) { - pak.header.open(pfs_header_path(), std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - pak.header.close(); - } -} - -void pfs_header_write(int file_size, u32 game_code, u16 company_code, const u8* ext_name, const u8* game_name, u8 file_index) { - pfs_state_t pak; - pak.header.open(pfs_header_path(), std::ios::binary | std::ios::out); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - - /* Set file parameters to header */ - u32 seek = file_index * 0x20; - - // file_size - pak.header.seekp(seek + 0x0, std::ios::beg); - pak.header.write((const char*)&file_size, 4); - // game_code - pak.header.seekp(seek + 0x4, std::ios::beg); - pak.header.write((const char*)&game_code, 4); - // company_code - pak.header.seekp(seek + 0x08, std::ios::beg); - pak.header.write((const char*)&company_code, 2); - // ext_name - pak.header.seekp(seek + 0xC, std::ios::beg); - pak.header.write((const char*)ext_name, 4); - // game_name - pak.header.seekp(seek + 0x10, std::ios::beg); - pak.header.write((const char*)game_name, 16); - - pak.header.close(); -} - -void pfs_header_write(const pfs_header_t& hdr, u8 file_index) { - pfs_header_write(hdr.file_size, hdr.game_code, hdr.company_code, hdr.ext_name, hdr.game_name, file_index); -} - -void pfs_header_read(pfs_header_t& hdr, u8 file_index) { - pfs_state_t pak; - pak.header.open(pfs_header_path(), std::ios::binary | std::ios::in); - - if (!pak.header.good()) { - assert(false); - } - if (!pak.header.is_open()) { - assert(false); - } - - /* Set file parameters to header */ - u32 seek = file_index * sizeof(OSPfsState); - - // file_size - pak.header.seekg(seek + 0x0, std::ios::beg); - pak.header.read((char*)&hdr.file_size, 4); - // game_code - pak.header.seekg(seek + 0x4, std::ios::beg); - pak.header.read((char*)&hdr.game_code, 4); - // company_code - pak.header.seekg(seek + 0x08, std::ios::beg); - pak.header.read((char*)&hdr.company_code, 2); - // ext_name - pak.header.seekg(seek + 0xC, std::ios::beg); - pak.header.read((char*)hdr.ext_name, 4); - // game_name - pak.header.seekg(seek + 0x10, std::ios::beg); - pak.header.read((char*)hdr.game_name, 16); - - pak.header.close(); -} - -#endif // __ULTRAMODERN_PFS_HPP__ - From 7b9b1722b91641b790b5feea6cf02474357dcb89 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Fri, 23 Jan 2026 16:20:26 -0800 Subject: [PATCH 45/49] remove usage --- ultramodern/src/pfs.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultramodern/src/pfs.cpp b/ultramodern/src/pfs.cpp index a68e580..eb68d14 100644 --- a/ultramodern/src/pfs.cpp +++ b/ultramodern/src/pfs.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -16,7 +15,7 @@ inline std::filesystem::path pfs_header_path() { } inline std::filesystem::path pfs_file_path(size_t file_no) { - const auto filename = std::format("controllerpak_file_{}.bin", file_no); + const auto filename = "controllerpak_file_" + std::to_string(file_no) + ".bin", file_no); return ultramodern::get_save_base_path() / filename; } From 584494be425a589e00b846c3cc769e9753ae5928 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Fri, 23 Jan 2026 16:26:28 -0800 Subject: [PATCH 46/49] fix lazy mistake --- ultramodern/src/pfs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultramodern/src/pfs.cpp b/ultramodern/src/pfs.cpp index eb68d14..c731956 100644 --- a/ultramodern/src/pfs.cpp +++ b/ultramodern/src/pfs.cpp @@ -15,7 +15,7 @@ inline std::filesystem::path pfs_header_path() { } inline std::filesystem::path pfs_file_path(size_t file_no) { - const auto filename = "controllerpak_file_" + std::to_string(file_no) + ".bin", file_no); + const auto filename = "controllerpak_file_" + std::to_string(file_no) + ".bin"; return ultramodern::get_save_base_path() / filename; } From 7f77d1857ec3536ec67797c66f6c9a760099509b Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sat, 24 Jan 2026 13:58:36 -0800 Subject: [PATCH 47/49] fix byteswapping --- ultramodern/include/ultramodern/ultra64.h | 15 +-- ultramodern/src/pfs.cpp | 121 ++++++++++++---------- 2 files changed, 75 insertions(+), 61 deletions(-) diff --git a/ultramodern/include/ultramodern/ultra64.h b/ultramodern/include/ultramodern/ultra64.h index aee0c19..7ca579e 100644 --- a/ultramodern/include/ultramodern/ultra64.h +++ b/ultramodern/include/ultramodern/ultra64.h @@ -289,12 +289,15 @@ typedef struct { } OSPfs; typedef struct { - /* 0x00 */ u32 file_size; /* bytes */ - /* 0x04 */ u32 game_code; - /* 0x0A */ char pad_0A[2]; - /* 0x08 */ u16 company_code; - /* 0x0C */ char ext_name[4]; - /* 0x10 */ char game_name[16]; + u32 file_size; + u32 game_code; + char ext_name_0[2]; // insane layout due to ext_name starting on halfword boundary + u16 company_code; + char game_name_0[2]; + char ext_name_1[2]; + char game_name_1[12]; + char padding[2]; + char game_name_2[2]; } OSPfsState; // size = 0x20 // Controller diff --git a/ultramodern/src/pfs.cpp b/ultramodern/src/pfs.cpp index c731956..5276c90 100644 --- a/ultramodern/src/pfs.cpp +++ b/ultramodern/src/pfs.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -9,6 +10,30 @@ /* PFS Context */ +struct pfs_header_t { // same layout as OSPfsState, but non-byteswapped + uint32_t file_size; + uint32_t game_code; + uint16_t company_code; + std::array ext_name; + std::array game_name; + uint16_t padding; + + pfs_header_t() = default; + pfs_header_t(uint32_t fs, uint32_t gc, uint16_t cc, const char* en, const char* gn) + : file_size{fs}, game_code{gc}, company_code{cc} { + std::memcpy(ext_name.data(), en, 4); + std::memcpy(game_name.data(), gn, 16); + } + inline bool valid() const { + return game_code != 0 && company_code != 0; + } + inline bool compare(uint32_t gcode, uint16_t ccode, const char* ename, const char* gname) const { + return game_code == gcode && company_code == ccode && + std::memcmp(ext_name.data(), ename, 4) == 0 && + std::memcmp(game_name.data(), gname, 14) == 0; + } +}; + inline std::filesystem::path pfs_header_path() { const auto filename = "controllerpak_header.bin"; return ultramodern::get_save_base_path() / filename; @@ -27,35 +52,30 @@ inline bool pfs_header_alloc() { return true; } -inline bool pfs_header_write(int file_no, u32 file_size, u32 game_code, u16 company_code, u8* ext_name, u8* game_name) { +inline bool pfs_header_write(int file_no, const pfs_header_t& hdr) { std::ofstream out(pfs_header_path(), std::ios::binary | std::ios::in); if (out.is_open() && out.good()) { - u16 padding = 0; - out.seekp(file_no * sizeof(OSPfsState), std::ios::beg); - out.write((const char*)&file_size, 4); - out.write((const char*)&game_code, 4); - out.write((const char*)&company_code, 2); - out.write((const char*)&padding, 2); - out.write((const char*)ext_name, 4); - out.write((const char*)game_name, 16); + out.seekp(file_no * sizeof(pfs_header_t), std::ios::beg); + out.write((const char*)&hdr.file_size, sizeof(hdr.file_size)); + out.write((const char*)&hdr.game_code, sizeof(hdr.game_code)); + out.write((const char*)&hdr.company_code, sizeof(hdr.company_code)); + out.write((const char*)&hdr.ext_name[0], hdr.ext_name.size()); + out.write((const char*)&hdr.game_name[0], hdr.game_name.size()); + out.write((const char*)&hdr.padding, sizeof(hdr.padding)); } return out.good(); } -inline bool pfs_header_write(int file_no, const OSPfsState& hdr) { - return pfs_header_write(file_no, hdr.file_size, hdr.game_code, hdr.company_code, (u8*)hdr.ext_name, (u8*)hdr.game_name); -} - -inline bool pfs_header_read(int file_no, OSPfsState& hdr) { +inline bool pfs_header_read(int file_no, pfs_header_t& hdr) { std::ifstream in(pfs_header_path(), std::ios::binary | std::ios::in); if (in.is_open() && in.good()) { - in.seekg(file_no * sizeof(OSPfsState), std::ios::beg); - in.read((char*)&hdr.file_size, 4); - in.read((char*)&hdr.game_code, 4); - in.read((char*)&hdr.company_code, 2); - in.read((char*)&hdr.pad_0A, 2); - in.read((char*)&hdr.ext_name[0], 4); - in.read((char*)&hdr.game_name[0], 16); + in.seekg(file_no * sizeof(pfs_header_t), std::ios::beg); + in.read((char*)&hdr.file_size, sizeof(hdr.file_size)); + in.read((char*)&hdr.game_code, sizeof(hdr.game_code)); + in.read((char*)&hdr.company_code, sizeof(hdr.company_code)); + in.read((char*)&hdr.ext_name[0], hdr.ext_name.size()); + in.read((char*)&hdr.game_name[0], hdr.game_name.size()); + in.read((char*)&hdr.padding, sizeof(hdr.padding)); } return in.good(); } @@ -175,16 +195,15 @@ extern "C" s32 osPfsChecker(RDRAM_ARG PTR(OSPfs) pfs) { extern "C" s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, int nbytes, PTR(s32) file_no_) { s32* file_no = TO_PTR(s32, file_no_); - if ((company_code == 0) || (game_code == 0)) { + if (company_code == 0 || game_code == 0) { return PFS_ERR_INVALID; } + pfs_header_t hdr{}; u8 free_file_index = 0; for (size_t i = 0; i < MAX_FILES; i++) { - OSPfsState hdr{}; pfs_header_read(i, hdr); - - if ((hdr.company_code == 0) || (hdr.game_code == 0)) { + if (!hdr.valid()) { free_file_index = i; break; } @@ -193,7 +212,7 @@ extern "C" s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 if (free_file_index == MAX_FILES) { return PFS_DIR_FULL; } - if (!pfs_header_write(free_file_index, nbytes, game_code, company_code, ext_name, game_name)) { + if (!pfs_header_write(free_file_index, pfs_header_t{(uint32_t)nbytes, game_code, company_code, (char*)ext_name, (char*)game_name})) { return PFS_ERR_INVALID; } if (!pfs_file_alloc(free_file_index, nbytes)) { @@ -204,24 +223,18 @@ extern "C" s32 osPfsAllocateFile(RDRAM_ARG PTR(OSPfs) pfs, u16 company_code, u32 } extern "C" s32 osPfsFindFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 game_code, u8* game_name, u8* ext_name, PTR(s32) file_no_) { - OSPfs* pfs = TO_PTR(OSPfs, pfs_); s32* file_no = TO_PTR(s32, file_no_); if (company_code == 0 || game_code == 0) { return PFS_ERR_INVALID; } + pfs_header_t hdr{}; for (size_t i = 0; i < MAX_FILES; i++) { - OSPfsState hdr{}; pfs_header_read(i, hdr); - - if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { - const auto gn_match = !std::memcmp(game_name, hdr.game_name, sizeof(hdr.game_name)); - const auto en_match = !std::memcmp(ext_name, hdr.ext_name, sizeof(hdr.ext_name)); - if (gn_match && en_match) { - *file_no = i; - return 0; - } + if (hdr.compare(game_code, company_code, (char*)ext_name, (char*)game_name)) { + *file_no = i; + return 0; } } return PFS_ERR_INVALID; @@ -232,18 +245,13 @@ extern "C" s32 osPfsDeleteFile(RDRAM_ARG PTR(OSPfs) pfs_, u16 company_code, u32 return PFS_ERR_INVALID; } + pfs_header_t hdr{}; for (int i = 0; i < MAX_FILES; i++) { - OSPfsState hdr{}; pfs_header_read(i, hdr); - - if ((game_code == hdr.game_code) && (company_code == hdr.company_code)) { - const auto gn_match = !std::memcmp(game_name, hdr.game_name, sizeof(hdr.game_name)); - const auto en_match = !std::memcmp(ext_name, hdr.ext_name, sizeof(hdr.ext_name)); - if (gn_match && en_match) { - pfs_header_write(i, OSPfsState{}); - std::filesystem::remove(pfs_file_path(i)); - return 0; - } + if (hdr.compare(game_code, company_code, (char*)ext_name, (char*)game_name)) { + pfs_header_write(i, pfs_header_t{}); + std::filesystem::remove(pfs_file_path(i)); + return 0; } } return PFS_ERR_INVALID; @@ -262,6 +270,10 @@ extern "C" s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, u8 fla return 0; } +inline void bswap_copy(char* dst, const char* src, int offset, int n) { + for (int i = 0; i < n; i++) { dst[(i + offset) ^ 3] = src[i + offset]; } +} + extern "C" s32 osPfsFileState(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, PTR(OSPfsState) state_) { OSPfsState *state = TO_PTR(OSPfsState, state_); @@ -269,14 +281,15 @@ extern "C" s32 osPfsFileState(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, PTR(OSPfsS return PFS_ERR_INVALID; } - OSPfsState hdr{}; + pfs_header_t hdr{}; pfs_header_read(file_no, hdr); state->file_size = hdr.file_size; state->company_code = hdr.company_code; state->game_code = hdr.game_code; - std::memcpy(state->game_name, hdr.game_name, sizeof(hdr.game_name)); - std::memcpy(state->ext_name, hdr.ext_name, sizeof(hdr.ext_name)); + + // FIXME OSPfsState layout is an absoute mess. giving up and byte swapping + bswap_copy((char*)state, (char*)&hdr, 10, 20); return 0; } @@ -333,11 +346,10 @@ extern "C" s32 osPfsFreeBlocks(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) bytes_not_use s32 *bytes_not_used = TO_PTR(s32, bytes_not_used_); s32 bytes_used = 0; + pfs_header_t hdr{}; for (size_t i = 0; i < MAX_FILES; i++) { - OSPfsState hdr{}; pfs_header_read(i, hdr); - - if ((hdr.company_code != 0) && (hdr.game_code != 0)) { + if (hdr.valid()) { bytes_used += hdr.file_size >> 8; } } @@ -352,11 +364,10 @@ extern "C" s32 osPfsNumFiles(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) max_files_, PTR s32 *files_used = TO_PTR(s32, files_used_); u8 num_files = 0; + pfs_header_t hdr{}; for (size_t i = 0; i < MAX_FILES; i++) { - OSPfsState hdr{}; pfs_header_read(i, hdr); - - if ((hdr.company_code != 0) && (hdr.game_code != 0)) { + if (hdr.valid()) { num_files++; } } From e2a57d9b2c180fbc2617f5c02eabcde262c5d9ba Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sat, 24 Jan 2026 16:23:57 -0800 Subject: [PATCH 48/49] add alignment checks and clear header before read --- ultramodern/src/pfs.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ultramodern/src/pfs.cpp b/ultramodern/src/pfs.cpp index 5276c90..097783e 100644 --- a/ultramodern/src/pfs.cpp +++ b/ultramodern/src/pfs.cpp @@ -4,6 +4,7 @@ #include #include +#define ALIGN_UP(x, align) (((x) + ((align) - 1)) & ~((align) - 1)) #define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) #define DEF_DIR_PAGES 2 #define MAX_FILES 16 @@ -21,16 +22,16 @@ struct pfs_header_t { // same layout as OSPfsState, but non-byteswapped pfs_header_t() = default; pfs_header_t(uint32_t fs, uint32_t gc, uint16_t cc, const char* en, const char* gn) : file_size{fs}, game_code{gc}, company_code{cc} { - std::memcpy(ext_name.data(), en, 4); - std::memcpy(game_name.data(), gn, 16); + std::memcpy(ext_name.data(), en, sizeof(ext_name)); + std::memcpy(game_name.data(), gn, sizeof(game_name)); } inline bool valid() const { return game_code != 0 && company_code != 0; } inline bool compare(uint32_t gcode, uint16_t ccode, const char* ename, const char* gname) const { return game_code == gcode && company_code == ccode && - std::memcmp(ext_name.data(), ename, 4) == 0 && - std::memcmp(game_name.data(), gname, 14) == 0; + std::memcmp(ext_name.data(), ename, sizeof(ext_name)) == 0 && + std::memcmp(game_name.data(), gname, sizeof(game_name)) == 0; } }; @@ -46,14 +47,16 @@ inline std::filesystem::path pfs_file_path(size_t file_no) { inline bool pfs_header_alloc() { if (!std::filesystem::exists(pfs_header_path())) { + std::vector zero_block(MAX_FILES * sizeof(pfs_header_t)); std::ofstream out(pfs_header_path(), std::ios::binary | std::ios::out | std::ios::trunc); + out.write(zero_block.data(), zero_block.size()); return out.good(); } return true; } inline bool pfs_header_write(int file_no, const pfs_header_t& hdr) { - std::ofstream out(pfs_header_path(), std::ios::binary | std::ios::in); + std::fstream out(pfs_header_path(), std::ios::binary | std::ios::out | std::ios::in); if (out.is_open() && out.good()) { out.seekp(file_no * sizeof(pfs_header_t), std::ios::beg); out.write((const char*)&hdr.file_size, sizeof(hdr.file_size)); @@ -67,6 +70,7 @@ inline bool pfs_header_write(int file_no, const pfs_header_t& hdr) { } inline bool pfs_header_read(int file_no, pfs_header_t& hdr) { + hdr = {}; // reset std::ifstream in(pfs_header_path(), std::ios::binary | std::ios::in); if (in.is_open() && in.good()) { in.seekg(file_no * sizeof(pfs_header_t), std::ios::beg); @@ -81,16 +85,16 @@ inline bool pfs_header_read(int file_no, pfs_header_t& hdr) { } inline bool pfs_file_alloc(int file_no, int nbytes) { - std::vector zero_block((nbytes + 31) & ~31, 0); + std::vector zero_block(ALIGN_UP(nbytes, PFS_ONE_PAGE * PFS_BLOCKSIZE)); std::ofstream out(pfs_file_path(file_no), std::ios::binary | std::ios::out | std::ios::trunc); if (out.is_open() && out.good()) { - out.write((const char*)zero_block.data(), zero_block.size()); + out.write(zero_block.data(), zero_block.size()); } return out.good(); } inline bool pfs_file_write(int file_no, int offset, const char* data_buffer, int nbytes) { - std::ofstream out(pfs_file_path(file_no), std::ios::binary | std::ios::out); + std::fstream out(pfs_file_path(file_no), std::ios::binary | std::ios::out | std::ios::in); if (out.is_open() && out.good()) { out.seekp(offset, std::ios::beg); out.write((const char*)data_buffer, nbytes); @@ -261,6 +265,11 @@ extern "C" s32 osPfsReadWriteFile(RDRAM_ARG PTR(OSPfs) pfs_, s32 file_no, u8 fla if (!std::filesystem::exists(pfs_file_path(file_no))) { return PFS_ERR_INVALID; } + + const auto file_size = std::filesystem::file_size(pfs_file_path(file_no)); + if (offset % PFS_BLOCKSIZE || nbytes % PFS_BLOCKSIZE || (offset + nbytes) > file_size) { + return PFS_ERR_INVALID; + } else if ((flag == PFS_READ) && !pfs_file_read(file_no, offset, (char*)data_buffer, nbytes)) { return PFS_ERR_INVALID; } From 8ca6a9f8d81b75730608af7820d2c5623673a5f1 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sat, 24 Jan 2026 17:45:42 -0800 Subject: [PATCH 49/49] no magic numbers --- ultramodern/src/pfs.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ultramodern/src/pfs.cpp b/ultramodern/src/pfs.cpp index 097783e..36ba29c 100644 --- a/ultramodern/src/pfs.cpp +++ b/ultramodern/src/pfs.cpp @@ -8,6 +8,7 @@ #define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) #define DEF_DIR_PAGES 2 #define MAX_FILES 16 +#define MAX_PAGES 123 // 128 total, 5 reserved for filesystem /* PFS Context */ @@ -15,8 +16,8 @@ struct pfs_header_t { // same layout as OSPfsState, but non-byteswapped uint32_t file_size; uint32_t game_code; uint16_t company_code; - std::array ext_name; - std::array game_name; + std::array ext_name; + std::array game_name; uint16_t padding; pfs_header_t() = default; @@ -354,16 +355,16 @@ extern "C" s32 osPfsFreeBlocks(RDRAM_ARG PTR(OSPfs) pfs_, PTR(s32) bytes_not_use OSPfs *pfs = TO_PTR(OSPfs, pfs_); s32 *bytes_not_used = TO_PTR(s32, bytes_not_used_); - s32 bytes_used = 0; + s32 pages_used = 0; pfs_header_t hdr{}; for (size_t i = 0; i < MAX_FILES; i++) { pfs_header_read(i, hdr); if (hdr.valid()) { - bytes_used += hdr.file_size >> 8; + pages_used += hdr.file_size >> 8; } } - *bytes_not_used = (123 - bytes_used) << 8; + *bytes_not_used = (MAX_PAGES - pages_used) << 8; return 0; }