From 8ad0c385ded218c3d02d9b6dcf86b657933a883b Mon Sep 17 00:00:00 2001 From: Fireblade <72758695+Firebladedoge229@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:36:42 -0400 Subject: [PATCH] feat: copy save (rockefeler) --- impls/Windows_Libs/Storage/src/4J_Storage.cpp | 105 ++++++++++++++++++ inc/4J_Storage.h | 9 +- 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/impls/Windows_Libs/Storage/src/4J_Storage.cpp b/impls/Windows_Libs/Storage/src/4J_Storage.cpp index 969818b..a2a47d2 100644 --- a/impls/Windows_Libs/Storage/src/4J_Storage.cpp +++ b/impls/Windows_Libs/Storage/src/4J_Storage.cpp @@ -137,6 +137,111 @@ void C4JStorage::CopySaveDataToNewSave(PBYTE pbThumbnail, DWORD cbThumbnail, WCH ; } +// copy save thingy for loadcreatejoin +// new folder gets a timestamp name so it doesn't overwrite anything +C4JStorage::ESaveGameState C4JStorage::CopySaveData(PSAVE_INFO pSaveInfo, int(*Func)(LPVOID, const bool, C4JStorage::ESaveGameState), bool(*FuncProg)(LPVOID, const int), LPVOID lpParam) +{ + if (!pSaveInfo) + { + if (Func) Func(lpParam, false, ESaveGame_Idle); + return ESaveGame_Idle; + } + + char curDir[256]; + GetCurrentDirectoryA(sizeof(curDir), curDir); + + char srcPath[512]; + sprintf(srcPath, "%s\\Windows64\\GameHDD\\%s", curDir, pSaveInfo->UTF8SaveFilename); + + // unique name so two copies can coexist + _SYSTEMTIME st; + GetSystemTime(&st); + char newUniqueName[64]; + sprintf_s(newUniqueName, "%4d%02d%02d%02d%02d%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + + char dstPath[512]; + sprintf(dstPath, "%s\\Windows64\\GameHDD\\%s", curDir, newUniqueName); + + // make the new folder + if (!CreateDirectoryA(dstPath, nullptr)) + { + if (Func) Func(lpParam, false, ESaveGame_CopyCompleteFail); + return ESaveGame_Idle; + } + + // copy files including subfolders like thumbnails + char searchPattern[512]; + sprintf(searchPattern, "%s\\*", srcPath); + + WIN32_FIND_DATAA findData; + HANDLE hFind = FindFirstFileA(searchPattern, &findData); + bool bSuccess = true; + + if (hFind != INVALID_HANDLE_VALUE) + { + do + { + if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0) + continue; + + char srcFile[512]; + sprintf(srcFile, "%s\\%s", srcPath, findData.cFileName); + + char dstFile[512]; + sprintf(dstFile, "%s\\%s", dstPath, findData.cFileName); + + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + CreateDirectoryA(dstFile, nullptr); + + char subSearch[512]; + sprintf(subSearch, "%s\\*", srcFile); + WIN32_FIND_DATAA subFind; + HANDLE hSubFind = FindFirstFileA(subSearch, &subFind); + if (hSubFind != INVALID_HANDLE_VALUE) + { + do + { + if (strcmp(subFind.cFileName, ".") == 0 || strcmp(subFind.cFileName, "..") == 0) + continue; + + char subSrc[512]; + sprintf(subSrc, "%s\\%s", srcFile, subFind.cFileName); + char subDst[512]; + sprintf(subDst, "%s\\%s", dstFile, subFind.cFileName); + + if (!CopyFileA(subSrc, subDst, FALSE)) + { + bSuccess = false; + break; + } + } while (FindNextFileA(hSubFind, &subFind)); + FindClose(hSubFind); + } + } + else + { + if (!CopyFileA(srcFile, dstFile, FALSE)) + { + bSuccess = false; + } + } + + if (!bSuccess) + break; + + } while (FindNextFileA(hFind, &findData)); + FindClose(hFind); + } + + // tell the ui to refresh the save list if it worked + if (Func) + { + Func(lpParam, bSuccess, bSuccess ? ESaveGame_Idle : ESaveGame_CopyCompleteFail); + } + return ESaveGame_Idle; +} + void C4JStorage::SetSaveDeviceSelected(unsigned int uiPad, bool bSelected) { // @Patoke: majorly used in XUI diff --git a/inc/4J_Storage.h b/inc/4J_Storage.h index 87dd0de..cbd69a2 100644 --- a/inc/4J_Storage.h +++ b/inc/4J_Storage.h @@ -133,9 +133,11 @@ public: ESaveGame_Rename, ESaveGame_Delete, - ESaveGame_GetSaveThumbnail // Not used as an actual state in the PS4, but the game expects this to be returned to indicate success when getting a thumbnail - + ESaveGame_GetSaveThumbnail, // Not used as an actual state in the PS4, but the game expects this to be returned to indicate success when getting a thumbnail + ESaveGame_CopyCompleteSuccess, + ESaveGame_CopyCompleteFail }; + enum ELoadGameStatus { ELoadGame_Idle = 0, @@ -261,7 +263,8 @@ public: unsigned int GetSaveSize(); void GetSaveData(void* pvData, unsigned int* puiBytes); PVOID AllocateSaveData(unsigned int uiBytes); - void SetSaveImages(PBYTE pbThumbnail, DWORD dwThumbnailBytes, PBYTE pbImage, DWORD dwImageBytes, PBYTE pbTextData, DWORD dwTextDataBytes); // Sets the thumbnail & image for the save, optionally setting the metadata in the png + void SetSaveImages(PBYTE pbThumbnail, DWORD dwThumbnailBytes, PBYTE pbImage, DWORD dwImageBytes, PBYTE pbTextData, DWORD dwTextDataBytes); + C4JStorage::ESaveGameState CopySaveData(PSAVE_INFO pSaveInfo, int (*Func)(LPVOID, const bool, C4JStorage::ESaveGameState), bool (*FuncProg)(LPVOID, const int), LPVOID lpParam); // Sets the thumbnail & image for the save, optionally setting the metadata in the png C4JStorage::ESaveGameState SaveSaveData(int(*Func)(LPVOID, const bool), LPVOID lpParam); void CopySaveDataToNewSave(PBYTE pbThumbnail, DWORD cbThumbnail, WCHAR* wchNewName, int (*Func)(LPVOID lpParam, bool), LPVOID lpParam); void SetSaveDeviceSelected(unsigned int uiPad, bool bSelected);