Improve file identification.

Fixes #21154
This commit is contained in:
Henrik Rydgård committed 2026-01-28 10:21:09 +01:00
1 parent 471b9846e0
commit 24f2deeb2e
7 files changed
+127 -52

No files matched your search

+1 -2
View File
@@ -46,7 +46,7 @@ public:
}
return true;
}
int GetBlockSize() const { return 2048;} // forced, it cannot be changed by subclasses
int GetBlockSize() const { return 2048;} // forced, it cannot be changed by subclasses. If a subclass uses bigger blocks internally, it must cache and virtualize.
virtual u32 GetNumBlocks() const = 0;
virtual u64 GetUncompressedSize() const {
return (u64)GetNumBlocks() * (u64)GetBlockSize();
@@ -87,7 +87,6 @@ private:
int ver_ = 0;
};
class FileBlockDevice : public BlockDevice {
public:
FileBlockDevice(FileLoader *fileLoader);
+65 -39
View File
@@ -28,6 +28,7 @@
#include "Core/FileLoaders/RetryingFileLoader.h"
#include "Core/FileLoaders/ZipFileLoader.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/FileSystems/BlockDevices.h"
#include "Core/PSPLoaders.h"
#include "Core/MemMap.h"
#include "Core/Loaders.h"
@@ -37,6 +38,15 @@
#include "Core/ELF/ParamSFO.h"
#include "Core/Util/GameManager.h"
struct PVD {
u8 type;
char identifier[5];
char version;
char pad0;
char systemId[32]; // PSP GAME normally
char volumeId[32]; // In PSP games, this sometimes has the name of the game but far from always.
};
FileLoader *ConstructFileLoader(const Path &filename) {
if (filename.Type() == PathType::HTTP) {
FileLoader *baseLoader = new RetryingFileLoader(new HTTPFileLoader(filename));
@@ -68,23 +78,11 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
}
std::string extension = fileLoader->GetFileExtension();
if (extension == ".iso") {
// may be a psx iso, they have 2352 byte sectors. You never know what some people try to open
if ((fileLoader->FileSize() % 2352) == 0) {
unsigned char sync[12];
fileLoader->ReadAt(0, 12, sync);
// each sector in a mode2 image starts with these 12 bytes
if (memcmp(sync,"\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", 12) == 0) {
*errorString = "ISO in Mode 2: Not a PSP game";
return IdentifiedFileType::ISO_MODE2;
}
bool isDiscImage = false;
// maybe it also just happened to have that size, let's assume it's a PSP ISO and error out later if it's not.
}
return IdentifiedFileType::PSP_ISO;
} else if (extension == ".cso" || extension == ".chd") {
return IdentifiedFileType::PSP_ISO;
if (extension == ".iso" || extension == ".cso" || extension == ".chd") {
isDiscImage = true;
} else if (extension == ".ppst") {
return IdentifiedFileType::PPSSPP_SAVESTATE;
} else if (extension == ".ppdmp") {
@@ -96,7 +94,7 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
}
// First, check if it's a directory with an EBOOT.PBP in it.
if (fileLoader->IsDirectory()) {
if (!isDiscImage && fileLoader->IsDirectory()) {
Path filename = fileLoader->GetPath();
if (filename.size() > 4) {
// Check for existence of EBOOT.PBP, as required for "Directory games".
@@ -130,6 +128,54 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
return IdentifiedFileType::ERROR_IDENTIFYING;
}
if (isDiscImage || fileLoader->FileSize() >= 0x8800) {
// All zeroes. ISO files start like this but their 16th 2048-byte sector contains metadata.
// Do the quick check for PSP ISOs here.
std::string error;
std::unique_ptr<BlockDevice> bd(ConstructBlockDevice(fileLoader, &error));
if (bd) {
u8 block16[2048]{};
bd->ReadBlock(16, (u8 *)block16);
PVD *pvd = (PVD *)(block16);
if (!memcmp(pvd->identifier, "CD001", 5)) {
// It's a PSP ISO file.
if (!memcmp(pvd->systemId, "PSP GAME", 8)) {
return IdentifiedFileType::PSP_ISO;
} else if (!memcmp(pvd->systemId, "PS3", 3)) {
return IdentifiedFileType::PS3_ISO;
} else if (!memcmp(pvd->systemId, "PLAYSTATION", 11)) {
*errorString = "PSX or PS2 ISO";
// Just do a size heuristic here.
if (bd->GetUncompressedSize() > 800LL * 1024LL * 1024LL) {
return IdentifiedFileType::PS2_ISO;
}
return IdentifiedFileType::PSX_ISO;
} else {
*errorString = "ISO missing PSP GAME or PSP NPU identifier";
return IdentifiedFileType::UNKNOWN_ISO;
}
}
// Do extra check for PSX ISO
// may be a psx iso, they have 2352 byte sectors. You never know what some people try to open
if ((fileLoader->FileSize() % 2352) == 0) {
unsigned char sync[12];
fileLoader->ReadAt(0, 12, sync);
// each sector in a mode2 image starts with these 12 bytes
if (memcmp(sync, "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", 12) == 0) {
*errorString = "ISO in Mode 2: Not a PSP game";
return IdentifiedFileType::PSX_ISO;
}
}
}
if (isDiscImage) {
*errorString = "Not a valid PSP ISO image";
return IdentifiedFileType::UNKNOWN_ISO;
}
}
u32_le psar_offset = 0, psar_id = 0;
u32 _id = id;
if (!memcmp(&_id, "PK\x03\x04", 4) || !memcmp(&_id, "PK\x05\x06", 4) || !memcmp(&_id, "PK\x07\x08", 4)) {
@@ -142,28 +188,6 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
return IdentifiedFileType::ARCHIVE_RAR;
} else if (!memcmp(&_id, "\x37\x7A\xBC\xAF", 4)) {
return IdentifiedFileType::ARCHIVE_7Z;
} else if (!memcmp(&_id, "\0\0\0\0", 4)) {
// All zeroes. ISO files start like this but their 16th 2048-byte sector contains metadata.
if (fileLoader->FileSize() > 0x8100) {
char buffer[16];
fileLoader->ReadAt(0x8000, sizeof(buffer), buffer);
if (!memcmp(buffer + 1, "CD001", 5)) {
// It's an ISO file.
if (!memcmp(buffer + 8, "PSP GAME", 8)) {
return IdentifiedFileType::PSP_ISO;
}
return IdentifiedFileType::UNKNOWN_ISO;
}
}
} else if (!memcmp(&_id, "CISO", 4)) {
// CISO are not used for many other kinds of ISO so let's just guess it's a PSP one and let it
// fail later...
return IdentifiedFileType::PSP_ISO;
} else if (!memcmp(&_id, "MCom", 4)) {
size_t readSize = fileLoader->ReadAt(4, 4, 1, &_id);
if (!memcmp(&_id, "prHD", 4)) {
return IdentifiedFileType::PSP_ISO; // CHD file
}
}
if (id == 'FLE\x7F') {
@@ -537,7 +561,9 @@ const char *IdentifiedFileTypeToString(IdentifiedFileType type) {
case IdentifiedFileType::ARCHIVE_ZIP: return "ARCHIVE_ZIP";
case IdentifiedFileType::ARCHIVE_7Z: return "ARCHIVE_7Z";
case IdentifiedFileType::PSP_PS1_PBP: return "PSP_PS1_PBP";
case IdentifiedFileType::ISO_MODE2: return "ISO_MODE2";
case IdentifiedFileType::PSX_ISO: return "PSX_ISO";
case IdentifiedFileType::PS2_ISO: return "PS2_ISO";
case IdentifiedFileType::PS3_ISO: return "PS3_ISO";
case IdentifiedFileType::NORMAL_DIRECTORY: return "NORMAL_DIRECTORY";
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY: return "PSP_SAVEDATA_DIRECTORY";
case IdentifiedFileType::PPSSPP_SAVESTATE: return "PPSSPP_SAVESTATE";
+7 -5
View File
@@ -36,16 +36,18 @@ enum class IdentifiedFileType {
PSP_DISC_DIRECTORY,
UNKNOWN_BIN,
UNKNOWN_ELF,
UNKNOWN_ISO,
// Try to reduce support emails...
ARCHIVE_RAR,
ARCHIVE_ZIP,
ARCHIVE_7Z,
PSP_PS1_PBP,
ISO_MODE2,
PSX_ISO,
PS2_ISO,
PS3_ISO,
UNKNOWN_BIN,
UNKNOWN_ELF,
UNKNOWN_ISO,
NORMAL_DIRECTORY,
+3 -1
View File
@@ -228,7 +228,9 @@ static void GetBootError(IdentifiedFileType type, std::string *errorString) {
break;
case IdentifiedFileType::ARCHIVE_7Z: *errorString = "7z file detected (Require 7-Zip)"; break;
case IdentifiedFileType::ISO_MODE2: *errorString = "PSX game image detected."; break;
case IdentifiedFileType::PSX_ISO: *errorString = "PSX game image detected."; break;
case IdentifiedFileType::PS2_ISO: *errorString = "PS2 game image detected."; break;
case IdentifiedFileType::PS3_ISO: *errorString = "PS2 game image detected."; break;
case IdentifiedFileType::NORMAL_DIRECTORY: *errorString = "Just a directory."; break;
case IdentifiedFileType::PPSSPP_SAVESTATE: *errorString = "This is a saved state, not a game."; break; // Actually, we could make it load it...
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY: *errorString = "This is save data, not a game."; break;
+8
View File
@@ -656,6 +656,7 @@ public:
case IdentifiedFileType::PSP_ELF:
handleELF:
info_->title = info_->GetFilePath().GetFilename();
// An elf on its own has no usable information, no icons, no nothing.
if (flags_ & GameInfoFlags::PARAM_SFO) {
info_->id = g_paramSFO.GenerateFakeID(gamePath_);
@@ -728,6 +729,7 @@ handleELF:
case IdentifiedFileType::PPSSPP_GE_DUMP:
{
info_->title = info_->GetFilePath().GetFilename();
if (flags_ & GameInfoFlags::ICON) {
Path screenshotPath = gamePath_.WithReplacedExtension(".ppdmp", ".png");
// Let's use the comparison screenshot as an icon, if it exists.
@@ -808,6 +810,8 @@ handleELF:
// quick-update the info while we have the lock, so we don't need to wait for the image load to display the title.
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
}
} else {
info_->title = info_->GetFilePath().GetFilename();
}
}
@@ -848,6 +852,7 @@ handleELF:
}
case IdentifiedFileType::ARCHIVE_ZIP:
info_->title = info_->GetFilePath().GetFilename();
if (flags_ & GameInfoFlags::ICON) {
ReadVFSToString("zip.png", &info_->icon.data, &info_->lock);
info_->icon.dataLoaded = true;
@@ -855,6 +860,7 @@ handleELF:
break;
case IdentifiedFileType::ARCHIVE_RAR:
info_->title = info_->GetFilePath().GetFilename();
if (flags_ & GameInfoFlags::ICON) {
ReadVFSToString("rargray.png", &info_->icon.data, &info_->lock);
info_->icon.dataLoaded = true;
@@ -862,6 +868,7 @@ handleELF:
break;
case IdentifiedFileType::ARCHIVE_7Z:
info_->title = info_->GetFilePath().GetFilename();
if (flags_ & GameInfoFlags::ICON) {
ReadVFSToString("7z.png", &info_->icon.data, &info_->lock);
info_->icon.dataLoaded = true;
@@ -870,6 +877,7 @@ handleELF:
case IdentifiedFileType::NORMAL_DIRECTORY:
default:
info_->title = info_->GetFilePath().GetFilename();
break;
}
+42 -4
View File
@@ -119,6 +119,37 @@ static bool FileTypeSupportsCRC(IdentifiedFileType fileType) {
}
}
static bool FileTypeHasIcon(IdentifiedFileType fileType) {
switch (fileType) {
case IdentifiedFileType::PSP_PBP:
case IdentifiedFileType::PSP_PBP_DIRECTORY:
case IdentifiedFileType::PSP_ISO_NP:
case IdentifiedFileType::PSP_ISO:
return true;
default:
return false;
}
}
// Reverse logic.
static bool FileTypeIsPlayable(IdentifiedFileType fileType) {
switch (fileType) {
case IdentifiedFileType::ERROR_IDENTIFYING:
case IdentifiedFileType::UNKNOWN:
case IdentifiedFileType::PSX_ISO:
case IdentifiedFileType::PS2_ISO:
case IdentifiedFileType::PS3_ISO:
case IdentifiedFileType::UNKNOWN_BIN:
case IdentifiedFileType::UNKNOWN_ELF:
case IdentifiedFileType::UNKNOWN_ISO:
case IdentifiedFileType::NORMAL_DIRECTORY:
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
return false;
default:
return true;
}
}
void GameScreen::CreateContentViews(UI::ViewGroup *parent) {
if (!info_) {
// Shouldn't happen
@@ -146,18 +177,23 @@ void GameScreen::CreateContentViews(UI::ViewGroup *parent) {
parent->Add(leftScroll);
const bool fileTypeSupportCRC = FileTypeSupportsCRC(info_->fileType);
const bool fileTypeHasIcon = FileTypeHasIcon(info_->fileType);
// Need an explicit size here because homebrew uses screenshots as icons.
LinearLayout *mainGameInfo;
if (portrait) {
mainGameInfo = new LinearLayout(ORIENT_VERTICAL);
leftColumn->Add(new Spacer(8.0f));
leftColumn->Add(new GameImageView(gamePath_, GameInfoFlags::ICON, 2.0f, new LinearLayoutParams(UI::Margins(0))));
if (fileTypeHasIcon) {
leftColumn->Add(new GameImageView(gamePath_, GameInfoFlags::ICON, 2.0f, new LinearLayoutParams(UI::Margins(0))));
}
leftColumn->Add(mainGameInfo);
} else {
mainGameInfo = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f));
ViewGroup *badgeHolder = new LinearLayout(ORIENT_HORIZONTAL);
badgeHolder->Add(new GameImageView(gamePath_, GameInfoFlags::ICON, 2.0f, new LinearLayoutParams(144 * 2, 80 * 2, UI::Margins(0))));
if (fileTypeHasIcon) {
badgeHolder->Add(new GameImageView(gamePath_, GameInfoFlags::ICON, 2.0f, new LinearLayoutParams(144 * 2, 80 * 2, UI::Margins(0))));
}
badgeHolder->Add(mainGameInfo);
leftColumn->Add(badgeHolder);
}
@@ -175,7 +211,9 @@ void GameScreen::CreateContentViews(UI::ViewGroup *parent) {
tvTitle->SetShadow(true);
std::string regionID = ReplaceAll(info_->id_version, "_", " v");
regionID += ": ";
if (!regionID.empty()) {
regionID += ": ";
}
if (info_->region != GameRegion::UNKNOWN) {
regionID += GameRegionToString(info_->region);
@@ -336,7 +374,7 @@ void GameScreen::CreateSettingsViews(UI::ViewGroup *rightColumn) {
rightColumnItems->SetSpacing(0.0f);
rightColumn->Add(rightColumnItems);
if (!inGame_) {
if (!inGame_ && FileTypeIsPlayable(info_->fileType)) {
rightColumnItems->Add(new Choice(ga->T("Play"), ImageID("I_PLAY")))->OnClick.Handle(this, &GameScreen::OnPlay);
}