2 Commits

Author SHA1 Message Date
JuiceyDev 6215e1365f fix/saving: Final storage fix. 2026-04-05 02:44:30 +02:00
JuiceyDev d30e18ef4d fix/saving: Storage implementation, gui fix and file.cpp refactor 2026-04-05 02:10:20 +02:00
7 changed files with 432 additions and 90 deletions
@@ -547,13 +547,43 @@ void UIScene_LoadOrJoinMenu::tick() {
m_iState = e_SavesIdle; m_iState = e_SavesIdle;
break; break;
} }
#else // 4jcraft: the game literally defaulted to nothing but default buttons
if (!m_bSavesDisplayed) { // if the game was on a non console / recognized platform.
AddDefaultButtons(); #else
m_bSavesDisplayed = true; if (!m_bSavesDisplayed) {
m_controlSavesTimer.setVisible(false); m_pSaveDetails = StorageManager.ReturnSavesInfo();
} if (m_pSaveDetails != nullptr) {
#endif 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 // SAVE TRANSFERS
} }
+16 -7
View File
@@ -8,9 +8,14 @@
#include <system_error> #include <system_error>
#include <vector> #include <vector>
#if defined(__linux__)
#include <unistd.h>
#endif
#include "util/StringHelpers.h" // 4jcraft TODO #include "util/StringHelpers.h" // 4jcraft TODO
#include "platform/PlatformServices.h" #include "platform/PlatformServices.h"
#include "java/FileFilter.h" #include "java/FileFilter.h"
#include "platform/StdFileIO.h"
const wchar_t File::pathSeparator = L'/'; const wchar_t File::pathSeparator = L'/';
@@ -64,11 +69,11 @@ File::File(const std::wstring& pathname) {
m_abstractPathName = fixedPath; m_abstractPathName = fixedPath;
#if defined(__linux__) #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); while (!request.empty() && request[0] == '/') request.erase(0, 1);
if (request.find("res/") == 0) request.erase(0, 4); 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; std::string fileName = request;
size_t lastSlash = fileName.find_last_of('/'); size_t lastSlash = fileName.find_last_of('/');
if (lastSlash != std::string::npos) if (lastSlash != std::string::npos)
@@ -78,21 +83,24 @@ File::File(const std::wstring& pathname) {
"/Common/res/TitleUpdate/res/", "/Common/res/TitleUpdate/res/",
"/Common/Media/", "/Common/Media/",
"/Common/res/", "/Common/res/",
"/Common/", "/Common/"};
"resources/"};
for (const char* base : bases) { for (const char* base : bases) {
std::string tryFull = exeDir + base + request; std::string tryFull = exeDir + base + request;
std::string tryFile = exeDir + base + fileName; std::string tryFile = exeDir + base + fileName;
if (PlatformFileIO.exists(tryFull)) { if (access(tryFull.c_str(), F_OK) != -1) {
m_abstractPathName = convStringToWstring(tryFull); m_abstractPathName = convStringToWstring(tryFull);
return; return;
} }
if (PlatformFileIO.exists(tryFile)) { if (access(tryFile.c_str(), F_OK) != -1) {
m_abstractPathName = convStringToWstring(tryFile); m_abstractPathName = convStringToWstring(tryFile);
return; 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 #endif
#ifdef _WINDOWS64 #ifdef _WINDOWS64
@@ -155,7 +163,8 @@ this->parent = nullptr;
// deleted; false otherwise // deleted; false otherwise
bool File::_delete() { bool File::_delete() {
std::error_code error; 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) { if (!result || error) {
#ifndef _CONTENT_PACKAGE #ifndef _CONTENT_PACKAGE
printf("File::_delete - Error code %d (%#0.8X)\n", error.value(), printf("File::_delete - Error code %d (%#0.8X)\n", error.value(),
@@ -99,7 +99,7 @@ void PendingConnection::sendPreLoginResponse() {
std::uint8_t ugcXuidCount = 0; std::uint8_t ugcXuidCount = 0;
std::uint8_t hostIndex = 0; std::uint8_t hostIndex = 0;
std::uint8_t ugcFriendsOnlyBits = 0; std::uint8_t ugcFriendsOnlyBits = 0;
char szUniqueMapName[14]; char szUniqueMapName[256]{};
StorageManager.GetSaveUniqueFilename(szUniqueMapName); StorageManager.GetSaveUniqueFilename(szUniqueMapName);
@@ -216,11 +216,13 @@ void ConsoleSaveFileSplit::RegionFileReference::Decompress() {
unsigned char* dataIn = dataCompressed + 4; unsigned char* dataIn = dataCompressed + 4;
unsigned char* dataInLast = dataCompressed + dataCompressedSize; unsigned char* dataInLast = dataCompressed + dataCompressedSize;
while (dataIn != dataInLast) { while (dataIn < dataInLast) {
unsigned char thisByte = *dataIn++; unsigned char thisByte = *dataIn++;
if (thisByte == 0) { if (thisByte == 0) {
if (dataIn >= dataInLast) break;
thisByte = *dataIn++; thisByte = *dataIn++;
if (thisByte == 0) { if (thisByte == 0) {
if (dataIn + 1 >= dataInLast) break;
unsigned int runLength = (*dataIn++) << 8; unsigned int runLength = (*dataIn++) << 8;
runLength |= (*dataIn++); runLength |= (*dataIn++);
runLength += 256; runLength += 256;
@@ -237,7 +239,6 @@ void ConsoleSaveFileSplit::RegionFileReference::Decompress() {
if (fileEntry->data.length != uncompressedSize) { if (fileEntry->data.length != uncompressedSize) {
// Treat as if it was an empty region file // Treat as if it was an empty region file
fileEntry->data.length = 0; fileEntry->data.length = 0;
assert(0);
return; return;
} }
} }
@@ -246,21 +247,24 @@ void ConsoleSaveFileSplit::RegionFileReference::Decompress() {
unsigned char* dataIn = dataCompressed + 4; unsigned char* dataIn = dataCompressed + 4;
unsigned char* dataInLast = dataCompressed + dataCompressedSize; unsigned char* dataInLast = dataCompressed + dataCompressedSize;
unsigned char* dataOut = data; unsigned char* dataOut = data;
unsigned char* dataOutLast = data + fileEntry->data.length;
while (dataIn != dataInLast) { while (dataIn < dataInLast && dataOut < dataOutLast) {
unsigned char thisByte = *dataIn++; unsigned char thisByte = *dataIn++;
if (thisByte == 0) { if (thisByte == 0) {
if (dataIn >= dataInLast) break;
thisByte = *dataIn++; thisByte = *dataIn++;
if (thisByte == 0) { if (thisByte == 0) {
if (dataIn + 1 >= dataInLast) break;
unsigned int runLength = (*dataIn++) << 8; unsigned int runLength = (*dataIn++) << 8;
runLength |= (*dataIn++); runLength |= (*dataIn++);
runLength += 256; runLength += 256;
for (unsigned int i = 0; i < runLength; i++) { for (unsigned int i = 0; i < runLength && dataOut < dataOutLast; i++) {
*dataOut++ = 0; *dataOut++ = 0;
} }
} else { } else {
unsigned int runLength = thisByte; unsigned int runLength = thisByte;
for (unsigned int i = 0; i < runLength; i++) { for (unsigned int i = 0; i < runLength && dataOut < dataOutLast; i++) {
*dataOut++ = 0; *dataOut++ = 0;
} }
} }
@@ -274,7 +278,6 @@ void ConsoleSaveFileSplit::RegionFileReference::Decompress() {
free(data); free(data);
fileEntry->data.length = 0; fileEntry->data.length = 0;
data = nullptr; data = nullptr;
assert(0);
} }
// std::int64_t endTime = System::currentTimeMillis(); // std::int64_t endTime = System::currentTimeMillis();
// app.DebugPrintf("Decompressing region file from 0x%.8x %d to %d bytes - // app.DebugPrintf("Decompressing region file from 0x%.8x %d to %d bytes -
@@ -437,6 +440,7 @@ void ConsoleSaveFileSplit::_init(const std::wstring& fileName, void* pvSaveData,
// Get details of region files. From this point on we are responsible for // Get details of region files. From this point on we are responsible for
// the memory that the storage manager initially allocated for them // the memory that the storage manager initially allocated for them
unsigned int regionCount = PlatformStorage.GetSubfileCount(); unsigned int regionCount = PlatformStorage.GetSubfileCount();
for (unsigned int i = 0; i < regionCount; i++) { for (unsigned int i = 0; i < regionCount; i++) {
unsigned int regionIndex; unsigned int regionIndex;
unsigned char* regionDataCompressed; unsigned char* regionDataCompressed;
@@ -446,8 +450,14 @@ void ConsoleSaveFileSplit::_init(const std::wstring& fileName, void* pvSaveData,
(void**)&regionDataCompressed, (void**)&regionDataCompressed,
&regionSizeCompressed); &regionSizeCompressed);
unsigned char* copiedData = nullptr;
if (regionSizeCompressed > 0 && regionDataCompressed) {
copiedData = (unsigned char*)malloc(regionSizeCompressed);
memcpy(copiedData, regionDataCompressed, regionSizeCompressed);
}
RegionFileReference* regionFileRef = new RegionFileReference( RegionFileReference* regionFileRef = new RegionFileReference(
i, regionIndex, regionSizeCompressed, regionDataCompressed); regionIndex, regionIndex, regionSizeCompressed, copiedData);
if (regionSizeCompressed > 0) { if (regionSizeCompressed > 0) {
regionFileRef->Decompress(); regionFileRef->Decompress();
} else { } else {
@@ -493,10 +503,12 @@ void ConsoleSaveFileSplit::_init(const std::wstring& fileName, void* pvSaveData,
PlatformStorage.GetSaveData(pvSaveMem, &storageLength); PlatformStorage.GetSaveData(pvSaveMem, &storageLength);
app.DebugPrintf("Filesize - %d, Adjusted size - %d\n", fileSize, app.DebugPrintf("Filesize - %d, Adjusted size - %d\n", fileSize,
storageLength); storageLength);
fileSize = storageLength; fileSize = storageLength;
} }
int compressed = *(int*)pvSaveMem; int compressed = *(int*)pvSaveMem;
if (compressed == 0) { if (compressed == 0) {
unsigned int decompSize = *((int*)pvSaveMem + 1); unsigned int decompSize = *((int*)pvSaveMem + 1);
@@ -1288,6 +1300,7 @@ void ConsoleSaveFileSplit::Flush(bool autosave, bool updateThumbnail) {
unsigned int fileSize = header.GetFileSize(); unsigned int fileSize = header.GetFileSize();
// Assume that the compression will make it smaller so initially attempt to // Assume that the compression will make it smaller so initially attempt to
// allocate the current file size We add 4 bytes to the start so that we can // allocate the current file size We add 4 bytes to the start so that we can
// signal compressed data And another 4 bytes to store the decompressed data // signal compressed data And another 4 bytes to store the decompressed data
@@ -203,6 +203,7 @@ void FileHeader::ReadHeader(
// based PlayerUID // based PlayerUID
// : Bumped it to 8 for Durango v1 when to save the chunks in a // : Bumped it to 8 for Durango v1 when to save the chunks in a
// different compressed format // different compressed format
case SAVE_FILE_VERSION_CHUNK_INHABITED_TIME:
case SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE: case SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE:
case SAVE_FILE_VERSION_DURANGO_CHANGE_MAP_DATA_MAPPING_SIZE: case SAVE_FILE_VERSION_DURANGO_CHANGE_MAP_DATA_MAPPING_SIZE:
case SAVE_FILE_VERSION_CHANGE_MAP_DATA_MAPPING_SIZE: case SAVE_FILE_VERSION_CHANGE_MAP_DATA_MAPPING_SIZE:
+25 -23
View File
@@ -1,16 +1,17 @@
platform_inc = include_directories('.') platform_inc = include_directories('.')
_threads = dependency('threads') _threads = dependency('threads')
lib_platform_core = static_library('platform_core', lib_platform_core = static_library(
files('C4JThread.cpp', 'ShutdownManager.cpp'), 'platform_core',
include_directories: [platform_inc, include_directories('..')], files('C4JThread.cpp', 'ShutdownManager.cpp'),
dependencies: [_threads], include_directories: [platform_inc, include_directories('..')],
cpp_args: global_cpp_args + global_cpp_defs, dependencies: [_threads],
cpp_args: global_cpp_args + global_cpp_defs,
) )
platform_dep = declare_dependency( platform_dep = declare_dependency(
link_with: lib_platform_core, link_with: lib_platform_core,
include_directories: platform_inc, include_directories: platform_inc,
) )
# SDL2-based platform implementations (formerly 4J.* modules) # SDL2-based platform implementations (formerly 4J.* modules)
@@ -18,33 +19,34 @@ _sdl2 = dependency('sdl2')
_defs = [] _defs = []
if get_option('renderer') == 'gles' if get_option('renderer') == 'gles'
_gl = dependency('glesv2', required: true) _gl = dependency('glesv2', required: true)
_defs += ['-DGLES'] _defs += ['-DGLES']
else else
_gl = dependency('gl', required: true) _gl = dependency('gl', required: true)
endif endif
sdl2_sources = files( sdl2_sources = files(
'sdl2/Input.cpp', 'sdl2/Input.cpp',
'sdl2/Profile.cpp', 'sdl2/Profile.cpp',
'sdl2/Render.cpp', 'sdl2/Render.cpp',
'sdl2/render_stubs.cpp', 'sdl2/Storage.cpp',
'sdl2/Storage.cpp', 'sdl2/render_stubs.cpp',
) )
lib_platform_sdl2 = static_library('platform_sdl2', lib_platform_sdl2 = static_library(
sdl2_sources, 'platform_sdl2',
include_directories: [platform_inc, include_directories('sdl2')], sdl2_sources,
dependencies: [_sdl2, _gl, _threads, glm_dep, stb_dep], include_directories: [platform_inc, include_directories('sdl2')],
cpp_args: _defs + global_cpp_args + global_cpp_defs, dependencies: [_sdl2, _gl, _threads, glm_dep, stb_dep, java_dep],
cpp_args: _defs + global_cpp_args + global_cpp_defs,
) )
# Combined dependency: interfaces + SDL2 implementations # Combined dependency: interfaces + SDL2 implementations
# Replaces the old render_dep, input_dep, profile_dep, storage_dep # Replaces the old render_dep, input_dep, profile_dep, storage_dep
render_dep = declare_dependency( render_dep = declare_dependency(
link_with: lib_platform_sdl2, link_with: lib_platform_sdl2,
include_directories: [platform_inc, include_directories('sdl2')], include_directories: [platform_inc, include_directories('sdl2')],
dependencies: [_sdl2, _gl, _threads, glm_dep], dependencies: [_sdl2, _gl, _threads, glm_dep],
) )
input_dep = render_dep input_dep = render_dep
profile_dep = render_dep profile_dep = render_dep
+329 -42
View File
@@ -1,16 +1,59 @@
#include "Storage.h" #include "Storage.h"
#include <stdlib.h> #include <stdlib.h>
#include <time.h>
#include <chrono>
#include <cstring> #include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
#include "StdFileIO.h"
C4JStorage StorageManager; C4JStorage StorageManager;
static XMARKETPLACE_CONTENTOFFER_INFO s_dummyOffer = {}; static XMARKETPLACE_CONTENTOFFER_INFO s_dummyOffer = {};
static XCONTENT_DATA s_dummyContentData = {}; 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<std::uint8_t> data;
};
#include <mutex>
static std::map<int, SubfileData> s_Subfiles;
static int s_SubfileCounter = 0;
static std::mutex s_SubfileMutex;
// 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) {} C4JStorage::C4JStorage() : m_pStringTable(nullptr) {}
void C4JStorage::Tick(void) {} void C4JStorage::Tick(void) {}
@@ -21,6 +64,7 @@ C4JStorage::EMessageResult C4JStorage::RequestMessageBox(
std::function<int(int, const C4JStorage::EMessageResult)> callback, std::function<int(int, const C4JStorage::EMessageResult)> callback,
C4JStringTable* pStringTable, wchar_t* pwchFormatString, C4JStringTable* pStringTable, wchar_t* pwchFormatString,
unsigned int focusButton) { unsigned int focusButton) {
if (callback) callback(pad, EMessage_ResultAccept);
return EMessage_ResultAccept; return EMessage_ResultAccept;
} }
@@ -30,6 +74,7 @@ C4JStorage::EMessageResult C4JStorage::GetMessageBoxResult() {
bool C4JStorage::SetSaveDevice(std::function<int(const bool)> callback, bool C4JStorage::SetSaveDevice(std::function<int(const bool)> callback,
bool bForceResetOfSaveDevice) { bool bForceResetOfSaveDevice) {
if (callback) callback(true);
return true; return true;
} }
@@ -37,48 +82,130 @@ void C4JStorage::Init(unsigned int uiSaveVersion,
const wchar_t* pwchDefaultSaveName, char* pszSavePackName, const wchar_t* pwchDefaultSaveName, char* pszSavePackName,
int iMinimumSaveSize, int iMinimumSaveSize,
std::function<int(const ESavingMessage, int)> callback, std::function<int(const ESavingMessage, int)> callback,
const char* szGroupID) {} const char* szGroupID) {
void C4JStorage::ResetSaveData() {} std::error_code ec;
std::filesystem::create_directories(GetSavesRoot(), ec);
}
void C4JStorage::ResetSaveData() {
s_CurrentSaveTitle = L"New World";
s_CurrentSaveFilename = "";
{
std::lock_guard<std::mutex> lock(s_SubfileMutex);
s_Subfiles.clear();
s_SubfileCounter = 0;
}
if (s_SaveBuffer) {
free(s_SaveBuffer);
s_SaveBuffer = nullptr;
}
s_SaveBufferSize = 0;
}
void C4JStorage::SetDefaultSaveNameForKeyboardDisplay( void C4JStorage::SetDefaultSaveNameForKeyboardDisplay(
const wchar_t* pwchDefaultSaveName) {} const wchar_t* pwchDefaultSaveName) {
void C4JStorage::SetSaveTitle(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) { 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; return true;
} }
bool C4JStorage::GetSaveUniqueFilename(char* pszName) { 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; return true;
} }
void C4JStorage::SetSaveUniqueFilename(char* szFilename) {} void C4JStorage::SetSaveUniqueFilename(char* szFilename) {
if (szFilename) s_CurrentSaveFilename = szFilename;
}
void C4JStorage::SetState(ESaveGameControlState eControlState, void C4JStorage::SetState(ESaveGameControlState eControlState,
std::function<int(const bool)> callback) {} std::function<int(const bool)> callback) {
void C4JStorage::SetSaveDisabled(bool bDisable) {} if (callback) callback(true);
bool C4JStorage::GetSaveDisabled(void) { return false; }
unsigned int C4JStorage::GetSaveSize() { return 0; }
void C4JStorage::GetSaveData(void* pvData, unsigned int* puiBytes) {
if (puiBytes) *puiBytes = 0;
} }
void C4JStorage::SetSaveDisabled(bool bDisable) { s_SaveDisabled = bDisable; }
bool C4JStorage::GetSaveDisabled(void) { return s_SaveDisabled; }
void* C4JStorage::AllocateSaveData(unsigned int uiBytes) { 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, void C4JStorage::SetSaveImages(std::uint8_t* pbThumbnail,
unsigned int thumbnailBytes, unsigned int thumbnailBytes,
std::uint8_t* pbImage, unsigned int imageBytes, std::uint8_t* pbImage, unsigned int imageBytes,
std::uint8_t* pbTextData, 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( C4JStorage::ESaveGameState C4JStorage::SaveSaveData(
std::function<int(const bool)> callback) { std::function<int(const bool)> 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, void C4JStorage::CopySaveDataToNewSave(std::uint8_t* pbThumbnail,
unsigned int cbThumbnail, unsigned int cbThumbnail,
wchar_t* wchNewName, wchar_t* wchNewName,
std::function<int(bool)> callback) {} std::function<int(bool)> callback) {
if (callback) callback(false);
}
void C4JStorage::SetSaveDeviceSelected(unsigned int uiPad, bool bSelected) {} void C4JStorage::SetSaveDeviceSelected(unsigned int uiPad, bool bSelected) {}
bool C4JStorage::GetSaveDeviceSelected(unsigned int iPad) { return true; } bool C4JStorage::GetSaveDeviceSelected(unsigned int iPad) { return true; }
C4JStorage::ESaveGameState C4JStorage::DoesSaveExist(bool* pbExists) { C4JStorage::ESaveGameState C4JStorage::DoesSaveExist(bool* pbExists) {
if (pbExists) *pbExists = false; if (pbExists)
*pbExists = s_FileIO.exists(GetSaveDir(s_CurrentSaveFilename));
return ESaveGame_Idle; return ESaveGame_Idle;
} }
bool C4JStorage::EnoughSpaceForAMinSaveGame() { return true; } bool C4JStorage::EnoughSpaceForAMinSaveGame() { return true; }
@@ -87,16 +214,87 @@ C4JStorage::ESaveGameState C4JStorage::GetSavesInfo(
int iPad, int iPad,
std::function<int(SAVE_DETAILS* pSaveDetails, const bool)> callback, std::function<int(SAVE_DETAILS* pSaveDetails, const bool)> callback,
char* pszSavePackName) { char* pszSavePackName) {
ClearSavesInfo();
auto savesRoot = GetSavesRoot();
std::error_code ec;
std::filesystem::create_directories(savesRoot, ec);
std::vector<std::string> 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; 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( C4JStorage::ESaveGameState C4JStorage::LoadSaveDataThumbnail(
PSAVE_INFO pSaveInfo, PSAVE_INFO pSaveInfo,
std::function<int(std::uint8_t* thumbnailData, std::function<int(std::uint8_t* thumbnailData, unsigned int thumbnailBytes)>
unsigned int thumbnailBytes)>
callback) { 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, void C4JStorage::GetSaveCacheFileInfo(unsigned int fileIndex,
XCONTENT_DATA& xContentData) { XCONTENT_DATA& xContentData) {
@@ -109,14 +307,63 @@ void C4JStorage::GetSaveCacheFileInfo(unsigned int fileIndex,
if (pImageBytes) *pImageBytes = 0; if (pImageBytes) *pImageBytes = 0;
} }
C4JStorage::ESaveGameState C4JStorage::LoadSaveData( C4JStorage::ESaveGameState C4JStorage::LoadSaveData(
PSAVE_INFO pSaveInfo, PSAVE_INFO pSaveInfo, std::function<int(const bool, const bool)> callback) {
std::function<int(const bool, const bool)> callback) { if (pSaveInfo) {
return ESaveGame_Idle; 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
{
std::lock_guard<std::mutex> lock(s_SubfileMutex);
s_Subfiles.clear();
int maxIndex = -1;
auto dir = GetSaveDir(s_CurrentSaveFilename);
std::error_code ec;
for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) {
if (!entry.is_regular_file()) continue;
std::string filename = entry.path().filename().string();
if (filename.find("subfile_") == 0 && filename.find(".dat") == filename.size() - 4) {
try {
int i = std::stoi(filename.substr(8, filename.size() - 12));
auto subData = s_FileIO.readFileToVec(entry.path());
if (!subData.empty()) {
SubfileData sd;
sd.data = std::move(subData);
s_Subfiles[i] = std::move(sd);
if (i > maxIndex) maxIndex = i;
}
} catch (...) {}
}
}
s_SubfileCounter = maxIndex + 1;
}
}
if (callback) callback(false, true);
return (C4JStorage::ESaveGameState)1;
} }
C4JStorage::ESaveGameState C4JStorage::DeleteSaveData( C4JStorage::ESaveGameState C4JStorage::DeleteSaveData(
PSAVE_INFO pSaveInfo, PSAVE_INFO pSaveInfo, std::function<int(const bool)> callback) {
std::function<int(const bool)> callback) { bool success = false;
return ESaveGame_Idle; 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( void C4JStorage::RegisterMarketplaceCountsCallback(
std::function<int(C4JStorage::DLC_TMS_DETAILS*, int)> callback) {} std::function<int(C4JStorage::DLC_TMS_DETAILS*, int)> callback) {}
@@ -124,6 +371,7 @@ void C4JStorage::SetDLCPackageRoot(char* pszDLCRoot) {}
C4JStorage::EDLCStatus C4JStorage::GetDLCOffers( C4JStorage::EDLCStatus C4JStorage::GetDLCOffers(
int iPad, std::function<int(int, std::uint32_t, int)> callback, int iPad, std::function<int(int, std::uint32_t, int)> callback,
std::uint32_t dwOfferTypesBitmask) { std::uint32_t dwOfferTypesBitmask) {
if (callback) callback(0, dwOfferTypesBitmask, iPad);
return EDLC_NoOffers; return EDLC_NoOffers;
} }
unsigned int C4JStorage::CancelGetDLCOffers() { return 0; } unsigned int C4JStorage::CancelGetDLCOffers() { return 0; }
@@ -190,32 +438,71 @@ unsigned int C4JStorage::CRC(unsigned char* buf, int len) {
unsigned int crc = 0xFFFFFFFF; unsigned int crc = 0xFFFFFFFF;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
crc ^= buf[i]; crc ^= buf[i];
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++)
crc = (crc >> 1) ^ (0xEDB88320 & (-(crc & 1))); crc = (crc >> 1) ^ (0xEDB88320 & (-(crc & 1)));
}
} }
return ~crc; return ~crc;
} }
// that fucking bird that i hate
int C4JStorage::AddSubfile(int regionIndex) { int C4JStorage::AddSubfile(int regionIndex) {
(void)regionIndex; std::lock_guard<std::mutex> lock(s_SubfileMutex);
return 0; s_Subfiles[regionIndex] = SubfileData();
if (regionIndex >= s_SubfileCounter) {
s_SubfileCounter = regionIndex + 1;
}
return regionIndex;
} }
unsigned int C4JStorage::GetSubfileCount() { return 0; }
unsigned int C4JStorage::GetSubfileCount() {
std::lock_guard<std::mutex> lock(s_SubfileMutex);
return s_Subfiles.size();
}
void C4JStorage::GetSubfileDetails(unsigned int i, int* regionIndex, void C4JStorage::GetSubfileDetails(unsigned int i, int* regionIndex,
void** data, unsigned int* size) { void** data, unsigned int* size) {
(void)i; std::lock_guard<std::mutex> lock(s_SubfileMutex);
if (regionIndex) *regionIndex = 0; auto it = s_Subfiles.begin();
if (data) *data = 0; unsigned int index = 0;
if (size) *size = 0; while (it != s_Subfiles.end() && index < i) {
++it;
++index;
}
if (it != s_Subfiles.end()) {
if (regionIndex) *regionIndex = it->first;
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() {
std::lock_guard<std::mutex> lock(s_SubfileMutex);
s_Subfiles.clear();
s_SubfileCounter = 0;
} }
void C4JStorage::ResetSubfiles() {}
void C4JStorage::UpdateSubfile(int index, void* data, unsigned int size) { void C4JStorage::UpdateSubfile(int index, void* data, unsigned int size) {
(void)index; std::lock_guard<std::mutex> lock(s_SubfileMutex);
(void)data; SubfileData& sd = s_Subfiles[index]; // inserts if missing
(void)size; sd.data.resize(size);
memcpy(sd.data.data(), data, size);
} }
void C4JStorage::SaveSubfiles(std::function<int(const bool)> callback) { void C4JStorage::SaveSubfiles(std::function<int(const bool)> callback) {
std::lock_guard<std::mutex> lock(s_SubfileMutex);
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); if (callback) callback(true);
} }
C4JStorage::ESaveGameState C4JStorage::GetSaveState() { return ESaveGame_Idle; } C4JStorage::ESaveGameState C4JStorage::GetSaveState() { return ESaveGame_Idle; }