feat(docs): add an implementation guide for more in-depth details on how to use 4JLibs expansions

feat(storage): save titles are now encoded into the name of the save files
feat(storage): added functionality to delete save files
chore(storage): document functions which don't have to be implemented, as they're unused for the Windows platform
chore(storage): rename some variables to be more accurate with the binaries
fix(render): in vs2012, m_backBufferTexture cannot be initialized in a header
fix(storage): in CDLC::GetMountedPath, m_szMountPath and m_szDirectoryPath were mixed up
fix(storage): in CSaveGame::SetSaveTitle, there was a wide string to narrow string mismatch
This commit is contained in:
Patoke
2026-03-08 04:11:22 -03:00
parent 53bc372890
commit 3ead2bb093
7 changed files with 234 additions and 29 deletions
+12
View File
@@ -49,6 +49,7 @@ C4JStorage::EMessageResult C4JStorage::GetMessageBoxResult()
bool C4JStorage::SetSaveDevice(int (*Func)(LPVOID, const bool), LPVOID lpParam, bool bForceResetOfSaveDevice)
{
// @Patoke: majorly used in XUI
return true;
}
@@ -65,6 +66,7 @@ void C4JStorage::ResetSaveData()
void C4JStorage::SetDefaultSaveNameForKeyboardDisplay(LPCWSTR pwchDefaultSaveName)
{
// @Patoke: unused for all platforms
;
}
@@ -90,6 +92,7 @@ void C4JStorage::SetSaveUniqueFilename(char *szFilename)
void C4JStorage::SetState(ESaveGameControlState eControlState, int (*Func)(LPVOID, const bool), LPVOID lpParam)
{
// @Patoke: only used in the xbox 360 platform
;
}
@@ -130,32 +133,38 @@ C4JStorage::ESaveGameState C4JStorage::SaveSaveData(int (*Func)(LPVOID, const bo
void C4JStorage::CopySaveDataToNewSave(PBYTE pbThumbnail, DWORD cbThumbnail, WCHAR *wchNewName, int (*Func)(LPVOID lpParam, bool), LPVOID lpParam)
{
// @Patoke: unused for other platforms that aren't the xbox 360
;
}
void C4JStorage::SetSaveDeviceSelected(unsigned int uiPad, bool bSelected)
{
// @Patoke: majorly used in XUI
;
}
bool C4JStorage::GetSaveDeviceSelected(unsigned int iPad)
{
// @Patoke: majorly used in XUI
return true;
}
C4JStorage::ESaveGameState C4JStorage::DoesSaveExist(bool *pbExists)
{
// @Patoke: implementation is stubbed out in other platforms too
*pbExists = true;
return ESaveGame_Idle;
}
bool C4JStorage::EnoughSpaceForAMinSaveGame()
{
// @Patoke: implementation is stubbed out in other platforms too
return true;
}
void C4JStorage::SetSaveMessageVPosition(float fY)
{
// @Patoke: completely unused
;
}
@@ -183,11 +192,13 @@ C4JStorage::ESaveGameState C4JStorage::LoadSaveDataThumbnail(PSAVE_INFO pSaveInf
void C4JStorage::GetSaveCacheFileInfo(DWORD dwFile, XCONTENT_DATA &xContentData)
{
// @Patoke: xbox 360 leftover
;
}
void C4JStorage::GetSaveCacheFileInfo(DWORD dwFile, PBYTE *ppbImageData, DWORD *pdwImageBytes)
{
// @Patoke: xbox 360 leftover
;
}
@@ -203,6 +214,7 @@ C4JStorage::ESaveGameState C4JStorage::DeleteSaveData(PSAVE_INFO pSaveInfo, int
void C4JStorage::RegisterMarketplaceCountsCallback(int (*Func)(LPVOID lpParam, C4JStorage::DLC_TMS_DETAILS *, int), LPVOID lpParam)
{
// @Patoke: only used in the xbox 360 platform
;
}
+4 -4
View File
@@ -208,11 +208,11 @@ std::string CDLC::GetMountedPath(std::string szMount)
if (szMount[ch] == ':')
{
std::string driveName = szMount.substr(0, ch);
for (int i = 0; i < m_vDLCDriveMappings.size(); ++i)
for (int i = 0; i < m_vDLCDriveMappings.size(); i++)
{
if (m_vDLCDriveMappings[i].m_szDirectoryPath == driveName)
if (m_vDLCDriveMappings[i].m_szMountPath == driveName)
{
std::string newPath = m_vDLCDriveMappings[i].m_szMountPath;
std::string newPath = m_vDLCDriveMappings[i].m_szDirectoryPath;
newPath.append(szMount.substr(ch + 1, -1));
@@ -256,7 +256,7 @@ void CDLC::Tick(void)
if (m_iHasNewMountedDLCs)
{
m_iHasNewMountedDLCs = false;
m_pMountedDLCFunc(m_pMountedDLCParam, 0, 0, dword94);
m_pMountedDLCFunc(m_pMountedDLCParam, 0, 0, m_dwLicenseMask);
}
}
+2 -2
View File
@@ -35,8 +35,8 @@ public:
;
}
std::string m_szDirectoryPath;
std::string m_szMountPath;
std::string m_szDirectoryPath;
};
XCONTENT_DATA &GetDLC(DWORD dw);
@@ -72,7 +72,7 @@ public:
LPVOID m_pMountedDLCParam;
std::string m_szMountPath;
DWORD m_uiCurrentMappedDLC;
DWORD dword94;
DWORD m_dwLicenseMask;
char m_szPackageRoot[40];
DWORD dwordC0;
std::vector<DriveMapping> m_vDLCDriveMappings;
+80 -11
View File
@@ -115,7 +115,7 @@ C4JStorage::ESaveGameState CSaveGame::GetSavesInfo(int iPad, int (*Func)(LPVOID
if (resultCount > 0)
{
m_pSaveDetails->SaveInfoA = new SAVE_INFO[resultCount];
memset(m_pSaveDetails->SaveInfoA, 0, 184LL * resultCount);
memset(m_pSaveDetails->SaveInfoA, 0, sizeof(SAVE_INFO) * resultCount);
m_pSaveDetails->iSaveC = 0;
int i = 0;
@@ -128,16 +128,45 @@ C4JStorage::ESaveGameState CSaveGame::GetSavesInfo(int iPad, int (*Func)(LPVOID
strcmp(findFileData.cFileName, ".."))
{
strcpy_s(m_pSaveDetails->SaveInfoA[i].UTF8SaveFilename, findFileData.cFileName);
strcpy_s(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, findFileData.cFileName);
// @Patoke add: we want to preserve the title name, so we save this in the actual save file name
char searchPath[280];
sprintf(searchPath, "%s\\Windows64\\GameHDD\\%s\\*", dirName, findFileData.cFileName);
WIN32_FIND_DATAA saveFileData;
HANDLE hSaveFile = FindFirstFileA(searchPath, &saveFileData);
char szTitleName[256] = {0};
if (hSaveFile != INVALID_HANDLE_VALUE)
{
do
{
// @Patoke todo: we assume the first actual file here to be the save file, ideally we would want to check the extension
// too but this is good enough for now
if (!(saveFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
strcpy_s(szTitleName, sizeof(szTitleName), saveFileData.cFileName);
strcpy_s(this->m_szSaveTitle, saveFileData.cFileName); // populate the save title
break;
}
} while (FindNextFileA(hSaveFile, &saveFileData));
FindClose(hSaveFile);
}
// set the actual save file name as the title now, before we would use the folder name which is the unique save name
strcpy_s(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, szTitleName);
char fileName[280];
sprintf(fileName, "%s\\Windows64\\GameHDD\\%s\\saveData.ms", dirName, findFileData.cFileName);
sprintf(fileName, "%s\\Windows64\\GameHDD\\%s\\%s", dirName, findFileData.cFileName, szTitleName);
GetFileAttributesExA(fileName, GetFileExInfoStandard, &fileInfoBuffer);
m_pSaveDetails->SaveInfoA[i].metaData.dataSize = fileInfoBuffer.nFileSizeLow;
// @Patoke todo: a save can have multiple thumbnails, implement this behaviour
char thumbName[280];
sprintf(thumbName, "%s\\Windows64\\GameHDD\\%s\\thumbData.png", dirName, findFileData.cFileName);
sprintf(thumbName, "%s\\Windows64\\GameHDD\\%s\\thumbnails\\thumbData.png", dirName, findFileData.cFileName);
GetFileAttributesExA(thumbName, GetFileExInfoStandard, &fileInfoBuffer);
m_pSaveDetails->SaveInfoA[i++].metaData.thumbnailSize = fileInfoBuffer.nFileSizeLow;
@@ -200,7 +229,7 @@ C4JStorage::ESaveGameState CSaveGame::LoadSaveDataThumbnail(PSAVE_INFO pSaveInfo
const char *saveName = (const char *)pSaveInfo;
char thumbPath[512];
sprintf(thumbPath, "%s/Windows64/GameHDD/%s/thumbData.ms", curDir, saveName);
sprintf(thumbPath, "%s/Windows64/GameHDD/%s/thumbnails/thumbData.png", curDir, saveName);
HANDLE hThumb = CreateFileA(thumbPath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
@@ -230,6 +259,8 @@ C4JStorage::ESaveGameState CSaveGame::LoadSaveData(PSAVE_INFO pSaveInfo, int (*F
{
SetSaveUniqueFilename(pSaveInfo->UTF8SaveFilename);
memcpy(this->m_szSaveTitle, pSaveInfo->UTF8SaveTitle, sizeof(this->m_szSaveTitle)); // @Patoke add
if (m_pSaveData)
{
free(m_pSaveData);
@@ -244,7 +275,7 @@ C4JStorage::ESaveGameState CSaveGame::LoadSaveData(PSAVE_INFO pSaveInfo, int (*F
GetCurrentDirectoryA(sizeof(curDir), curDir);
sprintf(dirName, "%s/Windows64/GameHDD/%s", curDir, m_szSaveUniqueName);
CreateDirectoryA(dirName, 0);
sprintf(fileName, "%s/saveData.ms", dirName);
sprintf(fileName, "%s/%s", dirName, this->m_szSaveTitle); // @Patoke add
HANDLE h = CreateFileA(fileName, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
@@ -320,7 +351,7 @@ bool CSaveGame::GetSaveUniqueFilename(char *pszName)
void CSaveGame::SetSaveTitle(LPCWSTR pwchDefaultSaveName)
{
CreateSaveUniqueName();
memmove(m_szSaveTitle, pwchDefaultSaveName, sizeof(m_szSaveTitle));
sprintf(m_szSaveTitle, "%S", pwchDefaultSaveName); // @Patoke add
}
PVOID CSaveGame::AllocateSaveData(unsigned int uiBytes)
@@ -365,8 +396,7 @@ void CSaveGame::SetSaveImages(PBYTE pbThumbnail, DWORD dwThumbnailBytes, PBYTE p
// inject text metadata into the thumbnail if it exists
if (dwTextDataBytes > 0)
{
this->AddTextFieldToPNG(reinterpret_cast<unsigned __int8 *>(this->m_pbThumbnailData), dwThumbnailBytes, pbTextData, dwTextDataBytes,
dwNewThumbnailBytes);
this->AddTextFieldToPNG(this->m_pbThumbnailData, dwThumbnailBytes, pbTextData, dwTextDataBytes, dwNewThumbnailBytes);
}
}
@@ -460,7 +490,7 @@ C4JStorage::ESaveGameState CSaveGame::SaveSaveData(int (*Func)(LPVOID, const boo
GetCurrentDirectoryA(sizeof(curDir), curDir);
sprintf(dirName, "%s/Windows64/GameHDD/%s", curDir, m_szSaveUniqueName);
CreateDirectoryA(dirName, 0);
sprintf(fileName, "%s/saveData.ms", dirName);
sprintf(fileName, "%s/%s", dirName, this->m_szSaveTitle); // @Patoke add
HANDLE h = CreateFileA(fileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
@@ -473,7 +503,11 @@ C4JStorage::ESaveGameState CSaveGame::SaveSaveData(int (*Func)(LPVOID, const boo
// @Patoke add
if (this->m_pbThumbnailData != nullptr && this->m_uiThumbnailSize > 0)
{
sprintf(thumbName, "%s/thumbData.png", dirName);
char thumbDir[280];
sprintf(thumbDir, "%s/thumbnails", dirName);
CreateDirectoryA(thumbDir, 0);
sprintf(thumbName, "%s/thumbnails/thumbData.png", dirName);
HANDLE hThumb = CreateFileA(thumbName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
@@ -489,8 +523,43 @@ C4JStorage::ESaveGameState CSaveGame::SaveSaveData(int (*Func)(LPVOID, const boo
return C4JStorage::ESaveGame_Idle;
}
// @Patoke add
C4JStorage::ESaveGameState CSaveGame::DeleteSaveData(PSAVE_INFO pSaveInfo, int (*Func)(LPVOID lpParam, const bool), LPVOID lpParam)
{
char dirName[256];
char curDir[256];
char fileName[280];
char thumbName[280];
GetCurrentDirectoryA(sizeof(curDir), curDir);
sprintf(dirName, "%s/Windows64/GameHDD/%s", curDir, pSaveInfo->UTF8SaveFilename);
sprintf(fileName, "%s/%s", dirName, pSaveInfo->UTF8SaveTitle);
sprintf(thumbName, "%s/thumbnails/thumbData.png", dirName);
DeleteFileA(fileName);
DeleteFileA(thumbName);
RemoveDirectoryA(dirName);
PSAVE_INFO m_pDeleteInfo = pSaveInfo; // only here for consistency with the xbox one assert
assert((m_pDeleteInfo >= &m_pSaveDetails->SaveInfoA[0]) && (m_pDeleteInfo < &m_pSaveDetails->SaveInfoA[m_pSaveDetails->iSaveC]));
uint64_t index = pSaveInfo - this->m_pSaveDetails->SaveInfoA;
// shift all save data by 1 to fill the gap
for (int j = index; j < this->m_pSaveDetails->iSaveC - 1; ++j)
{
this->m_pSaveDetails->SaveInfoA[j] = this->m_pSaveDetails->SaveInfoA[j + 1];
}
--this->m_pSaveDetails->iSaveC;
// not calling this function is what caused the softlock in the original binaries
if (Func)
{
Func(lpParam, true);
}
return C4JStorage::ESaveGame_Idle;
}