diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 2a1078413d..c5eb655449 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -292,14 +292,19 @@ void __KernelModuleShutdown() void WriteVarSymbol(u32 exportAddress, u32 relocAddress, u8 type) { + // Sometimes there are multiple LO16's or HI16's per pair, even though the ABI says nothing of this. + // For multiple LO16's, we need the original (unrelocated) instruction data of the HI16. + // For multiple HI16's, we just need to set each one. + struct HI16RelocInfo + { + u32 addr; + u32 data; + }; + // We have to post-process the HI16 part, since it might be +1 or not depending on the LO16 value. - static u32 lastHI16RelocAddress = 0; static u32 lastHI16ExportAddress = 0; - // Sometimes, there are multiple LO16s per HI16. This is okay if they match, but we need the old - // HI16 value (before our fixup) for each one. - static u32 lastHI16RelocData = 0; + static std::vector lastHI16Relocs; static bool lastHI16Processed = true; - static u32 lastHILO16Address = 0; u32 relocData = Memory::Read_Instruction(relocAddress); @@ -324,42 +329,52 @@ void WriteVarSymbol(u32 exportAddress, u32 relocAddress, u8 type) */ case R_MIPS_HI16: - if (!lastHI16Processed) - WARN_LOG_REPORT(LOADER, "Unsafe unpaired HI16 variable relocation @ %08x / %08x", lastHI16RelocAddress, relocAddress); + if (lastHI16ExportAddress != exportAddress) + { + if (!lastHI16Processed && lastHI16Relocs.size() >= 1) + WARN_LOG_REPORT(LOADER, "Unsafe unpaired HI16 variable relocation @ %08x / %08x", lastHI16Relocs[lastHI16Relocs.size() - 1].addr, relocAddress); + + lastHI16ExportAddress = exportAddress; + lastHI16Relocs.clear(); + } // After this will be an R_MIPS_LO16. If that addition overflows, we need to account for it in HI16. // The R_MIPS_LO16 and R_MIPS_HI16 will often be *different* relocAddress values. - lastHI16RelocAddress = relocAddress; - lastHI16ExportAddress = exportAddress; - lastHI16RelocData = Memory::Read_Instruction(relocAddress); + HI16RelocInfo reloc; + reloc.addr = relocAddress; + reloc.data = Memory::Read_Instruction(relocAddress); + lastHI16Relocs.push_back(reloc); lastHI16Processed = false; break; case R_MIPS_LO16: { - // The ABI requires that these come in pairs. - if (lastHI16ExportAddress != exportAddress) - { - ERROR_LOG_REPORT(LOADER, "HI16 and LO16 imports do not match for %08x => %08x/%08x (hi16 export: %08x)", exportAddress, lastHI16RelocAddress, relocAddress, lastHI16ExportAddress); - break; - } // Sign extend the existing low value (e.g. from addiu.) - u32 full = (lastHI16RelocData << 16) + (s16)(u16)(relocData & 0xFFFF) + exportAddress; + const u32 exportOffsetLo = exportAddress + (s16)(u16)(relocData & 0xFFFF); + u32 full = exportOffsetLo; - if (!lastHI16Processed) + // The ABI requires that these come in pairs, at least. + if (lastHI16Relocs.empty()) + ERROR_LOG_REPORT(LOADER, "LO16 without any HI16 variable import at %08x for %08x", relocAddress, exportAddress) + // Try to process at least the low relocation... + else if (lastHI16ExportAddress != exportAddress) + ERROR_LOG_REPORT(LOADER, "HI16 and LO16 imports do not match at %08x for %08x (should be %08x)", relocAddress, lastHI16ExportAddress, exportAddress) + else { - // The low instruction will be a signed add, which means (full & 0x8000) will subtract. - // We add 1 in that case so that it ends up the right value. - u16 high = (full >> 16) + ((full & 0x8000) ? 1 : 0); - Memory::Write_U32((lastHI16RelocData & ~0xFFFF) | high, lastHI16RelocAddress); + // Process each of the HI16. Usually there's only one. + for (auto it = lastHI16Relocs.begin(), end = lastHI16Relocs.end(); it != end; ++it) + { + full = (it->data << 16) + exportOffsetLo; + // The low instruction will be a signed add, which means (full & 0x8000) will subtract. + // We add 1 in that case so that it ends up the right value. + u16 high = (full >> 16) + ((full & 0x8000) ? 1 : 0); + Memory::Write_U32((it->data & ~0xFFFF) | high, it->addr); + } lastHI16Processed = true; } - else if (lastHILO16Address != full) - WARN_LOG_REPORT(LOADER, "Potentially unsafe unpaired LO16 variable relocation: hi=%08x lo=%08x / last=%08x this=%08x", lastHI16RelocAddress, relocAddress, lastHILO16Address, full); - // And then this is the low relocation, hurray. + // With full set above (hopefully), now we just need to correct the low instruction. relocData = (relocData & ~0xFFFF) | (full & 0xFFFF); - lastHILO16Address = full; } break;