mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
headless: add --re-decrypt and --re-raw-base to the RE tool
--re-decrypt runs pspDecryptPRX() over a file and unpacks the KL4E/KL3E stream behind it. This opens up flash0:/kd/resource/*.img, the images the Media Engine actually runs: they are ordinary tagged containers (tag 862648D1, which PrxDecrypter already has a key for) with the ~PSP signature blanked, so the normal module loader never touches them. --re-raw-base analyzes --re-module as a flat code image at a given address rather than as a PRX. The decrypted ME images are raw MIPS with no ELF around them; the address they were linked for is recoverable from their own jal targets (0x08300000 for meimg.img). Also makes PrxDecrypter.h self-contained - PSP_Header is built from _le types, so it needs Common/Swap.h rather than relying on the includer.
This commit is contained in:
1 parent
d163551823
commit
1a1430e8ca
6 files changed
+197
-76
No files matched your search
@@ -216,6 +216,9 @@ static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(reOut), CmdParamType::String, "re-out", '\0', "Directory for --re-module output (default: re-out)", CmdLineMode::Headless},
|
||||
{POFF(reFunc), CmdParamType::String, "re-func", '\0', "Only disassemble this function of --re-module, by name or address", CmdLineMode::Headless},
|
||||
{POFF(reSyms), CmdParamType::String, "re-syms", '\0', "Apply a .ppsym file of known names before dumping --re-module", CmdLineMode::Headless},
|
||||
{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(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"},
|
||||
|
||||
@@ -99,6 +99,15 @@ struct CommandLineOptions {
|
||||
// A .ppsym file of already-known names to apply before dumping, so the disassembly comes
|
||||
// out readable. Module-relative, same format the emulator saves.
|
||||
std::optional<std::string> reSyms;
|
||||
// Headless: analyze --re-module as a flat code image loaded at this address instead of as a
|
||||
// PRX. The decrypted ME images are raw MIPS with no ELF around them; the address they were
|
||||
// linked for is recoverable from their own jal targets.
|
||||
std::optional<std::string> reRawBase;
|
||||
// Headless: decrypt one encrypted PSP file (a tagged PRX, or an ME image from
|
||||
// flash0:/kd/resource) to --re-decrypt-out and exit. Nothing is loaded or run.
|
||||
std::optional<std::string> reDecrypt;
|
||||
// Where the plaintext goes. Defaults to "decrypted.bin".
|
||||
std::optional<std::string> reDecryptOut;
|
||||
|
||||
std::optional<int> memReadAction;
|
||||
std::optional<int> memWriteAction;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Swap.h" // PSP_Header below is all _le types.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(push, 1)
|
||||
|
||||
@@ -863,12 +863,17 @@ int main(int argc, const char* argv[]) {
|
||||
coreParameter.nandRoot = g_Config.nandRootDirectory;
|
||||
// Placed here rather than with the other early-exit subcommands above, because resolving a
|
||||
// "flash0:/kd/foo.prx" module path needs nandRootDirectory, which is only settled just above.
|
||||
if (cmdLineOptions.reDecrypt.has_value()) {
|
||||
return RunDecryptFile(cmdLineOptions.reDecrypt.value(), cmdLineOptions.reDecryptOut.value_or("decrypted.bin"));
|
||||
}
|
||||
if (cmdLineOptions.reModule.has_value()) {
|
||||
ReverseEngineerOptions reOptions;
|
||||
reOptions.modulePath = cmdLineOptions.reModule.value();
|
||||
reOptions.outDir = cmdLineOptions.reOut.value_or("re-out");
|
||||
reOptions.funcFilter = cmdLineOptions.reFunc.value_or("");
|
||||
reOptions.symsFile = cmdLineOptions.reSyms.value_or("");
|
||||
// Accepts "0x08300000" or plain decimal.
|
||||
reOptions.rawBase = (u32)strtoul(cmdLineOptions.reRawBase.value_or("0").c_str(), nullptr, 0);
|
||||
reOptions.verbose = testOptions.verbose;
|
||||
return RunReverseEngineer(reOptions);
|
||||
}
|
||||
|
||||
+168
-76
@@ -42,6 +42,7 @@
|
||||
#include "Core/Debugger/DebugInterface.h"
|
||||
#include "Core/Debugger/DisassemblyManager.h"
|
||||
#include "Core/Debugger/SymbolMap.h"
|
||||
#include "Core/ELF/PrxDecrypter.h"
|
||||
#include "Core/FileSystems/DirectoryFileSystem.h"
|
||||
#include "Core/FileSystems/MetaFileSystem.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
@@ -54,6 +55,7 @@
|
||||
#include "Core/MIPS/MIPSTables.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/Util/KL4E.h"
|
||||
|
||||
#include "headless/ReverseEngineer.h"
|
||||
|
||||
@@ -310,6 +312,57 @@ void WriteRegEvidence(FILE *f, const FuncInfo &func) {
|
||||
|
||||
} // namespace
|
||||
|
||||
int RunDecryptFile(const std::string &inPath, const std::string &outPath) {
|
||||
const Path in = ResolveModulePath(inPath);
|
||||
std::string data;
|
||||
if (!File::ReadBinaryFileToString(in, &data)) {
|
||||
fprintf(stderr, "re-decrypt: couldn't read %s\n", in.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (data.size() < 0x150) {
|
||||
fprintf(stderr, "re-decrypt: %s is too small to hold a header (%d bytes)\n", in.c_str(), (int)data.size());
|
||||
return 1;
|
||||
}
|
||||
|
||||
const u32 tag = *(const u32_le *)(data.data() + 0xD0);
|
||||
printf("re-decrypt: %s, %d bytes, tag %08X\n", in.c_str(), (int)data.size(), tag);
|
||||
|
||||
// Decrypts in place on the PSP too, but keep the input around so a failure leaves it readable.
|
||||
std::vector<u8> out(data.size());
|
||||
int outSize = pspDecryptPRX((const u8 *)data.data(), out.data(), (u32)data.size());
|
||||
if (outSize <= 0) {
|
||||
fprintf(stderr, "re-decrypt: no key for tag %08X, or the data didn't decrypt (%d)\n", tag, outSize);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The plaintext is usually still compressed - the ME images are KL4E. Unpack it here rather
|
||||
// than leaving that to the caller, since the point is to get at the code.
|
||||
bool isKL3E = false;
|
||||
if (IsKL4EMagic(out.data(), outSize, &isKL3E)) {
|
||||
// The header's elf_size is the decompressed size, but it's zero in the ME images, so
|
||||
// just give the decompressor plenty of room and go by what it returns.
|
||||
const int maxOut = std::max(16 * 1024 * 1024, outSize * 16);
|
||||
std::vector<u8> unpacked(maxOut);
|
||||
const int unpackedSize = DecompressKL4E(unpacked.data(), maxOut, out.data() + 4, (size_t)outSize - 4, nullptr, isKL3E);
|
||||
if (unpackedSize < 0) {
|
||||
fprintf(stderr, "re-decrypt: %s decompression failed (%d)\n", isKL3E ? "KL3E" : "KL4E", unpackedSize);
|
||||
return 1;
|
||||
}
|
||||
printf("re-decrypt: %s: %d -> %d bytes\n", isKL3E ? "KL3E" : "KL4E", outSize, unpackedSize);
|
||||
unpacked.resize(unpackedSize);
|
||||
out = std::move(unpacked);
|
||||
outSize = unpackedSize;
|
||||
}
|
||||
|
||||
const Path outFile(outPath);
|
||||
if (!File::WriteDataToFile(false, out.data(), outSize, outFile)) {
|
||||
fprintf(stderr, "re-decrypt: couldn't write %s\n", outFile.c_str());
|
||||
return 1;
|
||||
}
|
||||
printf("re-decrypt: wrote %d bytes to %s\n", outSize, outFile.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RunReverseEngineer(const ReverseEngineerOptions &opts) {
|
||||
const Path modulePath = ResolveModulePath(opts.modulePath);
|
||||
if (!File::Exists(modulePath)) {
|
||||
@@ -334,36 +387,65 @@ int RunReverseEngineer(const ReverseEngineerOptions &opts) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Mount the containing directory so the normal file-backed loader path can be used.
|
||||
const std::string dir = modulePath.GetDirectory();
|
||||
const std::string filename = modulePath.GetFilename();
|
||||
auto hostFs = std::make_shared<DirectoryFileSystem>(&pspFileSystem, Path(dir), FileSystemFlags::FLASH);
|
||||
pspFileSystem.Mount("host0:", hostFs);
|
||||
|
||||
std::string error;
|
||||
const SceUID uid = KernelLoadModule("host0:/" + filename, &error);
|
||||
if (uid < 0) {
|
||||
fprintf(stderr, "re: failed to load %s: %s\n", modulePath.c_str(), error.c_str());
|
||||
ShutdownMinimalPSP();
|
||||
return 1;
|
||||
}
|
||||
PSPModule *module = nullptr;
|
||||
std::string moduleName;
|
||||
u32 base = 0;
|
||||
u32 blockSize = 0;
|
||||
|
||||
u32 kerr = 0;
|
||||
PSPModule *module = kernelObjects.Get<PSPModule>(uid, kerr);
|
||||
if (!module) {
|
||||
fprintf(stderr, "re: loaded module vanished (uid %d)\n", uid);
|
||||
ShutdownMinimalPSP();
|
||||
return 1;
|
||||
}
|
||||
if (module->isFake) {
|
||||
fprintf(stderr, "re: module was fake-loaded (HLE stub) rather than really loaded - can't analyze\n");
|
||||
ShutdownMinimalPSP();
|
||||
return 1;
|
||||
}
|
||||
if (opts.rawBase) {
|
||||
// A flat image: no header, no relocation, nothing to resolve. Just put it where it was
|
||||
// linked to run and let the function scanner loose on it.
|
||||
std::string data;
|
||||
if (!File::ReadBinaryFileToString(modulePath, &data)) {
|
||||
fprintf(stderr, "re: couldn't read %s\n", modulePath.c_str());
|
||||
ShutdownMinimalPSP();
|
||||
return 1;
|
||||
}
|
||||
if (!Memory::IsValid4AlignedRange(opts.rawBase, (u32)data.size())) {
|
||||
fprintf(stderr, "re: %08x + %d bytes isn't a valid aligned RAM range\n",
|
||||
opts.rawBase, (int)data.size());
|
||||
ShutdownMinimalPSP();
|
||||
return 1;
|
||||
}
|
||||
Memory::MemcpyUnchecked(opts.rawBase, data.data(), (u32)data.size());
|
||||
moduleName = filename;
|
||||
base = opts.rawBase;
|
||||
blockSize = (u32)data.size();
|
||||
printf("re: raw image %s at %08x, %d bytes\n", filename.c_str(), base, blockSize);
|
||||
MIPSAnalyst::ScanForFunctions(base, base + blockSize - 4, true);
|
||||
} else {
|
||||
// Mount the containing directory so the normal file-backed loader path can be used.
|
||||
auto hostFs = std::make_shared<DirectoryFileSystem>(&pspFileSystem, Path(dir), FileSystemFlags::FLASH);
|
||||
pspFileSystem.Mount("host0:", hostFs);
|
||||
|
||||
const std::string moduleName = module->nm.name;
|
||||
const u32 base = module->memoryBlockAddr;
|
||||
const u32 blockSize = module->memoryBlockSize;
|
||||
std::string error;
|
||||
const SceUID uid = KernelLoadModule("host0:/" + filename, &error);
|
||||
if (uid < 0) {
|
||||
fprintf(stderr, "re: failed to load %s: %s\n", modulePath.c_str(), error.c_str());
|
||||
ShutdownMinimalPSP();
|
||||
return 1;
|
||||
}
|
||||
|
||||
u32 kerr = 0;
|
||||
module = kernelObjects.Get<PSPModule>(uid, kerr);
|
||||
if (!module) {
|
||||
fprintf(stderr, "re: loaded module vanished (uid %d)\n", uid);
|
||||
ShutdownMinimalPSP();
|
||||
return 1;
|
||||
}
|
||||
if (module->isFake) {
|
||||
fprintf(stderr, "re: module was fake-loaded (HLE stub) rather than really loaded - can't analyze\n");
|
||||
ShutdownMinimalPSP();
|
||||
return 1;
|
||||
}
|
||||
|
||||
moduleName = module->nm.name;
|
||||
base = module->memoryBlockAddr;
|
||||
blockSize = module->memoryBlockSize;
|
||||
}
|
||||
|
||||
// The loader's function scan names everything z_un_<addr>. We know better for two whole
|
||||
// categories: exported functions have a NID the HLE tables can often name, and every import
|
||||
@@ -371,21 +453,23 @@ int RunReverseEngineer(const ReverseEngineerOptions &opts) {
|
||||
// disassembly below reads as a name instead of a bare address.
|
||||
const int moduleIdx = g_symbolMap->GetModuleIndexByName(moduleName);
|
||||
int namedExports = 0, namedImports = 0;
|
||||
for (const FuncSymbolExport &exp : module->exportedFuncs) {
|
||||
const char *known = GetHLEFuncName(exp.moduleName, exp.nid);
|
||||
const std::string name = known ? known : StringFromFormat("%s_%08x", exp.moduleName, exp.nid);
|
||||
u32 size = g_symbolMap->GetFunctionSize(exp.symAddr);
|
||||
if (size == SymbolMap::INVALID_ADDRESS) {
|
||||
size = 4;
|
||||
if (module) {
|
||||
for (const FuncSymbolExport &exp : module->exportedFuncs) {
|
||||
const char *known = GetHLEFuncName(exp.moduleName, exp.nid);
|
||||
const std::string name = known ? known : StringFromFormat("%s_%08x", exp.moduleName, exp.nid);
|
||||
u32 size = g_symbolMap->GetFunctionSize(exp.symAddr);
|
||||
if (size == SymbolMap::INVALID_ADDRESS) {
|
||||
size = 4;
|
||||
}
|
||||
g_symbolMap->AddFunction(name.c_str(), exp.symAddr, size, moduleIdx, true);
|
||||
namedExports++;
|
||||
}
|
||||
for (const FuncSymbolImport &imp : module->importedFuncs) {
|
||||
const char *known = GetHLEFuncName(imp.moduleName, imp.nid);
|
||||
const std::string name = known ? known : StringFromFormat("%s_%08x", imp.moduleName, imp.nid);
|
||||
g_symbolMap->AddFunction(name.c_str(), imp.stubAddr, 8, moduleIdx, true);
|
||||
namedImports++;
|
||||
}
|
||||
g_symbolMap->AddFunction(name.c_str(), exp.symAddr, size, moduleIdx, true);
|
||||
namedExports++;
|
||||
}
|
||||
for (const FuncSymbolImport &imp : module->importedFuncs) {
|
||||
const char *known = GetHLEFuncName(imp.moduleName, imp.nid);
|
||||
const std::string name = known ? known : StringFromFormat("%s_%08x", imp.moduleName, imp.nid);
|
||||
g_symbolMap->AddFunction(name.c_str(), imp.stubAddr, 8, moduleIdx, true);
|
||||
namedImports++;
|
||||
}
|
||||
|
||||
// Optional pre-existing names, so the disassembly comes out readable instead of a wall of
|
||||
@@ -404,7 +488,7 @@ int RunReverseEngineer(const ReverseEngineerOptions &opts) {
|
||||
// file offsets rather than addresses, so the loader's own scan is left with nothing to look
|
||||
// at and finds no functions. For reverse engineering we would still like the disassembly, and
|
||||
// we know exactly which range is code, so scan it ourselves.
|
||||
{
|
||||
if (module) {
|
||||
bool anyInModule = false;
|
||||
for (const SymbolEntry &sym : g_symbolMap->GetAllActiveSymbols(ST_FUNCTION)) {
|
||||
if (sym.address >= base && sym.address < base + blockSize) {
|
||||
@@ -485,44 +569,50 @@ int RunReverseEngineer(const ReverseEngineerOptions &opts) {
|
||||
|
||||
fprintf(f, "# %s\n\n", moduleName.c_str());
|
||||
fprintf(f, "- file: `%s`\n", modulePath.GetFilename().c_str());
|
||||
fprintf(f, "- crc32: `%08x` (matches `PSP/SYSTEM/SYMBOLS/%s_%08x.ppsym`)\n", module->crc, moduleName.c_str(), module->crc);
|
||||
fprintf(f, "- attribute: `%04x`%s\n", (u32)module->nm.attribute,
|
||||
(module->nm.attribute & PSP_MODULE_KERNEL_MODE) ? " (kernel mode)" : "");
|
||||
fprintf(f, "- version: %d.%d\n", module->nm.version[1], module->nm.version[0]);
|
||||
fprintf(f, "- loaded at: `%08x`, size `%08x`\n", base, blockSize);
|
||||
fprintf(f, "- text: `%08x`..`%08x` data: `%x` bss: `%x` gp: `%08x`\n",
|
||||
(u32)module->nm.text_addr, (u32)module->nm.text_addr + (u32)module->nm.text_size,
|
||||
(u32)module->nm.data_size, (u32)module->nm.bss_size, (u32)module->nm.gp_value);
|
||||
fprintf(f, "- entry: `%08x` module_start: `%08x` module_stop: `%08x`\n\n",
|
||||
(u32)module->nm.entry_addr, (u32)module->nm.module_start_func, (u32)module->nm.module_stop_func);
|
||||
if (!module) {
|
||||
fprintf(f, "- raw image, loaded at `%08x`, size `%08x`\n", base, blockSize);
|
||||
fprintf(f, "- no module header: no exports, imports, segments or relocation.\n\n");
|
||||
} else {
|
||||
fprintf(f, "- crc32: `%08x` (matches `PSP/SYSTEM/SYMBOLS/%s_%08x.ppsym`)\n", module->crc, moduleName.c_str(), module->crc);
|
||||
fprintf(f, "- attribute: `%04x`%s\n", (u32)module->nm.attribute,
|
||||
(module->nm.attribute & PSP_MODULE_KERNEL_MODE) ? " (kernel mode)" : "");
|
||||
fprintf(f, "- version: %d.%d\n", module->nm.version[1], module->nm.version[0]);
|
||||
fprintf(f, "- loaded at: `%08x`, size `%08x`\n", base, blockSize);
|
||||
fprintf(f, "- text: `%08x`..`%08x` data: `%x` bss: `%x` gp: `%08x`\n",
|
||||
(u32)module->nm.text_addr, (u32)module->nm.text_addr + (u32)module->nm.text_size,
|
||||
(u32)module->nm.data_size, (u32)module->nm.bss_size, (u32)module->nm.gp_value);
|
||||
fprintf(f, "- entry: `%08x` module_start: `%08x` module_stop: `%08x`\n\n",
|
||||
(u32)module->nm.entry_addr, (u32)module->nm.module_start_func, (u32)module->nm.module_stop_func);
|
||||
|
||||
fprintf(f, "## Segments\n\n| # | address | size |\n|---|---|---|\n");
|
||||
for (u32 i = 0; i < module->nm.nsegment && i < 4; i++) {
|
||||
fprintf(f, "| %d | `%08x` | `%x` |\n", i, (u32)module->nm.segmentaddr[i], (u32)module->nm.segmentsize[i]);
|
||||
}
|
||||
fprintf(f, "## Segments\n\n| # | address | size |\n|---|---|---|\n");
|
||||
for (u32 i = 0; i < module->nm.nsegment && i < 4; i++) {
|
||||
fprintf(f, "| %d | `%08x` | `%x` |\n", i, (u32)module->nm.segmentaddr[i], (u32)module->nm.segmentsize[i]);
|
||||
}
|
||||
|
||||
fprintf(f, "\n## Exports (%d functions, %d variables)\n\n",
|
||||
(int)module->exportedFuncs.size(), (int)module->exportedVars.size());
|
||||
fprintf(f, "| library | NID | address | name |\n|---|---|---|---|\n");
|
||||
for (const FuncSymbolExport &exp : module->exportedFuncs) {
|
||||
const char *known = GetHLEFuncName(exp.moduleName, exp.nid);
|
||||
fprintf(f, "| `%s` | `%08x` | `%08x` | %s |\n", exp.moduleName, exp.nid, exp.symAddr,
|
||||
known ? known : nameOf(exp.symAddr).c_str());
|
||||
}
|
||||
for (const VarSymbolExport &exp : module->exportedVars) {
|
||||
fprintf(f, "| `%s` | `%08x` | `%08x` | *(variable)* |\n", exp.moduleName, exp.nid, exp.symAddr);
|
||||
}
|
||||
fprintf(f, "\n## Exports (%d functions, %d variables)\n\n",
|
||||
(int)module->exportedFuncs.size(), (int)module->exportedVars.size());
|
||||
fprintf(f, "| library | NID | address | name |\n|---|---|---|---|\n");
|
||||
for (const FuncSymbolExport &exp : module->exportedFuncs) {
|
||||
const char *known = GetHLEFuncName(exp.moduleName, exp.nid);
|
||||
fprintf(f, "| `%s` | `%08x` | `%08x` | %s |\n", exp.moduleName, exp.nid, exp.symAddr,
|
||||
known ? known : nameOf(exp.symAddr).c_str());
|
||||
}
|
||||
for (const VarSymbolExport &exp : module->exportedVars) {
|
||||
fprintf(f, "| `%s` | `%08x` | `%08x` | *(variable)* |\n", exp.moduleName, exp.nid, exp.symAddr);
|
||||
}
|
||||
|
||||
fprintf(f, "\n## Imports (%d functions, %d variables)\n\n",
|
||||
(int)module->importedFuncs.size(), (int)module->importedVars.size());
|
||||
fprintf(f, "| library | NID | stub | name |\n|---|---|---|---|\n");
|
||||
for (const FuncSymbolImport &imp : module->importedFuncs) {
|
||||
const char *known = GetHLEFuncName(imp.moduleName, imp.nid);
|
||||
fprintf(f, "| `%s` | `%08x` | `%08x` | %s |\n", imp.moduleName, imp.nid, imp.stubAddr,
|
||||
known ? known : "*(unknown NID)*");
|
||||
}
|
||||
for (const VarSymbolImport &imp : module->importedVars) {
|
||||
fprintf(f, "| `%s` | `%08x` | `%08x` | *(variable)* |\n", imp.moduleName, imp.nid, imp.stubAddr);
|
||||
}
|
||||
|
||||
fprintf(f, "\n## Imports (%d functions, %d variables)\n\n",
|
||||
(int)module->importedFuncs.size(), (int)module->importedVars.size());
|
||||
fprintf(f, "| library | NID | stub | name |\n|---|---|---|---|\n");
|
||||
for (const FuncSymbolImport &imp : module->importedFuncs) {
|
||||
const char *known = GetHLEFuncName(imp.moduleName, imp.nid);
|
||||
fprintf(f, "| `%s` | `%08x` | `%08x` | %s |\n", imp.moduleName, imp.nid, imp.stubAddr,
|
||||
known ? known : "*(unknown NID)*");
|
||||
}
|
||||
for (const VarSymbolImport &imp : module->importedVars) {
|
||||
fprintf(f, "| `%s` | `%08x` | `%08x` | *(variable)* |\n", imp.moduleName, imp.nid, imp.stubAddr);
|
||||
}
|
||||
|
||||
fprintf(f, "\n## Functions (%d)\n\n", (int)funcs.size());
|
||||
@@ -677,7 +767,9 @@ int RunReverseEngineer(const ReverseEngineerOptions &opts) {
|
||||
written++;
|
||||
}
|
||||
|
||||
printf("re: %s (crc %08x) at %08x, %d bytes\n", moduleName.c_str(), module->crc, base, blockSize);
|
||||
if (module) {
|
||||
printf("re: %s (crc %08x) at %08x, %d bytes\n", moduleName.c_str(), module->crc, base, blockSize);
|
||||
}
|
||||
printf("re: %d exports (%d named), %d imports (%d named), %d functions; wrote %d disassembly file(s)\n",
|
||||
(int)module->exportedFuncs.size(), namedExports, (int)module->importedFuncs.size(), namedImports,
|
||||
(int)funcs.size(), written);
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
struct ReverseEngineerOptions {
|
||||
// PRX/ELF to load. A host path, or a PSP path like "flash0:/kd/libmp3.prx" (resolved
|
||||
// against the configured NAND directory).
|
||||
@@ -29,9 +31,18 @@ struct ReverseEngineerOptions {
|
||||
std::string funcFilter;
|
||||
// Optional .syms file applied before dumping, so names show up in every caller.
|
||||
std::string symsFile;
|
||||
// If non-zero, modulePath is a flat code image rather than a PRX: it's copied to this
|
||||
// address and scanned directly, with no loader, no relocation and no imports/exports.
|
||||
u32 rawBase = 0;
|
||||
bool verbose = false;
|
||||
};
|
||||
|
||||
// Loads a module standalone (no game), analyzes it, and writes a report. Returns a process
|
||||
// exit code.
|
||||
int RunReverseEngineer(const ReverseEngineerOptions &opts);
|
||||
|
||||
// Decrypts one encrypted PSP file and writes the plaintext, without loading anything.
|
||||
// Handles any container pspDecryptPRX() knows a tag for - including the ME images in
|
||||
// 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);
|
||||
Reference in new issue
Block a user