diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 3197a6dff2..1074c83d54 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -676,6 +676,7 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) { bool hasIcon0PNG = false; bool hasPRX = false; bool hasPluginIni = false; + bool isPS3SaveData = false; // Same PARAM.SFO + ICON0.PNG layout as a PSP save, but useless to us. s64 totalFileSize = 0; std::vector archiveEntries; // In the root or one level down, where a disc image can be. @@ -757,6 +758,10 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) { if (ZipExtractFileToMemory(z, i, ¶mSFOContents)) { ParamSFOData sfo; if (sfo.ReadSFO((const u8 *)paramSFOContents.data(), paramSFOContents.size())) { + // PSP saves have category MS, PS3 saves SD. + if (sfo.GetValueString("CATEGORY") == "SD") { + isPS3SaveData = true; + } if (sfo.HasKey("TITLE")) { info->gameTitle = sfo.GetValueString("TITLE"); info->savedataTitle = sfo.GetValueString("SAVEDATA_TITLE"); @@ -772,6 +777,9 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) { } } } + } else if (lowerCaseName == "param.pfd" || endsWith(lowerCaseName, "/param.pfd")) { + // The PS3's save data protection file. PSP saves don't have one. + isPS3SaveData = true; } else if (endsWith(lowerCaseName, "/icon0.png")) { hasIcon0PNG = true; } else if (endsWith(lowerCaseName, "/plugin.ini") && slashCount >= 1) { @@ -827,6 +835,9 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) { info->stripChars = stripCharsTexturePack; info->ignoreMetaFiles = true; info->contents = ZipFileContents::TEXTURE_PACK; + } else if (isPS3SaveData) { + WARN_LOG(Log::Loader, "Zip contains PS3 save data, not PSP"); + info->contents = ZipFileContents::UNKNOWN; } else if (stripChars == 0 && filesInRoot == 0 && hasParamSFO && hasIcon0PNG && !isExtractedISO) { // As downloaded from GameFAQs, for example. info->contents = ZipFileContents::SAVE_DATA; diff --git a/unittest/TestZipSlip.cpp b/unittest/TestZipSlip.cpp index 17994606e0..58fe08dd4d 100644 --- a/unittest/TestZipSlip.cpp +++ b/unittest/TestZipSlip.cpp @@ -184,6 +184,53 @@ static bool TestRootSaveDataZip() { return true; } +// A PS3 save folder has the same PARAM.SFO + ICON0.PNG layout as a PSP one. Each signal alone must reject it. +static bool TestPS3SaveDataZip() { + Path tempRoot = File::GetCurDirectory() / "unittest_zip_ps3save_test"; + File::DeleteDirRecursively(tempRoot); + EXPECT_TRUE(File::CreateDir(tempRoot)); + + for (int variant = 0; variant < 2; variant++) { + const bool sdCategory = variant == 0; + ParamSFOData sfo; + sfo.SetValue("CATEGORY", sdCategory ? "SD" : "MS", 4); + sfo.SetValue("TITLE", "Some PS3 game", 128); + sfo.SetValue("SAVEDATA_DIRECTORY", "BCES00141", 64); + u8 *sfoData = nullptr; + size_t sfoSize = 0; + sfo.WriteSFO(&sfoData, &sfoSize); + std::string sfoContents((const char *)sfoData, sfoSize); + delete[] sfoData; + + Path zipPath = tempRoot / "ps3save.zip"; + int errorp = 0; + zip_t *z = zip_open(zipPath.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &errorp); + EXPECT_TRUE(z != nullptr); + static const char *const contents = "data"; + zip_source_t *source = zip_source_buffer(z, sfoContents.data(), sfoContents.size(), 0); + EXPECT_TRUE(zip_file_add(z, "BCES00141/PARAM.SFO", source, ZIP_FL_ENC_UTF_8) >= 0); + std::vector names = { "BCES00141/ICON0.PNG", "BCES00141/0" }; + if (!sdCategory) { + names.push_back("BCES00141/PARAM.PFD"); + } + for (const char *name : names) { + source = zip_source_buffer(z, contents, strlen(contents), 0); + EXPECT_TRUE(zip_file_add(z, name, source, ZIP_FL_ENC_UTF_8) >= 0); + } + EXPECT_TRUE(zip_close(z) == 0); + + z = zip_open(zipPath.c_str(), 0, &errorp); + EXPECT_TRUE(z != nullptr); + ZipFileInfo info; + DetectZipFileContents(z, &info); + zip_close(z); + EXPECT_EQ_INT((int)info.contents, (int)ZipFileContents::UNKNOWN); + } + + File::DeleteDirRecursively(tempRoot); + return true; +} + static bool TestIsSafePathComponent() { EXPECT_TRUE(IsSafePathComponent("ULUS10041")); EXPECT_TRUE(IsSafePathComponent("NPJH-50465")); @@ -214,5 +261,7 @@ bool TestZipSlip() { return false; if (!TestRootSaveDataZip()) return false; + if (!TestPS3SaveDataZip()) + return false; return true; }