Add a reader for PKG game update packages

PSP game updates were distributed as NPDRM .pkg files holding a patched
EBOOT (PBOOT.PBP) plus the data files the patch replaces. PkgUnpack reads
one: header, item table, both PARAM.SFOs, and the AES-128-CTR that covers
everything past the header - including the per-item key split, where an
item's pspType byte picks between the PSP and PS3 keys.

Nothing new is needed to decrypt these. All packages checked use PRX
tag 0x2E5E10F0 for their PBOOT, which PrxDecrypter already has a key for.

Also adds "PPSSPPHeadless --install-pkg=DIR", a sibling of --unpack-updater,
which installs without any UI. All packages install through it byte
-identically to a reference implementation.

Format notes are in docs/pkg_notes.md.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-09-08 11:15:11 -06:00
1 parent ddc673916d
commit 5089caf2a0
12 files changed
+662

No files matched your search

+33
View File
@@ -58,6 +58,7 @@
#include "Core/MIPS/MIPSTables.h"
#include "Core/System.h"
#include "Core/Util/PSARUnpack.h"
#include "Core/Util/PkgUnpack.h"
#include "Core/WebServer.h"
#include "Core/HLE/sceUtility.h"
#include "Core/SaveState.h"
@@ -671,6 +672,38 @@ int main(int argc, const char* argv[]) {
return ok ? 0 : 1;
}
// Same deal for installing a game update package.
if (cmdLineOptions.installPkg.has_value()) {
if (cmdLineOptions.bootFilenames.size() != 1) {
fprintf(stderr, "--install-pkg takes exactly one .pkg file\n");
return 1;
}
std::unique_ptr<FileLoader> loader(ConstructFileLoader(Path(cmdLineOptions.bootFilenames[0])));
PkgReader reader;
std::string pkgError;
if (!loader || !reader.Open(loader.get(), &pkgError)) {
fprintf(stderr, "Not a usable PKG: %s\n", pkgError.c_str());
return 1;
}
const PkgInfo &info = reader.Info();
printf("%s (%s)\n", info.title.c_str(), info.contentId.c_str());
printf("Category %s, %d items, %lld bytes installed\n", info.category.c_str(),
(int)info.items.size(), (long long)PkgInstalledSize(info));
if (info.isGameUpdate) {
printf("Game update for %s v%s -> app version %s (firmware %s)\n", info.discId.c_str(),
info.discVersion.c_str(), info.appVer.c_str(), info.systemVer.c_str());
} else {
fprintf(stderr, "This PKG isn't a game update - nothing we know how to install\n");
return 1;
}
if (!InstallPkg(reader, Path(cmdLineOptions.installPkg.value()), nullptr, &pkgError)) {
fprintf(stderr, "Install failed: %s\n", pkgError.c_str());
return 1;
}
printf("Installed into %s\n", cmdLineOptions.installPkg.value().c_str());
return 0;
}
g_Config.RestoreDefaults(RestoreSettingsBits::SETTINGS | RestoreSettingsBits::CONTROLS | RestoreSettingsBits::RECENT, false);
Core_RegisterDebugOutputListeners(&SendDebugOutput, &SendDebugScreenshot);