diff --git a/targets/app/common/src/UI/Scenes/Frontend Menu screens/UIScene_LoadOrJoinMenu.cpp b/targets/app/common/src/UI/Scenes/Frontend Menu screens/UIScene_LoadOrJoinMenu.cpp index e1610d1f4..895b027eb 100644 --- a/targets/app/common/src/UI/Scenes/Frontend Menu screens/UIScene_LoadOrJoinMenu.cpp +++ b/targets/app/common/src/UI/Scenes/Frontend Menu screens/UIScene_LoadOrJoinMenu.cpp @@ -547,13 +547,43 @@ void UIScene_LoadOrJoinMenu::tick() { m_iState = e_SavesIdle; break; } -#else - if (!m_bSavesDisplayed) { - AddDefaultButtons(); - m_bSavesDisplayed = true; - m_controlSavesTimer.setVisible(false); - } -#endif + // 4jcraft: the game literally defaulted to nothing but default buttons + // if the game was on a non console / recognized platform. + #else + if (!m_bSavesDisplayed) { + m_pSaveDetails = StorageManager.ReturnSavesInfo(); + if (m_pSaveDetails != nullptr) { + AddDefaultButtons(); + + if (m_saveDetails != nullptr) { + for (unsigned int i = 0; i < m_iSaveDetailsCount; ++i) { + if (m_saveDetails[i].pbThumbnailData != nullptr) + delete m_saveDetails[i].pbThumbnailData; + } + delete[] m_saveDetails; + } + + m_saveDetails = new SaveListDetails[m_pSaveDetails->iSaveC]; + m_iSaveDetailsCount = m_pSaveDetails->iSaveC; + + for (unsigned int i = 0; i < m_pSaveDetails->iSaveC; ++i) { + m_buttonListSaves.addItem( + m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, L""); + + m_saveDetails[i].saveId = i; + memcpy(m_saveDetails[i].UTF8SaveName, + m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, 128); + memcpy(m_saveDetails[i].UTF8SaveFilename, + m_pSaveDetails->SaveInfoA[i].UTF8SaveFilename, + MAX_SAVEFILENAME_LENGTH); + } + + m_bSavesDisplayed = true; + m_controlSavesTimer.setVisible(false); + UpdateGamesList(); + } + } + #endif // SAVE TRANSFERS } diff --git a/targets/java/src/File.cpp b/targets/java/src/File.cpp index 4af84d5de..defb9171c 100644 --- a/targets/java/src/File.cpp +++ b/targets/java/src/File.cpp @@ -8,9 +8,14 @@ #include #include +#if defined(__linux__) +#include +#endif + #include "util/StringHelpers.h" // 4jcraft TODO #include "platform/PlatformServices.h" #include "java/FileFilter.h" +#include "platform/StdFileIO.h" const wchar_t File::pathSeparator = L'/'; @@ -64,11 +69,11 @@ File::File(const std::wstring& pathname) { m_abstractPathName = fixedPath; #if defined(__linux__) - std::string request = std::filesystem::path(m_abstractPathName).string(); + std::string request = wstringtofilename(m_abstractPathName); while (!request.empty() && request[0] == '/') request.erase(0, 1); if (request.find("res/") == 0) request.erase(0, 4); - std::string exeDir = PlatformFileIO.getBasePath().string(); + std::string exeDir = StdFileIO().getBasePath().string(); std::string fileName = request; size_t lastSlash = fileName.find_last_of('/'); if (lastSlash != std::string::npos) @@ -78,21 +83,24 @@ File::File(const std::wstring& pathname) { "/Common/res/TitleUpdate/res/", "/Common/Media/", "/Common/res/", - "/Common/", - "resources/"}; + "/Common/"}; for (const char* base : bases) { std::string tryFull = exeDir + base + request; std::string tryFile = exeDir + base + fileName; - if (PlatformFileIO.exists(tryFull)) { + if (access(tryFull.c_str(), F_OK) != -1) { m_abstractPathName = convStringToWstring(tryFull); return; } - if (PlatformFileIO.exists(tryFile)) { + if (access(tryFile.c_str(), F_OK) != -1) { m_abstractPathName = convStringToWstring(tryFile); return; } } + + // 4jcraft: If it's not a core asset, anchor it to the executable directory + // so save files don't break + m_abstractPathName = convStringToWstring(exeDir + "/" + request); #endif #ifdef _WINDOWS64 @@ -155,7 +163,8 @@ this->parent = nullptr; // deleted; false otherwise bool File::_delete() { std::error_code error; - const bool result = fs::remove(ToFilesystemPath(getPath()), error); + // 4jcraft: remove all better + const bool result = fs::remove_all(ToFilesystemPath(getPath()), error); if (!result || error) { #ifndef _CONTENT_PACKAGE printf("File::_delete - Error code %d (%#0.8X)\n", error.value(), @@ -401,4 +410,4 @@ int File::hash_fnct(const File& k) { } return (int)hashCode; -} \ No newline at end of file +} diff --git a/targets/minecraft/server/network/PendingConnection.cpp b/targets/minecraft/server/network/PendingConnection.cpp index bdb7ca9c6..3f10eaa66 100644 --- a/targets/minecraft/server/network/PendingConnection.cpp +++ b/targets/minecraft/server/network/PendingConnection.cpp @@ -99,7 +99,7 @@ void PendingConnection::sendPreLoginResponse() { std::uint8_t ugcXuidCount = 0; std::uint8_t hostIndex = 0; std::uint8_t ugcFriendsOnlyBits = 0; - char szUniqueMapName[14]; + char szUniqueMapName[256]{}; StorageManager.GetSaveUniqueFilename(szUniqueMapName); @@ -241,4 +241,4 @@ std::wstring PendingConnection::getName() { bool PendingConnection::isServerPacketListener() { return true; } -bool PendingConnection::isDisconnected() { return done; } \ No newline at end of file +bool PendingConnection::isDisconnected() { return done; } diff --git a/targets/platform/meson.build b/targets/platform/meson.build index 893bcf60c..ac730d4d9 100644 --- a/targets/platform/meson.build +++ b/targets/platform/meson.build @@ -1,16 +1,17 @@ platform_inc = include_directories('.') _threads = dependency('threads') -lib_platform_core = static_library('platform_core', - files('C4JThread.cpp', 'ShutdownManager.cpp'), - include_directories: [platform_inc, include_directories('..')], - dependencies: [_threads], - cpp_args: global_cpp_args + global_cpp_defs, +lib_platform_core = static_library( + 'platform_core', + files('C4JThread.cpp', 'ShutdownManager.cpp'), + include_directories: [platform_inc, include_directories('..')], + dependencies: [_threads], + cpp_args: global_cpp_args + global_cpp_defs, ) platform_dep = declare_dependency( - link_with: lib_platform_core, - include_directories: platform_inc, + link_with: lib_platform_core, + include_directories: platform_inc, ) # SDL2-based platform implementations (formerly 4J.* modules) @@ -18,34 +19,35 @@ _sdl2 = dependency('sdl2') _defs = [] if get_option('renderer') == 'gles' - _gl = dependency('glesv2', required: true) - _defs += ['-DGLES'] + _gl = dependency('glesv2', required: true) + _defs += ['-DGLES'] else - _gl = dependency('gl', required: true) + _gl = dependency('gl', required: true) endif sdl2_sources = files( - 'sdl2/Input.cpp', - 'sdl2/Profile.cpp', - 'sdl2/Render.cpp', - 'sdl2/render_stubs.cpp', - 'sdl2/Storage.cpp', + 'sdl2/Input.cpp', + 'sdl2/Profile.cpp', + 'sdl2/Render.cpp', + 'sdl2/Storage.cpp', + 'sdl2/render_stubs.cpp', ) -lib_platform_sdl2 = static_library('platform_sdl2', - sdl2_sources, - include_directories: [platform_inc, include_directories('sdl2')], - dependencies: [_sdl2, _gl, _threads, glm_dep, stb_dep], - cpp_args: _defs + global_cpp_args + global_cpp_defs, +lib_platform_sdl2 = static_library( + 'platform_sdl2', + sdl2_sources, + include_directories: [platform_inc, include_directories('sdl2')], + dependencies: [_sdl2, _gl, _threads, glm_dep, stb_dep, java_dep], + cpp_args: _defs + global_cpp_args + global_cpp_defs, ) # Combined dependency: interfaces + SDL2 implementations # Replaces the old render_dep, input_dep, profile_dep, storage_dep render_dep = declare_dependency( - link_with: lib_platform_sdl2, - include_directories: [platform_inc, include_directories('sdl2')], - dependencies: [_sdl2, _gl, _threads, glm_dep], + link_with: lib_platform_sdl2, + include_directories: [platform_inc, include_directories('sdl2')], + dependencies: [_sdl2, _gl, _threads, glm_dep], ) input_dep = render_dep profile_dep = render_dep -storage_dep = render_dep +storage_dep = render_dep \ No newline at end of file diff --git a/targets/platform/sdl2/Storage.cpp b/targets/platform/sdl2/Storage.cpp index 012214a69..c0bff4a1b 100644 --- a/targets/platform/sdl2/Storage.cpp +++ b/targets/platform/sdl2/Storage.cpp @@ -1,16 +1,56 @@ #include "Storage.h" #include +#include +#include #include +#include +#include +#include +#include #include #include +#include "StdFileIO.h" + C4JStorage StorageManager; static XMARKETPLACE_CONTENTOFFER_INFO s_dummyOffer = {}; static XCONTENT_DATA s_dummyContentData = {}; +// def save state +// TODO: find a better way +static SAVE_DETAILS* s_SavesInfo = nullptr; +static std::wstring s_CurrentSaveTitle = L"New World"; +static std::string s_CurrentSaveFilename = ""; +static bool s_SaveDisabled = false; + +// Console VFS Memory Blob +static std::uint8_t* s_SaveBuffer = nullptr; +static unsigned int s_SaveBufferSize = 0; + +struct SubfileData { + std::vector data; +}; +static std::map s_Subfiles; +static int s_SubfileCounter = 0; + +// helper functions +static StdFileIO s_FileIO; + +static std::filesystem::path GetSavesRoot() { + return s_FileIO.getUserDataPath() / "Saves"; +} + +static std::filesystem::path GetSaveDir(const std::string& saveFilename) { + return GetSavesRoot() / saveFilename; +} + +static std::filesystem::path GetSaveFile(const std::string& saveFilename, + const std::string& filename) { + return GetSaveDir(saveFilename) / filename; +} C4JStorage::C4JStorage() : m_pStringTable(nullptr) {} void C4JStorage::Tick(void) {} @@ -21,6 +61,7 @@ C4JStorage::EMessageResult C4JStorage::RequestMessageBox( std::function callback, C4JStringTable* pStringTable, wchar_t* pwchFormatString, unsigned int focusButton) { + if (callback) callback(pad, EMessage_ResultAccept); return EMessage_ResultAccept; } @@ -30,6 +71,7 @@ C4JStorage::EMessageResult C4JStorage::GetMessageBoxResult() { bool C4JStorage::SetSaveDevice(std::function callback, bool bForceResetOfSaveDevice) { + if (callback) callback(true); return true; } @@ -37,48 +79,127 @@ void C4JStorage::Init(unsigned int uiSaveVersion, const wchar_t* pwchDefaultSaveName, char* pszSavePackName, int iMinimumSaveSize, std::function callback, - const char* szGroupID) {} -void C4JStorage::ResetSaveData() {} + const char* szGroupID) { + std::error_code ec; + std::filesystem::create_directories(GetSavesRoot(), ec); +} + +void C4JStorage::ResetSaveData() { + s_CurrentSaveTitle = L"New World"; + s_CurrentSaveFilename = ""; + s_Subfiles.clear(); + s_SubfileCounter = 0; + if (s_SaveBuffer) { + free(s_SaveBuffer); + s_SaveBuffer = nullptr; + } + s_SaveBufferSize = 0; +} + void C4JStorage::SetDefaultSaveNameForKeyboardDisplay( - const wchar_t* pwchDefaultSaveName) {} -void C4JStorage::SetSaveTitle(const wchar_t* pwchDefaultSaveName) {} + const wchar_t* pwchDefaultSaveName) { + if (pwchDefaultSaveName) s_CurrentSaveTitle = pwchDefaultSaveName; +} + +void C4JStorage::SetSaveTitle(const wchar_t* pwchDefaultSaveName) { + if (pwchDefaultSaveName) s_CurrentSaveTitle = pwchDefaultSaveName; + + if (s_CurrentSaveFilename.empty()) { + char buf[16]; + snprintf(buf, sizeof(buf), "sv%06X", + (unsigned int)(time(NULL) & 0xFFFFFF)); + s_CurrentSaveFilename = buf; + + std::error_code ec; + std::filesystem::create_directories(GetSaveDir(s_CurrentSaveFilename), + ec); + } +} + bool C4JStorage::GetSaveUniqueNumber(int* piVal) { - if (piVal) *piVal = 0; + if (piVal) *piVal = 1; // she turned a 0 to a 1 now the world explodes return true; } + bool C4JStorage::GetSaveUniqueFilename(char* pszName) { - if (pszName) pszName[0] = '\0'; + if (pszName) { + strncpy(pszName, s_CurrentSaveFilename.c_str(), + MAX_SAVEFILENAME_LENGTH - 1); + pszName[MAX_SAVEFILENAME_LENGTH - 1] = '\0'; + } return true; } -void C4JStorage::SetSaveUniqueFilename(char* szFilename) {} +void C4JStorage::SetSaveUniqueFilename(char* szFilename) { + if (szFilename) s_CurrentSaveFilename = szFilename; +} void C4JStorage::SetState(ESaveGameControlState eControlState, - std::function callback) {} -void C4JStorage::SetSaveDisabled(bool bDisable) {} -bool C4JStorage::GetSaveDisabled(void) { return false; } -unsigned int C4JStorage::GetSaveSize() { return 0; } -void C4JStorage::GetSaveData(void* pvData, unsigned int* puiBytes) { - if (puiBytes) *puiBytes = 0; + std::function callback) { + if (callback) callback(true); } +void C4JStorage::SetSaveDisabled(bool bDisable) { s_SaveDisabled = bDisable; } +bool C4JStorage::GetSaveDisabled(void) { return s_SaveDisabled; } void* C4JStorage::AllocateSaveData(unsigned int uiBytes) { - return malloc(uiBytes); + if (s_SaveBuffer) free(s_SaveBuffer); + s_SaveBuffer = (std::uint8_t*)malloc(uiBytes); + s_SaveBufferSize = uiBytes; + return s_SaveBuffer; } +void C4JStorage::GetSaveData(void* pvData, unsigned int* puiBytes) { + if (puiBytes) *puiBytes = s_SaveBufferSize; + if (pvData && s_SaveBuffer) memcpy(pvData, s_SaveBuffer, s_SaveBufferSize); +} +unsigned int C4JStorage::GetSaveSize() { return s_SaveBufferSize; } void C4JStorage::SetSaveImages(std::uint8_t* pbThumbnail, unsigned int thumbnailBytes, std::uint8_t* pbImage, unsigned int imageBytes, std::uint8_t* pbTextData, - unsigned int textDataBytes) {} + unsigned int textDataBytes) { + if (pbThumbnail && thumbnailBytes > 0 && !s_CurrentSaveFilename.empty()) { + auto dir = GetSaveDir(s_CurrentSaveFilename); + std::error_code ec; + std::filesystem::create_directories(dir, ec); + s_FileIO.writeFile(dir / "thumbnail.png", pbThumbnail, thumbnailBytes); + } +} + C4JStorage::ESaveGameState C4JStorage::SaveSaveData( std::function callback) { - return ESaveGame_Idle; + if (s_CurrentSaveFilename.empty()) { + if (callback) callback(false); + return ESaveGame_Idle; + } + + auto dir = GetSaveDir(s_CurrentSaveFilename); + std::error_code ec; + std::filesystem::create_directories(dir, ec); + + // vfs blob -> disk + if (s_SaveBuffer && s_SaveBufferSize > 0) + s_FileIO.writeFile(dir / "savegame.dat", s_SaveBuffer, + s_SaveBufferSize); + + // save... title.. in txt... they do that.. honestly + // i would wrap world info in a json. + // but i'm not rewriting the whole + // save -> storage system sooooooo nope! + std::string titleStr(s_CurrentSaveTitle.begin(), s_CurrentSaveTitle.end()); + s_FileIO.writeFile(dir / "title.txt", titleStr.data(), titleStr.size()); + + if (callback) callback(true); + return ESaveGame_Save; } void C4JStorage::CopySaveDataToNewSave(std::uint8_t* pbThumbnail, unsigned int cbThumbnail, wchar_t* wchNewName, - std::function callback) {} + std::function callback) { + if (callback) callback(false); +} + void C4JStorage::SetSaveDeviceSelected(unsigned int uiPad, bool bSelected) {} bool C4JStorage::GetSaveDeviceSelected(unsigned int iPad) { return true; } C4JStorage::ESaveGameState C4JStorage::DoesSaveExist(bool* pbExists) { - if (pbExists) *pbExists = false; + if (pbExists) + *pbExists = s_FileIO.exists(GetSaveDir(s_CurrentSaveFilename)); return ESaveGame_Idle; } bool C4JStorage::EnoughSpaceForAMinSaveGame() { return true; } @@ -87,16 +208,87 @@ C4JStorage::ESaveGameState C4JStorage::GetSavesInfo( int iPad, std::function callback, char* pszSavePackName) { + ClearSavesInfo(); + + auto savesRoot = GetSavesRoot(); + std::error_code ec; + std::filesystem::create_directories(savesRoot, ec); + + std::vector saveDirs; + + for (const auto& entry : + std::filesystem::directory_iterator(savesRoot, ec)) { + if (!entry.is_directory()) continue; + + std::string name = entry.path().filename().string(); + bool hasSave = s_FileIO.exists(entry.path() / "savegame.dat") || + s_FileIO.exists(entry.path() / "subfile_0.dat") || + s_FileIO.exists(entry.path() / "title.txt"); + + if (hasSave) saveDirs.push_back(name); + } + + s_SavesInfo = new SAVE_DETAILS(); + s_SavesInfo->iSaveC = (int)saveDirs.size(); + + if (s_SavesInfo->iSaveC > 0) { + s_SavesInfo->SaveInfoA = new SAVE_INFO[s_SavesInfo->iSaveC]; + for (size_t i = 0; i < saveDirs.size(); ++i) { + SAVE_INFO& info = s_SavesInfo->SaveInfoA[i]; + memset(&info, 0, sizeof(SAVE_INFO)); + + strncpy(info.UTF8SaveFilename, saveDirs[i].c_str(), + MAX_SAVEFILENAME_LENGTH - 1); + + auto titleData = + s_FileIO.readFileToVec(GetSaveFile(saveDirs[i], "title.txt")); + if (!titleData.empty()) { + std::string title(titleData.begin(), titleData.end()); + strncpy(info.UTF8SaveTitle, title.c_str(), + MAX_DISPLAYNAME_LENGTH - 1); + } else { + strncpy(info.UTF8SaveTitle, saveDirs[i].c_str(), + MAX_DISPLAYNAME_LENGTH - 1); + } + } + } else { + s_SavesInfo->SaveInfoA = nullptr; + } + + if (callback) callback(s_SavesInfo, true); return ESaveGame_Idle; } -PSAVE_DETAILS C4JStorage::ReturnSavesInfo() { return nullptr; } -void C4JStorage::ClearSavesInfo() {} + +PSAVE_DETAILS C4JStorage::ReturnSavesInfo() { return s_SavesInfo; } + +void C4JStorage::ClearSavesInfo() { + if (s_SavesInfo) { + if (s_SavesInfo->SaveInfoA) { + for (int i = 0; i < s_SavesInfo->iSaveC; ++i) { + if (s_SavesInfo->SaveInfoA[i].thumbnailData) + delete[] s_SavesInfo->SaveInfoA[i].thumbnailData; + } + delete[] s_SavesInfo->SaveInfoA; + } + delete s_SavesInfo; + s_SavesInfo = nullptr; + } +} + C4JStorage::ESaveGameState C4JStorage::LoadSaveDataThumbnail( PSAVE_INFO pSaveInfo, - std::function + std::function callback) { - return ESaveGame_Idle; + if (pSaveInfo) { + auto data = s_FileIO.readFileToVec( + GetSaveFile(pSaveInfo->UTF8SaveFilename, "thumbnail.png")); + if (!data.empty()) { + if (callback) callback(data.data(), (unsigned int)data.size()); + return (C4JStorage::ESaveGameState)4; + } + } + if (callback) callback(nullptr, 0); + return (C4JStorage::ESaveGameState)4; } void C4JStorage::GetSaveCacheFileInfo(unsigned int fileIndex, XCONTENT_DATA& xContentData) { @@ -109,14 +301,49 @@ void C4JStorage::GetSaveCacheFileInfo(unsigned int fileIndex, if (pImageBytes) *pImageBytes = 0; } C4JStorage::ESaveGameState C4JStorage::LoadSaveData( - PSAVE_INFO pSaveInfo, - std::function callback) { - return ESaveGame_Idle; + PSAVE_INFO pSaveInfo, std::function callback) { + if (pSaveInfo) { + s_CurrentSaveFilename = pSaveInfo->UTF8SaveFilename; + std::string title(pSaveInfo->UTF8SaveTitle); + s_CurrentSaveTitle = std::wstring(title.begin(), title.end()); + + auto blobData = s_FileIO.readFileToVec( + GetSaveFile(s_CurrentSaveFilename, "savegame.dat")); + if (!blobData.empty()) { + if (s_SaveBuffer) free(s_SaveBuffer); + s_SaveBufferSize = (unsigned int)blobData.size(); + s_SaveBuffer = (std::uint8_t*)malloc(s_SaveBufferSize); + memcpy(s_SaveBuffer, blobData.data(), s_SaveBufferSize); + } + + // put in mem + s_Subfiles.clear(); + for (int i = 0; i < 50; i++) { + auto subData = s_FileIO.readFileToVec( + GetSaveFile(s_CurrentSaveFilename, + "subfile_" + std::to_string(i) + ".dat")); + if (!subData.empty()) { + SubfileData sd; + sd.data = std::move(subData); + s_Subfiles[i] = std::move(sd); + } + } + } + + if (callback) callback(false, true); + return (C4JStorage::ESaveGameState)1; } C4JStorage::ESaveGameState C4JStorage::DeleteSaveData( - PSAVE_INFO pSaveInfo, - std::function callback) { - return ESaveGame_Idle; + PSAVE_INFO pSaveInfo, std::function callback) { + bool success = false; + if (pSaveInfo) { + std::error_code ec; + std::filesystem::remove_all(GetSaveDir(pSaveInfo->UTF8SaveFilename), + ec); + success = !ec; + } + if (callback) callback(success); + return (C4JStorage::ESaveGameState)3; } void C4JStorage::RegisterMarketplaceCountsCallback( std::function callback) {} @@ -124,6 +351,7 @@ void C4JStorage::SetDLCPackageRoot(char* pszDLCRoot) {} C4JStorage::EDLCStatus C4JStorage::GetDLCOffers( int iPad, std::function callback, std::uint32_t dwOfferTypesBitmask) { + if (callback) callback(0, dwOfferTypesBitmask, iPad); return EDLC_NoOffers; } unsigned int C4JStorage::CancelGetDLCOffers() { return 0; } @@ -190,32 +418,55 @@ unsigned int C4JStorage::CRC(unsigned char* buf, int len) { unsigned int crc = 0xFFFFFFFF; for (int i = 0; i < len; i++) { crc ^= buf[i]; - for (int j = 0; j < 8; j++) { + for (int j = 0; j < 8; j++) crc = (crc >> 1) ^ (0xEDB88320 & (-(crc & 1))); - } } return ~crc; } +// that fucking bird that i hate int C4JStorage::AddSubfile(int regionIndex) { - (void)regionIndex; - return 0; + s_Subfiles[s_SubfileCounter] = SubfileData(); + return s_SubfileCounter++; } -unsigned int C4JStorage::GetSubfileCount() { return 0; } + +unsigned int C4JStorage::GetSubfileCount() { return s_Subfiles.size(); } + void C4JStorage::GetSubfileDetails(unsigned int i, int* regionIndex, void** data, unsigned int* size) { - (void)i; - if (regionIndex) *regionIndex = 0; - if (data) *data = 0; - if (size) *size = 0; + auto it = s_Subfiles.find(i); + if (it != s_Subfiles.end()) { + if (regionIndex) *regionIndex = 0; + if (data) *data = it->second.data.data(); + if (size) *size = (unsigned int)it->second.data.size(); + } else { + if (regionIndex) *regionIndex = 0; + if (data) *data = nullptr; + if (size) *size = 0; + } +} + +void C4JStorage::ResetSubfiles() { + s_Subfiles.clear(); + s_SubfileCounter = 0; } -void C4JStorage::ResetSubfiles() {} void C4JStorage::UpdateSubfile(int index, void* data, unsigned int size) { - (void)index; - (void)data; - (void)size; + SubfileData& sd = s_Subfiles[index]; // inserts if missing + sd.data.resize(size); + memcpy(sd.data.data(), data, size); } void C4JStorage::SaveSubfiles(std::function callback) { + if (!s_CurrentSaveFilename.empty() && !s_Subfiles.empty()) { + auto dir = GetSaveDir(s_CurrentSaveFilename); + std::error_code ec; + std::filesystem::create_directories(dir, ec); + + for (const auto& pair : s_Subfiles) { + s_FileIO.writeFile( + dir / ("subfile_" + std::to_string(pair.first) + ".dat"), + pair.second.data.data(), pair.second.data.size()); + } + } if (callback) callback(true); } C4JStorage::ESaveGameState C4JStorage::GetSaveState() { return ESaveGame_Idle; }