Zip install: Reject PS3 save data

A zipped PS3 save folder has a PARAM.SFO and ICON0.PNG, the same layout
as a PSP save, so it was offered for install as one. PS3 saves have
CATEGORY SD in the SFO (PSP saves have MS) and a PARAM.PFD; either now
makes the zip unrecognized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-11 14:22:13 +01:00
1 parent 4da13731aa
commit e3ea1adc48
2 files changed
+60

No files matched your search

+11
View File
@@ -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<ArchiveEntry> 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, &paramSFOContents)) {
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;
+49
View File
@@ -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<const char *> 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;
}