diff --git a/include/services/ldr_ro.hpp b/include/services/ldr_ro.hpp index 7acfce698..71516547b 100644 --- a/include/services/ldr_ro.hpp +++ b/include/services/ldr_ro.hpp @@ -5,17 +5,25 @@ #include "memory.hpp" #include "result/result.hpp" +class Kernel; + class LDRService { Handle handle = KernelHandles::LDR_RO; Memory& mem; + Kernel& kernel; MAKE_LOG_FUNCTION(log, ldrLogger) + u32 loadedCRS; + // Service commands void initialize(u32 messagePointer); + void linkCRO(u32 messagePointer); + void loadCRO(u32 messagePointer, bool isNew); void loadCRR(u32 messagePointer); + void unloadCRO(u32 messagePointer); public: - LDRService(Memory& mem) : mem(mem) {} + LDRService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} void reset(); void handleSyncRequest(u32 messagePointer); }; \ No newline at end of file diff --git a/src/core/services/ldr_ro.cpp b/src/core/services/ldr_ro.cpp index 4ea73b4ef..2ad62d99c 100644 --- a/src/core/services/ldr_ro.cpp +++ b/src/core/services/ldr_ro.cpp @@ -1,20 +1,1207 @@ #include "services/ldr_ro.hpp" #include "ipc.hpp" +#include "kernel.hpp" + +#include +#include namespace LDRCommands { enum : u32 { Initialize = 0x000100C2, - LoadCRR = 0x00020082 + LoadCRR = 0x00020082, + LoadCRO = 0x000402C2, + UnloadCRO = 0x000500C2, + LinkCRO = 0x00060042, + LoadCRONew = 0x000902C2, }; } -void LDRService::reset() {} +namespace CROHeader { + enum : u32 { + ID = 0x080, + NameOffset = 0x084, + NextCRO = 0x088, + PrevCRO = 0x08C, + 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); + } + + 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 + dataVaddr; 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); } } @@ -26,10 +1213,77 @@ void LDRService::initialize(u32 messagePointer) { 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.clearInstructionCache(); + + 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"); + } + + kernel.clearInstructionCache(); + + 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); @@ -38,4 +1292,104 @@ void LDRService::loadCRR(u32 messagePointer) { 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.clearInstructionCache(); + + 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.clearInstructionCache(); + + mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/service_manager.cpp b/src/core/services/service_manager.cpp index 0fbcdac41..3696b0d1b 100644 --- a/src/core/services/service_manager.cpp +++ b/src/core/services/service_manager.cpp @@ -8,7 +8,7 @@ ServiceManager::ServiceManager(std::span regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config) : regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), cfg(mem), csnd(mem, kernel), dlp_srvr(mem), dsp(mem, kernel), hid(mem, kernel), http(mem), ir_user(mem, kernel), frd(mem), fs(mem, kernel, config), - gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mcu_hwc(mem, config), mic(mem, kernel), nfc(mem, kernel), nim(mem), ndm(mem), + gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem, kernel), mcu_hwc(mem, config), mic(mem, kernel), nfc(mem, kernel), nim(mem), ndm(mem), news_u(mem), nwm_uds(mem, kernel), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {} static constexpr int MAX_NOTIFICATION_COUNT = 16;