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 6df55ee..1695765 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -7,17 +7,10 @@ #include "recomp.h" #include "rsp.hpp" #include +#include namespace recomp { - enum class SaveType { - None, - Eep4k, - Eep16k, - Sram, - Flashram, - AllowAll, // Allows all save types to work and reports eeprom size as 16kbit. - }; - + using SaveType = ultramodern::SaveType; struct GameEntry { uint64_t rom_hash; std::string internal_name; @@ -109,11 +102,6 @@ namespace recomp { /// void start(const Configuration& cfg); - SaveType get_save_type(); - 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/eep.cpp b/librecomp/src/eep.cpp index 048246e..94fedf6 100644 --- a/librecomp/src/eep.cpp +++ b/librecomp/src/eep.cpp @@ -1,20 +1,19 @@ #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; +static std::vector save_buffer; 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 +23,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 +32,17 @@ 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); + 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; } 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 +53,17 @@ 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); + 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; } 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 +72,17 @@ 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); + 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; } 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 +93,11 @@ 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); + 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 f46261c..9a877aa 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,11 @@ 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; +std::vector save_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 +29,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 +40,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 +54,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 +63,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 +99,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 +119,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 +135,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 +157,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 +165,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 +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 - 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); @@ -199,7 +200,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 43509ba..67cd86d 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -7,11 +7,12 @@ #include "recomp.h" #include "librecomp/addresses.hpp" #include "librecomp/game.hpp" -#include "librecomp/files.hpp" +#include #include #include static std::vector rom; +static std::vector save_buffer; bool recomp::is_rom_loaded() { return !rom.empty(); @@ -84,188 +85,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 @@ -277,12 +96,16 @@ 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(); } // read sram - 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); @@ -294,12 +117,16 @@ 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(); } // write sram - 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/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index ea17675..a166c85 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,8 +686,8 @@ 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::init_saving(rdram); + ultramodern::set_save_type(game_entry.save_type); + ultramodern::init_saving(rdram, recomp::current_game_id()); try { game_entry.entrypoint(rdram, context); @@ -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/librecomp/include/librecomp/files.hpp b/ultramodern/include/ultramodern/files.hpp similarity index 80% rename from librecomp/include/librecomp/files.hpp rename to ultramodern/include/ultramodern/files.hpp index 63e3e9d..497d6d9 100644 --- a/librecomp/include/librecomp/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 new file mode 100644 index 0000000..568b900 --- /dev/null +++ b/ultramodern/include/ultramodern/save.hpp @@ -0,0 +1,47 @@ +#ifndef __ULTRAMODERN_SAVE_HPP__ +#define __ULTRAMODERN_SAVE_HPP__ + +#include +#include + +namespace ultramodern { + enum class SaveType { + None, + Eep4k, + Eep16k, + Sram, + Flashram, + 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_ARG const std::u8string& name); + + 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_read_ptr(void *out, 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/librecomp/src/files.cpp b/ultramodern/src/files.cpp similarity index 74% rename from librecomp/src/files.cpp rename to ultramodern/src/files.cpp index af6f18d..0dd1a2f 100644 --- a/librecomp/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 new file mode 100644 index 0000000..74f6927 --- /dev/null +++ b/ultramodern/src/save.cpp @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include +#include +#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; + +// 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; + +void ultramodern::set_save_type(ultramodern::SaveType type) { + save_type = type; +} + +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 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; + } + save_context.save_file_path = save_folder_path / (name + u8".bin"); +} + +void update_save_file() { + bool saving_failed = false; + { + 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 }; + save_file.write(save_context.save_buffer.data(), save_context.save_buffer.size()); + } + else { + saving_failed = true; + } + } + if (!saving_failed) { + 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."); + } +} + +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 ultramodern::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 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 }; + std::memcpy(out, &save_context.save_buffer[offset], count); +} + +void ultramodern::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 ultramodern::get_save_size(ultramodern::SaveType save_type) { + switch (save_type) { + case ultramodern::SaveType::AllowAll: + case ultramodern::SaveType::Flashram: + return 0x20000; + case ultramodern::SaveType::Sram: + return 0x8000; + case ultramodern::SaveType::Eep16k: + return 0x800; + case ultramodern::SaveType::Eep4k: + return 0x200; + case ultramodern::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 = 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()); + } + 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_ARG const std::u8string& name) { + set_save_file_path(u8"", name); + + save_context.save_buffer.resize(get_save_size(ultramodern::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(); + } +} +