GameInfoCache: report the firmware updater bundled on a game disc

Adds GameInfoFlags::BUNDLED_UPDATE_INFO, holding the version, title, size
and timestamp of the updater in PSP_GAME/SYSDIR/UPDATE. It comes from the
PARAM.SFO and the directory entry next to the archive, so it's a couple of
small reads on the ISOFileSystem the worker already has open - no
decryption, and DATA.BIN itself is only sniffed for its magic. Only
computed for ISOs; everything else is marked complete with an empty struct.

ISOFileSystem now parses the date out of the ISO9660 directory record,
stored as Unix UTC seconds and reported as the PSP's atime/ctime/mtime.
Those used to always read back as zero, so games calling sceIoGetstat on a
UMD file saw 1900 where hardware gives the mastering date.

Shown on GameScreen as e.g. "Firmware update on disc: 6.60 (2011-10-05),
25.6 MB".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149QcTVgZEXKXbgHyvXF4ZY
This commit is contained in:
Henrik Rydgård
2026-08-23 16:44:38 +02:00
co-authored by Claude Opus 5
parent c09e6bddaa
commit 396f9e802d
8 changed files with 167 additions and 3 deletions
+39
View File
@@ -18,8 +18,10 @@
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <ctime>
#include "Common/CommonTypes.h"
#include "Common/File/FileUtil.h" // for localtime_r on Windows
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/StringUtils.h"
@@ -138,6 +140,39 @@ struct VolDescriptor {
char zeroos[653];
};
// From http://howardhinnant.github.io/date_algorithms.html - same one sceRtc.cpp uses. Beats
// timegm(), which isn't portable, and mktime(), which would drag the host's timezone in.
static s64 DaysFromCivil(s64 y, u32 m, u32 d) {
y -= m <= 2;
const s64 era = (y >= 0 ? y : y - 399) / 400;
const u32 yoe = (u32)(y - era * 400); // [0, 399]
const u32 doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; // [0, 365]
const u32 doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
return era * 146097 + (s64)doe - 719468;
}
// The 7-byte date in a directory record, as Unix UTC seconds. Returns 0 if there isn't a real
// date in there - plenty of ISOs leave it zeroed, and "1900-00-00" isn't worth propagating.
static s64 UnixTimeFromDirectoryEntry(const DirectoryEntry &dir) {
if (dir.month < 1 || dir.month > 12 || dir.day < 1 || dir.day > 31) {
return 0;
}
// years is an offset from 1900, and offsetFromGMT is a signed count of 15-minute steps.
const s64 days = DaysFromCivil(1900 + (s64)dir.years, dir.month, dir.day);
return days * 86400 + dir.hour * 3600 + dir.minute * 60 + dir.second - (s64)(s8)dir.offsetFromGMT * 15 * 60;
}
// ISO9660 has one timestamp per file, so it goes into all three of the PSP's. Local time, to
// match what the other file systems report.
static void FillFileTimes(PSPFileInfo *info, s64 unixTime) {
if (unixTime == 0) {
return;
}
const time_t t = (time_t)unixTime;
localtime_r(&t, &info->mtime);
info->atime = info->ctime = info->mtime;
}
// Removes version numbers from filenames.
static std::string_view CleanISOFileName(std::string_view name) {
auto pos = name.find(';');
@@ -258,6 +293,7 @@ void ISOFileSystem::ReadDirectory(TreeEntry *root) const {
entry->startsector = dir.firstDataSector;
entry->dirsize = dir.dataLength;
entry->valid = isFile; // Can pre-mark as valid if file, as we don't recurse into those.
entry->recordTime = UnixTimeFromDirectoryEntry(dir);
VERBOSE_LOG(Log::FileSystem, "%s: %s %08x %08x %d", entry->isDirectory ? "D" : "F", entry->name.c_str(), (u32)dir.firstDataSector, entry->startingPosition, entry->startingPosition);
// Round down to avoid any false reports.
@@ -662,6 +698,7 @@ PSPFileInfo ISOFileSystem::GetFileInfo(std::string filename) {
x.type = entry->isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
x.isOnSectorSystem = true;
x.startSector = entry->startingPosition / 2048;
FillFileTimes(&x, entry->recordTime);
}
return x;
}
@@ -679,6 +716,7 @@ PSPFileInfo ISOFileSystem::GetFileInfoByHandle(u32 handle) {
x.type = entry->isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
x.isOnSectorSystem = true;
x.startSector = entry->startingPosition / 2048;
FillFileTimes(&x, entry->recordTime);
}
return x;
}
@@ -714,6 +752,7 @@ std::vector<PSPFileInfo> ISOFileSystem::GetDirListing(std::string_view path, boo
x.type = e->isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
x.isOnSectorSystem = true;
x.startSector = e->startingPosition/2048;
FillFileTimes(&x, e->recordTime);
x.sectorSize = sectorSize;
x.numSectors = (u32)((e->size + sectorSize - 1) / sectorSize);
myVector.push_back(x);