mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-06 06:05:40 +12:00
1394 lines
No EOL
43 KiB
C++
1394 lines
No EOL
43 KiB
C++
#include "services/ldr_ro.hpp"
|
|
#include "ipc.hpp"
|
|
#include "kernel.hpp"
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
namespace LDRCommands {
|
|
enum : u32 {
|
|
Initialize = 0x000100C2,
|
|
LoadCRR = 0x00020082,
|
|
LoadCRO = 0x000402C2,
|
|
UnloadCRO = 0x000500C2,
|
|
LinkCRO = 0x00060042,
|
|
LoadCRONew = 0x000902C2,
|
|
};
|
|
}
|
|
|
|
namespace CROHeader {
|
|
enum : u32 {
|
|
ID = 0x080,
|
|
NameOffset = 0x084,
|
|
NextCRO = 0x088,
|
|
PrevCRO = 0x08C,
|
|
FixedSize = 0x98,
|
|
OnUnresolved = 0x0AC,
|
|
CodeOffset = 0x0B0,
|
|
DataOffset = 0x0B8,
|
|
ModuleNameOffset = 0x0C0,
|
|
SegmentTableOffset = 0x0C8,
|
|
SegmentTableSize = 0x0CC,
|
|
NamedExportTableOffset = 0x0D0,
|
|
NamedExportTableSize = 0x0D4,
|
|
IndexedExportTableOffset = 0x0D8,
|
|
IndexedExportTableSize = 0x0DC,
|
|
ExportStringTableOffset = 0x0E0,
|
|
ExportStringSize = 0x0E4,
|
|
ExportTreeOffset = 0x0E8,
|
|
ImportModuleTableOffset = 0x0F0,
|
|
ImportModuleTableSize = 0x0F4,
|
|
ImportPatchTableOffset = 0x0F8,
|
|
ImportPatchTableSize = 0x0FC,
|
|
NamedImportTableOffset = 0x100,
|
|
NamedImportTableSize = 0x104,
|
|
IndexedImportTableOffset = 0x108,
|
|
IndexedImportTableSize = 0x10C,
|
|
AnonymousImportTableOffset = 0x110,
|
|
AnonymousImportTableSize = 0x114,
|
|
ImportStringTableOffset = 0x118,
|
|
ImportStringSize = 0x11C,
|
|
StaticAnonymousSymbolTableOffset = 0x120,
|
|
StaticAnonymousSymbolTableSize = 0x124,
|
|
RelocationPatchTableOffset = 0x128,
|
|
RelocationPatchTableSize = 0x12C,
|
|
StaticAnonymousPatchTableOffset = 0x130,
|
|
StaticAnonymousPatchTableSize = 0x134,
|
|
};
|
|
}
|
|
|
|
namespace SegmentTable {
|
|
enum : u32 {
|
|
Offset = 0,
|
|
Size = 4,
|
|
ID = 8,
|
|
};
|
|
|
|
namespace SegmentID {
|
|
enum : u32 {
|
|
TEXT, RODATA, DATA, BSS,
|
|
};
|
|
}
|
|
}
|
|
|
|
namespace NamedExportTable {
|
|
enum : u32 {
|
|
NameOffset = 0,
|
|
SegmentOffset = 4,
|
|
};
|
|
};
|
|
|
|
namespace IndexedExportTable {
|
|
enum : u32 {
|
|
SegmentOffset = 0,
|
|
};
|
|
};
|
|
|
|
namespace NamedImportTable {
|
|
enum : u32 {
|
|
NameOffset = 0,
|
|
RelocationOffset = 4,
|
|
};
|
|
};
|
|
|
|
namespace IndexedImportTable {
|
|
enum : u32 {
|
|
Index = 0,
|
|
RelocationOffset = 4,
|
|
};
|
|
};
|
|
|
|
namespace AnonymousImportTable {
|
|
enum : u32 {
|
|
SegmentOffset = 0,
|
|
RelocationOffset = 4,
|
|
};
|
|
};
|
|
|
|
namespace ImportModuleTable {
|
|
enum : u32 {
|
|
NameOffset = 0,
|
|
IndexedOffset = 4,
|
|
IndexedNum = 8,
|
|
AnonymousOffset = 12,
|
|
AnonymousNum = 16,
|
|
};
|
|
};
|
|
|
|
namespace RelocationPatch {
|
|
enum : u32 {
|
|
SegmentOffset = 0,
|
|
PatchType = 4,
|
|
IsLastEntry = 5, // For import patches
|
|
SegmentIndex = 5, // For relocation patches
|
|
IsResolved = 6,
|
|
Addend = 8,
|
|
};
|
|
|
|
namespace RelocationPatchType {
|
|
enum : u32 {
|
|
AbsoluteAddress = 2,
|
|
};
|
|
};
|
|
};
|
|
|
|
struct CROHeaderEntry {
|
|
u32 offset, size;
|
|
};
|
|
|
|
static constexpr u32 CRO_HEADER_SIZE = 0x138;
|
|
|
|
static const std::string CRO_MAGIC("CRO0");
|
|
static const std::string CRO_MAGIC_FIXED("FIXD");
|
|
static const std::string CRR_MAGIC("CRR0");
|
|
|
|
class CRO {
|
|
Memory &mem;
|
|
|
|
u32 croPointer; // Origin address of CRO in RAM
|
|
u32 oldDataSegmentOffset;
|
|
|
|
bool isCRO; // False if CRS
|
|
|
|
public:
|
|
CRO(Memory &mem, u32 croPointer, bool isCRO) : mem(mem), croPointer(croPointer), oldDataSegmentOffset(0), isCRO(isCRO) {}
|
|
~CRO() = default;
|
|
|
|
std::string getModuleName() {
|
|
const CROHeaderEntry moduleName = getHeaderEntry(CROHeader::ModuleNameOffset);
|
|
|
|
return mem.readString(moduleName.offset, moduleName.size);
|
|
}
|
|
|
|
u32 getNextCRO() {
|
|
return mem.read32(croPointer + CROHeader::NextCRO);
|
|
}
|
|
|
|
u32 getPrevCRO() {
|
|
return mem.read32(croPointer + CROHeader::PrevCRO);
|
|
}
|
|
|
|
u32 getFixedSize() {
|
|
return mem.read32(croPointer + CROHeader::FixedSize);
|
|
}
|
|
|
|
void setNextCRO(u32 nextCRO) {
|
|
mem.write32(croPointer + CROHeader::NextCRO, nextCRO);
|
|
}
|
|
|
|
void setPrevCRO(u32 prevCRO) {
|
|
mem.write32(croPointer + CROHeader::PrevCRO, prevCRO);
|
|
}
|
|
|
|
void write32(u32 addr, u32 value) {
|
|
// Note: some games export symbols to the static module, which doesn't contain any segments.
|
|
// Instead, its segments point to ROM segments. We need this special write handler for writes to .text, which
|
|
// can't be accessed via mem.write32()
|
|
auto writePointer = mem.getWritePointer(addr);
|
|
if (writePointer) {
|
|
*(u32*)writePointer = value;
|
|
} else {
|
|
auto readPointer = mem.getReadPointer(addr);
|
|
if (readPointer) {
|
|
*(u32*)readPointer = value;
|
|
} else {
|
|
Helpers::panic("LDR_RO write to invalid address = %X\n", addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns CRO header offset-size pair
|
|
CROHeaderEntry getHeaderEntry(u32 entry) {
|
|
return CROHeaderEntry{.offset = mem.read32(croPointer + entry), .size = mem.read32(croPointer + entry + 4)};
|
|
}
|
|
|
|
u32 getSegmentAddr(u32 segmentOffset) {
|
|
// "Decoded" segment tag
|
|
const u32 segmentIndex = segmentOffset & 0xF;
|
|
const u32 offset = segmentOffset >> 4;
|
|
|
|
const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset);
|
|
|
|
if (segmentIndex >= segmentTable.size) {
|
|
return 0;
|
|
}
|
|
|
|
// Get segment table entry
|
|
const u32 entryOffset = mem.read32(segmentTable.offset + 12 * segmentIndex + SegmentTable::Offset);
|
|
const u32 entrySize = mem.read32(segmentTable.offset + 12 * segmentIndex + SegmentTable::Size);
|
|
|
|
if (offset >= entrySize) {
|
|
return 0;
|
|
}
|
|
|
|
return entryOffset + offset;
|
|
}
|
|
|
|
u32 getOnUnresolvedAddr() {
|
|
return getSegmentAddr(mem.read32(croPointer + CROHeader::OnUnresolved));
|
|
}
|
|
|
|
u32 getNamedExportSymbolAddr(const std::string& symbolName) {
|
|
// Note: The CRO contains a trie for fast symbol lookup. For simplicity,
|
|
// we won't use it and instead look up the symbol in the named export symbol table
|
|
|
|
const u32 exportStringSize = mem.read32(croPointer + CROHeader::ExportStringSize);
|
|
|
|
const CROHeaderEntry namedExportTable = getHeaderEntry(CROHeader::NamedExportTableOffset);
|
|
|
|
for (u32 namedExport = 0; namedExport < namedExportTable.size; namedExport++) {
|
|
const u32 nameOffset = mem.read32(namedExportTable.offset + 8 * namedExport + NamedExportTable::NameOffset);
|
|
|
|
const std::string exportSymbolName = mem.readString(nameOffset, exportStringSize);
|
|
|
|
if (symbolName.compare(exportSymbolName) == 0) {
|
|
return getSegmentAddr(mem.read32(namedExportTable.offset + 8 * namedExport + NamedExportTable::SegmentOffset));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Patches one symbol
|
|
bool patchSymbol(u32 relocationTarget, u8 patchType, u32 addend, u32 symbolOffset) {
|
|
switch (patchType) {
|
|
case RelocationPatch::RelocationPatchType::AbsoluteAddress: write32(relocationTarget, symbolOffset + addend); break;
|
|
default: Helpers::panic("Unhandled relocation type = %X\n", patchType);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Patches symbol batches
|
|
bool patchBatch(u32 batchAddr, u32 symbolAddr, bool makeUnresolved = false) {
|
|
u32 relocationPatch = batchAddr;
|
|
|
|
while (true) {
|
|
const u32 segmentOffset = mem.read32(relocationPatch + RelocationPatch::SegmentOffset);
|
|
const u8 patchType = mem.read8(relocationPatch + RelocationPatch::PatchType);
|
|
const u8 isLastBatch = mem.read8(relocationPatch + RelocationPatch::IsLastEntry);
|
|
const u32 addend = mem.read32(relocationPatch + RelocationPatch::Addend);
|
|
|
|
const u32 relocationTarget = getSegmentAddr(segmentOffset);
|
|
|
|
if (relocationTarget == 0) {
|
|
Helpers::panic("Relocation target is NULL");
|
|
}
|
|
|
|
if (makeUnresolved) {
|
|
write32(relocationTarget, symbolAddr);
|
|
} else {
|
|
patchSymbol(relocationTarget, patchType, addend, symbolAddr);
|
|
}
|
|
|
|
if (isLastBatch != 0) {
|
|
break;
|
|
}
|
|
|
|
relocationPatch += 12;
|
|
}
|
|
|
|
if (makeUnresolved) {
|
|
mem.write8(relocationPatch + RelocationPatch::IsResolved, 0);
|
|
} else {
|
|
mem.write8(relocationPatch + RelocationPatch::IsResolved, 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool load() {
|
|
// TODO: verify SHA hashes?
|
|
|
|
// Verify CRO magic
|
|
const std::string magic = mem.readString(croPointer + CROHeader::ID, 4);
|
|
if (magic.compare(CRO_MAGIC) != 0) {
|
|
return false;
|
|
}
|
|
|
|
// These fields are initially 0, the RO service sets them on load. If non-0,
|
|
// this CRO has already been loaded
|
|
if ((getNextCRO() != 0) || (getPrevCRO() != 0)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool fix(u32 fixLevel) {
|
|
if (fixLevel != 0) {
|
|
mem.write8(croPointer + CROHeader::ID + 0, 'F');
|
|
mem.write8(croPointer + CROHeader::ID + 1, 'I');
|
|
mem.write8(croPointer + CROHeader::ID + 2, 'X');
|
|
mem.write8(croPointer + CROHeader::ID + 3, 'D');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Modifies CRO offsets to point at virtual addresses
|
|
bool rebase(u32 loadedCRS, u32 dataVaddr, u32 bssVaddr) {
|
|
rebaseHeader();
|
|
|
|
u32 oldDataVaddr = 0;
|
|
|
|
// Note: Citra rebases the segment table only if the file is not a CRS.
|
|
// Presumably because CRS files don't contain segments?
|
|
if (isCRO) {
|
|
rebaseSegmentTable(dataVaddr, bssVaddr, &oldDataVaddr);
|
|
}
|
|
|
|
rebaseNamedExportTable();
|
|
rebaseImportModuleTable();
|
|
rebaseNamedImportTable();
|
|
rebaseIndexedImportTable();
|
|
rebaseAnonymousImportTable();
|
|
|
|
// Note: Citra relocates static anonymous symbols and exit symbols only if the file is not a CRS
|
|
if (isCRO) {
|
|
relocateStaticAnonymousSymbols();
|
|
relocateInternalSymbols(oldDataVaddr);
|
|
relocateExitSymbols(loadedCRS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unrebase() {
|
|
unrebaseAnonymousImportTable();
|
|
unrebaseIndexedImportTable();
|
|
unrebaseNamedImportTable();
|
|
unrebaseImportModuleTable();
|
|
unrebaseNamedExportTable();
|
|
|
|
// Note: Citra unrebases the segment table only if the file is not a CRS.
|
|
// Presumably because CRS files don't contain segments?
|
|
if (isCRO) {
|
|
unrebaseSegmentTable();
|
|
}
|
|
|
|
unrebaseHeader();
|
|
|
|
setNextCRO(0);
|
|
setPrevCRO(0);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rebaseHeader() {
|
|
constexpr u32 headerOffsets[] = {
|
|
CROHeader::NameOffset,
|
|
CROHeader::CodeOffset,
|
|
CROHeader::DataOffset,
|
|
CROHeader::ModuleNameOffset,
|
|
CROHeader::SegmentTableOffset,
|
|
CROHeader::NamedExportTableOffset,
|
|
CROHeader::IndexedExportTableOffset,
|
|
CROHeader::ExportStringTableOffset,
|
|
CROHeader::ExportTreeOffset,
|
|
CROHeader::ImportModuleTableOffset,
|
|
CROHeader::ImportPatchTableOffset,
|
|
CROHeader::NamedImportTableOffset,
|
|
CROHeader::IndexedImportTableOffset,
|
|
CROHeader::AnonymousImportTableOffset,
|
|
CROHeader::ImportStringTableOffset,
|
|
CROHeader::StaticAnonymousSymbolTableOffset,
|
|
CROHeader::RelocationPatchTableOffset,
|
|
CROHeader::StaticAnonymousPatchTableOffset,
|
|
};
|
|
|
|
for (u32 offset : headerOffsets) {
|
|
mem.write32(croPointer + offset, mem.read32(croPointer + offset) + croPointer);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unrebaseHeader() {
|
|
constexpr u32 headerOffsets[] = {
|
|
CROHeader::NameOffset,
|
|
CROHeader::CodeOffset,
|
|
CROHeader::DataOffset,
|
|
CROHeader::ModuleNameOffset,
|
|
CROHeader::SegmentTableOffset,
|
|
CROHeader::NamedExportTableOffset,
|
|
CROHeader::IndexedExportTableOffset,
|
|
CROHeader::ExportStringTableOffset,
|
|
CROHeader::ExportTreeOffset,
|
|
CROHeader::ImportModuleTableOffset,
|
|
CROHeader::ImportPatchTableOffset,
|
|
CROHeader::NamedImportTableOffset,
|
|
CROHeader::IndexedImportTableOffset,
|
|
CROHeader::AnonymousImportTableOffset,
|
|
CROHeader::ImportStringTableOffset,
|
|
CROHeader::StaticAnonymousSymbolTableOffset,
|
|
CROHeader::RelocationPatchTableOffset,
|
|
CROHeader::StaticAnonymousPatchTableOffset,
|
|
};
|
|
|
|
for (u32 offset : headerOffsets) {
|
|
mem.write32(croPointer + offset, mem.read32(croPointer + offset) - croPointer);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rebaseSegmentTable(u32 dataVaddr, u32 bssVaddr, u32 *oldDataVaddr) {
|
|
const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset);
|
|
|
|
for (u32 segment = 0; segment < segmentTable.size; segment++) {
|
|
u32 segmentOffset = mem.read32(segmentTable.offset + 12 * segment + SegmentTable::Offset);
|
|
|
|
const u32 segmentID = mem.read32(segmentTable.offset + 12 * segment + SegmentTable::ID);
|
|
switch (segmentID) {
|
|
case SegmentTable::SegmentID::DATA:
|
|
*oldDataVaddr = segmentOffset + croPointer; oldDataSegmentOffset = segmentOffset; segmentOffset = dataVaddr; break;
|
|
case SegmentTable::SegmentID::BSS: segmentOffset = bssVaddr; break;
|
|
case SegmentTable::SegmentID::TEXT:
|
|
case SegmentTable::SegmentID::RODATA:
|
|
if (segmentOffset != 0) segmentOffset += croPointer; break;
|
|
default:
|
|
Helpers::panic("Unknown segment ID = %u", segmentID);
|
|
}
|
|
|
|
mem.write32(segmentTable.offset + 12 * segment + SegmentTable::Offset, segmentOffset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unrebaseSegmentTable() {
|
|
const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset);
|
|
|
|
for (u32 segment = 0; segment < segmentTable.size; segment++) {
|
|
u32 segmentOffset = mem.read32(segmentTable.offset + 12 * segment + SegmentTable::Offset);
|
|
|
|
const u32 segmentID = mem.read32(segmentTable.offset + 12 * segment + SegmentTable::ID);
|
|
switch (segmentID) {
|
|
case SegmentTable::SegmentID::DATA: segmentOffset = oldDataSegmentOffset; break;
|
|
case SegmentTable::SegmentID::BSS: segmentOffset = 0; break;
|
|
case SegmentTable::SegmentID::TEXT:
|
|
case SegmentTable::SegmentID::RODATA:
|
|
if (segmentOffset != 0) segmentOffset -= croPointer; break;
|
|
default:
|
|
Helpers::panic("Unknown segment ID = %u", segmentID);
|
|
}
|
|
|
|
mem.write32(segmentTable.offset + 12 * segment + SegmentTable::Offset, segmentOffset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rebaseNamedExportTable() {
|
|
const CROHeaderEntry namedExportTable = getHeaderEntry(CROHeader::NamedExportTableOffset);
|
|
|
|
for (u32 namedExport = 0; namedExport < namedExportTable.size; namedExport++) {
|
|
u32 nameOffset = mem.read32(namedExportTable.offset + 8 * namedExport);
|
|
|
|
if (nameOffset != 0) {
|
|
mem.write32(namedExportTable.offset + 8 * namedExport, nameOffset + croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unrebaseNamedExportTable() {
|
|
const CROHeaderEntry namedExportTable = getHeaderEntry(CROHeader::NamedExportTableOffset);
|
|
|
|
for (u32 namedExport = 0; namedExport < namedExportTable.size; namedExport++) {
|
|
u32 nameOffset = mem.read32(namedExportTable.offset + 8 * namedExport);
|
|
|
|
if (nameOffset != 0) {
|
|
mem.write32(namedExportTable.offset + 8 * namedExport, nameOffset - croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rebaseImportModuleTable() {
|
|
const CROHeaderEntry importModuleTable = getHeaderEntry(CROHeader::ImportModuleTableOffset);
|
|
|
|
for (u32 importModule = 0; importModule < importModuleTable.size; importModule++) {
|
|
u32 nameOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::NameOffset);
|
|
|
|
if (nameOffset != 0) {
|
|
mem.write32(importModuleTable.offset + 20 * importModule + ImportModuleTable::NameOffset, nameOffset + croPointer);
|
|
}
|
|
|
|
u32 indexedOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset);
|
|
|
|
if (indexedOffset != 0) {
|
|
mem.write32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset, indexedOffset + croPointer);
|
|
}
|
|
|
|
u32 anonymousOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset);
|
|
|
|
if (anonymousOffset != 0) {
|
|
mem.write32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset, anonymousOffset + croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unrebaseImportModuleTable() {
|
|
const CROHeaderEntry importModuleTable = getHeaderEntry(CROHeader::ImportModuleTableOffset);
|
|
|
|
for (u32 importModule = 0; importModule < importModuleTable.size; importModule++) {
|
|
u32 nameOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::NameOffset);
|
|
|
|
if (nameOffset != 0) {
|
|
mem.write32(importModuleTable.offset + 20 * importModule + ImportModuleTable::NameOffset, nameOffset - croPointer);
|
|
}
|
|
|
|
u32 indexedOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset);
|
|
|
|
if (indexedOffset != 0) {
|
|
mem.write32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset, indexedOffset - croPointer);
|
|
}
|
|
|
|
u32 anonymousOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset);
|
|
|
|
if (anonymousOffset != 0) {
|
|
mem.write32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset, anonymousOffset - croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rebaseNamedImportTable() {
|
|
const CROHeaderEntry namedImportTable = getHeaderEntry(CROHeader::NamedImportTableOffset);
|
|
|
|
for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) {
|
|
u32 nameOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset);
|
|
|
|
if (nameOffset != 0) {
|
|
mem.write32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset, nameOffset + croPointer);
|
|
}
|
|
|
|
u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset);
|
|
|
|
if (relocationOffset != 0) {
|
|
mem.write32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset, relocationOffset + croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unrebaseNamedImportTable() {
|
|
const CROHeaderEntry namedImportTable = getHeaderEntry(CROHeader::NamedImportTableOffset);
|
|
|
|
for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) {
|
|
u32 nameOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset);
|
|
|
|
if (nameOffset != 0) {
|
|
mem.write32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset, nameOffset - croPointer);
|
|
}
|
|
|
|
u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset);
|
|
|
|
if (relocationOffset != 0) {
|
|
mem.write32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset, relocationOffset - croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rebaseIndexedImportTable() {
|
|
const CROHeaderEntry indexedImportTable = getHeaderEntry(CROHeader::IndexedImportTableOffset);
|
|
|
|
for (u32 indexedImport = 0; indexedImport < indexedImportTable.size; indexedImport++) {
|
|
u32 relocationOffset = mem.read32(indexedImportTable.offset + 8 * indexedImport + IndexedImportTable::RelocationOffset);
|
|
|
|
if (relocationOffset != 0) {
|
|
mem.write32(indexedImportTable.offset + 8 * indexedImport + IndexedImportTable::RelocationOffset, relocationOffset + croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unrebaseIndexedImportTable() {
|
|
const CROHeaderEntry indexedImportTable = getHeaderEntry(CROHeader::IndexedImportTableOffset);
|
|
|
|
for (u32 indexedImport = 0; indexedImport < indexedImportTable.size; indexedImport++) {
|
|
u32 relocationOffset = mem.read32(indexedImportTable.offset + 8 * indexedImport + IndexedImportTable::RelocationOffset);
|
|
|
|
if (relocationOffset != 0) {
|
|
mem.write32(indexedImportTable.offset + 8 * indexedImport + IndexedImportTable::RelocationOffset, relocationOffset - croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rebaseAnonymousImportTable() {
|
|
const CROHeaderEntry anonymousImportTable = getHeaderEntry(CROHeader::AnonymousImportTableOffset);
|
|
|
|
for (u32 anonymousImport = 0; anonymousImport < anonymousImportTable.size; anonymousImport++) {
|
|
u32 relocationOffset = mem.read32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset);
|
|
|
|
if (relocationOffset != 0) {
|
|
mem.write32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset, relocationOffset + croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unrebaseAnonymousImportTable() {
|
|
const CROHeaderEntry anonymousImportTable = getHeaderEntry(CROHeader::AnonymousImportTableOffset);
|
|
|
|
for (u32 anonymousImport = 0; anonymousImport < anonymousImportTable.size; anonymousImport++) {
|
|
u32 relocationOffset = mem.read32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset);
|
|
|
|
if (relocationOffset != 0) {
|
|
mem.write32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset, relocationOffset - croPointer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool relocateInternalSymbols(u32 oldDataVaddr) {
|
|
const u8* header = (u8*)mem.getReadPointer(croPointer);
|
|
|
|
const CROHeaderEntry relocationPatchTable = getHeaderEntry(CROHeader::RelocationPatchTableOffset);
|
|
const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset);
|
|
|
|
for (u32 relocationPatch = 0; relocationPatch < relocationPatchTable.size; relocationPatch++) {
|
|
const u32 segmentOffset = mem.read32(relocationPatchTable.offset + 12 * relocationPatch + RelocationPatch::SegmentOffset);
|
|
const u8 patchType = mem.read8(relocationPatchTable.offset + 12 * relocationPatch + RelocationPatch::PatchType);
|
|
const u8 segmentIndex = mem.read8(relocationPatchTable.offset + 12 * relocationPatch + RelocationPatch::SegmentIndex);
|
|
const u32 addend = mem.read32(relocationPatchTable.offset + 12 * relocationPatch + RelocationPatch::Addend);
|
|
|
|
const u32 segmentAddr = getSegmentAddr(segmentOffset);
|
|
|
|
const u32 entryID = mem.read32(segmentTable.offset + 12 * (segmentOffset & 0xF) + SegmentTable::ID);
|
|
|
|
u32 relocationTarget = segmentAddr;
|
|
if (entryID == SegmentTable::SegmentID::DATA) {
|
|
// Recompute relocation target for .data
|
|
relocationTarget = oldDataVaddr + (segmentOffset >> 4);
|
|
}
|
|
|
|
if (relocationTarget == 0) {
|
|
Helpers::panic("Relocation target is NULL");
|
|
}
|
|
|
|
const u32 symbolOffset = mem.read32(segmentTable.offset + 12 * segmentIndex + SegmentTable::Offset);
|
|
|
|
patchSymbol(relocationTarget, patchType, addend, symbolOffset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool relocateStaticAnonymousSymbols() {
|
|
const CROHeaderEntry staticAnonymousSymbolTable = getHeaderEntry(CROHeader::StaticAnonymousSymbolTableOffset);
|
|
|
|
for (u32 symbol = 0; symbol < staticAnonymousSymbolTable.size; symbol++) {
|
|
Helpers::panic("TODO: relocate static anonymous symbols");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Patches "__aeabi_atexit" symbol to "nnroAeabiAtexit_"
|
|
bool relocateExitSymbols(u32 loadedCRS) {
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
const u32 importStringSize = mem.read32(croPointer + CROHeader::ImportStringSize);
|
|
|
|
const CROHeaderEntry namedImportTable = getHeaderEntry(CROHeader::NamedImportTableOffset);
|
|
|
|
for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) {
|
|
const u32 nameOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset);
|
|
const u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset);
|
|
|
|
const std::string symbolName = mem.readString(nameOffset, importStringSize);
|
|
|
|
if (symbolName.compare(std::string("__aeabi_atexit")) == 0) {
|
|
// Find exit symbol in other CROs
|
|
u32 currentCROPointer = loadedCRS;
|
|
while (currentCROPointer != 0) {
|
|
CRO cro(mem, currentCROPointer, true);
|
|
|
|
const u32 exportSymbolAddr = cro.getNamedExportSymbolAddr(std::string("nnroAeabiAtexit_"));
|
|
if (exportSymbolAddr != 0) {
|
|
patchBatch(relocationOffset, exportSymbolAddr);
|
|
|
|
return true;
|
|
}
|
|
|
|
currentCROPointer = cro.getNextCRO();
|
|
}
|
|
}
|
|
}
|
|
|
|
Helpers::warn("Failed to relocate exit symbols");
|
|
|
|
return false;
|
|
}
|
|
|
|
bool importNamedSymbols(u32 loadedCRS) {
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
const u32 importStringSize = mem.read32(croPointer + CROHeader::ImportStringSize);
|
|
|
|
const CROHeaderEntry namedImportTable = getHeaderEntry(CROHeader::NamedImportTableOffset);
|
|
|
|
for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) {
|
|
const u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset);
|
|
|
|
u8 isResolved = mem.read8(relocationOffset + RelocationPatch::IsResolved);
|
|
|
|
if (isResolved == 0) {
|
|
const u32 nameOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset);
|
|
|
|
const std::string symbolName = mem.readString(nameOffset, importStringSize);
|
|
|
|
// Check every loaded CRO for the symbol (the pain)
|
|
u32 currentCROPointer = loadedCRS;
|
|
while (currentCROPointer != 0) {
|
|
CRO cro(mem, currentCROPointer, true);
|
|
|
|
const u32 exportSymbolAddr = cro.getNamedExportSymbolAddr(symbolName);
|
|
if (exportSymbolAddr != 0) {
|
|
patchBatch(relocationOffset, exportSymbolAddr);
|
|
|
|
isResolved = 1;
|
|
break;
|
|
}
|
|
|
|
currentCROPointer = cro.getNextCRO();
|
|
}
|
|
|
|
if (isResolved == 0) {
|
|
Helpers::panic("Failed to resolve symbol %s", symbolName.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool clearNamedSymbols() {
|
|
const u32 onUnresolvedAddr = getOnUnresolvedAddr();
|
|
|
|
const CROHeaderEntry namedImportTable = getHeaderEntry(CROHeader::NamedImportTableOffset);
|
|
|
|
for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) {
|
|
const u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset);
|
|
|
|
patchBatch(relocationOffset, onUnresolvedAddr, true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool importModules(u32 loadedCRS) {
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
const u32 importStringSize = mem.read32(croPointer + CROHeader::ImportStringSize);
|
|
|
|
const CROHeaderEntry importModuleTable = getHeaderEntry(CROHeader::ImportModuleTableOffset);
|
|
|
|
for (u32 importModule = 0; importModule < importModuleTable.size; importModule++) {
|
|
const u32 nameOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::NameOffset);
|
|
|
|
const std::string importModuleName = mem.readString(nameOffset, importStringSize);
|
|
|
|
// Find import module
|
|
u32 currentCROPointer = loadedCRS;
|
|
while (currentCROPointer != 0) {
|
|
CRO cro(mem, currentCROPointer, true);
|
|
|
|
if (importModuleName.compare(cro.getModuleName()) == 0) {
|
|
// Import indexed symbols
|
|
const CROHeaderEntry indexedExportTable = cro.getHeaderEntry(CROHeader::IndexedExportTableOffset);
|
|
|
|
const u32 indexedOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset);
|
|
const u32 indexedNum = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedNum);
|
|
|
|
for (u32 indexedImport = 0; indexedImport < indexedNum; indexedImport++) {
|
|
if (indexedOffset == 0) {
|
|
Helpers::panic("Indexed symbol offset is NULL");
|
|
}
|
|
|
|
const u32 importIndex = mem.read32(indexedOffset + 8 * indexedImport + IndexedImportTable::Index);
|
|
|
|
const u32 segmentOffset = mem.read32(indexedExportTable.offset + 4 * importIndex + IndexedExportTable::SegmentOffset);
|
|
const u32 relocationOffset = mem.read32(indexedOffset + 8 * indexedImport + IndexedImportTable::RelocationOffset);
|
|
|
|
patchBatch(relocationOffset, cro.getSegmentAddr(segmentOffset));
|
|
}
|
|
|
|
// Import anonymous symbols
|
|
const u32 anonymousOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset);
|
|
const u32 anonymousNum = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousNum);
|
|
|
|
for (u32 anonymousImport = 0; anonymousImport < anonymousNum; anonymousImport++) {
|
|
if (anonymousOffset == 0) {
|
|
Helpers::panic("Anonymous symbol offset is NULL");
|
|
}
|
|
|
|
const u32 segmentOffset = mem.read32(anonymousOffset + 8 * anonymousImport + AnonymousImportTable::SegmentOffset);
|
|
const u32 relocationOffset = mem.read32(anonymousOffset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset);
|
|
|
|
patchBatch(relocationOffset, cro.getSegmentAddr(segmentOffset));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
currentCROPointer = cro.getNextCRO();
|
|
}
|
|
|
|
if (currentCROPointer == 0) {
|
|
Helpers::warn("Unable to find import module \"%s\"", importModuleName.c_str());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool clearModules() {
|
|
const u32 onUnresolvedAddr = getOnUnresolvedAddr();
|
|
|
|
const CROHeaderEntry importModuleTable = getHeaderEntry(CROHeader::ImportModuleTableOffset);
|
|
|
|
for (u32 importModule = 0; importModule < importModuleTable.size; importModule++) {
|
|
// Clear indexed symbol imports
|
|
const u32 indexedOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset);
|
|
const u32 indexedNum = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedNum);
|
|
|
|
for (u32 indexedImport = 0; indexedImport < indexedNum; indexedImport++) {
|
|
if (indexedOffset == 0) {
|
|
Helpers::panic("Indexed symbol offset is NULL");
|
|
}
|
|
|
|
const u32 relocationOffset = mem.read32(indexedOffset + 8 * indexedImport + IndexedImportTable::RelocationOffset);
|
|
|
|
patchBatch(relocationOffset, onUnresolvedAddr, true);
|
|
}
|
|
|
|
// Clear anonymous import symbols
|
|
const u32 anonymousOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset);
|
|
const u32 anonymousNum = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousNum);
|
|
|
|
for (u32 anonymousImport = 0; anonymousImport < anonymousNum; anonymousImport++) {
|
|
if (anonymousOffset == 0) {
|
|
Helpers::panic("Anonymous symbol offset is NULL");
|
|
}
|
|
|
|
const u32 relocationOffset = mem.read32(anonymousOffset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset);
|
|
|
|
patchBatch(relocationOffset, onUnresolvedAddr, true);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool exportSymbols(u32 loadedCRS) {
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
u32 currentCROPointer = loadedCRS;
|
|
while (currentCROPointer != 0) {
|
|
CRO cro(mem, currentCROPointer, true);
|
|
|
|
// Export named symbols
|
|
const u32 importStringSize = mem.read32(currentCROPointer + CROHeader::ImportStringSize);
|
|
|
|
const CROHeaderEntry namedImportTable = cro.getHeaderEntry(CROHeader::NamedImportTableOffset);
|
|
|
|
for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) {
|
|
const u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset);
|
|
|
|
u8 isResolved = mem.read8(relocationOffset + RelocationPatch::IsResolved);
|
|
|
|
if (isResolved == 0) {
|
|
const u32 nameOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset);
|
|
|
|
const std::string symbolName = mem.readString(nameOffset, importStringSize);
|
|
|
|
// Check our current CRO for the symbol
|
|
const u32 exportSymbolAddr = getNamedExportSymbolAddr(symbolName);
|
|
if (exportSymbolAddr == 0) {
|
|
continue;
|
|
}
|
|
|
|
cro.patchBatch(relocationOffset, exportSymbolAddr);
|
|
}
|
|
}
|
|
|
|
// Export indexed and anonymous symbols
|
|
const CROHeaderEntry importModuleTable = cro.getHeaderEntry(CROHeader::ImportModuleTableOffset);
|
|
|
|
for (u32 importModule = 0; importModule < importModuleTable.size; importModule++) {
|
|
// Check if other CROs request module imports from this CRO
|
|
const u32 nameOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::NameOffset);
|
|
|
|
const std::string moduleName = mem.readString(nameOffset, importStringSize);
|
|
|
|
if (moduleName.compare(getModuleName()) != 0) {
|
|
continue;
|
|
}
|
|
|
|
// Export indexed symbols
|
|
const u32 indexedOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset);
|
|
const u32 indexedNum = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedNum);
|
|
|
|
for (u32 indexedImport = 0; indexedImport < indexedNum; indexedImport++) {
|
|
Helpers::panic("TODO: indexed exports");
|
|
}
|
|
|
|
// Export anonymous symbols
|
|
const u32 anonymousOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset);
|
|
const u32 anonymousNum = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousNum);
|
|
|
|
for (u32 anonymousImport = 0; anonymousImport < anonymousNum; anonymousImport++) {
|
|
if (anonymousOffset == 0) {
|
|
Helpers::panic("Anonymous symbol offset is NULL");
|
|
}
|
|
|
|
const u32 segmentOffset = mem.read32(anonymousOffset + 8 * anonymousImport + AnonymousImportTable::SegmentOffset);
|
|
const u32 relocationOffset = mem.read32(anonymousOffset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset);
|
|
|
|
cro.patchBatch(relocationOffset, getSegmentAddr(segmentOffset));
|
|
}
|
|
}
|
|
|
|
currentCROPointer = cro.getNextCRO();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool clearSymbolExports(u32 loadedCRS) {
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
u32 currentCROPointer = loadedCRS;
|
|
while (currentCROPointer != 0) {
|
|
CRO cro(mem, currentCROPointer, true);
|
|
|
|
const u32 onUnresolvedAddr = cro.getOnUnresolvedAddr();
|
|
|
|
const u32 importStringSize = mem.read32(currentCROPointer + CROHeader::ImportStringSize);
|
|
|
|
// Clear named symbol exports
|
|
const CROHeaderEntry namedImportTable = cro.getHeaderEntry(CROHeader::NamedImportTableOffset);
|
|
|
|
for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) {
|
|
const u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset);
|
|
|
|
u8 isResolved = mem.read8(relocationOffset + RelocationPatch::IsResolved);
|
|
|
|
if (isResolved != 0) {
|
|
const u32 nameOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset);
|
|
|
|
const std::string symbolName = mem.readString(nameOffset, importStringSize);
|
|
|
|
// Check our current CRO for the symbol
|
|
const u32 exportSymbolAddr = getNamedExportSymbolAddr(symbolName);
|
|
if (exportSymbolAddr == 0) {
|
|
continue;
|
|
}
|
|
|
|
cro.patchBatch(relocationOffset, onUnresolvedAddr, true);
|
|
}
|
|
}
|
|
|
|
// Clear indexed and anonymous symbol exports
|
|
const CROHeaderEntry importModuleTable = cro.getHeaderEntry(CROHeader::ImportModuleTableOffset);
|
|
|
|
for (u32 importModule = 0; importModule < importModuleTable.size; importModule++) {
|
|
// Check if other CROs request module imports from this CRO
|
|
const u32 nameOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::NameOffset);
|
|
|
|
const std::string moduleName = mem.readString(nameOffset, importStringSize);
|
|
|
|
if (moduleName.compare(getModuleName()) != 0) {
|
|
continue;
|
|
}
|
|
|
|
// Export indexed symbols
|
|
const u32 indexedOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset);
|
|
const u32 indexedNum = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedNum);
|
|
|
|
for (u32 indexedImport = 0; indexedImport < indexedNum; indexedImport++) {
|
|
Helpers::panic("TODO: clear indexed exports");
|
|
}
|
|
|
|
// Export anonymous symbols
|
|
const u32 anonymousOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset);
|
|
const u32 anonymousNum = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousNum);
|
|
|
|
for (u32 anonymousImport = 0; anonymousImport < anonymousNum; anonymousImport++) {
|
|
if (anonymousOffset == 0) {
|
|
Helpers::panic("Anonymous symbol offset is NULL");
|
|
}
|
|
|
|
const u32 relocationOffset = mem.read32(anonymousOffset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset);
|
|
|
|
cro.patchBatch(relocationOffset, onUnresolvedAddr, true);
|
|
}
|
|
}
|
|
|
|
currentCROPointer = cro.getNextCRO();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Links CROs. Heavily based on Citra's CRO linker
|
|
bool link(u32 loadedCRS, bool isNew) {
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset);
|
|
|
|
// Fix data segment offset (LoadCRO_New)
|
|
// Note: the old LoadCRO does *not* fix .data
|
|
u32 dataVaddr;
|
|
if (isNew) {
|
|
if (segmentTable.size > 1) {
|
|
// Note: ldr:ro assumes that segment index 2 is .data
|
|
dataVaddr = mem.read32(segmentTable.offset + 24 + SegmentTable::Offset);
|
|
|
|
mem.write32(segmentTable.offset + 24 + SegmentTable::Offset, mem.read32(croPointer + CROHeader::DataOffset));
|
|
}
|
|
}
|
|
|
|
importNamedSymbols(loadedCRS);
|
|
importModules(loadedCRS);
|
|
exportSymbols(loadedCRS);
|
|
|
|
// Restore .data segment offset (LoadCRO_New)
|
|
if (isNew) {
|
|
if (segmentTable.size > 1) {
|
|
mem.write32(segmentTable.offset + 24 + SegmentTable::Offset, dataVaddr);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unlink(u32 loadedCRS) {
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
clearNamedSymbols();
|
|
clearModules();
|
|
clearSymbolExports(loadedCRS);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Adds CRO to the linked list of loaded CROs
|
|
void registerCRO(u32 loadedCRS, bool autoLink) {
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
CRO crs(mem, loadedCRS, false);
|
|
|
|
u32 headAddr = crs.getPrevCRO();
|
|
if (autoLink) {
|
|
headAddr = crs.getNextCRO();
|
|
}
|
|
|
|
if (headAddr == 0) {
|
|
// Register first CRO
|
|
setPrevCRO(croPointer);
|
|
|
|
if (autoLink) {
|
|
crs.setNextCRO(croPointer);
|
|
} else {
|
|
crs.setPrevCRO(croPointer);
|
|
}
|
|
} else {
|
|
// Register new CRO
|
|
CRO head(mem, headAddr, true);
|
|
CRO tail(mem, head.getPrevCRO(), true);
|
|
|
|
if (tail.getNextCRO() != 0) {
|
|
Helpers::panic("Invalid CRO tail");
|
|
}
|
|
|
|
setPrevCRO(tail.croPointer);
|
|
|
|
tail.setNextCRO(croPointer);
|
|
head.setPrevCRO(croPointer);
|
|
}
|
|
}
|
|
|
|
// Thanks, Citra
|
|
void unregisterCRO(u32 loadedCRS) {
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
CRO crs(mem, loadedCRS, false);
|
|
|
|
CRO next(mem, getNextCRO(), true);
|
|
CRO prev(mem, getPrevCRO(), true);
|
|
|
|
CRO nextHead(mem, crs.getNextCRO(), true);
|
|
CRO prevHead(mem, crs.getPrevCRO(), true);
|
|
|
|
if ((croPointer == nextHead.croPointer) || (croPointer == prevHead.croPointer)) {
|
|
// Our current CRO is the head, remove it
|
|
const u32 nextPointer = next.croPointer;
|
|
|
|
if (nextPointer != 0) {
|
|
next.setPrevCRO(prev.croPointer);
|
|
}
|
|
|
|
if (croPointer == prevHead.croPointer) {
|
|
crs.setPrevCRO(nextPointer);
|
|
} else {
|
|
crs.setNextCRO(nextPointer);
|
|
}
|
|
} else if (next.croPointer != 0) {
|
|
// Our current CRO is neither the head nor the tail,
|
|
// link the next and previous CRO with each other
|
|
prev.setNextCRO(next.croPointer);
|
|
next.setPrevCRO(prev.croPointer);
|
|
} else {
|
|
// Our current CRO is the tail, remove it
|
|
prev.setNextCRO(0);
|
|
|
|
const u32 prevPointer = prev.croPointer;
|
|
|
|
if ((nextHead.croPointer != 0) && (nextHead.getPrevCRO() == croPointer)) {
|
|
nextHead.setPrevCRO(prevPointer);
|
|
} else if ((prevHead.croPointer != 0) && (prevHead.getPrevCRO() == croPointer)) {
|
|
prevHead.setPrevCRO(prevPointer);
|
|
} else {
|
|
Helpers::panic("bwaaa");
|
|
}
|
|
}
|
|
|
|
setNextCRO(0);
|
|
setPrevCRO(0);
|
|
}
|
|
};
|
|
|
|
void LDRService::reset() {
|
|
loadedCRS = 0;
|
|
}
|
|
|
|
void LDRService::handleSyncRequest(u32 messagePointer) {
|
|
const u32 command = mem.read32(messagePointer);
|
|
switch (command) {
|
|
case LDRCommands::Initialize: initialize(messagePointer); break;
|
|
case LDRCommands::LoadCRR: loadCRR(messagePointer); break;
|
|
case LDRCommands::LoadCRO: loadCRO(messagePointer, false); break;
|
|
case LDRCommands::UnloadCRO: unloadCRO(messagePointer); break;
|
|
case LDRCommands::LinkCRO: linkCRO(messagePointer); break;
|
|
case LDRCommands::LoadCRONew: loadCRO(messagePointer, true); break;
|
|
default: Helpers::panic("LDR::RO service requested. Command: %08X\n", command);
|
|
}
|
|
}
|
|
|
|
void LDRService::initialize(u32 messagePointer) {
|
|
const u32 crsPointer = mem.read32(messagePointer + 4);
|
|
const u32 size = mem.read32(messagePointer + 8);
|
|
const u32 mapVaddr = mem.read32(messagePointer + 12);
|
|
const Handle process = mem.read32(messagePointer + 20);
|
|
|
|
log("LDR_RO::Initialize (buffer = %08X, size = %08X, vaddr = %08X, process = %X)\n", crsPointer, size, mapVaddr, process);
|
|
|
|
// Sanity checks
|
|
if (loadedCRS != 0) {
|
|
Helpers::panic("CRS already loaded\n");
|
|
}
|
|
|
|
if (size < CRO_HEADER_SIZE) {
|
|
Helpers::panic("CRS too small\n");
|
|
}
|
|
|
|
if (!mem.isAligned(size)) {
|
|
Helpers::panic("Unaligned CRS size\n");
|
|
}
|
|
|
|
if (!mem.isAligned(crsPointer)) {
|
|
Helpers::panic("Unaligned CRS pointer\n");
|
|
}
|
|
|
|
if (!mem.isAligned(mapVaddr)) {
|
|
Helpers::panic("Unaligned CRS output vaddr\n");
|
|
}
|
|
|
|
// Map CRO to output address
|
|
mem.mirrorMapping(mapVaddr, crsPointer, size);
|
|
|
|
CRO crs(mem, mapVaddr, false);
|
|
|
|
if (!crs.load()) {
|
|
Helpers::panic("Failed to load CRS");
|
|
}
|
|
|
|
if (!crs.rebase(0, 0, 0)) {
|
|
Helpers::panic("Failed to rebase CRS");
|
|
}
|
|
|
|
kernel.clearInstructionCacheRange(mapVaddr, size);
|
|
loadedCRS = mapVaddr;
|
|
|
|
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0));
|
|
mem.write32(messagePointer + 4, Result::Success);
|
|
}
|
|
|
|
void LDRService::linkCRO(u32 messagePointer) {
|
|
const u32 mapVaddr = mem.read32(messagePointer + 4);
|
|
const Handle process = mem.read32(messagePointer + 12);
|
|
|
|
log("LDR_RO::LinkCRO (vaddr = %X, process = %X)\n", mapVaddr, process);
|
|
|
|
if (loadedCRS == 0) {
|
|
Helpers::panic("CRS not loaded");
|
|
}
|
|
|
|
if (!mem.isAligned(mapVaddr)) {
|
|
Helpers::panic("Unaligned CRO vaddr\n");
|
|
}
|
|
|
|
CRO cro(mem, mapVaddr, true);
|
|
|
|
// TODO: check if CRO has been loaded prior to calling this
|
|
|
|
if (!cro.link(loadedCRS, false)) {
|
|
Helpers::panic("Failed to link CRO");
|
|
}
|
|
|
|
mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 0));
|
|
mem.write32(messagePointer + 4, Result::Success);
|
|
}
|
|
|
|
void LDRService::loadCRR(u32 messagePointer) {
|
|
const u32 crrPointer = mem.read32(messagePointer + 4);
|
|
const u32 size = mem.read32(messagePointer + 8);
|
|
const Handle process = mem.read32(messagePointer + 20);
|
|
|
|
log("LDR_RO::LoadCRR (buffer = %08X, size = %08X, process = %X)\n", crrPointer, size, process);
|
|
mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0));
|
|
mem.write32(messagePointer + 4, Result::Success);
|
|
}
|
|
|
|
void LDRService::loadCRO(u32 messagePointer, bool isNew) {
|
|
const u32 croPointer = mem.read32(messagePointer + 4);
|
|
const u32 mapVaddr = mem.read32(messagePointer + 8);
|
|
const u32 size = mem.read32(messagePointer + 12);
|
|
const u32 dataVaddr = mem.read32(messagePointer + 16);
|
|
const u32 dataSize = mem.read32(messagePointer + 24);
|
|
const u32 bssVaddr = mem.read32(messagePointer + 28);
|
|
const u32 bssSize = mem.read32(messagePointer + 32);
|
|
const bool autoLink = mem.read32(messagePointer + 36) != 0;
|
|
const u32 fixLevel = mem.read32(messagePointer + 40);
|
|
const Handle process = mem.read32(messagePointer + 52);
|
|
|
|
log("LDR_RO::LoadCRO (isNew = %d, buffer = %08X, vaddr = %08X, size = %08X, .data vaddr = %08X, .data size = %08X, .bss vaddr = %08X, .bss size = %08X, auto link = %d, fix level = %X, process = %X)\n", isNew, croPointer, mapVaddr, size, dataVaddr, dataSize, bssVaddr, bssSize, autoLink, fixLevel, process);
|
|
|
|
// Sanity checks
|
|
if (size < CRO_HEADER_SIZE) {
|
|
Helpers::panic("CRO too small\n");
|
|
}
|
|
|
|
if (!mem.isAligned(size)) {
|
|
Helpers::panic("Unaligned CRO size\n");
|
|
}
|
|
|
|
if (!mem.isAligned(croPointer)) {
|
|
Helpers::panic("Unaligned CRO pointer\n");
|
|
}
|
|
|
|
if (!mem.isAligned(mapVaddr)) {
|
|
Helpers::panic("Unaligned CRO output vaddr\n");
|
|
}
|
|
|
|
// Map CRO to output address
|
|
mem.mirrorMapping(mapVaddr, croPointer, size);
|
|
|
|
CRO cro(mem, mapVaddr, true);
|
|
|
|
if (!cro.load()) {
|
|
Helpers::panic("Failed to load CRO");
|
|
}
|
|
|
|
if (!cro.rebase(loadedCRS, dataVaddr, bssVaddr)) {
|
|
Helpers::panic("Failed to rebase CRO");
|
|
}
|
|
|
|
if (!cro.link(loadedCRS, isNew)) {
|
|
Helpers::panic("Failed to link CRO");
|
|
}
|
|
|
|
cro.registerCRO(loadedCRS, autoLink);
|
|
|
|
// TODO: add fixing
|
|
cro.fix(fixLevel);
|
|
kernel.clearInstructionCacheRange(mapVaddr, size);
|
|
|
|
if (isNew) {
|
|
mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0));
|
|
} else {
|
|
mem.write32(messagePointer, IPC::responseHeader(0x4, 2, 0));
|
|
}
|
|
|
|
mem.write32(messagePointer + 4, Result::Success);
|
|
mem.write32(messagePointer + 8, size);
|
|
}
|
|
|
|
void LDRService::unloadCRO(u32 messagePointer) {
|
|
const u32 mapVaddr = mem.read32(messagePointer + 4);
|
|
const u32 croPointer = mem.read32(messagePointer + 12);
|
|
const Handle process = mem.read32(messagePointer + 20);
|
|
|
|
log("LDR_RO::UnloadCRO (vaddr = %08X, buffer = %08X, process = %X)\n", mapVaddr, croPointer, process);
|
|
|
|
// TODO: is CRO loaded?
|
|
|
|
if (!mem.isAligned(croPointer)) {
|
|
Helpers::panic("Unaligned CRO pointer\n");
|
|
}
|
|
|
|
if (!mem.isAligned(mapVaddr)) {
|
|
Helpers::panic("Unaligned CRO output vaddr\n");
|
|
}
|
|
|
|
CRO cro(mem, mapVaddr, true);
|
|
cro.unregisterCRO(loadedCRS);
|
|
|
|
if (!cro.unlink(loadedCRS)) {
|
|
Helpers::panic("Failed to unlink CRO");
|
|
}
|
|
|
|
if (!cro.unrebase()) {
|
|
Helpers::panic("Failed to unrebase CRO");
|
|
}
|
|
|
|
kernel.clearInstructionCacheRange(mapVaddr, cro.getFixedSize());
|
|
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
|
|
mem.write32(messagePointer + 4, Result::Success);
|
|
} |