diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp index 7fd7d8a489..3182f3b3df 100644 --- a/Core/Dialog/SavedataParam.cpp +++ b/Core/Dialog/SavedataParam.cpp @@ -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]; + 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 (tmpDataOrig != NULL) - memcpy(tmpData, tmpDataOrig, tmpDataSize > FILE_LIST_TOTAL_SIZE ? FILE_LIST_TOTAL_SIZE : tmpDataSize); - else - memset(tmpData, 0, FILE_LIST_TOTAL_SIZE); + 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 SavedataParam::getSecureFileNames(std::string dirPath) { - PSPFileInfo sfoFileInfo = pspFileSystem.GetFileInfo(dirPath + "/" + SFO_FILENAME); - std::set secureFileNames; - if (!sfoFileInfo.exists) - return secureFileNames; +std::vector SavedataParam::GetSFOEntries(const std::string &dirPath) { + std::vector result; + const std::string sfoPath = dirPath + "/" + SFO_FILENAME; + if (!pspFileSystem.GetFileInfo(sfoPath).exists) + return result; ParamSFOData sfoFile; std::vector 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; + SaveSFOFileListEntry *sfoFileList = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize); + const u32 count = std::min((u32)FILE_LIST_COUNT_MAX, sfoFileListSize / (u32)sizeof(SaveSFOFileListEntry)); + + for (u32 i = 0; i < count; ++i) { + if (sfoFileList[i].filename[0] != '\0') + result.push_back(sfoFileList[i]); } - 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; + return result; +} - // Filenames are 13 bytes long at most. Add a NULL so there's no surprises. - char temp[14]; - temp[13] = '\0'; +std::set SavedataParam::GetSecureFileNames(const std::string &dirPath) { + auto entries = GetSFOEntries(dirPath); - 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; - } - - strncpy(temp, &sfoFileList[i * FILE_LIST_ITEM_SIZE], 13); + std::set 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 secureFilenames; if (sfoFileInfo.exists) { - secureFilenames = getSecureFileNames(dirPath); + secureFilenames = GetSecureFileNames(dirPath); } else { return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN; } diff --git a/Core/Dialog/SavedataParam.h b/Core/Dialog/SavedataParam.h index f4779a5256..0a26f1aeb1 100644 --- a/Core/Dialog/SavedataParam.h +++ b/Core/Dialog/SavedataParam.h @@ -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 @@ private: 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 getSecureFileNames(std::string dirPath); + std::vector GetSFOEntries(const std::string &dirPath); + std::set GetSecureFileNames(const std::string &dirPath); + bool GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]); SceUtilitySavedataParam* pspParam; int selectedSave;