CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/Dialog/SavedataParam.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <algorithm>18#include <memory>19#include "Common/Log.h"20#include "Common/Data/Text/I18n.h"21#include "Common/Data/Format/ZIMLoad.h"22#include "Common/Serialize/Serializer.h"23#include "Common/Serialize/SerializeFuncs.h"24#include "Common/System/OSD.h"25#include "Common/StringUtils.h"26#include "Core/Config.h"27#include "Core/Reporting.h"28#include "Core/System.h"29#include "Core/Debugger/MemBlockInfo.h"30#include "Core/Dialog/SavedataParam.h"31#include "Core/Dialog/PSPSaveDialog.h"32#include "Core/FileSystems/MetaFileSystem.h"33#include "Core/HLE/sceIo.h"34#include "Core/HLE/sceKernelMemory.h"35#include "Core/HLE/sceChnnlsv.h"36#include "Core/ELF/ParamSFO.h"37#include "Core/HW/MemoryStick.h"38#include "Core/Util/PPGeDraw.h"3940static const std::string ICON0_FILENAME = "ICON0.PNG";41static const std::string ICON1_FILENAME = "ICON1.PMF";42static const std::string PIC1_FILENAME = "PIC1.PNG";43static const std::string SND0_FILENAME = "SND0.AT3";44static const std::string SFO_FILENAME = "PARAM.SFO";4546static const int FILE_LIST_COUNT_MAX = 99;47static const u32 FILE_LIST_TOTAL_SIZE = sizeof(SaveSFOFileListEntry) * FILE_LIST_COUNT_MAX;4849static const std::string savePath = "ms0:/PSP/SAVEDATA/";5051namespace52{53int getSizeNormalized(int size)54{55int sizeCluster = (int)MemoryStick_SectorSize();56return ((int)((size + sizeCluster - 1) / sizeCluster)) * sizeCluster;57}5859void SetStringFromSFO(ParamSFOData &sfoFile, const char *name, char *str, int strLength)60{61std::string value = sfoFile.GetValueString(name);62truncate_cpy(str, strLength, value.c_str());63}6465bool ReadPSPFile(const std::string &filename, u8 **data, s64 dataSize, s64 *readSize)66{67int handle = pspFileSystem.OpenFile(filename, FILEACCESS_READ);68if (handle < 0)69return false;7071if (dataSize == -1) {72// Determine the size through seeking instead of querying.73pspFileSystem.SeekFile(handle, 0, FILEMOVE_END);74dataSize = pspFileSystem.GetSeekPos(handle);75pspFileSystem.SeekFile(handle, 0, FILEMOVE_BEGIN);7677*data = new u8[(size_t)dataSize];78}7980size_t result = pspFileSystem.ReadFile(handle, *data, dataSize);81pspFileSystem.CloseFile(handle);82if(readSize)83*readSize = result;8485return result != 0;86}8788bool WritePSPFile(const std::string &filename, const u8 *data, SceSize dataSize)89{90int handle = pspFileSystem.OpenFile(filename, (FileAccess)(FILEACCESS_WRITE | FILEACCESS_CREATE | FILEACCESS_TRUNCATE));91if (handle < 0)92return false;9394size_t result = pspFileSystem.WriteFile(handle, data, dataSize);95pspFileSystem.CloseFile(handle);9697return result == dataSize;98}99100PSPFileInfo FileFromListing(const std::vector<PSPFileInfo> &listing, const std::string &filename) {101for (const PSPFileInfo &sub : listing) {102if (sub.name == filename)103return sub;104}105106PSPFileInfo info;107info.name = filename;108info.exists = false;109return info;110}111112bool PSPMatch(std::string_view text, std::string_view regexp) {113if (text.empty() && regexp.empty())114return true;115else if (regexp == "*")116return true;117else if (text.empty())118return false;119else if (regexp.empty())120return false;121else if (regexp == "?" && text.length() == 1)122return true;123else if (text == regexp)124return true;125else if (regexp.data()[0] == '*')126{127bool res = PSPMatch(text.substr(1),regexp.substr(1));128if(!res)129res = PSPMatch(text.substr(1),regexp);130return res;131}132else if (regexp.data()[0] == '?')133{134return PSPMatch(text.substr(1),regexp.substr(1));135}136else if (regexp.data()[0] == text.data()[0])137{138return PSPMatch(text.substr(1),regexp.substr(1));139}140141return false;142}143144int align16(int address)145{146return (address + 15) & ~15;147}148149int GetSDKMainVersion(int sdkVersion)150{151if(sdkVersion > 0x0307FFFF)152return 6;153if(sdkVersion > 0x0300FFFF)154return 5;155if(sdkVersion > 0x0206FFFF)156return 4;157if(sdkVersion > 0x0205FFFF)158return 3;159if(sdkVersion >= 0x02000000)160return 2;161if(sdkVersion >= 0x01000000)162return 1;163return 0;164};165}166167void SaveFileInfo::DoState(PointerWrap &p)168{169auto s = p.Section("SaveFileInfo", 1, 2);170if (!s)171return;172173Do(p, size);174Do(p, saveName);175Do(p, idx);176177DoArray(p, title, sizeof(title));178DoArray(p, saveTitle, sizeof(saveTitle));179DoArray(p, saveDetail, sizeof(saveDetail));180181Do(p, modif_time);182183if (s <= 1) {184u32 textureData;185int textureWidth;186int textureHeight;187Do(p, textureData);188Do(p, textureWidth);189Do(p, textureHeight);190191if (textureData != 0) {192// Must be MODE_READ.193texture = new PPGeImage("");194texture->CompatLoad(textureData, textureWidth, textureHeight);195}196} else {197bool hasTexture = texture != NULL;198Do(p, hasTexture);199if (hasTexture) {200if (p.mode == p.MODE_READ) {201delete texture;202texture = new PPGeImage("");203}204texture->DoState(p);205}206}207}208209SavedataParam::SavedataParam() { }210211void SavedataParam::Init()212{213if (!pspFileSystem.GetFileInfo(savePath).exists)214{215pspFileSystem.MkDir(savePath);216}217// Create a nomedia file to hide save icons form Android image viewer218#if PPSSPP_PLATFORM(ANDROID)219int handle = pspFileSystem.OpenFile(savePath + ".nomedia", (FileAccess)(FILEACCESS_CREATE | FILEACCESS_WRITE), 0);220if (handle >= 0) {221pspFileSystem.CloseFile(handle);222} else {223INFO_LOG(Log::IO, "Failed to create .nomedia file (might be ok if it already exists)");224}225#endif226}227228std::string SavedataParam::GetSaveDirName(const SceUtilitySavedataParam *param, int saveId) const229{230if (!param) {231return "";232}233234if (saveId >= 0 && saveNameListDataCount > 0) // if user selection, use it235return GetFilename(saveId);236else237return GetSaveName(param);238}239240std::string SavedataParam::GetSaveDir(const SceUtilitySavedataParam *param, const std::string &saveDirName) const241{242if (!param) {243return "";244}245246return GetGameName(param) + saveDirName;247}248249std::string SavedataParam::GetSaveDir(const SceUtilitySavedataParam *param, int saveId) const250{251return GetSaveDir(param, GetSaveDirName(param, saveId));252}253254std::string SavedataParam::GetSaveFilePath(const SceUtilitySavedataParam *param, const std::string &saveDir) const255{256if (!param) {257return "";258}259260if (!saveDir.size())261return "";262263return savePath + saveDir;264}265266std::string SavedataParam::GetSaveFilePath(const SceUtilitySavedataParam *param, int saveId) const267{268return GetSaveFilePath(param, GetSaveDir(param, saveId));269}270271inline static std::string FixedToString(const char *str, size_t n)272{273if (!str) {274return std::string();275} else {276return std::string(str, strnlen(str, n));277}278}279280std::string SavedataParam::GetGameName(const SceUtilitySavedataParam *param) const281{282return FixedToString(param->gameName, ARRAY_SIZE(param->gameName));283}284285std::string SavedataParam::GetSaveName(const SceUtilitySavedataParam *param) const286{287const std::string saveName = FixedToString(param->saveName, ARRAY_SIZE(param->saveName));288if (saveName == "<>")289return "";290return saveName;291}292293std::string SavedataParam::GetFileName(const SceUtilitySavedataParam *param) const294{295return FixedToString(param->fileName, ARRAY_SIZE(param->fileName));296}297298std::string SavedataParam::GetKey(const SceUtilitySavedataParam *param) const299{300static const char* const lut = "0123456789ABCDEF";301302std::string output;303if (HasKey(param))304{305output.reserve(2 * sizeof(param->key));306for (size_t i = 0; i < sizeof(param->key); ++i)307{308const unsigned char c = param->key[i];309output.push_back(lut[c >> 4]);310output.push_back(lut[c & 15]);311}312}313return output;314}315316bool SavedataParam::HasKey(const SceUtilitySavedataParam *param) const317{318for (size_t i = 0; i < ARRAY_SIZE(param->key); ++i)319{320if (param->key[i] != 0)321return true;322}323return false;324}325326bool SavedataParam::Delete(SceUtilitySavedataParam* param, int saveId) {327if (!param) {328return false;329}330331// Sanity check, preventing full delete of savedata/ in MGS PW demo (!)332if (!strlen(param->gameName) && param->mode != SCE_UTILITY_SAVEDATA_TYPE_LISTALLDELETE) {333ERROR_LOG(Log::sceUtility, "Bad param with gameName empty - cannot delete save directory");334return false;335}336337std::string dirPath = GetSaveFilePath(param, GetSaveDir(saveId));338if (dirPath.size() == 0) {339ERROR_LOG(Log::sceUtility, "GetSaveFilePath (%.*s) returned empty - cannot delete save directory. Might already be deleted?", (int)sizeof(param->gameName), param->gameName);340return false;341}342343if (!pspFileSystem.GetFileInfo(dirPath).exists) {344return false;345}346347ClearSFOCache();348pspFileSystem.RmDir(dirPath);349return true;350}351352int SavedataParam::DeleteData(SceUtilitySavedataParam* param) {353if (!param) {354return SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND;355}356357std::string subFolder = GetGameName(param) + GetSaveName(param);358std::string fileName = GetFileName(param);359std::string dirPath = savePath + subFolder;360std::string filePath = dirPath + "/" + fileName;361std::string sfoPath = dirPath + "/" + SFO_FILENAME;362363if (!pspFileSystem.GetFileInfo(dirPath).exists) {364return SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;365}366367if (!pspFileSystem.GetFileInfo(sfoPath).exists)368return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;369370if (!fileName.empty() && !pspFileSystem.GetFileInfo(filePath).exists) {371return SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND;372}373374if (fileName.empty()) {375return 0;376}377378if (!subFolder.size()) {379ERROR_LOG(Log::sceUtility, "Bad subfolder, ignoring delete of %s", filePath.c_str());380return 0;381}382383ClearSFOCache();384pspFileSystem.RemoveFile(filePath);385386// Update PARAM.SFO to remove the file, if it was in the list.387std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfoPath);388if (sfoFile) {389// Note: do not update values such as TITLE in this operation.390u32 fileListSize = 0;391SaveSFOFileListEntry *fileList = (SaveSFOFileListEntry *)sfoFile->GetValueData("SAVEDATA_FILE_LIST", &fileListSize);392size_t fileListCount = fileListSize / sizeof(SaveSFOFileListEntry);393bool changed = false;394for (size_t i = 0; i < fileListCount; ++i) {395if (strncmp(fileList[i].filename, fileName.c_str(), sizeof(fileList[i].filename)) != 0)396continue;397398memset(fileList[i].filename, 0, sizeof(fileList[i].filename));399memset(fileList[i].hash, 0, sizeof(fileList[i].hash));400changed = true;401break;402}403404if (changed) {405auto updatedList = std::make_unique<u8[]> (fileListSize);406memcpy(updatedList.get(), fileList, fileListSize);407sfoFile->SetValue("SAVEDATA_FILE_LIST", updatedList.get(), fileListSize, (int)FILE_LIST_TOTAL_SIZE);408409u8 *sfoData;410size_t sfoSize;411sfoFile->WriteSFO(&sfoData, &sfoSize);412413ClearSFOCache();414WritePSPFile(sfoPath, sfoData, (SceSize)sfoSize);415delete[] sfoData;416}417}418419return 0;420}421422int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode) {423if (!param) {424return SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE;425}426if (param->dataSize > param->dataBufSize) {427ERROR_LOG_REPORT(Log::sceUtility, "Savedata buffer overflow: %d / %d", param->dataSize, param->dataBufSize);428return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;429}430auto validateSize = [](const PspUtilitySavedataFileData &data) {431if (data.buf.IsValid() && data.bufSize < data.size) {432ERROR_LOG_REPORT(Log::sceUtility, "Savedata subdata buffer overflow: %d / %d", data.size, data.bufSize);433return false;434}435return true;436};437if (!validateSize(param->icon0FileData) || !validateSize(param->icon1FileData) || !validateSize(param->pic1FileData) || !validateSize(param->snd0FileData)) {438return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;439}440441if (param->secureVersion > 3) {442ERROR_LOG_REPORT(Log::sceUtility, "Savedata version requested on save: %d", param->secureVersion);443return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM;444} else if (param->secureVersion != 0) {445if (param->secureVersion != 1 && !HasKey(param) && secureMode) {446ERROR_LOG_REPORT(Log::sceUtility, "Savedata version with missing key on save: %d", param->secureVersion);447return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM;448}449WARN_LOG(Log::sceUtility, "Savedata version requested on save: %d", param->secureVersion);450}451452std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));453454if (!pspFileSystem.GetFileInfo(dirPath).exists) {455if (!pspFileSystem.MkDir(dirPath)) {456auto err = GetI18NCategory(I18NCat::ERRORS);457g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Unable to write savedata, disk may be full"));458}459}460461u8* cryptedData = 0;462int cryptedSize = 0;463u8 cryptedHash[0x10]{};464// Encrypt save.465// TODO: Is this the correct difference between MAKEDATA and MAKEDATASECURE?466if (param->dataBuf.IsValid() && g_Config.bEncryptSave && secureMode)467{468cryptedSize = param->dataSize;469if (cryptedSize == 0 || (SceSize)cryptedSize > param->dataBufSize) {470ERROR_LOG(Log::sceUtility, "Bad cryptedSize %d", cryptedSize);471cryptedSize = param->dataBufSize; // fallback, should never use this472}473u8 *data_ = param->dataBuf;474475int aligned_len = align16(cryptedSize);476if (aligned_len != cryptedSize) {477WARN_LOG(Log::sceUtility, "cryptedSize unaligned: %d (%d)", cryptedSize, cryptedSize & 15);478}479480cryptedData = new u8[aligned_len + 0x10]();481memcpy(cryptedData, data_, cryptedSize);482// EncryptData will do a memmove to make room for the key in front.483// Technically we could just copy it into place here to avoid that.484485int decryptMode = DetermineCryptMode(param);486bool hasKey = decryptMode > 1;487if (hasKey && !HasKey(param)) {488delete[] cryptedData;489return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM;490}491492if (EncryptData(decryptMode, cryptedData, &cryptedSize, &aligned_len, cryptedHash, (hasKey ? param->key : 0)) != 0) {493auto err = GetI18NCategory(I18NCat::ERRORS);494g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Save encryption failed. This save won't work on real PSP"), 6.0f);495ERROR_LOG(Log::sceUtility,"Save encryption failed. This save won't work on real PSP");496delete[] cryptedData;497cryptedData = 0;498}499}500501// SAVE PARAM.SFO502std::string sfopath = dirPath + "/" + SFO_FILENAME;503std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfopath, true);504505// This was added in #18430, see below.506bool subWrite = param->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE || param->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA;507bool wasCrypted = GetSaveCryptMode(param, saveDirName) != 0;508509// Update values. NOTE! #18430 made this conditional on !subWrite, but this is not correct, as it causes #18687.510// So now we do a hacky trick and just check for a valid title before we proceed with updating the sfoFile.511if (strnlen(param->sfoParam.title, sizeof(param->sfoParam.title)) > 0) {512sfoFile->SetValue("TITLE", param->sfoParam.title, 128);513sfoFile->SetValue("SAVEDATA_TITLE", param->sfoParam.savedataTitle, 128);514sfoFile->SetValue("SAVEDATA_DETAIL", param->sfoParam.detail, 1024);515sfoFile->SetValue("PARENTAL_LEVEL", param->sfoParam.parentalLevel, 4);516sfoFile->SetValue("CATEGORY", "MS", 4);517sfoFile->SetValue("SAVEDATA_DIRECTORY", GetSaveDir(param, saveDirName), 64);518}519520// Always write and update the file list.521// For each file, 13 bytes for filename, 16 bytes for file hash (0 in PPSSPP), 3 byte for padding522u32 tmpDataSize = 0;523SaveSFOFileListEntry *tmpDataOrig = (SaveSFOFileListEntry *)sfoFile->GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);524SaveSFOFileListEntry *updatedList = new SaveSFOFileListEntry[FILE_LIST_COUNT_MAX];525if (tmpDataSize != 0)526memcpy(updatedList, tmpDataOrig, std::min(tmpDataSize, FILE_LIST_TOTAL_SIZE));527if (tmpDataSize < FILE_LIST_TOTAL_SIZE)528memset(updatedList + tmpDataSize, 0, FILE_LIST_TOTAL_SIZE - tmpDataSize);529// Leave a hash there and unchanged if it was already there.530if (secureMode && param->dataBuf.IsValid()) {531const std::string saveFilename = GetFileName(param);532for (auto entry = updatedList; entry < updatedList + FILE_LIST_COUNT_MAX; ++entry) {533if (entry->filename[0] != '\0') {534if (strncmp(entry->filename, saveFilename.c_str(), sizeof(entry->filename)) != 0)535continue;536}537538snprintf(entry->filename, sizeof(entry->filename), "%s", saveFilename.c_str());539memcpy(entry->hash, cryptedHash, 16);540break;541}542}543544sfoFile->SetValue("SAVEDATA_FILE_LIST", (u8 *)updatedList, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE);545delete[] updatedList;546547// Init param with 0. This will be used to detect crypted save or not on loading548u8 zeroes[128]{};549sfoFile->SetValue("SAVEDATA_PARAMS", zeroes, 128, 128);550551u8 *sfoData;552size_t sfoSize;553sfoFile->WriteSFO(&sfoData, &sfoSize);554555// Calc SFO hash for PSP.556if (cryptedData != 0 || (subWrite && wasCrypted)) {557int offset = sfoFile->GetDataOffset(sfoData, "SAVEDATA_PARAMS");558if (offset >= 0)559UpdateHash(sfoData, (int)sfoSize, offset, DetermineCryptMode(param));560}561562ClearSFOCache();563WritePSPFile(sfopath, sfoData, (SceSize)sfoSize);564delete[] sfoData;565sfoData = nullptr;566567if(param->dataBuf.IsValid()) // Can launch save without save data in mode 13568{569std::string fileName = GetFileName(param);570std::string filePath = dirPath + "/" + fileName;571u8 *data_ = 0;572SceSize saveSize = 0;573if(cryptedData == 0) // Save decrypted data574{575saveSize = param->dataSize;576if(saveSize == 0 || saveSize > param->dataBufSize)577saveSize = param->dataBufSize; // fallback, should never use this578579data_ = param->dataBuf;580}581else582{583data_ = cryptedData;584saveSize = cryptedSize;585}586587INFO_LOG(Log::sceUtility,"Saving file with size %u in %s",saveSize,filePath.c_str());588589// copy back save name in request590strncpy(param->saveName, saveDirName.c_str(), 20);591592if (!fileName.empty()) {593if (!WritePSPFile(filePath, data_, saveSize)) {594ERROR_LOG(Log::sceUtility, "Error writing file %s", filePath.c_str());595delete[] cryptedData;596return SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE;597}598}599delete[] cryptedData;600}601602// SAVE ICON0603if (param->icon0FileData.buf.IsValid())604{605std::string icon0path = dirPath + "/" + ICON0_FILENAME;606WritePSPFile(icon0path, param->icon0FileData.buf, param->icon0FileData.size);607}608// SAVE ICON1609if (param->icon1FileData.buf.IsValid())610{611std::string icon1path = dirPath + "/" + ICON1_FILENAME;612WritePSPFile(icon1path, param->icon1FileData.buf, param->icon1FileData.size);613}614// SAVE PIC1615if (param->pic1FileData.buf.IsValid())616{617std::string pic1path = dirPath + "/" + PIC1_FILENAME;618WritePSPFile(pic1path, param->pic1FileData.buf, param->pic1FileData.size);619}620// Save SND621if (param->snd0FileData.buf.IsValid())622{623std::string snd0path = dirPath + "/" + SND0_FILENAME;624WritePSPFile(snd0path, param->snd0FileData.buf, param->snd0FileData.size);625}626return 0;627}628629int SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &saveDirName, int saveId, bool secureMode) {630if (!param) {631return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;632}633634bool isRWMode = param->mode == SCE_UTILITY_SAVEDATA_TYPE_READDATA || param->mode == SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE;635636std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));637std::string fileName = GetFileName(param);638std::string filePath = dirPath + "/" + fileName;639640if (!pspFileSystem.GetFileInfo(dirPath).exists) {641return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA : SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;642}643644if (!fileName.empty() && !pspFileSystem.GetFileInfo(filePath).exists) {645return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND : SCE_UTILITY_SAVEDATA_ERROR_LOAD_FILE_NOT_FOUND;646}647648// If it wasn't zero, force to zero before loading and especially in case of error.649// This isn't reset if the path doesn't even exist.650param->dataSize = 0;651int result = LoadSaveData(param, saveDirName, dirPath, secureMode);652if (result != 0)653return result;654655// Load sfo656if (!LoadSFO(param, dirPath)) {657WARN_LOG(Log::sceUtility, "Load: Failed to load SFO from %s", dirPath.c_str());658return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN : SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN;659}660661// Don't know what it is, but PSP always respond this and this unlock some game662param->bind = 1021;663664// Load other files, seems these are required by some games, e.g. Fushigi no Dungeon Fuurai no Shiren 4 Plus.665666// Load ICON0.PNG667LoadFile(dirPath, ICON0_FILENAME, ¶m->icon0FileData);668// Load ICON1.PNG669LoadFile(dirPath, ICON1_FILENAME, ¶m->icon1FileData);670// Load PIC1.PNG671LoadFile(dirPath, PIC1_FILENAME, ¶m->pic1FileData);672// Load SND0.AT3673LoadFile(dirPath, SND0_FILENAME, ¶m->snd0FileData);674675return 0;676}677678int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string &dirPath, bool secureMode) {679if (param->secureVersion > 3) {680ERROR_LOG_REPORT(Log::sceUtility, "Savedata version requested: %d", param->secureVersion);681return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;682} else if (param->secureVersion != 0) {683if (param->secureVersion != 1 && !HasKey(param) && secureMode) {684ERROR_LOG_REPORT(Log::sceUtility, "Savedata version with missing key: %d", param->secureVersion);685return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;686}687WARN_LOG_REPORT(Log::sceUtility, "Savedata version requested: %d", param->secureVersion);688}689690std::string filename = GetFileName(param);691std::string filePath = dirPath + "/" + filename;692// Blank filename always means success, if secureVersion was correct.693if (filename.empty())694return 0;695696s64 readSize;697INFO_LOG(Log::sceUtility, "Loading file with size %u in %s", param->dataBufSize, filePath.c_str());698u8 *saveData = nullptr;699int saveSize = -1;700if (!ReadPSPFile(filePath, &saveData, saveSize, &readSize)) {701ERROR_LOG(Log::sceUtility,"Error reading file %s",filePath.c_str());702return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;703}704saveSize = (int)readSize;705706// copy back save name in request707strncpy(param->saveName, saveDirName.c_str(), 20);708709int prevCryptMode = GetSaveCryptMode(param, saveDirName);710bool isCrypted = prevCryptMode != 0 && secureMode;711bool saveDone = false;712u32 loadedSize = 0;713if (isCrypted) {714if (DetermineCryptMode(param) > 1 && !HasKey(param)) {715return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;716}717u8 hash[16];718bool hasExpectedHash = GetExpectedHash(dirPath, filename, hash);719loadedSize = LoadCryptedSave(param, param->dataBuf, saveData, saveSize, prevCryptMode, hasExpectedHash ? hash : nullptr, saveDone);720// TODO: Should return SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN here if !saveDone.721}722if (!saveDone) {723loadedSize = LoadNotCryptedSave(param, param->dataBuf, saveData, saveSize);724}725delete[] saveData;726727// Ignore error codes.728if (loadedSize != 0 && (loadedSize & 0x80000000) == 0) {729std::string tag = "LoadSaveData/" + filePath;730NotifyMemInfo(MemBlockFlags::WRITE, param->dataBuf.ptr, loadedSize, tag.c_str(), tag.size());731}732733if ((loadedSize & 0x80000000) != 0)734return loadedSize;735736param->dataSize = (SceSize)saveSize;737return 0;738}739740int SavedataParam::DetermineCryptMode(const SceUtilitySavedataParam *param) const {741int decryptMode = 1;742if (param->secureVersion == 1) {743decryptMode = 1;744} else if (param->secureVersion == 2) {745decryptMode = 3;746} else if (param->secureVersion == 3) {747decryptMode = GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 1;748} else if (HasKey(param)) {749// TODO: This should ignore HasKey(), which would trigger errors. Not doing that yet to play it safe.750decryptMode = GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 3;751}752return decryptMode;753}754755u32 SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, const u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone) {756int orig_size = saveSize;757int align_len = align16(saveSize);758u8 *data_base = new u8[align_len];759u8 *cryptKey = new u8[0x10];760761int decryptMode = DetermineCryptMode(param);762const int detectedMode = decryptMode;763bool hasKey;764765auto resetData = [&](int mode) {766saveSize = orig_size;767align_len = align16(saveSize);768hasKey = mode > 1;769770if (hasKey) {771memcpy(cryptKey, param->key, 0x10);772}773memcpy(data_base, saveData, saveSize);774memset(data_base + saveSize, 0, align_len - saveSize);775};776resetData(decryptMode);777778if (decryptMode != prevCryptMode) {779if (prevCryptMode == 1 && param->key[0] == 0) {780// Backwards compat for a bug we used to have.781WARN_LOG(Log::sceUtility, "Savedata loading with hashmode %d instead of detected %d", prevCryptMode, decryptMode);782decryptMode = prevCryptMode;783784// Don't notify the user if we're not going to upgrade the save.785if (!g_Config.bEncryptSave) {786auto di = GetI18NCategory(I18NCat::DIALOG);787g_OSD.Show(OSDType::MESSAGE_WARNING, di->T("When you save, it will load on a PSP, but not an older PPSSPP"), 6.0f);788g_OSD.Show(OSDType::MESSAGE_WARNING, di->T("Old savedata detected"), 6.0f);789}790} else {791if (decryptMode == 5 && prevCryptMode == 3) {792WARN_LOG(Log::sceUtility, "Savedata loading with detected hashmode %d instead of file's %d", decryptMode, prevCryptMode);793} else {794WARN_LOG_REPORT(Log::sceUtility, "Savedata loading with detected hashmode %d instead of file's %d", decryptMode, prevCryptMode);795}796if (g_Config.bSavedataUpgrade) {797decryptMode = prevCryptMode;798auto di = GetI18NCategory(I18NCat::DIALOG);799g_OSD.Show(OSDType::MESSAGE_WARNING, di->T("When you save, it will not work on outdated PSP Firmware anymore"), 6.0f);800g_OSD.Show(OSDType::MESSAGE_WARNING, di->T("Old savedata detected"), 6.0f);801}802}803hasKey = decryptMode > 1;804}805806int err = DecryptData(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);807// Perhaps the file had the wrong mode....808if (err != 0 && detectedMode != decryptMode) {809resetData(detectedMode);810err = DecryptData(detectedMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);811}812// TODO: Should return an error, but let's just try with a bad hash.813if (err != 0 && expectedHash != nullptr) {814WARN_LOG(Log::sceUtility, "Incorrect hash on save data, likely corrupt");815resetData(decryptMode);816err = DecryptData(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, nullptr);817}818819u32 sz = 0;820if (err == 0) {821if (param->dataBuf.IsValid()) {822if ((u32)saveSize > param->dataBufSize || !Memory::IsValidRange(param->dataBuf.ptr, saveSize)) {823sz = SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN;824} else {825sz = (u32)saveSize;826memcpy(data, data_base, sz);827}828}829saveDone = true;830}831delete[] data_base;832delete[] cryptKey;833834return sz;835}836837u32 SavedataParam::LoadNotCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize) {838if (param->dataBuf.IsValid()) {839if ((u32)saveSize > param->dataBufSize || !Memory::IsValidRange(param->dataBuf.ptr, saveSize)) {840return SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN;841}842memcpy(data, saveData, saveSize);843return saveSize;844}845return 0;846}847848bool SavedataParam::LoadSFO(SceUtilitySavedataParam *param, const std::string& dirPath) {849std::string sfopath = dirPath + "/" + SFO_FILENAME;850std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfopath);851if (sfoFile) {852// copy back info in request853strncpy(param->sfoParam.title, sfoFile->GetValueString("TITLE").c_str(), 128);854strncpy(param->sfoParam.savedataTitle, sfoFile->GetValueString("SAVEDATA_TITLE").c_str(), 128);855strncpy(param->sfoParam.detail, sfoFile->GetValueString("SAVEDATA_DETAIL").c_str(), 1024);856param->sfoParam.parentalLevel = sfoFile->GetValueInt("PARENTAL_LEVEL");857return true;858}859return false;860}861862std::vector<SaveSFOFileListEntry> SavedataParam::GetSFOEntries(const std::string &dirPath) {863std::vector<SaveSFOFileListEntry> result;864const std::string sfoPath = dirPath + "/" + SFO_FILENAME;865866std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfoPath);867if (!sfoFile) {868return result;869}870871u32 sfoFileListSize = 0;872SaveSFOFileListEntry *sfoFileList = (SaveSFOFileListEntry *)sfoFile->GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);873const u32 count = std::min((u32)FILE_LIST_COUNT_MAX, sfoFileListSize / (u32)sizeof(SaveSFOFileListEntry));874875for (u32 i = 0; i < count; ++i) {876if (sfoFileList[i].filename[0] != '\0')877result.push_back(sfoFileList[i]);878}879880return result;881}882883std::set<std::string> SavedataParam::GetSecureFileNames(const std::string &dirPath) {884auto entries = GetSFOEntries(dirPath);885886std::set<std::string> secureFileNames;887for (const auto &entry : entries) {888char temp[14];889truncate_cpy(temp, entry.filename);890secureFileNames.insert(temp);891}892return secureFileNames;893}894895bool SavedataParam::GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]) {896auto entries = GetSFOEntries(dirPath);897898for (auto entry : entries) {899if (strncmp(entry.filename, filename.c_str(), sizeof(entry.filename)) == 0) {900memcpy(hash, entry.hash, sizeof(entry.hash));901return true;902}903}904return false;905}906907void SavedataParam::LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData) {908std::string filePath = dirPath + "/" + filename;909if (!fileData->buf.IsValid())910return;911912u8 *buf = fileData->buf;913u32 size = Memory::ValidSize(fileData->buf.ptr, fileData->bufSize);914s64 readSize = -1;915if (ReadPSPFile(filePath, &buf, size, &readSize)) {916fileData->size = readSize;917const std::string tag = "SavedataLoad/" + filePath;918NotifyMemInfo(MemBlockFlags::WRITE, fileData->buf.ptr, fileData->size, tag.c_str(), tag.size());919INFO_LOG(Log::sceUtility, "Loaded subfile %s (size: %d bytes) into %08x", filePath.c_str(), fileData->size, fileData->buf.ptr);920} else {921WARN_LOG(Log::sceUtility, "Failed to load subfile %s into %08x", filePath.c_str(), fileData->buf.ptr);922}923}924925// Note: The work is done in-place, hence the memmove etc.926int SavedataParam::EncryptData(unsigned int mode,927unsigned char *data,928int *dataLen,929int *alignedLen,930unsigned char *hash,931unsigned char *cryptkey)932{933pspChnnlsvContext1 ctx1{};934pspChnnlsvContext2 ctx2{};935936INFO_LOG(Log::sceUtility, "EncryptData(mode=%d, *dataLen=%d, *alignedLen=%d)", mode, *dataLen, *alignedLen);937938/* Make room for the IV in front of the data. */939memmove(data + 0x10, data, *alignedLen);940941/* Set up buffers */942memset(hash, 0, 0x10);943944// Zero out the IV before we begin.945memset(data, 0, 0x10);946947/* Build the 0x10-byte IV and setup encryption */948if (sceSdCreateList_(ctx2, mode, 1, data, cryptkey) < 0)949return -1;950if (sceSdSetIndex_(ctx1, mode) < 0)951return -2;952if (sceSdRemoveValue_(ctx1, data, 0x10) < 0)953return -3;954if (sceSdSetMember_(ctx2, data + 0x10, *alignedLen) < 0)955return -4;956957/* Clear any extra bytes left from the previous steps */958memset(data + 0x10 + *dataLen, 0, *alignedLen - *dataLen);959960/* Encrypt the data */961if (sceSdRemoveValue_(ctx1, data + 0x10, *alignedLen) < 0)962return -5;963964/* Verify encryption */965if (sceSdCleanList_(ctx2) < 0)966return -6;967968/* Build the file hash from this PSP */969if (sceSdGetLastIndex_(ctx1, hash, cryptkey) < 0)970return -7;971972/* Adjust sizes to account for IV */973*alignedLen += 0x10;974*dataLen += 0x10;975976/* All done */977return 0;978}979980// Note: The work is done in-place, hence the memmove etc.981int SavedataParam::DecryptData(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash) {982pspChnnlsvContext1 ctx1{};983pspChnnlsvContext2 ctx2{};984985/* Need a 16-byte IV plus some data */986if (*alignedLen <= 0x10)987return -1;988*dataLen -= 0x10;989*alignedLen -= 0x10;990991/* Perform the magic */992if (sceSdSetIndex_(ctx1, mode) < 0)993return -2;994if (sceSdCreateList_(ctx2, mode, 2, data, cryptkey) < 0)995return -3;996if (sceSdRemoveValue_(ctx1, data, 0x10) < 0)997return -4;998if (sceSdRemoveValue_(ctx1, data + 0x10, *alignedLen) < 0)999return -5;1000if (sceSdSetMember_(ctx2, data + 0x10, *alignedLen) < 0)1001return -6;10021003/* Verify that it decrypted correctly */1004if (sceSdCleanList_(ctx2) < 0)1005return -7;10061007if (expectedHash) {1008u8 hash[16];1009if (sceSdGetLastIndex_(ctx1, hash, cryptkey) < 0)1010return -7;1011if (memcmp(hash, expectedHash, sizeof(hash)) != 0)1012return -8;1013}10141015/* The decrypted data starts at data + 0x10, so shift it back. */1016memmove(data, data + 0x10, *dataLen);1017return 0;1018}10191020// Requires sfoData to be padded with zeroes to the next 16-byte boundary (due to BuildHash)1021int SavedataParam::UpdateHash(u8* sfoData, int sfoSize, int sfoDataParamsOffset, int encryptmode)1022{1023int alignedLen = align16(sfoSize);1024memset(sfoData + sfoDataParamsOffset, 0, 128);1025u8 filehash[16];1026int ret = 0;10271028int firstHashMode = encryptmode & 2 ? 4 : 2;1029int secondHashMode = encryptmode & 2 ? 3 : 0;1030if (encryptmode & 4) {1031firstHashMode = 6;1032secondHashMode = 5;1033}10341035// Compute 11D0 hash over entire file1036if ((ret = BuildHash(filehash, sfoData, sfoSize, alignedLen, firstHashMode, 0)) < 0)1037{1038// Not sure about "2"1039return ret - 400;1040}10411042// Copy 11D0 hash to param.sfo and set flag indicating it's there1043memcpy(sfoData + sfoDataParamsOffset + 0x20, filehash, 0x10);1044*(sfoData + sfoDataParamsOffset) |= 0x01;10451046// If new encryption mode, compute and insert the 1220 hash.1047if (encryptmode & 6)1048{1049/* Enable the hash bit first */1050*(sfoData+sfoDataParamsOffset) |= (encryptmode & 6) << 4;10511052if ((ret = BuildHash(filehash, sfoData, sfoSize, alignedLen, secondHashMode, 0)) < 0)1053{1054return ret - 500;1055}1056memcpy(sfoData+sfoDataParamsOffset + 0x70, filehash, 0x10);1057}10581059/* Compute and insert the 11C0 hash. */1060if ((ret = BuildHash(filehash, sfoData, sfoSize, alignedLen, 1, 0)) < 0)1061{1062return ret - 600;1063}1064memcpy(sfoData+sfoDataParamsOffset + 0x10, filehash, 0x10);10651066/* All done. */1067return 0;1068}10691070// Requires sfoData to be padded with zeroes to the next 16-byte boundary.1071int SavedataParam::BuildHash(uint8_t *output,1072const uint8_t *data,1073unsigned int len,1074unsigned int alignedLen,1075int mode,1076const uint8_t *cryptkey) {1077pspChnnlsvContext1 ctx1;10781079/* Set up buffers */1080memset(&ctx1, 0, sizeof(pspChnnlsvContext1));1081memset(output, 0, 0x10);10821083/* Perform the magic */1084if (sceSdSetIndex_(ctx1, mode & 0xFF) < 0)1085return -1;1086if (sceSdRemoveValue_(ctx1, data, alignedLen) < 0)1087return -2;1088if (sceSdGetLastIndex_(ctx1, output, cryptkey) < 0)1089{1090// Got here since Kirk CMD5 missing, return random value;1091memset(output,0x1,0x10);1092return 0;1093}1094/* All done. */1095return 0;1096}10971098// TODO: Merge with NiceSizeFormat? That one has a decimal though.1099std::string SavedataParam::GetSpaceText(u64 size, bool roundUp)1100{1101char text[50];1102static const char * const suffixes[] = {"B", "KB", "MB", "GB"};1103for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i)1104{1105if (size < 1024)1106{1107snprintf(text, sizeof(text), "%lld %s", size, suffixes[i]);1108return std::string(text);1109}1110if (roundUp) {1111size = (size + 1023) / 1024;1112} else {1113size /= 1024;1114}1115}1116snprintf(text, sizeof(text), "%llu TB", size);1117return std::string(text);1118}11191120inline std::string FmtPspTime(const ScePspDateTime &dt) {1121return StringFromFormat("%04d-%02d-%02d %02d:%02d:%02d.%06d", dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond);1122}11231124int SavedataParam::GetSizes(SceUtilitySavedataParam *param)1125{1126if (!param) {1127return SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_DATA;1128}11291130int ret = 0;11311132if (param->msFree.IsValid())1133{1134const u64 freeBytes = MemoryStick_FreeSpace();1135param->msFree->clusterSize = (u32)MemoryStick_SectorSize();1136param->msFree->freeClusters = (u32)(freeBytes / MemoryStick_SectorSize());1137param->msFree->freeSpaceKB = (u32)(freeBytes / 0x400);1138const std::string spaceTxt = SavedataParam::GetSpaceText(freeBytes, false);1139memset(param->msFree->freeSpaceStr, 0, sizeof(param->msFree->freeSpaceStr));1140strncpy(param->msFree->freeSpaceStr, spaceTxt.c_str(), sizeof(param->msFree->freeSpaceStr));1141NotifyMemInfo(MemBlockFlags::WRITE, param->msFree.ptr, sizeof(SceUtilitySavedataMsFreeInfo), "SavedataGetSizes");1142}1143if (param->msData.IsValid())1144{1145const SceUtilitySavedataMsDataInfo *msData = param->msData;1146const std::string gameName(msData->gameName, strnlen(msData->gameName, sizeof(msData->gameName)));1147const std::string saveName(msData->saveName, strnlen(msData->saveName, sizeof(msData->saveName)));1148// TODO: How should <> be handled?1149std::string path = GetSaveFilePath(param, gameName + (saveName == "<>" ? "" : saveName));1150bool listingExists = false;1151auto listing = pspFileSystem.GetDirListing(path, &listingExists);1152if (listingExists) {1153param->msData->info.usedClusters = 0;1154for (auto &item : listing) {1155param->msData->info.usedClusters += (item.size + (u32)MemoryStick_SectorSize() - 1) / (u32)MemoryStick_SectorSize();1156}11571158// The usedSpaceKB value is definitely based on clusters, not bytes or even KB.1159// Fieldrunners expects 736 KB, even though the files add up to ~600 KB.1160int total_size = param->msData->info.usedClusters * (u32)MemoryStick_SectorSize();1161param->msData->info.usedSpaceKB = total_size / 0x400;1162std::string spaceTxt = SavedataParam::GetSpaceText(total_size, true);1163strncpy(param->msData->info.usedSpaceStr, spaceTxt.c_str(), sizeof(param->msData->info.usedSpaceStr));11641165// TODO: What does this mean, then? Seems to be the same.1166param->msData->info.usedSpace32KB = param->msData->info.usedSpaceKB;1167strncpy(param->msData->info.usedSpace32Str, spaceTxt.c_str(), sizeof(param->msData->info.usedSpace32Str));1168}1169else1170{1171param->msData->info.usedClusters = 0;1172param->msData->info.usedSpaceKB = 0;1173strncpy(param->msData->info.usedSpaceStr, "", sizeof(param->msData->info.usedSpaceStr));1174param->msData->info.usedSpace32KB = 0;1175strncpy(param->msData->info.usedSpace32Str, "", sizeof(param->msData->info.usedSpace32Str));1176ret = SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_DATA;1177}1178NotifyMemInfo(MemBlockFlags::WRITE, param->msData.ptr, sizeof(SceUtilitySavedataMsDataInfo), "SavedataGetSizes");1179}1180if (param->utilityData.IsValid())1181{1182int total_size = 0;11831184// The directory record itself.1185// TODO: Account for number of files / actual record size?1186total_size += getSizeNormalized(1);1187// Account for the SFO (is this always 1 sector?)1188total_size += getSizeNormalized(1);1189// Add the size of the data itself (don't forget encryption overhead.)1190// This is only added if a filename is specified.1191if (param->fileName[0] != 0) {1192if (g_Config.bEncryptSave) {1193total_size += getSizeNormalized((u32)param->dataSize + 16);1194} else {1195total_size += getSizeNormalized((u32)param->dataSize);1196}1197}1198total_size += getSizeNormalized(param->icon0FileData.size);1199total_size += getSizeNormalized(param->icon1FileData.size);1200total_size += getSizeNormalized(param->pic1FileData.size);1201total_size += getSizeNormalized(param->snd0FileData.size);12021203param->utilityData->usedClusters = total_size / (u32)MemoryStick_SectorSize();1204param->utilityData->usedSpaceKB = total_size / 0x400;1205std::string spaceTxt = SavedataParam::GetSpaceText(total_size, true);1206memset(param->utilityData->usedSpaceStr, 0, sizeof(param->utilityData->usedSpaceStr));1207strncpy(param->utilityData->usedSpaceStr, spaceTxt.c_str(), sizeof(param->utilityData->usedSpaceStr));12081209// TODO: Maybe these are rounded to the nearest 32KB? Or something?1210param->utilityData->usedSpace32KB = total_size / 0x400;1211std::string spaceTxt32 = SavedataParam::GetSpaceText(total_size, true);1212memset(param->utilityData->usedSpace32Str, 0, sizeof(param->utilityData->usedSpace32Str));1213strncpy(param->utilityData->usedSpace32Str, spaceTxt32.c_str(), sizeof(param->utilityData->usedSpace32Str));12141215INFO_LOG(Log::sceUtility, "GetSize: usedSpaceKB: %d (str: %s) (clusters: %d)", param->utilityData->usedSpaceKB, spaceTxt.c_str(), param->utilityData->usedClusters);1216INFO_LOG(Log::sceUtility, "GetSize: usedSpace32KB: %d (str32: %s)", param->utilityData->usedSpace32KB, spaceTxt32.c_str());12171218NotifyMemInfo(MemBlockFlags::WRITE, param->utilityData.ptr, sizeof(SceUtilitySavedataUsedDataInfo), "SavedataGetSizes");1219}1220return ret;1221}12221223bool SavedataParam::GetList(SceUtilitySavedataParam *param)1224{1225if (!param) {1226return false;1227}12281229if (param->idList.IsValid())1230{1231u32 maxFileCount = param->idList->maxCount;12321233std::vector<PSPFileInfo> validDir;1234std::vector<PSPFileInfo> sfoFiles;1235std::vector<PSPFileInfo> allDir = pspFileSystem.GetDirListing(savePath);12361237std::string searchString = GetGameName(param) + GetSaveName(param);1238for (size_t i = 0; i < allDir.size() && validDir.size() < maxFileCount; i++) {1239std::string dirName = allDir[i].name;1240if (PSPMatch(dirName, searchString)) {1241validDir.push_back(allDir[i]);1242}1243}12441245PSPFileInfo sfoFile;1246for (size_t i = 0; i < validDir.size(); ++i) {1247// GetFileName(param) == null here1248// so use sfo files to set the date.1249sfoFile = pspFileSystem.GetFileInfo(savePath + validDir[i].name + "/" + SFO_FILENAME);1250sfoFiles.push_back(sfoFile);1251}12521253SceUtilitySavedataIdListEntry *entries = param->idList->entries;1254for (u32 i = 0; i < (u32)validDir.size(); i++)1255{1256entries[i].st_mode = 0x11FF;1257if (sfoFiles[i].exists) {1258__IoCopyDate(entries[i].st_ctime, sfoFiles[i].ctime);1259__IoCopyDate(entries[i].st_atime, sfoFiles[i].atime);1260__IoCopyDate(entries[i].st_mtime, sfoFiles[i].mtime);1261} else {1262__IoCopyDate(entries[i].st_ctime, validDir[i].ctime);1263__IoCopyDate(entries[i].st_atime, validDir[i].atime);1264__IoCopyDate(entries[i].st_mtime, validDir[i].mtime);1265}1266// folder name without gamename (max 20 u8)1267std::string outName = validDir[i].name.substr(GetGameName(param).size());1268memset(entries[i].name, 0, sizeof(entries[i].name));1269strncpy(entries[i].name, outName.c_str(), sizeof(entries[i].name));1270}1271// Save num of folder found1272param->idList->resultCount = (u32)validDir.size();1273// Log out the listing.1274if (GenericLogEnabled(LogLevel::LINFO, Log::sceUtility)) {1275INFO_LOG(Log::sceUtility, "LIST (searchstring=%s): %d files (max: %d)", searchString.c_str(), param->idList->resultCount, maxFileCount);1276for (int i = 0; i < validDir.size(); i++) {1277INFO_LOG(Log::sceUtility, "%s: mode %08x, ctime: %s, atime: %s, mtime: %s",1278entries[i].name, entries[i].st_mode, FmtPspTime(entries[i].st_ctime).c_str(), FmtPspTime(entries[i].st_atime).c_str(), FmtPspTime(entries[i].st_mtime).c_str());1279}1280}1281NotifyMemInfo(MemBlockFlags::WRITE, param->idList.ptr, sizeof(SceUtilitySavedataIdListInfo), "SavedataGetList");1282NotifyMemInfo(MemBlockFlags::WRITE, param->idList->entries.ptr, (uint32_t)validDir.size() * sizeof(SceUtilitySavedataIdListEntry), "SavedataGetList");1283}1284return true;1285}12861287int SavedataParam::GetFilesList(SceUtilitySavedataParam *param, u32 requestAddr) {1288if (!param) {1289return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_STATUS;1290}12911292if (!param->fileList.IsValid()) {1293ERROR_LOG_REPORT(Log::sceUtility, "SavedataParam::GetFilesList(): bad fileList address %08x", param->fileList.ptr);1294// Should crash.1295return -1;1296}12971298auto &fileList = param->fileList;1299if (fileList->secureEntries.IsValid() && fileList->maxSecureEntries > 99) {1300ERROR_LOG_REPORT(Log::sceUtility, "SavedataParam::GetFilesList(): too many secure entries, %d", fileList->maxSecureEntries);1301return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;1302}1303if (fileList->normalEntries.IsValid() && fileList->maxNormalEntries > 8192) {1304ERROR_LOG_REPORT(Log::sceUtility, "SavedataParam::GetFilesList(): too many normal entries, %d", fileList->maxNormalEntries);1305return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;1306}1307if (sceKernelGetCompiledSdkVersion() >= 0x02060000) {1308if (fileList->systemEntries.IsValid() && fileList->maxSystemEntries > 5) {1309ERROR_LOG_REPORT(Log::sceUtility, "SavedataParam::GetFilesList(): too many system entries, %d", fileList->maxSystemEntries);1310return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;1311}1312}13131314std::string dirPath = savePath + GetGameName(param) + GetSaveName(param);1315bool dirPathExists = false;1316auto files = pspFileSystem.GetDirListing(dirPath, &dirPathExists);1317if (!dirPathExists) {1318DEBUG_LOG(Log::sceUtility, "SavedataParam::GetFilesList(): directory %s does not exist", dirPath.c_str());1319return SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;1320}13211322// Even if there are no files, initialize to 0.1323fileList->resultNumSecureEntries = 0;1324fileList->resultNumNormalEntries = 0;1325fileList->resultNumSystemEntries = 0;13261327// We need PARAM.SFO's SAVEDATA_FILE_LIST to determine which entries are secure.1328PSPFileInfo sfoFileInfo = FileFromListing(files, SFO_FILENAME);1329std::set<std::string> secureFilenames;13301331if (sfoFileInfo.exists) {1332secureFilenames = GetSecureFileNames(dirPath);1333} else {1334return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;1335}13361337// TODO: Does this always happen?1338// Don't know what it is, but PSP always respond this.1339param->bind = 1021;1340// This should be set around the same time as the file data. This runs on a thread, so set immediately.1341auto requestPtr = PSPPointer<SceUtilitySavedataParam>::Create(requestAddr);1342requestPtr->bind = 1021;13431344// Does not list directories, nor recurse into them, and ignores files not ALL UPPERCASE.1345bool isCrypted = GetSaveCryptMode(param, GetSaveDirName(param, 0)) != 0;1346for (auto file = files.begin(), end = files.end(); file != end; ++file) {1347if (file->type == FILETYPE_DIRECTORY) {1348continue;1349}1350// TODO: What are the exact rules? It definitely skips lowercase, and allows FILE or FILE.EXT.1351if (file->name.find_first_of("abcdefghijklmnopqrstuvwxyz") != file->name.npos) {1352DEBUG_LOG(Log::sceUtility, "SavedataParam::GetFilesList(): skipping file %s with lowercase", file->name.c_str());1353continue;1354}13551356bool isSystemFile = file->name == ICON0_FILENAME || file->name == ICON1_FILENAME || file->name == PIC1_FILENAME;1357isSystemFile = isSystemFile || file->name == SND0_FILENAME || file->name == SFO_FILENAME;13581359SceUtilitySavedataFileListEntry *entry = NULL;1360int sizeOffset = 0;1361if (isSystemFile) {1362if (fileList->systemEntries.IsValid() && fileList->resultNumSystemEntries < fileList->maxSystemEntries) {1363entry = &fileList->systemEntries[fileList->resultNumSystemEntries++];1364}1365} else if (secureFilenames.find(file->name) != secureFilenames.end()) {1366if (fileList->secureEntries.IsValid() && fileList->resultNumSecureEntries < fileList->maxSecureEntries) {1367entry = &fileList->secureEntries[fileList->resultNumSecureEntries++];1368}1369// Secure files are slightly bigger.1370if (isCrypted) {1371sizeOffset = -0x10;1372}1373} else {1374if (fileList->normalEntries.IsValid() && fileList->resultNumNormalEntries < fileList->maxNormalEntries) {1375entry = &fileList->normalEntries[fileList->resultNumNormalEntries++];1376}1377}13781379// Out of space for this file in the list.1380if (entry == NULL) {1381continue;1382}13831384entry->st_mode = 0x21FF;1385entry->st_size = file->size + sizeOffset;1386__IoCopyDate(entry->st_ctime, file->ctime);1387__IoCopyDate(entry->st_atime, file->atime);1388__IoCopyDate(entry->st_mtime, file->mtime);1389// TODO: Probably actually 13 + 3 pad...1390strncpy(entry->name, file->name.c_str(), 16);1391entry->name[15] = '\0';1392}13931394if (GenericLogEnabled(LogLevel::LINFO, Log::sceUtility)) {1395INFO_LOG(Log::sceUtility, "FILES: %d files listed", fileList->resultNumNormalEntries);1396for (int i = 0; i < (int)fileList->resultNumNormalEntries; i++) {1397const SceUtilitySavedataFileListEntry &info = fileList->systemEntries[i];1398INFO_LOG(Log::sceUtility, "%s: mode %08x, ctime: %s, atime: %s, mtime: %s",1399info.name, info.st_mode, FmtPspTime(info.st_ctime).c_str(), FmtPspTime(info.st_atime).c_str(), FmtPspTime(info.st_mtime).c_str());1400}1401}14021403NotifyMemInfo(MemBlockFlags::WRITE, fileList.ptr, sizeof(SceUtilitySavedataFileListInfo), "SavedataGetFilesList");1404if (fileList->resultNumSystemEntries != 0)1405NotifyMemInfo(MemBlockFlags::WRITE, fileList->systemEntries.ptr, fileList->resultNumSystemEntries * sizeof(SceUtilitySavedataFileListEntry), "SavedataGetFilesList");1406if (fileList->resultNumSecureEntries != 0)1407NotifyMemInfo(MemBlockFlags::WRITE, fileList->secureEntries.ptr, fileList->resultNumSecureEntries * sizeof(SceUtilitySavedataFileListEntry), "SavedataGetFilesList");1408if (fileList->resultNumNormalEntries != 0)1409NotifyMemInfo(MemBlockFlags::WRITE, fileList->normalEntries.ptr, fileList->resultNumNormalEntries * sizeof(SceUtilitySavedataFileListEntry), "SavedataGetFilesList");14101411return 0;1412}14131414bool SavedataParam::GetSize(SceUtilitySavedataParam *param) {1415if (!param) {1416return false;1417}14181419const std::string saveDir = savePath + GetGameName(param) + GetSaveName(param);1420bool exists = false;14211422if (param->sizeInfo.IsValid()) {1423auto listing = pspFileSystem.GetDirListing(saveDir, &exists);1424const u64 freeBytes = MemoryStick_FreeSpace();14251426s64 overwriteBytes = 0;1427s64 writeBytes = 0;1428for (int i = 0; i < param->sizeInfo->numNormalEntries; ++i) {1429const auto &entry = param->sizeInfo->normalEntries[i];1430overwriteBytes += FileFromListing(listing, entry.name).size;1431writeBytes += entry.size;1432}1433for (int i = 0; i < param->sizeInfo->numSecureEntries; ++i) {1434const auto &entry = param->sizeInfo->secureEntries[i];1435overwriteBytes += FileFromListing(listing, entry.name).size;1436writeBytes += entry.size + 0x10;1437}14381439param->sizeInfo->sectorSize = (int)MemoryStick_SectorSize();1440param->sizeInfo->freeSectors = (int)(freeBytes / MemoryStick_SectorSize());14411442// TODO: Is this after the specified files? Probably before?1443param->sizeInfo->freeKB = (int)(freeBytes / 1024);1444std::string spaceTxt = SavedataParam::GetSpaceText(freeBytes, false);1445truncate_cpy(param->sizeInfo->freeString, spaceTxt.c_str());14461447if (writeBytes - overwriteBytes < (s64)freeBytes) {1448param->sizeInfo->neededKB = 0;14491450// Note: this is "needed to overwrite".1451param->sizeInfo->overwriteKB = 0;14521453spaceTxt = GetSpaceText(0, true);1454truncate_cpy(param->sizeInfo->neededString, spaceTxt);1455truncate_cpy(param->sizeInfo->overwriteString, spaceTxt);1456} else {1457// Bytes needed to save additional data.1458s64 neededBytes = writeBytes - freeBytes;1459param->sizeInfo->neededKB = (neededBytes + 1023) / 1024;1460spaceTxt = GetSpaceText(neededBytes, true);1461truncate_cpy(param->sizeInfo->neededString, spaceTxt);14621463if (writeBytes - overwriteBytes < (s64)freeBytes) {1464param->sizeInfo->overwriteKB = 0;1465spaceTxt = GetSpaceText(0, true);1466truncate_cpy(param->sizeInfo->overwriteString, spaceTxt);1467} else {1468s64 neededOverwriteBytes = writeBytes - freeBytes - overwriteBytes;1469param->sizeInfo->overwriteKB = (neededOverwriteBytes + 1023) / 1024;1470spaceTxt = GetSpaceText(neededOverwriteBytes, true);1471truncate_cpy(param->sizeInfo->overwriteString, spaceTxt);1472}1473}14741475INFO_LOG(Log::sceUtility, "SectorSize: %d FreeSectors: %d FreeKB: %d neededKb: %d overwriteKb: %d",1476param->sizeInfo->sectorSize, param->sizeInfo->freeSectors, param->sizeInfo->freeKB, param->sizeInfo->neededKB, param->sizeInfo->overwriteKB);14771478NotifyMemInfo(MemBlockFlags::WRITE, param->sizeInfo.ptr, sizeof(PspUtilitySavedataSizeInfo), "SavedataGetSize");1479}14801481return exists;1482}14831484void SavedataParam::Clear()1485{1486if (saveDataList)1487{1488for (int i = 0; i < saveNameListDataCount; i++)1489{1490if (saveDataList[i].texture != NULL && (!noSaveIcon || saveDataList[i].texture != noSaveIcon->texture))1491delete saveDataList[i].texture;1492saveDataList[i].texture = NULL;1493}14941495delete [] saveDataList;1496saveDataList = NULL;1497saveDataListCount = 0;1498}1499if (noSaveIcon)1500{1501delete noSaveIcon->texture;1502noSaveIcon->texture = NULL;1503delete noSaveIcon;1504noSaveIcon = NULL;1505}1506}15071508int SavedataParam::SetPspParam(SceUtilitySavedataParam *param)1509{1510pspParam = param;1511if (!pspParam) {1512Clear();1513return 0;1514}15151516if (param->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTALLDELETE) {1517Clear();1518int realCount = 0;1519auto allSaves = pspFileSystem.GetDirListing(savePath);1520saveDataListCount = (int)allSaves.size();1521saveDataList = new SaveFileInfo[saveDataListCount];1522for (auto save : allSaves) {1523if (save.type != FILETYPE_DIRECTORY || save.name == "." || save.name == "..")1524continue;1525std::string fileDataDir = savePath + save.name;1526PSPFileInfo info = GetSaveInfo(fileDataDir);1527SetFileInfo(realCount, info, "", save.name);1528realCount++;1529}1530saveNameListDataCount = realCount;1531return 0;1532}15331534bool listEmptyFile = true;1535if (param->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTLOAD || param->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTDELETE) {1536listEmptyFile = false;1537}15381539SceUtilitySavedataSaveName *saveNameListData;1540bool hasMultipleFileName = false;1541if (param->saveNameList.IsValid()) {1542Clear();15431544saveNameListData = param->saveNameList;15451546// Get number of fileName in array1547saveDataListCount = 0;1548while (saveNameListData[saveDataListCount][0] != 0) {1549saveDataListCount++;1550}15511552if (saveDataListCount > 0 && WouldHaveMultiSaveName(param)) {1553hasMultipleFileName = true;1554saveDataList = new SaveFileInfo[saveDataListCount];15551556// get and stock file info for each file1557int realCount = 0;1558for (int i = 0; i < saveDataListCount; i++) {1559// "<>" means saveName can be anything...1560if (strncmp(saveNameListData[i], "<>", ARRAY_SIZE(saveNameListData[i])) == 0) {1561// TODO:Maybe we need a way to reorder the files?1562auto allSaves = pspFileSystem.GetDirListing(savePath);1563std::string gameName = GetGameName(param);1564for (auto it = allSaves.begin(); it != allSaves.end(); ++it) {1565if (it->name.compare(0, gameName.length(), gameName) == 0) {1566std::string saveName = it->name.substr(gameName.length());15671568if (IsInSaveDataList(saveName, realCount)) // Already in SaveDataList, skip...1569continue;15701571std::string fileDataPath = savePath + it->name;1572if (it->exists) {1573SetFileInfo(realCount, *it, saveName);1574DEBUG_LOG(Log::sceUtility, "%s Exist", fileDataPath.c_str());1575++realCount;1576} else {1577if (listEmptyFile) {1578// If file doesn't exist,we only skip...1579continue;1580}1581}1582break;1583}1584}1585continue;1586}15871588const std::string thisSaveName = FixedToString(saveNameListData[i], ARRAY_SIZE(saveNameListData[i]));15891590std::string fileDataDir = savePath + GetGameName(param) + thisSaveName;1591PSPFileInfo info = GetSaveInfo(fileDataDir);1592if (info.exists) {1593SetFileInfo(realCount, info, thisSaveName);1594INFO_LOG(Log::sceUtility, "Save data exists: %s = %s", thisSaveName.c_str(), fileDataDir.c_str());1595realCount++;1596} else {1597if (listEmptyFile) {1598ClearFileInfo(saveDataList[realCount], thisSaveName);1599INFO_LOG(Log::sceUtility, "Listing missing save data: %s = %s", thisSaveName.c_str(), fileDataDir.c_str());1600realCount++;1601} else {1602INFO_LOG(Log::sceUtility, "Save data not found: %s = %s", thisSaveName.c_str(), fileDataDir.c_str());1603}1604}1605}1606saveNameListDataCount = realCount;1607}1608}1609// Load info on only save1610if (!hasMultipleFileName) {1611saveNameListData = 0;16121613Clear();1614saveDataList = new SaveFileInfo[1];1615saveDataListCount = 1;16161617// get and stock file info for each file1618std::string fileDataDir = savePath + GetGameName(param) + GetSaveName(param);1619PSPFileInfo info = GetSaveInfo(fileDataDir);1620if (info.exists) {1621SetFileInfo(0, info, GetSaveName(param));1622INFO_LOG(Log::sceUtility, "Save data exists: %s = %s", GetSaveName(param).c_str(), fileDataDir.c_str());1623saveNameListDataCount = 1;1624} else {1625if (listEmptyFile) {1626ClearFileInfo(saveDataList[0], GetSaveName(param));1627INFO_LOG(Log::sceUtility, "Listing missing save data: %s = %s", GetSaveName(param).c_str(), fileDataDir.c_str());1628} else {1629INFO_LOG(Log::sceUtility, "Save data not found: %s = %s", GetSaveName(param).c_str(), fileDataDir.c_str());1630}1631saveNameListDataCount = 0;1632return 0;1633}1634}1635return 0;1636}16371638void SavedataParam::SetFileInfo(SaveFileInfo &saveInfo, PSPFileInfo &info, const std::string &saveName, const std::string &savrDir)1639{1640saveInfo.size = info.size;1641saveInfo.saveName = saveName;1642saveInfo.idx = 0;1643saveInfo.modif_time = info.mtime;16441645std::string saveDir = savrDir.empty() ? GetGameName(pspParam) + saveName : savrDir;1646saveInfo.saveDir = saveDir;16471648// Start with a blank slate.1649if (saveInfo.texture != NULL) {1650if (!noSaveIcon || saveInfo.texture != noSaveIcon->texture) {1651delete saveInfo.texture;1652}1653saveInfo.texture = NULL;1654}1655saveInfo.title[0] = 0;1656saveInfo.saveTitle[0] = 0;1657saveInfo.saveDetail[0] = 0;16581659// Search save image icon01660// TODO : If icon0 don't exist, need to use icon1 which is a moving icon. Also play sound1661if (!ignoreTextures_) {1662saveInfo.texture = new PPGeImage(savePath + saveDir + "/" + ICON0_FILENAME);1663}16641665// Load info in PARAM.SFO1666std::string sfoFilename = savePath + saveDir + "/" + SFO_FILENAME;1667std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfoFilename);1668if (sfoFile) {1669SetStringFromSFO(*sfoFile, "TITLE", saveInfo.title, sizeof(saveInfo.title));1670SetStringFromSFO(*sfoFile, "SAVEDATA_TITLE", saveInfo.saveTitle, sizeof(saveInfo.saveTitle));1671SetStringFromSFO(*sfoFile, "SAVEDATA_DETAIL", saveInfo.saveDetail, sizeof(saveInfo.saveDetail));1672} else {1673saveInfo.broken = true;1674truncate_cpy(saveInfo.title, saveDir);1675}1676}16771678void SavedataParam::SetFileInfo(int idx, PSPFileInfo &info, const std::string &saveName, const std::string &saveDir)1679{1680SetFileInfo(saveDataList[idx], info, saveName, saveDir);1681saveDataList[idx].idx = idx;1682}16831684void SavedataParam::ClearFileInfo(SaveFileInfo &saveInfo, const std::string &saveName) {1685saveInfo.size = 0;1686saveInfo.saveName = saveName;1687saveInfo.idx = 0;1688saveInfo.broken = false;1689if (saveInfo.texture != NULL) {1690if (!noSaveIcon || saveInfo.texture != noSaveIcon->texture) {1691delete saveInfo.texture;1692}1693saveInfo.texture = NULL;1694}16951696if (GetPspParam()->newData.IsValid() && GetPspParam()->newData->buf.IsValid()) {1697// We have a png to show1698if (!noSaveIcon) {1699noSaveIcon = new SaveFileInfo();1700PspUtilitySavedataFileData *newData = GetPspParam()->newData;1701noSaveIcon->texture = new PPGeImage(newData->buf.ptr, (SceSize)newData->size);1702}1703saveInfo.texture = noSaveIcon->texture;1704} else if ((u32)GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_SAVE && GetPspParam()->icon0FileData.buf.IsValid()) {1705const PspUtilitySavedataFileData &icon0FileData = GetPspParam()->icon0FileData;1706saveInfo.texture = new PPGeImage(icon0FileData.buf.ptr, (SceSize)icon0FileData.size);1707}1708}17091710PSPFileInfo SavedataParam::GetSaveInfo(const std::string &saveDir) {1711PSPFileInfo info = pspFileSystem.GetFileInfo(saveDir);1712if (info.exists) {1713info.access = 0777;1714auto allFiles = pspFileSystem.GetDirListing(saveDir);1715bool firstFile = true;1716for (auto file : allFiles) {1717if (file.type == FILETYPE_DIRECTORY || file.name == "." || file.name == "..")1718continue;1719// Use a file to determine save date.1720if (firstFile) {1721info.ctime = file.ctime;1722info.mtime = file.mtime;1723info.atime = file.atime;1724info.size += file.size;1725firstFile = false;1726} else {1727info.size += file.size;1728}1729}1730}1731return info;1732}17331734SceUtilitySavedataParam *SavedataParam::GetPspParam()1735{1736return pspParam;1737}17381739const SceUtilitySavedataParam *SavedataParam::GetPspParam() const1740{1741return pspParam;1742}17431744int SavedataParam::GetFilenameCount()1745{1746return saveNameListDataCount;1747}17481749const SaveFileInfo& SavedataParam::GetFileInfo(int idx)1750{1751return saveDataList[idx];1752}17531754std::string SavedataParam::GetFilename(int idx) const1755{1756return saveDataList[idx].saveName;1757}17581759std::string SavedataParam::GetSaveDir(int idx) const {1760return saveDataList[idx].saveDir;1761}17621763int SavedataParam::GetSelectedSave()1764{1765// The slot # of the same save on LOAD/SAVE lists can dismatch so this isn't right anyhow1766return selectedSave < saveNameListDataCount ? selectedSave : 0;1767}17681769void SavedataParam::SetSelectedSave(int idx)1770{1771selectedSave = idx;1772}17731774int SavedataParam::GetFirstListSave()1775{1776return 0;1777}17781779int SavedataParam::GetLastListSave()1780{1781return saveNameListDataCount - 1;1782}17831784int SavedataParam::GetLatestSave()1785{1786int idx = 0;1787time_t idxTime = 0;1788for (int i = 0; i < saveNameListDataCount; ++i)1789{1790if (saveDataList[i].size == 0)1791continue;1792time_t thisTime = mktime(&saveDataList[i].modif_time);1793if ((s64)idxTime < (s64)thisTime)1794{1795idx = i;1796idxTime = thisTime;1797}1798}1799return idx;1800}18011802int SavedataParam::GetOldestSave()1803{1804int idx = 0;1805time_t idxTime = 0;1806for (int i = 0; i < saveNameListDataCount; ++i)1807{1808if (saveDataList[i].size == 0)1809continue;1810time_t thisTime = mktime(&saveDataList[i].modif_time);1811if ((s64)idxTime > (s64)thisTime)1812{1813idx = i;1814idxTime = thisTime;1815}1816}1817return idx;1818}18191820int SavedataParam::GetFirstDataSave()1821{1822int idx = 0;1823for (int i = 0; i < saveNameListDataCount; ++i)1824{1825if (saveDataList[i].size != 0)1826{1827idx = i;1828break;1829}1830}1831return idx;1832}18331834int SavedataParam::GetLastDataSave()1835{1836int idx = 0;1837for (int i = saveNameListDataCount; i > 0; )1838{1839--i;1840if (saveDataList[i].size != 0)1841{1842idx = i;1843break;1844}1845}1846return idx;1847}18481849int SavedataParam::GetFirstEmptySave()1850{1851int idx = 0;1852for (int i = 0; i < saveNameListDataCount; ++i)1853{1854if (saveDataList[i].size == 0)1855{1856idx = i;1857break;1858}1859}1860return idx;1861}18621863int SavedataParam::GetLastEmptySave()1864{1865int idx = 0;1866for (int i = saveNameListDataCount; i > 0; )1867{1868--i;1869if (saveDataList[i].size == 0)1870{1871idx = i;1872break;1873}1874}1875return idx;1876}18771878int SavedataParam::GetSaveNameIndex(const SceUtilitySavedataParam *param) {1879std::string saveName = GetSaveName(param);1880for (int i = 0; i < saveNameListDataCount; i++)1881{1882// TODO: saveName may contain wildcards1883if (saveDataList[i].saveName == saveName)1884{1885return i;1886}1887}18881889return 0;1890}18911892bool SavedataParam::WouldHaveMultiSaveName(const SceUtilitySavedataParam *param) {1893switch ((SceUtilitySavedataType)(u32)param->mode) {1894case SCE_UTILITY_SAVEDATA_TYPE_LOAD:1895case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD:1896case SCE_UTILITY_SAVEDATA_TYPE_SAVE:1897case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE:1898case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE:1899case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA:1900case SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE:1901case SCE_UTILITY_SAVEDATA_TYPE_READDATA:1902case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE:1903case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA:1904case SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE:1905case SCE_UTILITY_SAVEDATA_TYPE_DELETE:1906case SCE_UTILITY_SAVEDATA_TYPE_ERASESECURE:1907case SCE_UTILITY_SAVEDATA_TYPE_ERASE:1908case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA:1909return false;1910default:1911return true;1912}1913}19141915void SavedataParam::DoState(PointerWrap &p) {1916auto s = p.Section("SavedataParam", 1, 2);1917if (!s)1918return;19191920// pspParam is handled in PSPSaveDialog.1921Do(p, selectedSave);1922Do(p, saveDataListCount);1923Do(p, saveNameListDataCount);1924if (p.mode == p.MODE_READ) {1925delete [] saveDataList;1926if (saveDataListCount != 0) {1927saveDataList = new SaveFileInfo[saveDataListCount];1928DoArray(p, saveDataList, saveDataListCount);1929} else {1930saveDataList = nullptr;1931}1932}1933else1934DoArray(p, saveDataList, saveDataListCount);19351936if (s >= 2) {1937Do(p, ignoreTextures_);1938} else {1939ignoreTextures_ = false;1940}1941}19421943void SavedataParam::ClearSFOCache() {1944std::lock_guard<std::mutex> guard(cacheLock_);1945sfoCache_.clear();1946}19471948std::shared_ptr<ParamSFOData> SavedataParam::LoadCachedSFO(const std::string &path, bool orCreate) {1949std::lock_guard<std::mutex> guard(cacheLock_);1950if (sfoCache_.find(path) == sfoCache_.end()) {1951std::vector<u8> data;1952if (pspFileSystem.ReadEntireFile(path, data, true) < 0) {1953// Mark as not existing for later.1954sfoCache_[path].reset();1955} else {1956sfoCache_.emplace(path, new ParamSFOData());1957// If it fails to load, also keep it to indicate failed.1958if (!sfoCache_.at(path)->ReadSFO(data))1959sfoCache_.at(path).reset();1960}1961}19621963if (!sfoCache_.at(path)) {1964if (!orCreate)1965return nullptr;1966sfoCache_.at(path).reset(new ParamSFOData());1967}1968return sfoCache_.at(path);1969}19701971int SavedataParam::GetSaveCryptMode(const SceUtilitySavedataParam *param, const std::string &saveDirName) {1972std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));1973std::string sfopath = dirPath + "/" + SFO_FILENAME;1974std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfopath);1975if (sfoFile) {1976// save created in PPSSPP and not encrypted has '0' in SAVEDATA_PARAMS1977u32 tmpDataSize = 0;1978const u8 *tmpDataOrig = sfoFile->GetValueData("SAVEDATA_PARAMS", &tmpDataSize);1979if (tmpDataSize == 0 || !tmpDataOrig) {1980return 0;1981}1982switch (tmpDataOrig[0]) {1983case 0:1984return 0;1985case 0x01:1986return 1;1987case 0x21:1988return 3;1989case 0x41:1990return 5;1991default:1992// Well, it's not zero, so yes.1993ERROR_LOG_REPORT(Log::sceUtility, "Unexpected SAVEDATA_PARAMS hash flag: %02x", tmpDataOrig[0]);1994return 1;1995}1996}1997return 0;1998}19992000bool SavedataParam::IsInSaveDataList(const std::string &saveName, int count) {2001for(int i = 0; i < count; ++i) {2002if(strcmp(saveDataList[i].saveName.c_str(),saveName.c_str()) == 0)2003return true;2004}2005return false;2006}200720082009