LCEMP/Minecraft.Client/Platform_Libs/Dev/Storage/STO_SaveGame.cpp
2026-04-21 01:27:06 -04:00

1195 lines
36 KiB
C++

/*
MIT License
Copyright (c) 2026 Patoke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if !defined(__linux__)
#include "stdafx.h"
#endif
#include "STO_SaveGame.h"
#include <stdio.h>
#ifdef __linux__
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <wchar.h>
#endif
static unsigned long s_pngCrcTable[256];
static bool s_pngCrcTableReady = false;
static void BuildPngCrcTable()
{
for (unsigned int n = 0; n < 256; n++)
{
unsigned long c = n;
for (int k = 0; k < 8; k++)
c = (c & 1) ? (0xEDB88320L ^ (c >> 1)) : (c >> 1);
s_pngCrcTable[n] = c;
}
s_pngCrcTableReady = true;
}
static unsigned long PngCrc32(const unsigned char *buf, unsigned int len)
{
if (!s_pngCrcTableReady) BuildPngCrcTable();
unsigned long c = 0xFFFFFFFFL;
for (unsigned int i = 0; i < len; i++)
c = s_pngCrcTable[(c ^ buf[i]) & 0xFF] ^ (c >> 8);
return c ^ 0xFFFFFFFFL;
}
static inline unsigned int WriteBE32(unsigned int v)
{
return ((v >> 24) & 0xFF) |
((v >> 8) & 0xFF00) |
((v << 8) & 0xFF0000) |
((v << 24) & 0xFF000000);
}
static void GetGameHDDPath(char *outPath, int maxLen)
{
char curDir[256];
#ifdef __linux__
getcwd(curDir, sizeof(curDir));
snprintf(outPath, maxLen, "%s/Linux/GameHDD", curDir);
#else
GetCurrentDirectoryA(sizeof(curDir), curDir);
sprintf_s(outPath, maxLen, "%s\\Windows64\\GameHDD", curDir);
#endif
}
CSaveGame::CSaveGame()
{
m_pSaveData = nullptr;
m_uiSaveSize = 0;
m_bIsSafeDisabled = false;
ZeroMemory(m_szSaveUniqueName, sizeof(m_szSaveUniqueName));
ZeroMemory(m_wszSaveTitle, sizeof(m_wszSaveTitle));
m_pSaveDetails = nullptr;
m_bHasSaveDetails = false;
m_pbThumbnail = nullptr;
m_dwThumbnailBytes = 0;
m_pbDefaultThumbnail = nullptr;
m_dwDefaultThumbnailBytes = 0;
m_pbDefaultSaveImage = nullptr;
m_dwDefaultSaveImageBytes = 0;
char gameHDDPath[256];
GetGameHDDPath(gameHDDPath, sizeof(gameHDDPath));
#ifdef __linux__
char curDir[256];
getcwd(curDir, sizeof(curDir));
char win64Path[256];
snprintf(win64Path, sizeof(win64Path), "%s/Linux", curDir);
mkdir(win64Path, 0755);
mkdir(gameHDDPath, 0755);
#else
char win64Path[256];
char curDir[256];
GetCurrentDirectoryA(sizeof(curDir), curDir);
sprintf_s(win64Path, sizeof(win64Path), "%s\\Windows64", curDir);
CreateDirectoryA(win64Path, 0);
CreateDirectoryA(gameHDDPath, 0);
#endif
}
void CSaveGame::SetSaveDisabled(bool bDisable)
{
m_bIsSafeDisabled = bDisable;
}
bool CSaveGame::GetSaveDisabled(void)
{
return m_bIsSafeDisabled;
}
void CSaveGame::ResetSaveData()
{
free(m_pSaveData);
m_pSaveData = nullptr;
m_uiSaveSize = 0;
m_szSaveUniqueName[0] = '\0';
}
C4JStorage::ESaveGameState CSaveGame::GetSavesInfo(int iPad, int (*Func)(LPVOID lpParam, SAVE_DETAILS *pSaveDetails, const bool), LPVOID lpParam,
char *pszSavePackName)
{
if (!m_pSaveDetails)
{
m_pSaveDetails = new SAVE_DETAILS();
memset(m_pSaveDetails, 0, sizeof(SAVE_DETAILS));
}
delete[] m_pSaveDetails->SaveInfoA;
m_pSaveDetails->SaveInfoA = nullptr;
m_pSaveDetails->iSaveC = 0;
char gameHDDPath[256];
GetGameHDDPath(gameHDDPath, sizeof(gameHDDPath));
#ifdef __linux__
int resultCount = 0;
DIR *dir = opendir(gameHDDPath);
if (!dir)
{
}
else
{
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char saveFilePath[512];
snprintf(saveFilePath, sizeof(saveFilePath), "%s/%s/saveData.ms", gameHDDPath, entry->d_name);
struct stat st;
if (stat(saveFilePath, &st) == 0)
resultCount++;
}
closedir(dir);
}
if (resultCount > 0)
{
m_pSaveDetails->SaveInfoA = new SAVE_INFO[resultCount];
memset(m_pSaveDetails->SaveInfoA, 0, sizeof(SAVE_INFO) * resultCount);
m_pSaveDetails->iSaveC = 0;
int i = 0;
dir = opendir(gameHDDPath);
if (dir)
{
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char saveFilePath[512];
snprintf(saveFilePath, sizeof(saveFilePath), "%s/%s/saveData.ms", gameHDDPath, entry->d_name);
struct stat stFile;
if (stat(saveFilePath, &stFile) != 0)
continue;
strncpy(m_pSaveDetails->SaveInfoA[i].UTF8SaveFilename, entry->d_name, sizeof(m_pSaveDetails->SaveInfoA[i].UTF8SaveFilename) - 1);
char saveDirPath[512];
snprintf(saveDirPath, sizeof(saveDirPath), "%s/%s", gameHDDPath, entry->d_name);
char titleBuf[MAX_DISPLAYNAME_LENGTH];
if (LoadTitleFromFile(saveDirPath, titleBuf, sizeof(titleBuf)))
{
strncpy(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, titleBuf, sizeof(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle) - 1);
}
else
{
strncpy(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, entry->d_name, sizeof(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle) - 1);
}
m_pSaveDetails->SaveInfoA[i].metaData.dataSize = (DWORD)stFile.st_size;
char thumbFilePath[512];
snprintf(thumbFilePath, sizeof(thumbFilePath), "%s/%s/saveThumbnail.png", gameHDDPath, entry->d_name);
struct stat stThumb;
if (stat(thumbFilePath, &stThumb) == 0)
{
m_pSaveDetails->SaveInfoA[i].metaData.thumbnailSize = (DWORD)stThumb.st_size;
}
m_pSaveDetails->SaveInfoA[i].metaData.modifiedTime = stFile.st_mtime;
i++;
m_pSaveDetails->iSaveC++;
}
closedir(dir);
}
}
#else
WIN32_FIND_DATAA findFileData;
WIN32_FILE_ATTRIBUTE_DATA fileInfoBuffer;
char searchPattern[280];
sprintf_s(searchPattern, sizeof(searchPattern), "%s\\*", gameHDDPath);
int resultCount = 0;
HANDLE h = FindFirstFileExA(searchPattern, FindExInfoStandard, &findFileData, FindExSearchLimitToDirectories, 0, 0);
if (h == INVALID_HANDLE_VALUE)
{
DWORD error = GetLastError();
printf("Error finding save dirs: 0x%08x\n", error);
}
else
{
do
{
if ((findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
strcmp(findFileData.cFileName, ".") != 0 &&
strcmp(findFileData.cFileName, "..") != 0)
{
char saveFilePath[512];
sprintf_s(saveFilePath, sizeof(saveFilePath), "%s\\%s\\saveData.ms", gameHDDPath, findFileData.cFileName);
if (GetFileAttributesA(saveFilePath) != INVALID_FILE_ATTRIBUTES)
{
resultCount++;
}
}
} while (FindNextFileA(h, &findFileData));
FindClose(h);
}
if (resultCount > 0)
{
m_pSaveDetails->SaveInfoA = new SAVE_INFO[resultCount];
memset(m_pSaveDetails->SaveInfoA, 0, sizeof(SAVE_INFO) * resultCount);
m_pSaveDetails->iSaveC = 0;
int i = 0;
HANDLE fi = FindFirstFileExA(searchPattern, FindExInfoStandard, &findFileData, FindExSearchLimitToDirectories, 0, 0);
if (fi != INVALID_HANDLE_VALUE)
{
do
{
if ((findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
strcmp(findFileData.cFileName, ".") != 0 &&
strcmp(findFileData.cFileName, "..") != 0)
{
char saveFilePath[512];
sprintf_s(saveFilePath, sizeof(saveFilePath), "%s\\%s\\saveData.ms", gameHDDPath, findFileData.cFileName);
if (GetFileAttributesA(saveFilePath) == INVALID_FILE_ATTRIBUTES)
continue;
strcpy_s(m_pSaveDetails->SaveInfoA[i].UTF8SaveFilename, findFileData.cFileName);
char saveDirPath[512];
sprintf_s(saveDirPath, sizeof(saveDirPath), "%s\\%s", gameHDDPath, findFileData.cFileName);
char titleBuf[MAX_DISPLAYNAME_LENGTH];
if (LoadTitleFromFile(saveDirPath, titleBuf, sizeof(titleBuf)))
{
strcpy_s(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, titleBuf);
}
else
{
strcpy_s(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, findFileData.cFileName);
}
GetFileAttributesExA(saveFilePath, GetFileExInfoStandard, &fileInfoBuffer);
m_pSaveDetails->SaveInfoA[i].metaData.dataSize = fileInfoBuffer.nFileSizeLow;
char thumbFilePath[512];
sprintf_s(thumbFilePath, sizeof(thumbFilePath), "%s\\%s\\saveThumbnail.png", gameHDDPath, findFileData.cFileName);
WIN32_FILE_ATTRIBUTE_DATA thumbInfo;
if (GetFileAttributesExA(thumbFilePath, GetFileExInfoStandard, &thumbInfo))
{
m_pSaveDetails->SaveInfoA[i].metaData.thumbnailSize = thumbInfo.nFileSizeLow;
}
FILETIME ft = fileInfoBuffer.ftLastWriteTime;
ULARGE_INTEGER ull;
ull.LowPart = ft.dwLowDateTime;
ull.HighPart = ft.dwHighDateTime;
m_pSaveDetails->SaveInfoA[i].metaData.modifiedTime = (time_t)((ull.QuadPart - 116444736000000000ULL) / 10000000ULL);
i++;
m_pSaveDetails->iSaveC++;
}
} while (FindNextFileA(fi, &findFileData));
FindClose(fi);
}
}
#endif
m_bHasSaveDetails = true;
if (Func)
{
Func(lpParam, m_pSaveDetails, true);
}
return C4JStorage::ESaveGame_Idle;
}
PSAVE_DETAILS CSaveGame::ReturnSavesInfo()
{
if (m_bHasSaveDetails)
return m_pSaveDetails;
else
return nullptr;
}
void CSaveGame::ClearSavesInfo()
{
m_bHasSaveDetails = false;
if (m_pSaveDetails)
{
if (m_pSaveDetails->SaveInfoA)
{
delete[] m_pSaveDetails->SaveInfoA;
m_pSaveDetails->SaveInfoA = nullptr;
m_pSaveDetails->iSaveC = 0;
}
delete m_pSaveDetails;
m_pSaveDetails = 0;
}
}
C4JStorage::ESaveGameState CSaveGame::LoadSaveDataThumbnail(PSAVE_INFO pSaveInfo,
int (*Func)(LPVOID lpParam, PBYTE pbThumbnail, DWORD dwThumbnailBytes), LPVOID lpParam)
{
PBYTE pbThumbnail = nullptr;
DWORD dwThumbnailBytes = 0;
char gameHDDPath[256];
GetGameHDDPath(gameHDDPath, sizeof(gameHDDPath));
char thumbPath[512];
#ifdef __linux__
snprintf(thumbPath, sizeof(thumbPath), "%s/%s/saveThumbnail.png", gameHDDPath, pSaveInfo->UTF8SaveFilename);
int fd = open(thumbPath, O_RDONLY);
if (fd >= 0)
{
struct stat st;
if (fstat(fd, &st) == 0 && st.st_size > 0)
{
pbThumbnail = (PBYTE)malloc(st.st_size);
if (pbThumbnail)
{
ssize_t bytesRead = read(fd, pbThumbnail, st.st_size);
if (bytesRead == st.st_size)
{
dwThumbnailBytes = (DWORD)st.st_size;
}
else
{
free(pbThumbnail);
pbThumbnail = nullptr;
}
}
}
close(fd);
}
#else
sprintf_s(thumbPath, sizeof(thumbPath), "%s\\%s\\saveThumbnail.png", gameHDDPath, pSaveInfo->UTF8SaveFilename);
HANDLE h = CreateFileA(thumbPath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (h != INVALID_HANDLE_VALUE)
{
DWORD fileSize = GetFileSize(h, NULL);
if (fileSize != 0 && fileSize != INVALID_FILE_SIZE)
{
pbThumbnail = (PBYTE)malloc(fileSize);
if (pbThumbnail)
{
DWORD bytesRead = 0;
if (ReadFile(h, pbThumbnail, fileSize, &bytesRead, 0) && bytesRead == fileSize)
{
dwThumbnailBytes = fileSize;
}
else
{
free(pbThumbnail);
pbThumbnail = nullptr;
}
}
}
CloseHandle(h);
}
#endif
Func(lpParam, pbThumbnail, dwThumbnailBytes);
if (pbThumbnail)
{
free(pbThumbnail);
}
return C4JStorage::ESaveGame_GetSaveThumbnail;
}
C4JStorage::ESaveGameState CSaveGame::LoadSaveData(PSAVE_INFO pSaveInfo, int (*Func)(LPVOID lpParam, const bool, const bool), LPVOID lpParam)
{
SetSaveUniqueFilename(pSaveInfo->UTF8SaveFilename);
if (m_pSaveData)
{
free(m_pSaveData);
m_pSaveData = nullptr;
}
char gameHDDPath[256];
GetGameHDDPath(gameHDDPath, sizeof(gameHDDPath));
#ifdef __linux__
char saveDirPath[512];
snprintf(saveDirPath, sizeof(saveDirPath), "%s/%s", gameHDDPath, m_szSaveUniqueName);
char titleBuf[MAX_DISPLAYNAME_LENGTH];
if (LoadTitleFromFile(saveDirPath, titleBuf, sizeof(titleBuf)))
{
mbstowcs(m_wszSaveTitle, titleBuf, MAX_DISPLAYNAME_LENGTH);
}
char fileName[512];
snprintf(fileName, sizeof(fileName), "%s/saveData.ms", saveDirPath);
struct stat stFile;
if (stat(fileName, &stFile) != 0)
{
if (Func) Func(lpParam, 0, false);
return C4JStorage::ESaveGame_Idle;
}
m_uiSaveSize = (unsigned int)stFile.st_size;
m_pSaveData = malloc(m_uiSaveSize);
int fd = open(fileName, O_RDONLY);
bool success = false;
if (fd >= 0)
{
ssize_t bytesRead = read(fd, m_pSaveData, m_uiSaveSize);
close(fd);
success = (bytesRead == (ssize_t)m_uiSaveSize);
}
if (!success && m_pSaveData)
{
free(m_pSaveData);
m_pSaveData = nullptr;
m_uiSaveSize = 0;
}
#else
char saveDirPath[512];
sprintf_s(saveDirPath, sizeof(saveDirPath), "%s\\%s", gameHDDPath, m_szSaveUniqueName);
char titleBuf[MAX_DISPLAYNAME_LENGTH];
if (LoadTitleFromFile(saveDirPath, titleBuf, sizeof(titleBuf)))
{
MultiByteToWideChar(CP_UTF8, 0, titleBuf, -1, m_wszSaveTitle, MAX_DISPLAYNAME_LENGTH);
}
char fileName[512];
sprintf_s(fileName, sizeof(fileName), "%s\\saveData.ms", saveDirPath);
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
if (!GetFileAttributesExA(fileName, GetFileExInfoStandard, &fileInfo))
{
if (Func) Func(lpParam, 0, false);
return C4JStorage::ESaveGame_Idle;
}
m_uiSaveSize = fileInfo.nFileSizeLow;
m_pSaveData = malloc(m_uiSaveSize);
HANDLE h = CreateFileA(fileName, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
bool success = false;
if (h != INVALID_HANDLE_VALUE)
{
DWORD bytesRead = 0;
BOOL res = ReadFile(h, m_pSaveData, m_uiSaveSize, &bytesRead, 0);
_ASSERT(res && bytesRead == m_uiSaveSize);
CloseHandle(h);
success = (res && bytesRead == m_uiSaveSize);
}
if (!success && m_pSaveData)
{
free(m_pSaveData);
m_pSaveData = nullptr;
m_uiSaveSize = 0;
}
#endif
if (Func)
{
Func(lpParam, 0, success);
}
return C4JStorage::ESaveGame_Idle;
}
unsigned int CSaveGame::GetSaveSize()
{
return m_uiSaveSize;
}
void CSaveGame::GetSaveData(void *pvData, unsigned int *puiBytes)
{
if (pvData)
{
memmove(pvData, m_pSaveData, m_uiSaveSize);
*puiBytes = m_uiSaveSize;
}
else
{
*puiBytes = 0;
}
}
// @Patoke add
bool CSaveGame::GetSaveUniqueNumber(INT *piVal)
{
if (m_szSaveUniqueName[0] == '\0')
{
return 0;
}
int year, month, day, hour, minute;
sscanf(&m_szSaveUniqueName[4], "%02d%02d%02d%02d%02d", &year, &month, &day, &hour, &minute);
*piVal = 2678400 * year + 86400 * month + 3600 * day + 60 * hour + minute;
return true;
}
// @Patoke add
bool CSaveGame::GetSaveUniqueFilename(char *pszName)
{
if (m_szSaveUniqueName[0] == '\0')
{
return false;
}
memset(pszName, 0, 14);
for (int i = 0; i < 12; i++)
{
pszName[i] = m_szSaveUniqueName[i + 2];
}
return true;
}
void CSaveGame::SetSaveTitle(LPCWSTR pwchDefaultSaveName)
{
if (m_szSaveUniqueName[0] == '\0')
{
CreateSaveUniqueName();
}
wcscpy_s(m_wszSaveTitle, MAX_DISPLAYNAME_LENGTH, pwchDefaultSaveName);
}
LPCWSTR CSaveGame::GetSaveTitle()
{
return m_wszSaveTitle;
}
PVOID CSaveGame::AllocateSaveData(unsigned int uiBytes)
{
free(m_pSaveData);
m_pSaveData = malloc(uiBytes);
if (m_pSaveData)
{
m_uiSaveSize = uiBytes;
}
return m_pSaveData;
}
// https://github.com/LCEMP/LCEMP
void CSaveGame::SetSaveImages(PBYTE pbThumbnail, DWORD dwThumbnailBytes, PBYTE pbImage, DWORD dwImageBytes, PBYTE pbTextData, DWORD dwTextDataBytes)
{
if (m_pbThumbnail)
{
free(m_pbThumbnail);
m_pbThumbnail = nullptr;
m_dwThumbnailBytes = 0;
}
if (pbThumbnail && dwThumbnailBytes > 0)
{
const DWORD kInsertOffset = 33; // end of PNG sig + IHDR
if (pbTextData && dwTextDataBytes > 0 && dwThumbnailBytes > kInsertOffset)
{
const DWORD chunkOverhead = 4 + 4 + 4;
const DWORD chunkTotal = chunkOverhead + dwTextDataBytes;
const DWORD newSize = dwThumbnailBytes + chunkTotal;
m_pbThumbnail = (PBYTE)malloc(newSize);
if (m_pbThumbnail)
{
memcpy(m_pbThumbnail, pbThumbnail, kInsertOffset);
PBYTE p = m_pbThumbnail + kInsertOffset;
*(unsigned int *)p = WriteBE32(dwTextDataBytes);
p += 4;
p[0] = 't'; p[1] = 'E'; p[2] = 'X'; p[3] = 't';
p += 4;
memcpy(p, pbTextData, dwTextDataBytes);
p += dwTextDataBytes;
unsigned long crc = PngCrc32(m_pbThumbnail + kInsertOffset + 4,
4 + dwTextDataBytes);
*(unsigned int *)p = WriteBE32((unsigned int)crc);
memcpy(m_pbThumbnail + kInsertOffset + chunkTotal,
pbThumbnail + kInsertOffset,
dwThumbnailBytes - kInsertOffset);
m_dwThumbnailBytes = newSize;
}
}
else
{
m_pbThumbnail = (PBYTE)malloc(dwThumbnailBytes);
if (m_pbThumbnail)
{
memcpy(m_pbThumbnail, pbThumbnail, dwThumbnailBytes);
m_dwThumbnailBytes = dwThumbnailBytes;
}
}
}
}
// https://github.com/LCEMP/LCEMP
C4JStorage::ESaveGameState CSaveGame::SaveSaveData(int (*Func)(LPVOID, const bool), LPVOID lpParam)
{
if (!m_pSaveData || m_uiSaveSize == 0)
{
if (Func) Func(lpParam, false);
return C4JStorage::ESaveGame_Idle;
}
char gameHDDPath[256];
GetGameHDDPath(gameHDDPath, sizeof(gameHDDPath));
#ifdef __linux__
char saveDirPath[512];
snprintf(saveDirPath, sizeof(saveDirPath), "%s/%s", gameHDDPath, m_szSaveUniqueName);
mkdir(saveDirPath, 0755);
char saveFilePath[512];
snprintf(saveFilePath, sizeof(saveFilePath), "%s/saveData.ms", saveDirPath);
int fd = open(saveFilePath, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0)
{
if (Func) Func(lpParam, false);
return C4JStorage::ESaveGame_Idle;
}
ssize_t bytesWritten = write(fd, m_pSaveData, m_uiSaveSize);
close(fd);
SaveTitleFile(saveDirPath);
if (m_pbThumbnail && m_dwThumbnailBytes > 0)
{
char thumbPath[512];
snprintf(thumbPath, sizeof(thumbPath), "%s/saveThumbnail.png", saveDirPath);
int tfd = open(thumbPath, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (tfd >= 0)
{
write(tfd, m_pbThumbnail, m_dwThumbnailBytes);
close(tfd);
}
free(m_pbThumbnail);
m_pbThumbnail = nullptr;
m_dwThumbnailBytes = 0;
}
bool success = (bytesWritten == (ssize_t)m_uiSaveSize);
#else
char saveDirPath[512];
sprintf_s(saveDirPath, sizeof(saveDirPath), "%s\\%s", gameHDDPath, m_szSaveUniqueName);
CreateDirectoryA(saveDirPath, 0);
char saveFilePath[512];
sprintf_s(saveFilePath, sizeof(saveFilePath), "%s\\saveData.ms", saveDirPath);
HANDLE h = CreateFileA(saveFilePath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (h == INVALID_HANDLE_VALUE)
{
if (Func) Func(lpParam, false);
return C4JStorage::ESaveGame_Idle;
}
DWORD bytesWritten = 0;
BOOL res = WriteFile(h, m_pSaveData, m_uiSaveSize, &bytesWritten, 0);
CloseHandle(h);
SaveTitleFile(saveDirPath);
if (m_pbThumbnail && m_dwThumbnailBytes > 0)
{
char thumbPath[512];
sprintf_s(thumbPath, sizeof(thumbPath), "%s\\saveThumbnail.png", saveDirPath);
HANDLE hThumb = CreateFileA(thumbPath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hThumb != INVALID_HANDLE_VALUE)
{
DWORD thumbWritten = 0;
WriteFile(hThumb, m_pbThumbnail, m_dwThumbnailBytes, &thumbWritten, 0);
CloseHandle(hThumb);
}
free(m_pbThumbnail);
m_pbThumbnail = nullptr;
m_dwThumbnailBytes = 0;
}
bool success = (res && bytesWritten == m_uiSaveSize);
#endif
if (Func) Func(lpParam, success);
return C4JStorage::ESaveGame_Idle;
}
C4JStorage::ESaveGameState CSaveGame::DeleteSaveData(PSAVE_INFO pSaveInfo, int (*Func)(LPVOID lpParam, const bool), LPVOID lpParam)
{
char gameHDDPath[256];
GetGameHDDPath(gameHDDPath, sizeof(gameHDDPath));
#ifdef __linux__
char saveDirPath[512];
snprintf(saveDirPath, sizeof(saveDirPath), "%s/%s", gameHDDPath, pSaveInfo->UTF8SaveFilename);
DIR *dir = opendir(saveDirPath);
if (dir)
{
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char filePath[512];
snprintf(filePath, sizeof(filePath), "%s/%s", saveDirPath, entry->d_name);
unlink(filePath);
}
closedir(dir);
}
rmdir(saveDirPath);
struct stat st;
bool success = (stat(saveDirPath, &st) != 0);
#else
char saveDirPath[512];
sprintf_s(saveDirPath, sizeof(saveDirPath), "%s\\%s", gameHDDPath, pSaveInfo->UTF8SaveFilename);
char searchPattern[512];
sprintf_s(searchPattern, sizeof(searchPattern), "%s\\*", saveDirPath);
WIN32_FIND_DATAA findData;
HANDLE hFind = FindFirstFileA(searchPattern, &findData);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
char filePath[512];
sprintf_s(filePath, sizeof(filePath), "%s\\%s", saveDirPath, findData.cFileName);
DeleteFileA(filePath);
}
} while (FindNextFileA(hFind, &findData));
FindClose(hFind);
}
RemoveDirectoryA(saveDirPath);
bool success = (GetFileAttributesA(saveDirPath) == INVALID_FILE_ATTRIBUTES);
#endif
if (Func) Func(lpParam, success);
return C4JStorage::ESaveGame_Idle;
}
C4JStorage::ESaveGameState CSaveGame::DoesSaveExist(bool *pbExists)
{
if (m_szSaveUniqueName[0] == '\0')
{
*pbExists = false;
return C4JStorage::ESaveGame_Idle;
}
char gameHDDPath[256];
GetGameHDDPath(gameHDDPath, sizeof(gameHDDPath));
char saveFilePath[512];
#ifdef __linux__
snprintf(saveFilePath, sizeof(saveFilePath), "%s/%s/saveData.ms", gameHDDPath, m_szSaveUniqueName);
struct stat st;
*pbExists = (stat(saveFilePath, &st) == 0);
#else
sprintf_s(saveFilePath, sizeof(saveFilePath), "%s\\%s\\saveData.ms", gameHDDPath, m_szSaveUniqueName);
*pbExists = (GetFileAttributesA(saveFilePath) != INVALID_FILE_ATTRIBUTES);
#endif
return C4JStorage::ESaveGame_Idle;
}
void CSaveGame::CopySaveDataToNewSave(PBYTE pbThumbnail, DWORD cbThumbnail, WCHAR *wchNewName, int (*Func)(LPVOID lpParam, bool), LPVOID lpParam)
{
char oldUniqueName[32];
strcpy_s(oldUniqueName, m_szSaveUniqueName);
CreateSaveUniqueName();
char gameHDDPath[256];
GetGameHDDPath(gameHDDPath, sizeof(gameHDDPath));
#ifdef __linux__
char newSaveDirPath[512];
snprintf(newSaveDirPath, sizeof(newSaveDirPath), "%s/%s", gameHDDPath, m_szSaveUniqueName);
mkdir(newSaveDirPath, 0755);
char oldSaveFile[512], newSaveFile[512];
snprintf(oldSaveFile, sizeof(oldSaveFile), "%s/%s/saveData.ms", gameHDDPath, oldUniqueName);
snprintf(newSaveFile, sizeof(newSaveFile), "%s/saveData.ms", newSaveDirPath);
{
int sfd = open(oldSaveFile, O_RDONLY);
int dfd = open(newSaveFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (sfd >= 0 && dfd >= 0)
{
char buf[8192];
ssize_t n;
while ((n = read(sfd, buf, sizeof(buf))) > 0) write(dfd, buf, n);
}
if (sfd >= 0) close(sfd);
if (dfd >= 0) close(dfd);
}
char oldThumbFile[512], newThumbFile[512];
snprintf(oldThumbFile, sizeof(oldThumbFile), "%s/%s/saveThumbnail.png", gameHDDPath, oldUniqueName);
snprintf(newThumbFile, sizeof(newThumbFile), "%s/saveThumbnail.png", newSaveDirPath);
{
int sfd = open(oldThumbFile, O_RDONLY);
int dfd = open(newThumbFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (sfd >= 0 && dfd >= 0)
{
char buf[8192];
ssize_t n;
while ((n = read(sfd, buf, sizeof(buf))) > 0) write(dfd, buf, n);
}
if (sfd >= 0) close(sfd);
if (dfd >= 0) close(dfd);
}
if (pbThumbnail && cbThumbnail > 0)
{
int tfd = open(newThumbFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (tfd >= 0)
{
write(tfd, pbThumbnail, cbThumbnail);
close(tfd);
}
}
if (wchNewName)
{
wcscpy_s(m_wszSaveTitle, MAX_DISPLAYNAME_LENGTH, wchNewName);
}
SaveTitleFile(newSaveDirPath);
struct stat st;
bool success = (stat(newSaveFile, &st) == 0);
#else
char newSaveDirPath[512];
sprintf_s(newSaveDirPath, sizeof(newSaveDirPath), "%s\\%s", gameHDDPath, m_szSaveUniqueName);
CreateDirectoryA(newSaveDirPath, 0);
char oldSaveFile[512], newSaveFile[512];
sprintf_s(oldSaveFile, sizeof(oldSaveFile), "%s\\%s\\saveData.ms", gameHDDPath, oldUniqueName);
sprintf_s(newSaveFile, sizeof(newSaveFile), "%s\\saveData.ms", newSaveDirPath);
CopyFileA(oldSaveFile, newSaveFile, FALSE);
char oldThumbFile[512], newThumbFile[512];
sprintf_s(oldThumbFile, sizeof(oldThumbFile), "%s\\%s\\saveThumbnail.png", gameHDDPath, oldUniqueName);
sprintf_s(newThumbFile, sizeof(newThumbFile), "%s\\saveThumbnail.png", newSaveDirPath);
CopyFileA(oldThumbFile, newThumbFile, FALSE);
if (pbThumbnail && cbThumbnail > 0)
{
HANDLE hThumb = CreateFileA(newThumbFile, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hThumb != INVALID_HANDLE_VALUE)
{
DWORD thumbWritten = 0;
WriteFile(hThumb, pbThumbnail, cbThumbnail, &thumbWritten, 0);
CloseHandle(hThumb);
}
}
if (wchNewName)
{
wcscpy_s(m_wszSaveTitle, MAX_DISPLAYNAME_LENGTH, wchNewName);
}
SaveTitleFile(newSaveDirPath);
bool success = (GetFileAttributesA(newSaveFile) != INVALID_FILE_ATTRIBUTES);
#endif
if (Func) Func(lpParam, success);
}
void CSaveGame::SetSaveUniqueFilename(char *szFilename)
{
strcpy_s(m_szSaveUniqueName, szFilename);
}
void CSaveGame::CreateSaveUniqueName(void)
{
#ifdef __linux__
time_t now = time(NULL);
struct tm t;
gmtime_r(&now, &t);
snprintf(m_szSaveUniqueName, sizeof(m_szSaveUniqueName), "%4d%02d%02d%02d%02d%02d",
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
#else
_SYSTEMTIME UTCSysTime;
GetSystemTime(&UTCSysTime);
sprintf_s(m_szSaveUniqueName, sizeof(m_szSaveUniqueName), "%4d%02d%02d%02d%02d%02d", UTCSysTime.wYear, UTCSysTime.wMonth, UTCSysTime.wDay,
UTCSysTime.wHour, UTCSysTime.wMinute, UTCSysTime.wSecond);
#endif
}
void CSaveGame::SaveTitleFile(const char *saveDirPath)
{
if (m_wszSaveTitle[0] == L'\0')
return;
char titleFilePath[512];
#ifdef __linux__
snprintf(titleFilePath, sizeof(titleFilePath), "%s/saveTitle.txt", saveDirPath);
char utf8Title[MAX_DISPLAYNAME_LENGTH * 3];
int len = (int)wcstombs(utf8Title, m_wszSaveTitle, sizeof(utf8Title));
if (len > 0)
{
int fd = open(titleFilePath, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0)
{
write(fd, utf8Title, len);
close(fd);
}
}
#else
sprintf_s(titleFilePath, sizeof(titleFilePath), "%s\\saveTitle.txt", saveDirPath);
char utf8Title[MAX_DISPLAYNAME_LENGTH * 3];
int len = WideCharToMultiByte(CP_UTF8, 0, m_wszSaveTitle, -1, utf8Title, sizeof(utf8Title), NULL, NULL);
if (len > 0)
{
HANDLE h = CreateFileA(titleFilePath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (h != INVALID_HANDLE_VALUE)
{
DWORD bytesWritten = 0;
WriteFile(h, utf8Title, len - 1, &bytesWritten, 0);
CloseHandle(h);
}
}
#endif
}
bool CSaveGame::LoadTitleFromFile(const char *saveDirPath, char *outUTF8Title, int maxLen)
{
char titleFilePath[512];
#ifdef __linux__
snprintf(titleFilePath, sizeof(titleFilePath), "%s/saveTitle.txt", saveDirPath);
int fd = open(titleFilePath, O_RDONLY);
if (fd < 0)
return false;
struct stat st;
if (fstat(fd, &st) != 0 || st.st_size == 0 || st.st_size >= maxLen)
{
close(fd);
return false;
}
ssize_t bytesRead = read(fd, outUTF8Title, st.st_size);
close(fd);
if (bytesRead > 0)
{
outUTF8Title[bytesRead] = '\0';
return true;
}
return false;
#else
sprintf_s(titleFilePath, sizeof(titleFilePath), "%s\\saveTitle.txt", saveDirPath);
HANDLE h = CreateFileA(titleFilePath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (h == INVALID_HANDLE_VALUE)
return false;
DWORD fileSize = GetFileSize(h, NULL);
if (fileSize == 0 || fileSize == INVALID_FILE_SIZE || fileSize >= (DWORD)maxLen)
{
CloseHandle(h);
return false;
}
DWORD bytesRead = 0;
ReadFile(h, outUTF8Title, fileSize, &bytesRead, 0);
CloseHandle(h);
if (bytesRead > 0)
{
outUTF8Title[bytesRead] = '\0';
return true;
}
return false;
#endif
}
void CSaveGame::SetDefaultImages(PBYTE pbOptionsImage, DWORD dwOptionsImageBytes, PBYTE pbSaveImage, DWORD dwSaveImageBytes, PBYTE pbSaveThumbnail, DWORD dwSaveThumbnailBytes)
{
if (m_pbDefaultSaveImage)
{
free(m_pbDefaultSaveImage);
m_pbDefaultSaveImage = nullptr;
m_dwDefaultSaveImageBytes = 0;
}
if (pbSaveImage && dwSaveImageBytes > 0)
{
m_pbDefaultSaveImage = (PBYTE)malloc(dwSaveImageBytes);
if (m_pbDefaultSaveImage)
{
memcpy(m_pbDefaultSaveImage, pbSaveImage, dwSaveImageBytes);
m_dwDefaultSaveImageBytes = dwSaveImageBytes;
}
}
if (m_pbDefaultThumbnail)
{
free(m_pbDefaultThumbnail);
m_pbDefaultThumbnail = nullptr;
m_dwDefaultThumbnailBytes = 0;
}
if (pbSaveThumbnail && dwSaveThumbnailBytes > 0)
{
m_pbDefaultThumbnail = (PBYTE)malloc(dwSaveThumbnailBytes);
if (m_pbDefaultThumbnail)
{
memcpy(m_pbDefaultThumbnail, pbSaveThumbnail, dwSaveThumbnailBytes);
m_dwDefaultThumbnailBytes = dwSaveThumbnailBytes;
}
}
}
void CSaveGame::GetDefaultSaveImage(PBYTE *ppbSaveImage, DWORD *pdwSaveImageBytes)
{
if (ppbSaveImage) *ppbSaveImage = m_pbDefaultSaveImage;
if (pdwSaveImageBytes) *pdwSaveImageBytes = m_dwDefaultSaveImageBytes;
}
void CSaveGame::GetDefaultSaveThumbnail(PBYTE *ppbSaveThumbnail, DWORD *pdwSaveThumbnailBytes)
{
if (ppbSaveThumbnail) *ppbSaveThumbnail = m_pbDefaultThumbnail;
if (pdwSaveThumbnailBytes) *pdwSaveThumbnailBytes = m_dwDefaultThumbnailBytes;
}
C4JStorage::ESaveGameState CSaveGame::RenameSaveData(int iRenameIndex, uint16_t *pui16NewName, int (*Func)(LPVOID lpParam, const bool), LPVOID lpParam)
{
bool bSuccess = false;
if (m_pSaveDetails && iRenameIndex >= 0 && iRenameIndex < m_pSaveDetails->iSaveC && pui16NewName)
{
char gameHDDPath[256];
GetGameHDDPath(gameHDDPath, sizeof(gameHDDPath));
wchar_t newTitle[MAX_DISPLAYNAME_LENGTH];
int i = 0;
while (i < MAX_DISPLAYNAME_LENGTH - 1 && pui16NewName[i] != 0)
{
newTitle[i] = (wchar_t)pui16NewName[i];
i++;
}
newTitle[i] = L'\0';
#ifdef __linux__
char saveDirPath[512];
snprintf(saveDirPath, sizeof(saveDirPath), "%s/%s", gameHDDPath, m_pSaveDetails->SaveInfoA[iRenameIndex].UTF8SaveFilename);
char titleFilePath[512];
snprintf(titleFilePath, sizeof(titleFilePath), "%s/saveTitle.txt", saveDirPath);
char utf8Title[MAX_DISPLAYNAME_LENGTH * 3];
int len = (int)wcstombs(utf8Title, newTitle, sizeof(utf8Title));
if (len > 0)
{
int fd = open(titleFilePath, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0)
{
write(fd, utf8Title, len);
close(fd);
wcstombs(m_pSaveDetails->SaveInfoA[iRenameIndex].UTF8SaveTitle, newTitle, MAX_DISPLAYNAME_LENGTH);
bSuccess = true;
}
}
#else
char saveDirPath[512];
sprintf_s(saveDirPath, sizeof(saveDirPath), "%s\\%s", gameHDDPath, m_pSaveDetails->SaveInfoA[iRenameIndex].UTF8SaveFilename);
char titleFilePath[512];
sprintf_s(titleFilePath, sizeof(titleFilePath), "%s\\saveTitle.txt", saveDirPath);
char utf8Title[MAX_DISPLAYNAME_LENGTH * 3];
int len = WideCharToMultiByte(CP_UTF8, 0, newTitle, -1, utf8Title, sizeof(utf8Title), NULL, NULL);
if (len > 0)
{
HANDLE h = CreateFileA(titleFilePath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (h != INVALID_HANDLE_VALUE)
{
DWORD bytesWritten = 0;
WriteFile(h, utf8Title, len - 1, &bytesWritten, 0);
CloseHandle(h);
WideCharToMultiByte(CP_UTF8, 0, newTitle, -1, m_pSaveDetails->SaveInfoA[iRenameIndex].UTF8SaveTitle, MAX_DISPLAYNAME_LENGTH, NULL, NULL);
bSuccess = true;
}
}
#endif
}
if (Func) Func(lpParam, bSuccess);
return C4JStorage::ESaveGame_Rename;
}