diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index 9804e517f3..88322d370f 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -203,14 +203,23 @@ DisableHLEFlags AlwaysDisableHLEFlags() { return DisableHLEFlags::scePsmf | DisableHLEFlags::scePsmfPlayer | DisableHLEFlags::sceCcc; } -// Process compat flags. +// Which modules we're HLE-ing is part of the machine's state, not a live setting: it's decided +// when each module is loaded, and the syscall stubs written into memory then are what a savestate +// captures. So latch it on the first use after boot, save it in the state, and restore it on load +// - otherwise a state made on one side of the boundary gets its imports re-resolved against the +// other, and every call into the module lands on an unresolved stub. Changing the setting takes +// effect on the next boot, which is the only point it could have taken effect anyway. +static DisableHLEFlags g_effectiveDisableHLE; +static bool g_disableHLELatched; + // Flags the user asked for that we can't honour this boot, because the firmware modules they -// need aren't in the NAND directory. Subtracted in GetDisableHLEFlags so that a missing dump +// need aren't in the NAND directory. Subtracted in ComputeDisableHLEFlags so that a missing dump // leaves the HLE in place rather than handing the game unresolved imports, which is much worse // than our stubs. Recomputed per boot, since the dump can appear between runs. static DisableHLEFlags g_unavailableDisableFlags = (DisableHLEFlags)0; -static DisableHLEFlags GetDisableHLEFlags() { +// Process compat flags. +static DisableHLEFlags ComputeDisableHLEFlags() { DisableHLEFlags flags = (DisableHLEFlags)g_Config.iDisableHLE | AlwaysDisableHLEFlags(); if (PSP_CoreParameter().compat.flags().DisableHLESceFont) { flags |= DisableHLEFlags::sceFont; @@ -225,6 +234,18 @@ static DisableHLEFlags GetDisableHLEFlags() { return flags; } +static DisableHLEFlags GetDisableHLEFlags() { + if (!g_disableHLELatched) { + g_effectiveDisableHLE = ComputeDisableHLEFlags(); + g_disableHLELatched = true; + } + return g_effectiveDisableHLE; +} + +DisableHLEFlags GetEffectiveDisableHLEFlags() { + return GetDisableHLEFlags(); +} + // Note: name is the modname from prx, not the export module name! bool ShouldHLEModule(std::string_view modname, bool *wasDisabledManually) { if (wasDisabledManually) { @@ -296,16 +317,28 @@ static void CheckDisableHLEAvailability() { void HLEInit() { CheckDisableHLEAvailability(); RegisterAllModules(); + // Latched lazily rather than here: the compat flags this depends on aren't loaded yet. + g_disableHLELatched = false; g_stackSize = 0; delayedResultEvent = CoreTiming::RegisterEvent("HLEDelayedResult", hleDelayResultFinish); g_idleOp = GetSyscallOp("FakeSysCalls", NID_IDLE); } void HLEDoState(PointerWrap &p) { - auto s = p.Section("HLE", 1, 2); + auto s = p.Section("HLE", 1, 3); if (!s) return; + if (s >= 3) { + int disableHLE = (int)GetDisableHLEFlags(); + Do(p, disableHLE); + if (p.mode == p.MODE_READ) { + // Whatever the config says now, this state's modules were loaded under these flags. + g_effectiveDisableHLE = (DisableHLEFlags)disableHLE; + g_disableHLELatched = true; + } + } + // Can't be inside a syscall when saving state, reset this so errors aren't misleading. if (g_stackSize) { ERROR_LOG(Log::HLE, "Can't save state while in a HLE syscall"); diff --git a/Core/HLE/HLE.h b/Core/HLE/HLE.h index a0c39650b3..b1fbc25772 100644 --- a/Core/HLE/HLE.h +++ b/Core/HLE/HLE.h @@ -127,6 +127,9 @@ void RegisterHLEModule(std::string_view name, int numFunctions, const HLEFunctio int GetNumRegisteredHLEModules(); const HLEModule *GetHLEModuleByIndex(int index); DisableHLEFlags AlwaysDisableHLEFlags(); +// The flags actually in effect for this boot - latched at the first module load and restored from +// savestates, so it can differ from what g_Config says if the setting changed since. +DisableHLEFlags GetEffectiveDisableHLEFlags(); // Run the current thread's callbacks after the syscall finishes. void hleCheckCurrentCallbacks(); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index c55352c3f1..bc30b219f8 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1927,14 +1927,14 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load return module; } -SceUID KernelLoadModule(const std::string &filename, std::string *error_string) { +SceUID KernelLoadModule(const std::string &filename, std::string *error_string, bool fromTop) { std::vector buffer; if (pspFileSystem.ReadEntireFile(filename, buffer) < 0) return SCE_KERNEL_ERROR_NOFILE; u32 error = SCE_KERNEL_ERROR_ILLEGAL_OBJECT; u32 magic; - PSPModule *module = __KernelLoadELFFromPtr(&buffer[0], buffer.size(), 0, false, error_string, &magic, filename, error); + PSPModule *module = __KernelLoadELFFromPtr(&buffer[0], buffer.size(), 0, fromTop, error_string, &magic, filename, error); if (module == nullptr) return error; diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h index 6fcbbcfd5b..c1f0c5d359 100644 --- a/Core/HLE/sceKernelModule.h +++ b/Core/HLE/sceKernelModule.h @@ -247,7 +247,11 @@ bool KernelFindImportByStubAddr(u32 stubAddr, std::string *importModuleName, u32 bool DescribeModuleAddress(u32 address, char *buffer, size_t bufferSize); int __KernelGPUReplay(); void __KernelReturnFromModuleFunc(); -SceUID KernelLoadModule(const std::string &filename, std::string *error_string); +// fromTop puts the module at the top of the user partition instead of the bottom. Use it for +// firmware modules we inject before the game loads - taking the bottom pushes the game's own ELF +// up, which shifts every address in it and invalidates cheats and achievements, and outright +// fails for a game like Tekken 6 whose EBOOT must load at a fixed low address. +SceUID KernelLoadModule(const std::string &filename, std::string *error_string, bool fromTop = false); int __KernelStartModule(SceUID moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, SceKernelSMOption *smoption, bool *needsWait); u32 __KernelStopUnloadSelfModuleWithOrWithoutStatus(u32 exitCode, u32 argSize, u32 argp, u32 statusAddr, u32 optionAddr, bool WithStatus); u32 sceKernelFindModuleByUID(u32 uid); diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 90102697cc..a6a9ba32fc 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -108,7 +108,7 @@ static void NotifyLoadStatusMp4(int state, u32 loadAddr, u32 totalSize) { continue; } std::string error; - SceUID id = KernelLoadModule(paths[i], &error); + SceUID id = KernelLoadModule(paths[i], &error, true); if (id < 0) { ERROR_LOG(Log::sceUtility, "sceMp4 HLE is disabled, but %s wouldn't load (%s) - " "the game will get unresolved imports", paths[i], error.c_str());