diff --git a/CHANGELOG.md b/CHANGELOG.md index be5c897..8d96732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # CHANGELOG -### `1.6.0-SS1.0` `2022-08-26` `LATEST` +### `1.6.0-SS1.1` `2022-09-10` `LATEST` +- Add save and restore offset between pouch item list count (i.e. `mCount`) and actual inventory size to level 3 +- Make save and restore key binding configurable + - In setting mode, hold the current key binding for 3 seconds, then hold the new key binding for 3 seconds. + +### `1.6.0-SS1.0` `2022-08-26` - Level 3 save and restore - Change save/restore key combo to include both left triggers (`L` and `ZL`). This is so that you don't accidentally save or restore during a super launch - Enable logging (except for debug logging) in ship builds diff --git a/README.md b/README.md index 39265f9..d56138c 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,13 @@ In setting mode. You can switch between levels of save state. - Hold `L` for 1 second: Decrease level - Hold `R` for 1 second: Increase level +- Hold save/restore to memory/file key binding for 3 seconds: Change the key binding (Hold the new keys for 3 seconds to change) -The level settings are preserved through restarts, meaning that when you close the game on a level, you will be on the same level when you boot up the game again. +The settings are preserved through restarts. For example, if you close the game on a level, you will be on the same level when you boot up the game again. + +If you somehow messed up the settings and want to reset them, you can do so by deleting `/botwsavs/worker.txt` file on your sd card. + +### Levels There are currently 4 levels: @@ -101,6 +106,8 @@ Level 3 save state includes everything available, which includes all from Level - Flame Resist - Shock Resist - Stealth +- Inventory offset: + - i.e. the number of broken slots ### Save/Restore with Different Levels If you save a state with a high level (for example, level 3), then switch to a lower level (for example, level 1) and restore, only the lower level values will be restored. However, the state still contains the higher level data. You can switch back to the higher level and be able to restore all the values @@ -110,7 +117,16 @@ If you save a state with a low level (for example, level 1), then switch to a hi You need to lower the setting level to restore! ``` -### Save State Transfer Script +### Change Key Binding + +You can change the key binding for save/restore following these steps: +1. Hold all triggers + whistle for 3 seconds to enter setting mode +2. Hold the current key binding for the one you want to change for 3 seconds +3. Let go of all buttons. You will see a message telling you to hold the new binding for 3 seconds +4. Hold the new binding for 3 seconds and you will see a confirmation message +5. Hold all triggers + whistle for 3 seconds to exit setting mode + +### FTP Transfer Script A python script `ftp.py` is also included for transfering save state files over FTP (with the `ftpd` homebrew app). Run `python3 ftp.py` to see how to use. (This has nothing to do in game, just makes managing the save state files generated by the mod easier) @@ -143,7 +159,7 @@ Need Python 3. (3.10 is preferred but other version probably works as well). Install these packages ``` -python3 -m pip install toml pylint pylint-quotes keystone +python3 -m pip install toml pylint pylint-quotes keystone-engine ``` #### clang-format clang-format-12 is required. Run this on linux or the mac-equivalent on mac diff --git a/lib/botw/Game/UI/uiPauseMenuDataMgr.h b/lib/botw/Game/UI/uiPauseMenuDataMgr.h index beafe53..f5b4107 100644 --- a/lib/botw/Game/UI/uiPauseMenuDataMgr.h +++ b/lib/botw/Game/UI/uiPauseMenuDataMgr.h @@ -376,7 +376,7 @@ class PauseMenuDataMgr { void grabbedItemStuff(PouchItem* item); - // SAVE-STATE-HACK expose equipped weapons aray + // SAVE-STATE-HACK expose equipped weapons array PouchItem* getEquippedWeapon(PouchItemType type) { return mEquippedWeapons[u32(type)]; } @@ -412,9 +412,12 @@ class PauseMenuDataMgr { sead::SafeArray buffer; }; +// BOTW-SAVE-STATE hack: make these public +public: sead::OffsetList& getItems() { return mItemLists.list1; } const sead::OffsetList& getItems() const { return mItemLists.list1; } - +// BOTW-SAVE-STATE hack end +private: // FIXME: types bool useItemFromRecipe(Lists* lists, void* unk, int multiplier, PouchItem* item); diff --git a/lib/sead/include/container/seadListImpl.h b/lib/sead/include/container/seadListImpl.h index c78979b..4791252 100644 --- a/lib/sead/include/container/seadListImpl.h +++ b/lib/sead/include/container/seadListImpl.h @@ -37,6 +37,17 @@ class ListImpl bool isEmpty() const { return mCount == 0; } s32 size() const { return mCount; } +// BOTW-sAVE-STATE hack + void setSize(s32 size) { mCount = size; } + s32 sizeSlow() const { + s32 size = 0; + ListNode* ptr = mStartEnd.mNext; + while(ptr && ptr != &mStartEnd){ + ptr = ptr->mNext; + size++; + } + return size; + } void reverse(); void shuffle(); diff --git a/src/core/Controller.cpp b/src/core/Controller.cpp index f2e64cd..e263b67 100644 --- a/src/core/Controller.cpp +++ b/src/core/Controller.cpp @@ -6,10 +6,6 @@ namespace botwsavs::core { -Controller::~Controller() { - mpController = nullptr; -} - bool Controller::TryGetController() { if (mpController) { return true; @@ -27,41 +23,18 @@ bool Controller::TryGetController() { return false; } - fs::FileBuffer buf; - buf.SafeAppendF("Found controller instance: %p", pController); - info(buf); + infof("Found controller instance: %p", pController); mpController = pController; return true; } -bool Controller::ShouldSaveState() { - return mpController->isHoldAll(Key::ZL | Key::L | Key::Plus | Key::DpadLeft) && - !mpController->isHold(Key::RStick); -} - -bool Controller::ShouldRestoreState() { - return mpController->isHoldAll(Key::ZL | Key::L | Key::Plus | Key::DpadRight) && - !mpController->isHold(Key::RStick); -} - -bool Controller::ShouldSaveStateToFile() { - return mpController->isHoldAll(Key::ZL | Key::L | Key::Plus | Key::DpadLeft | Key::RStick); +bool Controller::IsOnlyHolding(u32 mask) { + return mpController->isHoldAll(mask) && !mpController->isHold(~mask); } -bool Controller::ShouldRestoreStateFromFile() { - return mpController->isHoldAll(Key::ZL | Key::L | Key::Plus | Key::DpadRight | Key::RStick); -} - -bool Controller::ShouldSwitchMode() { - return mpController->isHoldAll(Key::DpadDown | Key::ZL | Key::ZR | Key::L | Key::R); -} - -bool Controller::ShouldIncreaseLevel() { - return mpController->isHold(Key::R); -} -bool Controller::ShouldDecreaseLevel() { - return mpController->isHold(Key::L); +u32 Controller::GetHoldKeys() { + return mpController->getHoldMask(); } } // namespace botwsavs::core diff --git a/src/core/Controller.hpp b/src/core/Controller.hpp index 878320c..8372d05 100644 --- a/src/core/Controller.hpp +++ b/src/core/Controller.hpp @@ -10,39 +10,20 @@ namespace botwsavs { namespace core { class Controller { -public: - enum Key : u32 { - B = 1 << 1, - ZL = 1 << 2, - ZR = 1 << 5, - RStick = 1 << 6, - Plus = 1 << 10, - - L = 1 << 13, - R = 1 << 14, - DpadDown = 1 << 17, - DpadLeft = 1 << 18, - DpadRight = 1 << 19 - }; - public: Controller() = default; - ~Controller(); + ~Controller() { mpController = nullptr; } - bool isInitialized() { + bool IsInitialized() { if (!mpController) { TryGetController(); } return mpController != nullptr; } - bool ShouldSaveState(); - bool ShouldRestoreState(); - bool ShouldSaveStateToFile(); - bool ShouldRestoreStateFromFile(); - bool ShouldSwitchMode(); - bool ShouldIncreaseLevel(); - bool ShouldDecreaseLevel(); + bool IsOnlyHolding(u32 mask); + + u32 GetHoldKeys(); private: // Return true if controller is cached succesfully diff --git a/src/core/Key.hpp b/src/core/Key.hpp new file mode 100644 index 0000000..c2e3a1d --- /dev/null +++ b/src/core/Key.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include "types.h" +#include "util/StringBuffer.hpp" + +namespace botwsavs::core { +enum Key : u32 { + A = 0, + B = 1 << 1, + ZL = 1 << 2, + Y = 1 << 3, + X = 1 << 4, + ZR = 1 << 5, + RStick = 1 << 6, + LStick = 1 << 7, + + Minus = (1 << 9) | (1 << 12), + Plus = (1 << 10) | (1 << 11), + + L = 1 << 13, + R = 1 << 14, + + DpadUp = 1 << 16, + DpadDown = 1 << 17, + DpadLeft = 1 << 18, + DpadRight = 1 << 19 +}; + +namespace key { +template +void GetKeyString(u32 mask, util::StringBuffer& outBuffer) { + static_assert(L >= 100, "Key string buffer length should be at least 100"); + outBuffer.Clear(); + if (mask == 0) { + return; + } + if (mask & Key::A) { + outBuffer.SafeAppend("A+"); + } + if (mask & Key::B) { + outBuffer.SafeAppend("B+"); + } + if (mask & Key::X) { + outBuffer.SafeAppend("X+"); + } + if (mask & Key::Y) { + outBuffer.SafeAppend("Y+"); + } + if (mask & Key::L) { + outBuffer.SafeAppend("L+"); + } + if (mask & Key::R) { + outBuffer.SafeAppend("R+"); + } + if (mask & Key::ZL) { + outBuffer.SafeAppend("ZL+"); + } + if (mask & Key::ZR) { + outBuffer.SafeAppend("ZR+"); + } + if (mask & Key::DpadUp) { + outBuffer.SafeAppend("DpadUp+"); + } + if (mask & Key::DpadDown) { + outBuffer.SafeAppend("DpadDown+"); + } + if (mask & Key::DpadLeft) { + outBuffer.SafeAppend("DpadLeft+"); + } + if (mask & Key::DpadRight) { + outBuffer.SafeAppend("DpadRight+"); + } + if (mask & Key::Minus) { + outBuffer.SafeAppend("Minus+"); + } + if (mask & Key::Plus) { + outBuffer.SafeAppend("Plus+"); + } + if (mask & Key::LStick) { + outBuffer.SafeAppend("LStick+"); + } + if (mask & Key::RStick) { + outBuffer.SafeAppend("RStick+"); + } + outBuffer.SafeDeleteEnd(1); +} +} // namespace key +} // namespace botwsavs::core diff --git a/src/core/KeyMgr.cpp b/src/core/KeyMgr.cpp new file mode 100644 index 0000000..cd4f129 --- /dev/null +++ b/src/core/KeyMgr.cpp @@ -0,0 +1,66 @@ +#include "KeyMgr.hpp" +#include "fs/ConfigFile.hpp" +#include "fs/Logger.hpp" + +namespace botwsavs::core { + +bool KeyMgr::DidHoldFor(u32 keys, u32 seconds) { + if (mHoldingKeys != keys) { + mHoldCounter = 0; + mHoldingKeys = keys; + } else { + mHoldCounter++; + } + // 1 tick = 3 frames + // 1s = 30 frames = 10 ticks + u32 ticks = seconds * 10; + if (mHoldCounter >= ticks) { + mHoldCounter = 0; + return true; + } + return false; +} + +void KeyMgr::StartConfigure(u32* configureKey) { + info("Configuring key binding"); + ClearHold(); + mpConfiguringKey = configureKey; + mConfigureCounter = 0; +} + +KeyMgr::ConfigureResult KeyMgr::FinishConfigure(u32 newKey) { + if (newKey == 0) { + mConfigureCounter = 0; + return ConfigureResult::Pending; + } + if (mConfigureCounter < 30) { + mConfigureCounter++; + return ConfigureResult::Pending; + } + + ClearHold(); + if (newKey == 0) { + mpConfiguringKey = nullptr; + return ConfigureResult::FailEmpty; + } + *mpConfiguringKey = newKey; + mpConfiguringKey = nullptr; + infof("New combo: %x", newKey); + return ConfigureResult::Success; +} + +void KeyMgr::Save(fs::ConfigFile& file) const { + file.WriteInteger(named(mKeySave)); + file.WriteInteger(named(mKeySaveFile)); + file.WriteInteger(named(mKeyRestore)); + file.WriteInteger(named(mKeyRestoreFile)); +} + +void KeyMgr::Load(fs::ConfigFile& file) { + file.ReadInteger(&mKeySave); + file.ReadInteger(&mKeySaveFile); + file.ReadInteger(&mKeyRestore); + file.ReadInteger(&mKeyRestoreFile); +} + +} // namespace botwsavs::core diff --git a/src/core/KeyMgr.hpp b/src/core/KeyMgr.hpp new file mode 100644 index 0000000..8cfb986 --- /dev/null +++ b/src/core/KeyMgr.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "Key.hpp" + +namespace botwsavs { + +namespace fs { +class ConfigFile; +} + +namespace core { +// class for managing key combos and key presses +class KeyMgr { +public: + enum class ConfigureResult : u32 { + Success = 0, + Pending = 1, + FailEmpty = 2, + FailRelease = 3, + }; + + bool DidHoldFor(u32 keys, u32 seconds); + void ClearHold() { + mHoldingKeys = 0; + mHoldCounter = 0; + } + void Save(fs::ConfigFile& file) const; + void Load(fs::ConfigFile& file); + + bool IsConfiguring() const { return mpConfiguringKey != nullptr; } + void StartConfigure(u32* configureKey); + ConfigureResult FinishConfigure(u32 newKey); + +public: + // Configurable + u32 mKeySave = Key::ZL | Key::L | Key::Plus | Key::DpadLeft; + u32 mKeySaveFile = Key::ZL | Key::L | Key::Plus | Key::DpadLeft | Key::RStick; + u32 mKeyRestore = Key::ZL | Key::L | Key::Plus | Key::DpadRight; + u32 mKeyRestoreFile = Key::ZL | Key::L | Key::Plus | Key::DpadRight | Key::RStick; + + // Not configurable + u32 mKeySwitchMode = Key::DpadDown | Key::ZL | Key::ZR | Key::L | Key::R; + u32 mKeyIncreaseLevel = Key::R; + u32 mKeyDecreaseLevel = Key::L; + +private: + u32 mHoldCounter = 0; + u32 mHoldingKeys = 0; + u32* mpConfiguringKey = nullptr; + u32 mConfigureCounter = 0; +}; + +} // namespace core +} // namespace botwsavs diff --git a/src/core/State.cpp b/src/core/State.cpp index e608536..71bbbe1 100644 --- a/src/core/State.cpp +++ b/src/core/State.cpp @@ -3,37 +3,69 @@ namespace botwsavs::core { bool State::ReadFromGame(u32 level) { - mError = Error::None; + ClearError(); switch (level) { case 3: - ReadLevel3(); + mStorageLevel3.ReadFromGame(); // fall through case 2: - ReadLevel2(); + mStorageLevel2.ReadFromGame(); // fall through case 1: - ReadLevel1(); + mStorageLevel1.ReadFromGame(); } mLevel = level; - return mError == Error::None; + return !HasAnyError(); } bool State::WriteToGame(u32 level) { if (mLevel < level) { return false; } - mError = Error::None; + ClearError(); switch (level) { case 3: - WriteLevel3(); + mStorageLevel3.WriteToGame(); // fall through case 2: - WriteLevel2(); + mStorageLevel2.WriteToGame(); // fall through case 1: - WriteLevel1(); + mStorageLevel1.WriteToGame(); } - return mError == Error::None; + return !HasAnyError(); +} +bool State::CanReadFromFile() { + return fs::ConfigFile(RESTORE_TXT_PATH).Exists(); +} +bool State::ReadFromFile() { + return fs::ConfigFile(RESTORE_TXT_PATH).Load(*this); +} +bool State::WriteToFile() const { + return fs::ConfigFile(LATEST_TXT_PATH).Save(*this); +} + +void State::Load(fs::ConfigFile& file) { + u32 version = 0; + file.ReadInteger(&version); + file.ReadInteger(&mLevel); + + if (version < 1 || version > STATE_SAVE_FILE_VERSION) { + errorf("Bad version: %d", version); + return; + } + + mStorageLevel1.ReadFromFile(file, version); + mStorageLevel2.ReadFromFile(file, version); + mStorageLevel3.ReadFromFile(file, version); +} +void State::Save(fs::ConfigFile& file) const { + file.WriteInteger("version", STATE_SAVE_FILE_VERSION); + file.WriteInteger(named(mLevel)); + + mStorageLevel1.WriteToFile(file); + mStorageLevel2.WriteToFile(file); + mStorageLevel3.WriteToFile(file); } } // namespace botwsavs::core diff --git a/src/core/State.hpp b/src/core/State.hpp index a3f82b1..c07b7f6 100644 --- a/src/core/State.hpp +++ b/src/core/State.hpp @@ -1,92 +1,55 @@ #pragma once -#include "NamedValue.hpp" +#include "fs/ConfigFile.hpp" +#include "util/NamedValue.hpp" + +#include "StateLevel1.hpp" +#include "StateLevel2.hpp" +#include "StateLevel3.hpp" + #include "types.h" +#define LATEST_TXT_PATH "sd:/botwsavs/latest.txt" +#define RESTORE_TXT_PATH "sd:/botwsavs/restore.txt" +#define STATE_SAVE_FILE_VERSION 4 + namespace botwsavs::core { -class State { +class State : fs::Config { public: - enum Error : u32 { - None = 0, - Pointer = 1, - NotEquipped = 1 << 1, - DifferentName = 1 << 2, - }; + u32 GetErrorMask() const { + return mStorageLevel1.GetErrorMask() | mStorageLevel2.GetErrorMask() | + mStorageLevel3.GetErrorMask(); + } + bool HasError(StateError mask) const { return (GetErrorMask() & mask) != 0; } + bool HasAnyError() const { return GetErrorMask() != StateError::None; } - Error GetError() { return mError; } - bool HasError(Error mask) { return (mError & mask) != 0; } + u32 GetLevel() const { return mLevel; } // Read from game memory bool ReadFromGame(u32 level); // Write to game memory bool WriteToGame(u32 level); + // Read from file + bool ReadFromFile(); + bool CanReadFromFile(); + // Write to file + bool WriteToFile() const; private: - Error mError = Error::None; - void SetError(Error mask) { mError = static_cast(mError | mask); } + void ClearError() { + mStorageLevel1.ClearError(); + mStorageLevel2.ClearError(); + mStorageLevel3.ClearError(); + } -public: - u32 mLevel = 0; // 0 = nothing stored - - // Level 1: Position, Health, Stam, Runes, Camera -public: - u32 mHealth; - f32 mStamina; - f32 mHavokPosition[3]; - f32 mMainPositionMatrix[12]; - f32 mCameraPanMatrix[12]; - f32 mCameraZoom; - f32 mCameraTilt; - // Extras: - // reset rune cooldown - // reset fall damage + // internal functions for read/write file + void Load(fs::ConfigFile& file) override; + void Save(fs::ConfigFile& file) const override; private: - void ReadLevel1(); - void WriteLevel1(); - - // Level 2: Durability -public: - NamedValue mMenuEquippedArrow{sInvalid}; - NamedValue mMenuEquippedWeapon{sInvalid}; - NamedValue mMenuEquippedBow{sInvalid}; - NamedValue mMenuEquippedShield{sInvalid}; - NamedValue mOverworldEquippedWeapon{sInvalid}; - NamedValue mOverworldEquippedBow{sInvalid}; - NamedValue mOverworldEquippedShield{sInvalid}; - void ReadLevel2(); - void WriteLevel2(); - - // Level 3: All -public: - f32 mTimeOfDayPaused; - f32 mTimeOfDayUnpaused; - f32 mBloodMoonTimer; - f32 mTemperatureDamageTimer; - f32 mFlameTimer; - f32 mGaleTimer; - f32 mFuryTimer; - f32 mProtectionTimer; - f32 mGraceTimer; - u32 mAbilityUses[3]; - f32 mMasterSwordCooldown; - - f32 mSpeedPotionTimer1; - f32 mSpeedPotionTimer2; - f32 mSpeedPotionTimer3; - f32 mAttackPotionTimer; - f32 mDefensePotionTimer; - f32 mHeatResistPotionTimer; - f32 mColdResistPotionTimer; - f32 mFlameResistPotionTimer; - f32 mShockResistPotionTimer; - f32 mStealthPotionTimer; - -private: - void ReadLevel3(); - void WriteLevel3(); - -private: - static const u32 sInvalid = 0xFFFFFFFF; + u32 mLevel = 0; // 0 = nothing stored + StateLevel1 mStorageLevel1; + StateLevel2 mStorageLevel2; + StateLevel3 mStorageLevel3; }; } // namespace botwsavs::core diff --git a/src/core/StateLevel1.cpp b/src/core/StateLevel1.cpp index ef47ebd..9165c96 100644 --- a/src/core/StateLevel1.cpp +++ b/src/core/StateLevel1.cpp @@ -1,33 +1,55 @@ -#include "State.hpp" -#include "StateMacros.hpp" -#include "fs/Logger.hpp" +#include "StateLevel1.hpp" +#include "fs/ConfigFile.hpp" #include "mem/GamePtr.h" namespace botwsavs::core { -void State::ReadLevel1() { - GameReadEz(Health); - GameReadEz(Stamina); - GameReadArrayEz(HavokPosition, 3); - GameReadArrayEz(MainPositionMatrix, 12); - GameReadArrayEz(CameraPanMatrix, 12); - GameReadEz(CameraZoom); - GameReadEz(CameraTilt); +void StateLevel1::ReadFromGame() { + ReadMemory(named(mem::GamePtr::Health()), &mHealth); + ReadMemory(named(mem::GamePtr::Stamina()), &mStamina); + ReadMemoryArray(named(mem::GamePtr::HavokPosition()), mHavokPosition, 3); + ReadMemoryArray(named(mem::GamePtr::MainPositionMatrix()), mMainPositionMatrix, 12); + ReadMemoryArray(named(mem::GamePtr::CameraPanMatrix()), mCameraPanMatrix, 12); + ReadMemory(named(mem::GamePtr::CameraZoom()), &mCameraZoom); + ReadMemory(named(mem::GamePtr::CameraTilt()), &mCameraTilt); } -void State::WriteLevel1() { - GameWriteEz(Health); - GameWriteEz(Stamina); - GameWriteArrayEz(HavokPosition, 3); - GameWriteArrayEz(MainPositionMatrix, 12); - GameWriteArrayEz(CameraPanMatrix, 12); - GameWriteEz(CameraZoom); - GameWriteEz(CameraTilt); +void StateLevel1::WriteToGame() { + WriteMemory(named(mem::GamePtr::Health()), mHealth); + WriteMemory(named(mem::GamePtr::Stamina()), mStamina); + WriteMemoryArray(named(mem::GamePtr::HavokPosition()), mHavokPosition, 3); + WriteMemoryArray(named(mem::GamePtr::MainPositionMatrix()), mMainPositionMatrix, 12); + WriteMemoryArray(named(mem::GamePtr::CameraPanMatrix()), mCameraPanMatrix, 12); + WriteMemory(named(mem::GamePtr::CameraZoom()), mCameraZoom); + WriteMemory(named(mem::GamePtr::CameraTilt()), mCameraTilt); // extras - GameWrite(GamePtr::RoundBombCooldown, 360.0F); - GameWrite(GamePtr::SquareBombCooldown, 360.0F); - GameWrite(GamePtr::StasisCooldown, 1.0F); + WriteMemory(named(mem::GamePtr::RoundBombCooldown()), 360.0F); + WriteMemory(named(mem::GamePtr::SquareBombCooldown()), 360.0F); + WriteMemory(named(mem::GamePtr::StasisCooldown()), 1.0F); +} + +void StateLevel1::ReadFromFile(fs::ConfigFile& file, u32 version) { + if (version < 1) { + return; + } + file.ReadInteger(&mHealth); + file.ReadFloat(&mStamina); + file.ReadFloatArray(mHavokPosition, 3); + file.ReadFloatArray(mMainPositionMatrix, 12); + file.ReadFloatArray(mCameraPanMatrix, 12); + file.ReadFloat(&mCameraZoom); + file.ReadFloat(&mCameraTilt); +} + +void StateLevel1::WriteToFile(fs::ConfigFile& file) const { + file.WriteInteger(named(mHealth)); + file.WriteFloat(named(mStamina)); + file.WriteFloatArray(named(mHavokPosition), 3); + file.WriteFloatArray(named(mMainPositionMatrix), 12); + file.WriteFloatArray(named(mCameraPanMatrix), 12); + file.WriteFloat(named(mCameraZoom)); + file.WriteFloat(named(mCameraTilt)); } } // namespace botwsavs::core diff --git a/src/core/StateLevel1.hpp b/src/core/StateLevel1.hpp new file mode 100644 index 0000000..16ae62d --- /dev/null +++ b/src/core/StateLevel1.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "StateStorage.hpp" + +namespace botwsavs::core { + +class StateLevel1 : public StateStorage { +public: + void ReadFromGame() override; + void WriteToGame() override; + void ReadFromFile(fs::ConfigFile& file, u32 version) override; + void WriteToFile(fs::ConfigFile& file) const override; + +private: + u32 mHealth; + f32 mStamina; + f32 mHavokPosition[3]; + f32 mMainPositionMatrix[12]; + f32 mCameraPanMatrix[12]; + f32 mCameraZoom; + f32 mCameraTilt; + // Extras: + // reset rune cooldown + // TODO reset fall damage +}; + +} // namespace botwsavs::core diff --git a/src/core/StateLevel2.cpp b/src/core/StateLevel2.cpp index 16f7f3c..10c5e3d 100644 --- a/src/core/StateLevel2.cpp +++ b/src/core/StateLevel2.cpp @@ -1,214 +1,163 @@ #include #include #include -#include "State.hpp" -#include "StateMacros.hpp" + +#include "fs/ConfigFile.hpp" #include "fs/Logger.hpp" #include "mem/GamePtr.h" +#include "StateLevel2.hpp" namespace botwsavs::core { -void State::ReadLevel2() { - GameUseSafePtrOrError(uking::ui::PauseMenuDataMgr::instance(), uking::ui::PauseMenuDataMgr, - pPauseMenuDataMgr) { - // update equipped weapons - pPauseMenuDataMgr->updateEquippedItemArray(); - - // Save arrow, weapon, bow, shield - GameUseSafePtr(pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Arrow), - uking::ui::PouchItem, pEquippedArrow) { - mMenuEquippedArrow.Set(pEquippedArrow->getName(), pEquippedArrow->getValue()); - } - else { - mMenuEquippedArrow.ClearName(); - SetError(Error::NotEquipped); - warn("Cannot read equipped arrow count"); - } - - GameUseSafePtr(pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Sword), - uking::ui::PouchItem, pEquippedWeapon) { - mMenuEquippedWeapon.Set(pEquippedWeapon->getName(), pEquippedWeapon->getValue()); - } - else { - mMenuEquippedWeapon.ClearName(); - SetError(Error::NotEquipped); - warn("Cannot read equipped weapon"); - } - - GameUseSafePtr(pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Bow), - uking::ui::PouchItem, pEquippedBow) { - mMenuEquippedBow.Set(pEquippedBow->getName(), pEquippedBow->getValue()); - } - else { - mMenuEquippedBow.ClearName(); - SetError(Error::NotEquipped); - warn("Cannot read equipped bow"); - } - - GameUseSafePtr(pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Shield), - uking::ui::PouchItem, pEquippedShield) { - mMenuEquippedShield.Set(pEquippedShield->getName(), pEquippedShield->getValue()); - } - else { - mMenuEquippedShield.ClearName(); - SetError(Error::NotEquipped); - warn("Cannot read equipped shield"); - } +void StateLevel2::ReadInventoryEquipment(const char* name, uking::ui::PouchItem* pItem, + util::NamedValue& value) { + if (mem::PtrLooksSafe(pItem)) { + value.Set(pItem->getName(), pItem->getValue()); + } else { + value.ClearName(); + SetError(StateError::NotEquipped); + warnf("Cannot read equipped %s count", name); } +} - GameUsePtr(mem::GamePtr::OverworldWeaponActor(), ksys::act::BaseProc, pActorWeapon) { - u32 overworldWeaponDurability; - GameRead(GamePtr::OverworldWeaponDurability, overworldWeaponDurability) { - mOverworldEquippedWeapon.Set(pActorWeapon->getName(), overworldWeaponDurability); - } - else { - mOverworldEquippedWeapon.ClearName(); - SetError(Error::Pointer); - warn("Cannot read equipped overworld weapon durability"); +void StateLevel2::WriteInventoryEquipment(const char* name, uking::ui::PouchItem* pItem, + const util::NamedValue& value) { + if (mem::PtrLooksSafe(pItem)) { + if (value.NameMatches(pItem->getName())) { + pItem->setValue(value.GetValue()); + } else { + SetError(StateError::DifferentName); + warnf("Cannot restore equipped %s count: different name", name); } + } else { + SetError(StateError::NotEquipped); + warnf("Cannot restore equipped %s count", name); } - else { - mOverworldEquippedWeapon.ClearName(); - SetError(Error::NotEquipped); - warn("Cannot read equipped overworld weapon actor"); - } +} - GameUsePtr(mem::GamePtr::OverworldBowActor(), ksys::act::BaseProc, pActorBow) { - u32 overworldBowDurability; - GameRead(GamePtr::OverworldBowDurability, overworldBowDurability) { - mOverworldEquippedBow.Set(pActorBow->getName(), overworldBowDurability); - } - else { - mOverworldEquippedBow.ClearName(); - SetError(Error::Pointer); - warn("Cannot read equipped overworld bow durability"); - } - } - else { - mOverworldEquippedBow.ClearName(); - SetError(Error::NotEquipped); - warn("Cannot read equipped overworld bow actor"); +void StateLevel2::ReadOverworldEquipment(const char* name, + const mem::SafePtr& safepActor, + const mem::SafePtr& safepDurability, + util::NamedValue& value) { + ksys::act::BaseProc* pActor; + if (safepActor.TakePtr(&pActor)) { + u32 durability; + if (safepDurability.Get(&durability)) { + value.Set(pActor->getName(), durability); + } else { + value.ClearName(); + SetError(StateError::Pointer); + warnf("Cannot read equipped overworld %s durability", name); + } + } else { + value.ClearName(); + SetError(StateError::NotEquipped); + warnf("Cannot read equipped overworld %s actor", name); } +} - GameUsePtr(mem::GamePtr::OverworldShieldActor(), ksys::act::BaseProc, pActorShield) { - u32 overworldShieldDurability; - GameRead(GamePtr::OverworldShieldDurability, overworldShieldDurability) { - mOverworldEquippedShield.Set(pActorShield->getName(), overworldShieldDurability); - } - else { - mOverworldEquippedShield.ClearName(); - SetError(Error::Pointer); - warn("Cannot read equipped overworld shield durability"); +void StateLevel2::WriteOverworldEquipment(const char* name, + const mem::SafePtr& safepActor, + const mem::SafePtr& safepDurability, + const util::NamedValue& value) { + ksys::act::BaseProc* pActor; + if (safepActor.TakePtr(&pActor)) { + if (value.NameMatches(pActor->getName())) { + if (!safepDurability.Set(value.GetValue())) { + SetError(StateError::Pointer); + warnf("Cannot restore equipped overworld %s: pointer error", name); + } + } else { + SetError(StateError::DifferentName); + warnf("Cannot restore equipped overworld %s: different name", name); } - } - else { - mOverworldEquippedShield.ClearName(); - SetError(Error::NotEquipped); - warn("Cannot read equipped overworld shield actor"); + } else { + SetError(StateError::NotEquipped); + warnf("Cannot restore equipped overworld %s actor", name); } } -void State::WriteLevel2() { - GameUseSafePtr(uking::ui::PauseMenuDataMgr::instance(), uking::ui::PauseMenuDataMgr, - pPauseMenuDataMgr) { + +void StateLevel2::ReadFromGame() { + uking::ui::PauseMenuDataMgr* pPauseMenuDataMgr; + if (TakePtrOrError(named(mem::SafePtr(uking::ui::PauseMenuDataMgr::instance())), + &pPauseMenuDataMgr)) { // update equipped weapons pPauseMenuDataMgr->updateEquippedItemArray(); - // Restore each of them - GameUseSafePtr(pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Arrow), - uking::ui::PouchItem, pEquippedArrow) { - if (mMenuEquippedArrow.NameMatches(pEquippedArrow->getName())) { - pEquippedArrow->setValue(mMenuEquippedArrow.GetValue()); - } else { - SetError(Error::DifferentName); - warn("Cannot restore equipped arrow count: different name"); - } - } - else { - SetError(Error::NotEquipped); - warn("Cannot restore equipped arrow count"); - } + // Save arrow, weapon, bow, shield + ReadInventoryEquipment( + "arrow", pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Arrow), + mMenuEquippedArrow); + ReadInventoryEquipment( + "weapon", pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Sword), + mMenuEquippedWeapon); + ReadInventoryEquipment("bow", + pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Bow), + mMenuEquippedBow); + ReadInventoryEquipment( + "shield", pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Shield), + mMenuEquippedShield); + } - GameUseSafePtr(pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Sword), - uking::ui::PouchItem, pEquippedWeapon) { - if (mMenuEquippedWeapon.NameMatches(pEquippedWeapon->getName())) { - pEquippedWeapon->setValue(mMenuEquippedWeapon.GetValue()); - } else { - SetError(Error::DifferentName); - warn("Cannot restore equipped weapon: different name"); - } - } - else { - SetError(Error::NotEquipped); - warn("Cannot restore equipped weapon"); - } + ReadOverworldEquipment("weapon", mem::GamePtr::OverworldWeaponActor(), + mem::GamePtr::OverworldWeaponDurability(), mOverworldEquippedWeapon); + ReadOverworldEquipment("bow", mem::GamePtr::OverworldBowActor(), + mem::GamePtr::OverworldBowDurability(), mOverworldEquippedBow); + ReadOverworldEquipment("shield", mem::GamePtr::OverworldShieldActor(), + mem::GamePtr::OverworldShieldDurability(), mOverworldEquippedShield); +} - GameUseSafePtr(pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Bow), - uking::ui::PouchItem, pEquippedBow) { - if (mMenuEquippedBow.NameMatches(pEquippedBow->getName())) { - pEquippedBow->setValue(mMenuEquippedBow.GetValue()); - } else { - SetError(Error::DifferentName); - warn("Cannot restore equipped bow: different name"); - } - } - else { - SetError(Error::NotEquipped); - warn("Cannot restore equipped bow"); - } +void StateLevel2::WriteToGame() { + uking::ui::PauseMenuDataMgr* pPauseMenuDataMgr; + if (TakePtrOrError(named(mem::SafePtr(uking::ui::PauseMenuDataMgr::instance())), + &pPauseMenuDataMgr)) { + // update equipped weapons + pPauseMenuDataMgr->updateEquippedItemArray(); - GameUseSafePtr(pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Shield), - uking::ui::PouchItem, pEquippedShield) { - if (mMenuEquippedShield.NameMatches(pEquippedShield->getName())) { - pEquippedShield->setValue(mMenuEquippedShield.GetValue()); - } else { - SetError(Error::DifferentName); - warn("Cannot restore equipped shield: different name"); - } - } - else { - SetError(Error::NotEquipped); - warn("Cannot restore equipped shield"); - } + // Restore arrow, weapon, bow, shield + WriteInventoryEquipment( + "arrow", pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Arrow), + mMenuEquippedArrow); + WriteInventoryEquipment( + "weapon", pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Sword), + mMenuEquippedWeapon); + WriteInventoryEquipment("bow", + pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Bow), + mMenuEquippedBow); + WriteInventoryEquipment( + "shield", pPauseMenuDataMgr->getEquippedWeapon(uking::ui::PouchItemType::Shield), + mMenuEquippedShield); } - GameUsePtr(mem::GamePtr::OverworldWeaponActor(), ksys::act::BaseProc, pActorWeapon) { - if (mOverworldEquippedWeapon.NameMatches(pActorWeapon->getName())) { - GameWrite(GamePtr::OverworldWeaponDurability, mOverworldEquippedWeapon.GetValue()); - } else { - SetError(Error::DifferentName); - warn("Cannot restore equipped overworld weapon: different name"); - } - } - else { - SetError(Error::NotEquipped); - warn("Cannot restore equipped overworld weapon actor"); - } + WriteOverworldEquipment("weapon", mem::GamePtr::OverworldWeaponActor(), + mem::GamePtr::OverworldWeaponDurability(), mOverworldEquippedWeapon); + WriteOverworldEquipment("bow", mem::GamePtr::OverworldBowActor(), + mem::GamePtr::OverworldBowDurability(), mOverworldEquippedBow); + WriteOverworldEquipment("shield", mem::GamePtr::OverworldShieldActor(), + mem::GamePtr::OverworldShieldDurability(), mOverworldEquippedShield); +} - GameUsePtr(mem::GamePtr::OverworldBowActor(), ksys::act::BaseProc, pActorBow) { - if (mOverworldEquippedBow.NameMatches(pActorBow->getName())) { - GameWrite(GamePtr::OverworldBowDurability, mOverworldEquippedBow.GetValue()); - } else { - SetError(Error::DifferentName); - warn("Cannot restore equipped overworld bow: different name"); - } - } - else { - SetError(Error::NotEquipped); - warn("Cannot restore equipped overworld bow actor"); +void StateLevel2::ReadFromFile(fs::ConfigFile& file, u32 version) { + if (version < 2) { + return; } + file.ReadNamedInteger(mMenuEquippedArrow); + file.ReadNamedInteger(mMenuEquippedWeapon); + file.ReadNamedInteger(mMenuEquippedBow); + file.ReadNamedInteger(mMenuEquippedShield); + file.ReadNamedInteger(mOverworldEquippedWeapon); + file.ReadNamedInteger(mOverworldEquippedBow); + file.ReadNamedInteger(mOverworldEquippedShield); +} - GameUsePtr(mem::GamePtr::OverworldShieldActor(), ksys::act::BaseProc, pActorShield) { - if (mOverworldEquippedShield.NameMatches(pActorShield->getName())) { - GameWrite(GamePtr::OverworldShieldDurability, mOverworldEquippedShield.GetValue()); - } else { - SetError(Error::DifferentName); - warn("Cannot restore equipped overworld shield: different name"); - } - } - else { - SetError(Error::NotEquipped); - warn("Cannot restore equipped overworld shield actor"); - } +void StateLevel2::WriteToFile(fs::ConfigFile& file) const { + file.WriteNamedInteger(named(mMenuEquippedArrow)); + file.WriteNamedInteger(named(mMenuEquippedWeapon)); + file.WriteNamedInteger(named(mMenuEquippedBow)); + file.WriteNamedInteger(named(mMenuEquippedShield)); + file.WriteNamedInteger(named(mOverworldEquippedWeapon)); + file.WriteNamedInteger(named(mOverworldEquippedBow)); + file.WriteNamedInteger(named(mOverworldEquippedShield)); } } // namespace botwsavs::core diff --git a/src/core/StateLevel2.hpp b/src/core/StateLevel2.hpp new file mode 100644 index 0000000..8a353d7 --- /dev/null +++ b/src/core/StateLevel2.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "StateStorage.hpp" +#include "util/NamedValue.hpp" + +namespace uking::ui { +class PouchItem; +} + +namespace ksys::act { +class BaseProc; +} +namespace botwsavs::core { + +class StateLevel2 : public StateStorage { +public: + void ReadFromGame() override; + void WriteToGame() override; + void ReadFromFile(fs::ConfigFile& file, u32 version) override; + void WriteToFile(fs::ConfigFile& file) const override; + +private: + void ReadInventoryEquipment(const char* name, uking::ui::PouchItem* pItem, + util::NamedValue& value); + void WriteInventoryEquipment(const char* name, uking::ui::PouchItem* pItem, + const util::NamedValue& value); + void ReadOverworldEquipment(const char* name, + const mem::SafePtr& safepActor, + const mem::SafePtr& safepDurability, + util::NamedValue& value); + void WriteOverworldEquipment(const char* name, + const mem::SafePtr& safepActor, + const mem::SafePtr& safepDurability, + const util::NamedValue& value); + util::NamedValue mMenuEquippedArrow{sInvalid}; + util::NamedValue mMenuEquippedWeapon{sInvalid}; + util::NamedValue mMenuEquippedBow{sInvalid}; + util::NamedValue mMenuEquippedShield{sInvalid}; + util::NamedValue mOverworldEquippedWeapon{sInvalid}; + util::NamedValue mOverworldEquippedBow{sInvalid}; + util::NamedValue mOverworldEquippedShield{sInvalid}; +}; + +} // namespace botwsavs::core diff --git a/src/core/StateLevel3.cpp b/src/core/StateLevel3.cpp index 87188cf..76f9b24 100644 --- a/src/core/StateLevel3.cpp +++ b/src/core/StateLevel3.cpp @@ -1,56 +1,137 @@ -#include "State.hpp" -#include "StateMacros.hpp" +#include +#include + +#include "fs/ConfigFile.hpp" #include "fs/Logger.hpp" #include "mem/GamePtr.h" +#include "StateLevel3.hpp" + namespace botwsavs::core { -void State::ReadLevel3() { - GameReadEz(TimeOfDayPaused); - GameReadEz(TimeOfDayUnpaused); - GameReadEz(BloodMoonTimer); - GameReadEz(TemperatureDamageTimer); - GameReadEz(FlameTimer); - GameReadEz(GaleTimer); - GameReadEz(FuryTimer); - GameReadEz(ProtectionTimer); - GameReadEz(GraceTimer); - GameReadArrayEz(AbilityUses, 3); - GameReadEz(MasterSwordCooldown); - GameReadEz(SpeedPotionTimer1); - GameReadEz(SpeedPotionTimer2); - GameReadEz(SpeedPotionTimer3); - GameReadEz(AttackPotionTimer); - GameReadEz(DefensePotionTimer); - GameReadEz(HeatResistPotionTimer); - GameReadEz(ColdResistPotionTimer); - GameReadEz(FlameResistPotionTimer); - GameReadEz(ShockResistPotionTimer); - GameReadEz(StealthPotionTimer); +void StateLevel3::ReadFromGame() { + ReadMemory(named(mem::GamePtr::TimeOfDayPaused()), &mTimeOfDayPaused); + ReadMemory(named(mem::GamePtr::TimeOfDayUnpaused()), &mTimeOfDayUnpaused); + ReadMemory(named(mem::GamePtr::BloodMoonTimer()), &mBloodMoonTimer); + ReadMemory(named(mem::GamePtr::TemperatureDamageTimer()), &mTemperatureDamageTimer); + ReadMemory(named(mem::GamePtr::FlameTimer()), &mFlameTimer); + ReadMemory(named(mem::GamePtr::GaleTimer()), &mGaleTimer); + ReadMemory(named(mem::GamePtr::FuryTimer()), &mFuryTimer); + ReadMemory(named(mem::GamePtr::ProtectionTimer()), &mProtectionTimer); + ReadMemory(named(mem::GamePtr::GraceTimer()), &mGraceTimer); + ReadMemoryArray(named(mem::GamePtr::AbilityUses()), mAbilityUses, 3); + ReadMemory(named(mem::GamePtr::MasterSwordCooldown()), &mMasterSwordCooldown); + ReadMemory(named(mem::GamePtr::SpeedPotionTimer1()), &mSpeedPotionTimer1); + ReadMemory(named(mem::GamePtr::SpeedPotionTimer2()), &mSpeedPotionTimer2); + ReadMemory(named(mem::GamePtr::SpeedPotionTimer3()), &mSpeedPotionTimer3); + ReadMemory(named(mem::GamePtr::AttackPotionTimer()), &mAttackPotionTimer); + ReadMemory(named(mem::GamePtr::DefensePotionTimer()), &mDefensePotionTimer); + ReadMemory(named(mem::GamePtr::HeatResistPotionTimer()), &mHeatResistPotionTimer); + ReadMemory(named(mem::GamePtr::ColdResistPotionTimer()), &mColdResistPotionTimer); + ReadMemory(named(mem::GamePtr::FlameResistPotionTimer()), &mFlameResistPotionTimer); + ReadMemory(named(mem::GamePtr::ShockResistPotionTimer()), &mShockResistPotionTimer); + ReadMemory(named(mem::GamePtr::StealthPotionTimer()), &mStealthPotionTimer); + + uking::ui::PauseMenuDataMgr* pPauseMenuDataMgr; + if (TakePtrOrError(named(mem::SafePtr(uking::ui::PauseMenuDataMgr::instance())), + &pPauseMenuDataMgr)) { + // Save mCount Offset + s32 count = pPauseMenuDataMgr->getItems().size(); + s32 actualSize = pPauseMenuDataMgr->getItems().sizeSlow(); + mCountOffset = actualSize - count; + } +} + +void StateLevel3::WriteToGame() { + WriteMemory(named(mem::GamePtr::TimeOfDayPaused()), mTimeOfDayPaused); + WriteMemory(named(mem::GamePtr::TimeOfDayUnpaused()), mTimeOfDayUnpaused); + WriteMemory(named(mem::GamePtr::BloodMoonTimer()), mBloodMoonTimer); + WriteMemory(named(mem::GamePtr::TemperatureDamageTimer()), mTemperatureDamageTimer); + WriteMemory(named(mem::GamePtr::FlameTimer()), mFlameTimer); + WriteMemory(named(mem::GamePtr::GaleTimer()), mGaleTimer); + WriteMemory(named(mem::GamePtr::FuryTimer()), mFuryTimer); + WriteMemory(named(mem::GamePtr::ProtectionTimer()), mProtectionTimer); + WriteMemory(named(mem::GamePtr::GraceTimer()), mGraceTimer); + WriteMemoryArray(named(mem::GamePtr::AbilityUses()), mAbilityUses, 3); + WriteMemory(named(mem::GamePtr::MasterSwordCooldown()), mMasterSwordCooldown); + WriteMemory(named(mem::GamePtr::SpeedPotionTimer1()), mSpeedPotionTimer1); + WriteMemory(named(mem::GamePtr::SpeedPotionTimer2()), mSpeedPotionTimer2); + WriteMemory(named(mem::GamePtr::SpeedPotionTimer3()), mSpeedPotionTimer3); + WriteMemory(named(mem::GamePtr::AttackPotionTimer()), mAttackPotionTimer); + WriteMemory(named(mem::GamePtr::DefensePotionTimer()), mDefensePotionTimer); + WriteMemory(named(mem::GamePtr::HeatResistPotionTimer()), mHeatResistPotionTimer); + WriteMemory(named(mem::GamePtr::ColdResistPotionTimer()), mColdResistPotionTimer); + WriteMemory(named(mem::GamePtr::FlameResistPotionTimer()), mFlameResistPotionTimer); + WriteMemory(named(mem::GamePtr::ShockResistPotionTimer()), mShockResistPotionTimer); + WriteMemory(named(mem::GamePtr::StealthPotionTimer()), mStealthPotionTimer); + + uking::ui::PauseMenuDataMgr* pPauseMenuDataMgr; + if (TakePtrOrError(named(mem::SafePtr(uking::ui::PauseMenuDataMgr::instance())), + &pPauseMenuDataMgr)) { + // Set mCount Offset + s32 actualSize = pPauseMenuDataMgr->getItems().sizeSlow(); + s32 count = actualSize - mCountOffset; + pPauseMenuDataMgr->getItems().setSize(count); + } +} + +void StateLevel3::ReadFromFile(fs::ConfigFile& file, u32 version) { + if (version < 3) { + return; + } + file.ReadFloat(&mTimeOfDayPaused); + file.ReadFloat(&mTimeOfDayUnpaused); + file.ReadFloat(&mBloodMoonTimer); + file.ReadFloat(&mTemperatureDamageTimer); + file.ReadFloat(&mFlameTimer); + file.ReadFloat(&mGaleTimer); + file.ReadFloat(&mFuryTimer); + file.ReadFloat(&mProtectionTimer); + file.ReadFloat(&mGraceTimer); + file.ReadIntegerArray(mAbilityUses, 3); + file.ReadFloat(&mMasterSwordCooldown); + file.ReadFloat(&mSpeedPotionTimer1); + file.ReadFloat(&mSpeedPotionTimer2); + file.ReadFloat(&mSpeedPotionTimer3); + file.ReadFloat(&mAttackPotionTimer); + file.ReadFloat(&mDefensePotionTimer); + file.ReadFloat(&mHeatResistPotionTimer); + file.ReadFloat(&mColdResistPotionTimer); + file.ReadFloat(&mFlameResistPotionTimer); + file.ReadFloat(&mShockResistPotionTimer); + file.ReadFloat(&mStealthPotionTimer); + + if (version < 4) { + return; + } + // Version 4 + file.ReadInteger(&mCountOffset); } -void State::WriteLevel3() { - GameWriteEz(TimeOfDayPaused); - GameWriteEz(TimeOfDayUnpaused); - GameWriteEz(BloodMoonTimer); - GameWriteEz(TemperatureDamageTimer); - GameWriteEz(FlameTimer); - GameWriteEz(GaleTimer); - GameWriteEz(FuryTimer); - GameWriteEz(ProtectionTimer); - GameWriteEz(GraceTimer); - GameWriteArrayEz(AbilityUses, 3); - GameWriteEz(MasterSwordCooldown); - GameWriteEz(SpeedPotionTimer1); - GameWriteEz(SpeedPotionTimer2); - GameWriteEz(SpeedPotionTimer3); - GameWriteEz(AttackPotionTimer); - GameWriteEz(DefensePotionTimer); - GameWriteEz(HeatResistPotionTimer); - GameWriteEz(ColdResistPotionTimer); - GameWriteEz(FlameResistPotionTimer); - GameWriteEz(ShockResistPotionTimer); - GameWriteEz(StealthPotionTimer); +void StateLevel3::WriteToFile(fs::ConfigFile& file) const { + file.WriteFloat(named(mTimeOfDayPaused)); + file.WriteFloat(named(mTimeOfDayUnpaused)); + file.WriteFloat(named(mBloodMoonTimer)); + file.WriteFloat(named(mTemperatureDamageTimer)); + file.WriteFloat(named(mFlameTimer)); + file.WriteFloat(named(mGaleTimer)); + file.WriteFloat(named(mFuryTimer)); + file.WriteFloat(named(mProtectionTimer)); + file.WriteFloat(named(mGraceTimer)); + file.WriteIntegerArray(named(mAbilityUses), 3); + file.WriteFloat(named(mMasterSwordCooldown)); + file.WriteFloat(named(mSpeedPotionTimer1)); + file.WriteFloat(named(mSpeedPotionTimer2)); + file.WriteFloat(named(mSpeedPotionTimer3)); + file.WriteFloat(named(mAttackPotionTimer)); + file.WriteFloat(named(mDefensePotionTimer)); + file.WriteFloat(named(mHeatResistPotionTimer)); + file.WriteFloat(named(mColdResistPotionTimer)); + file.WriteFloat(named(mFlameResistPotionTimer)); + file.WriteFloat(named(mShockResistPotionTimer)); + file.WriteFloat(named(mStealthPotionTimer)); + // Version 4 + file.WriteInteger(named(mCountOffset)); } } // namespace botwsavs::core diff --git a/src/core/StateLevel3.hpp b/src/core/StateLevel3.hpp new file mode 100644 index 0000000..0e8ab8c --- /dev/null +++ b/src/core/StateLevel3.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "StateStorage.hpp" + +namespace botwsavs::core { + +class StateLevel3 : public StateStorage { +public: + void ReadFromGame() override; + void WriteToGame() override; + void ReadFromFile(fs::ConfigFile& file, u32 version) override; + void WriteToFile(fs::ConfigFile& file) const override; + +private: + f32 mTimeOfDayPaused; + f32 mTimeOfDayUnpaused; + f32 mBloodMoonTimer; + f32 mTemperatureDamageTimer; + f32 mFlameTimer; + f32 mGaleTimer; + f32 mFuryTimer; + f32 mProtectionTimer; + f32 mGraceTimer; + u32 mAbilityUses[3]; + f32 mMasterSwordCooldown; + + f32 mSpeedPotionTimer1; + f32 mSpeedPotionTimer2; + f32 mSpeedPotionTimer3; + f32 mAttackPotionTimer; + f32 mDefensePotionTimer; + f32 mHeatResistPotionTimer; + f32 mColdResistPotionTimer; + f32 mFlameResistPotionTimer; + f32 mShockResistPotionTimer; + f32 mStealthPotionTimer; + + s32 mCountOffset; +}; +} // namespace botwsavs::core diff --git a/src/core/StateMacros.hpp b/src/core/StateMacros.hpp deleted file mode 100644 index 2377d86..0000000 --- a/src/core/StateMacros.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#define GameReadOrError(POINTER, MEMBER) \ - if (!(mem::POINTER)().Get(&MEMBER)) { \ - errorf("Read %s failed", #POINTER); \ - SetError(Error::Pointer); \ - } -#define GameReadEz(COMMON_NAME) GameReadOrError(GamePtr::COMMON_NAME, m##COMMON_NAME) -#define GameRead(POINTER, MEMBER) if ((mem::POINTER)().Get(&MEMBER)) -#define GameReadArray(POINTER, SIZE, MEMBER) \ - if (!(mem::POINTER)().GetArray(MEMBER, SIZE)) { \ - errorf("Read %s array failed", #POINTER); \ - SetError(Error::Pointer); \ - } -#define GameReadArrayEz(COMMON_NAME, SIZE) GameReadArray(GamePtr::COMMON_NAME, SIZE, m##COMMON_NAME) - -#define GameWrite(POINTER, MEMBER) \ - if (!(mem::POINTER)().Set(MEMBER)) { \ - errorf("Write %s failed", #POINTER); \ - SetError(Error::Pointer); \ - } -#define GameWriteEz(COMMON_NAME) GameWrite(GamePtr::COMMON_NAME, m##COMMON_NAME) -#define GameWriteArray(POINTER, SIZE, MEMBER) \ - if (!(mem::POINTER)().SetArray(MEMBER, SIZE)) { \ - errorf("Write %s array failed", #POINTER); \ - SetError(Error::Pointer); \ - } -#define GameWriteArrayEz(COMMON_NAME, SIZE) \ - GameWriteArray(GamePtr::COMMON_NAME, SIZE, m##COMMON_NAME) - -#define GameUsePtr(POINTER, TYPE, VAR) \ - TYPE* VAR; \ - if ((POINTER).TakePtr(&VAR)) -#define GameUsePtrOrError(POINTER, TYPE, VAR) \ - TYPE* VAR; \ - if (!(POINTER).TakePtr(&VAR)) { \ - errorf("Get pointer %s failed", #POINTER); \ - SetError(Error::Pointer); \ - } else -#define GameUseSafePtr(UNSAFE_POINTER, TYPE, VAR) \ - GameUsePtr(mem::SafePtr(UNSAFE_POINTER), TYPE, VAR) -#define GameUseSafePtrOrError(UNSAFE_POINTER, TYPE, VAR) \ - GameUsePtrOrError(mem::SafePtr(UNSAFE_POINTER), TYPE, VAR) diff --git a/src/core/StateStorage.hpp b/src/core/StateStorage.hpp new file mode 100644 index 0000000..308d976 --- /dev/null +++ b/src/core/StateStorage.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "fs/Logger.hpp" +#include "mem/SafePtr.hpp" +#include "types.h" + +namespace botwsavs { + +namespace fs { +class ConfigFile; +} + +namespace core { + +enum StateError : u32 { + None = 0, + Pointer = 1, + NotEquipped = 1 << 1, + DifferentName = 1 << 2, +}; + +class StateStorage { +public: + virtual void ReadFromGame(); + virtual void WriteToGame(); + virtual void ReadFromFile(fs::ConfigFile& file, u32 version); + virtual void WriteToFile(fs::ConfigFile& file) const; + + u32 GetErrorMask() const { return mError; } + bool HasError(StateError mask) const { return (mError & mask) != 0; } + bool HasAnyError() const { return mError != StateError::None; } + void ClearError() { mError = StateError::None; } + +protected: + void SetError(StateError mask) { mError = static_cast(mError | mask); } + // Helper functions for read/write game value + template + void ReadMemory(const char* name, mem::SafePtr ptr, T* out) { + if (!ptr.Get(out)) { + errorf("Read %s failed", name); + SetError(StateError::Pointer); + } + } + + template + void WriteMemory(const char* name, mem::SafePtr ptr, const T value) { + if (!ptr.Set(value)) { + errorf("Write %s failed", name); + SetError(StateError::Pointer); + } + } + + template + void ReadMemoryArray(const char* name, mem::SafePtr ptr, T* outArray, u32 size) { + if (!ptr.GetArray(outArray, size)) { + errorf("Read %s array failed", name); + SetError(StateError::Pointer); + } + } + + template + void WriteMemoryArray(const char* name, mem::SafePtr ptr, const T* outArray, u32 size) { + if (!ptr.SetArray(outArray, size)) { + errorf("Write %s array failed", name); + SetError(StateError::Pointer); + } + } + + template + bool TakePtrOrError(const char* name, mem::SafePtr ptr, T** out) { + if (!ptr.TakePtr(out)) { + errorf("Get pointer %s failed", name); + SetError(StateError::Pointer); + return false; + } + return true; + } + + StateError mError = StateError::None; + + static const u32 sInvalid = 0xFFFFFFFF; +}; + +} // namespace core +} // namespace botwsavs diff --git a/src/core/Worker.cpp b/src/core/Worker.cpp deleted file mode 100644 index 3aea100..0000000 --- a/src/core/Worker.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#include - -#include "Worker.hpp" -#include "fs/Logger.hpp" -#include "fs/StateSaveFile.hpp" -#include "fs/WorkerSaveFile.hpp" -#include "ui/OverlayString.hpp" - -namespace botwsavs::core { - -bool Worker::Init() { - info("Init worker config from file"); - - fs::WorkerSaveFile workerTxt(*this); - bool result = workerTxt.Load(); - - if (!result) { - warn("File operation failed. Cannot init worker"); - return false; - } - return true; -} - -bool Worker::Work() { - bool controllerReady = mController.isInitialized(); - if (!controllerReady) { - return false; - } - if (mController.ShouldSwitchMode()) { - if (DidHoldFor(Hold::ModeSwitch, 3)) { - info("Switching mode"); - if (mMode == Mode::Active) { - mMode = Mode::Setting; - ui::ShowOverridenMessage("Save State: Setting Mode"); - } else { - mMode = Mode::Active; - ui::ShowOverridenMessage("Save State: Active Mode"); - } - mHold = Hold::None; - } - } else if (mMode == Mode::Active) { - if (mController.ShouldSaveState()) { - ExecuteSave(); - } else if (mController.ShouldRestoreState()) { - ExecuteRestore(); - } else if (mController.ShouldSaveStateToFile()) { - ExecuteSaveToFile(); - } else if (mController.ShouldRestoreStateFromFile()) { - ExecuteRestoreFromFile(); - } - mHold = Hold::None; - } else { - if (mController.ShouldIncreaseLevel()) { - if (DidHoldFor(Hold::IncreaseLevel, 1)) { - info("Increase level"); - if (mLevel < 3) { - mLevel++; - SaveWorker(); - } - ui::ShowSetLevel(mLevel); - mHold = Hold::None; - } - } else if (mController.ShouldDecreaseLevel()) { - if (DidHoldFor(Hold::DecreaseLevel, 1)) { - info("Decrease level"); - if (mLevel > 0) { - mLevel--; - SaveWorker(); - } - ui::ShowSetLevel(mLevel); - mHold = Hold::None; - } - } else { - mHold = Hold::None; - } - } - - return true; -} - -bool Worker::DidHoldFor(Hold hold, u32 seconds) { - if (mHold != hold) { - mHoldCounter = 0; - mHold = hold; - } else { - mHoldCounter++; - } - // 1 tick = 3 frames - // 1s = 30 frames = 10 ticks - u32 ticks = seconds * 10; - if (mHoldCounter >= ticks) { - mHoldCounter = 0; - return true; - } - return false; -} - -void Worker::ExecuteSave() { - info("Saving state to memory"); - if (mLevel == 0) { - error("Save failed because setting level is 0"); - return; - } - if (!mState.ReadFromGame(mLevel)) { - DisplayStateError(mState); - error("State read failed"); - return; - } - ui::ShowFormattedMessage("Saved state to memory (level %d)", mLevel); - info("State saved to memory"); -} - -void Worker::ExecuteSaveToFile() { - info("Saving state to file"); - if (mLevel == 0) { - error("Save failed because setting level is 0"); - return; - } - State tempState; - if (!tempState.ReadFromGame(mLevel)) { - DisplayStateError(tempState); - warnf("State read gives error 0x%x, but continuing to write file anyway", - tempState.GetError()); - } - fs::StateSaveFile latestTxt("sd:/botwsavs/latest.txt", tempState); - bool result = latestTxt.Save(); - - if (!result) { - ui::ShowError(); - error("File operation failed"); - return; - } - - ui::ShowFormattedMessage("Saved state to file (level %d)", mLevel); - info("State saved to file"); -} -void Worker::ExecuteRestore() { - info("Restoring state from memory"); - - if (mLevel == 0) { - error("Restore failed because setting level is 0"); - return; - } - - if (mState.mLevel == 0) { - error("Restore failed because state level is 0 (nothing stored)"); - ui::ShowCantDoThatRightNow(); - return; - } - - if (mState.mLevel < mLevel) { - error("Restore failed because state level is less than setting level"); - ui::ShowLevelError(mState.mLevel); - return; - } - - if (!mState.WriteToGame(mLevel)) { - DisplayStateError(mState); - error("State write failed"); - return; - } -#ifndef GOLD_RUSH - // Don't show restore message for gold rush - ui::ShowFormattedMessage("Restored state from memory (level %d)", mLevel); -#endif - info("State restored from memory"); -} - -void Worker::ExecuteRestoreFromFile() { - info("Restoring state from file"); - if (mLevel == 0) { - error("Restore failed because setting level is 0"); - return; - } - - State tempState; - fs::StateSaveFile restoreTxt("sd:/botwsavs/restore.txt", tempState); - if (!restoreTxt.Exists()) { - error("Restore failed because restore.txt does not exist"); - ui::ShowCantDoThatRightNow(); - return; - } - - bool result = restoreTxt.Load(); - if (!result) { - ui::ShowError(); - error("File operation failed"); - return; - } - if (tempState.mLevel < mLevel) { - error("Restore failed because state level is less than setting level"); - ui::ShowLevelError(tempState.mLevel); - return; - } - - if (!tempState.WriteToGame(mLevel)) { - DisplayStateError(tempState); - error("State write failed"); - return; - } - -#ifndef GOLD_RUSH - // Don't show restore message for gold rush - ui::ShowFormattedMessage("Restored state from file (level %d)", mLevel); -#endif - info("State restored from file"); -} - -void Worker::DisplayStateError(State& state) { - if (state.HasError(State::Error::DifferentName)) { - ui::ShowOverridenMessage("Warning: Some equipped items are different!"); - return; - } - - if (state.HasError(State::Error::NotEquipped)) { - ui::ShowOverridenMessage("Warning: Something not equipped?"); - return; - } - - if (state.HasError(State::Error::Pointer)) { - ui::ShowOverridenMessage("Pointer error!"); - } -} - -void Worker::SaveWorker() { - info("Saving worker config to file"); - - fs::WorkerSaveFile workerTxt(*this); - bool result = workerTxt.Save(); - - if (!result) { - warn("File operation failed. Cannot save worker"); - } -} - -} // namespace botwsavs::core diff --git a/src/core/Worker.hpp b/src/core/Worker.hpp index 4dff8c3..c512fee 100644 --- a/src/core/Worker.hpp +++ b/src/core/Worker.hpp @@ -1,28 +1,54 @@ #pragma once + +#include "fs/ConfigFile.hpp" + #include "Controller.hpp" +#include "KeyMgr.hpp" #include "State.hpp" +#define WORKER_TXT_PATH "sd:/botwsavs/worker.txt" + namespace botwsavs { namespace core { -class Worker { +class Worker : fs::Config { public: enum class Mode { Active, Setting }; - enum class Hold { - None, - ModeSwitch, - IncreaseLevel, - DecreaseLevel, - }; + // enum class Hold { + // None, + // ModeSwitch, + // IncreaseLevel, + // DecreaseLevel, + // }; public: bool Init(); // Return if work is successful - bool Work(); + bool Work() { + if (!CanWork()) { + return false; + } + if (TrySwitchMode()) { + return true; + } + if (mMode == Mode::Active) { + return WorkActiveMode(); + } else { + return WorkSettingMode(); + } + } + + void Save(fs::ConfigFile& configFile) const override; + void Load(fs::ConfigFile& configFile) override; private: - bool DidHoldFor(Hold hold, u32 seconds); + // Core + bool CanWork() { return mController.IsInitialized(); } + bool WorkActiveMode(); + bool WorkSettingMode(); + bool TrySwitchMode(); + // Save state to memory void ExecuteSave(); // Save state to file @@ -34,17 +60,18 @@ class Worker { // Display the most important state error void DisplayStateError(State& state); // Save worker config - void SaveWorker(); + bool SaveConfig() const; + // Load worker config + bool LoadConfig(); public: u32 mLevel = 1; private: Controller mController; + KeyMgr mKeyMgr; State mState; Mode mMode = Mode::Active; - u32 mHoldCounter = 0; - Hold mHold = Hold::None; }; } // namespace core diff --git a/src/core/WorkerActions.cpp b/src/core/WorkerActions.cpp new file mode 100644 index 0000000..22a5a1a --- /dev/null +++ b/src/core/WorkerActions.cpp @@ -0,0 +1,131 @@ + +#include "Worker.hpp" +#include "fs/Logger.hpp" +#include "ui/OverlayString.hpp" + +namespace botwsavs::core { + +void Worker::ExecuteSave() { + info("Saving state to memory"); + if (mLevel == 0) { + error("Save failed because setting level is 0"); + return; + } + if (!mState.ReadFromGame(mLevel)) { + DisplayStateError(mState); + error("State read failed"); + return; + } + ui::ShowFormattedMessage("Saved state to memory (level %d)", mLevel); + info("State saved to memory"); +} + +void Worker::ExecuteSaveToFile() { + info("Saving state to file"); + if (mLevel == 0) { + error("Save failed because setting level is 0"); + return; + } + State tempState; + if (!tempState.ReadFromGame(mLevel)) { + DisplayStateError(tempState); + warnf("State read gives error 0x%x, but continuing to write file anyway", + tempState.GetErrorMask()); + } + + if (!tempState.WriteToFile()) { + ui::ShowError(); + error("File operation failed"); + return; + } + + ui::ShowFormattedMessage("Saved state to file (level %d)", mLevel); + info("State saved to file"); +} +void Worker::ExecuteRestore() { + info("Restoring state from memory"); + + if (mLevel == 0) { + error("Restore failed because setting level is 0"); + return; + } + + if (mState.GetLevel() == 0) { + error("Restore failed because state level is 0 (nothing stored)"); + ui::ShowCantDoThatRightNow(); + return; + } + + if (mState.GetLevel() < mLevel) { + error("Restore failed because state level is less than setting level"); + ui::ShowLevelError(mState.GetLevel()); + return; + } + + if (!mState.WriteToGame(mLevel)) { + DisplayStateError(mState); + error("State write failed"); + return; + } +#ifndef GOLD_RUSH + // Don't show restore message for gold rush + ui::ShowFormattedMessage("Restored state from memory (level %d)", mLevel); +#endif + info("State restored from memory"); +} + +void Worker::ExecuteRestoreFromFile() { + info("Restoring state from file"); + if (mLevel == 0) { + error("Restore failed because setting level is 0"); + return; + } + + State tempState; + if (!tempState.CanReadFromFile()) { + error("Restore failed because restore.txt does not exist"); + ui::ShowCantDoThatRightNow(); + return; + } + + if (!tempState.ReadFromFile()) { + ui::ShowError(); + error("File operation failed"); + return; + } + if (tempState.GetLevel() < mLevel) { + error("Restore failed because state level is less than setting level"); + ui::ShowLevelError(tempState.GetLevel()); + return; + } + + if (!tempState.WriteToGame(mLevel)) { + DisplayStateError(tempState); + error("State write failed"); + return; + } + +#ifndef GOLD_RUSH + // Don't show restore message for gold rush + ui::ShowFormattedMessage("Restored state from file (level %d)", mLevel); +#endif + info("State restored from file"); +} + +void Worker::DisplayStateError(State& state) { + if (state.HasError(StateError::DifferentName)) { + ui::ShowOverridenMessage("Warning: Some equipped items are different!"); + return; + } + + if (state.HasError(StateError::NotEquipped)) { + ui::ShowOverridenMessage("Warning: Something not equipped?"); + return; + } + + if (state.HasError(StateError::Pointer)) { + ui::ShowOverridenMessage("Pointer error!"); + } +} + +} // namespace botwsavs::core diff --git a/src/core/WorkerConfig.cpp b/src/core/WorkerConfig.cpp new file mode 100644 index 0000000..c1ead60 --- /dev/null +++ b/src/core/WorkerConfig.cpp @@ -0,0 +1,38 @@ +#include "Worker.hpp" +#include "fs/Logger.hpp" + +namespace botwsavs::core { + +void Worker::Save(fs::ConfigFile& file) const { + file.WriteInteger(named(mLevel)); + mKeyMgr.Save(file); +} + +void Worker::Load(fs::ConfigFile& file) { + file.ReadInteger(&mLevel); + mKeyMgr.Load(file); +} + +bool Worker::SaveConfig() const { + info("Saving worker config to file"); + fs::ConfigFile configFile(WORKER_TXT_PATH); + + if (!configFile.Save(*this)) { + warn("File operation failed. Cannot save worker config."); + return false; + } + return true; +} + +bool Worker::LoadConfig() { + info("Loading worker config from file"); + fs::ConfigFile configFile(WORKER_TXT_PATH); + + if (!configFile.Load(*this)) { + warn("File operation failed. Cannot init worker config. Will use default values."); + return false; + } + return true; +} + +} // namespace botwsavs::core diff --git a/src/core/WorkerCore.cpp b/src/core/WorkerCore.cpp new file mode 100644 index 0000000..9114a6e --- /dev/null +++ b/src/core/WorkerCore.cpp @@ -0,0 +1,116 @@ +#include "Worker.hpp" +#include "fs/Logger.hpp" +#include "ui/OverlayString.hpp" + +namespace botwsavs::core { + +bool Worker::Init() { + info("Init worker"); + + return LoadConfig(); +} + +bool Worker::TrySwitchMode() { + if (mController.IsOnlyHolding(mKeyMgr.mKeySwitchMode)) { + if (mKeyMgr.DidHoldFor(mKeyMgr.mKeySwitchMode, 3)) { + info("Switching mode"); + if (mMode == Mode::Active) { + mMode = Mode::Setting; + ui::ShowOverridenMessage("Save State: Setting Mode"); + } else { + mMode = Mode::Active; + ui::ShowOverridenMessage("Save State: Active Mode"); + } + mKeyMgr.ClearHold(); + } + return true; + } + + return false; +} +bool Worker::WorkActiveMode() { + if (mController.IsOnlyHolding(mKeyMgr.mKeySave)) { + ExecuteSave(); + } else if (mController.IsOnlyHolding(mKeyMgr.mKeySaveFile)) { + ExecuteSaveToFile(); + } else if (mController.IsOnlyHolding(mKeyMgr.mKeyRestore)) { + ExecuteRestore(); + } else if (mController.IsOnlyHolding(mKeyMgr.mKeyRestoreFile)) { + ExecuteRestoreFromFile(); + } + + return true; +} + +bool Worker::WorkSettingMode() { + if (mKeyMgr.IsConfiguring()) { + u32 newKey = mController.GetHoldKeys(); + KeyMgr::ConfigureResult result = mKeyMgr.FinishConfigure(newKey); + switch (result) { + case KeyMgr::ConfigureResult::Success: { + util::StringBuffer<120> buffer; + key::GetKeyString(newKey, buffer); + ui::ShowFormattedMessage("Configured: %s", buffer.Content()); + SaveConfig(); + break; + } + case KeyMgr::ConfigureResult::FailEmpty: + ui::ShowOverridenMessage("Key binding cannot be updated to none!"); + break; + case KeyMgr::ConfigureResult::FailRelease: + ui::ShowOverridenMessage("Holding keys changed, key binding change failed!"); + break; + case KeyMgr::ConfigureResult::Pending: + default: + break; + } + return true; + } + + if (mController.IsOnlyHolding(mKeyMgr.mKeyIncreaseLevel)) { + if (mKeyMgr.DidHoldFor(mKeyMgr.mKeyIncreaseLevel, 1)) { + info("Increase level"); + if (mLevel < 3) { + mLevel++; + SaveConfig(); + } + ui::ShowSetLevel(mLevel); + mKeyMgr.ClearHold(); + } + } else if (mController.IsOnlyHolding(mKeyMgr.mKeyDecreaseLevel)) { + if (mKeyMgr.DidHoldFor(mKeyMgr.mKeyDecreaseLevel, 1)) { + info("Decrease level"); + if (mLevel > 0) { + mLevel--; + SaveConfig(); + } + ui::ShowSetLevel(mLevel); + mKeyMgr.ClearHold(); + } + } else if (mController.IsOnlyHolding(mKeyMgr.mKeySave)) { + if (mKeyMgr.DidHoldFor(mKeyMgr.mKeySave, 3)) { + ui::ShowSetKeyBinding("memory save"); + mKeyMgr.StartConfigure(&mKeyMgr.mKeySave); + } + } else if (mController.IsOnlyHolding(mKeyMgr.mKeySaveFile)) { + if (mKeyMgr.DidHoldFor(mKeyMgr.mKeySaveFile, 3)) { + ui::ShowSetKeyBinding("file save"); + mKeyMgr.StartConfigure(&mKeyMgr.mKeySaveFile); + } + } else if (mController.IsOnlyHolding(mKeyMgr.mKeyRestore)) { + if (mKeyMgr.DidHoldFor(mKeyMgr.mKeyRestore, 3)) { + ui::ShowSetKeyBinding("memory restore"); + mKeyMgr.StartConfigure(&mKeyMgr.mKeyRestore); + } + } else if (mController.IsOnlyHolding(mKeyMgr.mKeyRestoreFile)) { + if (mKeyMgr.DidHoldFor(mKeyMgr.mKeyRestoreFile, 3)) { + ui::ShowSetKeyBinding("file restore"); + mKeyMgr.StartConfigure(&mKeyMgr.mKeyRestoreFile); + } + } else { + mKeyMgr.ClearHold(); + } + + return true; +} +} // namespace botwsavs::core diff --git a/src/fs/ConfigFile.cpp b/src/fs/ConfigFile.cpp index 9f1c726..c0ef35c 100644 --- a/src/fs/ConfigFile.cpp +++ b/src/fs/ConfigFile.cpp @@ -5,7 +5,7 @@ namespace botwsavs::fs { -bool ConfigFile::Save() { +bool ConfigFile::Save(const Config& config) { if (!mFile.Open()) { return false; } @@ -16,19 +16,19 @@ bool ConfigFile::Save() { } mSuccess = true; mMode = Mode::Save; - SaveInternal(); + config.Save(*this); mFile.Close(); return mSuccess; } -bool ConfigFile::Load() { +bool ConfigFile::Load(Config& config) { if (!mFile.Open()) { return false; } mSuccess = true; mMode = Mode::Load; mBuffer.Clear(); - LoadInternal(); + config.Load(*this); mFile.Close(); return mSuccess; } diff --git a/src/fs/ConfigFile.hpp b/src/fs/ConfigFile.hpp index 28b0cd8..d60d4f7 100644 --- a/src/fs/ConfigFile.hpp +++ b/src/fs/ConfigFile.hpp @@ -1,5 +1,6 @@ #pragma once #include "File.hpp" +#include "util/NamedValue.hpp" #include "util/StringBuffer.hpp" #pragma GCC diagnostic push @@ -8,6 +9,14 @@ namespace botwsavs { namespace fs { +class ConfigFile; + +class Config { +public: + virtual void Save(ConfigFile& file) const; + virtual void Load(ConfigFile& file); +}; + class ConfigFile { public: ConfigFile(const char* path) : mFile(path) {} @@ -15,12 +24,8 @@ class ConfigFile { bool Exists() { return mFile.Exists(); } - bool Save(); - bool Load(); - -protected: - virtual void SaveInternal(); - virtual void LoadInternal(); + bool Save(const Config& config); + bool Load(Config& config); void WriteInteger(const char* fieldName, const u64 value) { if (mSuccess) { @@ -57,6 +62,8 @@ class ConfigFile { *outValue = truncated; } + void ReadInteger(s32* outValue) { ReadInteger(reinterpret_cast(outValue)); } + void WriteFloat(const char* fieldName, const f32 value) { WriteInteger(fieldName, reinterpret_cast(value)); } @@ -124,6 +131,30 @@ class ConfigFile { } } + template + void WriteNamedInteger(const char* fieldName, const util::NamedValue& value) { + WriteString("Name of value below", value.GetName()); + WriteInteger(fieldName, value.GetValue()); + } + + template + void ReadNamedInteger(util::NamedValue& value) { + ReadString(value.Name(), L); + ReadInteger(value.GetValuePtr()); + } + + template + void WriteNamedFloat(const char* fieldName, const util::NamedValue& value) { + WriteString("Name of value below", value.GetName()); + WriteFloat(fieldName, value.GetValue()); + } + + template + void ReadNamedFloat(const char* fieldName, const util::NamedValue& value) { + ReadString(value.Name(), L); + ReadFloat(value.GetValuePtr()); + } + private: // In save mode all read functions will be disabled, vise versa enum class Mode { Save, Load }; diff --git a/src/fs/StateSaveFile.cpp b/src/fs/StateSaveFile.cpp deleted file mode 100644 index 577d07d..0000000 --- a/src/fs/StateSaveFile.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "StateSaveFile.hpp" -#include "core/State.hpp" -#include "fs/Logger.hpp" - -namespace botwsavs::fs { -void StateSaveFile::LoadInternal() { - u32 version = 0; - ReadInteger(&version); - - switch (version) { - case 1: - ReadVersion1(); - break; - case 2: - ReadVersion2(); - break; - case 3: - ReadVersion3(); - break; - default: - errorf("Bad version: %d", version); - } -} - -void StateSaveFile::SaveInternal() { - WriteInteger("version", STATE_SAVE_FILE_VERSION); - StateFileWrite(Integer, mLevel); - // Level 1 - StateFileWrite(Integer, mHealth); - StateFileWrite(Float, mStamina); - StateFileWriteArray(Float, mHavokPosition, 3); - StateFileWriteArray(Float, mMainPositionMatrix, 12); - StateFileWriteArray(Float, mCameraPanMatrix, 12); - StateFileWrite(Float, mCameraZoom); - StateFileWrite(Float, mCameraTilt); - // Level 2 - StateFileWriteNamedValue(Integer, mMenuEquippedArrow); - StateFileWriteNamedValue(Integer, mMenuEquippedWeapon); - StateFileWriteNamedValue(Integer, mMenuEquippedBow); - StateFileWriteNamedValue(Integer, mMenuEquippedShield); - StateFileWriteNamedValue(Integer, mOverworldEquippedWeapon); - StateFileWriteNamedValue(Integer, mOverworldEquippedBow); - StateFileWriteNamedValue(Integer, mOverworldEquippedShield); - // Level 3 - StateFileWrite(Float, mTimeOfDayPaused); - StateFileWrite(Float, mTimeOfDayUnpaused); - StateFileWrite(Float, mBloodMoonTimer); - StateFileWrite(Float, mTemperatureDamageTimer); - StateFileWrite(Float, mFlameTimer); - StateFileWrite(Float, mGaleTimer); - StateFileWrite(Float, mFuryTimer); - StateFileWrite(Float, mProtectionTimer); - StateFileWrite(Float, mGraceTimer); - StateFileWriteArray(Integer, mAbilityUses, 3); - StateFileWrite(Float, mMasterSwordCooldown); - StateFileWrite(Float, mSpeedPotionTimer1); - StateFileWrite(Float, mSpeedPotionTimer2); - StateFileWrite(Float, mSpeedPotionTimer3); - StateFileWrite(Float, mAttackPotionTimer); - StateFileWrite(Float, mDefensePotionTimer); - StateFileWrite(Float, mHeatResistPotionTimer); - StateFileWrite(Float, mColdResistPotionTimer); - StateFileWrite(Float, mFlameResistPotionTimer); - StateFileWrite(Float, mShockResistPotionTimer); - StateFileWrite(Float, mStealthPotionTimer); -} - -void StateSaveFile::ReadVersion1() { - StateFileRead(Integer, mLevel); - StateFileRead(Integer, mHealth); - StateFileRead(Float, mStamina); - StateFileReadArray(Float, mHavokPosition, 3); - StateFileReadArray(Float, mMainPositionMatrix, 12); - StateFileReadArray(Float, mCameraPanMatrix, 12); - StateFileRead(Float, mCameraZoom); - StateFileRead(Float, mCameraTilt); -} - -void StateSaveFile::ReadVersion2() { - ReadVersion1(); - StateFileReadNamedValue(Integer, mMenuEquippedArrow, 64); - StateFileReadNamedValue(Integer, mMenuEquippedWeapon, 64); - StateFileReadNamedValue(Integer, mMenuEquippedBow, 64); - StateFileReadNamedValue(Integer, mMenuEquippedShield, 64); - StateFileReadNamedValue(Integer, mOverworldEquippedWeapon, 64); - StateFileReadNamedValue(Integer, mOverworldEquippedBow, 64); - StateFileReadNamedValue(Integer, mOverworldEquippedShield, 64); -} - -void StateSaveFile::ReadVersion3() { - ReadVersion2(); - StateFileRead(Float, mTimeOfDayPaused); - StateFileRead(Float, mTimeOfDayUnpaused); - StateFileRead(Float, mBloodMoonTimer); - StateFileRead(Float, mTimeOfDayPaused); - StateFileRead(Float, mTimeOfDayUnpaused); - StateFileRead(Float, mBloodMoonTimer); - StateFileRead(Float, mTemperatureDamageTimer); - StateFileRead(Float, mFlameTimer); - StateFileRead(Float, mGaleTimer); - StateFileRead(Float, mFuryTimer); - StateFileRead(Float, mProtectionTimer); - StateFileRead(Float, mGraceTimer); - StateFileReadArray(Integer, mAbilityUses, 3); - StateFileRead(Float, mMasterSwordCooldown); - StateFileRead(Float, mSpeedPotionTimer1); - StateFileRead(Float, mSpeedPotionTimer2); - StateFileRead(Float, mSpeedPotionTimer3); - StateFileRead(Float, mAttackPotionTimer); - StateFileRead(Float, mDefensePotionTimer); - StateFileRead(Float, mHeatResistPotionTimer); - StateFileRead(Float, mColdResistPotionTimer); - StateFileRead(Float, mFlameResistPotionTimer); - StateFileRead(Float, mShockResistPotionTimer); - StateFileRead(Float, mStealthPotionTimer); -} -} // namespace botwsavs::fs diff --git a/src/fs/StateSaveFile.hpp b/src/fs/StateSaveFile.hpp deleted file mode 100644 index 63f9ead..0000000 --- a/src/fs/StateSaveFile.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once -#include "ConfigFile.hpp" - -namespace botwsavs { - -namespace core { -class State; -} - -namespace fs { - -class StateSaveFile : public ConfigFile { -public: - StateSaveFile(const char* path, core::State& state) : ConfigFile(path), mState(state){}; - ~StateSaveFile() = default; - -protected: - void SaveInternal() override; - void LoadInternal() override; - -private: - void ReadVersion1(); - void ReadVersion2(); - void ReadVersion3(); - - core::State& mState; -}; - -} // namespace fs -} // namespace botwsavs - -#define STATE_SAVE_FILE_VERSION 3 -#define StateFileWrite(TYPE, MEMBER) Write##TYPE(#MEMBER, mState.MEMBER) -#define StateFileWriteArray(TYPE, MEMBER, SIZE) Write##TYPE##Array(#MEMBER, mState.MEMBER, SIZE) -#define StateFileWriteString(MEMBER, SIZE) WriteString(#MEMBER, mState.MEMBER, SIZE) -#define StateFileWriteNamedValue(TYPE, MEMBER) \ - WriteString("Name of value below", mState.MEMBER.GetName()); \ - Write##TYPE(#MEMBER, mState.MEMBER.GetValue()) - -#define StateFileRead(TYPE, MEMBER) Read##TYPE(&mState.MEMBER) -#define StateFileReadArray(TYPE, MEMBER, SIZE) Read##TYPE##Array(mState.MEMBER, SIZE); -#define StateFileReadString(MEMBER, SIZE) ReadString(mState.MEMBER, SIZE) -#define StateFileReadNamedValue(TYPE, MEMBER, SIZE) \ - ReadString(mState.MEMBER.Name(), SIZE); \ - Read##TYPE(mState.MEMBER.GetValuePtr()); diff --git a/src/fs/WorkerSaveFile.cpp b/src/fs/WorkerSaveFile.cpp deleted file mode 100644 index 49b978e..0000000 --- a/src/fs/WorkerSaveFile.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "WorkerSaveFile.hpp" -#include "core/Worker.hpp" - -namespace botwsavs::fs { -void WorkerSaveFile::LoadInternal() { - ReadInteger(&mWorker.mLevel); -} - -void WorkerSaveFile::SaveInternal() { - WriteInteger("level", mWorker.mLevel); -} - -} // namespace botwsavs::fs diff --git a/src/fs/WorkerSaveFile.hpp b/src/fs/WorkerSaveFile.hpp deleted file mode 100644 index 789ba35..0000000 --- a/src/fs/WorkerSaveFile.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include "ConfigFile.hpp" - -namespace botwsavs { - -namespace core { -class Worker; -} - -namespace fs { - -class WorkerSaveFile : public ConfigFile { -public: - WorkerSaveFile(core::Worker& worker) : ConfigFile("sd:/botwsavs/worker.txt"), mWorker(worker){}; - ~WorkerSaveFile() = default; - -protected: - void SaveInternal() override; - void LoadInternal() override; - - core::Worker& mWorker; -}; - -} // namespace fs -} // namespace botwsavs diff --git a/src/mem/KingPtr.hpp b/src/mem/KingPtr.hpp index 0342c02..b53b262 100644 --- a/src/mem/KingPtr.hpp +++ b/src/mem/KingPtr.hpp @@ -27,6 +27,7 @@ extern u64 GetMessageString(void* file, void* messageId, void* outString); // Other used: // uking::ui::PauseMenuDataMgr::sInstance -// +// 0x02CA6D50 +// _ZN5uking2ui16PauseMenuDataMgr9sInstanceE // } // namespace botwsavs::mem::KingPtr diff --git a/src/mem/SafePtr.hpp b/src/mem/SafePtr.hpp index 553c00b..ba5cf8f 100644 --- a/src/mem/SafePtr.hpp +++ b/src/mem/SafePtr.hpp @@ -5,7 +5,7 @@ namespace botwsavs::mem { -static bool PtrLooksSafe(void* p) { +inline bool PtrLooksSafe(void* p) { u64 raw = reinterpret_cast(p); if (raw > 0xFFFFFFFFFF || (raw >> 32 == 0)) { @@ -42,7 +42,7 @@ class SafePtr { return true; } - bool Set(T value) { + bool Set(T value) const { if (!LooksSafe()) { return false; } @@ -58,7 +58,7 @@ class SafePtr { return true; } - bool Set(T value, u32 i) { + bool Set(T value, u32 i) const { if (!LooksSafe()) { return false; } @@ -76,7 +76,7 @@ class SafePtr { return true; } - bool SetArray(const T* array, u32 len) { + bool SetArray(const T* array, u32 len) const { if (!LooksSafe()) { return false; } diff --git a/src/types.h b/src/types.h index 33f87d4..8c33e4a 100644 --- a/src/types.h +++ b/src/types.h @@ -49,3 +49,6 @@ typedef char16_t wchar; #define infof(mes, value) botwsavs::fs::Logger::Instance().InfoF(mes, value) #define warnf(mes, value) botwsavs::fs::Logger::Instance().WarnF(mes, value) #define errorf(mes, value) botwsavs::fs::Logger::Instance().ErrorF(mes, value) + +#define nameof(x) #x +#define named(x) #x, x diff --git a/src/ui/OverlayString.hpp b/src/ui/OverlayString.hpp index cc50cc3..c5a0842 100644 --- a/src/ui/OverlayString.hpp +++ b/src/ui/OverlayString.hpp @@ -15,7 +15,7 @@ u64 GetMessageStringHook(void* file, sead::SafeString& messageId, WideString* ou void ShowOverridenMessage(const char* message); template -void ShowFormattedMessage(const char* format, T& value) { +void ShowFormattedMessage(const char* format, T value) { char result[200]; snprintf(result, 200, format, value); ShowOverridenMessage(result); @@ -52,4 +52,8 @@ inline void ShowLevelError(u32 stateLevel) { ShowFormattedMessage("You need to lower the setting level to %d to restore!", stateLevel); } +inline void ShowSetKeyBinding(const char* keyName) { + ShowFormattedMessage("Configure %s key (Hold for 3s)", keyName); +} + } // namespace botwsavs::ui diff --git a/src/core/NamedValue.hpp b/src/util/NamedValue.hpp similarity index 86% rename from src/core/NamedValue.hpp rename to src/util/NamedValue.hpp index 992e81c..16e7d69 100644 --- a/src/core/NamedValue.hpp +++ b/src/util/NamedValue.hpp @@ -2,7 +2,7 @@ #include #include -namespace botwsavs::core { +namespace botwsavs::util { template class NamedValue { @@ -14,7 +14,7 @@ class NamedValue { mName[L - 1] = '\0'; } - bool NameMatches(const sead::FixedSafeString& string) { + bool NameMatches(const sead::FixedSafeString& string) const { return strncmp(mName, string.cstr(), L) == 0; } @@ -34,4 +34,4 @@ class NamedValue { char mName[L]; T mValue; }; -} // namespace botwsavs::core +} // namespace botwsavs::util diff --git a/src/util/StringBuffer.hpp b/src/util/StringBuffer.hpp index 2a202a3..eb24916 100644 --- a/src/util/StringBuffer.hpp +++ b/src/util/StringBuffer.hpp @@ -70,6 +70,15 @@ class StringBuffer { EnsureTermination(); } + void SafeDeleteEnd(u32 size) { + if (mLen <= size) { + mLen = 0; + } else { + mLen -= size; + } + EnsureTermination(); + } + private: char mContent[L + 1]; u32 mLen;