mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-20 03:18:04 +02:00
An installed update silently replaces what the game boots, so the info pane now says when there is one - version, size and where it lives - and the context menu offers to remove it again. Removing takes the whole PSP/GAME/<DISC_ID> folder when the update is all that's in it. When a digital game shares the folder, only PBOOT.PBP goes, since deleting the folder would take the game with it and nothing records what the install wrote. The confirmation names the exact path either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018izZ1mGTWhz2RqeudqsDQR
131 lines
5.8 KiB
C++
131 lines
5.8 KiB
C++
// Copyright (c) 2026- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#pragma once
|
|
|
|
#include <functional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/File/Path.h"
|
|
|
|
class FileLoader;
|
|
|
|
// Reads .PKG files - the NPDRM container Sony distributed downloadable content in. We're only
|
|
// interested in one flavor: PSP *game updates*, which hold a patched EBOOT (PBOOT.PBP) plus the
|
|
// data files the patch replaces. Installing one puts them in PSP/GAME/<DISC_ID>/, and the game
|
|
// then boots from there with the original disc still supplying everything the patch doesn't
|
|
// override - see FindGameUpdatePBOOT() in Core/PSPLoaders.cpp.
|
|
//
|
|
// The whole package past the header is AES-128-CTR, and every PBOOT seen so far is encrypted with
|
|
// a PRX tag we already have a key for, so nothing here needs new crypto secrets.
|
|
//
|
|
// docs/pkg_notes.md describes the format and what these packages turned out to contain.
|
|
|
|
struct PkgItem {
|
|
std::string name;
|
|
u64 dataOffset = 0; // Relative to the encrypted area, not to the file.
|
|
u64 dataSize = 0;
|
|
u8 pspType = 0; // 0x90 selects the PSP key, anything else the PS3 key.
|
|
u8 flags = 0; // Content type. See docs/pkg_notes.md for the values.
|
|
|
|
bool IsDirectory() const { return flags == 4 || flags == 18; }
|
|
};
|
|
|
|
struct PkgInfo {
|
|
std::string contentId; // "JP0177-ULJM05681_00-PJD2UPDATEVR0101"
|
|
std::string titleId; // "ULJM05681"
|
|
u32 contentType = 0; // 7 for PSP. See kPkgContentTypePSP.
|
|
std::string title; // From the package's own PARAM.SFO.
|
|
std::string category; // "PP" for a game update.
|
|
|
|
// A game update carries a PBOOT.PBP whose own PARAM.SFO says what it patches. Without one
|
|
// there's nothing here we know how to install.
|
|
bool isGameUpdate = false;
|
|
std::string discId; // The disc this patches, e.g. "ULJM05681".
|
|
std::string discVersion; // The disc version it patches, e.g. "1.00".
|
|
std::string appVer; // The patch's own version, e.g. "01.01".
|
|
std::string systemVer; // Firmware the patch asks for, e.g. "6.20".
|
|
std::string pbootTitle; // "Update 2.01", when the patch bothers to name itself.
|
|
|
|
std::vector<PkgItem> items;
|
|
};
|
|
|
|
const u32 kPkgContentTypePSP = 7;
|
|
|
|
class PkgReader {
|
|
public:
|
|
// Parses the header, the item table and the two PARAM.SFOs. Doesn't take ownership of the
|
|
// loader, which has to outlive the reader.
|
|
bool Open(FileLoader *loader, std::string *error);
|
|
|
|
const PkgInfo &Info() const { return info_; }
|
|
|
|
// Decrypts `size` bytes at `offset` within an item.
|
|
bool ReadItemData(const PkgItem &item, u64 offset, size_t size, u8 *out);
|
|
// The whole item, for small ones. Fails rather than allocating more than maxSize.
|
|
bool ReadItem(const PkgItem &item, std::vector<u8> *out, size_t maxSize = 4 * 1024 * 1024);
|
|
|
|
private:
|
|
bool ReadEncrypted(u64 offset, size_t size, const u8 *key, u8 *out);
|
|
const u8 *ItemKey(const PkgItem &item) const;
|
|
bool ReadPBOOTInfo(const PkgItem &pboot);
|
|
|
|
FileLoader *loader_ = nullptr;
|
|
u64 dataOffset_ = 0;
|
|
u64 dataSize_ = 0;
|
|
u8 riv_[16]{};
|
|
u8 mainKey_[16]{};
|
|
PkgInfo info_;
|
|
};
|
|
|
|
// Where an item ends up inside the installed game folder, or empty for the ones that shouldn't be
|
|
// installed at all. A package wraps its payload in USRDIR/CONTENT/, PS3-style, and also carries
|
|
// store metadata (PARAM.SFO, PS3LOGO.DAT, the icons) that isn't part of the PSP-side install.
|
|
std::string PkgItemInstallPath(const PkgItem &item);
|
|
|
|
// What the install will take up on disk. Package contents aren't compressed, so this is exact
|
|
// rather than an estimate - modulo the filesystem's own per-file overhead.
|
|
u64 PkgInstalledSize(const PkgInfo &info);
|
|
|
|
// Unpacks the installable items into destDir, which should be the game folder itself
|
|
// (PSP/GAME/<DISC_ID>). progress is called with 0..1 as it goes, and may be null.
|
|
bool InstallPkg(PkgReader &reader, const Path &destDir, const std::function<void(float)> &progress, std::string *error);
|
|
|
|
// An update that has been installed, i.e. what's left over in PSP/GAME/<DISC_ID> afterwards.
|
|
struct InstalledGameUpdate {
|
|
Path folder;
|
|
Path pbootPath;
|
|
std::string appVer; // The update's version, from PBOOT.PBP's own PARAM.SFO.
|
|
std::string discVersion; // The disc version it was built against.
|
|
std::string title; // PBOOT_TITLE, when the update names itself ("Update 2.01").
|
|
u64 sizeOnDisk = 0; // How much deleting it would actually free.
|
|
// True when a game shares the folder - a digital title, whose EBOOT.PBP sits right next to the
|
|
// update. Deleting the whole folder would take the game with it.
|
|
bool sharesFolderWithGame = false;
|
|
};
|
|
|
|
// Looks for an update installed for discId. Cheap enough to call while building a UI - a stat and
|
|
// a small read, plus a directory walk for the size.
|
|
bool FindInstalledGameUpdate(std::string_view discId, InstalledGameUpdate *update);
|
|
|
|
// Removes an installed update, to the trash where there is one. Takes the whole folder when the
|
|
// update is all that's in it, and only PBOOT.PBP when a game shares the folder - that stops the
|
|
// update from being used, and it's the only part we can still identify after the fact.
|
|
bool DeleteInstalledGameUpdate(const InstalledGameUpdate &update);
|