Merge pull request #21169 from hrydgard/more-umd-video-detection

Make UMD_VIDEO discs with game data detect as games.
This commit is contained in:
Henrik Rydgård
2026-01-29 02:01:01 +01:00
committed by GitHub
10 changed files with 106 additions and 62 deletions
+3 -1
View File
@@ -399,9 +399,11 @@ GameRegion DetectGameRegionFromID(std::string_view id_full) {
return GameRegion::TEST;
} else if (id_letters == "UMDT") {
return GameRegion::DIAGNOSTIC;
} else if (id_letters == "STEA") {
// The bizarre Stealth + Wipeout Pure combo.
return GameRegion::INTERNAL;
}
}
return GameRegion::HOMEBREW;
}
+2 -1
View File
@@ -22,6 +22,7 @@
#include "Common/System/OSD.h"
#include "Common/Log.h"
#include "Common/Swap.h"
#include "Common/Data/Text/Parsers.h"
#include "Common/File/FileUtil.h"
#include "Common/File/DirListing.h"
#include "Common/StringUtils.h"
@@ -223,7 +224,7 @@ CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader)
u64 lastIndexPos = index[indexSize - 1] & 0x7FFFFFFF;
u64 expectedFileSize = lastIndexPos << indexShift;
if (expectedFileSize > fileSize) {
errorString_ = StringFromFormat("Expected CSO to at least be %lld bytes, but file is %lld bytes", expectedFileSize, fileSize);
errorString_ = StringFromFormat("CSO file incomplete: expected %s, but is %s", NiceSizeFormat(expectedFileSize).c_str(), NiceSizeFormat(fileSize).c_str());
return;
}
+65 -39
View File
@@ -120,43 +120,55 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
// OK, quick methods of identification for common types failed. Moving on to more expensive methods,
// starting by reading the first few bytes.
// This can be necessary for weird Android content storage path types, see issue #17462
u32_le id;
size_t readSize = fileLoader->ReadAt(0, 4, 1, &id);
if (readSize != 1) {
*errorString = "Failed to read identification bytes";
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));
std::string bdError;
std::unique_ptr<BlockDevice> bd(ConstructBlockDevice(fileLoader, &bdError));
if (bd) {
u8 block16[2048]{};
bd->ReadBlock(16, (u8 *)block16);
PVD *pvd = (PVD *)(block16);
if (!memcmp(pvd->identifier, "CD001", 5)) {
PVD pvd;
memcpy(&pvd, block16, sizeof(PVD));
if (!memcmp(pvd.identifier, "CD001", 5)) {
// It's a valid DVD-style ISO file. Let's see which type.
if (!memcmp(pvd->systemId, "PSP GAME", 8)) {
// Yes, a proper PSP game, let's get it going.
if (!memcmp(pvd.systemId, "PSP GAME", 8) || !memcmp(pvd.systemId, "\"PSP GAME\"", 10)) {
// Yes, a known proper PSP game, let's get it going.
return IdentifiedFileType::PSP_ISO;
} else if (!memcmp(pvd->systemId, "UMD VIDEO", 9) || !memcmp(pvd->systemId, "UMD AUDIO", 9)) {
} else if (!memcmp(pvd.systemId, "UMD VIDEO", 9) || !memcmp(pvd.systemId, "UMD AUDIO", 9)) {
// This is rare so being slightly slow here shouldn't be a problem. Let's go check for the presence of
// actual game data.
SequentialHandleAllocator hAlloc;
ISOFileSystem umd(&hAlloc, bd.release());
if (umd.GetFileInfo("/PSP_GAME").exists) {
INFO_LOG(Log::Loader, "Found an UMD VIDEO disc with game data. Treating as game.");
*errorString = "UMD Video with PSP GAME data";
return IdentifiedFileType::PSP_ISO;
}
// UMD AUDIO exists technically, but in reality, not really? Let's map it to VIDEO since we support neither.
return IdentifiedFileType::PSP_UMD_VIDEO_ISO;
} else if (!memcmp(pvd->systemId, "PS3", 3)) {
} else if (!memcmp(pvd.systemId, "PS3", 3)) {
*errorString = "PS3 ISO";
return IdentifiedFileType::PS3_ISO;
} else if (!memcmp(pvd->systemId, "PLAYSTATION", 11)) {
*errorString = "PSX or PS2 ISO";
} else if (!memcmp(pvd.systemId, "PLAYSTATION", 11)) {
// Just do a size heuristic here to differentiate. There are better ways but slower.
if (bd->GetUncompressedSize() > 800LL * 1024LL * 1024LL) {
*errorString = "PS2 ISO";
return IdentifiedFileType::PS2_ISO;
}
*errorString = "PSX ISO?";
return IdentifiedFileType::PSX_ISO;
} else {
*errorString = "ISO missing PSP GAME or PSP NPU identifier";
// Let's go check for PSP game data.
SequentialHandleAllocator hAlloc;
ISOFileSystem umd(&hAlloc, bd.release());
if (umd.GetFileInfo("/PSP_GAME").exists) {
INFO_LOG(Log::Loader, "PSP ISO with unknown system ID: %.32s: %s", pvd.systemId, fileLoader->GetPath().c_str());
return IdentifiedFileType::PSP_ISO;
}
INFO_LOG(Log::Loader, "Unknown ISO with unknown system ID: %.32s: %s", pvd.systemId, fileLoader->GetPath().c_str());
*errorString = StringFromFormat("ISO with unknown system ID: %.32s", pvd.systemId);
return IdentifiedFileType::UNKNOWN_ISO;
}
}
@@ -169,29 +181,41 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
// 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";
*errorString = "ISO is a CD - likely PSX"; // Mode 2 CDs are used for PSX games
return IdentifiedFileType::PSX_ISO;
}
}
}
if (isDiscImage) {
*errorString = "Not a valid PSP ISO image";
if (!bdError.empty()) {
*errorString = bdError;
} else {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
*errorString = sy->T("Not a PSP game");
}
return IdentifiedFileType::UNKNOWN_ISO;
}
}
u32 id;
size_t readSize = fileLoader->ReadAt(0, 4, 1, &id);
if (readSize != 1) {
*errorString = "Failed to read identification bytes";
return IdentifiedFileType::ERROR_IDENTIFYING;
}
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)) {
if (!memcmp(&id, "PK\x03\x04", 4) || !memcmp(&id, "PK\x05\x06", 4) || !memcmp(&id, "PK\x07\x08", 4)) {
return IdentifiedFileType::ARCHIVE_ZIP;
} else if (!memcmp(&_id, "\x00PBP", 4)) {
} else if (!memcmp(&id, "\x00PBP", 4)) {
fileLoader->ReadAt(0x24, 4, 1, &psar_offset);
fileLoader->ReadAt(psar_offset, 4, 1, &psar_id);
// Fall through to the below if chain.
} else if (!memcmp(&_id, "Rar!", 4)) {
} else if (!memcmp(&id, "Rar!", 4)) {
return IdentifiedFileType::ARCHIVE_RAR;
} else if (!memcmp(&_id, "\x37\x7A\xBC\xAF", 4)) {
} else if (!memcmp(&id, "\x37\x7A\xBC\xAF", 4)) {
return IdentifiedFileType::ARCHIVE_7Z;
}
@@ -253,17 +277,18 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
return IdentifiedFileType::UNKNOWN;
}
FileLoader *ResolveFileLoaderTarget(FileLoader *fileLoader) {
std::string errorString;
IdentifiedFileType type = Identify_File(fileLoader, &errorString);
if (type == IdentifiedFileType::PSP_PBP_DIRECTORY) {
FileLoader *ResolveFileLoaderTarget(FileLoader *fileLoader, IdentifiedFileType *fileType, std::string *errorString) {
*fileType = Identify_File(fileLoader, errorString);
if (*fileType == IdentifiedFileType::PSP_PBP_DIRECTORY) {
const Path ebootFilename = ResolvePBPFile(fileLoader->GetPath());
if (ebootFilename != fileLoader->GetPath()) {
// Switch fileLoader to the actual EBOOT.
delete fileLoader;
fileLoader = ConstructFileLoader(ebootFilename);
// Re-identify the file.
*fileType = Identify_File(fileLoader, errorString);
}
} else if (type == IdentifiedFileType::ARCHIVE_ZIP) {
} else if (*fileType == IdentifiedFileType::ARCHIVE_ZIP) {
// Handle zip files, take automatic action depending on contents.
// Can also return nullptr.
ZipFileLoader *zipLoader = new ZipFileLoader(fileLoader);
@@ -276,6 +301,8 @@ FileLoader *ResolveFileLoaderTarget(FileLoader *fileLoader) {
case ZipFileContents::FRAME_DUMP:
{
zipLoader->Initialize(zipFileInfo.isoFileIndex);
// Re-identify the file.
*fileType = Identify_File(zipLoader, errorString);
return zipLoader;
}
default:
@@ -323,14 +350,13 @@ bool UmdReplace(const Path &filepath, FileLoader **fileLoader, std::string &erro
}
UpdateLoadedFile(loadedFile);
loadedFile = ResolveFileLoaderTarget(loadedFile);
std::string errorString;
IdentifiedFileType fileType;
loadedFile = ResolveFileLoaderTarget(loadedFile, &fileType, &errorString);
*fileLoader = loadedFile;
std::string errorString;
IdentifiedFileType type = Identify_File(loadedFile, &errorString);
switch (type) {
switch (fileType) {
case IdentifiedFileType::PSP_ISO:
case IdentifiedFileType::PSP_ISO_NP:
case IdentifiedFileType::PSP_DISC_DIRECTORY:
@@ -340,7 +366,7 @@ bool UmdReplace(const Path &filepath, FileLoader **fileLoader, std::string &erro
}
break;
default:
error = "Unsupported file type: " + std::to_string((int)type) + " " + errorString;
error = "Unsupported file type: " + std::string(IdentifiedFileTypeToString(fileType)) + " " + errorString;
return false;
break;
}
+2 -2
View File
@@ -152,8 +152,8 @@ inline u32 operator & (const FileLoader::Flags &a, const FileLoader::Flags &b) {
}
FileLoader *ConstructFileLoader(const Path &filename);
// Resolve to the target binary, ISO, or other file (e.g. from a directory.)
FileLoader *ResolveFileLoaderTarget(FileLoader *fileLoader);
// Identifies the file and resolves to the target binary, ISO, or other file (e.g. from a directory.)
FileLoader *ResolveFileLoaderTarget(FileLoader *fileLoader, IdentifiedFileType *fileType, std::string *errorString);
Path ResolvePBPDirectory(const Path &filename);
Path ResolvePBPFile(const Path &filename);
+14 -4
View File
@@ -134,19 +134,29 @@ namespace Reporting
AndroidJNIThreadContext jniContext;
FileLoader *fileLoader = ResolveFileLoaderTarget(ConstructFileLoader(crcFilename));
IdentifiedFileType type;
std::string errorString;
BlockDevice *blockDevice = ConstructBlockDevice(fileLoader, &errorString);
FileLoader *fileLoader = ResolveFileLoaderTarget(ConstructFileLoader(crcFilename), &type, &errorString);
if (!fileLoader) {
ERROR_LOG(Log::Loader, "Failed to construct file loader for CRC: %s", errorString.c_str());
std::lock_guard<std::mutex> guard(crcLock);
crcResults[crcFilename] = 0;
crcPending = false;
crcCond.notify_one();
return 0;
}
std::unique_ptr<BlockDevice> blockDevice(ConstructBlockDevice(fileLoader, &errorString));
u32 crc = 0;
if (blockDevice) {
crc = CalculateCRC(blockDevice, &crcCancel);
crc = CalculateCRC(blockDevice.get(), &crcCancel);
} else {
ERROR_LOG(Log::Loader, "Failed to read from block device for CRC: %s", errorString.c_str());
}
delete blockDevice;
blockDevice.reset();
delete fileLoader;
std::lock_guard<std::mutex> guard(crcLock);
+12 -12
View File
@@ -340,7 +340,6 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
// Trying to boot other things lands us here. We need to return a sensible error string.
ERROR_LOG(Log::Loader, "CPU_Init didn't recognize file. %s", errorString->c_str());
auto sy = GetI18NCategory(I18NCat::SYSTEM);
*errorString = sy->T("Not a PSP game"); // best string we have.
return false;
}
}
@@ -600,7 +599,7 @@ bool PSP_InitStart(const CoreParameter &coreParam) {
}
g_CoreParameter.errorString.clear();
std::string *error_string = &g_CoreParameter.errorString;
std::string *errorString = &g_CoreParameter.errorString;
INFO_LOG(Log::Loader, "Starting loader thread...");
@@ -608,7 +607,7 @@ bool PSP_InitStart(const CoreParameter &coreParam) {
Core_NotifyLifecycle(CoreLifecycle::STARTING);
g_loadingThread = std::thread([error_string]() {
g_loadingThread = std::thread([errorString]() {
SetCurrentThreadName("ExecLoader");
AndroidJNIThreadContext jniContext;
@@ -616,14 +615,13 @@ bool PSP_InitStart(const CoreParameter &coreParam) {
NOTICE_LOG(Log::Boot, "PPSSPP %s", PPSSPP_GIT_VERSION);
Path filename = g_CoreParameter.fileToStart;
FileLoader *loadedFile = ResolveFileLoaderTarget(ConstructFileLoader(filename));
IdentifiedFileType type = Identify_File(loadedFile, &g_CoreParameter.errorString);
g_CoreParameter.fileType = type;
IdentifiedFileType fileType;
FileLoader *loadedFile = ResolveFileLoaderTarget(ConstructFileLoader(filename), &fileType, errorString);
if (System_GetPropertyBool(SYSPROP_ENOUGH_RAM_FOR_FULL_ISO)) {
if (g_Config.bCacheFullIsoInRam) {
switch (g_CoreParameter.fileType) {
switch (fileType) {
case IdentifiedFileType::PSP_ISO:
case IdentifiedFileType::PSP_ISO_NP:
loadedFile = new RamCachingFileLoader(loadedFile);
@@ -635,14 +633,16 @@ bool PSP_InitStart(const CoreParameter &coreParam) {
}
}
g_CoreParameter.fileType = fileType;
// TODO: The reason we pass in g_CoreParameter.errorString here is that it's persistent -
// it gets written to from the loader thread that gets spawned.
if (!CPU_Init(loadedFile, type, &g_CoreParameter.errorString)) {
if (!CPU_Init(loadedFile, fileType, &g_CoreParameter.errorString)) {
CPU_Shutdown(false);
g_CoreParameter.fileToStart.clear();
*error_string = g_CoreParameter.errorString;
if (error_string->empty()) {
*error_string = "Failed initializing CPU/Memory";
*errorString = g_CoreParameter.errorString;
if (errorString->empty()) {
*errorString = "Failed initializing CPU/Memory";
}
g_bootState = BootState::Failed;
return;
@@ -651,7 +651,7 @@ bool PSP_InitStart(const CoreParameter &coreParam) {
// Initialize the GPU as far as we can here (do things like load cache files).
_dbg_assert_(!gpu);
#ifndef __LIBRETRO__
InitGPU(error_string);
InitGPU(errorString);
#endif
g_bootState = BootState::Complete;
});
+1 -1
View File
@@ -1457,7 +1457,7 @@ void EmuScreen::update() {
std::string errLoadingFile = gamePath_.ToVisualString() + "\n\n";
errLoadingFile.append(err->T("Error loading file", "Could not load game"));
errLoadingFile.append("\n");
errLoadingFile.append(err->T(errorMessage_.c_str()));
errLoadingFile.append(errorMessage_);
screenManager()->push(new PromptScreen(gamePath_, errLoadingFile, di->T("OK"), ""));
errorMessage_.clear();
+2
View File
@@ -121,6 +121,8 @@ struct ImConfig {
bool sasShowAllVoices = false;
float fbViewerZoom = 1.0f;
// We use a separate ini file from the main PPSSPP config.
void LoadConfig(const Path &iniFile);
+3 -1
View File
@@ -79,10 +79,12 @@ void DrawFramebuffersWindow(ImConfig &cfg, FramebufferManagerCommon *framebuffer
}
if (cfg.selectedFramebuffer != -1) {
ImGui::SliderFloat("Scale", &cfg.fbViewerZoom, 0.5f, 16.0f, "%.2f", ImGuiSliderFlags_Logarithmic);
// Now, draw the image of the selected framebuffer.
Draw::Framebuffer *fb = vfbs[cfg.selectedFramebuffer]->fbo;
ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(fb, Draw::Aspect::COLOR_BIT, ImGuiPipeline::TexturedOpaque);
ImGui::Image(texId, ImVec2(fb->Width(), fb->Height()));
ImGui::Image(texId, ImVec2(fb->Width() * cfg.fbViewerZoom, fb->Height() * cfg.fbViewerZoom));
}
ImGui::End();
+2 -1
View File
@@ -231,13 +231,14 @@ NPUH90087 = true # demo
NPEH90042 = true # demo
[Force04154000Download]
# This applies a hack to Dangan Ronpa, its demo, and its sequel.
# This applies a hack to Dangan Ronpa, its demo, and its sequel, Super Dangan Ronpa 2.
# The game draws solid colors to a small framebuffer, and then reads this directly in VRAM.
# We force this framebuffer to 1x and force download it automatically.
NPJH50631 = true
NPJH50372 = true
NPJH90164 = true
NPJH50515 = true
# Let's also apply to Me & My Katamari.
ULUS10094 = true
ULES00339 = true