Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Dialog/SavedataParam.cpp
3186 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <algorithm>
19
#include <memory>
20
#include "Common/Log.h"
21
#include "Common/Data/Text/I18n.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"
39
40
static const std::string ICON0_FILENAME = "ICON0.PNG";
41
static const std::string ICON1_FILENAME = "ICON1.PMF";
42
static const std::string PIC1_FILENAME = "PIC1.PNG";
43
static const std::string SND0_FILENAME = "SND0.AT3";
44
static const std::string SFO_FILENAME = "PARAM.SFO";
45
46
static const int FILE_LIST_COUNT_MAX = 99;
47
static const u32 FILE_LIST_TOTAL_SIZE = sizeof(SaveSFOFileListEntry) * FILE_LIST_COUNT_MAX;
48
49
static const std::string savePath = "ms0:/PSP/SAVEDATA/";
50
51
namespace
52
{
53
int getSizeNormalized(int size)
54
{
55
int sizeCluster = (int)MemoryStick_SectorSize();
56
return ((int)((size + sizeCluster - 1) / sizeCluster)) * sizeCluster;
57
}
58
59
void SetStringFromSFO(ParamSFOData &sfoFile, const char *name, char *str, int strLength)
60
{
61
truncate_cpy(str, strLength, sfoFile.GetValueString(name));
62
}
63
64
bool ReadPSPFile(const std::string &filename, u8 **data, s64 dataSize, s64 *readSize)
65
{
66
int handle = pspFileSystem.OpenFile(filename, FILEACCESS_READ);
67
if (handle < 0)
68
return false;
69
70
if (dataSize == -1) {
71
// Determine the size through seeking instead of querying.
72
pspFileSystem.SeekFile(handle, 0, FILEMOVE_END);
73
dataSize = pspFileSystem.GetSeekPos(handle);
74
pspFileSystem.SeekFile(handle, 0, FILEMOVE_BEGIN);
75
76
*data = new u8[(size_t)dataSize];
77
}
78
79
size_t result = pspFileSystem.ReadFile(handle, *data, dataSize);
80
pspFileSystem.CloseFile(handle);
81
if (readSize)
82
*readSize = result;
83
84
return result != 0;
85
}
86
87
bool WritePSPFile(const std::string &filename, const u8 *data, SceSize dataSize)
88
{
89
int handle = pspFileSystem.OpenFile(filename, (FileAccess)(FILEACCESS_WRITE | FILEACCESS_CREATE | FILEACCESS_TRUNCATE));
90
if (handle < 0)
91
return false;
92
93
size_t result = pspFileSystem.WriteFile(handle, data, dataSize);
94
pspFileSystem.CloseFile(handle);
95
96
return result == dataSize;
97
}
98
99
PSPFileInfo FileFromListing(const std::vector<PSPFileInfo> &listing, const std::string &filename) {
100
for (const PSPFileInfo &sub : listing) {
101
if (sub.name == filename)
102
return sub;
103
}
104
105
PSPFileInfo info;
106
info.name = filename;
107
info.exists = false;
108
return info;
109
}
110
111
bool PSPMatch(std::string_view text, std::string_view regexp) {
112
if (text.empty() && regexp.empty())
113
return true;
114
else if (regexp == "*")
115
return true;
116
else if (text.empty())
117
return false;
118
else if (regexp.empty())
119
return false;
120
else if (regexp == "?" && text.length() == 1)
121
return true;
122
else if (text == regexp)
123
return true;
124
else if (regexp.data()[0] == '*')
125
{
126
bool res = PSPMatch(text.substr(1),regexp.substr(1));
127
if(!res)
128
res = PSPMatch(text.substr(1),regexp);
129
return res;
130
}
131
else if (regexp.data()[0] == '?')
132
{
133
return PSPMatch(text.substr(1),regexp.substr(1));
134
}
135
else if (regexp.data()[0] == text.data()[0])
136
{
137
return PSPMatch(text.substr(1),regexp.substr(1));
138
}
139
140
return false;
141
}
142
143
int align16(int address)
144
{
145
return (address + 15) & ~15;
146
}
147
148
int GetSDKMainVersion(int sdkVersion)
149
{
150
if(sdkVersion > 0x0307FFFF)
151
return 6;
152
if(sdkVersion > 0x0300FFFF)
153
return 5;
154
if(sdkVersion > 0x0206FFFF)
155
return 4;
156
if(sdkVersion > 0x0205FFFF)
157
return 3;
158
if(sdkVersion >= 0x02000000)
159
return 2;
160
if(sdkVersion >= 0x01000000)
161
return 1;
162
return 0;
163
};
164
}
165
166
void SaveFileInfo::DoState(PointerWrap &p)
167
{
168
auto s = p.Section("SaveFileInfo", 1, 2);
169
if (!s)
170
return;
171
172
Do(p, size);
173
Do(p, saveName);
174
Do(p, idx);
175
176
DoArray(p, title, sizeof(title));
177
DoArray(p, saveTitle, sizeof(saveTitle));
178
DoArray(p, saveDetail, sizeof(saveDetail));
179
180
Do(p, modif_time);
181
182
if (s <= 1) {
183
u32 textureData;
184
int textureWidth;
185
int textureHeight;
186
Do(p, textureData);
187
Do(p, textureWidth);
188
Do(p, textureHeight);
189
190
if (textureData != 0) {
191
// Must be MODE_READ.
192
texture = new PPGeImage("");
193
texture->CompatLoad(textureData, textureWidth, textureHeight);
194
}
195
} else {
196
bool hasTexture = texture != NULL;
197
Do(p, hasTexture);
198
if (hasTexture) {
199
if (p.mode == p.MODE_READ) {
200
delete texture;
201
texture = new PPGeImage("");
202
}
203
texture->DoState(p);
204
}
205
}
206
}
207
208
SavedataParam::SavedataParam() { }
209
210
void SavedataParam::Init() {
211
// If the folder already exists, this is a no-op.
212
pspFileSystem.MkDir(savePath);
213
// Create a nomedia file to hide save icons form Android image viewer
214
#if PPSSPP_PLATFORM(ANDROID)
215
int handle = pspFileSystem.OpenFile(savePath + ".nomedia", (FileAccess)(FILEACCESS_CREATE | FILEACCESS_WRITE), 0);
216
if (handle >= 0) {
217
pspFileSystem.CloseFile(handle);
218
} else {
219
INFO_LOG(Log::IO, "Failed to create .nomedia file (might be ok if it already exists)");
220
}
221
#endif
222
}
223
224
std::string SavedataParam::GetSaveDirName(const SceUtilitySavedataParam *param, int saveId) const
225
{
226
if (!param) {
227
return "";
228
}
229
230
if (saveId >= 0 && saveNameListDataCount > 0) // if user selection, use it
231
return GetFilename(saveId);
232
else
233
return GetSaveName(param);
234
}
235
236
std::string SavedataParam::GetSaveDir(const SceUtilitySavedataParam *param, const std::string &saveDirName) const
237
{
238
if (!param) {
239
return "";
240
}
241
242
return GetGameName(param) + saveDirName;
243
}
244
245
std::string SavedataParam::GetSaveDir(const SceUtilitySavedataParam *param, int saveId) const
246
{
247
return GetSaveDir(param, GetSaveDirName(param, saveId));
248
}
249
250
std::string SavedataParam::GetSaveFilePath(const SceUtilitySavedataParam *param, const std::string &saveDir) const
251
{
252
if (!param) {
253
return "";
254
}
255
256
if (!saveDir.size())
257
return "";
258
259
return savePath + saveDir;
260
}
261
262
std::string SavedataParam::GetSaveFilePath(const SceUtilitySavedataParam *param, int saveId) const
263
{
264
return GetSaveFilePath(param, GetSaveDir(param, saveId));
265
}
266
267
inline static std::string FixedToString(const char *str, size_t n)
268
{
269
if (!str) {
270
return std::string();
271
} else {
272
return std::string(str, strnlen(str, n));
273
}
274
}
275
276
std::string SavedataParam::GetGameName(const SceUtilitySavedataParam *param) const
277
{
278
return FixedToString(param->gameName, ARRAY_SIZE(param->gameName));
279
}
280
281
std::string SavedataParam::GetSaveName(const SceUtilitySavedataParam *param) const
282
{
283
const std::string saveName = FixedToString(param->saveName, ARRAY_SIZE(param->saveName));
284
if (saveName == "<>")
285
return "";
286
return saveName;
287
}
288
289
std::string SavedataParam::GetFileName(const SceUtilitySavedataParam *param) const
290
{
291
return FixedToString(param->fileName, ARRAY_SIZE(param->fileName));
292
}
293
294
std::string SavedataParam::GetKey(const SceUtilitySavedataParam *param) const
295
{
296
static const char* const lut = "0123456789ABCDEF";
297
298
std::string output;
299
if (HasKey(param))
300
{
301
output.reserve(2 * sizeof(param->key));
302
for (size_t i = 0; i < sizeof(param->key); ++i)
303
{
304
const unsigned char c = param->key[i];
305
output.push_back(lut[c >> 4]);
306
output.push_back(lut[c & 15]);
307
}
308
}
309
return output;
310
}
311
312
bool SavedataParam::HasKey(const SceUtilitySavedataParam *param) const
313
{
314
for (size_t i = 0; i < ARRAY_SIZE(param->key); ++i)
315
{
316
if (param->key[i] != 0)
317
return true;
318
}
319
return false;
320
}
321
322
bool SavedataParam::Delete(SceUtilitySavedataParam* param, int saveId) {
323
if (!param) {
324
return false;
325
}
326
327
// Sanity check, preventing full delete of savedata/ in MGS PW demo (!)
328
if (!strnlen(param->gameName, sizeof(param->gameName)) && param->mode != SCE_UTILITY_SAVEDATA_TYPE_LISTALLDELETE) {
329
ERROR_LOG(Log::sceUtility, "Bad param with gameName empty - cannot delete save directory");
330
return false;
331
}
332
333
std::string dirPath = GetSaveFilePath(param, GetSaveDir(saveId));
334
if (dirPath.size() == 0) {
335
ERROR_LOG(Log::sceUtility, "GetSaveFilePath (%.*s) returned empty - cannot delete save directory. Might already be deleted?", (int)sizeof(param->gameName), param->gameName);
336
return false;
337
}
338
339
if (!pspFileSystem.GetFileInfo(dirPath).exists) {
340
return false;
341
}
342
343
ClearSFOCache();
344
pspFileSystem.RmDir(dirPath);
345
return true;
346
}
347
348
int SavedataParam::DeleteData(SceUtilitySavedataParam* param) {
349
if (!param) {
350
return SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND;
351
}
352
353
std::string subFolder = GetGameName(param) + GetSaveName(param);
354
std::string fileName = GetFileName(param);
355
std::string dirPath = savePath + subFolder;
356
std::string filePath = dirPath + "/" + fileName;
357
std::string sfoPath = dirPath + "/" + SFO_FILENAME;
358
359
if (!pspFileSystem.GetFileInfo(dirPath).exists) {
360
return SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
361
}
362
363
if (!pspFileSystem.GetFileInfo(sfoPath).exists)
364
return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;
365
366
if (!fileName.empty() && !pspFileSystem.GetFileInfo(filePath).exists) {
367
return SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND;
368
}
369
370
if (fileName.empty()) {
371
return 0;
372
}
373
374
if (!subFolder.size()) {
375
ERROR_LOG(Log::sceUtility, "Bad subfolder, ignoring delete of %s", filePath.c_str());
376
return 0;
377
}
378
379
ClearSFOCache();
380
pspFileSystem.RemoveFile(filePath);
381
382
// Update PARAM.SFO to remove the file, if it was in the list.
383
std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfoPath);
384
if (sfoFile) {
385
// Note: do not update values such as TITLE in this operation.
386
u32 fileListSize = 0;
387
SaveSFOFileListEntry *fileList = (SaveSFOFileListEntry *)sfoFile->GetValueData("SAVEDATA_FILE_LIST", &fileListSize);
388
size_t fileListCount = fileListSize / sizeof(SaveSFOFileListEntry);
389
bool changed = false;
390
for (size_t i = 0; i < fileListCount; ++i) {
391
if (strncmp(fileList[i].filename, fileName.c_str(), sizeof(fileList[i].filename)) != 0)
392
continue;
393
394
memset(fileList[i].filename, 0, sizeof(fileList[i].filename));
395
memset(fileList[i].hash, 0, sizeof(fileList[i].hash));
396
changed = true;
397
break;
398
}
399
400
if (changed) {
401
auto updatedList = std::make_unique<u8[]> (fileListSize);
402
memcpy(updatedList.get(), fileList, fileListSize);
403
sfoFile->SetValue("SAVEDATA_FILE_LIST", updatedList.get(), fileListSize, (int)FILE_LIST_TOTAL_SIZE);
404
405
u8 *sfoData;
406
size_t sfoSize;
407
sfoFile->WriteSFO(&sfoData, &sfoSize);
408
409
ClearSFOCache();
410
WritePSPFile(sfoPath, sfoData, (SceSize)sfoSize);
411
delete[] sfoData;
412
}
413
}
414
415
return 0;
416
}
417
418
int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode) {
419
if (!param) {
420
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE;
421
}
422
if (param->dataSize > param->dataBufSize) {
423
ERROR_LOG_REPORT(Log::sceUtility, "Savedata buffer overflow: %d / %d", param->dataSize, param->dataBufSize);
424
return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;
425
}
426
auto validateSize = [](const PspUtilitySavedataFileData &data) {
427
if (data.buf.IsValid() && data.bufSize < data.size) {
428
ERROR_LOG_REPORT(Log::sceUtility, "Savedata subdata buffer overflow: %d / %d", data.size, data.bufSize);
429
return false;
430
}
431
return true;
432
};
433
if (!validateSize(param->icon0FileData) || !validateSize(param->icon1FileData) || !validateSize(param->pic1FileData) || !validateSize(param->snd0FileData)) {
434
return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;
435
}
436
437
if (param->secureVersion > 3) {
438
ERROR_LOG_REPORT(Log::sceUtility, "Savedata version requested on save: %d", param->secureVersion);
439
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM;
440
} else if (param->secureVersion != 0) {
441
if (param->secureVersion != 1 && !HasKey(param) && secureMode) {
442
ERROR_LOG_REPORT(Log::sceUtility, "Savedata version with missing key on save: %d", param->secureVersion);
443
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM;
444
}
445
WARN_LOG(Log::sceUtility, "Savedata version requested on save: %d", param->secureVersion);
446
}
447
448
std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));
449
450
if (!pspFileSystem.GetFileInfo(dirPath).exists) {
451
if (!pspFileSystem.MkDir(dirPath)) {
452
auto err = GetI18NCategory(I18NCat::ERRORS);
453
g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Unable to write savedata, disk may be full"));
454
}
455
}
456
457
u8* cryptedData = nullptr;
458
int cryptedSize = 0;
459
u8 cryptedHash[0x10]{};
460
// Encrypt save.
461
// TODO: Is this the correct difference between MAKEDATA and MAKEDATASECURE?
462
if (param->dataBuf.IsValid() && g_Config.bEncryptSave && secureMode)
463
{
464
cryptedSize = param->dataSize;
465
if (cryptedSize == 0 || (SceSize)cryptedSize > param->dataBufSize) {
466
ERROR_LOG(Log::sceUtility, "Bad cryptedSize %d", cryptedSize);
467
cryptedSize = param->dataBufSize; // fallback, should never use this
468
}
469
u8 *data_ = param->dataBuf;
470
471
int aligned_len = align16(cryptedSize);
472
if (aligned_len != cryptedSize) {
473
WARN_LOG(Log::sceUtility, "cryptedSize unaligned: %d (%d)", cryptedSize, cryptedSize & 15);
474
}
475
476
cryptedData = new u8[aligned_len + 0x10]();
477
memcpy(cryptedData, data_, cryptedSize);
478
// EncryptData will do a memmove to make room for the key in front.
479
// Technically we could just copy it into place here to avoid that.
480
481
int decryptMode = DetermineCryptMode(param);
482
bool hasKey = decryptMode > 1;
483
if (hasKey && !HasKey(param)) {
484
delete[] cryptedData;
485
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM;
486
}
487
488
if (EncryptData(decryptMode, cryptedData, &cryptedSize, &aligned_len, cryptedHash, (hasKey ? param->key : 0)) != 0) {
489
auto err = GetI18NCategory(I18NCat::ERRORS);
490
g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Save encryption failed. This save won't work on real PSP"), 6.0f);
491
ERROR_LOG(Log::sceUtility,"Save encryption failed. This save won't work on real PSP");
492
delete[] cryptedData;
493
cryptedData = 0;
494
}
495
}
496
497
// SAVE PARAM.SFO
498
std::string sfopath = dirPath + "/" + SFO_FILENAME;
499
std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfopath, true);
500
501
// This was added in #18430, see below.
502
bool subWrite = param->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE || param->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA;
503
bool wasCrypted = GetSaveCryptMode(param, saveDirName) != 0;
504
505
// Update values. NOTE! #18430 made this conditional on !subWrite, but this is not correct, as it causes #18687.
506
// So now we do a hacky trick and just check for a valid title before we proceed with updating the sfoFile.
507
if (strnlen(param->sfoParam.title, sizeof(param->sfoParam.title)) > 0) {
508
sfoFile->SetValue("TITLE", param->sfoParam.title, 128);
509
sfoFile->SetValue("SAVEDATA_TITLE", param->sfoParam.savedataTitle, 128);
510
sfoFile->SetValue("SAVEDATA_DETAIL", param->sfoParam.detail, 1024);
511
sfoFile->SetValue("PARENTAL_LEVEL", param->sfoParam.parentalLevel, 4);
512
sfoFile->SetValue("CATEGORY", "MS", 4);
513
sfoFile->SetValue("SAVEDATA_DIRECTORY", GetSaveDir(param, saveDirName), 64);
514
}
515
516
// Always write and update the file list.
517
// For each file, 13 bytes for filename, 16 bytes for file hash (0 in PPSSPP), 3 byte for padding
518
u32 tmpDataSize = 0;
519
SaveSFOFileListEntry *tmpDataOrig = (SaveSFOFileListEntry *)sfoFile->GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);
520
SaveSFOFileListEntry *updatedList = new SaveSFOFileListEntry[FILE_LIST_COUNT_MAX];
521
if (tmpDataSize != 0)
522
memcpy(updatedList, tmpDataOrig, std::min(tmpDataSize, FILE_LIST_TOTAL_SIZE));
523
if (tmpDataSize < FILE_LIST_TOTAL_SIZE)
524
memset(updatedList + tmpDataSize, 0, FILE_LIST_TOTAL_SIZE - tmpDataSize);
525
// Leave a hash there and unchanged if it was already there.
526
if (secureMode && param->dataBuf.IsValid()) {
527
const std::string saveFilename = GetFileName(param);
528
for (auto entry = updatedList; entry < updatedList + FILE_LIST_COUNT_MAX; ++entry) {
529
if (entry->filename[0] != '\0') {
530
if (strncmp(entry->filename, saveFilename.c_str(), sizeof(entry->filename)) != 0)
531
continue;
532
}
533
534
snprintf(entry->filename, sizeof(entry->filename), "%s", saveFilename.c_str());
535
memcpy(entry->hash, cryptedHash, 16);
536
break;
537
}
538
}
539
540
sfoFile->SetValue("SAVEDATA_FILE_LIST", (u8 *)updatedList, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE);
541
delete[] updatedList;
542
543
// Init param with 0. This will be used to detect crypted save or not on loading
544
u8 zeroes[128]{};
545
sfoFile->SetValue("SAVEDATA_PARAMS", zeroes, 128, 128);
546
547
u8 *sfoData;
548
size_t sfoSize;
549
sfoFile->WriteSFO(&sfoData, &sfoSize);
550
551
// Calc SFO hash for PSP.
552
if (cryptedData != 0 || (subWrite && wasCrypted)) {
553
int offset = sfoFile->GetDataOffset(sfoData, "SAVEDATA_PARAMS");
554
if (offset >= 0)
555
UpdateHash(sfoData, (int)sfoSize, offset, DetermineCryptMode(param));
556
}
557
558
ClearSFOCache();
559
WritePSPFile(sfopath, sfoData, (SceSize)sfoSize);
560
delete[] sfoData;
561
sfoData = nullptr;
562
563
if(param->dataBuf.IsValid()) // Can launch save without save data in mode 13
564
{
565
std::string fileName = GetFileName(param);
566
std::string filePath = dirPath + "/" + fileName;
567
u8 *data_ = 0;
568
SceSize saveSize = 0;
569
if(cryptedData == 0) // Save decrypted data
570
{
571
saveSize = param->dataSize;
572
if(saveSize == 0 || saveSize > param->dataBufSize)
573
saveSize = param->dataBufSize; // fallback, should never use this
574
575
data_ = param->dataBuf;
576
}
577
else
578
{
579
data_ = cryptedData;
580
saveSize = cryptedSize;
581
}
582
583
INFO_LOG(Log::sceUtility,"Saving file with size %u in %s",saveSize,filePath.c_str());
584
585
// copy back save name in request
586
strncpy(param->saveName, saveDirName.c_str(), 20);
587
588
if (!fileName.empty()) {
589
if (!WritePSPFile(filePath, data_, saveSize)) {
590
ERROR_LOG(Log::sceUtility, "Error writing file %s", filePath.c_str());
591
delete[] cryptedData;
592
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE;
593
}
594
}
595
delete[] cryptedData;
596
}
597
598
// SAVE ICON0
599
if (param->icon0FileData.buf.IsValid())
600
{
601
std::string icon0path = dirPath + "/" + ICON0_FILENAME;
602
WritePSPFile(icon0path, param->icon0FileData.buf, param->icon0FileData.size);
603
}
604
// SAVE ICON1
605
if (param->icon1FileData.buf.IsValid())
606
{
607
std::string icon1path = dirPath + "/" + ICON1_FILENAME;
608
WritePSPFile(icon1path, param->icon1FileData.buf, param->icon1FileData.size);
609
}
610
// SAVE PIC1
611
if (param->pic1FileData.buf.IsValid())
612
{
613
std::string pic1path = dirPath + "/" + PIC1_FILENAME;
614
WritePSPFile(pic1path, param->pic1FileData.buf, param->pic1FileData.size);
615
}
616
// Save SND
617
if (param->snd0FileData.buf.IsValid())
618
{
619
std::string snd0path = dirPath + "/" + SND0_FILENAME;
620
WritePSPFile(snd0path, param->snd0FileData.buf, param->snd0FileData.size);
621
}
622
return 0;
623
}
624
625
int SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &saveDirName, int saveId, bool secureMode) {
626
if (!param) {
627
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;
628
}
629
630
bool isRWMode = param->mode == SCE_UTILITY_SAVEDATA_TYPE_READDATA || param->mode == SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE;
631
632
std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));
633
std::string fileName = GetFileName(param);
634
std::string filePath = dirPath + "/" + fileName;
635
636
if (!pspFileSystem.GetFileInfo(dirPath).exists) {
637
return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA : SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;
638
}
639
640
if (!fileName.empty() && !pspFileSystem.GetFileInfo(filePath).exists) {
641
return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND : SCE_UTILITY_SAVEDATA_ERROR_LOAD_FILE_NOT_FOUND;
642
}
643
644
// If it wasn't zero, force to zero before loading and especially in case of error.
645
// This isn't reset if the path doesn't even exist.
646
param->dataSize = 0;
647
int result = LoadSaveData(param, saveDirName, dirPath, secureMode);
648
if (result != 0)
649
return result;
650
651
// Load sfo
652
if (!LoadSFO(param, dirPath)) {
653
WARN_LOG(Log::sceUtility, "Load: Failed to load SFO from %s", dirPath.c_str());
654
return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN : SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN;
655
}
656
657
// Don't know what it is, but PSP always respond this and this unlock some game
658
param->bind = 1021;
659
660
// Load other files, seems these are required by some games, e.g. Fushigi no Dungeon Fuurai no Shiren 4 Plus.
661
662
// Load ICON0.PNG
663
LoadFile(dirPath, ICON0_FILENAME, &param->icon0FileData);
664
// Load ICON1.PNG
665
LoadFile(dirPath, ICON1_FILENAME, &param->icon1FileData);
666
// Load PIC1.PNG
667
LoadFile(dirPath, PIC1_FILENAME, &param->pic1FileData);
668
// Load SND0.AT3
669
LoadFile(dirPath, SND0_FILENAME, &param->snd0FileData);
670
671
return 0;
672
}
673
674
int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string &dirPath, bool secureMode) {
675
if (param->secureVersion > 3) {
676
ERROR_LOG_REPORT(Log::sceUtility, "Savedata version requested: %d", param->secureVersion);
677
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;
678
} else if (param->secureVersion != 0) {
679
if (param->secureVersion != 1 && !HasKey(param) && secureMode) {
680
ERROR_LOG_REPORT(Log::sceUtility, "Savedata version with missing key: %d", param->secureVersion);
681
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;
682
}
683
WARN_LOG_REPORT(Log::sceUtility, "Savedata version requested: %d", param->secureVersion);
684
}
685
686
std::string filename = GetFileName(param);
687
std::string filePath = dirPath + "/" + filename;
688
// Blank filename always means success, if secureVersion was correct.
689
if (filename.empty())
690
return 0;
691
692
s64 readSize;
693
INFO_LOG(Log::sceUtility, "Loading file with size %u in %s", param->dataBufSize, filePath.c_str());
694
u8 *saveData = nullptr;
695
int saveSize = -1;
696
if (!ReadPSPFile(filePath, &saveData, saveSize, &readSize)) {
697
ERROR_LOG(Log::sceUtility,"Error reading file %s",filePath.c_str());
698
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;
699
}
700
saveSize = (int)readSize;
701
702
// copy back save name in request
703
strncpy(param->saveName, saveDirName.c_str(), 20);
704
705
int prevCryptMode = GetSaveCryptMode(param, saveDirName);
706
bool isCrypted = prevCryptMode != 0 && secureMode;
707
bool saveDone = false;
708
u32 loadedSize = 0;
709
if (isCrypted) {
710
if (DetermineCryptMode(param) > 1 && !HasKey(param)) {
711
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;
712
}
713
u8 hash[16];
714
bool hasExpectedHash = GetExpectedHash(dirPath, filename, hash);
715
loadedSize = LoadCryptedSave(param, param->dataBuf, saveData, saveSize, prevCryptMode, hasExpectedHash ? hash : nullptr, saveDone);
716
// TODO: Should return SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN here if !saveDone.
717
}
718
if (!saveDone) {
719
loadedSize = LoadNotCryptedSave(param, param->dataBuf, saveData, saveSize);
720
}
721
delete[] saveData;
722
723
// Ignore error codes.
724
if (loadedSize != 0 && (loadedSize & 0x80000000) == 0) {
725
std::string tag = "LoadSaveData/" + filePath;
726
NotifyMemInfo(MemBlockFlags::WRITE, param->dataBuf.ptr, loadedSize, tag.c_str(), tag.size());
727
}
728
729
if ((loadedSize & 0x80000000) != 0)
730
return loadedSize;
731
732
param->dataSize = (SceSize)saveSize;
733
return 0;
734
}
735
736
int SavedataParam::DetermineCryptMode(const SceUtilitySavedataParam *param) const {
737
int decryptMode = 1;
738
if (param->secureVersion == 1) {
739
decryptMode = 1;
740
} else if (param->secureVersion == 2) {
741
decryptMode = 3;
742
} else if (param->secureVersion == 3) {
743
decryptMode = GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 1;
744
} else if (HasKey(param)) {
745
// TODO: This should ignore HasKey(), which would trigger errors. Not doing that yet to play it safe.
746
decryptMode = GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 3;
747
}
748
return decryptMode;
749
}
750
751
u32 SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, const u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone) {
752
int orig_size = saveSize;
753
int align_len = align16(saveSize);
754
u8 *data_base = new u8[align_len];
755
u8 *cryptKey = new u8[0x10];
756
757
int decryptMode = DetermineCryptMode(param);
758
const int detectedMode = decryptMode;
759
bool hasKey;
760
761
auto resetData = [&](int mode) {
762
saveSize = orig_size;
763
align_len = align16(saveSize);
764
hasKey = mode > 1;
765
766
if (hasKey) {
767
memcpy(cryptKey, param->key, 0x10);
768
}
769
memcpy(data_base, saveData, saveSize);
770
memset(data_base + saveSize, 0, align_len - saveSize);
771
};
772
resetData(decryptMode);
773
774
if (decryptMode != prevCryptMode) {
775
if (prevCryptMode == 1 && param->key[0] == 0) {
776
// Backwards compat for a bug we used to have.
777
WARN_LOG(Log::sceUtility, "Savedata loading with hashmode %d instead of detected %d", prevCryptMode, decryptMode);
778
decryptMode = prevCryptMode;
779
780
// Don't notify the user if we're not going to upgrade the save.
781
if (!g_Config.bEncryptSave) {
782
auto di = GetI18NCategory(I18NCat::DIALOG);
783
g_OSD.Show(OSDType::MESSAGE_WARNING, di->T("When you save, it will load on a PSP, but not an older PPSSPP"), 6.0f);
784
g_OSD.Show(OSDType::MESSAGE_WARNING, di->T("Old savedata detected"), 6.0f);
785
}
786
} else {
787
if (decryptMode == 5 && prevCryptMode == 3) {
788
WARN_LOG(Log::sceUtility, "Savedata loading with detected hashmode %d instead of file's %d", decryptMode, prevCryptMode);
789
} else {
790
WARN_LOG_REPORT(Log::sceUtility, "Savedata loading with detected hashmode %d instead of file's %d", decryptMode, prevCryptMode);
791
}
792
793
decryptMode = prevCryptMode;
794
auto di = GetI18NCategory(I18NCat::DIALOG);
795
g_OSD.Show(OSDType::MESSAGE_WARNING, di->T("When you save, it will not work on outdated PSP Firmware anymore"), 6.0f);
796
g_OSD.Show(OSDType::MESSAGE_WARNING, di->T("Old savedata detected"), 6.0f);
797
}
798
hasKey = decryptMode > 1;
799
}
800
801
int err = DecryptData(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);
802
// Perhaps the file had the wrong mode....
803
if (err != 0 && detectedMode != decryptMode) {
804
resetData(detectedMode);
805
err = DecryptData(detectedMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);
806
}
807
// TODO: Should return an error, but let's just try with a bad hash.
808
if (err != 0 && expectedHash != nullptr) {
809
WARN_LOG(Log::sceUtility, "Incorrect hash on save data, likely corrupt");
810
resetData(decryptMode);
811
err = DecryptData(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, nullptr);
812
}
813
814
u32 sz = 0;
815
if (err == 0) {
816
if (param->dataBuf.IsValid()) {
817
if ((u32)saveSize > param->dataBufSize || !Memory::IsValidRange(param->dataBuf.ptr, saveSize)) {
818
sz = SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN;
819
} else {
820
sz = (u32)saveSize;
821
memcpy(data, data_base, sz);
822
}
823
}
824
saveDone = true;
825
}
826
delete[] data_base;
827
delete[] cryptKey;
828
829
return sz;
830
}
831
832
u32 SavedataParam::LoadNotCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize) {
833
if (param->dataBuf.IsValid()) {
834
if ((u32)saveSize > param->dataBufSize || !Memory::IsValidRange(param->dataBuf.ptr, saveSize)) {
835
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN;
836
}
837
memcpy(data, saveData, saveSize);
838
return saveSize;
839
}
840
return 0;
841
}
842
843
bool SavedataParam::LoadSFO(SceUtilitySavedataParam *param, const std::string& dirPath) {
844
std::string sfopath = dirPath + "/" + SFO_FILENAME;
845
std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfopath);
846
if (sfoFile) {
847
// copy back info in request
848
strncpy(param->sfoParam.title, sfoFile->GetValueString("TITLE").c_str(), 128);
849
strncpy(param->sfoParam.savedataTitle, sfoFile->GetValueString("SAVEDATA_TITLE").c_str(), 128);
850
strncpy(param->sfoParam.detail, sfoFile->GetValueString("SAVEDATA_DETAIL").c_str(), 1024);
851
param->sfoParam.parentalLevel = sfoFile->GetValueInt("PARENTAL_LEVEL");
852
return true;
853
}
854
return false;
855
}
856
857
std::vector<SaveSFOFileListEntry> SavedataParam::GetSFOEntries(const std::string &dirPath) {
858
std::vector<SaveSFOFileListEntry> result;
859
const std::string sfoPath = dirPath + "/" + SFO_FILENAME;
860
861
std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfoPath);
862
if (!sfoFile) {
863
return result;
864
}
865
866
u32 sfoFileListSize = 0;
867
SaveSFOFileListEntry *sfoFileList = (SaveSFOFileListEntry *)sfoFile->GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);
868
const u32 count = std::min((u32)FILE_LIST_COUNT_MAX, sfoFileListSize / (u32)sizeof(SaveSFOFileListEntry));
869
870
for (u32 i = 0; i < count; ++i) {
871
if (sfoFileList[i].filename[0] != '\0')
872
result.push_back(sfoFileList[i]);
873
}
874
875
return result;
876
}
877
878
std::set<std::string> SavedataParam::GetSecureFileNames(const std::string &dirPath) {
879
auto entries = GetSFOEntries(dirPath);
880
881
std::set<std::string> secureFileNames;
882
for (const auto &entry : entries) {
883
char temp[14]{};
884
truncate_cpy(temp, entry.filename);
885
secureFileNames.insert(temp);
886
}
887
return secureFileNames;
888
}
889
890
bool SavedataParam::GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]) {
891
auto entries = GetSFOEntries(dirPath);
892
893
for (auto entry : entries) {
894
if (strncmp(entry.filename, filename.c_str(), sizeof(entry.filename)) == 0) {
895
memcpy(hash, entry.hash, sizeof(entry.hash));
896
return true;
897
}
898
}
899
return false;
900
}
901
902
void SavedataParam::LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData) {
903
std::string filePath = dirPath + "/" + filename;
904
if (!fileData->buf.IsValid())
905
return;
906
907
u8 *buf = fileData->buf;
908
u32 size = Memory::ValidSize(fileData->buf.ptr, fileData->bufSize);
909
s64 readSize = -1;
910
if (ReadPSPFile(filePath, &buf, size, &readSize)) {
911
fileData->size = readSize;
912
const std::string tag = "SavedataLoad/" + filePath;
913
NotifyMemInfo(MemBlockFlags::WRITE, fileData->buf.ptr, fileData->size, tag.c_str(), tag.size());
914
INFO_LOG(Log::sceUtility, "Loaded subfile %s (size: %d bytes) into %08x", filePath.c_str(), fileData->size, fileData->buf.ptr);
915
} else {
916
WARN_LOG(Log::sceUtility, "Failed to load subfile %s into %08x", filePath.c_str(), fileData->buf.ptr);
917
}
918
}
919
920
// Note: The work is done in-place, hence the memmove etc.
921
int SavedataParam::EncryptData(unsigned int mode,
922
unsigned char *data,
923
int *dataLen,
924
int *alignedLen,
925
unsigned char *hash,
926
unsigned char *cryptkey)
927
{
928
pspChnnlsvContext1 ctx1{};
929
pspChnnlsvContext2 ctx2{};
930
931
INFO_LOG(Log::sceUtility, "EncryptData(mode=%d, *dataLen=%d, *alignedLen=%d)", mode, *dataLen, *alignedLen);
932
933
/* Make room for the IV in front of the data. */
934
memmove(data + 0x10, data, *alignedLen);
935
936
/* Set up buffers */
937
memset(hash, 0, 0x10);
938
939
// Zero out the IV before we begin.
940
memset(data, 0, 0x10);
941
942
/* Build the 0x10-byte IV and setup encryption */
943
if (sceSdCipherInit(ctx2, mode, 1, data, cryptkey) < 0)
944
return -1;
945
if (sceSdMacInit(ctx1, mode) < 0)
946
return -2;
947
if (sceSdMacUpdate(ctx1, data, 0x10) < 0)
948
return -3;
949
if (sceSdCipherUpdate(ctx2, data + 0x10, *alignedLen) < 0)
950
return -4;
951
952
/* Clear any extra bytes left from the previous steps */
953
memset(data + 0x10 + *dataLen, 0, *alignedLen - *dataLen);
954
955
/* Encrypt the data */
956
if (sceSdMacUpdate(ctx1, data + 0x10, *alignedLen) < 0)
957
return -5;
958
959
/* Verify encryption */
960
if (sceSdCipherFinal(ctx2) < 0)
961
return -6;
962
963
/* Build the file hash from this PSP */
964
if (sceSdMacFinal(ctx1, hash, cryptkey) < 0)
965
return -7;
966
967
/* Adjust sizes to account for IV */
968
*alignedLen += 0x10;
969
*dataLen += 0x10;
970
971
/* All done */
972
return 0;
973
}
974
975
// Note: The work is done in-place, hence the memmove etc.
976
int SavedataParam::DecryptData(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash) {
977
pspChnnlsvContext1 ctx1{};
978
pspChnnlsvContext2 ctx2{};
979
980
/* Need a 16-byte IV plus some data */
981
if (*alignedLen <= 0x10)
982
return -1;
983
*dataLen -= 0x10;
984
*alignedLen -= 0x10;
985
986
/* Perform the magic */
987
if (sceSdMacInit(ctx1, mode) < 0)
988
return -2;
989
if (sceSdCipherInit(ctx2, mode, 2, data, cryptkey) < 0)
990
return -3;
991
if (sceSdMacUpdate(ctx1, data, 0x10) < 0)
992
return -4;
993
if (sceSdMacUpdate(ctx1, data + 0x10, *alignedLen) < 0)
994
return -5;
995
if (sceSdCipherUpdate(ctx2, data + 0x10, *alignedLen) < 0)
996
return -6;
997
998
/* Verify that it decrypted correctly */
999
if (sceSdCipherFinal(ctx2) < 0)
1000
return -7;
1001
1002
if (expectedHash) {
1003
u8 hash[16];
1004
if (sceSdMacFinal(ctx1, hash, cryptkey) < 0)
1005
return -7;
1006
if (memcmp(hash, expectedHash, sizeof(hash)) != 0)
1007
return -8;
1008
}
1009
1010
/* The decrypted data starts at data + 0x10, so shift it back. */
1011
memmove(data, data + 0x10, *dataLen);
1012
return 0;
1013
}
1014
1015
// Requires sfoData to be padded with zeroes to the next 16-byte boundary (due to BuildHash)
1016
int SavedataParam::UpdateHash(u8* sfoData, int sfoSize, int sfoDataParamsOffset, int encryptmode)
1017
{
1018
int alignedLen = align16(sfoSize);
1019
memset(sfoData + sfoDataParamsOffset, 0, 128);
1020
u8 filehash[16];
1021
int ret = 0;
1022
1023
int firstHashMode = encryptmode & 2 ? 4 : 2;
1024
int secondHashMode = encryptmode & 2 ? 3 : 0;
1025
if (encryptmode & 4) {
1026
firstHashMode = 6;
1027
secondHashMode = 5;
1028
}
1029
1030
// Compute 11D0 hash over entire file
1031
if ((ret = BuildHash(filehash, sfoData, sfoSize, alignedLen, firstHashMode, 0)) < 0)
1032
{
1033
// Not sure about "2"
1034
return ret - 400;
1035
}
1036
1037
// Copy 11D0 hash to param.sfo and set flag indicating it's there
1038
memcpy(sfoData + sfoDataParamsOffset + 0x20, filehash, 0x10);
1039
*(sfoData + sfoDataParamsOffset) |= 0x01;
1040
1041
// If new encryption mode, compute and insert the 1220 hash.
1042
if (encryptmode & 6)
1043
{
1044
/* Enable the hash bit first */
1045
*(sfoData+sfoDataParamsOffset) |= (encryptmode & 6) << 4;
1046
1047
if ((ret = BuildHash(filehash, sfoData, sfoSize, alignedLen, secondHashMode, 0)) < 0)
1048
{
1049
return ret - 500;
1050
}
1051
memcpy(sfoData+sfoDataParamsOffset + 0x70, filehash, 0x10);
1052
}
1053
1054
/* Compute and insert the 11C0 hash. */
1055
if ((ret = BuildHash(filehash, sfoData, sfoSize, alignedLen, 1, 0)) < 0)
1056
{
1057
return ret - 600;
1058
}
1059
memcpy(sfoData+sfoDataParamsOffset + 0x10, filehash, 0x10);
1060
1061
/* All done. */
1062
return 0;
1063
}
1064
1065
// Requires sfoData to be padded with zeroes to the next 16-byte boundary.
1066
int SavedataParam::BuildHash(uint8_t *output,
1067
const uint8_t *data,
1068
unsigned int len,
1069
unsigned int alignedLen,
1070
int mode,
1071
const uint8_t *cryptkey) {
1072
pspChnnlsvContext1 ctx1;
1073
1074
/* Set up buffers */
1075
memset(&ctx1, 0, sizeof(pspChnnlsvContext1));
1076
memset(output, 0, 0x10);
1077
1078
/* Perform the magic */
1079
if (sceSdMacInit(ctx1, mode & 0xFF) < 0)
1080
return -1;
1081
if (sceSdMacUpdate(ctx1, data, alignedLen) < 0)
1082
return -2;
1083
if (sceSdMacFinal(ctx1, output, cryptkey) < 0)
1084
{
1085
// Got here since Kirk CMD5 missing, return random value;
1086
memset(output,0x1,0x10);
1087
return 0;
1088
}
1089
/* All done. */
1090
return 0;
1091
}
1092
1093
// TODO: Merge with NiceSizeFormat? That one has a decimal though.
1094
std::string SavedataParam::GetSpaceText(u64 size, bool roundUp)
1095
{
1096
char text[50];
1097
static const char * const suffixes[] = {"B", "KB", "MB", "GB"};
1098
for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i)
1099
{
1100
if (size < 1024)
1101
{
1102
snprintf(text, sizeof(text), "%lld %s", size, suffixes[i]);
1103
return std::string(text);
1104
}
1105
if (roundUp) {
1106
size = (size + 1023) / 1024;
1107
} else {
1108
size /= 1024;
1109
}
1110
}
1111
snprintf(text, sizeof(text), "%llu TB", size);
1112
return std::string(text);
1113
}
1114
1115
inline std::string FmtPspTime(const ScePspDateTime &dt) {
1116
return StringFromFormat("%04d-%02d-%02d %02d:%02d:%02d.%06d", dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond);
1117
}
1118
1119
int SavedataParam::GetSizes(SceUtilitySavedataParam *param) {
1120
if (!param) {
1121
return SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_DATA;
1122
}
1123
1124
int ret = 0;
1125
if (param->msFree.IsValid())
1126
{
1127
const u64 freeBytes = MemoryStick_FreeSpace(GetGameName(param));
1128
param->msFree->clusterSize = (u32)MemoryStick_SectorSize();
1129
param->msFree->freeClusters = (u32)(freeBytes / MemoryStick_SectorSize());
1130
param->msFree->freeSpaceKB = (u32)(freeBytes / 0x400);
1131
const std::string spaceTxt = SavedataParam::GetSpaceText(freeBytes, false);
1132
memset(param->msFree->freeSpaceStr, 0, sizeof(param->msFree->freeSpaceStr));
1133
strncpy(param->msFree->freeSpaceStr, spaceTxt.c_str(), sizeof(param->msFree->freeSpaceStr));
1134
NotifyMemInfo(MemBlockFlags::WRITE, param->msFree.ptr, sizeof(SceUtilitySavedataMsFreeInfo), "SavedataGetSizes");
1135
}
1136
if (param->msData.IsValid())
1137
{
1138
const SceUtilitySavedataMsDataInfo *msData = param->msData;
1139
const std::string gameName(msData->gameName, strnlen(msData->gameName, sizeof(msData->gameName)));
1140
const std::string saveName(msData->saveName, strnlen(msData->saveName, sizeof(msData->saveName)));
1141
// TODO: How should <> be handled?
1142
std::string path = GetSaveFilePath(param, gameName + (saveName == "<>" ? "" : saveName));
1143
bool listingExists = false;
1144
auto listing = pspFileSystem.GetDirListing(path, &listingExists);
1145
if (listingExists) {
1146
param->msData->info.usedClusters = 0;
1147
for (auto &item : listing) {
1148
param->msData->info.usedClusters += (item.size + (u32)MemoryStick_SectorSize() - 1) / (u32)MemoryStick_SectorSize();
1149
}
1150
1151
// The usedSpaceKB value is definitely based on clusters, not bytes or even KB.
1152
// Fieldrunners expects 736 KB, even though the files add up to ~600 KB.
1153
int total_size = param->msData->info.usedClusters * (u32)MemoryStick_SectorSize();
1154
param->msData->info.usedSpaceKB = total_size / 0x400;
1155
std::string spaceTxt = SavedataParam::GetSpaceText(total_size, true);
1156
strncpy(param->msData->info.usedSpaceStr, spaceTxt.c_str(), sizeof(param->msData->info.usedSpaceStr));
1157
1158
// TODO: What does this mean, then? Seems to be the same.
1159
param->msData->info.usedSpace32KB = param->msData->info.usedSpaceKB;
1160
strncpy(param->msData->info.usedSpace32Str, spaceTxt.c_str(), sizeof(param->msData->info.usedSpace32Str));
1161
}
1162
else
1163
{
1164
param->msData->info.usedClusters = 0;
1165
param->msData->info.usedSpaceKB = 0;
1166
strncpy(param->msData->info.usedSpaceStr, "", sizeof(param->msData->info.usedSpaceStr));
1167
param->msData->info.usedSpace32KB = 0;
1168
strncpy(param->msData->info.usedSpace32Str, "", sizeof(param->msData->info.usedSpace32Str));
1169
ret = SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_DATA;
1170
}
1171
NotifyMemInfo(MemBlockFlags::WRITE, param->msData.ptr, sizeof(SceUtilitySavedataMsDataInfo), "SavedataGetSizes");
1172
}
1173
if (param->utilityData.IsValid())
1174
{
1175
int total_size = 0;
1176
1177
// The directory record itself.
1178
// TODO: Account for number of files / actual record size?
1179
total_size += getSizeNormalized(1);
1180
// Account for the SFO (is this always 1 sector?)
1181
total_size += getSizeNormalized(1);
1182
// Add the size of the data itself (don't forget encryption overhead.)
1183
// This is only added if a filename is specified.
1184
if (param->fileName[0] != 0) {
1185
if (g_Config.bEncryptSave) {
1186
total_size += getSizeNormalized((u32)param->dataSize + 16);
1187
} else {
1188
total_size += getSizeNormalized((u32)param->dataSize);
1189
}
1190
}
1191
total_size += getSizeNormalized(param->icon0FileData.size);
1192
total_size += getSizeNormalized(param->icon1FileData.size);
1193
total_size += getSizeNormalized(param->pic1FileData.size);
1194
total_size += getSizeNormalized(param->snd0FileData.size);
1195
1196
param->utilityData->usedClusters = total_size / (u32)MemoryStick_SectorSize();
1197
param->utilityData->usedSpaceKB = total_size / 0x400;
1198
std::string spaceTxt = SavedataParam::GetSpaceText(total_size, true);
1199
memset(param->utilityData->usedSpaceStr, 0, sizeof(param->utilityData->usedSpaceStr));
1200
strncpy(param->utilityData->usedSpaceStr, spaceTxt.c_str(), sizeof(param->utilityData->usedSpaceStr));
1201
1202
// TODO: Maybe these are rounded to the nearest 32KB? Or something?
1203
param->utilityData->usedSpace32KB = total_size / 0x400;
1204
std::string spaceTxt32 = SavedataParam::GetSpaceText(total_size, true);
1205
memset(param->utilityData->usedSpace32Str, 0, sizeof(param->utilityData->usedSpace32Str));
1206
strncpy(param->utilityData->usedSpace32Str, spaceTxt32.c_str(), sizeof(param->utilityData->usedSpace32Str));
1207
1208
INFO_LOG(Log::sceUtility, "GetSize: usedSpaceKB: %d (str: %s) (clusters: %d)", param->utilityData->usedSpaceKB, spaceTxt.c_str(), param->utilityData->usedClusters);
1209
INFO_LOG(Log::sceUtility, "GetSize: usedSpace32KB: %d (str32: %s)", param->utilityData->usedSpace32KB, spaceTxt32.c_str());
1210
1211
NotifyMemInfo(MemBlockFlags::WRITE, param->utilityData.ptr, sizeof(SceUtilitySavedataUsedDataInfo), "SavedataGetSizes");
1212
}
1213
return ret;
1214
}
1215
1216
bool SavedataParam::GetList(SceUtilitySavedataParam *param)
1217
{
1218
if (!param) {
1219
return false;
1220
}
1221
1222
if (param->idList.IsValid())
1223
{
1224
u32 maxFileCount = param->idList->maxCount;
1225
1226
std::vector<PSPFileInfo> validDir;
1227
std::vector<PSPFileInfo> sfoFiles;
1228
1229
// TODO: Here we can filter by prefix - only the savename in param is likely to be a regex.
1230
std::vector<PSPFileInfo> allDir = pspFileSystem.GetDirListing(savePath);
1231
1232
std::string searchString = GetGameName(param) + GetSaveName(param);
1233
for (size_t i = 0; i < allDir.size() && validDir.size() < maxFileCount; i++) {
1234
std::string dirName = allDir[i].name;
1235
if (PSPMatch(dirName, searchString)) {
1236
validDir.push_back(allDir[i]);
1237
}
1238
}
1239
1240
PSPFileInfo sfoFile;
1241
for (size_t i = 0; i < validDir.size(); ++i) {
1242
// GetFileName(param) == null here
1243
// so use sfo files to set the date.
1244
sfoFile = pspFileSystem.GetFileInfo(savePath + validDir[i].name + "/" + SFO_FILENAME);
1245
sfoFiles.push_back(sfoFile);
1246
}
1247
1248
SceUtilitySavedataIdListEntry *entries = param->idList->entries;
1249
for (u32 i = 0; i < (u32)validDir.size(); i++)
1250
{
1251
entries[i].st_mode = 0x11FF;
1252
if (sfoFiles[i].exists) {
1253
__IoCopyDate(entries[i].st_ctime, sfoFiles[i].ctime);
1254
__IoCopyDate(entries[i].st_atime, sfoFiles[i].atime);
1255
__IoCopyDate(entries[i].st_mtime, sfoFiles[i].mtime);
1256
} else {
1257
__IoCopyDate(entries[i].st_ctime, validDir[i].ctime);
1258
__IoCopyDate(entries[i].st_atime, validDir[i].atime);
1259
__IoCopyDate(entries[i].st_mtime, validDir[i].mtime);
1260
}
1261
// folder name without gamename (max 20 u8)
1262
std::string outName = validDir[i].name.substr(GetGameName(param).size());
1263
memset(entries[i].name, 0, sizeof(entries[i].name));
1264
strncpy(entries[i].name, outName.c_str(), sizeof(entries[i].name));
1265
}
1266
// Save num of folder found
1267
param->idList->resultCount = (u32)validDir.size();
1268
// Log out the listing.
1269
if (GenericLogEnabled(Log::sceUtility, LogLevel::LINFO)) {
1270
INFO_LOG(Log::sceUtility, "LIST (searchstring=%s): %d files (max: %d)", searchString.c_str(), param->idList->resultCount, maxFileCount);
1271
for (int i = 0; i < validDir.size(); i++) {
1272
INFO_LOG(Log::sceUtility, "%s: mode %08x, ctime: %s, atime: %s, mtime: %s",
1273
entries[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());
1274
}
1275
}
1276
NotifyMemInfo(MemBlockFlags::WRITE, param->idList.ptr, sizeof(SceUtilitySavedataIdListInfo), "SavedataGetList");
1277
NotifyMemInfo(MemBlockFlags::WRITE, param->idList->entries.ptr, (uint32_t)validDir.size() * sizeof(SceUtilitySavedataIdListEntry), "SavedataGetList");
1278
}
1279
return true;
1280
}
1281
1282
int SavedataParam::GetFilesList(SceUtilitySavedataParam *param, u32 requestAddr) {
1283
if (!param) {
1284
return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_STATUS;
1285
}
1286
1287
if (!param->fileList.IsValid()) {
1288
ERROR_LOG_REPORT(Log::sceUtility, "SavedataParam::GetFilesList(): bad fileList address %08x", param->fileList.ptr);
1289
// Should crash.
1290
return -1;
1291
}
1292
1293
PSPPointer<SceUtilitySavedataFileListInfo> fileList = param->fileList;
1294
if (fileList->secureEntries.IsValid() && fileList->maxSecureEntries > 99) {
1295
ERROR_LOG_REPORT(Log::sceUtility, "SavedataParam::GetFilesList(): too many secure entries, %d", fileList->maxSecureEntries);
1296
return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;
1297
}
1298
if (fileList->normalEntries.IsValid() && fileList->maxNormalEntries > 8192) {
1299
ERROR_LOG_REPORT(Log::sceUtility, "SavedataParam::GetFilesList(): too many normal entries, %d", fileList->maxNormalEntries);
1300
return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;
1301
}
1302
if (sceKernelGetCompiledSdkVersion() >= 0x02060000) {
1303
if (fileList->systemEntries.IsValid() && fileList->maxSystemEntries > 5) {
1304
ERROR_LOG_REPORT(Log::sceUtility, "SavedataParam::GetFilesList(): too many system entries, %d", fileList->maxSystemEntries);
1305
return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;
1306
}
1307
}
1308
1309
std::string dirPath = savePath + GetGameName(param) + GetSaveName(param);
1310
bool dirPathExists = false;
1311
auto files = pspFileSystem.GetDirListing(dirPath, &dirPathExists);
1312
if (!dirPathExists) {
1313
DEBUG_LOG(Log::sceUtility, "SavedataParam::GetFilesList(): directory %s does not exist", dirPath.c_str());
1314
return SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
1315
}
1316
1317
// Even if there are no files, initialize to 0.
1318
fileList->resultNumSecureEntries = 0;
1319
fileList->resultNumNormalEntries = 0;
1320
fileList->resultNumSystemEntries = 0;
1321
1322
// We need PARAM.SFO's SAVEDATA_FILE_LIST to determine which entries are secure.
1323
PSPFileInfo sfoFileInfo = FileFromListing(files, SFO_FILENAME);
1324
std::set<std::string> secureFilenames;
1325
1326
if (sfoFileInfo.exists) {
1327
secureFilenames = GetSecureFileNames(dirPath);
1328
} else {
1329
return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;
1330
}
1331
1332
// TODO: Does this always happen?
1333
// Don't know what it is, but PSP always respond this.
1334
param->bind = 1021;
1335
// This should be set around the same time as the file data. This runs on a thread, so set immediately.
1336
auto requestPtr = PSPPointer<SceUtilitySavedataParam>::Create(requestAddr);
1337
requestPtr->bind = 1021;
1338
1339
// Does not list directories, nor recurse into them, and ignores files not ALL UPPERCASE.
1340
bool isCrypted = GetSaveCryptMode(param, GetSaveDirName(param, 0)) != 0;
1341
for (auto file = files.begin(), end = files.end(); file != end; ++file) {
1342
if (file->type == FILETYPE_DIRECTORY) {
1343
continue;
1344
}
1345
// TODO: What are the exact rules? It definitely skips lowercase, and allows FILE or FILE.EXT.
1346
if (file->name.find_first_of("abcdefghijklmnopqrstuvwxyz") != file->name.npos) {
1347
DEBUG_LOG(Log::sceUtility, "SavedataParam::GetFilesList(): skipping file %s with lowercase", file->name.c_str());
1348
continue;
1349
}
1350
1351
bool isSystemFile = file->name == ICON0_FILENAME || file->name == ICON1_FILENAME || file->name == PIC1_FILENAME;
1352
isSystemFile = isSystemFile || file->name == SND0_FILENAME || file->name == SFO_FILENAME;
1353
1354
SceUtilitySavedataFileListEntry *entry = NULL;
1355
int sizeOffset = 0;
1356
if (isSystemFile) {
1357
if (fileList->systemEntries.IsValid() && fileList->resultNumSystemEntries < fileList->maxSystemEntries) {
1358
entry = &fileList->systemEntries[fileList->resultNumSystemEntries++];
1359
}
1360
} else if (secureFilenames.find(file->name) != secureFilenames.end()) {
1361
if (fileList->secureEntries.IsValid() && fileList->resultNumSecureEntries < fileList->maxSecureEntries) {
1362
entry = &fileList->secureEntries[fileList->resultNumSecureEntries++];
1363
}
1364
// Secure files are slightly bigger.
1365
if (isCrypted) {
1366
sizeOffset = -0x10;
1367
}
1368
} else {
1369
if (fileList->normalEntries.IsValid() && fileList->resultNumNormalEntries < fileList->maxNormalEntries) {
1370
entry = &fileList->normalEntries[fileList->resultNumNormalEntries++];
1371
}
1372
}
1373
1374
// Out of space for this file in the list.
1375
if (entry == NULL) {
1376
continue;
1377
}
1378
1379
entry->st_mode = 0x21FF;
1380
entry->st_size = file->size + sizeOffset;
1381
__IoCopyDate(entry->st_ctime, file->ctime);
1382
__IoCopyDate(entry->st_atime, file->atime);
1383
__IoCopyDate(entry->st_mtime, file->mtime);
1384
// TODO: Probably actually 13 + 3 pad...
1385
strncpy(entry->name, file->name.c_str(), 16);
1386
entry->name[15] = '\0';
1387
}
1388
1389
if (GenericLogEnabled(Log::sceUtility, LogLevel::LINFO)) {
1390
INFO_LOG(Log::sceUtility, "FILES: %d files listed (+ %d system, %d secure)", fileList->resultNumNormalEntries, fileList->resultNumSystemEntries, fileList->resultNumSecureEntries);
1391
if (fileList->normalEntries.IsValid()) {
1392
for (int i = 0; i < (int)fileList->resultNumNormalEntries; i++) {
1393
const SceUtilitySavedataFileListEntry &info = fileList->normalEntries[i];
1394
INFO_LOG(Log::sceUtility, "%s: mode %08x, ctime: %s, atime: %s, mtime: %s",
1395
info.name, info.st_mode, FmtPspTime(info.st_ctime).c_str(), FmtPspTime(info.st_atime).c_str(), FmtPspTime(info.st_mtime).c_str());
1396
}
1397
} else if (fileList->resultNumNormalEntries > 0) {
1398
WARN_LOG(Log::sceUtility, "Invalid normalEntries pointer (%d entries)", fileList->resultNumNormalEntries);
1399
}
1400
// TODO: Log system and secure entries?
1401
}
1402
1403
NotifyMemInfo(MemBlockFlags::WRITE, fileList.ptr, sizeof(SceUtilitySavedataFileListInfo), "SavedataGetFilesList");
1404
if (fileList->resultNumSystemEntries != 0)
1405
NotifyMemInfo(MemBlockFlags::WRITE, fileList->systemEntries.ptr, fileList->resultNumSystemEntries * sizeof(SceUtilitySavedataFileListEntry), "SavedataGetFilesList");
1406
if (fileList->resultNumSecureEntries != 0)
1407
NotifyMemInfo(MemBlockFlags::WRITE, fileList->secureEntries.ptr, fileList->resultNumSecureEntries * sizeof(SceUtilitySavedataFileListEntry), "SavedataGetFilesList");
1408
if (fileList->resultNumNormalEntries != 0)
1409
NotifyMemInfo(MemBlockFlags::WRITE, fileList->normalEntries.ptr, fileList->resultNumNormalEntries * sizeof(SceUtilitySavedataFileListEntry), "SavedataGetFilesList");
1410
1411
return 0;
1412
}
1413
1414
bool SavedataParam::GetSize(SceUtilitySavedataParam *param) {
1415
if (!param) {
1416
return false;
1417
}
1418
1419
const std::string saveDir = savePath + GetGameName(param) + GetSaveName(param);
1420
bool exists = false;
1421
1422
if (param->sizeInfo.IsValid()) {
1423
auto listing = pspFileSystem.GetDirListing(saveDir, &exists);
1424
const u64 freeBytes = MemoryStick_FreeSpace(GetGameName(param));
1425
1426
s64 overwriteBytes = 0;
1427
s64 writeBytes = 0;
1428
for (int i = 0; i < param->sizeInfo->numNormalEntries; ++i) {
1429
const auto &entry = param->sizeInfo->normalEntries[i];
1430
overwriteBytes += FileFromListing(listing, entry.name).size;
1431
writeBytes += entry.size;
1432
}
1433
for (int i = 0; i < param->sizeInfo->numSecureEntries; ++i) {
1434
const auto &entry = param->sizeInfo->secureEntries[i];
1435
overwriteBytes += FileFromListing(listing, entry.name).size;
1436
writeBytes += entry.size + 0x10;
1437
}
1438
1439
param->sizeInfo->sectorSize = (int)MemoryStick_SectorSize();
1440
param->sizeInfo->freeSectors = (int)(freeBytes / MemoryStick_SectorSize());
1441
1442
// TODO: Is this after the specified files? Probably before?
1443
param->sizeInfo->freeKB = (int)(freeBytes / 1024);
1444
std::string spaceTxt = SavedataParam::GetSpaceText(freeBytes, false);
1445
truncate_cpy(param->sizeInfo->freeString, spaceTxt.c_str());
1446
1447
if (writeBytes - overwriteBytes < (s64)freeBytes) {
1448
param->sizeInfo->neededKB = 0;
1449
1450
// Note: this is "needed to overwrite".
1451
param->sizeInfo->overwriteKB = 0;
1452
1453
spaceTxt = GetSpaceText(0, true);
1454
truncate_cpy(param->sizeInfo->neededString, spaceTxt);
1455
truncate_cpy(param->sizeInfo->overwriteString, spaceTxt);
1456
} else {
1457
// Bytes needed to save additional data.
1458
s64 neededBytes = writeBytes - freeBytes;
1459
param->sizeInfo->neededKB = (neededBytes + 1023) / 1024;
1460
spaceTxt = GetSpaceText(neededBytes, true);
1461
truncate_cpy(param->sizeInfo->neededString, spaceTxt);
1462
1463
if (writeBytes - overwriteBytes < (s64)freeBytes) {
1464
param->sizeInfo->overwriteKB = 0;
1465
spaceTxt = GetSpaceText(0, true);
1466
truncate_cpy(param->sizeInfo->overwriteString, spaceTxt);
1467
} else {
1468
s64 neededOverwriteBytes = writeBytes - freeBytes - overwriteBytes;
1469
param->sizeInfo->overwriteKB = (neededOverwriteBytes + 1023) / 1024;
1470
spaceTxt = GetSpaceText(neededOverwriteBytes, true);
1471
truncate_cpy(param->sizeInfo->overwriteString, spaceTxt);
1472
}
1473
}
1474
1475
INFO_LOG(Log::sceUtility, "SectorSize: %d FreeSectors: %d FreeKB: %d neededKb: %d overwriteKb: %d",
1476
param->sizeInfo->sectorSize, param->sizeInfo->freeSectors, param->sizeInfo->freeKB, param->sizeInfo->neededKB, param->sizeInfo->overwriteKB);
1477
1478
NotifyMemInfo(MemBlockFlags::WRITE, param->sizeInfo.ptr, sizeof(PspUtilitySavedataSizeInfo), "SavedataGetSize");
1479
}
1480
1481
return exists;
1482
}
1483
1484
void SavedataParam::Clear()
1485
{
1486
if (saveDataList)
1487
{
1488
for (int i = 0; i < saveNameListDataCount; i++)
1489
{
1490
if (saveDataList[i].texture != NULL && (!noSaveIcon || saveDataList[i].texture != noSaveIcon->texture))
1491
delete saveDataList[i].texture;
1492
saveDataList[i].texture = NULL;
1493
}
1494
1495
delete [] saveDataList;
1496
saveDataList = NULL;
1497
saveDataListCount = 0;
1498
}
1499
if (noSaveIcon)
1500
{
1501
delete noSaveIcon->texture;
1502
noSaveIcon->texture = NULL;
1503
delete noSaveIcon;
1504
noSaveIcon = NULL;
1505
}
1506
}
1507
1508
int SavedataParam::SetPspParam(SceUtilitySavedataParam *param)
1509
{
1510
pspParam = param;
1511
if (!pspParam) {
1512
Clear();
1513
return 0;
1514
}
1515
1516
std::string gameName = GetGameName(param);
1517
if (!gameName.empty()) {
1518
MemoryStick_NotifyGameName(gameName);
1519
}
1520
1521
if (param->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTALLDELETE) {
1522
Clear();
1523
int realCount = 0;
1524
auto allSaves = pspFileSystem.GetDirListing(savePath);
1525
saveDataListCount = (int)allSaves.size();
1526
saveDataList = new SaveFileInfo[saveDataListCount];
1527
for (auto save : allSaves) {
1528
if (save.type != FILETYPE_DIRECTORY || save.name == "." || save.name == "..")
1529
continue;
1530
std::string fileDataDir = savePath + save.name;
1531
PSPFileInfo info = GetSaveInfo(fileDataDir);
1532
SetFileInfo(realCount, info, "", save.name);
1533
realCount++;
1534
}
1535
saveNameListDataCount = realCount;
1536
return 0;
1537
}
1538
1539
bool listEmptyFile = true;
1540
if (param->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTLOAD || param->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTDELETE) {
1541
listEmptyFile = false;
1542
}
1543
1544
SceUtilitySavedataSaveName *saveNameListData;
1545
bool hasMultipleFileName = false;
1546
if (param->saveNameList.IsValid()) {
1547
Clear();
1548
1549
saveNameListData = param->saveNameList;
1550
1551
// Get number of fileName in array
1552
saveDataListCount = 0;
1553
while (saveNameListData[saveDataListCount][0] != 0) {
1554
saveDataListCount++;
1555
}
1556
1557
if (saveDataListCount > 0 && WouldHaveMultiSaveName(param)) {
1558
hasMultipleFileName = true;
1559
saveDataList = new SaveFileInfo[saveDataListCount];
1560
1561
// get and stock file info for each file
1562
int realCount = 0;
1563
1564
// TODO: Filter away non-directories directly?
1565
std::vector<PSPFileInfo> allSaves = pspFileSystem.GetDirListing(savePath);
1566
1567
std::string gameName = GetGameName(param);
1568
1569
for (int i = 0; i < saveDataListCount; i++) {
1570
// "<>" means saveName can be anything...
1571
if (strncmp(saveNameListData[i], "<>", ARRAY_SIZE(saveNameListData[i])) == 0) {
1572
// TODO: Maybe we need a way to reorder the files?
1573
for (auto it = allSaves.begin(); it != allSaves.end(); ++it) {
1574
if (it->name.compare(0, gameName.length(), gameName) == 0) {
1575
std::string saveName = it->name.substr(gameName.length());
1576
1577
if (IsInSaveDataList(saveName, realCount)) // Already in SaveDataList, skip...
1578
continue;
1579
1580
std::string fileDataPath = savePath + it->name;
1581
if (it->exists) {
1582
SetFileInfo(realCount, *it, saveName);
1583
++realCount;
1584
} else {
1585
if (listEmptyFile) {
1586
// If file doesn't exist,we only skip...
1587
continue;
1588
}
1589
}
1590
break;
1591
}
1592
}
1593
continue;
1594
}
1595
1596
const std::string thisSaveName = FixedToString(saveNameListData[i], ARRAY_SIZE(saveNameListData[i]));
1597
1598
const std::string folderName = gameName + thisSaveName;
1599
1600
// Check if thisSaveName is in the list before processing.
1601
// This is hopefully faster than doing file I/O.
1602
bool found = false;
1603
for (int i = 0; i < allSaves.size(); i++) {
1604
if (allSaves[i].name == folderName) {
1605
found = true;
1606
}
1607
}
1608
1609
const std::string fileDataDir = savePath + gameName + thisSaveName;
1610
if (found) {
1611
PSPFileInfo info = GetSaveInfo(fileDataDir);
1612
if (info.exists) {
1613
SetFileInfo(realCount, info, thisSaveName);
1614
INFO_LOG(Log::sceUtility, "Save data exists: %s = %s", thisSaveName.c_str(), fileDataDir.c_str());
1615
realCount++;
1616
} else {
1617
found = false;
1618
}
1619
}
1620
1621
if (!found) { // NOTE: May be changed above, can't merge with the expression
1622
if (listEmptyFile) {
1623
ClearFileInfo(saveDataList[realCount], thisSaveName);
1624
DEBUG_LOG(Log::sceUtility, "Listing missing save data: %s = %s", thisSaveName.c_str(), fileDataDir.c_str());
1625
realCount++;
1626
} else {
1627
INFO_LOG(Log::sceUtility, "Save data not found: %s = %s", thisSaveName.c_str(), fileDataDir.c_str());
1628
}
1629
}
1630
}
1631
saveNameListDataCount = realCount;
1632
}
1633
}
1634
// Load info on only save
1635
if (!hasMultipleFileName) {
1636
saveNameListData = 0;
1637
1638
Clear();
1639
saveDataList = new SaveFileInfo[1];
1640
saveDataListCount = 1;
1641
1642
// get and stock file info for each file
1643
std::string fileDataDir = savePath + GetGameName(param) + GetSaveName(param);
1644
PSPFileInfo info = GetSaveInfo(fileDataDir);
1645
if (info.exists) {
1646
SetFileInfo(0, info, GetSaveName(param));
1647
INFO_LOG(Log::sceUtility, "Save data exists: %s = %s", GetSaveName(param).c_str(), fileDataDir.c_str());
1648
saveNameListDataCount = 1;
1649
} else {
1650
if (listEmptyFile) {
1651
ClearFileInfo(saveDataList[0], GetSaveName(param));
1652
DEBUG_LOG(Log::sceUtility, "Listing missing save data: %s = %s", GetSaveName(param).c_str(), fileDataDir.c_str());
1653
} else {
1654
INFO_LOG(Log::sceUtility, "Save data not found: %s = %s", GetSaveName(param).c_str(), fileDataDir.c_str());
1655
}
1656
saveNameListDataCount = 0;
1657
return 0;
1658
}
1659
}
1660
return 0;
1661
}
1662
1663
void SavedataParam::SetFileInfo(SaveFileInfo &saveInfo, PSPFileInfo &info, const std::string &saveName, const std::string &savrDir)
1664
{
1665
saveInfo.size = info.size;
1666
saveInfo.saveName = saveName;
1667
saveInfo.idx = 0;
1668
saveInfo.modif_time = info.mtime;
1669
1670
std::string saveDir = savrDir.empty() ? GetGameName(pspParam) + saveName : savrDir;
1671
saveInfo.saveDir = saveDir;
1672
1673
// Start with a blank slate.
1674
if (saveInfo.texture != NULL) {
1675
if (!noSaveIcon || saveInfo.texture != noSaveIcon->texture) {
1676
delete saveInfo.texture;
1677
}
1678
saveInfo.texture = NULL;
1679
}
1680
saveInfo.title[0] = 0;
1681
saveInfo.saveTitle[0] = 0;
1682
saveInfo.saveDetail[0] = 0;
1683
1684
// Search save image icon0
1685
// TODO : If icon0 don't exist, need to use icon1 which is a moving icon. Also play sound
1686
if (!ignoreTextures_) {
1687
saveInfo.texture = new PPGeImage(savePath + saveDir + "/" + ICON0_FILENAME);
1688
}
1689
1690
// Load info in PARAM.SFO
1691
std::string sfoFilename = savePath + saveDir + "/" + SFO_FILENAME;
1692
std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfoFilename);
1693
if (sfoFile) {
1694
SetStringFromSFO(*sfoFile, "TITLE", saveInfo.title, sizeof(saveInfo.title));
1695
SetStringFromSFO(*sfoFile, "SAVEDATA_TITLE", saveInfo.saveTitle, sizeof(saveInfo.saveTitle));
1696
SetStringFromSFO(*sfoFile, "SAVEDATA_DETAIL", saveInfo.saveDetail, sizeof(saveInfo.saveDetail));
1697
} else {
1698
saveInfo.broken = true;
1699
truncate_cpy(saveInfo.title, saveDir);
1700
}
1701
}
1702
1703
void SavedataParam::SetFileInfo(int idx, PSPFileInfo &info, const std::string &saveName, const std::string &saveDir)
1704
{
1705
SetFileInfo(saveDataList[idx], info, saveName, saveDir);
1706
saveDataList[idx].idx = idx;
1707
}
1708
1709
void SavedataParam::ClearFileInfo(SaveFileInfo &saveInfo, const std::string &saveName) {
1710
saveInfo.size = 0;
1711
saveInfo.saveName = saveName;
1712
saveInfo.idx = 0;
1713
saveInfo.broken = false;
1714
if (saveInfo.texture != NULL) {
1715
if (!noSaveIcon || saveInfo.texture != noSaveIcon->texture) {
1716
delete saveInfo.texture;
1717
}
1718
saveInfo.texture = NULL;
1719
}
1720
1721
if (GetPspParam()->newData.IsValid() && GetPspParam()->newData->buf.IsValid()) {
1722
// We may have a png to show
1723
if (!noSaveIcon) {
1724
noSaveIcon = new SaveFileInfo();
1725
PspUtilitySavedataFileData *newData = GetPspParam()->newData;
1726
if (Memory::IsValidRange(newData->buf.ptr, newData->size)) {
1727
noSaveIcon->texture = new PPGeImage(newData->buf.ptr, (SceSize)newData->size);
1728
}
1729
}
1730
saveInfo.texture = noSaveIcon->texture;
1731
} else if ((u32)GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_SAVE && GetPspParam()->icon0FileData.buf.IsValid()) {
1732
const PspUtilitySavedataFileData &icon0FileData = GetPspParam()->icon0FileData;
1733
saveInfo.texture = new PPGeImage(icon0FileData.buf.ptr, (SceSize)icon0FileData.size);
1734
}
1735
}
1736
1737
PSPFileInfo SavedataParam::GetSaveInfo(const std::string &saveDir) {
1738
PSPFileInfo info = pspFileSystem.GetFileInfo(saveDir);
1739
if (info.exists) {
1740
info.access = 0777;
1741
auto allFiles = pspFileSystem.GetDirListing(saveDir);
1742
bool firstFile = true;
1743
for (auto file : allFiles) {
1744
if (file.type == FILETYPE_DIRECTORY || file.name == "." || file.name == "..")
1745
continue;
1746
// Use a file to determine save date.
1747
if (firstFile) {
1748
info.ctime = file.ctime;
1749
info.mtime = file.mtime;
1750
info.atime = file.atime;
1751
info.size += file.size;
1752
firstFile = false;
1753
} else {
1754
info.size += file.size;
1755
}
1756
}
1757
}
1758
return info;
1759
}
1760
1761
SceUtilitySavedataParam *SavedataParam::GetPspParam()
1762
{
1763
return pspParam;
1764
}
1765
1766
const SceUtilitySavedataParam *SavedataParam::GetPspParam() const
1767
{
1768
return pspParam;
1769
}
1770
1771
int SavedataParam::GetFilenameCount()
1772
{
1773
return saveNameListDataCount;
1774
}
1775
1776
const SaveFileInfo& SavedataParam::GetFileInfo(int idx)
1777
{
1778
return saveDataList[idx];
1779
}
1780
1781
std::string SavedataParam::GetFilename(int idx) const
1782
{
1783
return saveDataList[idx].saveName;
1784
}
1785
1786
std::string SavedataParam::GetSaveDir(int idx) const {
1787
return saveDataList[idx].saveDir;
1788
}
1789
1790
int SavedataParam::GetSelectedSave()
1791
{
1792
// The slot # of the same save on LOAD/SAVE lists can dismatch so this isn't right anyhow
1793
return selectedSave < saveNameListDataCount ? selectedSave : 0;
1794
}
1795
1796
void SavedataParam::SetSelectedSave(int idx)
1797
{
1798
selectedSave = idx;
1799
}
1800
1801
int SavedataParam::GetFirstListSave()
1802
{
1803
return 0;
1804
}
1805
1806
int SavedataParam::GetLastListSave()
1807
{
1808
return saveNameListDataCount - 1;
1809
}
1810
1811
int SavedataParam::GetLatestSave()
1812
{
1813
int idx = 0;
1814
time_t idxTime = 0;
1815
for (int i = 0; i < saveNameListDataCount; ++i)
1816
{
1817
if (saveDataList[i].size == 0)
1818
continue;
1819
time_t thisTime = mktime(&saveDataList[i].modif_time);
1820
if ((s64)idxTime < (s64)thisTime)
1821
{
1822
idx = i;
1823
idxTime = thisTime;
1824
}
1825
}
1826
return idx;
1827
}
1828
1829
int SavedataParam::GetOldestSave()
1830
{
1831
int idx = 0;
1832
time_t idxTime = 0;
1833
for (int i = 0; i < saveNameListDataCount; ++i)
1834
{
1835
if (saveDataList[i].size == 0)
1836
continue;
1837
time_t thisTime = mktime(&saveDataList[i].modif_time);
1838
if ((s64)idxTime > (s64)thisTime)
1839
{
1840
idx = i;
1841
idxTime = thisTime;
1842
}
1843
}
1844
return idx;
1845
}
1846
1847
int SavedataParam::GetFirstDataSave()
1848
{
1849
int idx = 0;
1850
for (int i = 0; i < saveNameListDataCount; ++i)
1851
{
1852
if (saveDataList[i].size != 0)
1853
{
1854
idx = i;
1855
break;
1856
}
1857
}
1858
return idx;
1859
}
1860
1861
int SavedataParam::GetLastDataSave()
1862
{
1863
int idx = 0;
1864
for (int i = saveNameListDataCount; i > 0; )
1865
{
1866
--i;
1867
if (saveDataList[i].size != 0)
1868
{
1869
idx = i;
1870
break;
1871
}
1872
}
1873
return idx;
1874
}
1875
1876
int SavedataParam::GetFirstEmptySave()
1877
{
1878
int idx = 0;
1879
for (int i = 0; i < saveNameListDataCount; ++i)
1880
{
1881
if (saveDataList[i].size == 0)
1882
{
1883
idx = i;
1884
break;
1885
}
1886
}
1887
return idx;
1888
}
1889
1890
int SavedataParam::GetLastEmptySave()
1891
{
1892
int idx = 0;
1893
for (int i = saveNameListDataCount; i > 0; )
1894
{
1895
--i;
1896
if (saveDataList[i].size == 0)
1897
{
1898
idx = i;
1899
break;
1900
}
1901
}
1902
return idx;
1903
}
1904
1905
int SavedataParam::GetSaveNameIndex(const SceUtilitySavedataParam *param) {
1906
std::string saveName = GetSaveName(param);
1907
for (int i = 0; i < saveNameListDataCount; i++)
1908
{
1909
// TODO: saveName may contain wildcards
1910
if (saveDataList[i].saveName == saveName)
1911
{
1912
return i;
1913
}
1914
}
1915
1916
return 0;
1917
}
1918
1919
bool SavedataParam::WouldHaveMultiSaveName(const SceUtilitySavedataParam *param) {
1920
switch ((SceUtilitySavedataType)(u32)param->mode) {
1921
case SCE_UTILITY_SAVEDATA_TYPE_LOAD:
1922
case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD:
1923
case SCE_UTILITY_SAVEDATA_TYPE_SAVE:
1924
case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE:
1925
case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE:
1926
case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA:
1927
case SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE:
1928
case SCE_UTILITY_SAVEDATA_TYPE_READDATA:
1929
case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE:
1930
case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA:
1931
case SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE:
1932
case SCE_UTILITY_SAVEDATA_TYPE_DELETE:
1933
case SCE_UTILITY_SAVEDATA_TYPE_ERASESECURE:
1934
case SCE_UTILITY_SAVEDATA_TYPE_ERASE:
1935
case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA:
1936
return false;
1937
default:
1938
return true;
1939
}
1940
}
1941
1942
void SavedataParam::DoState(PointerWrap &p) {
1943
auto s = p.Section("SavedataParam", 1, 2);
1944
if (!s)
1945
return;
1946
1947
// pspParam is handled in PSPSaveDialog.
1948
Do(p, selectedSave);
1949
Do(p, saveDataListCount);
1950
Do(p, saveNameListDataCount);
1951
if (p.mode == p.MODE_READ) {
1952
delete [] saveDataList;
1953
if (saveDataListCount != 0) {
1954
saveDataList = new SaveFileInfo[saveDataListCount];
1955
DoArray(p, saveDataList, saveDataListCount);
1956
} else {
1957
saveDataList = nullptr;
1958
}
1959
}
1960
else
1961
DoArray(p, saveDataList, saveDataListCount);
1962
1963
if (s >= 2) {
1964
Do(p, ignoreTextures_);
1965
} else {
1966
ignoreTextures_ = false;
1967
}
1968
}
1969
1970
void SavedataParam::ClearSFOCache() {
1971
std::lock_guard<std::mutex> guard(cacheLock_);
1972
sfoCache_.clear();
1973
}
1974
1975
std::shared_ptr<ParamSFOData> SavedataParam::LoadCachedSFO(const std::string &path, bool orCreate) {
1976
std::lock_guard<std::mutex> guard(cacheLock_);
1977
if (sfoCache_.find(path) == sfoCache_.end()) {
1978
std::vector<u8> data;
1979
if (pspFileSystem.ReadEntireFile(path, data, true) < 0) {
1980
// Mark as not existing for later.
1981
sfoCache_[path].reset();
1982
} else {
1983
sfoCache_.emplace(path, new ParamSFOData());
1984
// If it fails to load, also keep it to indicate failed.
1985
if (!sfoCache_.at(path)->ReadSFO(data))
1986
sfoCache_.at(path).reset();
1987
}
1988
}
1989
1990
if (!sfoCache_.at(path)) {
1991
if (!orCreate)
1992
return nullptr;
1993
sfoCache_.at(path).reset(new ParamSFOData());
1994
}
1995
return sfoCache_.at(path);
1996
}
1997
1998
int SavedataParam::GetSaveCryptMode(const SceUtilitySavedataParam *param, const std::string &saveDirName) {
1999
std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));
2000
std::string sfopath = dirPath + "/" + SFO_FILENAME;
2001
std::shared_ptr<ParamSFOData> sfoFile = LoadCachedSFO(sfopath);
2002
if (sfoFile) {
2003
// save created in PPSSPP and not encrypted has '0' in SAVEDATA_PARAMS
2004
u32 tmpDataSize = 0;
2005
const u8 *tmpDataOrig = sfoFile->GetValueData("SAVEDATA_PARAMS", &tmpDataSize);
2006
if (tmpDataSize == 0 || !tmpDataOrig) {
2007
return 0;
2008
}
2009
switch (tmpDataOrig[0]) {
2010
case 0:
2011
return 0;
2012
case 0x01:
2013
return 1;
2014
case 0x21:
2015
return 3;
2016
case 0x41:
2017
return 5;
2018
default:
2019
// Well, it's not zero, so yes.
2020
ERROR_LOG_REPORT(Log::sceUtility, "Unexpected SAVEDATA_PARAMS hash flag: %02x", tmpDataOrig[0]);
2021
return 1;
2022
}
2023
}
2024
return 0;
2025
}
2026
2027
bool SavedataParam::IsInSaveDataList(const std::string &saveName, int count) {
2028
for(int i = 0; i < count; ++i) {
2029
if (saveDataList[i].saveName == saveName)
2030
return true;
2031
}
2032
return false;
2033
}
2034
2035