From cdd9d34af99b8485a4c141edd4115e8bdc9b7e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 3 Feb 2026 13:46:59 +0100 Subject: [PATCH] Improve handling of UMD Video (still unsupported but show background and icon, fix info screen) --- Core/Loaders.cpp | 1 + UI/GameInfoCache.cpp | 49 ++++++++++++++++++++++++++------------------ UI/GameInfoCache.h | 2 +- UI/GameScreen.cpp | 15 ++++++++++++-- UI/MainScreen.cpp | 44 ++++++++++++++++++++++----------------- 5 files changed, 69 insertions(+), 42 deletions(-) diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index f3729ba879..27b67644db 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -605,6 +605,7 @@ const char *IdentifiedFileTypeToString(IdentifiedFileType type) { case IdentifiedFileType::PSX_ISO: return "PSX_ISO"; case IdentifiedFileType::PS2_ISO: return "PS2_ISO"; case IdentifiedFileType::PS3_ISO: return "PS3_ISO"; + case IdentifiedFileType::PSP_UMD_VIDEO_ISO: return "UMD_VIDEO"; case IdentifiedFileType::NORMAL_DIRECTORY: return "NORMAL_DIRECTORY"; case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY: return "PSP_SAVEDATA_DIRECTORY"; case IdentifiedFileType::PPSSPP_SAVESTATE: return "PPSSPP_SAVESTATE"; diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index ffeb74a979..66790bfdd4 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -364,14 +364,20 @@ bool GameInfo::DeleteAllSaveData() { return true; } -void GameInfo::ParseParamSFO() { +void GameInfo::ParseParamSFO(IdentifiedFileType type) { title = paramSFO.GetValueString("TITLE"); - id = paramSFO.GetValueString("DISC_ID"); - id_version = id + "_" + paramSFO.GetValueString("DISC_VERSION"); - disc_total = paramSFO.GetValueInt("DISC_TOTAL"); - disc_number = paramSFO.GetValueInt("DISC_NUMBER"); - // region = paramSFO.GetValueInt("REGION"); // Always seems to be 32768? - region = DetectGameRegionFromID(id); + if (type != IdentifiedFileType::PSP_UMD_VIDEO_ISO) { + id = paramSFO.GetValueString("DISC_ID"); + id_version = id + "_" + paramSFO.GetValueString("DISC_VERSION"); + disc_total = paramSFO.GetValueInt("DISC_TOTAL"); + disc_number = paramSFO.GetValueInt("DISC_NUMBER"); + // region = paramSFO.GetValueInt("REGION"); // Always seems to be 32768? + region = DetectGameRegionFromID(id); + } else { + id.clear(); + id_version.clear(); + region = GameRegion::UNKNOWN; + } } std::string GameInfo::GetTitle() { @@ -437,13 +443,14 @@ void GameInfo::SetupTexture(Draw::DrawContext *thin3d, GameInfoTex &tex) { } } -static bool ReadFileToString(IFileSystem *fs, const char *filename, std::string *contents, std::mutex *mtx) { - PSPFileInfo info = fs->GetFileInfo(filename); +static bool ReadFileToString(IFileSystem *fs, std::string_view filename, std::string *contents, std::mutex *mtx) { + std::string fn(filename); + PSPFileInfo info = fs->GetFileInfo(fn); if (!info.exists) { return false; } - int handle = fs->OpenFile(filename, FILEACCESS_READ); + int handle = fs->OpenFile(fn, FILEACCESS_READ); if (handle < 0) { return false; } @@ -561,7 +568,6 @@ public: switch (info_->fileType) { case IdentifiedFileType::PSP_PBP: case IdentifiedFileType::PSP_PBP_DIRECTORY: - case IdentifiedFileType::PSP_UMD_VIDEO_ISO: { auto pbpLoader = info_->GetFileLoader(); if (info_->fileType == IdentifiedFileType::PSP_PBP_DIRECTORY) { @@ -590,7 +596,7 @@ public: if (pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) { std::lock_guard lock(info_->lock); info_->paramSFO.ReadSFO(sfoData); - info_->ParseParamSFO(); + info_->ParseParamSFO(info_->fileType); // Assuming PSP_PBP_DIRECTORY without ID or with disc_total < 1 in GAME dir must be homebrew if ((info_->id.empty() || !info_->disc_total) @@ -697,7 +703,7 @@ handleELF: if (ReadFileToString(&umd, "/PARAM.SFO", ¶mSFOcontents, 0)) { std::lock_guard lock(info_->lock); info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size()); - info_->ParseParamSFO(); + info_->ParseParamSFO(info_->fileType); info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO); } } @@ -754,7 +760,7 @@ handleELF: if (ReadFileToString(&umd, "/PSP_GAME/PARAM.SFO", ¶mSFOcontents, nullptr)) { std::lock_guard lock(info_->lock); info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size()); - info_->ParseParamSFO(); + info_->ParseParamSFO(info_->fileType); } } @@ -779,7 +785,9 @@ handleELF: case IdentifiedFileType::PSP_ISO: case IdentifiedFileType::PSP_ISO_NP: + case IdentifiedFileType::PSP_UMD_VIDEO_ISO: { + std::string_view gameRoot = info_->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO ? "/UMD_VIDEO/" : "/PSP_GAME/"; SequentialHandleAllocator handles; // Let's assume it's an ISO. // TODO: This will currently read in the whole directory tree. Not really necessary for just a @@ -804,11 +812,12 @@ handleELF: // Alright, let's fetch the PARAM.SFO. if (flags_ & GameInfoFlags::PARAM_SFO) { std::string paramSFOcontents; - if (ReadFileToString(&umd, "/PSP_GAME/PARAM.SFO", ¶mSFOcontents, nullptr)) { + + if (ReadFileToString(&umd, join(gameRoot, "PARAM.SFO"), ¶mSFOcontents, nullptr)) { { std::lock_guard lock(info_->lock); info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size()); - info_->ParseParamSFO(); + info_->ParseParamSFO(info_->fileType); // 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); @@ -819,15 +828,15 @@ handleELF: } if (flags_ & GameInfoFlags::PIC0) { - info_->pic0.dataLoaded = ReadFileToString(&umd, "/PSP_GAME/PIC0.PNG", &info_->pic0.data, &info_->lock); + info_->pic0.dataLoaded = ReadFileToString(&umd, join(gameRoot, "PIC0.PNG"), &info_->pic0.data, &info_->lock); } if (flags_ & GameInfoFlags::PIC1) { - info_->pic1.dataLoaded = ReadFileToString(&umd, "/PSP_GAME/PIC1.PNG", &info_->pic1.data, &info_->lock); + info_->pic1.dataLoaded = ReadFileToString(&umd, join(gameRoot, "PIC1.PNG"), &info_->pic1.data, &info_->lock); } if (flags_ & GameInfoFlags::SND) { - info_->sndDataLoaded = ReadFileToString(&umd, "/PSP_GAME/SND0.AT3", &info_->sndFileData, &info_->lock); + info_->sndDataLoaded = ReadFileToString(&umd, join(gameRoot, "SND0.AT3"), &info_->sndFileData, &info_->lock); } // Fall back to unknown icon if ISO is broken/is a homebrew ISO, override is allowed though @@ -835,7 +844,7 @@ handleELF: if (flags_ & GameInfoFlags::ICON) { if (LoadReplacementImage(info_.get(), &info_->icon, "icon.png")) { // Nothing more to do - } else if (ReadFileToString(&umd, "/PSP_GAME/ICON0.PNG", &info_->icon.data, &info_->lock)) { + } else if (ReadFileToString(&umd, join(gameRoot, "ICON0.PNG"), &info_->icon.data, &info_->lock)) { info_->icon.dataLoaded = true; } else { Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.jpg"); diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h index 1b63c6a4f1..09bfb7ffb2 100644 --- a/UI/GameInfoCache.h +++ b/UI/GameInfoCache.h @@ -95,7 +95,7 @@ public: // NOTE: This one actually performs I/O directly, not cached. std::string GetMTime() const; - void ParseParamSFO(); + void ParseParamSFO(IdentifiedFileType type); const ParamSFOData &GetParamSFO() const { _dbg_assert_(hasFlags & GameInfoFlags::PARAM_SFO); return paramSFO; diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 84caa2c8b5..ece862f6f9 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -126,13 +126,13 @@ static bool FileTypeHasIcon(IdentifiedFileType fileType) { case IdentifiedFileType::PSP_PBP_DIRECTORY: case IdentifiedFileType::PSP_ISO_NP: case IdentifiedFileType::PSP_ISO: + case IdentifiedFileType::PSP_UMD_VIDEO_ISO: return true; default: return false; } } -// Reverse logic. static bool FileTypeIsPlayable(IdentifiedFileType fileType) { switch (fileType) { case IdentifiedFileType::ERROR_IDENTIFYING: @@ -145,6 +145,8 @@ static bool FileTypeIsPlayable(IdentifiedFileType fileType) { case IdentifiedFileType::UNKNOWN_ISO: case IdentifiedFileType::NORMAL_DIRECTORY: case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY: + case IdentifiedFileType::PSP_UMD_VIDEO_ISO: + // Reverse logic. return false; default: return true; @@ -233,9 +235,18 @@ void GameScreen::CreateContentViews(UI::ViewGroup *parent) { LinearLayout *infoLayout = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(10, 200, NONE, NONE)); leftColumn->Add(infoLayout); + if (info_->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO) { + auto er = GetI18NCategory(I18NCat::ERRORS); + leftColumn->Add(new NoticeView(NoticeLevel::INFO, er->T("PPSSPP doesn't support UMD Video."), "")); + leftColumn->Add(new Choice(di->T("More info"), ImageID("I_LINK_OUT_QUESTION"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)))->OnClick.Add([](UI::EventParams &e) { + System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/docs/reference/umd-video/"); + }); + } + if ((knownFlags_ & GameInfoFlags::UNCOMPRESSED_SIZE) && (knownFlags_ & GameInfoFlags::SIZE)) { + auto st = GetI18NCategory(I18NCat::STORE); // Borrow the size string from here char temp[256]; - snprintf(temp, sizeof(temp), "%s: %s", ga->T_cstr("Game"), NiceSizeFormat(info_->gameSizeOnDisk).c_str()); + snprintf(temp, sizeof(temp), "%s: %s", st->T_cstr("Size"), NiceSizeFormat(info_->gameSizeOnDisk).c_str()); if (info_->gameSizeUncompressed != info_->gameSizeOnDisk) { size_t len = strlen(temp); snprintf(temp + len, sizeof(temp) - len, " (%s: %s)", ga->T_cstr("Uncompressed"), NiceSizeFormat(info_->gameSizeUncompressed).c_str()); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 0fb80b0c4e..7d2a4e3cb0 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -81,6 +81,15 @@ static void LaunchFile(ScreenManager *screenManager, Screen *currentScreen, cons // If is a zip file, we have a screen for that. screenManager->push(new InstallZipScreen(path)); } else { + // Check if we already know that this game isn't playable. + auto info = g_gameInfoCache->GetInfo(nullptr, path, GameInfoFlags::FILE_TYPE); + + if (info->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO) { + // We show info about it. + screenManager->push(new GameScreen(path, false)); + return; + } + if (currentScreen) { screenManager->cancelScreensAbove(currentScreen); } @@ -136,6 +145,7 @@ static void DrawIconWithText(UIContext &dc, ImageID image, std::string_view text if (image.isValid()) { const AtlasImage *img = dc.Draw()->GetAtlas()->getImage(image); if (img && img->h > 0) { + dc.RebindTexture(); dc.Draw()->DrawImage(image, bounds.centerX(), bounds.y + y + 2, iconSize / (float)img->h, style.fgColor, ALIGN_TOP | ALIGN_HCENTER); } } @@ -312,22 +322,6 @@ void GameButton::Draw(UIContext &dc) { } } - if (imageIcon.isValid()) { - Style style = dc.GetTheme().itemStyle; - - if (HasFocus()) style = dc.GetTheme().itemFocusedStyle; - if (down_) style = dc.GetTheme().itemDownStyle; - if (!IsEnabled()) style = dc.GetTheme().itemDisabledStyle; - - dc.FillRect(style.background, bounds_); - DrawIconWithText(dc, imageIcon, ginfo->GetTitle(), bounds_, gridStyle_, style); - - if (overlayColor) { - dc.FillRect(Drawable(overlayColor), overlayBounds); - } - return; - } - if (ginfo->Ready(GameInfoFlags::ICON) && ginfo->icon.texture) { texture = ginfo->icon.texture; } @@ -395,6 +389,21 @@ void GameButton::Draw(UIContext &dc) { dc.Draw()->Flush(); } + if (imageIcon.isValid()) { + Style style = dc.GetTheme().itemStyle; + + if (HasFocus()) style = dc.GetTheme().itemFocusedStyle; + if (down_) style = dc.GetTheme().itemDownStyle; + if (!IsEnabled()) style = dc.GetTheme().itemDisabledStyle; + + DrawIconWithText(dc, imageIcon, ginfo->GetTitle(), bounds_, gridStyle_, style); + + if (overlayColor) { + dc.FillRect(Drawable(overlayColor), overlayBounds); + } + return; + } + char discNumInfo[8]; if (ginfo->disc_total > 1) snprintf(discNumInfo, sizeof(discNumInfo), "-DISC%d", ginfo->disc_number); @@ -1078,7 +1087,6 @@ void GameBrowser::GameButtonClick(UI::EventParams &e) { GameButton *button = static_cast(e.v); UI::EventParams e2{}; e2.s = button->GamePath().ToString(); - // Insta-update - here we know we are already on the right thread. OnChoice.Trigger(e2); } @@ -1086,12 +1094,10 @@ void GameBrowser::GameButtonHoldClick(UI::EventParams &e) { GameButton *button = static_cast(e.v); UI::EventParams e2{}; e2.s = button->GamePath().ToString(); - // Insta-update - here we know we are already on the right thread. OnHoldChoice.Trigger(e2); } void GameBrowser::GameButtonHighlight(UI::EventParams &e) { - // Insta-update - here we know we are already on the right thread. OnHighlight.Trigger(e); }