Headless: Add --dump-file to copy a file out of a disc image

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-25 15:23:08 -06:00
1 parent cae623f4e6
commit e015977f3c
5 files changed
+60

No files matched your search

+2
View File
@@ -224,6 +224,8 @@ static const CommandLineParam g_autoParams[] = {
{POFF(reRawBase), CmdParamType::String, "re-raw-base", '\0', "Treat --re-module as a raw code image loaded at this address, e.g. 0x08300000", CmdLineMode::Headless},
{POFF(reDecrypt), CmdParamType::String, "re-decrypt", '\0', "Decrypt one encrypted PSP file (PRX or ME image) and exit", CmdLineMode::Headless},
{POFF(reDecryptOut), CmdParamType::String, "re-decrypt-out", '\0', "Output file for --re-decrypt (default: decrypted.bin)", CmdLineMode::Headless},
{POFF(dumpFile), CmdParamType::String, "dump-file", '\0', "Copy one file out of the disc image, e.g. disc0:/PSP_GAME/USRDIR/MOVIE.PMF, and exit", CmdLineMode::Headless},
{POFF(dumpFileOut), CmdParamType::String, "dump-file-out", '\0', "Output file for --dump-file (default: the file's own name)", CmdLineMode::Headless},
{POFF(odsLog), CmdParamType::Bool, "odslog", 'o', "Also log through OutputDebugString (Windows)", CmdLineMode::Headless},
{POFF(generateInterpreterDispatch), CmdParamType::Bool, "generate-interpreter-dispatch", '\0', "Generate C++ interpreter dispatch code (ExecInstruction) to stdout and exit", CmdLineMode::Headless},
{POFF(resolutionScale), CmdParamType::Int, "resolution-scale", '\0', "Set the resolution scale factor"},
+4
View File
@@ -126,6 +126,10 @@ struct CommandLineOptions {
std::optional<std::string> reDecrypt;
// Where the plaintext goes. Defaults to "decrypted.bin".
std::optional<std::string> reDecryptOut;
// Headless: copy one file out of the disc image given as the positional argument, to
// --dump-file-out, and exit.
std::optional<std::string> dumpFile;
std::optional<std::string> dumpFileOut;
std::optional<int> memReadAction;
std::optional<int> memWriteAction;
+8
View File
@@ -1065,6 +1065,14 @@ int main(int argc, const char* argv[]) {
if (cmdLineOptions.reDecrypt.has_value()) {
return RunDecryptFile(cmdLineOptions.reDecrypt.value(), cmdLineOptions.reDecryptOut.value_or("decrypted.bin"));
}
if (cmdLineOptions.dumpFile.has_value()) {
if (cmdLineOptions.bootFilenames.empty()) {
fprintf(stderr, "--dump-file needs the disc image as the positional argument\n");
return 1;
}
const std::string &inPath = cmdLineOptions.dumpFile.value();
return RunDumpDiscFile(cmdLineOptions.bootFilenames[0], inPath, cmdLineOptions.dumpFileOut.value_or(Path(inPath.substr(inPath.find(':') + 1)).GetFilename()));
}
if (cmdLineOptions.reModule.has_value()) {
ReverseEngineerOptions reOptions;
reOptions.modulePath = cmdLineOptions.reModule.value();
+42
View File
@@ -393,6 +393,48 @@ int RunDecryptFile(const std::string &inPath, const std::string &outPath) {
return 0;
}
int RunDumpDiscFile(const std::string &discPath, const std::string &inPath, const std::string &outPath) {
std::string insideDisc = inPath;
const size_t colon = insideDisc.find(':');
if (colon != std::string::npos) {
insideDisc = insideDisc.substr(colon + 1);
}
if (insideDisc.empty() || insideDisc[0] != '/') {
insideDisc = "/" + insideDisc;
}
std::unique_ptr<FileLoader> loader(ConstructFileLoader(Path(discPath)));
std::string blockError;
std::shared_ptr<BlockDevice> device(loader ? ConstructBlockDevice(loader.get(), &blockError) : nullptr);
if (!device) {
fprintf(stderr, "dump-file: %s isn't a disc image we can read: %s\n", discPath.c_str(), blockError.c_str());
return 1;
}
// The ISO filesystem takes ownership of the block device, which owns the loader.
loader.release();
ISOFileSystem isoFs(&pspFileSystem, device);
const int handle = isoFs.OpenFile(insideDisc, FILEACCESS_READ);
if (handle < 0) {
fprintf(stderr, "dump-file: no such file on the disc: %s\n", insideDisc.c_str());
return 1;
}
const PSPFileInfo info = isoFs.GetFileInfoByHandle(handle);
std::vector<u8> data((size_t)info.size);
const size_t readBytes = data.empty() ? 0 : isoFs.ReadFile(handle, data.data(), (s64)data.size());
isoFs.CloseFile(handle);
if (readBytes != data.size()) {
fprintf(stderr, "dump-file: short read, %d of %d bytes\n", (int)readBytes, (int)data.size());
return 1;
}
if (!File::WriteDataToFile(false, data.data(), data.size(), Path(outPath))) {
fprintf(stderr, "dump-file: couldn't write %s\n", outPath.c_str());
return 1;
}
printf("dump-file: wrote %d bytes of %s to %s\n", (int)data.size(), insideDisc.c_str(), outPath.c_str());
return 0;
}
int RunReverseEngineer(const ReverseEngineerOptions &opts) {
// A module inside a disc image rather than on the host - see ReverseEngineerOptions.
const bool fromDisc = startsWithNoCase(opts.modulePath, "disc0:") || startsWithNoCase(opts.modulePath, "umd0:");
+4
View File
@@ -50,3 +50,7 @@ int RunReverseEngineer(const ReverseEngineerOptions &opts);
// flash0:/kd/resource, which are ordinary tagged containers with the signature blanked, so the
// normal module loader won't touch them. Returns a process exit code.
int RunDecryptFile(const std::string &inPath, const std::string &outPath);
// Copies one file out of a disc image (ISO/CSO/...) as-is, e.g. "disc0:/PSP_GAME/USRDIR/MOVIE.PMF".
// Returns a process exit code.
int RunDumpDiscFile(const std::string &discPath, const std::string &inPath, const std::string &outPath);