CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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