From 911625ac23ec95a73758de7a8348af674166e13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 1 Nov 2020 18:25:47 +0100 Subject: [PATCH] ksys/res: Add GameSaveData --- data/uking_functions.csv | 28 +-- ...ameSaveData9doCreate_EPhjPN4sead4HeapE.bin | Bin 0 -> 1572 bytes ...ask13submitRequestERNS0_11TaskRequestE.bin | Bin 536 -> 536 bytes lib/sead | 2 +- src/KingSystem/Resource/CMakeLists.txt | 2 + .../Resource/resResourceGameSaveData.cpp | 207 ++++++++++++++++++ .../Resource/resResourceGameSaveData.h | 109 +++++++++ 7 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 expected/_ZN4ksys3res12GameSaveData9doCreate_EPhjPN4sead4HeapE.bin create mode 100644 src/KingSystem/Resource/resResourceGameSaveData.cpp create mode 100644 src/KingSystem/Resource/resResourceGameSaveData.h diff --git a/data/uking_functions.csv b/data/uking_functions.csv index 292caa4b..2ef40f93 100644 --- a/data/uking_functions.csv +++ b/data/uking_functions.csv @@ -92284,20 +92284,20 @@ 0x000000710127a9b4,LowPrioThreadMgr::submitRequest,412, 0x000000710127ac44,sub_710127AC44,112, 0x000000710127acb4,sub_710127ACB4,92, -0x000000710127ad10,sub_710127AD10,140, -0x000000710127ad9c,sub_710127AD9C,164, -0x000000710127ae40,Bgsvdata::File::forEachFlag,140, -0x000000710127aecc,sub_710127AECC,204, -0x000000710127af98,Bgsvdata::doCreate,1572, -0x000000710127b5bc,Bgsvdata::cleanUp,156, -0x000000710127b658,Bgsvdata::allocFileList,12, -0x000000710127b664,Bgsvdata::addFile,112, -0x000000710127b6d4,Bgsvdata::allocFileFlags,228, -0x000000710127b7b8,Bgsvdata::copyFilesFrom,932, -0x000000710127bb5c,Bgsvdata::FlagArray::sort,936, -0x000000710127bf04,Bgsvdata::sort,64, -0x000000710127bf44,Bgsvdata::dtor,160, -0x000000710127bfe4,Bgsvdata::dtorDelete,168, +0x000000710127ad10,sub_710127AD10,140,_ZN4ksys3res12GameSaveData4File11forEachFlagERKN4sead9Delegate1INS_7SaveMgrERKNS1_4FlagEEE +0x000000710127ad9c,sub_710127AD9C,164,_ZN4ksys3res12GameSaveData4File11forEachFlagERKN4sead9Delegate1INS_7SaveMgrERKNS1_4FlagEEEii +0x000000710127ae40,Bgsvdata::File::forEachFlag,140,_ZN4ksys3res12GameSaveData4File11forEachFlagERKN4sead9Delegate1INS_7SaveMgrERNS1_4FlagEEE +0x000000710127aecc,sub_710127AECC,204,_ZNK4ksys3res12GameSaveData4File13findFlagIndexEj +0x000000710127af98,Bgsvdata::doCreate,1572,_ZN4ksys3res12GameSaveData9doCreate_EPhjPN4sead4HeapE? +0x000000710127b5bc,Bgsvdata::cleanUp,156,_ZN4ksys3res12GameSaveData8finalizeEv +0x000000710127b658,Bgsvdata::allocFileList,12,_ZN4ksys3res12GameSaveData10allocFilesEiPN4sead4HeapE +0x000000710127b664,Bgsvdata::addFile,112,_ZN4ksys3res12GameSaveData7addFileEjPN4sead4HeapE +0x000000710127b6d4,Bgsvdata::allocFileFlags,228,_ZN4ksys3res12GameSaveData10allocFlagsEjiPN4sead4HeapE +0x000000710127b7b8,Bgsvdata::copyFilesFrom,932,_ZN4ksys3res12GameSaveData8copyFromERKS1_PN4sead4HeapE +0x000000710127bb5c,Bgsvdata::FlagArray::sort,936,_ZN4ksys3resL21sortGameSaveDataFlagsERN4sead10RingBufferINS0_12GameSaveData4FlagEEE? +0x000000710127bf04,Bgsvdata::sort,64,_ZN4ksys3res12GameSaveData9sortFlagsEv +0x000000710127bf44,Bgsvdata::dtor,160,_ZN4ksys3res12GameSaveDataD2Ev +0x000000710127bfe4,Bgsvdata::dtorDelete,168,_ZN4ksys3res12GameSaveDataD0Ev 0x000000710127c08c,sub_710127C08C,56, 0x000000710127c0c4,sub_710127C0C4,92, 0x000000710127c120,AutoGenFramework::a,144, diff --git a/expected/_ZN4ksys3res12GameSaveData9doCreate_EPhjPN4sead4HeapE.bin b/expected/_ZN4ksys3res12GameSaveData9doCreate_EPhjPN4sead4HeapE.bin new file mode 100644 index 0000000000000000000000000000000000000000..7defce769512d86d5d7026721ee12911b19bb84a GIT binary patch literal 1572 zcmZuyTWDNW6y4{ZOmgp}X`Qs@WtwRoEt!c$9DHO1$;rbqNh-xUpoT)E`EaHq6s=H7 zYaT`J}X8rQj*SP?&lb7t?e z&t7M(z4y(J^4ay1roH-_Wv}%6?Rzhk+INraus6<@*&Cf)#tizy{G>yfOsIQ3L9{UV(>i7?E{?&^*o+7{Sy^#f8;WX{Q zoh5bH?~P>eUzKc5Wr5qnLk&{n&2ZBR)>AadNF~ZiHQ}B_xlrDjrUuCZEhguAm6PGs&H`{f1fFVB&$N1P{iaj(F*@&YKhq%dixk!G>dQx^uCdGj z$^4siM!)kkC$+kZ)M3;Dl|JBj7`$n|vfyjE0X%_g&DE#isvF#7!AIyyCv>7{bAhwW zt}UK|oGlT!(LCgMFcKy!-J5y9Lk~F7Z`VA86G?BcdC(lRfrAj%|KlOct?++&s4noZ z03N6L(n8Q`t-36(dtumP8|2y89nPrnXq(*=!E1-&%3mc$89 zFO5xO()#oiup!>l2s(Qj<+wj=288Q}Ms3VYHAAB+4_hVlsd6p>+xm&tR*gmL!ZN6N zXwh-2L0&*_ZreM4LqGhw<(v4oy{l2O+?M@)*l!fm8|hMtUmX;aIbIt{4u1JjCv+M3 zx=UReJU6L(CC{xTV$(nCo*jUmg44C+@8@nquUY>_*KOUy4$;%-pU--Yn?IP3l5GlI zw{*>dW{U;8{~+1^gUmKZNTU9hM)LXiGWt&8H|!@hw9~jgT}SSd6YM%iy-vPE^26gK zPvf00GrD{aT&>}*`#bl^W5`qW`Lj~bs3WL3-Q#+eVa|k4;a$T;+w)A@DxI}ch8=+2 zn`xceR^_aglA1AKD}|N@g$(WrV +#include +#include "KingSystem/GameData/gdtManager.h" +#include "KingSystem/Utils/Byaml.h" + +namespace ksys::res { + +void GameSaveData::File::forEachFlag(const sead::Delegate1& delegate) { + for (const auto& flag : flags) + delegate(flag); +} + +void GameSaveData::File::forEachFlag(const sead::Delegate1& delegate, + s32 start, s32 end) { + if (start == end) + return; + + for (auto it = flags.begin(start), it_end = flags.begin(end); it != it_end; ++it) + delegate(*it); +} + +void GameSaveData::File::forEachFlag(const sead::Delegate1& delegate) { + for (auto& flag : flags) + delegate(flag); +} + +s32 GameSaveData::File::findFlagIndex(u32 flag_name_hash) const { + s32 a = 0; + s32 b = flags.size() - 1; + while (a < b) { + const s32 m = (a + b) / 2; + if (u32(flags[m].kv.name_hash) == flag_name_hash) + return m; + if (u32(flags[m].kv.name_hash) < flag_name_hash) + a = m + 1; + else + b = m; + } + + if (u32(flags[a].kv.name_hash) == flag_name_hash) + return a; + return -1; +} + +// NON_MATCHING: cNullChar is loaded too late (which throws off a lot of things) and Clang is +// using a different register to access file->info +void GameSaveData::doCreate_(u8* buffer, u32, sead::Heap*) { + auto* heap = gdt::Manager::instance()->getSaveAreaHeap(); + al::ByamlIter root_iter{buffer}; + al::ByamlIter file_info_iter; + al::ByamlIter save_info_iter; + al::ByamlIter flags_iter; + + { + al::ByamlIter iter; + if (root_iter.tryGetIterByKey(&iter, "file_list")) { + iter.tryGetIterByIndex(&file_info_iter, 0); + iter.tryGetIterByIndex(&flags_iter, 1); + } + al::ByamlIter iter2; + if (root_iter.tryGetIterByKey(&iter2, "save_info")) + iter2.tryGetIterByIndex(&save_info_iter, 0); + } + + mFiles.allocBuffer(1, heap); + + mSaveInfo = new (heap) SaveInfo; + save_info_iter.tryGetIntByKey(&mSaveInfo->revision, "revision"); + save_info_iter.tryGetIntByKey(&mSaveInfo->directory_num, "directory_num"); + save_info_iter.tryGetBoolByKey(&mSaveInfo->is_build_machine, "is_build_machine"); + +#ifdef MATCHING_HACK_NX_CLANG + // This isn't required but makes the diff cleaner... cNullChar is loaded here in the original. + *static_cast(&sead::SafeString::cNullChar); +#endif + + const auto num_flags = flags_iter.getSize(); + + auto* file = new (heap) File; + file->name_hash = std::numeric_limits::min(); + file->info = new (heap) FileInfo; + + const char* file_name = ""; + file_info_iter.tryGetStringByKey(&file_name, "file_name"); + auto* file_name_str = new (heap) sead::FixedSafeString<64>; + file_name_str->copy(file_name); + file->info->name = file_name_str->cstr(); + + file_info_iter.tryGetBoolByKey(&file->info->is_common, "IsCommon"); + file_info_iter.tryGetBoolByKey(&file->info->is_common_at_same_account, "IsCommonAtSameAccount"); + file_info_iter.tryGetBoolByKey(&file->info->is_save_secure_code, "IsSaveSecureCode"); + + if (num_flags > 0) { + file->flags.allocBuffer(num_flags, heap); + for (s32 i = 0; i < num_flags; ++i) { + al::ByamlIter iter; + if (!flags_iter.tryGetIterByIndex(&iter, i)) + continue; + + Flag flag; + iter.tryGetIntByKey(&flag.kv.name_hash, "HashValue"); + const char* data_name = ""; + iter.tryGetStringByKey(&data_name, "DataName"); + const s32 data_name_hash = sead::HashCRC32::calcStringHash(data_name); + if (flag.kv.name_hash != data_name_hash) + flag.kv.name_hash = data_name_hash; + + file->flags.pushBack(flag); + } + } + + mFiles.pushBack(file); +} + +void GameSaveData::finalize() { + if (mSaveInfo) + delete mSaveInfo; + + if (mFiles.isBufferReady()) { + for (auto it = mFiles.begin(), end = mFiles.end(); it != end; ++it) { + it->flags.freeBuffer(); + delete &*it; + [[maybe_unused]] auto next = it; + ++next; + } + mFiles.freeBuffer(); + } +} + +void GameSaveData::allocFiles(s32 count, sead::Heap* heap) { + mFiles.allocBuffer(count, heap); +} + +void GameSaveData::addFile(u32 hash, sead::Heap* heap) { + if (!mFiles.isBufferReady()) + return; + + auto* file = new (heap) File; + file->info = nullptr; + file->name_hash = hash; + mFiles.pushBack(file); +} + +std::optional GameSaveData::getFile(u32 hash) const { + for (auto& file : mFiles) + if (file.name_hash == hash) + return &file; + + return std::nullopt; +} + +void GameSaveData::allocFlags(u32 file_name_hash, s32 count, sead::Heap* heap) { + if (!mFiles.isBufferReady()) + return; + + auto file = getFile(file_name_hash); + if (!file) + return; + + (*file)->flags.allocBuffer(count, heap); +} + +void GameSaveData::copyFrom(const GameSaveData& src, sead::Heap* heap) { + if (!mSaveInfo && src.mSaveInfo) { + mSaveInfo = new (heap) SaveInfo; + *mSaveInfo = *src.mSaveInfo; + } + + if (!mFiles.isBufferReady()) + return; + + auto it = mFiles.begin(), end = mFiles.end(); + const File& src_file = *src.mFiles[0]; + for (; it != end; ++it) { + const u32 other_hash = sead::HashCRC32::calcStringHash(src_file.info->name); + if (it->name_hash != other_hash) + continue; + + if (it->info == nullptr) { + it->info = new (heap) FileInfo; + auto* target = it->info; + *target = *src_file.info; + } + + for (auto flag = src_file.flags.begin(), e = src_file.flags.end(); flag != e; ++flag) + it->flags.pushBack(*flag); + } +} + +[[gnu::noinline]] static void sortGameSaveDataFlags(sead::RingBuffer& flags) { + const auto wrapper = sead::stdIterator(flags); + std::sort(wrapper.begin(), wrapper.end(), + [](const GameSaveData::Flag& lhs, const GameSaveData::Flag& rhs) { + return lhs.kv.name_hash < rhs.kv.name_hash; + }); +} + +void GameSaveData::sortFlags() { + if (!mFiles.isBufferReady()) + return; + + for (auto& file : mFiles) + sortGameSaveDataFlags(file.flags); +} + +} // namespace ksys::res diff --git a/src/KingSystem/Resource/resResourceGameSaveData.h b/src/KingSystem/Resource/resResourceGameSaveData.h new file mode 100644 index 00000000..7b36ec49 --- /dev/null +++ b/src/KingSystem/Resource/resResourceGameSaveData.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "KingSystem/GameData/gdtFlag.h" +#include "KingSystem/Utils/Types.h" + +namespace ksys { +class SaveMgr; +} + +namespace ksys::res { + +class GameSaveData : public sead::DirectResource { +public: + struct SaveInfo { + SaveInfo() = default; + SaveInfo(const SaveInfo& other) { *this = other; } + SaveInfo& operator=(const SaveInfo& other) { + revision = other.revision; + directory_num = other.directory_num; + is_build_machine = other.is_build_machine; + return *this; + } + + s32 revision = 0; + s32 directory_num = 0; + bool is_build_machine = false; + }; + KSYS_CHECK_SIZE_NX150(SaveInfo, 0xc); + + struct FileInfo { + FileInfo() = default; + FileInfo(const FileInfo& other) { *this = other; } + FileInfo& operator=(const FileInfo& other) { + name = other.name; + is_common = other.is_common; + is_common_at_same_account = other.is_common_at_same_account; + is_save_secure_code = other.is_save_secure_code; + return *this; + } + + sead::FixedSafeString<32> name = sead::SafeString::cEmptyString; + bool is_common = false; + bool is_common_at_same_account = false; + bool is_save_secure_code = false; + }; + KSYS_CHECK_SIZE_NX150(FileInfo, 0x40); + + struct Flag { + Flag() = default; + Flag(const Flag& other) { *this = other; } + Flag& operator=(const Flag& other) { + _8 = other._8; + kv = other.kv; + type = other.type; + return *this; + } + + struct KeyValue { + s32 name_hash = std::numeric_limits::min(); + s32 value = std::numeric_limits::min(); + }; + KeyValue kv; + u32 _8 = 0; + gdt::FlagType type = gdt::FlagType::Invalid; + }; + KSYS_CHECK_SIZE_NX150(Flag, 0x10); + + struct File { + void forEachFlag(const sead::Delegate1& delegate); + void forEachFlag(const sead::Delegate1& delegate, s32 start, s32 end); + void forEachFlag(const sead::Delegate1& delegate); + s32 findFlagIndex(u32 flag_name_hash) const; + + u32 name_hash = std::numeric_limits::min(); + FileInfo* info = nullptr; + sead::RingBuffer flags; + }; + KSYS_CHECK_SIZE_NX150(File, 0x28); + + GameSaveData() = default; + ~GameSaveData() override { finalize(); } + + void finalize(); + + void allocFiles(s32 count, sead::Heap* heap); + void addFile(u32 hash, sead::Heap* heap); + std::optional getFile(u32 hash) const; + void allocFlags(u32 file_name_hash, s32 count, sead::Heap* heap); + void copyFrom(const GameSaveData& src, sead::Heap* heap); + void sortFlags(); + + const sead::PtrArray& getFiles() const { return mFiles; } + +private: + void doCreate_(u8* buffer, u32 size, sead::Heap* heap) override; + + SaveInfo* mSaveInfo = nullptr; + sead::PtrArray mFiles; +}; +KSYS_CHECK_SIZE_NX150(GameSaveData, 0x38); + +} // namespace ksys::res