mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-02-03 20:26:10 +00:00
move save APIs without changes
This commit is contained in:
parent
1c798b13d2
commit
b42d7bd2e4
4 changed files with 205 additions and 193 deletions
|
|
@ -9,21 +9,12 @@
|
|||
#include <ultramodern/ultramodern.hpp>
|
||||
|
||||
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<uint8_t> (*decompression_routine)(std::span<const uint8_t> 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();
|
||||
|
|
|
|||
|
|
@ -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<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;
|
||||
|
||||
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
|
||||
|
|
|
|||
20
ultramodern/include/ultramodern/save.hpp
Normal file
20
ultramodern/include/ultramodern/save.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef __ULTRAMODERN_SAVE_HPP__
|
||||
#define __ULTRAMODERN_SAVE_HPP__
|
||||
|
||||
#include <ultramodern/ultramodern.hpp>
|
||||
|
||||
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__
|
||||
|
||||
184
ultramodern/src/save.cpp
Normal file
184
ultramodern/src/save.cpp
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
#include <ultramodern/save.hpp>
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Reference in a new issue