#include "Cafe/OS/common/OSCommon.h" #include "Cafe/Filesystem/fsc.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "util/VirtualHeap/VirtualHeap.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "util/ChunkedHeap/ChunkedHeap.h" #include #include "util/crypto/crc32.h" #include "config/ActiveSettings.h" #include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" #include "gui/guiWrapper.h" class PPCCodeHeap : public VHeap { public: PPCCodeHeap(void* heapBase, uint32 heapSize) : VHeap(heapBase, heapSize) { }; void* alloc(uint32 size, uint32 alignment = 4) override { return VHeap::alloc(size, alignment); } void free(void* addr) override { uint32 allocSize = getAllocationSizeFromAddr(addr); MPTR ppcAddr = memory_getVirtualOffsetFromPointer(addr); PPCRecompiler_invalidateRange(ppcAddr, ppcAddr + allocSize); VHeap::free(addr); } }; VHeap rplLoaderHeap_workarea(nullptr, MEMORY_RPLLOADER_AREA_SIZE); PPCCodeHeap rplLoaderHeap_lowerAreaCodeMem2(nullptr, MEMORY_CODE_TRAMPOLINE_AREA_SIZE); PPCCodeHeap rplLoaderHeap_codeArea2(nullptr, MEMORY_CODEAREA_SIZE); bool rplLoader_applicationHasMemoryControl = false; uint32 rplLoader_maxCodeAddress = 0; // highest used code address ChunkedFlatAllocator<64 * 1024> g_heapTrampolineArea; std::vector rplDependencyList = std::vector(); RPLModule* rplModuleList[256]; sint32 rplModuleCount = 0; uint32 _currentTLSModuleIndex = 1; // value 0 is reserved uint32 rplLoader_sdataAddr = MPTR_NULL; // r13 uint32 rplLoader_sdata2Addr = MPTR_NULL; // r2 std::map g_map_callableExports; struct RPLMappingRegion { MPTR baseAddress; uint32 endAddress; uint32 calcEndAddress; // used to verify endAddress }; struct RPLRegionMappingTable { RPLMappingRegion region[4]; }; #define RPL_MAPPING_REGION_DATA 0 #define RPL_MAPPING_REGION_LOADERINFO 1 #define RPL_MAPPING_REGION_TEXT 2 #define RPL_MAPPING_REGION_TEMP 3 void RPLLoader_UnloadModule(RPLModule* rpl); void RPLLoader_RemoveDependency(const char* name); char _ansiToLower(char c) { if (c >= 'A' && c <= 'Z') c -= ('A' - 'a'); return c; } uint8* RPLLoader_AllocateTrampolineCodeSpace(RPLModule* rplLoaderContext, sint32 size) { if (rplLoaderContext) { // allocation owned by rpl return (uint8*)rplLoaderContext->heapTrampolineArea.alloc(size, 4); } // allocation owned by global context auto result = (uint8*)g_heapTrampolineArea.alloc(size, 4); rplLoader_maxCodeAddress = std::max(rplLoader_maxCodeAddress, memory_getVirtualOffsetFromPointer(g_heapTrampolineArea.getCurrentBlockPtr()) + g_heapTrampolineArea.getCurrentBlockOffset()); return result; } uint8* RPLLoader_AllocateTrampolineCodeSpace(sint32 size) { return RPLLoader_AllocateTrampolineCodeSpace(nullptr, size); } MPTR RPLLoader_AllocateCodeSpace(uint32 size, uint32 alignment) { cemu_assert_debug((alignment & (alignment - 1)) == 0); // alignment must be a power of 2 MPTR codeAddr = memory_getVirtualOffsetFromPointer(rplLoaderHeap_codeArea2.alloc(size, alignment)); rplLoader_maxCodeAddress = std::max(rplLoader_maxCodeAddress, codeAddr + size); PPCRecompiler_allocateRange(codeAddr, size); return codeAddr; } uint32 rpl3_currentDataAllocatorAddr = 0x10000000; uint32 RPLLoader_AllocateDataSpace(RPLModule* rpl, uint32 size, uint32 alignment) { if (rplLoader_applicationHasMemoryControl) { StackAllocator memPtr; *(memPtr.GetPointer()) = 0; PPCCoreCallback(rpl->funcAlloc.value(), size, alignment, memPtr.GetPointer()); return (uint32)*(memPtr.GetPointer()); } rpl3_currentDataAllocatorAddr = (rpl3_currentDataAllocatorAddr + alignment - 1)&~(alignment-1); uint32 mem = rpl3_currentDataAllocatorAddr; rpl3_currentDataAllocatorAddr += size; return mem; } void RPLLoader_FreeData(RPLModule* rpl, void* ptr) { PPCCoreCallback(rpl->funcFree.value(), ptr); } uint32 RPLLoader_GetDataAllocatorAddr() { return (rpl3_currentDataAllocatorAddr + 0xFFF)&(~0xFFF); } uint32 RPLLoader_GetMaxCodeOffset() { return rplLoader_maxCodeAddress; } #define PPCASM_OPC_R_TEMPL_SIMM(_rD, _rA, _IMM) (((_rD)<<21)|((_rA)<<16)|((_IMM)&0xFFFF)) // generates 32-bit jump. Modifies R11 and CTR MPTR _generateTrampolineFarJump(RPLModule* rplLoaderContext, MPTR destAddr) { auto itr = rplLoaderContext->trampolineMap.find(destAddr); if (itr != rplLoaderContext->trampolineMap.end()) return itr->second; MPTR trampolineAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(rplLoaderContext, 4*4)); uint32 destAddrU32 = (uint32)destAddr; uint32 ppcOpcode = 0; // ADDI R11, R0, ... ppcOpcode = PPCASM_OPC_R_TEMPL_SIMM(11, 0, destAddrU32 & 0xFFFF); ppcOpcode |= (14 << 26); memory_writeU32(trampolineAddr + 0x0, ppcOpcode); // ADDIS R11, R11, ...<<16 ppcOpcode = PPCASM_OPC_R_TEMPL_SIMM(11, 11, ((destAddrU32 >> 16) + ((destAddrU32 >> 15) & 1)) & 0xFFFF); ppcOpcode |= (15 << 26); memory_writeU32(trampolineAddr + 0x4, ppcOpcode); // MTCTR r11 memory_writeU32(trampolineAddr + 0x8, 0x7D6903A6); // BCTR memory_writeU32(trampolineAddr + 0xC, 0x4E800420); // if the destination is a known symbol, create a proxy (duplicate) symbol at the jump rplSymbolStorage_createJumpProxySymbol(trampolineAddr, destAddr); rplLoaderContext->trampolineMap.emplace(destAddr, trampolineAddr); return trampolineAddr; } void* RPLLoader_AllocWorkarea(uint32 size, uint32 alignment, uint32* allocSize) { size = (size + 31)&~31; *allocSize = size; void* allocAddr = rplLoaderHeap_workarea.alloc(size, alignment); cemu_assert(allocAddr != nullptr); memset(allocAddr, 0, size); return allocAddr; } void RPLLoader_FreeWorkarea(void* allocAddr) { rplLoaderHeap_workarea.free(allocAddr); } bool RPLLoader_CheckBounds(RPLModule* rplLoaderContext, uint32 offset, uint32 size) { if ((offset + size) > rplLoaderContext->RPLRawData.size_bytes()) return false; return true; } bool RPLLoader_ProcessHeaders(std::string_view moduleName, uint8* rplData, uint32 rplSize, RPLModule** rplLoaderContextOut) { rplHeaderNew_t* rplHeader = (rplHeaderNew_t*)rplData; *rplLoaderContextOut = nullptr; if (rplHeader->version04 != 0x01) return false; if (rplHeader->ukn05 != 0x02) return false; if (rplHeader->magic2_0 != 0xCA) return false; if (rplHeader->magic2_1 != 0xFE) return false; if (rplHeader->ukn06 > 1) return false; if (rplHeader->ukn12 != 0x14) return false; if (rplHeader->ukn14 != 0x01) return false; if (rplHeader->sectionTableEntryCount < 2) return false; // RPL must end with two sections: CRCS + FILEINFO // setup RPL info struct RPLModule* rplLoaderContext = new RPLModule(); rplLoaderContext->RPLRawData = std::span(rplData, rplSize); rplLoaderContext->rplData_depr = rplData; rplLoaderContext->heapTrampolineArea.setBaseAllocator(&rplLoaderHeap_lowerAreaCodeMem2); // load section table if ((uint32)rplHeader->sectionTableEntrySize != sizeof(rplSectionEntryNew_t)) assert_dbg(); sint32 sectionCount = (sint32)rplHeader->sectionTableEntryCount; sint32 sectionTableSize = (sint32)rplHeader->sectionTableEntrySize * sectionCount; rplLoaderContext->sectionTablePtr = (rplSectionEntryNew_t*)malloc(sectionTableSize); memcpy(rplLoaderContext->sectionTablePtr, rplData + (uint32)(rplHeader->sectionTableOffset), sectionTableSize); // copy rpl header memcpy(&rplLoaderContext->rplHeader, rplHeader, sizeof(rplHeaderNew_t)); // verify that section n-1 is FILEINFO rplSectionEntryNew_t* fileinfoSection = rplLoaderContext->sectionTablePtr + ((uint32)rplLoaderContext->rplHeader.sectionTableEntryCount - 1); if (fileinfoSection->fileOffset == 0 || (uint32)fileinfoSection->fileOffset >= rplSize || (uint32)fileinfoSection->type != SHT_RPL_FILEINFO) { forceLogDebug_printf("RPLLoader: Last section not FILEINFO"); } // verify that section n-2 is CRCs rplSectionEntryNew_t* crcSection = rplLoaderContext->sectionTablePtr + ((uint32)rplLoaderContext->rplHeader.sectionTableEntryCount - 2); if (crcSection->fileOffset == 0 || (uint32)crcSection->fileOffset >= rplSize || (uint32)crcSection->type != SHT_RPL_CRCS) { forceLogDebug_printf("RPLLoader: The section before FILEINFO must be CRCs"); } // load FILEINFO section if (fileinfoSection->sectionSize < sizeof(RPLFileInfoData)) { cemuLog_force("RPLLoader: FILEINFO section size is below expected size"); return false; } // read RPL mapping info uint8* fileInfoRawPtr = (uint8*)(rplData + fileinfoSection->fileOffset); if (((uint64)fileinfoSection->fileOffset+fileinfoSection->sectionSize) > (uint64)rplSize) { cemuLog_force("RPLLoader: FILEINFO section outside of RPL file bounds"); return false; } rplLoaderContext->sectionData_fileInfo.resize(fileinfoSection->sectionSize); memcpy(rplLoaderContext->sectionData_fileInfo.data(), fileInfoRawPtr, rplLoaderContext->sectionData_fileInfo.size()); RPLFileInfoData* fileInfoPtr = (RPLFileInfoData*)rplLoaderContext->sectionData_fileInfo.data(); if (fileInfoPtr->fileInfoMagic != 0xCAFE0402) { cemuLog_force("RPLLoader: Invalid FILEINFO magic"); return false; } // process FILEINFO rplLoaderContext->fileInfo.textRegionSize = fileInfoPtr->textRegionSize; rplLoaderContext->fileInfo.dataRegionSize = fileInfoPtr->dataRegionSize; rplLoaderContext->fileInfo.baseAlign = fileInfoPtr->baseAlign; rplLoaderContext->fileInfo.ukn14 = fileInfoPtr->ukn14; rplLoaderContext->fileInfo.trampolineAdjustment = fileInfoPtr->trampolineAdjustment; rplLoaderContext->fileInfo.ukn4C = fileInfoPtr->ukn4C; rplLoaderContext->fileInfo.tlsModuleIndex = fileInfoPtr->tlsModuleIndex; rplLoaderContext->fileInfo.sdataBase1 = fileInfoPtr->sdataBase1; rplLoaderContext->fileInfo.sdataBase2 = fileInfoPtr->sdataBase2; // init section address table rplLoaderContext->sectionAddressTable2.resize(sectionCount); // init modulename rplLoaderContext->moduleName2.assign(moduleName); // convert modulename to lower-case for(auto& c : rplLoaderContext->moduleName2) c = _ansiToLower(c); // cemuhook compatibility rplLoaderContext->moduleNamePtr__depr = rplLoaderContext->moduleName2.data(); rplLoaderContext->moduleNameLength__depr = rplLoaderContext->moduleName2.size(); rplLoaderContext->moduleNameSize = 0; rplLoaderContext->sectionAddressTable__depr = rplLoaderContext->sectionAddressTable2.data(); rplLoaderContext->sectionAddressTableSize__depr = rplLoaderContext->sectionAddressTable2.size() * sizeof(rplSectionAddressEntry_t); // load CRC section uint32 crcTableExpectedSize = sectionCount * sizeof(uint32be); if (!RPLLoader_CheckBounds(rplLoaderContext, crcSection->fileOffset, crcTableExpectedSize)) { cemuLog_force("RPLLoader: CRC section outside of RPL file bounds"); crcSection->sectionSize = 0; } else if (crcSection->sectionSize < crcTableExpectedSize) { cemuLog_force("RPLLoader: CRC section size (0x{:x}) less than required (0x{:x})", (uint32)crcSection->sectionSize, crcTableExpectedSize); } else if (crcSection->sectionSize != crcTableExpectedSize) { cemuLog_force("RPLLoader: CRC section size (0x{:x}) does not match expected size (0x{:x})", (uint32)crcSection->sectionSize, crcTableExpectedSize); } uint32 crcActualSectionCount = crcSection->sectionSize / sizeof(uint32); // how many CRCs are actually stored rplLoaderContext->crcTable.resize(sectionCount); if (crcActualSectionCount > 0) { uint32be* crcTableData = (uint32be*)(rplData + crcSection->fileOffset); for (uint32 i = 0; i < crcActualSectionCount; i++) rplLoaderContext->crcTable[i] = crcTableData[i]; } // verify CRC of FILEINFO section uint32 crcCalcFileinfo = crc32_calc(0, rplLoaderContext->sectionData_fileInfo.data(), rplLoaderContext->sectionData_fileInfo.size()); uint32 crcFileinfo = rplLoaderContext->GetSectionCRC(sectionCount - 1); if (crcCalcFileinfo != crcFileinfo) { cemuLog_force("RPLLoader: FILEINFO section has CRC mismatch - Calculated: {:08x} Actual: {:08x}", crcCalcFileinfo, crcFileinfo); } rplLoaderContext->sectionAddressTable2[sectionCount - 1].ptr = rplLoaderContext->sectionData_fileInfo.data(); rplLoaderContext->sectionAddressTable2[sectionCount - 2].ptr = nullptr;// rplLoaderContext->crcTablePtr; // set output *rplLoaderContextOut = rplLoaderContext; return true; } class RPLUncompressedSection { public: std::vector sectionData; }; rplSectionEntryNew_t* RPLLoader_GetSection(RPLModule* rplLoaderContext, sint32 sectionIndex) { sint32 sectionCount = rplLoaderContext->rplHeader.sectionTableEntryCount; if (sectionIndex < 0 || sectionIndex >= sectionCount) { forceLog_printf("RPLLoader: Section index out of bounds"); rplLoaderContext->hasError = true; return nullptr; } rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + sectionIndex; return section; } RPLUncompressedSection* RPLLoader_LoadUncompressedSection(RPLModule* rplLoaderContext, sint32 sectionIndex) { const rplSectionEntryNew_t* section = RPLLoader_GetSection(rplLoaderContext, sectionIndex); if (section == nullptr) return nullptr; RPLUncompressedSection* uSection = new RPLUncompressedSection(); if ((uint32)section->type == 0x8) { uSection->sectionData.resize(section->sectionSize); std::fill(uSection->sectionData.begin(), uSection->sectionData.end(), 0); return uSection; } // check if raw size does not exceed bounds of rpl if (!RPLLoader_CheckBounds(rplLoaderContext, section->fileOffset, section->sectionSize)) { // BSS forceLog_printf("RPLLoader: Raw data for section %d exceeds bounds of RPL file", sectionIndex); rplLoaderContext->hasError = true; delete uSection; return nullptr; } uint32 sectionFlags = section->flags; if ((sectionFlags & SHF_RPL_COMPRESSED) != 0) { // decompress if (!RPLLoader_CheckBounds(rplLoaderContext, section->fileOffset, sizeof(uint32be)) ) { forceLog_printf("RPLLoader: Uncompressed data of section %d is too large", sectionIndex); rplLoaderContext->hasError = true; delete uSection; return nullptr; } uint32 uncompressedSize = *(uint32be*)(rplLoaderContext->RPLRawData.data() + (uint32)section->fileOffset); if (uncompressedSize >= 1*1024*1024*1024) // sections bigger than 1GB not allowed { forceLog_printf("RPLLoader: Uncompressed data of section %d is too large", sectionIndex); rplLoaderContext->hasError = true; delete uSection; return nullptr; } int ret; z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; ret = inflateInit(&strm); if (ret == Z_OK) { strm.avail_in = (uint32)section->sectionSize - 4; strm.next_in = rplLoaderContext->RPLRawData.data() + (uint32)section->fileOffset + 4; strm.avail_out = uncompressedSize; uSection->sectionData.resize(uncompressedSize); strm.next_out = uSection->sectionData.data(); ret = inflate(&strm, Z_FULL_FLUSH); inflateEnd(&strm); if ((ret != Z_OK && ret != Z_STREAM_END) || strm.avail_in != 0 || strm.avail_out != 0) { forceLog_printf("RPLLoader: Error while inflating data for section %d", sectionIndex); rplLoaderContext->hasError = true; delete uSection; return nullptr; } } } else { // no decompression uSection->sectionData.resize(section->sectionSize); const uint8* sectionDataBegin = rplLoaderContext->RPLRawData.data() + (uint32)section->fileOffset; std::copy(sectionDataBegin, sectionDataBegin + section->sectionSize, uSection->sectionData.data()); } return uSection; } bool RPLLoader_LoadSingleSection(RPLModule* rplLoaderContext, sint32 sectionIndex, RPLMappingRegion* regionMappingInfo, MPTR mappedAddress) { rplSectionEntryNew_t* section = RPLLoader_GetSection(rplLoaderContext, sectionIndex); if (section == nullptr) return false; uint32 mappingOffset = (uint32)section->virtualAddress - (uint32)regionMappingInfo->baseAddress; if (mappingOffset >= 0x10000000) forceLogDebug_printf("Suspicious section mapping offset: 0x%08x", mappingOffset); uint32 sectionAddress = mappedAddress + mappingOffset; rplLoaderContext->sectionAddressTable2[sectionIndex].ptr = memory_getPointerFromVirtualOffset(sectionAddress); cemu_assert(rplLoaderContext->debugSectionLoadMask[sectionIndex] == false); rplLoaderContext->debugSectionLoadMask[sectionIndex] = true; // extract section RPLUncompressedSection* uncompressedSection = RPLLoader_LoadUncompressedSection(rplLoaderContext, sectionIndex); if (uncompressedSection == nullptr) { rplLoaderContext->hasError = true; return false; } // copy to mapped address if(section->virtualAddress < regionMappingInfo->baseAddress || (section->virtualAddress + uncompressedSection->sectionData.size()) > regionMappingInfo->endAddress) cemuLog_force("RPLLoader: Section {} (0x{:08x} to 0x{:08x}) is not fully contained in it's bounding region (0x{:08x} to 0x{:08x})", sectionIndex, section->virtualAddress, section->virtualAddress + uncompressedSection->sectionData.size(), regionMappingInfo->baseAddress, regionMappingInfo->endAddress); uint8* sectionAddressPtr = memory_getPointerFromVirtualOffset(sectionAddress); std::copy(uncompressedSection->sectionData.begin(), uncompressedSection->sectionData.end(), sectionAddressPtr); // update size in section (todo - use separate field) if (uncompressedSection->sectionData.size() < section->sectionSize) forceLog_printf("RPLLoader: Section %d uncompresses to %d bytes but sectionSize is %d", sectionIndex, uncompressedSection->sectionData.size(), (uint32)section->sectionSize); section->sectionSize = uncompressedSection->sectionData.size(); delete uncompressedSection; return true; } bool RPLLoader_LoadSections(sint32 aProcId, RPLModule* rplLoaderContext) { RPLRegionMappingTable regionMappingTable; memset(®ionMappingTable, 0, sizeof(RPLRegionMappingTable)); regionMappingTable.region[0].baseAddress = 0xFFFFFFFF; regionMappingTable.region[1].baseAddress = 0xFFFFFFFF; regionMappingTable.region[2].baseAddress = 0xFFFFFFFF; regionMappingTable.region[3].baseAddress = 0xFFFFFFFF; for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; uint32 sectionVirtualAddr = section->virtualAddress; uint32 sectionFileOffset = section->fileOffset; uint32 sectionSize = section->sectionSize; if(sectionSize == 0) continue; if (sectionType == SHT_RPL_CRCS) continue; if (sectionType == SHT_RPL_FILEINFO) continue; //if (sectionType == SHT_RPL_IMPORTS) -> The official loader seems to skip these, leading to incorrect boundary calculations // continue; if ((sectionFlags & 2) == 0) { uint32 endFileOffset = sectionFileOffset + sectionSize; regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress, sectionFileOffset); regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress = std::max(regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress, endFileOffset); continue; } if ((sectionFlags & 4) != 0 && sectionType != SHT_RPL_EXPORTS && sectionType != SHT_RPL_IMPORTS) { regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress, sectionVirtualAddr); continue; } if ((sectionFlags & 1) != 0) { regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress, sectionVirtualAddr); continue; } else { regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress, sectionVirtualAddr); continue; } } for (sint32 i = 0; i < 4; i++) { if (regionMappingTable.region[i].baseAddress == 0xFFFFFFFF) regionMappingTable.region[i].baseAddress = 0; } regionMappingTable.region[RPL_MAPPING_REGION_TEXT].endAddress = (regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress + rplLoaderContext->fileInfo.textRegionSize) - rplLoaderContext->fileInfo.trampolineAdjustment; regionMappingTable.region[RPL_MAPPING_REGION_DATA].endAddress = regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress + rplLoaderContext->fileInfo.dataRegionSize; regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].endAddress = (regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress + rplLoaderContext->fileInfo.ukn14) - rplLoaderContext->fileInfo.ukn4C; // calculate region size uint32 regionDataSize = regionMappingTable.region[RPL_MAPPING_REGION_DATA].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress; uint32 regionLoaderinfoSize = regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress; uint32 regionTextSize = regionMappingTable.region[RPL_MAPPING_REGION_TEXT].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress; rplLoaderContext->regionMappingBase_data = RPLLoader_AllocateDataSpace(rplLoaderContext, regionDataSize, 0x1000); rplLoaderContext->regionMappingBase_loaderInfo = RPLLoader_AllocateDataSpace(rplLoaderContext, regionLoaderinfoSize, 0x1000); rplLoaderContext->regionMappingBase_text = rplLoaderHeap_codeArea2.alloc(regionTextSize + 0x1000, 0x1000); rplLoader_maxCodeAddress = std::max(rplLoader_maxCodeAddress, rplLoaderContext->regionMappingBase_text.GetMPTR() + regionTextSize + 0x1000); PPCRecompiler_allocateRange(rplLoaderContext->regionMappingBase_text.GetMPTR(), regionTextSize + 0x1000); // workaround for DKC Tropical Freeze if (boost::iequals(rplLoaderContext->moduleName2, "rs10_production")) { // allocate additional 12MB of unused data to get below a size of 0x3E200000 for the main ExpHeap // otherwise the game will assume it's running on a Devkit unit with 2GB of RAM and subtract 1GB from available space RPLLoader_AllocateDataSpace(rplLoaderContext, 12*1024*1024, 0x1000); } // set region sizes rplLoaderContext->regionSize_data = regionDataSize; rplLoaderContext->regionSize_loaderInfo = regionLoaderinfoSize; rplLoaderContext->regionSize_text = regionTextSize; // load data sections for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if (section->sectionSize == 0) continue; if( rplLoaderContext->sectionAddressTable2[i].ptr != nullptr ) continue; if ((sectionFlags & 2) == 0) continue; if ((sectionFlags & 1) == 0) continue; RPLLoader_LoadSingleSection(rplLoaderContext, i, regionMappingTable.region + RPL_MAPPING_REGION_DATA, rplLoaderContext->regionMappingBase_data); } // load loaderinfo sections for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if (section->sectionSize == 0) continue; if (rplLoaderContext->sectionAddressTable2[i].ptr != nullptr) continue; if ((sectionFlags & 2) == 0) continue; if(sectionType != SHT_RPL_EXPORTS && sectionType != SHT_RPL_IMPORTS && (sectionFlags&5) != 0 ) continue; bool readRaw = false; RPLLoader_LoadSingleSection(rplLoaderContext, i, regionMappingTable.region + RPL_MAPPING_REGION_LOADERINFO, rplLoaderContext->regionMappingBase_loaderInfo); if (sectionType == SHT_RPL_EXPORTS) { uint8* sectionAddress = (uint8*)rplLoaderContext->sectionAddressTable2[i].ptr; if ((sectionFlags & 4) != 0) { rplLoaderContext->exportFCount = *(uint32be*)(sectionAddress + 0); rplLoaderContext->exportFDataPtr = (rplExportTableEntry_t*)(sectionAddress + 8); } else { rplLoaderContext->exportDCount = *(uint32be*)(sectionAddress + 0); rplLoaderContext->exportDDataPtr = (rplExportTableEntry_t*)(sectionAddress + 8); } } } // load text sections uint32 textSectionMappedBase = rplLoaderContext->regionMappingBase_text.GetMPTR() + (uint32)rplLoaderContext->fileInfo.trampolineAdjustment; // leave some space for trampolines before the code section begins for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if( section->sectionSize == 0 ) continue; if (rplLoaderContext->sectionAddressTable2[i].ptr != nullptr) continue; if ((sectionFlags & 2) == 0) continue; if ((sectionFlags & 4) == 0) continue; if( sectionType == SHT_RPL_EXPORTS) continue; if (section->type == 0x8) { cemuLog_force("RPLLoader: Unsupported text section type 0x8"); cemu_assert_debug(false); } RPLLoader_LoadSingleSection(rplLoaderContext, i, regionMappingTable.region + RPL_MAPPING_REGION_TEXT, textSectionMappedBase); } // load temp region sections uint32 tempRegionSize = regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress; uint8* tempRegionPtr; uint32 tempRegionAllocSize = 0; tempRegionPtr = (uint8*)RPLLoader_AllocWorkarea(tempRegionSize, 0x20, &tempRegionAllocSize); rplLoaderContext->tempRegionPtr = tempRegionPtr; rplLoaderContext->tempRegionAllocSize = tempRegionAllocSize; memcpy(tempRegionPtr, rplLoaderContext->RPLRawData.data()+regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress, tempRegionSize); // load temp region sections for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if (section->sectionSize == 0) continue; if (rplLoaderContext->sectionAddressTable2[i].ptr != nullptr) continue; if (sectionType == SHT_RPL_FILEINFO || sectionType == SHT_RPL_CRCS) continue; // calculate offset within temp section uint32 sectionFileOffset = section->fileOffset; uint32 sectionSize = section->sectionSize; cemu_assert_debug(sectionFileOffset >= regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress); cemu_assert_debug((sectionFileOffset + sectionSize) <= regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress); rplLoaderContext->sectionAddressTable2[i].ptr = (tempRegionPtr + (sectionFileOffset - regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress)); uint32 sectionEndAddress = sectionFileOffset + sectionSize; regionMappingTable.region[RPL_MAPPING_REGION_TEMP].calcEndAddress = std::max(regionMappingTable.region[RPL_MAPPING_REGION_TEMP].calcEndAddress, sectionEndAddress); } // todo: Verify calcEndAddress<=endAddress for each region // dump loaded sections /* for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if (section->sectionSize == 0) continue; if (rplLoaderContext->sectionAddressTable2[i].ptr == nullptr) continue; FileStream* fs = FileStream::createFile2(fmt::format("dump/rpl_sections/{}_{:08x}_type{:08x}.bin", i, (uint32)section->virtualAddress, (uint32)sectionType)); fs->writeData(rplLoaderContext->sectionAddressTable2[i].ptr, section->sectionSize); delete fs; } */ return true; } struct RPLFileSymtabEntry { /* +0x0 */ uint32be ukn00; /* +0x4 */ uint32be symbolAddress; /* +0x8 */ uint32be ukn08; /* +0xC */ uint8 info; /* +0xD */ uint8 ukn0D; /* +0xE */ uint16be sectionIndex; }; struct RPLSharedImportTracking { RPLModule* rplLoaderContext; // rpl loader context of module with exports rplSectionEntryNew_t* exportSection; // export section char modulename[RPL_MODULE_NAME_LENGTH]; }; static_assert(sizeof(RPLFileSymtabEntry) == 0x10, "rplSymtabEntry_t has invalid size"); typedef struct { uint64 hash1; uint64 hash2; uint32 address; }mappedFunctionImport_t; std::vector list_mappedFunctionImports = std::vector(); void _calculateMappedImportNameHash(const char* rplName, const char* funcName, uint64* h1Out, uint64* h2Out) { uint64 h1 = 0; uint64 h2 = 0; // rplName while (*rplName) { uint64 v = (uint64)*rplName; h1 += v; h2 ^= v; h1 = _rotl64(h1, 3); h2 = _rotl64(h2, 7); rplName++; } // funcName while (*funcName) { uint64 v = (uint64)*funcName; h1 += v; h2 ^= v; h1 = _rotl64(h1, 3); h2 = _rotl64(h2, 7); funcName++; } *h1Out = h1; *h2Out = h2; } uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(PPCInterpreter_t* hCPU)) { auto it = g_map_callableExports.find(ppcCallableExport); if (it != g_map_callableExports.end()) return it->second; // get HLE function index sint32 functionIndex = PPCInterpreter_registerHLECall(ppcCallableExport); MPTR codeAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(4)); uint32 opcode = (1 << 26) | functionIndex; memory_writeU32Direct(codeAddr, opcode); g_map_callableExports[ppcCallableExport] = codeAddr; return codeAddr; } uint32 rpl_mapHLEImport(RPLModule* rplLoaderContext, const char* rplName, const char* funcName, bool functionMustExist) { // calculate import name hash uint64 mappedImportHash1; uint64 mappedImportHash2; _calculateMappedImportNameHash(rplName, funcName, &mappedImportHash1, &mappedImportHash2); // find already mapped name for (auto& importItr : list_mappedFunctionImports) { if (importItr.hash1 == mappedImportHash1 && importItr.hash2 == mappedImportHash2) { return importItr.address; } } // copy lib file name and cut off .rpl from libName if present char libName[512]; strcpy_s(libName, rplName); for (sint32 i = 0; i < sizeof(libName); i++) { if (libName[i] == '\0') break; if (libName[i] == '.') { libName[i] = '\0'; break; } } // find import in list of known exports sint32 functionIndex = osLib_getFunctionIndex(libName, funcName); if (functionIndex >= 0) { MPTR codeAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(4)); uint32 opcode = (1 << 26) | functionIndex; memory_writeU32Direct(codeAddr, opcode); // register mapped import mappedFunctionImport_t newImport; newImport.hash1 = mappedImportHash1; newImport.hash2 = mappedImportHash2; newImport.address = codeAddr; list_mappedFunctionImports.push_back(newImport); // remember in symbol storage for debugger rplSymbolStorage_store(libName, funcName, codeAddr); return codeAddr; } else { if (functionMustExist == false) return MPTR_NULL; } // create code for unsupported import uint32 codeStart = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(256)); uint32 currentAddress = codeStart; uint32 opcode = (1 << 26) | (0xFFD0); // opcode for HLE: Unsupported import memory_writeU32Direct(codeStart + 0, opcode); memory_writeU32Direct(codeStart + 4, 0x4E800020); currentAddress += 8; // write name of lib/function sint32 libNameLength = std::min(128, (sint32)strlen(libName)); sint32 funcNameLength = std::min(128, (sint32)strlen(funcName)); memcpy(memory_getPointerFromVirtualOffset(currentAddress), libName, libNameLength); currentAddress += libNameLength; memory_writeU8(currentAddress, '.'); currentAddress++; memcpy(memory_getPointerFromVirtualOffset(currentAddress), funcName, funcNameLength); currentAddress += funcNameLength; memory_writeU8(currentAddress, '\0'); currentAddress++; // align address to 4 byte boundary currentAddress = (currentAddress + 3)&~3; // register mapped import mappedFunctionImport_t newImport; newImport.hash1 = mappedImportHash1; newImport.hash2 = mappedImportHash2; newImport.address = codeStart; list_mappedFunctionImports.push_back(newImport); // remember in symbol storage for debugger rplSymbolStorage_store(libName, funcName, codeStart); // return address of code start return codeStart; } MPTR RPLLoader_FindRPLExport(RPLModule* rplLoaderContext, const char* symbolName, bool isData) { if (isData) { cemu_assert_debug(false); // todo - look in DDataPtr } if (rplLoaderContext->exportFDataPtr) { char* exportNameData = (char*)((uint8*)rplLoaderContext->exportFDataPtr - 8); for (uint32 f = 0; f < rplLoaderContext->exportFCount; f++) { char* name = exportNameData + (uint32)rplLoaderContext->exportFDataPtr[f].nameOffset; if (strcmp(name, symbolName) == 0) { return (uint32)rplLoaderContext->exportFDataPtr[f].virtualOffset; } } } return MPTR_NULL; } MPTR _findHLEExport(RPLModule* rplLoaderContext, RPLSharedImportTracking* sharedImportTrackingEntry, char* libname, char* symbolName, bool isData) { if (isData) { // data export MPTR weakExportAddr = osLib_getPointer(libname, symbolName); if (weakExportAddr != 0xFFFFFFFF) return weakExportAddr; cemuLog_logDebug(LogType::Force, "Unsupported data export ({}): {}.{}", rplLoaderContext->moduleName2, libname, symbolName); return MPTR_NULL; } else { // try to find HLE/placeholder export MPTR mappedFunctionAddr = rpl_mapHLEImport(rplLoaderContext, libname, symbolName, true); if (mappedFunctionAddr == MPTR_NULL) cemu_assert_debug(false); return mappedFunctionAddr; } } uint32 RPLLoader_FindModuleExport(RPLModule* rplLoaderContext, bool isData, const char* exportName) { if (isData == false) { // find function export char* exportNameData = (char*)((uint8*)rplLoaderContext->exportFDataPtr - 8); for (uint32 f = 0; f < rplLoaderContext->exportFCount; f++) { char* name = exportNameData + (uint32)rplLoaderContext->exportFDataPtr[f].nameOffset; if (strcmp(name, exportName) == 0) { uint32 exportAddress = rplLoaderContext->exportFDataPtr[f].virtualOffset; return exportAddress; } } } else { // find data export char* exportNameData = (char*)((uint8*)rplLoaderContext->exportDDataPtr - 8); for (uint32 f = 0; f < rplLoaderContext->exportDCount; f++) { char* name = exportNameData + (uint32)rplLoaderContext->exportDDataPtr[f].nameOffset; if (strcmp(name, exportName) == 0) { uint32 exportAddress = rplLoaderContext->exportDDataPtr[f].virtualOffset; return exportAddress; } } } return 0; } bool RPLLoader_FixImportSymbols(RPLModule* rplLoaderContext, sint32 symtabSectionIndex, rplSectionEntryNew_t* symTabSection, std::span sharedImportTracking, uint32 linkMode) { uint32 sectionSize = symTabSection->sectionSize; uint32 symbolEntrySize = symTabSection->ukn24; if (symbolEntrySize == 0) symbolEntrySize = 0x10; cemu_assert(symbolEntrySize == 0x10); cemu_assert((sectionSize % symbolEntrySize) == 0); uint32 symbolCount = sectionSize / symbolEntrySize; cemu_assert(symbolCount >= 2); uint16 sectionCount = rplLoaderContext->rplHeader.sectionTableEntryCount; uint8* symtabData = (uint8*)rplLoaderContext->sectionAddressTable2[symtabSectionIndex].ptr; uint32 strtabSectionIndex = symTabSection->symtabSectionIndex; uint8* strtabData = (uint8*)rplLoaderContext->sectionAddressTable2[strtabSectionIndex].ptr; uint32 strtabSize = rplLoaderContext->sectionTablePtr[strtabSectionIndex].sectionSize; for (uint32 i = 0; i < symbolCount; i++) { RPLFileSymtabEntry* sym = (RPLFileSymtabEntry*)(symtabData + i*symbolEntrySize); uint16 symSectionIndex = sym->sectionIndex; if (symSectionIndex == 0 || symSectionIndex >= sectionCount) continue; void* symbolSectionAddress = rplLoaderContext->sectionAddressTable2[symSectionIndex].ptr; if (symbolSectionAddress == nullptr) { sym->symbolAddress = 0xCD000000 | i; continue; } rplSectionEntryNew_t* symbolSection = rplLoaderContext->sectionTablePtr + symSectionIndex; uint32 symbolOffset = sym->symbolAddress - symbolSection->virtualAddress; if (symSectionIndex >= sharedImportTracking.size()) { cemuLog_force("RPL-Loader: Symbol {} references invalid section", i); } else if (sharedImportTracking[symSectionIndex].rplLoaderContext != nullptr) { if (linkMode == 0) { continue; // ? } if (symbolOffset < 8) { cemu_assert(symbolSectionAddress >= memory_base && symbolSectionAddress <= (memory_base + 0x100000000ULL)); uint32 symbolSectionMPTR = memory_getVirtualOffsetFromPointer(symbolSectionAddress); uint32 symbolRelativeAddress = (uint32)sym->symbolAddress - (uint32)symbolSection->virtualAddress; sym->symbolAddress = (symbolSectionMPTR + symbolRelativeAddress); continue; // ? } if (sharedImportTracking[symSectionIndex].rplLoaderContext == HLE_MODULE_PTR) { // get address uint32 nameOffset = sym->ukn00; char* symbolName = (char*)strtabData + nameOffset; if (nameOffset >= strtabSize) { forceLog_printf("RPLLoader: Symbol %d in section %d has out-of-bounds name offset", i, symSectionIndex); continue; } uint32 exportAddress; if (nameOffset == 0) { cemu_assert_debug(symbolName[0] == '\0'); exportAddress = 0; } else { bool isDataExport = (rplLoaderContext->sectionTablePtr[symSectionIndex].flags & 0x4) == 0; exportAddress = _findHLEExport(rplLoaderContext, sharedImportTracking.data() + symSectionIndex, sharedImportTracking[symSectionIndex].modulename, symbolName, isDataExport); } sym->symbolAddress = exportAddress; } else { RPLModule* ctxExportModule = sharedImportTracking[symSectionIndex].rplLoaderContext; uint32 nameOffset = sym->ukn00; char* symbolName = (char*)strtabData + nameOffset; bool foundExport = false; if ((rplLoaderContext->sectionTablePtr[symSectionIndex].flags & 0x4) != 0) { // find function export char* exportNameData = (char*)((uint8*)ctxExportModule->exportFDataPtr - 8); for (uint32 f = 0; f < ctxExportModule->exportFCount; f++) { char* name = exportNameData + (uint32)ctxExportModule->exportFDataPtr[f].nameOffset; if (strcmp(name, symbolName) == 0) { uint32 exportAddress = ctxExportModule->exportFDataPtr[f].virtualOffset; sym->symbolAddress = exportAddress; foundExport = true; break; } } } else { // find data export char* exportNameData = (char*)((uint8*)ctxExportModule->exportDDataPtr - 8); for (uint32 f = 0; f < ctxExportModule->exportDCount; f++) { char* name = exportNameData + (uint32)ctxExportModule->exportDDataPtr[f].nameOffset; if (strcmp(name, symbolName) == 0) { uint32 exportAddress = ctxExportModule->exportDDataPtr[f].virtualOffset; sym->symbolAddress = exportAddress; foundExport = true; break; } } } if (foundExport == false) { #ifndef PUBLIC_RELEASE if (nameOffset > 0) { forceLogDebug_printf("export not found - force lookup in function exports"); // workaround - force look up export in function exports char* exportNameData = (char*)((uint8*)ctxExportModule->exportFDataPtr - 8); for (uint32 f = 0; f < ctxExportModule->exportFCount; f++) { char* name = exportNameData + (uint32)ctxExportModule->exportFDataPtr[f].nameOffset; if (strcmp(name, symbolName) == 0) { uint32 exportAddress = ctxExportModule->exportFDataPtr[f].virtualOffset; sym->symbolAddress = exportAddress; foundExport = true; break; } } } #endif continue; } } } else { uint32 symbolType = sym->info & 0xF; if (symbolType == 6) continue; if (((uint32)symbolSection->type != SHT_RPL_IMPORTS && linkMode != 2) || ((uint32)symbolSection->type == SHT_RPL_IMPORTS && linkMode != 1 && linkMode != 2) ) { // update virtual address to match actual mapped address cemu_assert(symbolSectionAddress >= memory_base && symbolSectionAddress <= (memory_base + 0x100000000ULL)); uint32 symbolSectionMPTR = memory_getVirtualOffsetFromPointer(symbolSectionAddress); uint32 symbolRelativeAddress = (uint32)sym->symbolAddress - (uint32)symbolSection->virtualAddress; sym->symbolAddress = (symbolSectionMPTR + symbolRelativeAddress); } } } return true; } bool RPLLoader_ApplySingleReloc(RPLModule* rplLoaderContext, uint32 uknR3, uint8* relocTargetSectionAddress, uint32 relocType, bool isSymbolBinding2, uint32 relocOffset, uint32 relocAddend, uint32 symbolAddress, sint16 tlsModuleIndex) { MPTR relocTargetSectionMPTR = memory_getVirtualOffsetFromPointer(relocTargetSectionAddress); MPTR relocAddrMPTR = relocTargetSectionMPTR + relocOffset; uint8* relocAddr = memory_getPointerFromVirtualOffset(relocAddrMPTR); if (relocType == RPL_RELOC_HA16) { uint32 relocDestAddr = symbolAddress + relocAddend; uint32 p = (relocDestAddr >> 16); p += (relocDestAddr >> 15) & 1; *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == RPL_RELOC_LO16) { uint32 relocDestAddr = symbolAddress + relocAddend; uint32 p = relocDestAddr; *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == RPL_RELOC_HI16) { uint32 relocDestAddr = symbolAddress + relocAddend; uint32 p = relocDestAddr>>16; *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == RPL_RELOC_REL24) { // todo - effect of isSymbolBinding2? uint32 opc = *(uint32be*)relocAddr; uint32 relocDestAddr = symbolAddress + relocAddend; uint32 jumpDistance = relocDestAddr - memory_getVirtualOffsetFromPointer(relocAddr); if ((jumpDistance>>25) != 0 && (jumpDistance >> 25) != 0x7F) { // can't reach with 24bit jump, use trampoline + absolute branch MPTR trampolineAddr = _generateTrampolineFarJump(rplLoaderContext, relocDestAddr); // make absolute branch cemu_assert_debug((opc >> 26) == 18); // should be B/BL instruction opc &= ~0x03fffffc; opc |= (trampolineAddr & 0x3FFFFFC); opc |= (1 << 1); // absolute jump *(uint32be*)relocAddr = opc; } else { // within range, update jump opcode if ((jumpDistance & 3) != 0) cemuLog_force("RPL-Loader: Encountered unaligned RPL_RELOC_REL24"); opc &= ~0x03fffffc; opc |= (jumpDistance &0x03fffffc); *(uint32be*)relocAddr = opc; } } else if (relocType == RPL_RELOC_REL14) { // seen in Your Shape: Fitness Evolved // todo - effect of isSymbolBinding2? uint32 opc = *(uint32be*)relocAddr; uint32 relocDestAddr = symbolAddress + relocAddend; uint32 jumpDistance = relocDestAddr - memory_getVirtualOffsetFromPointer(relocAddr); if ((jumpDistance & ~0x7fff) != 0xFFFF8000 && (jumpDistance & ~0x7fff) != 0x00000000) { cemu_assert_debug(false); } else { // within range, update jump opcode if ((jumpDistance & 3) != 0) cemuLog_force("RPL-Loader: Encountered unaligned RPL_RELOC_REL14"); opc &= ~0xfffc; opc |= (jumpDistance & 0xfffc); *(uint32be*)relocAddr = opc; } } else if (relocType == RPL_RELOC_ADDR32) { uint32 relocDestAddr = symbolAddress + relocAddend; uint32 p = relocDestAddr; *(uint32be*)(relocAddr) = (uint32)p; } else if (relocType == R_PPC_DTPMOD32) { // patch tls_index.moduleIndex *(uint32be*)(relocAddr) = (uint32)(sint32)tlsModuleIndex; } else if (relocType == R_PPC_DTPREL32) { // patch tls_index.size *(uint32be*)(relocAddr) = (uint32)(sint32)(symbolAddress + relocAddend); } else if (relocType == R_PPC_REL16_HA) { // used by WUT uint32 relAddr = (symbolAddress + relocAddend) - relocAddrMPTR; uint32 p = (relAddr >> 16); p += (relAddr >> 15) & 1; *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == R_PPC_REL16_HI) { // used by WUT uint32 relAddr = (symbolAddress + relocAddend) - relocAddrMPTR; uint32 p = (relAddr >> 16); *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == R_PPC_REL16_LO) { // used by WUT uint32 relAddr = (symbolAddress + relocAddend) - relocAddrMPTR; uint32 p = (relAddr & 0xFFFF); *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == 0x6D) // SDATA reloc { uint32 opc = *(uint32be*)relocAddr; uint32 registerIndex = (opc >> 16) & 0x1F; uint32 destination = (symbolAddress + relocAddend);; if (registerIndex == 2) { uint32 offset = destination - rplLoader_sdata2Addr; uint32 newOpc = (opc & 0xffe00000) | (offset & 0xffff) | (registerIndex << 16); *(uint32be*)relocAddr = newOpc; } else if (registerIndex == 13) { uint32 offset = destination - rplLoader_sdataAddr; uint32 newOpc = (opc & 0xffe00000) | (offset & 0xffff) | (registerIndex << 16); *(uint32be*)relocAddr = newOpc; } else { cemuLog_force("RPLLoader: sdata reloc uses register other than r2/r13"); cemu_assert(false); } } else if (relocType == 0xFB) { // relative offset - high uint32 relocDestAddr = symbolAddress + relocAddend; uint32 relativeOffset = relocDestAddr - relocAddrMPTR; uint16 prevValue = *(uint16be*)relocAddr; uint32 newImm = ((relativeOffset >> 16) + ((relativeOffset >> 15) & 0x1)); newImm &= 0xFFFF; *(uint16be*)relocAddr = newImm; if (symbolAddress != 0) { cemu_assert_debug((uint32)prevValue == newImm); } } else if (relocType == 0xFD) { // relative offset - low uint32 relocDestAddr = symbolAddress + relocAddend; uint32 relativeOffset = relocDestAddr - relocAddrMPTR; uint16 prevValue = *(uint16be*)relocAddr; uint32 newImm = relativeOffset; newImm &= 0xFFFF; *(uint16be*)relocAddr = newImm; if (symbolAddress != 0) { cemu_assert_debug((uint32)prevValue == newImm); } } else { cemuLog_force("RPLLoader: Unsupported reloc type 0x{:02x}", relocType); cemu_assert_debug(false); // unknown reloc type } return true; } bool RPLLoader_ApplyRelocs(RPLModule* rplLoaderContext, sint32 relaSectionIndex, rplSectionEntryNew_t* section, uint32 linkMode) { uint32 relocTargetSectionIndex = section->relocTargetSectionIndex; if (relocTargetSectionIndex >= (uint32)rplLoaderContext->rplHeader.sectionTableEntryCount) assert_dbg(); uint32 symtabSectionIndex = section->symtabSectionIndex; uint8* relocTargetSectionAddress = (uint8*)(rplLoaderContext->sectionAddressTable2[relocTargetSectionIndex].ptr); cemu_assert(relocTargetSectionAddress); // get symtab info rplSectionEntryNew_t* symtabSection = rplLoaderContext->sectionTablePtr + symtabSectionIndex; uint32 symtabSectionSize = symtabSection->sectionSize; uint32 symbolEntrySize = symtabSection->ukn24; if (symbolEntrySize == 0) symbolEntrySize = 0x10; cemu_assert(symbolEntrySize == 0x10); cemu_assert((symtabSectionSize % symbolEntrySize) == 0); uint32 symbolCount = symtabSectionSize / symbolEntrySize; cemu_assert(symbolCount >= 2); uint8* symtabData = (uint8*)rplLoaderContext->sectionAddressTable2[symtabSectionIndex].ptr; // decompress reloc section if needed uint8* relocData; uint32 relocSize; if ((uint32)(section->flags) & SHF_RPL_COMPRESSED) { uint8* relocRawData = (uint8*)rplLoaderContext->sectionAddressTable2[relaSectionIndex].ptr; uint32 relocUncompressedSize = *(uint32be*)relocRawData; relocData = (uint8*)malloc(relocUncompressedSize); relocSize = relocUncompressedSize; // decompress int ret; z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; ret = inflateInit(&strm); if (ret == Z_OK) { strm.avail_in = (uint32)section->sectionSize - 4; strm.next_in = relocRawData + 4; strm.avail_out = relocUncompressedSize; strm.next_out = relocData; ret = inflate(&strm, Z_FULL_FLUSH); cemu_assert_debug(ret == Z_OK || ret == Z_STREAM_END); cemu_assert_debug(strm.avail_in == 0 && strm.avail_out == 0); inflateEnd(&strm); } } else { relocData = (uint8*)rplLoaderContext->sectionAddressTable2[relaSectionIndex].ptr; relocSize = section->sectionSize; } // check CRC uint32 calcCRC = crc32_calc(0, relocData, relocSize); uint32 crc = rplLoaderContext->GetSectionCRC(relaSectionIndex); if (calcCRC != crc) { forceLog_printf("RPLLoader %s - Relocation section %d has CRC mismatch - Calc: %08x Actual: %08x", rplLoaderContext->moduleName2.c_str(), relaSectionIndex, calcCRC, crc); } // process relocations sint32 relocCount = relocSize / sizeof(rplRelocNew_t); rplRelocNew_t* reloc = (rplRelocNew_t*)relocData; for (sint32 i = 0; i < relocCount; i++) { uint32 relocType = (uint32)reloc->symbolIndexAndType & 0xFF; uint32 relocSymbolIndex = (uint32)reloc->symbolIndexAndType >> 8; if (relocType == 0) { // next reloc++; continue; } if (relocSymbolIndex >= symbolCount) { forceLogDebug_printf("reloc with symbol index out of range 0x%04x", (uint32)relocSymbolIndex); reloc++; continue; } // get symbol RPLFileSymtabEntry* sym = (RPLFileSymtabEntry*)(symtabData + symbolEntrySize*relocSymbolIndex); if ((uint32)sym->sectionIndex >= (uint32)rplLoaderContext->rplHeader.sectionTableEntryCount) { forceLogDebug_printf("reloc with sectionIndex out of range 0x%04x", (uint32)sym->sectionIndex); reloc++; continue; } // exclude symbols that arent ready yet if (linkMode == 0) { if ((uint32)rplLoaderContext->sectionTablePtr[(uint32)sym->sectionIndex].type == SHT_RPL_IMPORTS) { reloc++; continue; } } uint32 symbolAddress = sym->symbolAddress; uint8 symbolType = (sym->info >> 0) & 0xF; uint8 symbolBinding = (sym->info >> 4) & 0xF; if ((symbolAddress&0xFF000000) == 0xCD000000) { cemu_assert_unimplemented(); // next reloc++; continue; } sint16 tlsModuleIndex = -1; if (relocType == R_PPC_DTPMOD32 || relocType == R_PPC_DTPREL32) { // TLS-relocation if (symbolType != 6) assert_dbg(); // not a TLS symbol if (rplLoaderContext->fileInfo.tlsModuleIndex == -1) { cemuLog_force("RPLLoader: TLS relocs applied to non-TLS module"); cemu_assert_debug(false); // module not a TLS-module } tlsModuleIndex = rplLoaderContext->fileInfo.tlsModuleIndex; } uint32 relocOffset = (uint32)reloc->relocOffset - (uint32)rplLoaderContext->sectionTablePtr[relocTargetSectionIndex].virtualAddress; RPLLoader_ApplySingleReloc(rplLoaderContext, 0, relocTargetSectionAddress, relocType, symbolBinding == 2, relocOffset, reloc->relocAddend, symbolAddress, tlsModuleIndex); // next reloc reloc++; } if ((uint32)(section->flags) & SHF_RPL_COMPRESSED) free(relocData); return true; } bool RPLLoader_HandleRelocs(RPLModule* rplLoaderContext, std::span sharedImportTracking, uint32 linkMode) { // resolve relocs for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; if( sectionType != SHT_SYMTAB ) continue; RPLLoader_FixImportSymbols(rplLoaderContext, i, section, sharedImportTracking, linkMode); } // apply relocs again after we have fixed the import section for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; if (sectionType != SHT_RELA) continue; RPLLoader_ApplyRelocs(rplLoaderContext, i, section, linkMode); } return true; } void _RPLLoader_ExtractModuleNameFromPath(char* output, const char* input) { // scan to last '/' sint32 inputLen = (sint32)strlen(input); cemu_assert(inputLen > 0); sint32 startIndex = inputLen - 1; while (startIndex > 0) { if (input[startIndex] == '/') { startIndex++; break; } startIndex--; } // cut off after '.' sint32 endIndex = startIndex; while (endIndex <= inputLen) { if (input[endIndex] == '.') break; endIndex++; } sint32 nameLen = endIndex - startIndex; cemu_assert(nameLen != 0); nameLen = std::min(nameLen, RPL_MODULE_NAME_LENGTH-1); memcpy(output, input + startIndex, nameLen); output[nameLen] = '\0'; // convert to lower case for (sint32 i = 0; i < nameLen; i++) { output[i] = _ansiToLower(output[i]); } } void RPLLoader_InitState() { cemu_assert_debug(!rplLoaderHeap_lowerAreaCodeMem2.hasAllocations()); cemu_assert_debug(!rplLoaderHeap_codeArea2.hasAllocations()); cemu_assert_debug(!rplLoaderHeap_workarea.hasAllocations()); rplLoaderHeap_lowerAreaCodeMem2.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_CODE_TRAMPOLINE_AREA_ADDR)); rplLoaderHeap_codeArea2.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_CODEAREA_ADDR)); rplLoaderHeap_workarea.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_RPLLOADER_AREA_ADDR)); g_heapTrampolineArea.setBaseAllocator(&rplLoaderHeap_lowerAreaCodeMem2); } void RPLLoader_ResetState() { // unload all RPL modules while (rplModuleCount > 0) RPLLoader_UnloadModule(rplModuleList[0]); // clear dependency list cemu_assert_debug(false); // unload all remaining symbols rplSymbolStorage_unloadAll(); // free all code imports g_heapTrampolineArea.releaseAll(); list_mappedFunctionImports.clear(); g_map_callableExports.clear(); rplLoader_applicationHasMemoryControl = false; rplLoader_maxCodeAddress = 0; rpl3_currentDataAllocatorAddr = 0x10000000; cemu_assert_debug(rplDependencyList.empty()); rplDependencyList.clear(); _currentTLSModuleIndex = 1; rplLoader_sdataAddr = MPTR_NULL; rplLoader_sdata2Addr = MPTR_NULL; } void RPLLoader_BeginCemuhookCRC(RPLModule* rpl) { // calculate some values required for CRC sint32 sectionSymTableIndex = -1; sint32 sectionStrTableIndex = -1; for (sint32 i = 0; i < rpl->rplHeader.sectionTableEntryCount; i++) { if (rpl->sectionTablePtr[i].type == SHT_SYMTAB) sectionSymTableIndex = i; if (rpl->sectionTablePtr[i].type == SHT_STRTAB && i != rpl->rplHeader.nameSectionIndex && sectionStrTableIndex == -1) sectionStrTableIndex = i; } // init patches CRC rpl->patchCRC = 0; static const uint8 rplMagic[4] = { 0x7F, 'R', 'P', 'X' }; rpl->patchCRC = crc32_calc(rpl->patchCRC, rplMagic, sizeof(rplMagic)); sint32 sectionCount = rpl->rplHeader.sectionTableEntryCount; rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionCount, sizeof(sectionCount)); rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionSymTableIndex, sizeof(sectionSymTableIndex)); rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionStrTableIndex, sizeof(sectionStrTableIndex)); sint32 sectionSectNameTableIndex = rpl->rplHeader.nameSectionIndex; rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionSectNameTableIndex, sizeof(sectionSectNameTableIndex)); // sections for (sint32 i = 0; i < rpl->rplHeader.sectionTableEntryCount; i++) { auto sect = rpl->sectionTablePtr + i; uint32 nameOffset = sect->nameOffset; uint32 shType = sect->type; uint32 flags = sect->flags; uint32 virtualAddress = sect->virtualAddress; uint32 alignment = sect->alignment; uint32 sectionFileOffset = sect->fileOffset; uint32 sectionCompressedSize = sect->sectionSize; uint32 rawSize = 0; bool memoryAllocated = false; void* rawData = nullptr; if (shType == SHT_NOBITS) { rawData = NULL; rawSize = sectionCompressedSize; } else if ((flags&SHF_RPL_COMPRESSED) != 0) { uint32 decompressedSize = _swapEndianU32(*(uint32*)(rpl->RPLRawData.data() + sectionFileOffset)); rawSize = decompressedSize; if (rawSize >= 1024*1024*1024) { forceLogDebug_printf("RPLLoader-CRC: Cannot load section %d which is too large (%u bytes)", i, decompressedSize); cemu_assert_debug(false); continue; } rawData = (uint8*)malloc(decompressedSize); if (rawData == nullptr) { forceLogDebug_printf("RPLLoader-CRC: Failed to allocate memory for uncompressed section %d (%u bytes)", i, decompressedSize); cemu_assert_debug(false); continue; } memoryAllocated = true; // decompress z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; inflateInit(&strm); strm.avail_in = sectionCompressedSize - 4; strm.next_in = rpl->RPLRawData.data() + (uint32)sectionFileOffset + 4; strm.avail_out = decompressedSize; strm.next_out = (Bytef*)rawData; auto ret = inflate(&strm, Z_FULL_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END || strm.avail_in != 0 || strm.avail_out != 0) { forceLogDebug_printf("RPLLoader-CRC: Unable to decompress section %d", i); forceLogDebug_printf("zRet %d availIn %d availOut %d", ret, (sint32)strm.avail_in, (sint32)strm.avail_out); cemu_assert_debug(false); free(rawData); inflateEnd(&strm); continue; } inflateEnd(&strm); } else { rawSize = sectionCompressedSize; rawData = rpl->RPLRawData.data() + sectionFileOffset; } rpl->patchCRC = crc32_calc(rpl->patchCRC, &nameOffset, sizeof(nameOffset)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &shType, sizeof(shType)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &flags, sizeof(flags)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &virtualAddress, sizeof(virtualAddress)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &rawSize, sizeof(rawSize)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &alignment, sizeof(alignment)); if (rawData != nullptr && rawSize > 0) { rpl->patchCRC = crc32_calc(rpl->patchCRC, rawData, rawSize); } if (memoryAllocated && rawData) free(rawData); } } void RPLLoader_incrementModuleDependencyRefs(RPLModule* rpl) { for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) { if (rpl->sectionTablePtr[i].type != (uint32be)SHT_RPL_IMPORTS) continue; char* libName = (char*)((uint8*)rpl->sectionAddressTable2[i].ptr + 8); RPLLoader_AddDependency(libName); } } void RPLLoader_decrementModuleDependencyRefs(RPLModule* rpl) { for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) { if (rpl->sectionTablePtr[i].type != (uint32be)SHT_RPL_IMPORTS) continue; char* libName = (char*)((uint8*)rpl->sectionAddressTable2[i].ptr + 8); RPLLoader_RemoveDependency(libName); } } void RPLLoader_UpdateEntrypoint(RPLModule* rpl) { uint32 virtualEntrypoint = rpl->rplHeader.entrypoint; uint32 entrypoint = 0xFFFFFFFF; for (sint32 i = 0; i < (sint32)rpl->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rpl->sectionTablePtr + i; uint32 sectionStartAddr = (uint32)section->virtualAddress; uint32 sectionEndAddr = (uint32)section->virtualAddress + (uint32)section->sectionSize; if (virtualEntrypoint >= sectionStartAddr && virtualEntrypoint < sectionEndAddr) { cemu_assert_debug(entrypoint == 0xFFFFFFFF); entrypoint = (virtualEntrypoint - sectionStartAddr + memory_getVirtualOffsetFromPointer(rpl->sectionAddressTable2[i].ptr)); } } cemu_assert(entrypoint != 0xFFFFFFFF); rpl->entrypoint = entrypoint; } void RPLLoader_InitModuleAllocator(RPLModule* rpl) { if (!rplLoader_applicationHasMemoryControl) { rpl->funcAlloc = 0; rpl->funcFree = 0; return; } coreinit::OSDynLoad_GetAllocator(&rpl->funcAlloc, &rpl->funcFree); } // map rpl into memory, but do not resolve relocs and imports yet RPLModule* rpl_loadFromMem(uint8* rplData, sint32 size, char* name) { char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); RPLModule* rpl = nullptr; if (RPLLoader_ProcessHeaders({ moduleName }, rplData, size, &rpl) == false) { delete rpl; return nullptr; } RPLLoader_InitModuleAllocator(rpl); RPLLoader_BeginCemuhookCRC(rpl); if (RPLLoader_LoadSections(0, rpl) == false) { delete rpl; return nullptr; } forceLogDebug_printf("Load %s Code-Offset: -0x%x", name, rpl->regionMappingBase_text.GetMPTR() - 0x02000000); // sdata (r2/r13) uint32 sdataBaseAddress = rpl->fileInfo.sdataBase1; // base + 0x8000 uint32 sdataBaseAddress2 = rpl->fileInfo.sdataBase2; // base + 0x8000 for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) { if(rpl->sectionTablePtr[i].sectionSize == 0) continue; uint32 sectionFlags = rpl->sectionTablePtr[i].flags; uint32 sectionVirtualAddress = rpl->sectionTablePtr[i].virtualAddress; uint32 sectionSize = rpl->sectionTablePtr[i].sectionSize; if( (sectionFlags&4) != 0 ) continue; if(sdataBaseAddress == 0x00008000 && sdataBaseAddress2 == 0x00008000) continue; // sdata not used (this workaround is needed for Wii U Party to boot) // sdata 1 if ((sdataBaseAddress - 0x8000) >= (sectionVirtualAddress) && (sdataBaseAddress - 0x8000) <= (sectionVirtualAddress + sectionSize)) { uint32 rplLoader_sdataAddrNew = memory_getVirtualOffsetFromPointer(rpl->sectionAddressTable2[i].ptr) + (sdataBaseAddress - sectionVirtualAddress); rplLoader_sdataAddr = rplLoader_sdataAddrNew; } // sdata 2 if ((sdataBaseAddress2 - 0x8000) >= (sectionVirtualAddress) && (sdataBaseAddress2 - 0x8000) <= (sectionVirtualAddress + sectionSize)) { rplLoader_sdata2Addr = memory_getVirtualOffsetFromPointer(rpl->sectionAddressTable2[i].ptr) + (sdataBaseAddress2 - sectionVirtualAddress); } } // cancel if error if (rpl->hasError) { forceLog_printf("RPLLoader: Unable to load RPL due to errors"); delete rpl; return nullptr; } // find TLS section uint32 tlsStartAddress = 0xFFFFFFFF; uint32 tlsEndAddress = 0; for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) { if ( ((uint32)rpl->sectionTablePtr[i].flags & SHF_TLS) == 0 ) continue; uint32 sectionVirtualAddress = rpl->sectionTablePtr[i].virtualAddress; uint32 sectionSize = rpl->sectionTablePtr[i].sectionSize; tlsStartAddress = std::min(tlsStartAddress, sectionVirtualAddress); tlsEndAddress = std::max(tlsEndAddress, sectionVirtualAddress+sectionSize); } if (tlsStartAddress == 0xFFFFFFFF) { tlsStartAddress = 0; tlsEndAddress = 0; } rpl->tlsStartAddress = tlsStartAddress; rpl->tlsEndAddress = tlsEndAddress; // add to module list cemu_assert(rplModuleCount < 256); rplModuleList[rplModuleCount] = rpl; rplModuleCount++; // track dependencies RPLLoader_incrementModuleDependencyRefs(rpl); // update entrypoint RPLLoader_UpdateEntrypoint(rpl); return rpl; } void RPLLoader_flushMemory(RPLModule* rpl) { // invalidate recompiler cache PPCRecompiler_invalidateRange(rpl->regionMappingBase_text.GetMPTR(), rpl->regionMappingBase_text.GetMPTR() + rpl->regionSize_text); rpl->heapTrampolineArea.forEachBlock([](void* mem, uint32 size) { MEMPTR memVirtual = mem; PPCRecompiler_invalidateRange(memVirtual.GetMPTR(), memVirtual.GetMPTR() + size); }); } // resolve relocs and imports of all modules. Or resolve exports void RPLLoader_LinkSingleModule(RPLModule* rplLoaderContext, bool resolveOnlyExports) { // setup shared import tracking std::vector sharedImportTracking; sharedImportTracking.resize(rplLoaderContext->rplHeader.sectionTableEntryCount - 2); memset(sharedImportTracking.data(), 0, sizeof(RPLSharedImportTracking) * sharedImportTracking.size()); for (uint32 i = 0; i < (uint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { if( rplLoaderContext->sectionTablePtr[i].type != (uint32be)SHT_RPL_IMPORTS ) continue; char* libName = (char*)((uint8*)rplLoaderContext->sectionAddressTable2[i].ptr + 8); // make module name char _importModuleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(_importModuleName, libName); // find in loaded module list std::string importModuleName{_importModuleName}; bool foundModule = false; for (sint32 f = 0; f < rplModuleCount; f++) { if (boost::iequals(rplModuleList[f]->moduleName2, importModuleName)) { sharedImportTracking[i].rplLoaderContext = rplModuleList[f]; memset(sharedImportTracking[i].modulename, 0, sizeof(sharedImportTracking[i].modulename)); strcpy_s(sharedImportTracking[i].modulename, importModuleName.c_str()); foundModule = true; break; } } if( foundModule ) continue; // if not found, we assume it's a HLE lib sharedImportTracking[i].rplLoaderContext = HLE_MODULE_PTR; sharedImportTracking[i].exportSection = nullptr; strcpy_s(sharedImportTracking[i].modulename, libName); } if (resolveOnlyExports) RPLLoader_HandleRelocs(rplLoaderContext, sharedImportTracking, 2); else RPLLoader_HandleRelocs(rplLoaderContext, sharedImportTracking, 0); RPLLoader_flushMemory(rplLoaderContext); } void RPLLoader_LoadSectionDebugSymbols(RPLModule* rplLoaderContext, rplSectionEntryNew_t* section, int symtabSectionIndex) { uint32 sectionSize = section->sectionSize; uint32 symbolEntrySize = section->ukn24; if (symbolEntrySize == 0) symbolEntrySize = 0x10; cemu_assert(symbolEntrySize == 0x10); cemu_assert((sectionSize % symbolEntrySize) == 0); uint32 symbolCount = sectionSize / symbolEntrySize; cemu_assert(symbolCount >= 2); uint16 sectionCount = rplLoaderContext->rplHeader.sectionTableEntryCount; uint8* symtabData = (uint8*)rplLoaderContext->sectionAddressTable2[symtabSectionIndex].ptr; uint32 strtabSectionIndex = section->symtabSectionIndex; uint8* strtabData = (uint8*)rplLoaderContext->sectionAddressTable2[strtabSectionIndex].ptr; for (uint32 i = 0; i < symbolCount; i++) { RPLFileSymtabEntry* sym = (RPLFileSymtabEntry*)(symtabData + i * symbolEntrySize); uint16 symSectionIndex = sym->sectionIndex; if (symSectionIndex == 0 || symSectionIndex >= sectionCount) continue; void* symbolSectionAddress = rplLoaderContext->sectionAddressTable2[symSectionIndex].ptr; if (symbolSectionAddress == nullptr) continue; rplSectionEntryNew_t* symbolSection = rplLoaderContext->sectionTablePtr + symSectionIndex; if(symbolSection->type == SHT_RPL_EXPORTS || symbolSection->type == SHT_RPL_IMPORTS) continue; // exports and imports are handled separately uint32 symbolOffset = sym->symbolAddress - symbolSection->virtualAddress; uint32 nameOffset = sym->ukn00; if (nameOffset > 0) { char* symbolName = (char*)strtabData + nameOffset; if (sym->info == 0x12) { rplSymbolStorage_store(rplLoaderContext->moduleName2.c_str(), symbolName, sym->symbolAddress); } } } } void RPLLoader_LoadDebugSymbols(RPLModule* rplLoaderContext) { for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; if (sectionType != SHT_SYMTAB) continue; RPLLoader_LoadSectionDebugSymbols(rplLoaderContext, section, i); } } void RPLLoader_UnloadModule(RPLModule* rpl) { /* A note: Mario Party 10's mg0480.rpl (minigame Spike Ball Scramble) has a bug where it keeps running code (function 0x02086BCC for example) after RPL unload It seems to rely on the RPL loader not zeroing released memory */ // decrease reference counters of all dependencies RPLLoader_decrementModuleDependencyRefs(rpl); // save module config for this module in the debugger debuggerWindow_notifyModuleUnloaded(rpl); // release memory rplLoaderHeap_codeArea2.free(rpl->regionMappingBase_text.GetPtr()); rpl->regionMappingBase_text = nullptr; // for some reason freeing the data allocations causes a crash in MP10 on boot //RPLLoader_FreeData(rpl, MEMPTR(rpl->regionMappingBase_data).GetPtr()); //rpl->regionMappingBase_data = 0; //RPLLoader_FreeData(rpl, MEMPTR(rpl->regionMappingBase_loaderInfo).GetPtr()); //rpl->regionMappingBase_loaderInfo = 0; rpl->heapTrampolineArea.releaseAll(); // todo - remove from rplSymbolStorage_store if (rpl->sectionTablePtr) { free(rpl->sectionTablePtr); rpl->sectionTablePtr = nullptr; } // unload temp region if (rpl->tempRegionPtr) { RPLLoader_FreeWorkarea(rpl->tempRegionPtr); rpl->tempRegionPtr = nullptr; } // remove from rpl module list for (sint32 i = 0; i < rplModuleCount; i++) { if (rplModuleList[i] == rpl) { rplModuleList[i] = rplModuleList[rplModuleCount-1]; rplModuleCount--; break; } } delete rpl; } void RPLLoader_FixModuleTLSIndex(RPLModule* rplLoaderContext) { sint16 tlsModuleIndex = -1; for (auto& dep : rplDependencyList) { if (boost::iequals(rplLoaderContext->moduleName2, dep->modulename)) { tlsModuleIndex = dep->tlsModuleIndex; break; } } cemu_assert(tlsModuleIndex != -1); rplLoaderContext->fileInfo.tlsModuleIndex = tlsModuleIndex; } void RPLLoader_Link() { // calculate TLS index for (sint32 i = 0; i < rplModuleCount; i++) { if (rplModuleList[i]->isLinked) continue; RPLLoader_FixModuleTLSIndex(rplModuleList[i]); } // resolve relocs for (sint32 i = 0; i < rplModuleCount; i++) { if(rplModuleList[i]->isLinked) continue; RPLLoader_LinkSingleModule(rplModuleList[i], false); } // resolve imports and load debug symbols for (sint32 i = 0; i < rplModuleCount; i++) { if (rplModuleList[i]->isLinked) continue; RPLLoader_LinkSingleModule(rplModuleList[i], true); RPLLoader_LoadDebugSymbols(rplModuleList[i]); rplModuleList[i]->isLinked = true; // mark as linked GraphicPack2::NotifyModuleLoaded(rplModuleList[i]); debuggerWindow_notifyModuleLoaded(rplModuleList[i]); } } uint32 RPLLoader_GetModuleEntrypoint(RPLModule* rplLoaderContext) { return rplLoaderContext->entrypoint; } uint32 rplLoader_currentHandleCounter = 0x00001000; sint16 rplLoader_currentTlsModuleIndex = 0x0001; // increment reference counter for module void RPLLoader_AddDependency(const char* name) { cemu_assert(name[0] != '\0'); // if name includes a path, cut it off const char* namePtr = name + strlen(name) - 1; while (namePtr > name) { if (*namePtr == '/') { namePtr++; break; } namePtr--; } name = namePtr; // get module name from path char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); // check if dependency already exists for (auto& dep : rplDependencyList) { if (strcmp(moduleName, dep->modulename) == 0) { // entry already exists, increment reference counter dep->referenceCount++; return; } } // add new entry rplDependency_t* newDependency = new rplDependency_t(); strcpy(newDependency->modulename, moduleName); newDependency->referenceCount = 1; newDependency->coreinitHandle = rplLoader_currentHandleCounter; newDependency->tlsModuleIndex = rplLoader_currentTlsModuleIndex; rplLoader_currentTlsModuleIndex++; rplLoader_currentHandleCounter++; if (rplLoader_currentTlsModuleIndex == 0x7FFF) cemuLog_force("RPLLoader: Exhausted TLS module indices pool"); // convert name to path/filename if it isn't already one if (strstr(name, ".")) { strcpy_s(newDependency->filepath, name); } else { strcpy_s(newDependency->filepath, name); strcat_s(newDependency->filepath, ".rpl"); } newDependency->filepath[RPL_MODULE_PATH_LENGTH - 1] = '\0'; rplDependencyList.push_back(newDependency); } // decrement reference counter for dependency by module path void RPLLoader_RemoveDependency(const char* name) { cemu_assert(*name != '\0'); // if name includes a path, cut it off const char* namePtr = name + strlen(name) - 1; while (namePtr > name) { if (*namePtr == '/') { namePtr++; break; } namePtr--; } name = namePtr; // get module name from path char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); // find dependency and decrement ref count for (auto& dep : rplDependencyList) { if (strcmp(moduleName, dep->modulename) == 0) { dep->referenceCount--; return; } } } // decrement reference counter for dependency by module handle void RPLLoader_RemoveDependency(uint32 handle) { for (auto& dep : rplDependencyList) { if (dep->coreinitHandle == handle) { cemu_assert_debug(dep->referenceCount != 0); if(dep->referenceCount > 0) dep->referenceCount--; return; } } } uint32 RPLLoader_GetHandleByModuleName(const char* name) { // get module name from path char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); // search for existing dependency for (auto& dep : rplDependencyList) { if (strcmp(moduleName, dep->modulename) == 0) { return dep->coreinitHandle; } } return RPL_INVALID_HANDLE; } uint32 RPLLoader_GetMaxTLSModuleIndex() { return rplLoader_currentTlsModuleIndex - 1; } bool RPLLoader_GetTLSDataByTLSIndex(sint16 tlsModuleIndex, uint8** tlsData, sint32* tlsSize) { RPLModule* rplLoaderContext = nullptr; for (auto& dep : rplDependencyList) { if (dep->tlsModuleIndex == tlsModuleIndex) { rplLoaderContext = dep->rplLoaderContext; break; } } if (rplLoaderContext == nullptr) return false; cemu_assert(rplLoaderContext->tlsStartAddress != 0); uint32 tlsDataSize = rplLoaderContext->tlsEndAddress - rplLoaderContext->tlsStartAddress; cemu_assert_debug(tlsDataSize < 0x10000); // check for suspiciously large TLS area *tlsData = (uint8*)memory_getPointerFromVirtualOffset(rplLoaderContext->tlsStartAddress); *tlsSize = tlsDataSize; return true; } bool RPLLoader_LoadFromVirtualPath(rplDependency_t* dependency, char* filePath) { uint32 rplSize = 0; uint8* rplData = fsc_extractFile(filePath, &rplSize); if (rplData) { forceLogDebug_printf("Loading: %s", filePath); dependency->rplLoaderContext = rpl_loadFromMem(rplData, rplSize, filePath); free(rplData); return true; } return false; } void RPLLoader_LoadDependency(rplDependency_t* dependency) { dependency->loadAttempted = true; // check if module is already loaded for (sint32 i = 0; i < rplModuleCount; i++) { if(!boost::iequals(rplModuleList[i]->moduleName2, dependency->modulename)) continue; // already loaded dependency->rplLoaderContext = rplModuleList[i]; return; } // attempt to load rpl from various locations char filePath[RPL_MODULE_PATH_LENGTH]; // check if path is absolute if (dependency->filepath[0] == '/') { strcpy_s(filePath, dependency->filepath); RPLLoader_LoadFromVirtualPath(dependency, filePath); return; } // attempt to load rpl from internal folder strcpy_s(filePath, "/internal/current_title/code/"); strcat_s(filePath, dependency->filepath); // except if it is blacklisted bool isBlacklisted = false; if (boost::iequals(dependency->filepath, "erreula.rpl")) { if (fsc_doesFileExist(filePath)) isBlacklisted = true; } if (isBlacklisted) cemuLog_log(LogType::Force, fmt::format("Game tried to load \"{}\" but it is blacklisted (using Cemu's implementation instead)", filePath)); else if (RPLLoader_LoadFromVirtualPath(dependency, filePath)) return; // attempt to load rpl from Cemu's /cafeLibs/ directory if (ActiveSettings::LoadSharedLibrariesEnabled()) { const auto filePath = ActiveSettings::GetPath("cafeLibs/{}", dependency->filepath); auto fileData = FileStream::LoadIntoMemory(filePath); if (fileData) { forceLog_printf("Loading RPL: /cafeLibs/%s", dependency->filepath); dependency->rplLoaderContext = rpl_loadFromMem(fileData->data(), fileData->size(), dependency->filepath); return; } } } // loads and unloads modules based on the current dependency list void RPLLoader_UpdateDependencies() { bool repeat = true; while (repeat) { repeat = false; for(auto idx = 0; idxreferenceCount, dependency->modulename); if(dependency->referenceCount == 0) { // unload RPLs // todo - should we let HLE modules know if they are being unloaded? if (dependency->rplLoaderContext) { RPLLoader_UnloadModule(dependency->rplLoaderContext); dependency->rplLoaderContext = nullptr; } // remove from dependency list rplDependencyList.erase(rplDependencyList.begin()+idx); idx--; repeat = true; // unload can effect reference count of other dependencies break; } else if (!dependency->loadAttempted) { // load if (dependency->rplLoaderContext == nullptr) { RPLLoader_LoadDependency(dependency); repeat = true; idx++; break; } } idx++; } } RPLLoader_Link(); } RPLModule* rplLoader_mainModule = nullptr; void RPLLoader_SetMainModule(RPLModule* rplLoaderContext) { rplLoaderContext->entrypointCalled = true; rplLoader_mainModule = rplLoaderContext; } uint32 RPLLoader_GetMainModuleHandle() { for (auto& dep : rplDependencyList) { if (dep->rplLoaderContext == rplLoader_mainModule) { return dep->coreinitHandle; } } cemu_assert(false); return 0; } RPLModule* RPLLoader_FindModuleByCodeAddr(uint32 addr) { for (sint32 i = 0; i < rplModuleCount; i++) { uint32 startAddr = rplModuleList[i]->regionMappingBase_text.GetMPTR(); uint32 endAddr = rplModuleList[i]->regionMappingBase_text.GetMPTR() + rplModuleList[i]->regionSize_text; if (addr >= startAddr && addr < endAddr) return rplModuleList[i]; } return nullptr; } RPLModule* RPLLoader_FindModuleByDataAddr(uint32 addr) { for (sint32 i = 0; i < rplModuleCount; i++) { // data uint32 startAddr = rplModuleList[i]->regionMappingBase_data; uint32 endAddr = rplModuleList[i]->regionMappingBase_data + rplModuleList[i]->regionSize_data; if (addr >= startAddr && addr < endAddr) return rplModuleList[i]; // loaderinfo startAddr = rplModuleList[i]->regionMappingBase_loaderInfo; endAddr = rplModuleList[i]->regionMappingBase_loaderInfo + rplModuleList[i]->regionSize_loaderInfo; if (addr >= startAddr && addr < endAddr) return rplModuleList[i]; } return nullptr; } RPLModule* RPLLoader_FindModuleByName(std::string module) { for (sint32 i = 0; i < rplModuleCount; i++) { if (rplModuleList[i]->moduleName2 == module) return rplModuleList[i]; } return nullptr; } void RPLLoader_CallEntrypoints() { for (sint32 i = 0; i < rplModuleCount; i++) { if( rplModuleList[i]->entrypointCalled ) continue; uint32 moduleHandle = RPLLoader_GetHandleByModuleName(rplModuleList[i]->moduleName2.c_str()); MPTR entryPoint = RPLLoader_GetModuleEntrypoint(rplModuleList[i]); PPCCoreCallback(entryPoint, moduleHandle, 1); // 1 -> load, 2 -> unload rplModuleList[i]->entrypointCalled = true; } } void RPLLoader_NotifyControlPassedToApplication() { rplLoader_applicationHasMemoryControl = true; } uint32 RPLLoader_FindModuleOrHLEExport(uint32 moduleHandle, bool isData, const char* exportName) { // find dependency from handle RPLModule* rplLoaderContext = nullptr; rplDependency_t* dependency = nullptr; for (auto& dep : rplDependencyList) { if (dep->coreinitHandle == moduleHandle) { rplLoaderContext = dep->rplLoaderContext; dependency = dep; break; } } uint32 exportResult = 0; if (rplLoaderContext) { exportResult = RPLLoader_FindModuleExport(rplLoaderContext, isData, exportName); } else { // attempt to find HLE export if (isData) { MPTR weakExportAddr = osLib_getPointer(dependency->modulename, exportName); cemu_assert_debug(weakExportAddr != 0xFFFFFFFF); exportResult = weakExportAddr; } else { exportResult = rpl_mapHLEImport(rplLoaderContext, dependency->modulename, exportName, true); } } return exportResult; } uint32 RPLLoader_GetSDA1Base() { return rplLoader_sdataAddr; } uint32 RPLLoader_GetSDA2Base() { return rplLoader_sdata2Addr; } RPLModule** RPLLoader_GetModuleList() { return rplModuleList; } sint32 RPLLoader_GetModuleCount() { return rplModuleCount; } template class SimpleHeap { struct allocEntry_t { TAddr start; TAddr end; allocEntry_t(TAddr start, TAddr end) : start(start), end(end) {}; }; public: SimpleHeap(TAddr baseAddress, TSize size) : m_base(baseAddress), m_size(size) { } TAddr alloc(TSize size, TSize alignment) { cemu_assert_debug(alignment != 0); TAddr allocBase = m_base; allocBase = (allocBase + alignment - 1)&~(alignment-1); while (true) { bool hasCollision = false; for (auto& itr : list_allocatedEntries) { if (allocBase < itr.end && (allocBase + size) >= itr.start) { allocBase = itr.end; allocBase = (allocBase + alignment - 1)&~(alignment - 1); hasCollision = true; break; } } if(hasCollision == false) break; } if ((allocBase + size) > (m_base + m_size)) return 0; list_allocatedEntries.emplace_back(allocBase, allocBase + size); return allocBase; } void free(TAddr addr) { for (sint32 i = 0; i < list_allocatedEntries.size(); i++) { if (list_allocatedEntries[i].start == addr) { list_allocatedEntries.erase(list_allocatedEntries.begin() + i); return; } } cemu_assert(false); return; } private: TAddr m_base; TSize m_size; std::vector list_allocatedEntries; }; SimpleHeap heapCodeCaveArea(MEMORY_CODECAVEAREA_ADDR, MEMORY_CODECAVEAREA_SIZE); MEMPTR RPLLoader_AllocateCodeCaveMem(uint32 alignment, uint32 size) { uint32 addr = heapCodeCaveArea.alloc(size, 256); return MEMPTR{addr}; } void RPLLoader_ReleaseCodeCaveMem(MEMPTR addr) { heapCodeCaveArea.free(addr.GetMPTR()); }