Cemu/src/Cafe/OS/RPL/rpl.cpp
Tom Lally d3a7b3b5a6
Misc. Linux improvements and bug fixes. (#121)
Co-authored-by: Tom Lally <tomlally@protonmail.com>
2022-09-01 20:46:20 +02:00

2390 lines
79 KiB
C++

#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 <zlib.h>
#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<rplDependency_t*> rplDependencyList = std::vector<rplDependency_t*>();
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<void(*)(PPCInterpreter_t* hCPU), uint32> 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<uint32be> 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<uint8>(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<uint8> 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(&regionMappingTable, 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<mappedFunctionImport_t> list_mappedFunctionImports = std::vector<mappedFunctionImport_t>();
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 = std::rotl<uint64>(h1, 3);
h2 = std::rotl<uint64>(h2, 7);
rplName++;
}
// funcName
while (*funcName)
{
uint64 v = (uint64)*funcName;
h1 += v;
h2 ^= v;
h1 = std::rotl<uint64>(h1, 3);
h2 = std::rotl<uint64>(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<RPLSharedImportTracking> 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<RPLSharedImportTracking> 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, &sectionCount, sizeof(sectionCount));
rpl->patchCRC = crc32_calc(rpl->patchCRC, &sectionSymTableIndex, sizeof(sectionSymTableIndex));
rpl->patchCRC = crc32_calc(rpl->patchCRC, &sectionStrTableIndex, sizeof(sectionStrTableIndex));
sint32 sectionSectNameTableIndex = rpl->rplHeader.nameSectionIndex;
rpl->patchCRC = crc32_calc(rpl->patchCRC, &sectionSectNameTableIndex, 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<void> 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<RPLSharedImportTracking> 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<void>(rpl->regionMappingBase_data).GetPtr());
//rpl->regionMappingBase_data = 0;
//RPLLoader_FreeData(rpl, MEMPTR<void>(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; idx<rplDependencyList.size(); )
{
auto dependency = rplDependencyList[idx];
// debug_printf("DEP 0x%02x %s\n", dependency->referenceCount, 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<typename TAddr, typename TSize>
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<allocEntry_t> list_allocatedEntries;
};
SimpleHeap<uint32, uint32> heapCodeCaveArea(MEMORY_CODECAVEAREA_ADDR, MEMORY_CODECAVEAREA_SIZE);
MEMPTR<void> RPLLoader_AllocateCodeCaveMem(uint32 alignment, uint32 size)
{
uint32 addr = heapCodeCaveArea.alloc(size, 256);
return MEMPTR<void>{addr};
}
void RPLLoader_ReleaseCodeCaveMem(MEMPTR<void> addr)
{
heapCodeCaveArea.free(addr.GetMPTR());
}