From 1c798b13d28ed806c0b39e6eddc2faf399824d6b Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Sat, 17 Jan 2026 22:10:40 -0800 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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