diff --git a/librecomp/include/librecomp/game.hpp b/librecomp/include/librecomp/game.hpp index da26710..56480d6 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -109,6 +109,7 @@ namespace recomp { void start_game(const std::u8string& game_id); std::u8string current_game_id(); + std::string current_mod_game_id(); } #endif diff --git a/librecomp/src/mod_config_api.cpp b/librecomp/src/mod_config_api.cpp index 8551df2..9878a7a 100644 --- a/librecomp/src/mod_config_api.cpp +++ b/librecomp/src/mod_config_api.cpp @@ -63,6 +63,16 @@ void recomp_get_mod_version(uint8_t* rdram, recomp_context* ctx, size_t mod_inde *patch_out = version.patch; } +void recomp_change_save_file(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { + std::string name = _arg_string<0>(rdram, ctx); + std::u8string name_u8 = std::u8string{reinterpret_cast(name.data()), name.size()}; + + std::string mod_id = recomp::mods::get_mod_id(mod_index); + std::u8string mod_id_u8 = std::u8string{reinterpret_cast(mod_id.data()), mod_id.size()}; + + ultramodern::change_save_file(mod_id_u8, name_u8); +} + void recomp_free_config_string(uint8_t* rdram, recomp_context* ctx) { gpr str_rdram = (gpr)_arg<0, PTR(char)>(rdram, ctx); gpr offset = str_rdram - 0xFFFFFFFF80000000ULL; @@ -75,5 +85,6 @@ void recomp::mods::register_config_exports() { recomp::overlays::register_ext_base_export("recomp_get_config_double", recomp_get_config_double); recomp::overlays::register_ext_base_export("recomp_get_config_string", recomp_get_config_string); recomp::overlays::register_ext_base_export("recomp_get_mod_version", recomp_get_mod_version); + recomp::overlays::register_ext_base_export("recomp_change_save_file", recomp_change_save_file); recomp::overlays::register_base_export("recomp_free_config_string", recomp_free_config_string); } diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index ede0c63..aa5b17f 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -88,7 +88,12 @@ void recomp::do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_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; @@ -97,7 +102,15 @@ const std::u8string save_folder = u8"saves"; extern std::filesystem::path config_path; std::filesystem::path get_save_file_path() { - return config_path / save_folder / (std::u8string{recomp::current_game_id()} + u8".bin"); + 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() { @@ -143,6 +156,10 @@ void saving_thread_func(RDRAM_ARG1) { if (save_buffer_updated) { update_save_file(); } + + if (save_context.swap_file_pending_sempahore.tryWait()) { + save_context.swap_file_ready_sempahore.signal(); + } } } @@ -207,14 +224,12 @@ size_t get_save_size(recomp::SaveType save_type) { return 0; } -void ultramodern::init_saving(RDRAM_ARG1) { +void read_save_file() { std::filesystem::path save_file_path = get_save_file_path(); // Ensure the save file directory exists. std::filesystem::create_directories(save_file_path.parent_path()); - save_context.save_buffer.resize(get_save_size(recomp::get_save_type())); - // 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()) { @@ -224,10 +239,28 @@ void ultramodern::init_saving(RDRAM_ARG1) { // 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(); diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index 443d05f..ffc093f 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -490,6 +490,13 @@ std::u8string recomp::current_game_id() { return current_game.value(); }; +std::string recomp::current_mod_game_id() { + auto find_it = game_roms.find(current_game_id()); + const recomp::GameEntry& game_entry = find_it->second; + + return game_entry.mod_game_id; +} + void recomp::start_game(const std::u8string& game_id) { std::lock_guard lock(current_game_mutex); current_game = game_id; diff --git a/ultramodern/include/ultramodern/ultramodern.hpp b/ultramodern/include/ultramodern/ultramodern.hpp index 06df95f..838a8ca 100644 --- a/ultramodern/include/ultramodern/ultramodern.hpp +++ b/ultramodern/include/ultramodern/ultramodern.hpp @@ -37,6 +37,9 @@ void init_events(RDRAM_ARG renderer::WindowHandle window_handle); void init_timers(RDRAM_ARG1); void init_thread_cleanup(); +// Saving +void change_save_file(const std::u8string& subfolder, const std::u8string& name); + // Thread queues. constexpr PTR(PTR(OSThread)) running_queue = (PTR(PTR(OSThread)))-1;