diff --git a/soh/soh/Enhancements/debugconsole.cpp b/soh/soh/Enhancements/debugconsole.cpp index 91fc0a2a4..72eea1cd8 100644 --- a/soh/soh/Enhancements/debugconsole.cpp +++ b/soh/soh/Enhancements/debugconsole.cpp @@ -307,15 +307,175 @@ static bool EntranceHandler(const std::vector& args) { } static bool SaveStateHandler(const std::vector& args) { - SaveState::Save(0); - return CMD_SUCCESS; + const SaveStateReturn rtn = SaveState::Save(gCurrentSlot); + + switch (rtn) { + case SaveStateReturn::SUCCESS: + INFO("[SOH] Saved state to slot %u", gCurrentSlot); + return CMD_SUCCESS; + case SaveStateReturn::FAIL_INVALID_SLOT: + ERROR("[SOH] Invalid State Slot Number (%u)", gCurrentSlot); + return CMD_FAILED; + case SaveStateReturn::FAIL_NO_MEMORY: + ERROR("[SOH] No Memory to save state to slot %u", gCurrentSlot); + return CMD_FAILED; + } } static bool LoadStateHandler(const std::vector& args) { - SaveState::Load(0); - return CMD_SUCCESS; + const SaveStateReturn rtn = SaveState::Load(gCurrentSlot); + + switch (rtn) { + case SaveStateReturn::SUCCESS: + INFO("[SOH] Loaded state from slot %u", gCurrentSlot); + return CMD_SUCCESS; + case SaveStateReturn::FAIL_INVALID_SLOT: + ERROR("[SOH] Invalid State Slot Number (%u)", gCurrentSlot); + return CMD_FAILED; + case SaveStateReturn::FAIL_STATE_EMPTY: + ERROR("[SOH] State Slot (%u) is empty", gCurrentSlot); + return CMD_FAILED; + + } + } +static bool StateSlotCycleHandler(const std::vector& args) { + if (gCurrentSlot == SaveState::SAVE_SLOT_MAX) { + gCurrentSlot = 0; + INFO("[SOH] Slot 0 selected"); + } else { + gCurrentSlot++; + INFO("[SOH] Slot %u selected", gCurrentSlot); + } + return CMD_SUCCESS; +} + +static bool StateSlotSelectHandler(const std::vector& args) { + if (args.size() != 2) { + ERROR("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + int slot; + + try { + slot = std::stoi(args[1], nullptr, 10); + } catch (std::invalid_argument const& ex) { + ERROR("[SOH] SaveState slot value must be a number."); + return CMD_FAILED; + } + + if (slot < 0 || slot > SaveState::SAVE_SLOT_MAX) { + ERROR("[SOH] Invalid slot passed. Slot must be between 0 and 2"); + return CMD_FAILED; + } + + gCurrentSlot = slot; + INFO("[SOH] Slot %u selected", gCurrentSlot); + return CMD_SUCCESS; +} + +static bool StateDeleteHandler(const std::vector& args) { + int slot = gCurrentSlot; + + if (args.size() == 2) { + + try { + slot = std::stoi(args[1], nullptr, 10); + } catch (std::invalid_argument const& ex) { + ERROR("[SOH] SaveState slot value must be a number."); + return CMD_FAILED; + } + + if (slot < 0 || slot > SaveState::SAVE_SLOT_MAX) { + ERROR("[SOH] Invalid slot passed. Slot must be between 0 and 2"); + return CMD_FAILED; + } + + } + SaveState::Delete(slot); + + return CMD_SUCCESS; + +} + +static bool StateExportHandler(const std::vector& args) { + int slot = gCurrentSlot; + + if (args.size() == 2) { + + try { + slot = std::stoi(args[1], nullptr, 10); + } catch (std::invalid_argument const& ex) { + ERROR("[SOH] SaveState slot value must be a number."); + return CMD_FAILED; + } + + if (slot < 0 || slot > SaveState::SAVE_SLOT_MAX) { + ERROR("[SOH] Invalid slot passed. Slot must be between 0 and 2"); + return CMD_FAILED; + } + + } + + const SaveStateReturn rtn = SaveState::Export(slot); + + switch (rtn) { + case SaveStateReturn::SUCCESS: + INFO("[SOH] Exported slot %u", slot); + return CMD_SUCCESS; + case SaveStateReturn::FAIL_FILE_NOT_OPENED: + ERROR("[SOH] Could not open file for writing"); + return CMD_FAILED; + case SaveStateReturn::FAIL_INVALID_SLOT: + ERROR("[SOH] Invalid State Slot Number %u", slot); + return CMD_FAILED; + case SaveStateReturn::FAIL_STATE_EMPTY: + ERROR("[SOH] SaveState slot %u empty", slot); + return CMD_FAILED; + } + +} + +static bool StateImportHandler(const std::vector& args) { + int slot = gCurrentSlot; + + if (args.size() == 2) { + + try { + slot = std::stoi(args[1], nullptr, 10); + } catch (std::invalid_argument const& ex) { + ERROR("[SOH] SaveState slot value must be a number."); + return CMD_FAILED; + } + + if (slot < 0 || slot > SaveState::SAVE_SLOT_MAX) { + ERROR("[SOH] Invalid slot passed. Slot must be between 0 and 2"); + return CMD_FAILED; + } + } + + const SaveStateReturn rtn = SaveState::Import(slot); + + switch (rtn) { + case SaveStateReturn::SUCCESS: + INFO("[SOH] Imported slot %u", slot); + return CMD_SUCCESS; + case SaveStateReturn::FAIL_FILE_NOT_OPENED: + ERROR("[SOH] Could not open file for reading"); + return CMD_FAILED; + case SaveStateReturn::FAIL_NO_MEMORY: + ERROR("[SOH] Could not allocate memory for Slot Number %u", slot); + return CMD_FAILED; + case SaveStateReturn::FAIL_STATE_EMPTY: + ERROR("[SOH] SaveState slot %u empty", slot); + return CMD_FAILED; + case SaveStateReturn::FAIL_INVALID_MAGIC: + ERROR("[SOH] Invalid magic number in file"); + return CMD_FAILED; + } + +} #define VARTYPE_INTEGER 0 #define VARTYPE_FLOAT 1 @@ -433,6 +593,19 @@ void DebugConsole_Init(void) { CMD_REGISTER("save_state", { SaveStateHandler, "Save a state." }); CMD_REGISTER("load_state", { LoadStateHandler, "Load a state." }); + CMD_REGISTER("cycle_state", { StateSlotCycleHandler, "Cycle through states.",}); + CMD_REGISTER("clear_state", { StateDeleteHandler, "Clear a save slot", { + { "Slot number", ArgumentType::NUMBER, true }, + } }); + CMD_REGISTER("set_slot", { StateSlotSelectHandler, "Selects a SaveState slot", { + { "Slot number", ArgumentType::NUMBER, } + } }); + CMD_REGISTER("export_state", { StateExportHandler, "Exports a SaveState to a file", { + { "Slot number", ArgumentType::NUMBER, true }, + } }); + CMD_REGISTER("import_state", { StateImportHandler, "Imports a SaveState to a file", { + { "Slot number", ArgumentType::NUMBER, true }, + } }); DebugConsole_LoadCVars(); } diff --git a/soh/soh/Enhancements/savestates.cpp b/soh/soh/Enhancements/savestates.cpp index 9eca20db1..403510fbb 100644 --- a/soh/soh/Enhancements/savestates.cpp +++ b/soh/soh/Enhancements/savestates.cpp @@ -2,20 +2,20 @@ #include "GameVersions.h" #include + +#include // std::sprintf + + //#include "global.h" //#include -std::array gSaveStates; +//TODO is there a better way? +unsigned int gCurrentSlot = 0; +static std::array gSaveStates; -SaveState::SaveState() { - -} -#if 0 - -SaveState::~SaveState() { -} -#endif +SaveState::SaveState(){} +SaveState::~SaveState() {} void SaveState::Init(void) { gSaveStates[0] = nullptr; @@ -23,17 +23,105 @@ void SaveState::Init(void) { gSaveStates[2] = nullptr; } -void SaveState::WriteHeader(SaveStateHeader& header) { - //OTRGlobals::Instance->context->GetResourceManager()->GetGameVersion(); - // header.gameVersion = ResourceMgr_GetGameVersion(); +void SaveState::SetHeader(SaveStateHeader& header) { + header.stateMagic = 0x07151129; + //TODO state version ENUM; + header.stateVersion = 0; } -extern "C" MtxF* sMatrixStack; // "Matrix_stack" -extern "C" MtxF* sCurrentMatrix; // "Matrix_now" -extern "C" LightsBuffer sLightsBuffer; +SaveStateReturn SaveState::Delete(const unsigned int slot) { + if (slot > SAVE_SLOT_MAX) { + return SaveStateReturn::FAIL_INVALID_SLOT; + } + if (gSaveStates[slot] != nullptr) { + delete gSaveStates[slot]; + gSaveStates[slot] = nullptr; + } + return SaveStateReturn::SUCCESS; +} -extern "C" SaveStateReturn SaveState::Save(unsigned int slot) { - if (slot > 2) { +SaveStateReturn SaveState::Export(const unsigned int slot) { + if (slot > SAVE_SLOT_MAX) { + return SaveStateReturn::FAIL_INVALID_SLOT; + } + if (gSaveStates[slot] == nullptr) { + return SaveStateReturn::FAIL_STATE_EMPTY; + } + + char outFileName[20]; + std::sprintf(outFileName, "SOH_STATE_%u.state", gCurrentSlot); + + FILE* outFile = fopen(outFileName, "wb+"); + + if (outFile == nullptr) { + return SaveStateReturn::FAIL_FILE_NOT_OPENED; + } + + SaveState::SetHeader(gSaveStates[slot]->stateHeader); + std::fwrite(gSaveStates[slot], sizeof(SaveState), 1, outFile); + std::fclose(outFile); + + return SaveStateReturn::SUCCESS; +} + +SaveStateReturn SaveState::Import(const unsigned int slot) { + if (slot > SAVE_SLOT_MAX) { + return SaveStateReturn::FAIL_INVALID_SLOT; + } + + char inFileName[20]; + std::sprintf(inFileName, "SOH_STATE_%u.state", gCurrentSlot); + + FILE* inFile = std::fopen(inFileName, "rb"); + + if (inFile == nullptr) { + return SaveStateReturn::FAIL_FILE_NOT_OPENED; + } + + std::fseek(inFile, 0, SEEK_END); + const size_t inFileSize = std::ftell(inFile); + + if (inFileSize != sizeof(SaveState)) { + std::fclose(inFile); + return SaveStateReturn::FAIL_INVALID_SIZE; + } + std::fseek(inFile, 0, SEEK_SET); + + SaveStateHeader tempHeader; + std::fread(&tempHeader, sizeof(SaveStateHeader), 1, inFile); + + if (tempHeader.stateMagic != 0x07151129) { + std::fclose(inFile); + return SaveStateReturn::FAIL_INVALID_MAGIC; + } + + if (gSaveStates[slot] == nullptr) { + gSaveStates[slot] = new SaveState; + if (gSaveStates[slot] == nullptr) { + fclose(inFile); + return SaveStateReturn::FAIL_NO_MEMORY; + } + } + + std::fseek(inFile, 0, SEEK_SET); + std::fread(gSaveStates[slot], sizeof(SaveState), 1, inFile); + std::fclose(inFile); + //TODO version system + //if (gSaveStates[slot]->stateHeader.stateVersion != 0) { + // return SaveStateReturn::FAIL_INVALID_STATE; + //} + + return SaveStateReturn::SUCCESS; + +} + +extern "C" MtxF* sMatrixStack; +extern "C" MtxF* sCurrentMatrix; +extern "C" LightsBuffer sLightsBuffer; +extern "C" s16 sWarpTimerTarget; + +extern "C" SaveStateReturn SaveState::Save(const unsigned int slot) { + if (slot > SaveState::SAVE_SLOT_MAX) { return SaveStateReturn::FAIL_INVALID_SLOT; } if (gSaveStates[slot] == nullptr) { @@ -43,9 +131,8 @@ extern "C" SaveStateReturn SaveState::Save(unsigned int slot) { } } - gSaveStates[slot]->stateHeader.gameVersion = 0; - gSaveStates[slot]->stateHeader.stateVersion = 0; - + SaveState::SetHeader(gSaveStates[slot]->stateHeader); + memcpy(&gSaveStates[slot]->sysHeapCopy, &gSystemHeap, 1024 * 1024 * 4 /* sizeof(gSystemHeap) */); memcpy(&gSaveStates[slot]->saveContextCopy, &gSaveContext, sizeof(gSaveContext)); memcpy(&gSaveStates[slot]->gameInfoCopy, gGameInfo, sizeof(*gGameInfo)); @@ -53,14 +140,15 @@ extern "C" SaveStateReturn SaveState::Save(unsigned int slot) { memcpy(&gSaveStates[slot]->lightBufferCopy, &sLightsBuffer, sizeof(sLightsBuffer)); memcpy(&gSaveStates[slot]->mtxStackCopy, &sMatrixStack, sizeof(MtxF) * 20); memcpy(&gSaveStates[slot]->currentMtxCopy, &sCurrentMatrix, sizeof(MtxF)); + gSaveStates[slot]->blueWarpTimerCopy = sWarpTimerTarget; //TODO RNG seed return SaveStateReturn::SUCCESS; } -SaveStateReturn SaveState::Load(unsigned int slot) { - if (slot > 2) { +SaveStateReturn SaveState::Load(const unsigned int slot) { + if (slot > SaveState::SAVE_SLOT_MAX) { return SaveStateReturn::FAIL_INVALID_SLOT; } if (gSaveStates[slot] == nullptr) { @@ -73,10 +161,11 @@ SaveStateReturn SaveState::Load(unsigned int slot) { memcpy(&gSystemHeap, &gSaveStates[slot]->sysHeapCopy, 1024 * 1024 * 4); memcpy(&gSaveContext, &gSaveStates[slot]->saveContextCopy, sizeof(gSaveContext)); memcpy(gGameInfo, &gSaveStates[slot]->gameInfoCopy, sizeof(*gGameInfo)); - //memcpy(&gAudioContext, &gSaveStates[slot]->audioContextCopy, sizeof(AudioContext)); - memcpy(&sLightsBuffer, &gSaveStates[slot]->lightBufferCopy, sizeof(sLightsBuffer)); + //memcpy(&gAudioContext, &gSaveStates[slot]->audioContextCopy, sizeof(AudioContext)); + memcpy(&sLightsBuffer, &gSaveStates[slot]->lightBufferCopy, sizeof(sLightsBuffer)); memcpy(&sMatrixStack, &gSaveStates[slot]->mtxStackCopy, sizeof(MtxF) * 20); memcpy(&sCurrentMatrix, &gSaveStates[slot]->currentMtxCopy, sizeof(MtxF)); + sWarpTimerTarget = gSaveStates[slot]->blueWarpTimerCopy; //TODO RNG seed diff --git a/soh/soh/Enhancements/savestates.h b/soh/soh/Enhancements/savestates.h index cfc9f6e31..7cb92337e 100644 --- a/soh/soh/Enhancements/savestates.h +++ b/soh/soh/Enhancements/savestates.h @@ -23,27 +23,39 @@ enum class SaveStateReturn { FAIL_INVALID_SLOT, FAIL_NO_MEMORY, FAIL_STATE_EMPTY, + FAIL_FILE_NOT_FOUND, + FAIL_FILE_NOT_OPENED, + FAIL_INVALID_MAGIC, + FAIL_INVALID_SIZE, }; typedef struct SaveStateHeader { + uint32_t stateMagic; uint32_t stateVersion; - uint32_t gameVersion; + //uint32_t gameVersion; } SaveStateHeader; +extern unsigned int gCurrentSlot; + class SaveState { public: + static constexpr unsigned int SAVE_SLOT_MAX = 2; SaveState(); - ~SaveState() = delete; + ~SaveState(); + SaveState& operator=(const SaveState& rhs) = delete; SaveState(const SaveState& rhs) = delete; void Init(void); - static SaveStateReturn Save(unsigned int slot); - static SaveStateReturn Load(unsigned int slot); + static SaveStateReturn Save(const unsigned int slot); + static SaveStateReturn Load(const unsigned int slot); + static SaveStateReturn Export(const unsigned int slot); + static SaveStateReturn Import(const unsigned int slot); + static SaveStateReturn Delete(const unsigned int slot); private: - static void WriteHeader(SaveStateHeader& header); - static void ReadHeader(SaveStateHeader& header); + static void SetHeader(SaveStateHeader& header); + static void GetHeader(SaveStateHeader& header); SaveStateHeader stateHeader; unsigned char sysHeapCopy[1024 * 1024 * 4]; //TODO, make a macro for this SaveContext saveContextCopy; @@ -53,6 +65,7 @@ class SaveState { std::array mtxStackCopy; // always 20 matricies MtxF currentMtxCopy; uint32_t rngSeed; + int16_t blueWarpTimerCopy; /* From door_warp_1 */ }; diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 6218b7d76..2934e5e1b 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -421,7 +421,7 @@ void Gameplay_Update(GlobalContext* globalCtx) { input = globalCtx->state.input; - if ((SREG(1) < 0) || (DREG(0) != 0)) { + if ((SREG(1) < 0) || (DREG(0) != 0)) { SREG(1) = 0; ZeldaArena_Display(); } diff --git a/soh/src/overlays/actors/ovl_Door_Warp1/z_door_warp1.c b/soh/src/overlays/actors/ovl_Door_Warp1/z_door_warp1.c index 46f3d1857..ee503dd43 100644 --- a/soh/src/overlays/actors/ovl_Door_Warp1/z_door_warp1.c +++ b/soh/src/overlays/actors/ovl_Door_Warp1/z_door_warp1.c @@ -53,7 +53,7 @@ static InitChainEntry sInitChain[] = { ICHAIN_F32(uncullZoneDownward, 4000, ICHAIN_STOP), }; -static s16 sWarpTimerTarget; +s16 sWarpTimerTarget; void DoorWarp1_SetupAction(DoorWarp1* this, DoorWarp1ActionFunc actionFunc) { this->actionFunc = actionFunc;