mirror of
https://github.com/ApfelTeeSaft/DolHook.git
synced 2026-08-27 03:33:36 +00:00
282 lines
8.2 KiB
C++
282 lines
8.2 KiB
C++
/**
|
|
* DolHook ISO Patcher
|
|
* Injects DolHook runtime into GameCube ISO
|
|
*/
|
|
|
|
#include "gcm.h"
|
|
#include "dol.h"
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <map>
|
|
#include <cstring>
|
|
|
|
using namespace dolhook;
|
|
|
|
struct PatcherConfig {
|
|
std::string input_iso;
|
|
std::string output_iso;
|
|
std::string game_id;
|
|
int log_level = 1; // 0=errors, 1=info, 2=debug
|
|
bool dry_run = false;
|
|
bool print_dol = false;
|
|
};
|
|
|
|
struct SymbolMap {
|
|
std::map<std::string, uint32_t> symbols;
|
|
|
|
bool load(const std::string& path) {
|
|
std::ifstream file(path);
|
|
if (!file) return false;
|
|
|
|
std::string line;
|
|
while (std::getline(file, line)) {
|
|
if (line.empty() || line[0] == '#') continue;
|
|
|
|
std::istringstream iss(line);
|
|
std::string name;
|
|
uint32_t addr;
|
|
|
|
if (iss >> name >> std::hex >> addr) {
|
|
symbols[name] = addr;
|
|
}
|
|
}
|
|
|
|
return !symbols.empty();
|
|
}
|
|
|
|
bool has(const std::string& name) const {
|
|
return symbols.find(name) != symbols.end();
|
|
}
|
|
|
|
uint32_t get(const std::string& name) const {
|
|
auto it = symbols.find(name);
|
|
return it != symbols.end() ? it->second : 0;
|
|
}
|
|
};
|
|
|
|
void print_usage(const char* prog) {
|
|
std::cout << "DolHook ISO Patcher v1.0\n\n";
|
|
std::cout << "Usage: " << prog << " INPUT.iso [OPTIONS]\n\n";
|
|
std::cout << "Options:\n";
|
|
std::cout << " --out FILE Output ISO path (default: modify input after backup)\n";
|
|
std::cout << " --id GAMEID Override game ID\n";
|
|
std::cout << " --log LEVEL Log level: 0=errors, 1=info, 2=debug (default: 1)\n";
|
|
std::cout << " --dry-run Parse only, don't write\n";
|
|
std::cout << " --print-dol Display DOL section table\n";
|
|
std::cout << " --help Show this help\n";
|
|
}
|
|
|
|
bool parse_args(int argc, char** argv, PatcherConfig& cfg) {
|
|
if (argc < 2) return false;
|
|
|
|
cfg.input_iso = argv[1];
|
|
|
|
for (int i = 2; i < argc; i++) {
|
|
std::string arg = argv[i];
|
|
|
|
if (arg == "--help") {
|
|
return false;
|
|
} else if (arg == "--out" && i + 1 < argc) {
|
|
cfg.output_iso = argv[++i];
|
|
} else if (arg == "--id" && i + 1 < argc) {
|
|
cfg.game_id = argv[++i];
|
|
} else if (arg == "--log" && i + 1 < argc) {
|
|
cfg.log_level = std::atoi(argv[++i]);
|
|
} else if (arg == "--dry-run") {
|
|
cfg.dry_run = true;
|
|
} else if (arg == "--print-dol") {
|
|
cfg.print_dol = true;
|
|
} else {
|
|
std::cerr << "Unknown option: " << arg << "\n";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void write_be32(uint8_t* p, uint32_t v) {
|
|
p[0] = (v >> 24) & 0xFF;
|
|
p[1] = (v >> 16) & 0xFF;
|
|
p[2] = (v >> 8) & 0xFF;
|
|
p[3] = v & 0xFF;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
PatcherConfig cfg;
|
|
|
|
if (!parse_args(argc, argv, cfg)) {
|
|
print_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
// Load ISO
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << "Loading ISO: " << cfg.input_iso << "\n";
|
|
}
|
|
|
|
GCMFile iso;
|
|
if (!iso.load(cfg.input_iso)) {
|
|
std::cerr << "Error: Failed to load ISO\n";
|
|
return 1;
|
|
}
|
|
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << iso.header().format() << "\n";
|
|
}
|
|
|
|
// Read DOL
|
|
DOLFile dol = iso.read_dol();
|
|
if (cfg.log_level >= 2 || cfg.print_dol) {
|
|
std::cout << dol.format_header() << "\n";
|
|
}
|
|
|
|
// Load payload
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << "Loading payload...\n";
|
|
}
|
|
|
|
std::ifstream payload_file("payload/payload.bin", std::ios::binary);
|
|
if (!payload_file) {
|
|
std::cerr << "Error: payload/payload.bin not found\n";
|
|
std::cerr << "Build the runtime first with 'make runtime'\n";
|
|
return 1;
|
|
}
|
|
|
|
payload_file.seekg(0, std::ios::end);
|
|
size_t payload_size = payload_file.tellg();
|
|
payload_file.seekg(0, std::ios::beg);
|
|
|
|
std::vector<uint8_t> payload(payload_size);
|
|
payload_file.read(reinterpret_cast<char*>(payload.data()), payload_size);
|
|
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << " Payload size: " << payload_size << " bytes\n";
|
|
}
|
|
|
|
// Load symbol map
|
|
SymbolMap symbols;
|
|
if (!symbols.load("payload/payload.sym")) {
|
|
std::cerr << "Warning: payload.sym not found, using defaults\n";
|
|
// Set reasonable defaults
|
|
symbols.symbols["__dolhook_entry"] = 0x80400000;
|
|
symbols.symbols["__dolhook_original_entry"] = 0x80400100;
|
|
}
|
|
|
|
if (!symbols.has("__dolhook_entry")) {
|
|
std::cerr << "Error: __dolhook_entry symbol not found\n";
|
|
return 1;
|
|
}
|
|
|
|
uint32_t hook_entry = symbols.get("__dolhook_entry");
|
|
uint32_t orig_entry_slot = symbols.get("__dolhook_original_entry");
|
|
|
|
if (cfg.log_level >= 2) {
|
|
std::cout << " Hook entry: 0x" << std::hex << hook_entry << "\n";
|
|
std::cout << " Original entry slot: 0x" << std::hex << orig_entry_slot << "\n";
|
|
}
|
|
|
|
// Save original entry
|
|
uint32_t original_entry = dol.header().entry_point;
|
|
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << "\nPatching:\n";
|
|
std::cout << " Original entry: 0x" << std::hex << original_entry << "\n";
|
|
std::cout << " New entry: 0x" << std::hex << hook_entry << "\n";
|
|
}
|
|
|
|
// Find offset of __dolhook_original_entry in payload
|
|
// For simplicity, scan for the placeholder value 0x80003100
|
|
uint32_t placeholder = 0x80003100;
|
|
size_t entry_offset = 0;
|
|
bool found_slot = false;
|
|
|
|
for (size_t i = 0; i + 4 <= payload.size(); i += 4) {
|
|
uint32_t val = (payload[i] << 24) | (payload[i+1] << 16) |
|
|
(payload[i+2] << 8) | payload[i+3];
|
|
if (val == placeholder) {
|
|
entry_offset = i;
|
|
found_slot = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found_slot && cfg.log_level >= 1) {
|
|
std::cout << " Warning: Placeholder not found, appending entry data\n";
|
|
entry_offset = payload.size();
|
|
payload.resize(payload.size() + 4);
|
|
}
|
|
|
|
// Write original entry to payload
|
|
write_be32(payload.data() + entry_offset, original_entry);
|
|
|
|
if (cfg.log_level >= 2) {
|
|
std::cout << " Wrote original entry at payload offset: 0x"
|
|
<< std::hex << entry_offset << "\n";
|
|
}
|
|
|
|
if (cfg.dry_run) {
|
|
std::cout << "\nDry run - no changes written\n";
|
|
return 0;
|
|
}
|
|
|
|
// Choose load address (after highest existing section)
|
|
uint32_t load_addr = (dol.header().get_highest_addr() + 0xFF) & ~0xFF;
|
|
if (load_addr < 0x80400000) load_addr = 0x80400000;
|
|
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << " Loading payload at: 0x" << std::hex << load_addr << "\n";
|
|
}
|
|
|
|
// Inject payload as text section
|
|
if (!dol.inject_payload(payload, load_addr, true)) {
|
|
std::cerr << "Error: Failed to inject payload\n";
|
|
return 1;
|
|
}
|
|
|
|
// Update entry point
|
|
dol.header().entry_point = hook_entry;
|
|
|
|
if (cfg.log_level >= 2) {
|
|
std::cout << "\nModified DOL:\n" << dol.format_header() << "\n";
|
|
}
|
|
|
|
// Create backup
|
|
if (cfg.output_iso.empty()) {
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << "Creating backup...\n";
|
|
}
|
|
iso.create_backup(cfg.input_iso);
|
|
cfg.output_iso = cfg.input_iso;
|
|
}
|
|
|
|
// Try to write DOL in place first
|
|
bool wrote_inline = iso.write_dol(dol);
|
|
|
|
if (!wrote_inline) {
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << "DOL too large, relocating to end of ISO...\n";
|
|
}
|
|
iso.relocate_dol(dol);
|
|
}
|
|
|
|
// Write ISO
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << "Writing patched ISO: " << cfg.output_iso << "\n";
|
|
}
|
|
|
|
if (!iso.save(cfg.output_iso)) {
|
|
std::cerr << "Error: Failed to write ISO\n";
|
|
return 1;
|
|
}
|
|
|
|
if (cfg.log_level >= 1) {
|
|
std::cout << "\n✓ Patch complete!\n";
|
|
std::cout << " Original entry: 0x" << std::hex << original_entry << "\n";
|
|
std::cout << " New entry: 0x" << std::hex << hook_entry << "\n";
|
|
std::cout << " Payload size: " << std::dec << payload_size << " bytes\n";
|
|
}
|
|
|
|
return 0;
|
|
} |