From 05ead67dbc242e3433865a9c4db3c906f2dc9759 Mon Sep 17 00:00:00 2001 From: qudix <17361645+qudix@users.noreply.github.com> Date: Sun, 3 Nov 2024 15:01:11 -0600 Subject: [PATCH] feat: save (#294) - I renamed `TESForm::LookupByX` to `TESForm::GetFormByX` to match CommonlibF4 and what Bethesda internally calls them --- include/RE/B/BGSSaveLoad.h | 225 ++++++++++++++++++++++++++++++ include/RE/B/BGSSaveLoadManager.h | 26 ---- include/RE/B/BSPauseRequester.h | 12 ++ include/RE/E/Events.h | 32 +++++ include/RE/IDs.h | 9 +- include/RE/M/Main.h | 8 +- include/RE/Starfield.h | 3 +- 7 files changed, 286 insertions(+), 29 deletions(-) create mode 100644 include/RE/B/BGSSaveLoad.h delete mode 100644 include/RE/B/BGSSaveLoadManager.h create mode 100644 include/RE/B/BSPauseRequester.h diff --git a/include/RE/B/BGSSaveLoad.h b/include/RE/B/BGSSaveLoad.h new file mode 100644 index 00000000..2330c391 --- /dev/null +++ b/include/RE/B/BGSSaveLoad.h @@ -0,0 +1,225 @@ +#pragma once + +#include "RE/B/BSPauseRequester.h" +#include "RE/B/BSFixedString.h" +#include "RE/B/BSTArray.h" +#include "RE/B/BSTEvent.h" +#include "RE/B/BSTScatterTable.h" + +namespace RE +{ + namespace PlayerNameEvent + { + struct NameChangedEvent; + } + + class BGSSaveLoadFile + { + public: + + }; + + class BGSSaveLoadFileEntry + { + public: + [[nodiscard]] char* GetFileName() const + { + return fileName; + } + + [[nodiscard]] char* GetPlayerName() + { + if (playerName) + return playerName; + + LoadData(); + return playerName; + } + + [[nodiscard]] bool IsAutoSave() const + { + return fileName && !_strnicmp(fileName, "AutoSave", 8); + } + + [[nodiscard]] bool IsExitSave() const + { + return fileName && !_strnicmp(fileName, "ExitSave", 8); + } + + [[nodiscard]] bool IsGenerated() const + { + return fileName && !_strnicmp(fileName, "Save", 4); + } + + [[nodiscard]] bool IsQuickSave() const + { + return fileName && !_strnicmp(fileName, "QuickSave", 9); + } + + void LoadData() + { + using func_t = decltype(&BGSSaveLoadFileEntry::LoadData); + static REL::Relocation func{ ID::BGSSaveLoadFileEntry::LoadData }; + func(this); + } + + // members + char* fileName; // 00 + char* playerName; // 08 + }; + + class BGSSaveLoadThread + { + public: + SF_RTTI_VTABLE(BGSSaveLoadThread); + + struct AsyncRequest + { + using TaskFinishedCallback_t = std::add_pointer_t; + }; + }; + + class BGSSaveLoadManager : + public BSTEventSink, // 000 + public BSPauseRequester // 008 + { + public: + SF_RTTI_VTABLE(BGSSaveLoadManager); + + enum class SaveFileCategory : std::uint32_t + { + kUser = 0, + kAuto = 1, + kQuick = 2, + kExit = 3 + }; + + enum class QueuedTask : std::uint32_t + { + kAutoSave = 0x1, + kForceSave = 0x2, + kQuickSave = 0x8, + kQuickLoad = 0x10, + kLoadGame = 0x40, + kAutoSaveCommit = 0x200, + kQuickSaveCommit = 0x400, + kBuildSaveGameList = 0x1000, + kSaveAndQuit = 0x4000, + }; + + virtual ~BGSSaveLoadManager(); // 00 + + // override (BSTEventSink) + BSEventNotifyControl ProcessEvent(const PlayerNameEvent::NameChangedEvent& a_event, BSTEventSource* a_source) override; // 01 + + [[nodiscard]] static auto GetSingleton() + { + static REL::Relocation singleton{ ID::BGSSaveLoadManager::Singleton }; + return *singleton; + } + + [[nodiscard]] static bool IsSaveFileNameAutoSave(const char* a_name) + { + return a_name && !_strnicmp(a_name, "AutoSave", 8); + } + + [[nodiscard]] static bool IsSaveFileNameExitSave(const char* a_name) + { + return a_name && !_strnicmp(a_name, "ExitSave", 8); + } + + [[nodiscard]] static bool IsSaveFileNameGenerated(const char* a_name) + { + return a_name && !_strnicmp(a_name, "Save", 4); + } + + [[nodiscard]] static bool IsSaveFileNameQuickSave(const char* a_name) + { + return a_name && !_strnicmp(a_name, "QuickSave", 9); + } + + bool DeleteSaveFile(const char* a_filename, void* a_unk1, bool a_skipRemainingSavesCheck) + { + using func_t = decltype(&BGSSaveLoadManager::DeleteSaveFile); + static REL::Relocation func{ ID::BGSSaveLoadManager::DeleteSaveFile }; + return func(this, a_filename, a_unk1, a_skipRemainingSavesCheck); + } + + void QueueBuildSaveGameList(BGSSaveLoadThread::AsyncRequest::TaskFinishedCallback_t a_taskFinished = nullptr) + { + using func_t = decltype(&BGSSaveLoadManager::QueueBuildSaveGameList); + static REL::Relocation func{ ID::BGSSaveLoadManager::QueueBuildSaveGameList }; + func(this, a_taskFinished); + } + + void QueueLoadGame(BGSSaveLoadFileEntry* a_entry) + { + queuedEntryToLoad = a_entry; + queuedTasks.set(QueuedTask::kLoadGame); + } + + void QueueLoadMostRecentSaveGame() + { + if (!saveGameListBuilt) { + return QueueBuildSaveGameList([](bool a_result) { + if (a_result) + GetSingleton()->QueueLoadMostRecentSaveGame(); + }); + } + + if (saveGameCount > 0) + QueueLoadGame(saveGameList.back()); + } + + void QueueSaveLoadTask(QueuedTask a_task) + { + using func_t = decltype(&BGSSaveLoadManager::QueueSaveLoadTask); + static REL::Relocation func{ ID::BGSSaveLoadManager::QueueSaveLoadTask }; + func(this, a_task); + } + + [[nodiscard]] bool IsSaveGameListBuilt() const + { + return saveGameListBuilt; + } + + // members + std::byte pad010[0x008]; // 010 + BSTArray saveGameList; // 018 + std::byte pad028[0x008]; // 028 + bool saveGameListBuilt; // 030 + std::uint32_t saveGameCount; // 034 + std::uint32_t currentSaveGameNumber; // 038 + std::byte pad03C[0x004]; // 03C + std::uint64_t saveGameListBuildID; // 040 + std::uint32_t currentAutoSaveNumber; // 048 + std::byte pad04C[0x004]; // 04C + REX::EnumSet queuedTasks; // 050 + std::byte pad054[0x004]; // 054 + BGSSaveLoadFileEntry* queuedEntryToLoad; // 058 + std::byte pad060[0x010]; // 060 + char* mostRecentSaveGame; // 070 + std::int32_t mostRecentSaveGameDeviceID; // 078 + std::byte pad07C[0x08C]; // 07C + std::uint64_t currentPlayerID; // 108 + std::uint64_t displayPlayerID; // 110 + BSFixedString saveFileNameToDelete; // 118 + BSTHashMap autoSaveFileNames; // 120 + BSFixedString quickSaveFileName; // 158 + BSFixedString exitSaveFileName; // 160 + std::byte pad168[0x010]; // 168 + BGSSaveLoadFile* saveLoadFile; // 178 + bool returnedFromSysUtil; // 180 + bool sysUtilLoadDataComplete; // 181 + }; + static_assert(offsetof(BGSSaveLoadManager, saveGameList) == 0x018); + static_assert(offsetof(BGSSaveLoadManager, saveGameListBuilt) == 0x030); + static_assert(offsetof(BGSSaveLoadManager, saveGameCount) == 0x034); + static_assert(offsetof(BGSSaveLoadManager, saveGameListBuildID) == 0x040); + static_assert(offsetof(BGSSaveLoadManager, queuedTasks) == 0x050); + static_assert(offsetof(BGSSaveLoadManager, mostRecentSaveGame) == 0x070); + static_assert(offsetof(BGSSaveLoadManager, currentPlayerID) == 0x108); + static_assert(offsetof(BGSSaveLoadManager, quickSaveFileName) == 0x158); + static_assert(offsetof(BGSSaveLoadManager, saveLoadFile) == 0x178); + static_assert(offsetof(BGSSaveLoadManager, sysUtilLoadDataComplete) == 0x181); +} diff --git a/include/RE/B/BGSSaveLoadManager.h b/include/RE/B/BGSSaveLoadManager.h deleted file mode 100644 index ab4918fd..00000000 --- a/include/RE/B/BGSSaveLoadManager.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -namespace RE -{ - class BGSSaveLoadManager - { - public: - static BGSSaveLoadManager* GetSingleton() - { - static REL::Relocation singleton{ ID::BGSSaveLoadManager::Singleton }; - return *singleton; - } - - bool DeleteSaveFile(const char* a_filename, void* a_unk1, bool a_unk2) - { - using func_t = decltype(&BGSSaveLoadManager::DeleteSaveFile); - static REL::Relocation func{ ID::BGSSaveLoadManager::DeleteSaveFile }; - return func(this, a_filename, a_unk1, a_unk2); - } - - // members - std::uint8_t unk00[0x110]; // 000 - std::uint64_t currentPlayerID; // 110 - }; - static_assert(offsetof(BGSSaveLoadManager, currentPlayerID) == 0x110); -} diff --git a/include/RE/B/BSPauseRequester.h b/include/RE/B/BSPauseRequester.h new file mode 100644 index 00000000..e7a731f9 --- /dev/null +++ b/include/RE/B/BSPauseRequester.h @@ -0,0 +1,12 @@ +#pragma once + +namespace RE +{ + class BSPauseRequester + { + public: + SF_RTTI_VTABLE(BSPauseRequester); + + virtual ~BSPauseRequester(); + }; +} diff --git a/include/RE/E/Events.h b/include/RE/E/Events.h index e8e5914f..88d3a87f 100644 --- a/include/RE/E/Events.h +++ b/include/RE/E/Events.h @@ -233,6 +233,20 @@ namespace RE } }; + struct BGSAppPausedEvent + { + [[nodiscard]] static BSTEventSource* GetEventSource() + { + using func_t = decltype(&BGSAppPausedEvent::GetEventSource); + static REL::Relocation func{ REL::ID(167011) }; + return func(); + } + + // members + bool paused; // 00 + }; + static_assert(sizeof(BGSAppPausedEvent) == 0x1); + struct BGSCellGridLoadEvent { [[nodiscard]] static BSTEventSource* GetEventSource() @@ -2290,6 +2304,24 @@ namespace RE } }; + struct PlayerDifficultySettingChanged + { + struct Event + { + [[nodiscard]] static BSTEventSource* GetEventSource() + { + using func_t = decltype(&PlayerDifficultySettingChanged::Event::GetEventSource); + static REL::Relocation func{ REL::ID(153667) }; + return func(); + } + + // members + std::uint32_t oldDifficulty; // 00 + std::uint32_t newDifficulty; // 04 + }; + static_assert(sizeof(PlayerDifficultySettingChanged::Event) == 0x8); + }; + struct PlayerFastTravel { struct Event diff --git a/include/RE/IDs.h b/include/RE/IDs.h index af4d2e64..9e7db4fb 100644 --- a/include/RE/IDs.h +++ b/include/RE/IDs.h @@ -65,6 +65,11 @@ namespace RE::ID inline constexpr REL::ID ctor{ 101725 }; } + namespace BGSSaveLoadFileEntry + { + inline constexpr REL::ID LoadData{ 147331 }; + } + namespace BGSSaveLoadGame { inline constexpr REL::ID SaveGame{ 147515 }; @@ -73,8 +78,10 @@ namespace RE::ID namespace BGSSaveLoadManager { - inline constexpr REL::ID Singleton{ 880997 }; inline constexpr REL::ID DeleteSaveFile{ 147844 }; + inline constexpr REL::ID QueueBuildSaveGameList{ 147900 }; + inline constexpr REL::ID QueueSaveLoadTask{ 1536882 }; + inline constexpr REL::ID Singleton{ 880997 }; } namespace BGSStoryTeller diff --git a/include/RE/M/Main.h b/include/RE/M/Main.h index afcbb97f..bf891da5 100644 --- a/include/RE/M/Main.h +++ b/include/RE/M/Main.h @@ -47,8 +47,14 @@ namespace RE } // members - std::byte pad008[0x440]; // 008 + std::byte pad008[0x020]; // 008 + bool quitGame; // 028 + std::byte pad029[0x005]; // 029 + bool resetGame; // 02E + std::byte pad02F[0x419]; // 02F bool isGameMenuPaused; // 448 }; + static_assert(offsetof(Main, quitGame) == 0x028); + static_assert(offsetof(Main, resetGame) == 0x02E); static_assert(offsetof(Main, isGameMenuPaused) == 0x448); } diff --git a/include/RE/Starfield.h b/include/RE/Starfield.h index 8711f2b6..e001d226 100644 --- a/include/RE/Starfield.h +++ b/include/RE/Starfield.h @@ -144,7 +144,7 @@ #include "RE/B/BGSResourceGenerationData.h" #include "RE/B/BGSReverbParameters.h" #include "RE/B/BGSSaveLoadGame.h" -#include "RE/B/BGSSaveLoadManager.h" +#include "RE/B/BGSSaveLoad.h" #include "RE/B/BGSScene.h" #include "RE/B/BGSSecondaryDamageList.h" #include "RE/B/BGSShaderParticleGeometryData.h" @@ -197,6 +197,7 @@ #include "RE/B/BSIntrusiveRefCounted.h" #include "RE/B/BSLock.h" #include "RE/B/BSLog.h" +#include "RE/B/BSPauseRequester.h" #include "RE/B/BSPointerHandle.h" #include "RE/B/BSReflection.h" #include "RE/B/BSResourceEnums.h"