Permalink
Browse files

Savedata: Use file hash to validate hash mode.

This makes older PPSSPP save data also work, and also logs when save data
is detected as corrupt.
  • Loading branch information...
unknownbrackets committed Jun 30, 2018
1 parent 5e6429a commit 1976be48abc16938a4a3424491d336d9b8e04f4c
Showing with 97 additions and 63 deletions.
  1. +86 −60 Core/Dialog/SavedataParam.cpp
  2. +11 −3 Core/Dialog/SavedataParam.h
@@ -435,39 +435,31 @@ int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveD
sfoFile.SetValue("SAVEDATA_DIRECTORY", GetSaveDir(param, saveDirName), 64);
// For each file, 13 bytes for filename, 16 bytes for file hash (0 in PPSSPP), 3 byte for padding
if (secureMode)
{
const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3;
if (secureMode) {
const int FILE_LIST_COUNT_MAX = 99;
const u32 FILE_LIST_TOTAL_SIZE = FILE_LIST_ITEM_SIZE * FILE_LIST_COUNT_MAX;
const u32 FILE_LIST_TOTAL_SIZE = sizeof(SaveSFOFileListEntry) * FILE_LIST_COUNT_MAX;
u32 tmpDataSize = 0;
u8 *tmpDataOrig = sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);
u8 *tmpData = new u8[FILE_LIST_TOTAL_SIZE];
if (tmpDataOrig != NULL)
memcpy(tmpData, tmpDataOrig, tmpDataSize > FILE_LIST_TOTAL_SIZE ? FILE_LIST_TOTAL_SIZE : tmpDataSize);
else
memset(tmpData, 0, FILE_LIST_TOTAL_SIZE);
SaveSFOFileListEntry *tmpDataOrig = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);
SaveSFOFileListEntry *updatedList = new SaveSFOFileListEntry[FILE_LIST_COUNT_MAX];
if (tmpDataSize != 0)
memcpy(updatedList, tmpDataOrig, std::min(tmpDataSize, FILE_LIST_TOTAL_SIZE));
if (tmpDataSize < FILE_LIST_TOTAL_SIZE)
memset(updatedList + tmpDataSize, 0, FILE_LIST_TOTAL_SIZE - tmpDataSize);
if (param->dataBuf.IsValid()) {
const std::string saveFilename = GetFileName(param);
for (auto entry = updatedList; entry < updatedList + FILE_LIST_COUNT_MAX; ++entry) {
if (entry->filename[0] != '\0') {
if (strncmp(entry->filename, saveFilename.c_str(), sizeof(entry->filename)) != 0)
continue;
}
if (param->dataBuf.IsValid())
{
char *fName = (char*)tmpData;
for(int i = 0; i < FILE_LIST_COUNT_MAX; i++)
{
if(fName[0] == 0)
break; // End of list
if(strncmp(fName,GetFileName(param).c_str(),20) == 0)
break;
fName += FILE_LIST_ITEM_SIZE;
snprintf(entry->filename, sizeof(entry->filename), "%s", saveFilename.c_str());
memcpy(entry->hash, cryptedHash, 16);
}
if (fName + 13 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE)
snprintf(fName, 13, "%s",GetFileName(param).c_str());
if (fName + 13 + 16 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE)
memcpy(fName+13, cryptedHash, 16);
}
sfoFile.SetValue("SAVEDATA_FILE_LIST", tmpData, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE);
delete[] tmpData;
sfoFile.SetValue("SAVEDATA_FILE_LIST", (u8 *)updatedList, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE);
delete[] updatedList;
}
// Init param with 0. This will be used to detect crypted save or not on loading
@@ -599,10 +591,11 @@ int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::strin
WARN_LOG_REPORT(SCEUTILITY, "Savedata version requested: %d", param->secureVersion);
}
u8 *data_ = param->dataBuf;
std::string filePath = dirPath+"/"+GetFileName(param);
std::string filename = GetFileName(param);
std::string filePath = dirPath + "/" + filename;
s64 readSize;
INFO_LOG(SCEUTILITY,"Loading file with size %u in %s",param->dataBufSize,filePath.c_str());
u8* saveData = 0;
u8 *saveData = nullptr;
int saveSize = -1;
if (!ReadPSPFile(filePath, &saveData, saveSize, &readSize)) {
ERROR_LOG(SCEUTILITY,"Error reading file %s",filePath.c_str());
@@ -619,7 +612,10 @@ int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::strin
if (isCrypted) {
if (DetermineCryptMode(param) > 1 && !HasKey(param))
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;
LoadCryptedSave(param, data_, saveData, saveSize, prevCryptMode, saveDone);
u8 hash[16];
bool hasExpectedHash = GetExpectedHash(dirPath, filename, hash);
LoadCryptedSave(param, data_, saveData, saveSize, prevCryptMode, hasExpectedHash ? hash : nullptr, saveDone);
// TODO: Should return SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN here if !saveDone.
}
if (!saveDone) {
@@ -646,13 +642,14 @@ int SavedataParam::DetermineCryptMode(const SceUtilitySavedataParam *param) cons
return decryptMode;
}
void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, bool &saveDone) {
void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone) {
int align_len = align16(saveSize);
u8 *data_base = new u8[align_len];
u8 *cryptKey = new u8[0x10];
memset(cryptKey, 0, 0x10);
int decryptMode = DetermineCryptMode(param);
const int detectedMode = decryptMode;
bool hasKey = decryptMode > 1;
if (hasKey) {
memcpy(cryptKey, param->key, 0x10);
@@ -688,7 +685,19 @@ void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8
hasKey = decryptMode > 1;
}
if (DecryptSave(decryptMode, data_base, &saveSize, &align_len, (hasKey?cryptKey:0)) == 0) {
int err = DecryptSave(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);
// Perhaps the file had the wrong mode....
if (err != 0 && detectedMode != decryptMode) {
hasKey = detectedMode > 1;
err = DecryptSave(detectedMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);
}
// TODO: Should return an error, but let's just try with a bad hash.
if (err != 0 && expectedHash != nullptr) {
WARN_LOG(SCEUTILITY, "Incorrect hash on save data, likely corrupt");
err = DecryptSave(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, nullptr);
}
if (err == 0) {
if (param->dataBuf.IsValid())
memcpy(data, data_base, std::min((u32)saveSize, (u32)param->dataBufSize));
saveDone = true;
@@ -721,39 +730,54 @@ void SavedataParam::LoadSFO(SceUtilitySavedataParam *param, const std::string& d
}
}
std::set<std::string> SavedataParam::getSecureFileNames(std::string dirPath) {
PSPFileInfo sfoFileInfo = pspFileSystem.GetFileInfo(dirPath + "/" + SFO_FILENAME);
std::set<std::string> secureFileNames;
if (!sfoFileInfo.exists)
return secureFileNames;
std::vector<SaveSFOFileListEntry> SavedataParam::GetSFOEntries(const std::string &dirPath) {
std::vector<SaveSFOFileListEntry> result;
const std::string sfoPath = dirPath + "/" + SFO_FILENAME;
if (!pspFileSystem.GetFileInfo(sfoPath).exists)
return result;
ParamSFOData sfoFile;
std::vector<u8> sfoData;
if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0) {
if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0)
sfoFile.ReadSFO(sfoData);
}
const int FILE_LIST_COUNT_MAX = 99;
u32 sfoFileListSize = 0;
char *sfoFileList = (char *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);
const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3;
const u32 FILE_LIST_COUNT_MAX = 99;
SaveSFOFileListEntry *sfoFileList = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);
const u32 count = std::min((u32)FILE_LIST_COUNT_MAX, sfoFileListSize / (u32)sizeof(SaveSFOFileListEntry));
// Filenames are 13 bytes long at most. Add a NULL so there's no surprises.
char temp[14];
temp[13] = '\0';
for (u32 i = 0; i < count; ++i) {
if (sfoFileList[i].filename[0] != '\0')
result.push_back(sfoFileList[i]);
}
for (u32 i = 0; i < FILE_LIST_COUNT_MAX; ++i) {
// Ends at a NULL filename.
if (i * FILE_LIST_ITEM_SIZE >= sfoFileListSize || sfoFileList[i * FILE_LIST_ITEM_SIZE] == '\0') {
break;
}
return result;
}
std::set<std::string> SavedataParam::GetSecureFileNames(const std::string &dirPath) {
auto entries = GetSFOEntries(dirPath);
strncpy(temp, &sfoFileList[i * FILE_LIST_ITEM_SIZE], 13);
std::set<std::string> secureFileNames;
for (auto entry : entries) {
char temp[14];
truncate_cpy(temp, entry.filename);
secureFileNames.insert(temp);
}
return secureFileNames;
}
bool SavedataParam::GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]) {
auto entries = GetSFOEntries(dirPath);
for (auto entry : entries) {
if (strncmp(entry.filename, filename.c_str(), sizeof(entry.filename)) == 0) {
memcpy(hash, entry.hash, sizeof(entry.hash));
return true;
}
}
return false;
}
void SavedataParam::LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData) {
std::string filePath = dirPath + "/" + filename;
s64 readSize = -1;
@@ -816,13 +840,7 @@ int SavedataParam::EncryptData(unsigned int mode,
return 0;
}
int SavedataParam::DecryptSave(unsigned int mode,
unsigned char *data,
int *dataLen,
int *alignedLen,
unsigned char *cryptkey)
{
int SavedataParam::DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash) {
pspChnnlsvContext1 ctx1;
pspChnnlsvContext2 ctx2;
@@ -852,6 +870,14 @@ int SavedataParam::DecryptSave(unsigned int mode,
if (sceChnnlsv_21BE78B4_(ctx2) < 0)
return -7;
if (expectedHash) {
u8 hash[16];
if (sceSdGetLastIndex_(ctx1, hash, cryptkey) < 0)
return -7;
if (memcmp(hash, expectedHash, sizeof(hash)) != 0)
return -8;
}
/* The decrypted data starts at data + 0x10, so shift it back. */
memmove(data, data + 0x10, *dataLen);
return 0;
@@ -1154,7 +1180,7 @@ int SavedataParam::GetFilesList(SceUtilitySavedataParam *param)
std::set<std::string> secureFilenames;
if (sfoFileInfo.exists) {
secureFilenames = getSecureFileNames(dirPath);
secureFilenames = GetSecureFileNames(dirPath);
} else {
return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;
}
@@ -293,6 +293,12 @@ struct SaveFileInfo
void DoState(PointerWrap &p);
};
struct SaveSFOFileListEntry {
char filename[13];
u8 hash[16];
u8 pad[3];
};
class SavedataParam
{
public:
@@ -355,18 +361,20 @@ class SavedataParam
void ClearFileInfo(SaveFileInfo &saveInfo, const std::string &saveName);
int LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode);
void LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, bool &saveDone);
void LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone);
void LoadNotCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize);
void LoadSFO(SceUtilitySavedataParam *param, const std::string& dirPath);
void LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData);
int DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey);
int DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash);
int EncryptData(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *hash, unsigned char *cryptkey);
int UpdateHash(u8* sfoData, int sfoSize, int sfoDataParamsOffset, int encryptmode);
int BuildHash(unsigned char *output, unsigned char *data, unsigned int len, unsigned int alignedLen, int mode, unsigned char *cryptkey);
int DetermineCryptMode(const SceUtilitySavedataParam *param) const;
std::set<std::string> getSecureFileNames(std::string dirPath);
std::vector<SaveSFOFileListEntry> GetSFOEntries(const std::string &dirPath);
std::set<std::string> GetSecureFileNames(const std::string &dirPath);
bool GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]);
SceUtilitySavedataParam* pspParam;
int selectedSave;

0 comments on commit 1976be4

Please sign in to comment.