Added a mechanism to swap save files at runtime and a corresponding mod API export (#101)
Some checks are pending
validate / ubuntu (x64, Release) (push) Waiting to run
validate / ubuntu (arm64, Debug) (push) Waiting to run
validate / ubuntu (arm64, Release) (push) Waiting to run
validate / ubuntu (x64, Debug) (push) Waiting to run
validate / windows (x64, Debug) (push) Waiting to run
validate / windows (x64, Release) (push) Waiting to run
validate / macos (arm64, Debug) (push) Waiting to run
validate / macos (arm64, Release) (push) Waiting to run
validate / macos (x64, Debug) (push) Waiting to run
validate / macos (x64, Release) (push) Waiting to run

This commit is contained in:
Wiseguy 2025-04-08 02:21:06 -04:00 committed by GitHub
parent cacb14fee5
commit 1f2a5838ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 59 additions and 4 deletions

View file

@ -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

View file

@ -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<const char8_t*>(name.data()), name.size()};
std::string mod_id = recomp::mods::get_mod_id(mod_index);
std::u8string mod_id_u8 = std::u8string{reinterpret_cast<const char8_t*>(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);
}

View file

@ -88,7 +88,12 @@ void recomp::do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr)
struct {
std::vector<char> 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();

View file

@ -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<std::mutex> lock(current_game_mutex);
current_game = game_id;

View file

@ -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;