mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-04-28 04:51:44 +00:00
Merge branch 'save_api' into controller_pak
This commit is contained in:
commit
552a8cf3ff
13 changed files with 350 additions and 287 deletions
|
|
@ -13,7 +13,6 @@ add_library(librecomp STATIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/eep.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/eep.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/euc-jp.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/euc-jp.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/extensions.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/flash.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/heap.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/heap.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/math_routines.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/math_routines.cpp"
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,10 @@
|
||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
#include "rsp.hpp"
|
#include "rsp.hpp"
|
||||||
#include <ultramodern/ultramodern.hpp>
|
#include <ultramodern/ultramodern.hpp>
|
||||||
|
#include <ultramodern/save.hpp>
|
||||||
|
|
||||||
namespace recomp {
|
namespace recomp {
|
||||||
enum class SaveType {
|
using SaveType = ultramodern::SaveType;
|
||||||
None,
|
|
||||||
Eep4k,
|
|
||||||
Eep16k,
|
|
||||||
Sram,
|
|
||||||
Flashram,
|
|
||||||
AllowAll, // Allows all save types to work and reports eeprom size as 16kbit.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GameEntry {
|
struct GameEntry {
|
||||||
uint64_t rom_hash;
|
uint64_t rom_hash;
|
||||||
std::string internal_name;
|
std::string internal_name;
|
||||||
|
|
@ -109,11 +102,6 @@ namespace recomp {
|
||||||
///
|
///
|
||||||
void start(const Configuration& cfg);
|
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);
|
void start_game(const std::u8string& game_id);
|
||||||
std::u8string current_game_id();
|
std::u8string current_game_id();
|
||||||
std::string current_mod_game_id();
|
std::string current_mod_game_id();
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
#include "librecomp/game.hpp"
|
#include "librecomp/game.hpp"
|
||||||
|
|
||||||
#include "ultramodern/ultra64.h"
|
#include <ultramodern/save.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);
|
|
||||||
|
|
||||||
constexpr int eeprom_block_size = 8;
|
constexpr int eeprom_block_size = 8;
|
||||||
|
static std::vector<uint8_t> save_buffer;
|
||||||
|
|
||||||
extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
switch (recomp::get_save_type()) {
|
switch (ultramodern::get_save_type()) {
|
||||||
case recomp::SaveType::AllowAll:
|
case ultramodern::SaveType::AllowAll:
|
||||||
case recomp::SaveType::Eep16k:
|
case ultramodern::SaveType::Eep16k:
|
||||||
ctx->r2 = 0x02; // EEPROM_TYPE_16K
|
ctx->r2 = 0x02; // EEPROM_TYPE_16K
|
||||||
break;
|
break;
|
||||||
case recomp::SaveType::Eep4k:
|
case ultramodern::SaveType::Eep4k:
|
||||||
ctx->r2 = 0x01; // EEPROM_TYPE_4K
|
ctx->r2 = 0x01; // EEPROM_TYPE_4K
|
||||||
break;
|
break;
|
||||||
default:
|
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) {
|
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::error_handling::message_box("Attempted to use EEPROM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
@ -33,13 +32,17 @@ extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
gpr buffer = ctx->r6;
|
gpr buffer = ctx->r6;
|
||||||
int32_t nbytes = eeprom_block_size;
|
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;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
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::error_handling::message_box("Attempted to use EEPROM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
@ -50,13 +53,17 @@ extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
|
||||||
assert((nbytes % eeprom_block_size) == 0);
|
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;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
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::error_handling::message_box("Attempted to use EEPROM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
@ -65,13 +72,17 @@ extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
gpr buffer = ctx->r6;
|
gpr buffer = ctx->r6;
|
||||||
int32_t nbytes = eeprom_block_size;
|
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;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
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::error_handling::message_box("Attempted to use EEPROM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +93,11 @@ extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
|
||||||
assert((nbytes % eeprom_block_size) == 0);
|
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;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <ultramodern/save.hpp>
|
||||||
#include <ultramodern/ultra64.h>
|
#include <ultramodern/ultra64.h>
|
||||||
#include <ultramodern/ultramodern.hpp>
|
#include <ultramodern/ultramodern.hpp>
|
||||||
#include "recomp.h"
|
#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_size = page_size * pages_per_sector;
|
||||||
constexpr uint32_t sector_count = flash_size / sector_size;
|
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<char, page_size> write_buffer;
|
std::array<char, page_size> write_buffer;
|
||||||
|
std::vector<uint8_t> save_buffer;
|
||||||
|
|
||||||
extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
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) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
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) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
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) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
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) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
||||||
save_clear(0, ultramodern::save_size, 0xFF);
|
ultramodern::save_clear(0, ultramodern::save_size, 0xFF);
|
||||||
|
|
||||||
ctx->r2 = 0;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osFlashAllEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
||||||
save_clear(0, ultramodern::save_size, 0xFF);
|
ultramodern::save_clear(0, ultramodern::save_size, 0xFF);
|
||||||
|
|
||||||
ctx->r2 = 0;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is named sector but really means page.
|
// This function is named sector but really means page.
|
||||||
extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
@ -102,14 +99,14 @@ extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
save_clear(page_num * page_size, page_size, 0xFF);
|
ultramodern::save_clear(page_num * page_size, page_size, 0xFF);
|
||||||
|
|
||||||
ctx->r2 = 0;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same naming issue as above.
|
// Same naming issue as above.
|
||||||
extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
@ -122,13 +119,13 @@ extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
save_clear(page_num * page_size, page_size, 0xFF);
|
ultramodern::save_clear(page_num * page_size, page_size, 0xFF);
|
||||||
|
|
||||||
ctx->r2 = 0;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ctx) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
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) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
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) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
@ -168,13 +165,13 @@ extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx)
|
||||||
uint32_t page_num = ctx->r4;
|
uint32_t page_num = ctx->r4;
|
||||||
|
|
||||||
// Copy the write buffer into the save file
|
// 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;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
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;
|
uint32_t count = n_pages * page_size;
|
||||||
|
|
||||||
// Read from the save file into the provided buffer
|
// 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
|
// Send the message indicating read completion
|
||||||
ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi);
|
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) {
|
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::error_handling::message_box("Attempted to use FlashRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
#include "json/json.hpp"
|
#include "json/json.hpp"
|
||||||
|
|
||||||
#include "recompiler/context.h"
|
#include "recompiler/context.h"
|
||||||
#include "librecomp/files.hpp"
|
|
||||||
#include "librecomp/mods.hpp"
|
#include "librecomp/mods.hpp"
|
||||||
|
#include <ultramodern/files.hpp>
|
||||||
|
|
||||||
static bool read_json(std::ifstream input_file, nlohmann::json &json_out) {
|
static bool read_json(std::ifstream input_file, nlohmann::json &json_out) {
|
||||||
if (!input_file.good()) {
|
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.
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "librecomp/files.hpp"
|
#include <ultramodern/files.hpp>
|
||||||
#include "librecomp/mods.hpp"
|
#include "librecomp/mods.hpp"
|
||||||
#include "librecomp/overlays.hpp"
|
#include "librecomp/overlays.hpp"
|
||||||
#include "librecomp/game.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.
|
// 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;
|
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()) {
|
if (!output_file.good()) {
|
||||||
return false;
|
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 << std::setw(4) << config_json;
|
||||||
output_file.close();
|
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<std::string> &enabled_mods, std::vector<std::string> &mod_order) {
|
bool parse_mods_config(const std::filesystem::path &path, std::unordered_set<std::string> &enabled_mods, std::vector<std::string> &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["enabled_mods"] = enabled_mods;
|
||||||
config_json["mod_order"] = mod_order;
|
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()) {
|
if (!output_file.good()) {
|
||||||
return false;
|
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 << std::setw(4) << config_json;
|
||||||
output_file.close();
|
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() {
|
void recomp::mods::ModContext::dirty_mod_configuration_thread_process() {
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,12 @@
|
||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
#include "librecomp/addresses.hpp"
|
#include "librecomp/addresses.hpp"
|
||||||
#include "librecomp/game.hpp"
|
#include "librecomp/game.hpp"
|
||||||
#include "librecomp/files.hpp"
|
#include <ultramodern/files.hpp>
|
||||||
#include <ultramodern/ultra64.h>
|
#include <ultramodern/ultra64.h>
|
||||||
#include <ultramodern/ultramodern.hpp>
|
#include <ultramodern/ultramodern.hpp>
|
||||||
|
|
||||||
static std::vector<uint8_t> rom;
|
static std::vector<uint8_t> rom;
|
||||||
|
static std::vector<uint8_t> save_buffer;
|
||||||
|
|
||||||
bool recomp::is_rom_loaded() {
|
bool recomp::is_rom_loaded() {
|
||||||
return !rom.empty();
|
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++;
|
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) {
|
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 asynchronous transfer
|
||||||
// TODO implement unaligned DMA correctly
|
// 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
|
// Send a message to the mq to indicate that the transfer completed
|
||||||
ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi);
|
ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi);
|
||||||
} else if (physical_addr >= recomp::sram_base) {
|
} 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::error_handling::message_box("Attempted to use SRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
// read sram
|
// 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
|
// Send a message to the mq to indicate that the transfer completed
|
||||||
ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi);
|
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
|
// write cart rom
|
||||||
throw std::runtime_error("ROM DMA write unimplemented");
|
throw std::runtime_error("ROM DMA write unimplemented");
|
||||||
} else if (physical_addr >= recomp::sram_base) {
|
} 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::error_handling::message_box("Attempted to use SRAM saving with other save type");
|
||||||
ULTRAMODERN_QUICK_EXIT();
|
ULTRAMODERN_QUICK_EXIT();
|
||||||
}
|
}
|
||||||
// write sram
|
// 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
|
// Send a message to the mq to indicate that the transfer completed
|
||||||
ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi);
|
ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
#include "xxHash/xxh3.h"
|
#include "xxHash/xxh3.h"
|
||||||
#include "ultramodern/ultramodern.hpp"
|
#include "ultramodern/ultramodern.hpp"
|
||||||
#include "ultramodern/error_handling.hpp"
|
#include "ultramodern/error_handling.hpp"
|
||||||
|
#include "ultramodern/save.hpp"
|
||||||
#include "librecomp/addresses.hpp"
|
#include "librecomp/addresses.hpp"
|
||||||
#include "librecomp/mods.hpp"
|
#include "librecomp/mods.hpp"
|
||||||
#include "recompiler/live_recompiler.h"
|
#include "recompiler/live_recompiler.h"
|
||||||
|
|
@ -57,8 +58,6 @@ std::unordered_map<std::u8string, recomp::GameEntry> game_roms {};
|
||||||
std::unique_ptr<recomp::mods::ModContext> mod_context = std::make_unique<recomp::mods::ModContext>();
|
std::unique_ptr<recomp::mods::ModContext> mod_context = std::make_unique<recomp::mods::ModContext>();
|
||||||
// The project's version.
|
// The project's version.
|
||||||
recomp::Version project_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 {
|
std::u8string recomp::GameEntry::stored_filename() const {
|
||||||
return game_id + u8".z64";
|
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);
|
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);
|
ultramodern::init_saving(rdram, recomp::current_game_id());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
game_entry.entrypoint(rdram, context);
|
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) {
|
void recomp::start(const recomp::Configuration& cfg) {
|
||||||
project_version = cfg.project_version;
|
project_version = cfg.project_version;
|
||||||
recomp::check_all_stored_roms();
|
recomp::check_all_stored_roms();
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,13 @@ add_library(ultramodern STATIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/error_handling.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/error_handling.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/events.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/events.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/extensions.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/input.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mesgqueue.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/mesgqueue.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/misc_ultra.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/misc_ultra.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/renderer_context.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/renderer_context.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/rsp.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/scheduling.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/task_win32.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/task_win32.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/threadqueue.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/threadqueue.cpp"
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
#ifndef __RECOMP_FILES_H__
|
#ifndef __ULTRAMODERN_FILES_HPP__
|
||||||
#define __RECOMP_FILES_H__
|
#define __ULTRAMODERN_FILES_HPP__
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
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_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::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);
|
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);
|
bool finalize_output_file_with_backup(const std::filesystem::path& filepath);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
#endif // __ULTRAMODERN_FILES_HPP__
|
||||||
|
|
||||||
#endif
|
|
||||||
47
ultramodern/include/ultramodern/save.hpp
Normal file
47
ultramodern/include/ultramodern/save.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#ifndef __ULTRAMODERN_SAVE_HPP__
|
||||||
|
#define __ULTRAMODERN_SAVE_HPP__
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#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.
|
||||||
|
};
|
||||||
|
|
||||||
|
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__
|
||||||
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
#include "files.hpp"
|
#include <ultramodern/files.hpp>
|
||||||
|
|
||||||
constexpr std::u8string_view backup_suffix = u8".bak";
|
constexpr std::u8string_view backup_suffix = u8".bak";
|
||||||
constexpr std::u8string_view temp_suffix = u8".temp";
|
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};
|
std::filesystem::path backup_path{filepath};
|
||||||
backup_path += backup_suffix;
|
backup_path += backup_suffix;
|
||||||
return std::ifstream{backup_path, mode};
|
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};
|
std::ifstream ret{filepath, mode};
|
||||||
|
|
||||||
// Check if the file failed to open and open the corresponding backup file instead if so.
|
// 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;
|
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};
|
std::filesystem::path temp_path{filepath};
|
||||||
temp_path += temp_suffix;
|
temp_path += temp_suffix;
|
||||||
std::ofstream temp_file_out{ temp_path, mode };
|
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;
|
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};
|
std::filesystem::path backup_path{filepath};
|
||||||
backup_path += backup_suffix;
|
backup_path += backup_suffix;
|
||||||
|
|
||||||
207
ultramodern/src/save.cpp
Normal file
207
ultramodern/src/save.cpp
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
#include <filesystem>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
#include <ultramodern/files.hpp>
|
||||||
|
#include <ultramodern/save.hpp>
|
||||||
|
#include <ultramodern/ultramodern.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;
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue