Files
ppsspp/Core/Dialog/SavedataParam.h
Henrik RydgårdandClaude Opus 5.5 9e69c1f9aa Savedata: Keep the IO thread out of the dialog's state
Replaces the temporary fix that did the hidden modes' IO inside Update.

The IO thread read and wrote the dialog's request, display state and save
list, all shared with the emulator thread, which kept using them to draw the
dialog and reload the request from the game.

Now the IO thread works on its own copy of the request, its own SavedataParam
and directory names resolved up front, and shares nothing else with the
emulator thread but the (locked) file system, MemoryStick_FreeSpace's cached
use and sceChnnlsv's scratch buffer and kirk state, the last two now under
locks too. It still reads and writes the game's buffers directly, like a PSP's
utility threads and sceIoReadAsync do, so a savestate waits for it before it
saves or loads memory. Save
bookkeeping, the save indicator and display changes happen on the emulator
thread when the results are taken, and only the request fields the IO changed
are copied back, so a game's own edits in the meantime survive.

Hidden modes take the results at the next Update (or, with Host IO timing,
the first Update that finds them done). The visible dialogs keep drawing and
take them once the IO is done; save and load used to stall the emulator
thread for the whole operation. Savestates keep results that haven't been
taken yet.

When the results land in PSP memory doesn't matter to games, so
utility/savedata/filelist now only prints them once the utility has finished.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-25 12:50:39 -06:00

405 lines
13 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include <memory>
#include <mutex>
#include <set>
#include <unordered_map>
#include <cstdint>
#include "Common/CommonTypes.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/MemMap.h"
#include "Core/HLE/sceRtc.h"
#include "Core/Dialog/PSPDialog.h"
#undef st_ctime
#undef st_atime
#undef st_mtime
class PPGeImage;
struct PSPFileInfo;
typedef u32_le SceSize_le;
enum SceUtilitySavedataType
{
SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD = 0,
SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE = 1,
SCE_UTILITY_SAVEDATA_TYPE_LOAD = 2,
SCE_UTILITY_SAVEDATA_TYPE_SAVE = 3,
SCE_UTILITY_SAVEDATA_TYPE_LISTLOAD = 4,
SCE_UTILITY_SAVEDATA_TYPE_LISTSAVE = 5,
SCE_UTILITY_SAVEDATA_TYPE_LISTDELETE = 6,
SCE_UTILITY_SAVEDATA_TYPE_LISTALLDELETE = 7,// When run on a PSP, displays a list of all saves on the PSP. Weird. (Not really, it's to let you free up space)
SCE_UTILITY_SAVEDATA_TYPE_SIZES = 8,
SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE = 9,
SCE_UTILITY_SAVEDATA_TYPE_DELETE = 10,
SCE_UTILITY_SAVEDATA_TYPE_LIST = 11,
SCE_UTILITY_SAVEDATA_TYPE_FILES = 12,
SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE = 13,
SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA = 14,
SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE = 15,
SCE_UTILITY_SAVEDATA_TYPE_READDATA = 16,
SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE = 17,
SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA = 18,
SCE_UTILITY_SAVEDATA_TYPE_ERASESECURE = 19,
SCE_UTILITY_SAVEDATA_TYPE_ERASE = 20,
SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA = 21,
SCE_UTILITY_SAVEDATA_TYPE_GETSIZE = 22,
};
static const char *const utilitySavedataTypeNames[] = {
"AUTOLOAD",
"AUTOSAVE",
"LOAD",
"SAVE",
"LISTLOAD",
"LISTSAVE",
"LISTDELETE",
"LISTALLDELETE",
"SIZES",
"AUTODELETE",
"DELETE",
"LIST",
"FILES",
"MAKEDATASECURE",
"MAKEDATA",
"READDATASECURE",
"READDATA",
"WRITEDATASECURE",
"WRITEDATA",
"ERASESECURE",
"ERASE",
"DELETEDATA",
"GETSIZE",
};
enum SceUtilitySavedataFocus
{
SCE_UTILITY_SAVEDATA_FOCUS_NAME = 0, // specified by saveName[]
SCE_UTILITY_SAVEDATA_FOCUS_FIRSTLIST = 1, // first listed (on screen or of all?)
SCE_UTILITY_SAVEDATA_FOCUS_LASTLIST = 2, // last listed (on screen or of all?)
SCE_UTILITY_SAVEDATA_FOCUS_LATEST = 3, // latest by modification date (first if none)
SCE_UTILITY_SAVEDATA_FOCUS_OLDEST = 4, // oldest by modification date (first if none)
SCE_UTILITY_SAVEDATA_FOCUS_FIRSTDATA = 5, // first non-empty (first if none)
SCE_UTILITY_SAVEDATA_FOCUS_LASTDATA = 6, // last non-empty (first if none)
SCE_UTILITY_SAVEDATA_FOCUS_FIRSTEMPTY = 7, // first empty (what if no empty?)
SCE_UTILITY_SAVEDATA_FOCUS_LASTEMPTY = 8, // last empty (what if no empty?)
};
#if COMMON_LITTLE_ENDIAN
typedef SceUtilitySavedataType SceUtilitySavedataType_le;
typedef SceUtilitySavedataFocus SceUtilitySavedataFocus_le;
#else
typedef swap_struct_t<SceUtilitySavedataType, swap_32_t<SceUtilitySavedataType> > SceUtilitySavedataType_le;
typedef swap_struct_t<SceUtilitySavedataFocus, swap_32_t<SceUtilitySavedataFocus> > SceUtilitySavedataFocus_le;
#endif
typedef char SceUtilitySavedataSaveName[20];
// title, savedataTitle, detail: parts of the unencrypted SFO
// data, it contains what the VSH and standard load screen shows
struct PspUtilitySavedataSFOParam
{
char title[0x80];
char savedataTitle[0x80];
char detail[0x400];
unsigned char parentalLevel;
unsigned char unknown[3];
};
struct PspUtilitySavedataFileData {
PSPPointer<u8> buf;
SceSize_le bufSize; // Size of the buffer pointed to by buf
SceSize_le size; // Actual file size to write / was read
s32_le unknown;
};
struct PspUtilitySavedataSizeEntry {
u64_le size;
char name[16];
};
struct PspUtilitySavedataSizeInfo {
s32_le numSecureEntries;
s32_le numNormalEntries;
PSPPointer<PspUtilitySavedataSizeEntry> secureEntries;
PSPPointer<PspUtilitySavedataSizeEntry> normalEntries;
s32_le sectorSize;
s32_le freeSectors;
s32_le freeKB;
char freeString[8];
s32_le neededKB;
char neededString[8];
s32_le overwriteKB;
char overwriteString[8];
};
struct SceUtilitySavedataIdListEntry
{
s32_le st_mode;
ScePspDateTime st_ctime;
ScePspDateTime st_atime;
ScePspDateTime st_mtime;
SceUtilitySavedataSaveName name;
};
struct SceUtilitySavedataIdListInfo
{
s32_le maxCount;
s32_le resultCount;
PSPPointer<SceUtilitySavedataIdListEntry> entries;
};
struct SceUtilitySavedataFileListEntry
{
s32_le st_mode;
u32_le st_unk0;
u64_le st_size;
ScePspDateTime st_ctime;
ScePspDateTime st_atime;
ScePspDateTime st_mtime;
char name[16];
};
struct SceUtilitySavedataFileListInfo
{
u32_le maxSecureEntries;
u32_le maxNormalEntries;
u32_le maxSystemEntries;
u32_le resultNumSecureEntries;
u32_le resultNumNormalEntries;
u32_le resultNumSystemEntries;
PSPPointer<SceUtilitySavedataFileListEntry> secureEntries;
PSPPointer<SceUtilitySavedataFileListEntry> normalEntries;
PSPPointer<SceUtilitySavedataFileListEntry> systemEntries;
};
struct SceUtilitySavedataMsFreeInfo
{
s32_le clusterSize;
s32_le freeClusters;
s32_le freeSpaceKB;
char freeSpaceStr[8];
};
struct SceUtilitySavedataUsedDataInfo
{
s32_le usedClusters;
s32_le usedSpaceKB;
char usedSpaceStr[8];
s32_le usedSpace32KB;
char usedSpace32Str[8];
};
struct SceUtilitySavedataMsDataInfo
{
char gameName[13];
char pad[3];
SceUtilitySavedataSaveName saveName;
SceUtilitySavedataUsedDataInfo info;
};
// Structure to hold the parameters for the sceUtilitySavedataInitStart function.
struct SceUtilitySavedataParam
{
pspUtilityDialogCommon common;
SceUtilitySavedataType_le mode; // 0 to load, 1 to save
s32_le bind;
s32_le overwriteMode; // use 0x10 ?
/** gameName: name used from the game for saves, equal for all saves */
char gameName[13];
char unused[3];
/** saveName: name of the particular save, normally a number */
SceUtilitySavedataSaveName saveName;
PSPPointer<SceUtilitySavedataSaveName> saveNameList;
/** fileName: name of the data file of the game for example DATA.BIN */
char fileName[13];
char unused2[3];
/** pointer to a buffer that will contain data file unencrypted data */
PSPPointer<u8> dataBuf;
/** size of allocated space to dataBuf */
SceSize_le dataBufSize;
SceSize_le dataSize; // Size of the actual save data
PspUtilitySavedataSFOParam sfoParam;
PspUtilitySavedataFileData icon0FileData;
PspUtilitySavedataFileData icon1FileData;
PspUtilitySavedataFileData pic1FileData;
PspUtilitySavedataFileData snd0FileData;
PSPPointer<PspUtilitySavedataFileData> newData;
SceUtilitySavedataFocus_le focus;
s32_le abortStatus;
// Function SCE_UTILITY_SAVEDATA_TYPE_SIZES
PSPPointer<SceUtilitySavedataMsFreeInfo> msFree;
PSPPointer<SceUtilitySavedataMsDataInfo> msData;
PSPPointer<SceUtilitySavedataUsedDataInfo> utilityData;
u8 key[16];
u32_le secureVersion;
s32_le multiStatus;
// Function 11 LIST
PSPPointer<SceUtilitySavedataIdListInfo> idList;
// Function 12 FILES
PSPPointer<SceUtilitySavedataFileListInfo> fileList;
// Function 22 GETSIZES
PSPPointer<PspUtilitySavedataSizeInfo> sizeInfo;
};
// Non native, this one we can reorganize as we like
struct SaveFileInfo
{
s64 size = 0;
std::string saveName;
std::string saveDir;
int idx = 0;
char title[128]{};
char saveTitle[128]{};
char saveDetail[1024]{};
bool broken = false;
tm modif_time{};
PPGeImage *texture = nullptr;
void DoState(PointerWrap &p);
};
struct SaveSFOFileListEntry {
char filename[13];
u8 hash[16];
u8 pad[3];
};
class SavedataParam
{
public:
SavedataParam();
static void Init();
std::string GetSaveFilePath(const SceUtilitySavedataParam *param, int saveId = -1) const;
std::string GetSaveFilePath(const SceUtilitySavedataParam *param, const std::string &saveDir) const;
std::string GetSaveDirName(const SceUtilitySavedataParam *param, int saveId = -1) const;
std::string GetSaveDir(const SceUtilitySavedataParam *param, int saveId = -1) const;
std::string GetSaveDir(const SceUtilitySavedataParam *param, const std::string &saveDirName) const;
bool Delete(SceUtilitySavedataParam* param, const std::string &saveDir);
int DeleteData(SceUtilitySavedataParam* param);
int Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode = true);
int Load(SceUtilitySavedataParam* param, const std::string &saveDirName, int saveId = -1, bool secureMode = true);
int GetSizes(SceUtilitySavedataParam* param);
bool GetList(SceUtilitySavedataParam* param);
int GetFilesList(SceUtilitySavedataParam* param, u32 requestAddr, const std::string &saveDirName);
bool GetSize(SceUtilitySavedataParam* param);
int GetSaveCryptMode(const SceUtilitySavedataParam *param, const std::string &saveDirName);
bool IsInSaveDataList(const std::string &saveName, int count);
std::string GetGameName(const SceUtilitySavedataParam *param) const;
std::string GetSaveName(const SceUtilitySavedataParam *param) const;
std::string GetFileName(const SceUtilitySavedataParam *param) const;
std::string GetKey(const SceUtilitySavedataParam *param) const;
bool HasKey(const SceUtilitySavedataParam *param) const;
static std::string GetSpaceText(u64 size, bool roundUp);
void SetIgnoreTextures(bool state) {
ignoreTextures_ = state;
}
int SetPspParam(SceUtilitySavedataParam* param);
SceUtilitySavedataParam *GetPspParam();
const SceUtilitySavedataParam *GetPspParam() const;
int GetFilenameCount();
const SaveFileInfo& GetFileInfo(int idx);
std::string GetFilename(int idx) const;
std::string GetSaveDir(int idx) const;
int GetSelectedSave();
void SetSelectedSave(int idx);
int GetFirstListSave();
int GetLastListSave();
int GetLatestSave();
int GetOldestSave();
int GetFirstDataSave();
int GetLastDataSave();
int GetFirstEmptySave();
int GetLastEmptySave();
int GetSaveNameIndex(const SceUtilitySavedataParam *param);
bool WouldHaveMultiSaveName(const SceUtilitySavedataParam *param);
void ClearSFOCache();
void DoState(PointerWrap &p);
private:
void Clear();
void SetFileInfo(int idx, PSPFileInfo &info, const std::string &saveName, const std::string &saveDir = "");
void SetFileInfo(SaveFileInfo &saveInfo, PSPFileInfo &info, const std::string &saveName, const std::string &saveDir = "");
void ClearFileInfo(SaveFileInfo &saveInfo, const std::string &saveName);
PSPFileInfo GetSaveInfo(const std::string &saveDir);
int LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode);
u32 LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, const u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone);
u32 LoadNotCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize);
bool LoadSFO(SceUtilitySavedataParam *param, const std::string& dirPath);
void LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData);
int DecryptData(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, const u8 *cryptkey, const u8 *expectedHash);
int EncryptData(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, u8 *hash, const u8 *cryptkey);
int UpdateHash(u8* sfoData, int sfoSize, int sfoDataParamsOffset, int encryptmode);
// data must be zero-padded from len to alignedLen (which should be the next multiply of 16)!
int BuildHash(uint8_t *output, const uint8_t *data, unsigned int len, unsigned int alignedLen, int mode, const uint8_t *cryptkey);
int DetermineCryptMode(const SceUtilitySavedataParam *param) const;
bool UsesSecureVersion(const SceUtilitySavedataParam *param) const;
bool MissingRequiredKey(const SceUtilitySavedataParam *param) const;
std::vector<SaveSFOFileListEntry> GetSFOEntries(const std::string &dirPath);
std::set<std::string> GetSecureFileNames(const std::string &dirPath);
bool GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]);
std::shared_ptr<ParamSFOData> LoadCachedSFO(const std::string &path, bool orCreate = false);
SceUtilitySavedataParam* pspParam = nullptr;
int selectedSave = 0;
SaveFileInfo *saveDataList = nullptr;
SaveFileInfo *noSaveIcon = nullptr;
int saveDataListCount = 0;
int saveNameListDataCount = 0;
bool ignoreTextures_ = false;
// Cleared before returning to PSP, no need to save state.
std::mutex cacheLock_;
std::unordered_map<std::string, std::shared_ptr<ParamSFOData>> sfoCache_;
};