mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 16:00:32 +02:00
648 lines
25 KiB
C++
648 lines
25 KiB
C++
#include "system_bridge.h"
|
|
|
|
#include <cctype>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#if defined(_WIN32)
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
|
|
#include "abi_bridge.h"
|
|
#include "memory.h"
|
|
#include "ppc_runtime.h"
|
|
#include "recomp_mod_loader.h"
|
|
#include "runtime_config.h"
|
|
#include "runtime_log.h"
|
|
#include "runtime_product.h"
|
|
#include "timebase_contract.h"
|
|
|
|
// Global flag to suppress SEH reporting during static constructor execution
|
|
bool g_suppressSehReporting = false;
|
|
thread_local MkwJmpBuf* g_sehJumpTarget = nullptr;
|
|
thread_local uint32_t g_sehLastExceptionCode = 0;
|
|
thread_local uintptr_t g_sehLastExceptionAddress = 0;
|
|
thread_local uintptr_t g_sehLastAccessedAddress = 0;
|
|
thread_local uint32_t g_sehLastAccessType = 0;
|
|
|
|
// Forward declaration for data section initialization generated by the translator.
|
|
extern "C" void InitializeDataSections();
|
|
|
|
namespace {
|
|
std::string Hex32(uint32_t value) {
|
|
std::ostringstream oss;
|
|
oss << std::hex << std::uppercase << std::setw(8) << std::setfill('0') << value;
|
|
return oss.str();
|
|
}
|
|
|
|
std::optional<std::string> PrintableU32(uint32_t value) {
|
|
std::string out;
|
|
out.reserve(4);
|
|
for (int shift = 24; shift >= 0; shift -= 8) {
|
|
const auto ch = static_cast<unsigned char>((value >> shift) & 0xFFu);
|
|
if (!std::isprint(ch)) {
|
|
return std::nullopt;
|
|
}
|
|
out.push_back(static_cast<char>(ch));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::optional<TranslatedFunctionInfo> FindNearestTranslatedFunction(uint32_t address) {
|
|
auto best = TranslatedFunctionRegistry::FindNearestByAddress(address);
|
|
|
|
// Crash-only heuristic: large gaps are usually data/FourCC values, not code offsets.
|
|
if (best && address - best->address < 0x10000u) {
|
|
return best;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
// generated/guest_symbol_table.cpp (or the stub when it is absent): the
|
|
// project's function-map names, sorted by address, for crash symbolization.
|
|
extern "C" {
|
|
extern const uint32_t kGuestMapSymbolCount;
|
|
extern const uint32_t kGuestMapSymbolAddresses[];
|
|
extern const char* const kGuestMapSymbolNames[];
|
|
}
|
|
|
|
// Floor lookup into the function-map name table with the same gap cap as the
|
|
// registry heuristic above. Returns nullptr when no named symbol is close.
|
|
const char* GuestMapSymbolFloor(uint32_t address, uint32_t* symbolStart) {
|
|
if (kGuestMapSymbolCount == 0) {
|
|
return nullptr;
|
|
}
|
|
uint32_t lo = 0;
|
|
uint32_t hi = kGuestMapSymbolCount;
|
|
while (lo < hi) {
|
|
const uint32_t mid = lo + (hi - lo) / 2;
|
|
if (kGuestMapSymbolAddresses[mid] <= address) {
|
|
lo = mid + 1;
|
|
} else {
|
|
hi = mid;
|
|
}
|
|
}
|
|
if (lo == 0) {
|
|
return nullptr;
|
|
}
|
|
const uint32_t index = lo - 1;
|
|
if (address - kGuestMapSymbolAddresses[index] >= 0x10000u) {
|
|
return nullptr;
|
|
}
|
|
if (symbolStart) {
|
|
*symbolStart = kGuestMapSymbolAddresses[index];
|
|
}
|
|
return kGuestMapSymbolNames[index];
|
|
}
|
|
|
|
std::optional<uint32_t> ReadGuest32NoThrow(uint32_t address) {
|
|
uint32_t value = 0;
|
|
if (MemoryInline::TryReadMappedScalar(address, value)) {
|
|
return value;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<uint8_t> ReadGuest8NoThrow(uint32_t address) {
|
|
uint8_t value = 0;
|
|
if (MemoryInline::TryReadMappedScalar(address, value)) {
|
|
return value;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> GuestCStringPreview(uint32_t address) {
|
|
std::string out;
|
|
out.reserve(48);
|
|
for (uint32_t i = 0; i < 48; ++i) {
|
|
const auto byte = ReadGuest8NoThrow(address + i);
|
|
if (!byte) {
|
|
return std::nullopt;
|
|
}
|
|
if (*byte == 0) {
|
|
return out.size() >= 3 ? std::optional<std::string>(out) : std::nullopt;
|
|
}
|
|
if (!std::isprint(*byte)) {
|
|
return std::nullopt;
|
|
}
|
|
out.push_back(static_cast<char>(*byte));
|
|
}
|
|
out += "...";
|
|
return out;
|
|
}
|
|
|
|
void PrintGuestValue(std::ostream& os, std::string_view label, uint32_t value) {
|
|
os << " ";
|
|
if (!label.empty()) {
|
|
os << label << "=";
|
|
}
|
|
os << "0x" << Hex32(value);
|
|
// Prefer the function-map name (human symbol) over the registry's
|
|
// func_XXXXXXXX label; when both resolve, the closer floor wins so a map
|
|
// symbol from a preceding function cannot shadow an exact registry hit.
|
|
uint32_t mapStart = 0;
|
|
const char* mapName = GuestMapSymbolFloor(value, &mapStart);
|
|
if (const auto* exact = TranslatedFunctionRegistry::FindByAddressPtr(value)) {
|
|
if (mapName && mapStart >= exact->address) {
|
|
os << "<" << mapName;
|
|
if (value != mapStart) {
|
|
os << "+0x" << std::hex << std::uppercase << (value - mapStart) << std::dec;
|
|
}
|
|
os << ">";
|
|
} else {
|
|
os << "<" << exact->name << ">";
|
|
}
|
|
} else if (auto nearest = FindNearestTranslatedFunction(value)) {
|
|
if (mapName && mapStart >= nearest->address) {
|
|
os << "<" << mapName;
|
|
if (value != mapStart) {
|
|
os << "+0x" << std::hex << std::uppercase << (value - mapStart) << std::dec;
|
|
}
|
|
os << ">";
|
|
} else {
|
|
os << "<" << nearest->name << "+0x" << std::hex << std::uppercase
|
|
<< (value - nearest->address) << std::dec << ">";
|
|
}
|
|
} else if (mapName) {
|
|
os << "<" << mapName << "+0x" << std::hex << std::uppercase
|
|
<< (value - mapStart) << std::dec << ">";
|
|
}
|
|
if (auto text = PrintableU32(value)) {
|
|
os << "('" << *text << "')";
|
|
} else if (auto guestString = GuestCStringPreview(value)) {
|
|
os << "(\"" << *guestString << "\")";
|
|
}
|
|
}
|
|
|
|
void PrintFrameTailWords(std::ostream& os, uint32_t previousSp) {
|
|
os << " tail:";
|
|
for (int32_t offset = -0x40; offset <= 0x04; offset += 4) {
|
|
const uint32_t address = previousSp + static_cast<uint32_t>(offset);
|
|
const auto value = ReadGuest32NoThrow(address);
|
|
if (!value) {
|
|
continue;
|
|
}
|
|
os << " " << (offset < 0 ? "-" : "+")
|
|
<< std::hex << std::uppercase << std::setw(2) << std::setfill('0')
|
|
<< static_cast<uint32_t>(std::abs(offset)) << "=0x" << Hex32(*value);
|
|
}
|
|
os << std::dec << std::setfill(' ');
|
|
}
|
|
|
|
void DumpGuestBackchain(std::ostream& os, const CpuContext* cpu) {
|
|
uint32_t sp = cpu->gpr[1];
|
|
if (sp == 0) {
|
|
return;
|
|
}
|
|
|
|
os << "[runtime] Stack backchain:" << std::endl;
|
|
for (int frame = 0; frame < 12; ++frame) {
|
|
const auto previousSp = ReadGuest32NoThrow(sp);
|
|
if (!previousSp || *previousSp == 0) {
|
|
os << "[runtime] #" << frame << " sp=0x" << Hex32(sp) << " prev=(unmapped)" << std::endl;
|
|
break;
|
|
}
|
|
|
|
const uint32_t frameSize = *previousSp - sp;
|
|
os << "[runtime] #" << frame << " sp=0x" << Hex32(sp)
|
|
<< " prev=0x" << Hex32(*previousSp)
|
|
<< " size=0x" << std::hex << std::uppercase << frameSize << std::dec;
|
|
|
|
if (frameSize <= 0x20000u) {
|
|
if (const auto savedLr = ReadGuest32NoThrow(*previousSp + 4u)) {
|
|
PrintGuestValue(os, "savedLR", *savedLr);
|
|
}
|
|
PrintFrameTailWords(os, *previousSp);
|
|
}
|
|
os << std::endl;
|
|
|
|
if (*previousSp <= sp || frameSize > 0x20000u) {
|
|
break;
|
|
}
|
|
sp = *previousSp;
|
|
}
|
|
}
|
|
|
|
void DumpGuestStackHints(std::ostream& os, const CpuContext* cpu) {
|
|
const uint32_t sp = cpu->gpr[1];
|
|
if (sp == 0) {
|
|
return;
|
|
}
|
|
|
|
os << "[runtime] Stack words:";
|
|
for (uint32_t offset = 0; offset < 0x40; offset += 4) {
|
|
const auto value = ReadGuest32NoThrow(sp + offset);
|
|
if (!value) {
|
|
break;
|
|
}
|
|
os << " +" << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
|
|
<< offset << "=0x" << Hex32(*value);
|
|
}
|
|
os << std::dec << std::setfill(' ') << std::endl;
|
|
|
|
int printed = 0;
|
|
for (uint32_t offset = 0; offset < 0x800 && printed < 32; offset += 4) {
|
|
const auto value = ReadGuest32NoThrow(sp + offset);
|
|
if (!value) {
|
|
continue;
|
|
}
|
|
if (!TranslatedFunctionRegistry::FindByAddressPtr(*value) && !FindNearestTranslatedFunction(*value)) {
|
|
continue;
|
|
}
|
|
if (printed == 0) {
|
|
os << "[runtime] Stack code refs:";
|
|
}
|
|
os << " +" << std::hex << std::uppercase << offset << std::dec << ":";
|
|
PrintGuestValue(os, "", *value);
|
|
++printed;
|
|
}
|
|
if (printed != 0) {
|
|
os << std::endl;
|
|
}
|
|
}
|
|
|
|
void DumpGuestCrashHints(std::ostream& os, const CpuContext* cpu) {
|
|
if (!cpu) {
|
|
return;
|
|
}
|
|
|
|
const uint32_t active = RecompMod::CurrentTranslatedExecutionAddress();
|
|
if (active != 0) {
|
|
os << "[runtime] Active translated target: ";
|
|
PrintGuestValue(os, "guest", active);
|
|
os << std::endl;
|
|
}
|
|
|
|
os << "[runtime] Guest refs:";
|
|
PrintGuestValue(os, "pc", cpu->pc);
|
|
PrintGuestValue(os, "lr", cpu->lr);
|
|
PrintGuestValue(os, "ctr", cpu->ctr);
|
|
PrintGuestValue(os, "r12", cpu->gpr[12]);
|
|
PrintGuestValue(os, "r3", cpu->gpr[3]);
|
|
PrintGuestValue(os, "r4", cpu->gpr[4]);
|
|
PrintGuestValue(os, "r5", cpu->gpr[5]);
|
|
PrintGuestValue(os, "r15", cpu->gpr[15]);
|
|
PrintGuestValue(os, "r16", cpu->gpr[16]);
|
|
PrintGuestValue(os, "r18", cpu->gpr[18]);
|
|
PrintGuestValue(os, "r26", cpu->gpr[26]);
|
|
PrintGuestValue(os, "r27", cpu->gpr[27]);
|
|
os << std::endl;
|
|
|
|
if (active != 0 && active != cpu->pc) {
|
|
os << "[runtime] Note: active translated target differs from ctx->pc; ctx->pc may be stale "
|
|
"without PC tracing."
|
|
<< std::endl;
|
|
}
|
|
|
|
DumpGuestStackHints(os, cpu);
|
|
DumpGuestBackchain(os, cpu);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void SystemBridge::Initialize() {
|
|
const auto memoryConfig = Memory::Config::WiiDefaults();
|
|
Memory::Init(memoryConfig);
|
|
|
|
SeedLowMemDefaults(memoryConfig);
|
|
|
|
// Initialize from the embedded DOL/REL data sections. This provides the
|
|
// initial memory state for .data, .rodata, .sdata, etc.
|
|
RT_LOG(RT_TAG_RUNTIME) << "Initializing data sections from embedded DOL/REL data" << std::endl;
|
|
InitializeDataSections();
|
|
|
|
// Constructors run before the main entry point, but they still require the
|
|
// PowerPC ABI environment. Keep using the persistent context here: the old
|
|
// AbiCallContext path implicitly selected and seeded it for every ctor.
|
|
InitializePersistentCpuContext();
|
|
CpuContext& cpu = GetPersistentCpuContext();
|
|
cpu.gpr[1] = 0x81700000u;
|
|
CpuContextScope scope(&cpu);
|
|
|
|
g_suppressSehReporting = true;
|
|
|
|
// Run main DOL static constructors first
|
|
// These set up vtables and other critical infrastructure
|
|
RT_LOG(RT_TAG_RUNTIME) << "Running static constructors for main DOL..." << std::endl;
|
|
|
|
const uint32_t dolCtorStart = 0x80244DE0;
|
|
const uint32_t dolCtorEnd = 0x80244EA0;
|
|
|
|
int dolCount = 0;
|
|
for (uint32_t addr = dolCtorStart; addr < dolCtorEnd; addr += 4) {
|
|
uint32_t funcAddr = 0;
|
|
try {
|
|
funcAddr = Memory::Read32(addr);
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
|
|
if (funcAddr == 0 || funcAddr == 0xFFFFFFFF) continue;
|
|
|
|
if (TranslatedFunctionRegistry::FindByAddressPtr(funcAddr)) {
|
|
MkwJmpBuf jumpBuf;
|
|
g_sehJumpTarget = &jumpBuf;
|
|
if (MKW_SETJMP(jumpBuf) == 0) {
|
|
cpu.gpr[1] = 0x81700000u;
|
|
InvokeIndirectCpu(funcAddr, &cpu);
|
|
dolCount++;
|
|
} else {
|
|
RT_LOG(RT_TAG_RUNTIME) << "SEH Exception caught invoking DOL ctor 0x" << std::hex << funcAddr
|
|
<< " code=0x" << g_sehLastExceptionCode
|
|
<< " at=0x" << g_sehLastExceptionAddress;
|
|
if (g_sehLastExceptionCode == 0xC0000005u && g_sehLastAccessedAddress != 0) {
|
|
std::cerr << " " << (g_sehLastAccessType ? "write" : "read")
|
|
<< "=0x" << g_sehLastAccessedAddress;
|
|
}
|
|
std::cerr << std::dec << " - skipping." << std::endl;
|
|
}
|
|
g_sehJumpTarget = nullptr;
|
|
}
|
|
}
|
|
RT_LOG(RT_TAG_RUNTIME) << "Executed " << dolCount << " main DOL static constructors." << std::endl;
|
|
|
|
// StaticR.rel ctors must be run manually since we aren't using OSLink
|
|
RT_LOG(RT_TAG_RUNTIME) << "Running static constructors for StaticR.rel..." << std::endl;
|
|
|
|
// Define range for StaticR.rel .ctors
|
|
const uint32_t ctorStart = 0x8088f400;
|
|
const uint32_t ctorEnd = 0x8088f704;
|
|
|
|
int count = 0;
|
|
for (uint32_t addr = ctorStart; addr < ctorEnd; addr += 4) {
|
|
uint32_t funcAddr = 0;
|
|
try {
|
|
funcAddr = Memory::Read32(addr);
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
|
|
if (funcAddr == 0 || funcAddr == 0xFFFFFFFF) continue;
|
|
|
|
if (TranslatedFunctionRegistry::FindByAddressPtr(funcAddr)) {
|
|
MkwJmpBuf jumpBuf;
|
|
g_sehJumpTarget = &jumpBuf;
|
|
if (MKW_SETJMP(jumpBuf) == 0) {
|
|
cpu.gpr[1] = 0x81700000u;
|
|
InvokeIndirectCpu(funcAddr, &cpu);
|
|
count++;
|
|
} else {
|
|
RT_LOG(RT_TAG_RUNTIME) << "SEH Exception caught invoking ctor 0x" << std::hex << funcAddr
|
|
<< " code=0x" << g_sehLastExceptionCode
|
|
<< " at=0x" << g_sehLastExceptionAddress;
|
|
if (g_sehLastExceptionCode == 0xC0000005u && g_sehLastAccessedAddress != 0) {
|
|
std::cerr << " " << (g_sehLastAccessType ? "write" : "read")
|
|
<< "=0x" << g_sehLastAccessedAddress;
|
|
}
|
|
std::cerr << std::dec << " - skipping." << std::endl;
|
|
}
|
|
g_sehJumpTarget = nullptr;
|
|
}
|
|
}
|
|
g_suppressSehReporting = false;
|
|
RT_LOG(RT_TAG_RUNTIME) << "Executed " << count << " static constructors." << std::endl;
|
|
}
|
|
|
|
void SystemBridge::WriteGuestMemorySnapshot(std::ostream& os, const std::filesystem::path& mem1Path) {
|
|
constexpr uint32_t kMem1Base = 0x80000000u;
|
|
constexpr uint32_t kMem1Size = 0x01800000u;
|
|
if (Memory::Contains(kMem1Base, kMem1Size)) {
|
|
std::ofstream dump(mem1Path, std::ios::binary | std::ios::trunc);
|
|
dump.write(reinterpret_cast<const char*>(Memory::GetPointer(kMem1Base, kMem1Size)),
|
|
kMem1Size);
|
|
os << "[runtime] MEM1 snapshot written to " << RuntimeConfigFile::PathToUtf8(mem1Path)
|
|
<< (dump.good() ? "" : " (write failed)") << std::endl;
|
|
}
|
|
constexpr uint32_t kMem2Base = 0x90000000u;
|
|
for (uint32_t mem2Size : {0x08000000u, 0x04000000u}) {
|
|
if (!Memory::Contains(kMem2Base, mem2Size)) {
|
|
continue;
|
|
}
|
|
std::filesystem::path mem2Path = mem1Path;
|
|
mem2Path += ".mem2";
|
|
std::ofstream dump(mem2Path, std::ios::binary | std::ios::trunc);
|
|
dump.write(reinterpret_cast<const char*>(Memory::GetPointer(kMem2Base, mem2Size)),
|
|
mem2Size);
|
|
os << "[runtime] MEM2 snapshot written to " << RuntimeConfigFile::PathToUtf8(mem2Path)
|
|
<< (dump.good() ? "" : " (write failed)") << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SystemBridge::DumpCrashHeuristics(std::ostream& os, const CpuContext* cpu,
|
|
const uint32_t* missingGuestTarget) {
|
|
if (!cpu) {
|
|
return;
|
|
}
|
|
|
|
if (missingGuestTarget && *missingGuestTarget == 0) {
|
|
os << "[runtime] The guest jumped to address 0 - almost always a virtual call "
|
|
"through a bad object, or a callback pointer that was never set."
|
|
<< std::endl;
|
|
if (cpu->gpr[3] == 0) {
|
|
os << "[runtime] r3 (the C++ 'this' pointer) is NULL at the call. A very common "
|
|
"cause is an unchecked guest heap allocation failure: every "
|
|
"\"heap (...)->alloc(size(...))\" OSReport line above is a FAILED "
|
|
"allocation (the number after 'free' is the bytes actually left in "
|
|
"that heap). If those lines are present, the heap was exhausted "
|
|
"before this crash." << std::endl;
|
|
}
|
|
} else if (missingGuestTarget) {
|
|
os << "[runtime] The guest jumped to 0x" << std::hex << std::uppercase
|
|
<< *missingGuestTarget << std::dec
|
|
<< ", which is not a translated function entry. If the value looks like "
|
|
"readable text or a data pointer, a function pointer or vtable was "
|
|
"overwritten (guest memory stomp)." << std::endl;
|
|
}
|
|
}
|
|
|
|
// MEM2 space reserved at startup for the runtime FST.
|
|
extern "C" uint32_t g_dvdFstReservedBase = 0;
|
|
extern "C" uint32_t g_dvdFstReservedSize = 0;
|
|
|
|
void SystemBridge::SeedLowMemDefaults(const Memory::Config& config) {
|
|
|
|
const auto* mem1Region = FindRegionConfig(config, "MEM1");
|
|
const auto* mem2Region = FindRegionConfig(config, "MEM2");
|
|
const uint32_t mem1Size = mem1Region ? static_cast<uint32_t>(mem1Region->sizeBytes) : 0;
|
|
const uint32_t mem2Size = mem2Region ? static_cast<uint32_t>(mem2Region->sizeBytes) : 0;
|
|
|
|
struct SeedEntry {
|
|
uint32_t address;
|
|
uint32_t value;
|
|
const char* label;
|
|
bool force = false;
|
|
};
|
|
|
|
std::vector<SeedEntry> entries;
|
|
constexpr uint32_t kMem1ArenaLoDefault = 0x80399180u;
|
|
uint32_t mem1ArenaHiDefault = 0x817f0520u;
|
|
constexpr uint32_t kBusClockHz =
|
|
static_cast<uint32_t>(TimeBaseContract::kBusClockHz);
|
|
constexpr uint32_t kCpuClockHz = kBusClockHz * 3u;
|
|
// Mario Kart Wii boots under IOS36. These are the IOS kernel values that
|
|
// Dolphin exposes in low memory for IOS36 (VersionInfo.cpp). SDK helpers
|
|
// read them through the 0xC0000000 MEM1 alias.
|
|
constexpr uint32_t kIos36Version = 0x00240E18u;
|
|
constexpr uint32_t kIos36Date = 0x00030110u;
|
|
|
|
constexpr uint32_t kMem2LoFloor = 0x90000800u;
|
|
// Reserve enough guest memory for the runtime-built DVD FST.
|
|
constexpr uint32_t kDvdFstReserveSize = 0x200000u;
|
|
// IOS36 reserves 128 KiB for IPC immediately below a 128 KiB IOS-owned
|
|
// block. Dolphin's RAM override preserves both sizes and moves them to the
|
|
// top of the expanded MEM2 mapping.
|
|
constexpr uint32_t kIPCArenaSize = 0x20000u;
|
|
constexpr uint32_t kIosReservedSize = 0x20000u;
|
|
|
|
// The boot code exposes the current disc ID in low memory before DVDInit.
|
|
// Retro Rewind reads the region byte directly from here while building its
|
|
// Retro-WFC payload URL, and OSGetAppGamename reads the app code mirrors
|
|
// at 0x80003180/0x80003194 while building NAS auth fields.
|
|
entries.push_back({0x80000000u, 0x524D4350u, "Disc game code"}); // RMCP
|
|
entries.push_back({0x80000004u, 0x30310100u, "Disc maker/id"}); // 01 + disc 1
|
|
entries.push_back({0x80003180u, 0x524D4350u, "OS app game code"}); // RMCP
|
|
entries.push_back({0x80003194u, 0x524D4350u, "OS app gamename"}); // RMCP
|
|
if (RuntimeProduct::IsRetroRewind()) {
|
|
entries.push_back({0x800017D8u, 0x00000001u, "Retro Rewind recomp runtime marker", true});
|
|
}
|
|
|
|
for (const auto& reservation : RecompMod::MemoryReservations()) {
|
|
if (reservation.start >= kMem1ArenaLoDefault &&
|
|
reservation.start < mem1ArenaHiDefault &&
|
|
reservation.end <= 0x81800000u) {
|
|
mem1ArenaHiDefault = reservation.start;
|
|
RT_LOG(RT_TAG_MOD) << "Reserved MEM1 range 0x" << std::hex << std::uppercase
|
|
<< reservation.start << "-0x" << reservation.end
|
|
<< " for " << reservation.name
|
|
<< "; MEM1 arena hi now 0x" << mem1ArenaHiDefault
|
|
<< std::dec << std::nouppercase << std::endl;
|
|
}
|
|
}
|
|
|
|
// Seed OS clock values used by SDK timing helpers (OSBusClock/OSClock).
|
|
entries.push_back({0x800000F8u, kBusClockHz, "__OSBusClock"});
|
|
entries.push_back({0x800000FCu, kCpuClockHz, "__OSClock"});
|
|
entries.push_back({0x80003140u, kIos36Version, "IOS36 version"});
|
|
entries.push_back({0x80003144u, kIos36Date, "IOS36 date"});
|
|
|
|
if (mem1Size != 0) {
|
|
const uint32_t mem1End = Memory::kMem1CachedBase + mem1Size;
|
|
entries.push_back({0x80000028u, mem1Size, "Legacy MEM1 size", true});
|
|
entries.push_back({0x80000030u, kMem1ArenaLoDefault, "Legacy MEM1 arena lo", true});
|
|
entries.push_back({0x80000034u, mem1ArenaHiDefault, "Legacy MEM1 arena hi", true});
|
|
entries.push_back({0x800000F0u, mem1Size, "Legacy simulated MEM1 size", true});
|
|
entries.push_back({0x80003100u, mem1Size, "Physical MEM1 size", true});
|
|
entries.push_back({0x80003104u, mem1Size, "Simulated MEM1 size", true});
|
|
entries.push_back({0x80003108u, mem1End, "MEM1 end", true});
|
|
entries.push_back({0x8000310Cu, kMem1ArenaLoDefault, "MEM1 arena lo", true});
|
|
entries.push_back({0x80003110u, mem1ArenaHiDefault, "MEM1 arena hi", true});
|
|
}
|
|
|
|
if (mem2Size != 0) {
|
|
const uint32_t physicalMem2End = Memory::kMem2CachedBase + mem2Size;
|
|
const uint32_t iosReservedLo = physicalMem2End - kIosReservedSize;
|
|
const uint32_t mem2End = iosReservedLo;
|
|
const uint32_t ipcBufHi = mem2End;
|
|
const uint32_t ipcBufLo = ipcBufHi - kIPCArenaSize;
|
|
// Reserve FST memory before guest code can use this part of the arena.
|
|
g_dvdFstReservedSize = kDvdFstReserveSize;
|
|
g_dvdFstReservedBase = ipcBufLo - kDvdFstReserveSize;
|
|
const uint32_t mem2ArenaHi = g_dvdFstReservedBase;
|
|
|
|
entries.push_back({0x80003118u, mem2Size, "Physical MEM2 size", true});
|
|
entries.push_back({0x8000311Cu, mem2Size, "Simulated MEM2 size", true});
|
|
entries.push_back({0x80003120u, mem2End, "MEM2 end", true});
|
|
entries.push_back({0x80003124u, kMem2LoFloor, "MEM2 arena lo", true});
|
|
entries.push_back({0x80003128u, mem2ArenaHi, "MEM2 arena hi", true});
|
|
entries.push_back({0x80003130u, ipcBufLo, "IPC Buffer lo", true});
|
|
entries.push_back({0x80003134u, ipcBufHi, "IPC Buffer hi", true});
|
|
entries.push_back({0x80003138u, 0x00000002u, "Hollywood revision", true});
|
|
entries.push_back({0x80003148u, iosReservedLo, "IOS reserved lo", true});
|
|
entries.push_back({0x8000314Cu, physicalMem2End, "IOS reserved hi", true});
|
|
}
|
|
|
|
bool patchedAny = false;
|
|
size_t seededCount = 0;
|
|
for (const auto& entry : entries) {
|
|
if (entry.value == 0) continue;
|
|
|
|
// Only patch if the address is mapped
|
|
if (!Memory::Contains(entry.address, sizeof(uint32_t))) continue;
|
|
|
|
// Only patch if currently zero (don't overwrite if OS already initialized)
|
|
if (!entry.force && Memory::Read32(entry.address) != 0) continue;
|
|
|
|
Memory::Write32(entry.address, entry.value);
|
|
patchedAny = true;
|
|
++seededCount;
|
|
}
|
|
|
|
if (seededCount != 0) {
|
|
RT_LOG(RT_TAG_RUNTIME) << "Seeded " << seededCount << " low-memory default(s)" << std::endl;
|
|
}
|
|
|
|
if (patchedAny && mem2Size != 0) {
|
|
const uint32_t patchedLo = Memory::Read32(0x80003124u);
|
|
const uint32_t patchedHi = Memory::Read32(0x80003128u);
|
|
const uint32_t patchedSize = Memory::Read32(0x8000311Cu);
|
|
RT_LOG(RT_TAG_RUNTIME) << "MEM2 defaults active -> size=" << (patchedSize / (1024 * 1024))
|
|
<< " MB, arena 0x" << std::hex << std::uppercase << patchedLo
|
|
<< " - 0x" << patchedHi << std::dec << std::nouppercase << std::endl;
|
|
}
|
|
}
|
|
|
|
const Memory::RegionConfig* SystemBridge::FindRegionConfig(const Memory::Config& config, std::string_view name) {
|
|
for (const auto& region : config.regions) {
|
|
if (region.name == name) {
|
|
return ®ion;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void SystemBridge::DumpCpuState(const CpuContext* cpu) {
|
|
DumpCpuState(std::cerr, cpu);
|
|
}
|
|
|
|
void SystemBridge::DumpCpuState(std::ostream& os, const CpuContext* cpu) {
|
|
if (!cpu) {
|
|
os << "[runtime] CPU context unavailable; registers could not be dumped." << std::endl;
|
|
return;
|
|
}
|
|
|
|
DumpGuestCrashHints(os, cpu);
|
|
|
|
os << "[runtime] PC=0x" << Hex32(cpu->pc)
|
|
<< " LR=0x" << Hex32(cpu->lr)
|
|
<< " CTR=0x" << Hex32(cpu->ctr)
|
|
<< " CR=0x" << Hex32(cpu->cr)
|
|
<< " XER=0x" << Hex32(cpu->xer) << std::endl;
|
|
os << "[runtime] SRR0=0x" << Hex32(cpu->srr0)
|
|
<< " SRR1=0x" << Hex32(cpu->srr1) << std::endl;
|
|
os << "[runtime] SP(r1)=0x" << Hex32(cpu->gpr[1])
|
|
<< " R2=0x" << Hex32(cpu->gpr[2])
|
|
<< " R13=0x" << Hex32(cpu->gpr[13]) << std::endl;
|
|
|
|
for (int base = 0; base < 32; base += 8) {
|
|
os << "[runtime] GPR" << std::setw(2) << std::setfill('0') << base
|
|
<< "-" << std::setw(2) << std::setfill('0') << (base + 7) << ": ";
|
|
for (int i = 0; i < 8; ++i) {
|
|
const int reg = base + i;
|
|
os << "r" << std::setw(2) << std::setfill('0') << reg << "=0x"
|
|
<< Hex32(cpu->gpr[reg]) << (i == 7 ? "" : " ");
|
|
}
|
|
os << std::setfill(' ') << std::endl;
|
|
}
|
|
os << std::setfill(' ');
|
|
}
|