Modified sectional saving to utilize an integer ID for calling specific sections to cut down on string copying. Utilizes enum values for non-mod sections to allow for cross-project use of those IDs, and sets up `AddSaveFunction` to add mod sections after that using max value enum.

This commit is contained in:
Malkierian 2023-05-15 17:21:14 -07:00
parent 4bc0d7d60d
commit 50fbe5d00c
5 changed files with 114 additions and 87 deletions

View File

@ -61,6 +61,15 @@ typedef struct {
u8 isRoom; u8 isRoom;
} SceneTimestamp; } SceneTimestamp;
typedef enum { // Pre-existing IDs for save sections in base code
SECTION_ID_BASE,
SECTION_ID_RANDOMIZER,
SECTION_ID_STATS,
SECTION_ID_ENTRANCES,
SECTION_ID_SCENES,
SECTION_ID_MAX
} SaveFuncIDs;
typedef struct { typedef struct {
/* */ char buildVersion[50]; /* */ char buildVersion[50];
/* */ s16 buildVersionMajor; /* */ s16 buildVersionMajor;

View File

@ -231,8 +231,8 @@ void LoadStatsVersion1() {
}); });
} }
void SaveStats(SaveContext* saveContext, const std::string& subSection) { void SaveStats(SaveContext* saveContext, int sectionID) {
if (subSection == "all") { if (sectionID == SECTION_ID_BASE) {
std::string buildVersion; std::string buildVersion;
SaveManager::Instance->LoadData("buildVersion", buildVersion); SaveManager::Instance->LoadData("buildVersion", buildVersion);
strncpy(gSaveContext.sohStats.buildVersion, buildVersion.c_str(), ARRAY_COUNT(gSaveContext.sohStats.buildVersion) - 1); strncpy(gSaveContext.sohStats.buildVersion, buildVersion.c_str(), ARRAY_COUNT(gSaveContext.sohStats.buildVersion) - 1);
@ -268,12 +268,12 @@ void SaveStats(SaveContext* saveContext, const std::string& subSection) {
SaveManager::Instance->LoadData("", gSaveContext.sohStats.locationsSkipped[i]); SaveManager::Instance->LoadData("", gSaveContext.sohStats.locationsSkipped[i]);
}); });
} }
if (subSection == "entrances" || subSection == "all") { if (sectionID == SECTION_ID_ENTRANCES || sectionID == SECTION_ID_BASE) {
SaveManager::Instance->SaveArray("entrancesDiscovered", ARRAY_COUNT(saveContext->sohStats.entrancesDiscovered), [&](size_t i) { SaveManager::Instance->SaveArray("entrancesDiscovered", ARRAY_COUNT(saveContext->sohStats.entrancesDiscovered), [&](size_t i) {
SaveManager::Instance->SaveData("", saveContext->sohStats.entrancesDiscovered[i]); SaveManager::Instance->SaveData("", saveContext->sohStats.entrancesDiscovered[i]);
}); });
} }
if (subSection == "scenes" || subSection == "all") { if (sectionID == SECTION_ID_SCENES || sectionID == SECTION_ID_BASE) {
SaveManager::Instance->SaveArray("scenesDiscovered", ARRAY_COUNT(saveContext->sohStats.scenesDiscovered), [&](size_t i) { SaveManager::Instance->SaveArray("scenesDiscovered", ARRAY_COUNT(saveContext->sohStats.scenesDiscovered), [&](size_t i) {
SaveManager::Instance->SaveData("", saveContext->sohStats.scenesDiscovered[i]); SaveManager::Instance->SaveData("", saveContext->sohStats.scenesDiscovered[i]);
}); });
@ -821,7 +821,10 @@ extern "C" void InitStatTracker() {
SetupDisplayNames(); SetupDisplayNames();
SetupDisplayColors(); SetupDisplayColors();
SaveManager::Instance->AddLoadFunction("sohStats", 1, LoadStatsVersion1); SaveManager::Instance->AddLoadFunction("sohStats", 1, LoadStatsVersion1);
SaveManager::Instance->AddSaveFunction("sohStats", 1, SaveStats); // Add main section save, no parent
SaveManager::Instance->AddSaveFunction("sohStats", 1, SaveStats, true, SECTION_PARENT_NONE);
// Add subsections, parent of "sohStats". Not sure how to do this without the redundant references to "SaveStats"
SaveManager::Instance->AddSaveFunction("entrances", 1, SaveStats, false, SECTION_ID_STATS);
SaveManager::Instance->AddSaveFunction("scenes", 1, SaveStats, false, SECTION_ID_STATS);
SaveManager::Instance->AddInitFunction(InitStats); SaveManager::Instance->AddInitFunction(InitStats);
SaveManager::Instance->RegisterGameSaveSection("sohStats");
} }

View File

@ -769,7 +769,8 @@ void Entrance_SetSceneDiscovered(u8 sceneNum) {
u32 sceneBit = 1 << (sceneNum - (idx * bitsPerIndex)); u32 sceneBit = 1 << (sceneNum - (idx * bitsPerIndex));
gSaveContext.sohStats.scenesDiscovered[idx] |= sceneBit; gSaveContext.sohStats.scenesDiscovered[idx] |= sceneBit;
} }
Save_SaveSection("sohStats.scenes"); // Save scenesDiscovered
Save_SaveSection(SECTION_ID_SCENES);
} }
u8 Entrance_GetIsEntranceDiscovered(u16 entranceIndex) { u8 Entrance_GetIsEntranceDiscovered(u16 entranceIndex) {
@ -802,5 +803,6 @@ void Entrance_SetEntranceDiscovered(u16 entranceIndex) {
} }
} }
} }
Save_SaveSection("sohStats.entrances"); // Save entrancesDiscovered
Save_SaveSection(SECTION_ID_ENTRANCES);
} }

View File

@ -17,6 +17,7 @@
#include <array> #include <array>
extern "C" SaveContext gSaveContext; extern "C" SaveContext gSaveContext;
using namespace std::string_literals;
void SaveManager::WriteSaveFile(const std::filesystem::path& savePath, const uintptr_t addr, void* dramAddr, void SaveManager::WriteSaveFile(const std::filesystem::path& savePath, const uintptr_t addr, void* dramAddr,
const size_t size) { const size_t size) {
@ -46,17 +47,20 @@ std::filesystem::path SaveManager::GetFileName(int fileNum) {
} }
SaveManager::SaveManager() { SaveManager::SaveManager() {
coreSectionIDsByName["base"] = SECTION_ID_BASE;
coreSectionIDsByName["randomizer"] = SECTION_ID_RANDOMIZER;
coreSectionIDsByName["sohStats"] = SECTION_ID_STATS;
coreSectionIDsByName["entrances"] = SECTION_ID_ENTRANCES;
coreSectionIDsByName["scenes"] = SECTION_ID_SCENES;
AddLoadFunction("base", 1, LoadBaseVersion1); AddLoadFunction("base", 1, LoadBaseVersion1);
AddLoadFunction("base", 2, LoadBaseVersion2); AddLoadFunction("base", 2, LoadBaseVersion2);
AddLoadFunction("base", 3, LoadBaseVersion3); AddLoadFunction("base", 3, LoadBaseVersion3);
AddLoadFunction("base", 4, LoadBaseVersion4); AddLoadFunction("base", 4, LoadBaseVersion4);
AddSaveFunction("base", 4, SaveBase); AddSaveFunction("base", 4, SaveBase, true, SECTION_PARENT_NONE);
RegisterGameSaveSection("base");
AddLoadFunction("randomizer", 1, LoadRandomizerVersion1); AddLoadFunction("randomizer", 1, LoadRandomizerVersion1);
AddLoadFunction("randomizer", 2, LoadRandomizerVersion2); AddLoadFunction("randomizer", 2, LoadRandomizerVersion2);
AddSaveFunction("randomizer", 2, SaveRandomizer); AddSaveFunction("randomizer", 2, SaveRandomizer, true, SECTION_PARENT_NONE);
RegisterGameSaveSection("randomizer");
AddInitFunction(InitFileImpl); AddInitFunction(InitFileImpl);
@ -86,20 +90,6 @@ SaveManager::SaveManager() {
} }
} }
// GameSaveSection functions affect the list of sections that save their data when a game save is triggered
void SaveManager::RegisterGameSaveSection(std::string section) {
if (std::find(gameSaveRegistry.begin(), gameSaveRegistry.end(), section) == gameSaveRegistry.end()) {
gameSaveRegistry.push_back(section);
}
}
void SaveManager::UnregisterAutosaveSection(std::string section) {
auto find = std::find(gameSaveRegistry.begin(), gameSaveRegistry.end(), section);
if (find != gameSaveRegistry.end()) {
gameSaveRegistry.erase(find);
}
}
void SaveManager::LoadRandomizerVersion1() { void SaveManager::LoadRandomizerVersion1() {
for (int i = 0; i < ARRAY_COUNT(gSaveContext.itemLocations); i++) { for (int i = 0; i < ARRAY_COUNT(gSaveContext.itemLocations); i++) {
SaveManager::Instance->LoadStruct("get" + std::to_string(i), [&]() { SaveManager::Instance->LoadStruct("get" + std::to_string(i), [&]() {
@ -278,7 +268,7 @@ void SaveManager::LoadRandomizerVersion2() {
}); });
} }
void SaveManager::SaveRandomizer(SaveContext* saveContext, const std::string& subString) { void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID) {
if(!saveContext->n64ddFlag) return; if(!saveContext->n64ddFlag) return;
@ -700,40 +690,42 @@ void SaveManager::InitFileDebug() {
gSaveContext.sceneFlags[5].swch = 0x40000000; gSaveContext.sceneFlags[5].swch = 0x40000000;
} }
// Threaded SaveFile takes copy of gSaveContext for local unmodified storage void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID) {
void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, const std::string& sectionString) { // Needed for first time save, hasn't changed in forever anyway
saveBlock["version"] = 1; saveBlock["version"] = 1;
size_t period = sectionString.find("."); if (sectionID == SECTION_ID_BASE) {
std::string section = sectionString; for (auto& sectionHandlerPair : sectionSaveHandlers) {
std::string subsection = ""; auto& saveFuncInfo = sectionHandlerPair.second;
if (period != std::string::npos) { // Don't call SaveFuncs for sections that aren't tied to game save
subsection = sectionString.substr(period + 1, std::string::npos); if (!saveFuncInfo.saveWithBase) {
section = sectionString.substr(0, sectionString.length() - (subsection.length() + 1)); continue;
}
if (sectionString == "all") {
for (auto& sectionHandler : sectionSaveHandlers) {
if (std::find(gameSaveRegistry.begin(), gameSaveRegistry.end(), sectionHandler.first) != gameSaveRegistry.end()) {
nlohmann::json& sectionBlock = saveBlock["sections"][sectionHandler.first];
sectionBlock["version"] = sectionHandler.second.first;
// If any save file is loaded for medatata, or a spoiler log is loaded (not sure which at this point), there is still data in the "randomizer" section
// This clears the randomizer data block if and only if the section being called is "randomizer" and n64ddFlag is false.
if (sectionHandler.first == "randomizer" && !gSaveContext.n64ddFlag) {
sectionBlock["data"] = nlohmann::json::object();
}
currentJsonContext = &sectionBlock["data"];
sectionHandler.second.second(saveContext, "all");
} }
nlohmann::json& sectionBlock = saveBlock["sections"][saveFuncInfo.name];
sectionBlock["version"] = sectionHandlerPair.second.version;
// If any save file is loaded for medatata, or a spoiler log is loaded (not sure which at this point), there is still data in the "randomizer" section
// This clears the randomizer data block if and only if the section being called is "randomizer" and n64ddFlag is false.
if (sectionHandlerPair.second.name == "randomizer" && !gSaveContext.n64ddFlag) {
sectionBlock["data"] = nlohmann::json::object();
continue;
}
currentJsonContext = &sectionBlock["data"];
sectionHandlerPair.second.func(saveContext, sectionID);
} }
} else if (sectionSaveHandlers.contains(section)) {
SectionSaveHandler handler = sectionSaveHandlers.find(section)->second;
nlohmann::json& sectionBlock = saveBlock["sections"][section];
sectionBlock["version"] = handler.first;
currentJsonContext = &sectionBlock["data"];
handler.second(saveContext, subsection);
} else { } else {
// save function for specified section does not exist. should this error? SaveFuncInfo svi = sectionSaveHandlers.find(sectionID)->second;
return; auto& sectionName = svi.name;
auto sectionVersion = svi.version;
// If section has a parentSection, it is a subsection. Load parentSection version and set sectionBlock to parent string
if (svi.parentSection != -1 && svi.parentSection < sectionIndex) {
auto parentSvi = sectionSaveHandlers.find(svi.parentSection)->second;
sectionName = parentSvi.name;
sectionVersion = parentSvi.version;
}
nlohmann::json& sectionBlock = saveBlock["sections"][sectionName];
sectionBlock["version"] = sectionVersion;
currentJsonContext = &sectionBlock["data"];
svi.func(saveContext, sectionID);
} }
#if defined(__SWITCH__) || defined(__WIIU__) #if defined(__SWITCH__) || defined(__WIIU__)
@ -751,18 +743,24 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, const
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum); GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum);
} }
void SaveManager::SaveSection(int fileNum, const std::string& sectionString) { // SaveSection creates a copy of gSaveContext to prevent mid-save data modification, and passes its reference to SaveFileThreaded
void SaveManager::SaveSection(int fileNum, int sectionID) {
if (fileNum == 0xFF) { if (fileNum == 0xFF) {
return; return;
} }
// Can't think of any time the promise would be needed, so use push_task instead of submit // Don't save a nonexistent section
if (sectionID >= sectionIndex) {
SPDLOG_ERROR("SaveSection: Section ID not registered.");
return;
}
auto saveContext = new SaveContext; auto saveContext = new SaveContext;
memcpy(saveContext, &gSaveContext, sizeof(gSaveContext)); memcpy(saveContext, &gSaveContext, sizeof(gSaveContext));
smThreadPool->push_task_back(&SaveManager::SaveFileThreaded, this, fileNum, saveContext, sectionString); // Can't think of any time the promise would be needed, so use push_task instead of submit
smThreadPool->push_task_back(&SaveManager::SaveFileThreaded, this, fileNum, saveContext, sectionID);
} }
void SaveManager::SaveFile(int fileNum) { void SaveManager::SaveFile(int fileNum) {
SaveSection(fileNum, "all"); SaveSection(fileNum, SECTION_ID_BASE);
} }
void SaveManager::SaveGlobal() { void SaveManager::SaveGlobal() {
@ -860,14 +858,21 @@ void SaveManager::AddLoadFunction(const std::string& name, int version, LoadFunc
sectionLoadHandlers[name][version] = func; sectionLoadHandlers[name][version] = func;
} }
void SaveManager::AddSaveFunction(const std::string& name, int version, SaveFunc func) { void SaveManager::AddSaveFunction(const std::string& name, int version, SaveFunc func, bool saveWithBase, int parentSection = -1) {
if (sectionSaveHandlers.contains(name)) { if (sectionRegistry.contains(name)) {
SPDLOG_ERROR("Adding save function for section that already has one: " + name); SPDLOG_ERROR("Adding save function for section that already has one: " + name);
assert(false); assert(false);
return; return;
} }
int index = sectionIndex;
sectionSaveHandlers[name] = std::make_pair(version, func); if (coreSectionIDsByName.contains(name)) {
index = coreSectionIDsByName.find(name)->second;
} else {
sectionIndex++;
}
SaveFuncInfo sfi = { name, version, func, saveWithBase, parentSection };
sectionSaveHandlers.emplace(index, sfi);
sectionRegistry.emplace(name);
} }
void SaveManager::AddPostFunction(const std::string& name, PostFunc func) { void SaveManager::AddPostFunction(const std::string& name, PostFunc func) {
@ -1623,7 +1628,7 @@ void SaveManager::LoadBaseVersion4() {
SaveManager::Instance->LoadData("dogParams", gSaveContext.dogParams); SaveManager::Instance->LoadData("dogParams", gSaveContext.dogParams);
} }
void SaveManager::SaveBase(SaveContext* saveContext, const std::string& subString) { void SaveManager::SaveBase(SaveContext* saveContext, int sectionID) {
SaveManager::Instance->SaveData("entranceIndex", saveContext->entranceIndex); SaveManager::Instance->SaveData("entranceIndex", saveContext->entranceIndex);
SaveManager::Instance->SaveData("linkAge", saveContext->linkAge); SaveManager::Instance->SaveData("linkAge", saveContext->linkAge);
SaveManager::Instance->SaveData("cutsceneIndex", saveContext->cutsceneIndex); SaveManager::Instance->SaveData("cutsceneIndex", saveContext->cutsceneIndex);
@ -2290,8 +2295,8 @@ extern "C" void Save_SaveFile(void) {
SaveManager::Instance->SaveFile(gSaveContext.fileNum); SaveManager::Instance->SaveFile(gSaveContext.fileNum);
} }
extern "C" void Save_SaveSection(char* sectionString) { extern "C" void Save_SaveSection(int sectionID) {
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionString); SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionID);
} }
extern "C" void Save_SaveGlobal(void) { extern "C" void Save_SaveGlobal(void) {
@ -2307,8 +2312,8 @@ extern "C" void Save_AddLoadFunction(char* name, int version, SaveManager::LoadF
SaveManager::Instance->AddLoadFunction(name, version, func); SaveManager::Instance->AddLoadFunction(name, version, func);
} }
extern "C" void Save_AddSaveFunction(char* name, int version, SaveManager::SaveFunc func) { extern "C" void Save_AddSaveFunction(char* name, int version, SaveManager::SaveFunc func, bool saveWithBase, int parentSection = SECTION_PARENT_NONE) {
SaveManager::Instance->AddSaveFunction(name, version, func); SaveManager::Instance->AddSaveFunction(name, version, func, saveWithBase, parentSection);
} }
extern "C" SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum) { extern "C" SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum) {

View File

@ -2,6 +2,7 @@
#include <libultraship/libultra/gbi.h> #include <libultraship/libultra/gbi.h>
#define SECTION_PARENT_NONE -1
typedef struct { typedef struct {
u8 valid; u8 valid;
u16 deaths; u16 deaths;
@ -27,6 +28,7 @@ typedef struct {
#include <tuple> #include <tuple>
#include <functional> #include <functional>
#include <vector> #include <vector>
#include <set>
#include <filesystem> #include <filesystem>
#include "thread-pool/BS_thread_pool.hpp" #include "thread-pool/BS_thread_pool.hpp"
@ -38,6 +40,7 @@ extern "C" {
class SaveManager { class SaveManager {
public: public:
static SaveManager* Instance; static SaveManager* Instance;
static void WriteSaveFile(const std::filesystem::path& savePath, uintptr_t addr, void* dramAddr, size_t size); static void WriteSaveFile(const std::filesystem::path& savePath, uintptr_t addr, void* dramAddr, size_t size);
@ -45,15 +48,23 @@ public:
using InitFunc = void(*)(bool isDebug); using InitFunc = void(*)(bool isDebug);
using LoadFunc = void(*)(); using LoadFunc = void(*)();
using SaveFunc = void(*)(SaveContext* saveContext, const std::string& subSection); using SaveFunc = void(*)(SaveContext* saveContext, int sectionID);
using PostFunc = void(*)(int version); using PostFunc = void (*)(int version);
typedef struct {
std::string name;
int version;
SaveManager::SaveFunc func;
bool saveWithBase;
int parentSection;
} SaveFuncInfo;
SaveManager(); SaveManager();
void Init(); void Init();
void InitFile(bool isDebug); void InitFile(bool isDebug);
void SaveFile(int fileNum); void SaveFile(int fileNum);
void SaveSection(int fileNum, const std::string& sectionString); void SaveSection(int fileNum, int sectionID);
void SaveGlobal(); void SaveGlobal();
void LoadFile(int fileNum); void LoadFile(int fileNum);
bool SaveFile_Exist(int fileNum); bool SaveFile_Exist(int fileNum);
@ -66,7 +77,7 @@ public:
void AddLoadFunction(const std::string& name, int version, LoadFunc func); void AddLoadFunction(const std::string& name, int version, LoadFunc func);
// Adds a function that is called when saving. This should only be called once for each function, the version is filled in automatically. // Adds a function that is called when saving. This should only be called once for each function, the version is filled in automatically.
void AddSaveFunction(const std::string& name, int version, SaveFunc func); void AddSaveFunction(const std::string& name, int version, SaveFunc func, bool saveWithBase, int parentSection);
// Adds a function to be called after loading is complete. This is to handle any cleanup required from loading old versions. // Adds a function to be called after loading is complete. This is to handle any cleanup required from loading old versions.
void AddPostFunction(const std::string& name, PostFunc func); void AddPostFunction(const std::string& name, PostFunc func);
@ -119,9 +130,6 @@ public:
static const int MaxFiles = 3; static const int MaxFiles = 3;
std::array<SaveFileMetaInfo, MaxFiles> fileMetaInfo; std::array<SaveFileMetaInfo, MaxFiles> fileMetaInfo;
void RegisterGameSaveSection(std::string section);
void UnregisterAutosaveSection(std::string section);
private: private:
std::filesystem::path GetFileName(int fileNum); std::filesystem::path GetFileName(int fileNum);
nlohmann::json saveBlock; nlohmann::json saveBlock;
@ -129,7 +137,7 @@ public:
void ConvertFromUnversioned(); void ConvertFromUnversioned();
void CreateDefaultGlobal(); void CreateDefaultGlobal();
void SaveFileThreaded(int fileNum, SaveContext* saveContext, const std::string& sectionString); void SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID);
void InitMeta(int slotNum); void InitMeta(int slotNum);
static void InitFileImpl(bool isDebug); static void InitFileImpl(bool isDebug);
@ -138,23 +146,23 @@ public:
static void LoadRandomizerVersion1(); static void LoadRandomizerVersion1();
static void LoadRandomizerVersion2(); static void LoadRandomizerVersion2();
static void SaveRandomizer(SaveContext* saveContext, const std::string& subString); static void SaveRandomizer(SaveContext* saveContext, int sectionID);
static void LoadBaseVersion1(); static void LoadBaseVersion1();
static void LoadBaseVersion2(); static void LoadBaseVersion2();
static void LoadBaseVersion3(); static void LoadBaseVersion3();
static void LoadBaseVersion4(); static void LoadBaseVersion4();
static void SaveBase(SaveContext* saveContext, const std::string& subString); static void SaveBase(SaveContext* saveContext, int sectionID);
std::vector<InitFunc> initFuncs; std::vector<InitFunc> initFuncs;
using SectionLoadHandler = std::map<int, LoadFunc>; using SectionLoadHandler = std::map<int, LoadFunc>;
std::map<std::string, SectionLoadHandler> sectionLoadHandlers; std::map<std::string, SectionLoadHandler> sectionLoadHandlers;
using SectionSaveHandler = std::pair<int, SaveFunc>; int sectionIndex = SECTION_ID_MAX;
std::map<std::string, SectionSaveHandler> sectionSaveHandlers; std::map<std::string, int> coreSectionIDsByName;
// tracks sections to save during game saves std::map<int, SaveFuncInfo> sectionSaveHandlers;
std::vector<std::string> gameSaveRegistry; std::set<std::string> sectionRegistry;
std::map<std::string, PostFunc> postHandlers; std::map<std::string, PostFunc> postHandlers;
@ -168,16 +176,16 @@ public:
// TODO feature parity to the C++ interface. We need Save_AddInitFunction and Save_AddPostFunction at least // TODO feature parity to the C++ interface. We need Save_AddInitFunction and Save_AddPostFunction at least
typedef void (*Save_LoadFunc)(void); typedef void (*Save_LoadFunc)(void);
typedef void (*Save_SaveFunc)(const SaveContext* saveContext); typedef void (*Save_SaveFunc)(const SaveContext* saveContext, int sectionID);
void Save_Init(void); void Save_Init(void);
void Save_InitFile(int isDebug); void Save_InitFile(int isDebug);
void Save_SaveFile(void); void Save_SaveFile(void);
void Save_SaveSection(char* sectionString); void Save_SaveSection(int sectionID);
void Save_SaveGlobal(void); void Save_SaveGlobal(void);
void Save_LoadGlobal(void); void Save_LoadGlobal(void);
void Save_AddLoadFunction(char* name, int version, Save_LoadFunc func); void Save_AddLoadFunction(char* name, int version, Save_LoadFunc func);
void Save_AddSaveFunction(char* name, int version, Save_SaveFunc func); void Save_AddSaveFunction(char* name, int version, Save_SaveFunc func, bool saveWithBase, int parentSection);
SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum); SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum);
void Save_CopyFile(int from, int to); void Save_CopyFile(int from, int to);
void Save_DeleteFile(int fileNum); void Save_DeleteFile(int fileNum);