diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj
index 13782d7a60..1847fc0fac 100644
--- a/Common/Common.vcxproj
+++ b/Common/Common.vcxproj
@@ -405,6 +405,7 @@
+
diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters
index 61b63aee76..48c9352654 100644
--- a/Common/Common.vcxproj.filters
+++ b/Common/Common.vcxproj.filters
@@ -691,6 +691,9 @@
ext\sol
+
+ ext\imgui
+
@@ -1481,4 +1484,4 @@
ext\lua
-
+
\ No newline at end of file
diff --git a/Core/Config.cpp b/Core/Config.cpp
index 01243bc523..45760dd5e2 100644
--- a/Core/Config.cpp
+++ b/Core/Config.cpp
@@ -238,6 +238,7 @@ static const ConfigSetting generalSettings[] = {
ConfigSetting("GameListScrollPosition", &g_Config.fGameListScrollPosition, 0.0f, CfgFlag::DEFAULT),
ConfigSetting("DebugOverlay", &g_Config.iDebugOverlay, 0, CfgFlag::DONT_SAVE),
ConfigSetting("DefaultTab", &g_Config.iDefaultTab, 0, CfgFlag::DEFAULT),
+ ConfigSetting("DisableHLEFlags", &g_Config.iDisableHLE, 0, CfgFlag::PER_GAME),
ConfigSetting("ScreenshotMode", &g_Config.iScreenshotMode, 0, CfgFlag::DEFAULT),
ConfigSetting("ScreenshotsAsPNG", &g_Config.bScreenshotsAsPNG, false, CfgFlag::PER_GAME),
diff --git a/Core/Config.h b/Core/Config.h
index 451eaec99b..ca2d958678 100644
--- a/Core/Config.h
+++ b/Core/Config.h
@@ -139,6 +139,7 @@ public:
bool bLoadPlugins;
int iAskForExitConfirmationAfterSeconds;
int iUIScaleFactor; // In 8ths of powers of two.
+ int iDisableHLE;
int iScreenRotation; // The rotation angle of the PPSSPP UI. Only supported on Android and possibly other mobile platforms.
int iInternalScreenRotation; // The internal screen rotation angle. Useful for vertical SHMUPs and similar.
diff --git a/Core/ConfigValues.h b/Core/ConfigValues.h
index 73691206bb..85096ab877 100644
--- a/Core/ConfigValues.h
+++ b/Core/ConfigValues.h
@@ -116,6 +116,21 @@ enum class RestoreSettingsBits : int {
};
ENUM_CLASS_BITOPS(RestoreSettingsBits);
+// Modules that are candidates for disabling HLE of.
+enum class DisableHLEFlags : int {
+ sceFont = (1 << 0),
+ sceAtrac = (1 << 1),
+ scePsmf = (1 << 2),
+ scePsmfPlayer = (1 << 3),
+ sceMpeg = (1 << 4),
+ sceMp3 = (1 << 5),
+ sceJpeg = (1 << 6),
+ sceParseHttp = (1 << 7),
+ Count = 8,
+ // TODO: Some of the networking libraries may be interesting candidates, like HTTP.
+};
+ENUM_CLASS_BITOPS(DisableHLEFlags);
+
std::string GPUBackendToString(GPUBackend backend);
GPUBackend GPUBackendFromString(std::string_view backend);
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 0ab243b90d..9fb5aab8aa 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -680,7 +680,6 @@
- MaxSpeed
MaxSpeed
MaxSpeed
Default
diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp
index 71845918da..d63b7cc94d 100644
--- a/Core/HLE/HLE.cpp
+++ b/Core/HLE/HLE.cpp
@@ -113,6 +113,111 @@ static std::vector enqueuedMipsCalls;
// Does need to be saved, referenced by the stack and owned.
static std::vector mipsCallActions;
+// Modules that games try to load from disk, that we should not load normally. Instead we just HLE hook them.
+// TODO: Merge with moduleDB?
+static const HLEModuleMeta g_moduleMeta[] = {
+ {"sceATRAC3plus_Library", "sceAtrac3plus", DisableHLEFlags::sceAtrac},
+ {"sceFont_Library", "sceLibFont", DisableHLEFlags::sceFont},
+ {"SceFont_Library", "sceLibFont", DisableHLEFlags::sceFont},
+ {"SceFont_Library", "sceLibFttt", DisableHLEFlags::sceFont},
+ {"SceHttp_Library", "sceHttp"},
+ {"sceMpeg_library", "sceMpeg", DisableHLEFlags::sceMpeg},
+ {"sceNetAdhocctl_Library"},
+ {"sceNetAdhocDownload_Library"},
+ {"sceNetAdhocMatching_Library"},
+ {"sceNetApDialogDummy_Library"},
+ {"sceNetAdhoc_Library"},
+ {"sceNetApctl_Library"},
+ {"sceNetInet_Library"},
+ {"sceNetResolver_Library"},
+ {"sceNet_Library"},
+ {"sceNetAdhoc_Library"},
+ {"sceNetAdhocAuth_Service"},
+ {"sceNetAdhocctl_Library"},
+ {"sceNetIfhandle_Service"},
+ {"sceSsl_Module"},
+ {"sceDEFLATE_Library"},
+ {"sceMD5_Library"},
+ {"sceMemab"},
+ {"sceAvcodec_driver"},
+ {"sceAudiocodec_Driver"},
+ {"sceAudiocodec"},
+ {"sceVideocodec_Driver"},
+ {"sceVideocodec"},
+ {"sceMpegbase_Driver"},
+ {"sceMpegbase"},
+ {"scePsmf_library", "scePsmf", DisableHLEFlags::scePsmf},
+ {"scePsmfP_library", "scePsmfPlayer", DisableHLEFlags::scePsmfPlayer},
+ {"scePsmfPlayer", "scePsmfPlayer", DisableHLEFlags::scePsmfPlayer},
+ {"sceSAScore", "sceSasCore"},
+ {"sceCcc_Library", "sceCcc"},
+ {"SceParseHTTPheader_Library", "sceParseHttp", DisableHLEFlags::sceParseHttp},
+ {"SceParseURI_Library"},
+ // Guessing these names
+ {"sceJpeg", "sceJpeg", DisableHLEFlags::sceJpeg},
+ {"sceJpeg_library", "sceJpeg", DisableHLEFlags::sceJpeg},
+ {"sceJpeg_Library", "sceJpeg", DisableHLEFlags::sceJpeg},
+};
+
+const HLEModuleMeta *GetHLEModuleMeta(std::string_view modname) {
+ for (size_t i = 0; i < ARRAY_SIZE(g_moduleMeta); i++) {
+ if (equalsNoCase(modname, g_moduleMeta[i].modname)) {
+ return &g_moduleMeta[i];
+ }
+ }
+ return nullptr;
+}
+
+const HLEModuleMeta *GetHLEModuleMetaByFlag(DisableHLEFlags flag) {
+ for (size_t i = 0; i < ARRAY_SIZE(g_moduleMeta); i++) {
+ if (g_moduleMeta[i].disableFlag == flag) {
+ return &g_moduleMeta[i];
+ }
+ }
+ return nullptr;
+}
+
+const HLEModuleMeta *GetHLEModuleMetaByImport(std::string_view importModuleName) {
+ for (size_t i = 0; i < ARRAY_SIZE(g_moduleMeta); i++) {
+ if (g_moduleMeta[i].importName && equalsNoCase(importModuleName, g_moduleMeta[i].importName)) {
+ return &g_moduleMeta[i];
+ }
+ }
+ return nullptr;
+}
+
+// Note: name is the modname from prx, not the export module name!
+bool ShouldHLEModule(std::string_view modname, bool *wasDisabled) {
+ if (wasDisabled) {
+ *wasDisabled = false;
+ }
+ const HLEModuleMeta *meta = GetHLEModuleMeta(modname);
+ if (!meta) {
+ return false;
+ }
+
+ bool disabled = meta->disableFlag & (DisableHLEFlags)g_Config.iDisableHLE;
+ if (disabled) {
+ if (wasDisabled) {
+ *wasDisabled = true;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool ShouldHLEModuleByImportName(std::string_view name) {
+ // Check our special metadata lookup. Should probably be merged with the main one.
+ const HLEModuleMeta *meta = GetHLEModuleMetaByImport(name);
+ if (meta) {
+ bool disabled = meta->disableFlag & (DisableHLEFlags)g_Config.iDisableHLE;
+ return !disabled;
+ }
+
+ // Otherwise, just fall back to the regular db. If it's in there, we should HLE it.
+ return GetHLEModuleByName(name) != nullptr;
+}
+
static void hleDelayResultFinish(u64 userdata, int cycleslate) {
u32 error;
SceUID threadID = (SceUID) userdata;
@@ -278,7 +383,7 @@ const char *GetHLEFuncName(int moduleIndex, int func) {
u32 GetSyscallOp(std::string_view moduleName, u32 nib) {
// Special case to hook up bad imports.
if (moduleName.empty()) {
- return (0x03FFFFCC); // invalid syscall
+ return 0x03FFFFCC; // invalid syscall
}
int modindex = GetHLEModuleIndex(moduleName);
@@ -292,15 +397,10 @@ u32 GetSyscallOp(std::string_view moduleName, u32 nib) {
}
} else {
ERROR_LOG(Log::HLE, "Unknown module %.*s!", (int)moduleName.size(), moduleName.data());
- return 0x03FFFFCC; // invalid syscall
+ return 0x03FFFFCC; // invalid syscall (invalid mod index and func index..)
}
}
-bool FuncImportIsSyscall(std::string_view module, u32 nib)
-{
- return GetHLEFunc(module, nib) != nullptr;
-}
-
void WriteFuncStub(u32 stubAddr, u32 symAddr)
{
// Note that this should be J not JAL, as otherwise control will return to the stub..
@@ -317,7 +417,7 @@ void WriteFuncMissingStub(u32 stubAddr, u32 nid)
Memory::Write_U32(GetSyscallOp("", nid), stubAddr + 4);
}
-bool WriteSyscall(std::string_view moduleName, u32 nib, u32 address)
+bool WriteHLESyscall(std::string_view moduleName, u32 nib, u32 address)
{
if (nib == 0)
{
diff --git a/Core/HLE/HLE.h b/Core/HLE/HLE.h
index 1b2cfd774d..60e892b479 100644
--- a/Core/HLE/HLE.h
+++ b/Core/HLE/HLE.h
@@ -25,6 +25,7 @@
#include "Common/CommonTypes.h"
#include "Common/Log.h"
#include "Core/MIPS/MIPS.h"
+#include "Core/ConfigValues.h"
#ifdef _MSC_VER
#pragma warning (error: 4834) // discarding return value of function with 'nodiscard' attribute
@@ -97,8 +98,21 @@ struct Syscall {
#define RETURN64(n) {u64 RETURN64_tmp = n; currentMIPS->r[MIPS_REG_V0] = RETURN64_tmp & 0xFFFFFFFF; currentMIPS->r[MIPS_REG_V1] = RETURN64_tmp >> 32;}
#define RETURNF(fl) currentMIPS->f[0] = fl
+struct HLEModuleMeta {
+ // This is the modname (name from the PRX header). Probably, we should really blacklist on the module names of the exported symbol metadata.
+ const char *modname;
+ const char *importName; // Technically a module can export functions with different module names, but doesn't seem to happen.
+ DisableHLEFlags disableFlag;
+};
+
+const HLEModuleMeta *GetHLEModuleMetaByFlag(DisableHLEFlags flag);
+const HLEModuleMeta *GetHLEModuleMeta(std::string_view modname);
+bool ShouldHLEModule(std::string_view modname, bool *wasDisabled = nullptr);
+bool ShouldHLEModuleByImportName(std::string_view importModuleName);
+
const char *GetHLEFuncName(std::string_view module, u32 nib);
const char *GetHLEFuncName(int module, int func);
+const HLEModule *GetHLEModuleByName(std::string_view name);
const HLEFunction *GetHLEFunc(std::string_view module, u32 nib);
int GetHLEFuncIndexByNib(int moduleIndex, u32 nib);
int GetHLEModuleIndex(std::string_view modulename);
@@ -160,8 +174,7 @@ void HLEInit();
void HLEDoState(PointerWrap &p);
void HLEShutdown();
u32 GetSyscallOp(std::string_view module, u32 nib);
-bool FuncImportIsSyscall(std::string_view module, u32 nib);
-bool WriteSyscall(std::string_view module, u32 nib, u32 address);
+bool WriteHLESyscall(std::string_view module, u32 nib, u32 address);
void CallSyscall(MIPSOpcode op);
void WriteFuncStub(u32 stubAddr, u32 symAddr);
void WriteFuncMissingStub(u32 stubAddr, u32 nid);
diff --git a/Core/HLE/sceKernel.h b/Core/HLE/sceKernel.h
index 1780c1cc0f..1c521df73d 100644
--- a/Core/HLE/sceKernel.h
+++ b/Core/HLE/sceKernel.h
@@ -172,6 +172,15 @@ public:
return occupied[index];
}
+ template
+ bool Is(SceUID handle) const {
+ int index = handle - handleOffset;
+ if (index < 0 || index >= maxCount)
+ return false;
+ else
+ return occupied[index] && pool[handle - handleOffset]->GetIDType() == T::GetStaticIDType();
+ }
+
template
T* Get(SceUID handle, u32 &outError) {
if (handle < handleOffset || handle >= handleOffset+maxCount || !occupied[handle-handleOffset]) {
@@ -204,15 +213,15 @@ public:
return static_cast(pool[realHandle]);
}
- template
- void Iterate(bool func(T *, ArgT), ArgT arg) {
+ template
+ void Iterate(F func) {
int type = T::GetStaticIDType();
for (int i = 0; i < maxCount; i++) {
if (!occupied[i])
continue;
T *t = static_cast(pool[i]);
if (t->GetIDType() == type) {
- if (!func(t, arg))
+ if (!func(i + handleOffset, t))
break;
}
}
diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp
index 423c31c771..2b34498111 100644
--- a/Core/HLE/sceKernelMemory.cpp
+++ b/Core/HLE/sceKernelMemory.cpp
@@ -1970,14 +1970,6 @@ struct FindTLSByIndexArg {
TLSPL *result = nullptr;
};
-static bool FindTLSByIndex(TLSPL *possible, FindTLSByIndexArg *state) {
- if (possible->ntls.index == state->index) {
- state->result = possible;
- return false;
- }
- return true;
-}
-
int sceKernelGetTlsAddr(SceUID uid) {
if (!__KernelIsDispatchEnabled() || __IsInInterrupt())
return hleLogWarning(Log::sceKernel, 0, "dispatch disabled");
@@ -1994,7 +1986,14 @@ int sceKernelGetTlsAddr(SceUID uid) {
FindTLSByIndexArg state;
state.index = (uid >> 3) & 15;
- kernelObjects.Iterate(&FindTLSByIndex, &state);
+ kernelObjects.Iterate([&state](int id, TLSPL *possible) {
+ if (possible->ntls.index == state.index) {
+ state.result = possible;
+ return false;
+ }
+ return true;
+ });
+
if (!state.result)
return hleLogError(Log::sceKernel, 0, "tlspl not found");
diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp
index c547342e3b..ecb81fa31c 100644
--- a/Core/HLE/sceKernelModule.cpp
+++ b/Core/HLE/sceKernelModule.cpp
@@ -52,12 +52,11 @@
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/Util/BlockAllocator.h"
#include "Core/CoreTiming.h"
+#include "Core/ConfigValues.h"
#include "Core/PSPLoaders.h"
#include "Core/System.h"
#include "Core/MemMapHelpers.h"
#include "Core/Debugger/SymbolMap.h"
-#include "Core/MIPS/MIPS.h"
-
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelModule.h"
#include "Core/HLE/sceKernelThread.h"
@@ -103,32 +102,6 @@ static const char * const lieAboutSuccessModules[] = {
"flash0:/kd/pspnet_resolver.prx",
};
-// Modules to not load. TODO: Look into loosening this a little (say sceFont).
-static const char * const blacklistedModules[] = {
- "sceATRAC3plus_Library",
- "sceFont_Library",
- "SceFont_Library",
- "SceHttp_Library",
- "sceMpeg_library",
- "sceNetAdhocctl_Library",
- "sceNetAdhocDownload_Library",
- "sceNetAdhocMatching_Library",
- "sceNetApDialogDummy_Library",
- "sceNetAdhoc_Library",
- "sceNetApctl_Library",
- "sceNetInet_Library",
- "sceNetResolver_Library",
- "sceNet_Library",
- "sceNetAdhoc_Library",
- "sceNetAdhocAuth_Service",
- "sceNetAdhocctl_Library",
- "sceNetIfhandle_Service",
- "sceSsl_Module",
- "sceDEFLATE_Library",
- "sceMD5_Library",
- "sceMemab",
-};
-
const char *NativeModuleStatusToString(NativeModuleStatus status) {
switch (status) {
case MODULE_STATUS_STARTING: return "STARTING";
@@ -167,6 +140,34 @@ struct ModuleInfo {
char name[28];
};
+struct PspLibStubEntry {
+ u32_le name;
+ u16_le version;
+ u16_le flags;
+ u8 size;
+ u8 numVars;
+ u16_le numFuncs;
+ // each symbol has an associated nid; nidData is a pointer
+ // (in .rodata.sceNid section) to an array of longs, one
+ // for each function, which identifies the function whose
+ // address is to be inserted.
+ //
+ // The hash is the first 4 bytes of a SHA-1 hash of the function
+ // name. (Represented as a little-endian long, so the order
+ // of the bytes is reversed.)
+ u32_le nidData;
+ // the address of the function stubs where the function address jumps
+ // should be filled in
+ u32_le firstSymAddr;
+ // Optional, this is where var relocations are.
+ // They use the format: u32 addr, u32 nid, ...
+ // WARNING: May have garbage if size < 6.
+ u32_le varData;
+ // Not sure what this is yet, assume garbage for now.
+ // TODO: Tales of the World: Radiant Mythology 2 has something here?
+ u32_le extra;
+};
+
PSPModule::~PSPModule() {
if (memoryBlockAddr) {
// If it's either below user memory, or using a high kernel bit, it's in kernel.
@@ -295,14 +296,14 @@ void PSPModule::ImportFunc(const FuncSymbolImport &func, bool reimporting) {
// Keep track and actually hook it up if possible.
importedFuncs.push_back(func);
- impExpModuleNames.insert(func.moduleName);
+ impModuleNames.insert(func.moduleName);
ImportFuncSymbol(func, reimporting, GetName());
}
void PSPModule::ImportVar(WriteVarSymbolState &state, const VarSymbolImport &var) {
// Keep track and actually hook it up if possible.
importedVars.push_back(var);
- impExpModuleNames.insert(var.moduleName);
+ impModuleNames.insert(var.moduleName);
ImportVarSymbol(state, var);
}
@@ -311,7 +312,7 @@ void PSPModule::ExportFunc(const FuncSymbolExport &func) {
return;
}
exportedFuncs.push_back(func);
- impExpModuleNames.insert(func.moduleName);
+ expModuleNames.insert(func.moduleName);
ExportFuncSymbol(func);
}
@@ -320,7 +321,7 @@ void PSPModule::ExportVar(const VarSymbolExport &var) {
return;
}
exportedVars.push_back(var);
- impExpModuleNames.insert(var.moduleName);
+ expModuleNames.insert(var.moduleName);
ExportVarSymbol(var);
}
@@ -639,14 +640,26 @@ void UnexportVarSymbol(const VarSymbolExport &var) {
}
}
+static bool FuncImportIsHLE(std::string_view module, u32 nid) {
+ // TODO: Take into account whether HLE is enabled for the module.
+ // Also, this needs to be more efficient.
+ if (!ShouldHLEModuleByImportName(module)) {
+ return false;
+ }
+
+ return GetHLEFunc(module, nid) != nullptr;
+}
+
void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting, const char *importingModule) {
- // Prioritize HLE implementations.
- // TODO: Or not?
- if (FuncImportIsSyscall(func.moduleName, func.nid)) {
+ bool shouldHLE = ShouldHLEModuleByImportName(func.moduleName);
+
+ // Prioritize HLE implementations, if we should HLE this.
+ if (shouldHLE && GetHLEFunc(func.moduleName, func.nid)) {
if (reimporting && Memory::Read_Instruction(func.stubAddr + 4) != GetSyscallOp(func.moduleName, func.nid)) {
WARN_LOG(Log::Loader, "Reimporting updated syscall %s", GetHLEFuncName(func.moduleName, func.nid));
}
- WriteSyscall(func.moduleName, func.nid, func.stubAddr);
+ // TODO: There's some double lookup going on here (we already did the lookup in GetHLEFunc above).
+ WriteHLESyscall(func.moduleName, func.nid, func.stubAddr);
currentMIPS->InvalidateICache(func.stubAddr, 8);
if (g_Config.bPreloadFunctions) {
MIPSAnalyst::PrecompileFunction(func.stubAddr, 8);
@@ -678,21 +691,21 @@ void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting, const char
}
// It hasn't been exported yet, but hopefully it will later. Check if we know about it through HLE.
- const bool isKnownHLEModule = GetHLEModuleIndex(func.moduleName) != -1;
- if (isKnownHLEModule) {
+ if (shouldHLE) {
// We used to report this, but I don't think it's very interesting anymore.
WARN_LOG(Log::Loader, "Unknown syscall from known HLE module '%s': 0x%08x (import for '%s')", func.moduleName, func.nid, importingModule);
} else {
INFO_LOG(Log::Loader, "Function (%s,%08x) unresolved in '%s', storing for later resolving", func.moduleName, func.nid, importingModule);
}
- if (isKnownHLEModule || !reimporting) {
+
+ if (shouldHLE || !reimporting) {
WriteFuncMissingStub(func.stubAddr, func.nid);
currentMIPS->InvalidateICache(func.stubAddr, 8);
}
}
void ExportFuncSymbol(const FuncSymbolExport &func) {
- if (FuncImportIsSyscall(func.moduleName, func.nid)) {
+ if (FuncImportIsHLE(func.moduleName, func.nid)) {
// HLE covers this already - let's ignore the function.
WARN_LOG(Log::Loader, "Ignoring func export %s/%08x, already implemented in HLE.", func.moduleName, func.nid);
return;
@@ -720,7 +733,7 @@ void ExportFuncSymbol(const FuncSymbolExport &func) {
}
void UnexportFuncSymbol(const FuncSymbolExport &func) {
- if (FuncImportIsSyscall(func.moduleName, func.nid)) {
+ if (FuncImportIsHLE(func.moduleName, func.nid)) {
// Oops, HLE covers this.
return;
}
@@ -769,72 +782,7 @@ void PSPModule::Cleanup() {
}
}
-static bool IsHLEVersionedModule(const char *name) {
- // TODO: Only some of these are currently known to be versioned.
- // Potentially only sceMpeg_library matters.
- // For now, we're just reporting version numbers.
- for (size_t i = 0; i < ARRAY_SIZE(blacklistedModules); i++) {
- if (!strncmp(name, blacklistedModules[i], 28)) {
- return true;
- }
- }
- static const char *otherModules[] = {
- "sceAvcodec_driver",
- "sceAudiocodec_Driver",
- "sceAudiocodec",
- "sceVideocodec_Driver",
- "sceVideocodec",
- "sceMpegbase_Driver",
- "sceMpegbase",
- "scePsmf_library",
- "scePsmfP_library",
- "scePsmfPlayer",
- "sceSAScore",
- "sceCcc_Library",
- "SceParseHTTPheader_Library",
- "SceParseURI_Library",
- // Guessing.
- "sceJpeg",
- "sceJpeg_library",
- "sceJpeg_Library",
- };
- for (size_t i = 0; i < ARRAY_SIZE(otherModules); i++) {
- if (!strncmp(name, otherModules[i], 28)) {
- return true;
- }
- }
- return false;
-}
-
static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr, bool reimporting) {
- struct PspLibStubEntry {
- u32_le name;
- u16_le version;
- u16_le flags;
- u8 size;
- u8 numVars;
- u16_le numFuncs;
- // each symbol has an associated nid; nidData is a pointer
- // (in .rodata.sceNid section) to an array of longs, one
- // for each function, which identifies the function whose
- // address is to be inserted.
- //
- // The hash is the first 4 bytes of a SHA-1 hash of the function
- // name. (Represented as a little-endian long, so the order
- // of the bytes is reversed.)
- u32_le nidData;
- // the address of the function stubs where the function address jumps
- // should be filled in
- u32_le firstSymAddr;
- // Optional, this is where var relocations are.
- // They use the format: u32 addr, u32 nid, ...
- // WARNING: May have garbage if size < 6.
- u32_le varData;
- // Not sure what this is yet, assume garbage for now.
- // TODO: Tales of the World: Radiant Mythology 2 has something here?
- u32_le extra;
- };
-
// Can't run - we didn't keep track of the libstub entry.
if (module->libstub == 0) {
return false;
@@ -1099,7 +1047,8 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
head = (const PSP_Header *)ptr;
devkitVersion = head->devkitversion;
- if (IsHLEVersionedModule(head->modname)) {
+ bool wasDisabled;
+ if (ShouldHLEModule(head->modname, &wasDisabled)) {
int ver = (head->module_ver_hi << 8) | head->module_ver_lo;
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x, crc %x", head->modname, ver, head->devkitversion, module->crc);
@@ -1108,6 +1057,9 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
fakeLoadedModule = true;
}
+ if (wasDisabled) {
+ g_OSD.Show(OSDType::MESSAGE_WARNING, StringFromFormat("HLE for %s has been manually disabled", head->modname));
+ }
const u8 *in = ptr;
const auto isGzip = head->comp_attribute & 1;
// Kind of odd.
@@ -1150,11 +1102,11 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
// Opportunity to dump the decrypted elf, even if we choose to fake it.
// NOTE: filename is not necessarily a good choice!
std::string elfFilename(KeepAfterLast(filename, '/'));
- if (elfFilename.empty()) {
+ if (elfFilename.empty() || startsWith(elfFilename, "sce_lbn")) {
// Use the name from the header.
elfFilename = head->modname;
}
- DumpFileIfEnabled(ptr, head->psp_size, elfFilename.c_str(), DumpFileType::PRX);
+ DumpFileIfEnabled(ptr, (u32)elfSize, elfFilename.c_str(), DumpFileType::PRX);
// This should happen for all "kernel" modules.
*error_string = "Missing key";
@@ -1310,16 +1262,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
char moduleName[29] = {0};
strncpy(moduleName, modinfo->name, ARRAY_SIZE(module->nm.name));
- // Check for module blacklist - we don't allow games to load these modules from disc
- // as we have HLE implementations and the originals won't run in the emu because they
- // directly access hardware or for other reasons.
- for (u32 i = 0; i < ARRAY_SIZE(blacklistedModules); i++) {
- if (strncmp(modinfo->name, blacklistedModules[i], ARRAY_SIZE(modinfo->name)) == 0) {
- module->isFake = true;
- }
- }
-
- if (!module->isFake && module->memoryBlockAddr != 0) {
+ if (module->memoryBlockAddr != 0) {
g_symbolMap->AddModule(moduleName, module->memoryBlockAddr, module->memoryBlockSize);
}
@@ -1415,6 +1358,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
}
// Look at the exports, too.
+ // TODO: Add them to the symbol map!
struct PspLibEntEntry {
u32_le name; /* ent's name (module name) address */
@@ -1500,7 +1444,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
func.nid = nid;
func.symAddr = exportAddr;
if (ent->name == 0) {
- WARN_LOG_REPORT(Log::HLE, "Exporting func from syslib export: %08x", nid);
+ WARN_LOG(Log::HLE, "Exporting func from syslib export: %08x", nid);
}
module->ExportFunc(func);
}
@@ -1581,14 +1525,13 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
module->nm.entry_addr = module->nm.module_start_func;
MIPSAnalyst::PrecompileFunctions();
-
} else {
module->nm.entry_addr = -1;
}
delete [] newptr;
- if (!reportedModule && IsHLEVersionedModule(modinfo->name)) {
+ if (!reportedModule && ShouldHLEModule(modinfo->name)) {
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x", modinfo->name, modinfo->moduleVersion, devkitVersion);
if (!strcmp(modinfo->name, "sceMpeg_library")) {
@@ -2364,22 +2307,21 @@ struct GetModuleIdByAddressArg
SceUID result;
};
-static bool __GetModuleIdByAddressIterator(PSPModule *module, GetModuleIdByAddressArg *state) {
- const u32 start = module->memoryBlockAddr, size = module->memoryBlockSize;
- if (start != 0 && start <= state->addr && start + size > state->addr) {
- state->result = module->GetUID();
- return false;
- }
- return true;
-}
-
static u32 sceKernelGetModuleIdByAddress(u32 moduleAddr)
{
GetModuleIdByAddressArg state;
state.addr = moduleAddr;
state.result = SCE_KERNEL_ERROR_UNKNOWN_MODULE;
- kernelObjects.Iterate(&__GetModuleIdByAddressIterator, &state);
+ kernelObjects.Iterate([&state](int id, PSPModule *module) -> bool {
+ const u32 start = module->memoryBlockAddr, size = module->memoryBlockSize;
+ if (start != 0 && start <= state.addr && start + size > state.addr) {
+ state.result = module->GetUID();
+ return false;
+ }
+ return true;
+ });
+
if (state.result == (SceUID)SCE_KERNEL_ERROR_UNKNOWN_MODULE) {
return hleLogError(Log::sceModule, state.result, "module not found at address");
} else {
diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h
index eb8480fbe5..61ead2a4da 100644
--- a/Core/HLE/sceKernelModule.h
+++ b/Core/HLE/sceKernelModule.h
@@ -161,32 +161,43 @@ public:
void ExportVar(const VarSymbolExport &var);
template
- void RebuildImpExpList(const std::vector &list) {
+ void RebuildExpList(const std::vector &list) {
for (size_t i = 0; i < list.size(); ++i) {
- impExpModuleNames.insert(list[i].moduleName);
+ expModuleNames.insert(list[i].moduleName);
+ }
+ }
+
+ template
+ void RebuildImpList(const std::vector &list) {
+ for (size_t i = 0; i < list.size(); ++i) {
+ impModuleNames.insert(list[i].moduleName);
}
}
void RebuildImpExpModuleNames() {
- impExpModuleNames.clear();
- RebuildImpExpList(exportedFuncs);
- RebuildImpExpList(importedFuncs);
- RebuildImpExpList(exportedVars);
- RebuildImpExpList(importedVars);
+ impModuleNames.clear();
+ expModuleNames.clear();
+ RebuildExpList(exportedFuncs);
+ RebuildImpList(importedFuncs);
+ RebuildExpList(exportedVars);
+ RebuildImpList(importedVars);
}
bool ImportsOrExportsModuleName(const std::string &moduleName) {
- return impExpModuleNames.find(moduleName) != impExpModuleNames.end();
+ return impModuleNames.find(moduleName) != impModuleNames.end() ||
+ expModuleNames.find(moduleName) != expModuleNames.end();
}
NativeModule nm{};
std::vector waitingThreads;
+ // TODO: Should we store these grouped by moduleName instead? Seems more reasonable.
std::vector exportedFuncs;
std::vector importedFuncs;
std::vector exportedVars;
std::vector importedVars;
- std::set impExpModuleNames;
+ std::set impModuleNames;
+ std::set expModuleNames;
// Keep track of the code region so we can throw out analysis results
// when unloaded.
diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp
index 1c236af5b5..4ad49ab66a 100644
--- a/Core/HLE/sceKernelThread.cpp
+++ b/Core/HLE/sceKernelThread.cpp
@@ -746,7 +746,7 @@ static void __KernelWriteFakeSysCall(u32 nid, u32 *ptr, u32 &pos)
{
*ptr = pos;
pos += 8;
- WriteSyscall("FakeSysCalls", nid, *ptr);
+ WriteHLESyscall("FakeSysCalls", nid, *ptr);
MIPSAnalyst::PrecompileFunction(*ptr, 8);
}
@@ -1931,7 +1931,7 @@ int __KernelStartThread(SceUID threadToStartID, int argSize, u32 argBlockPtr, bo
// At the bottom of those 64 bytes, the return syscall and ra is written.
// Test Drive Unlimited actually depends on it being in the correct place.
- WriteSyscall("FakeSysCalls", NID_THREADRETURN, sp);
+ WriteHLESyscall("FakeSysCalls", NID_THREADRETURN, sp);
Memory::Write_U32(MIPS_MAKE_B(-1), sp + 8);
Memory::Write_U32(MIPS_MAKE_NOP(), sp + 12);
diff --git a/Core/System.cpp b/Core/System.cpp
index 5fbe8b9632..ba7d498aee 100644
--- a/Core/System.cpp
+++ b/Core/System.cpp
@@ -119,7 +119,7 @@ GlobalUIState GetUIState() {
return globalUIState;
}
-void SetGPUBackend(GPUBackend type, const std::string &device) {
+void SetGPUBackend(GPUBackend type, std::string_view device) {
gpuBackend = type;
gpuBackendDevice = device;
}
@@ -869,7 +869,7 @@ const char *DumpFileTypeToFileExtension(DumpFileType type) {
}
}
-void DumpFileIfEnabled(const u8 *dataPtr, const u32 length, const char *name, DumpFileType type) {
+void DumpFileIfEnabled(const u8 *dataPtr, const u32 length, std::string_view name, DumpFileType type) {
if (!(g_Config.iDumpFileTypes & (int)type)) {
return;
}
@@ -883,7 +883,7 @@ void DumpFileIfEnabled(const u8 *dataPtr, const u32 length, const char *name, Du
}
const char *extension = DumpFileTypeToFileExtension(type);
- std::string filenameToDumpTo = StringFromFormat("%s_%s", g_paramSFO.GetDiscID().c_str(), name);
+ std::string filenameToDumpTo = g_paramSFO.GetDiscID() + "_" + std::string(name);
if (!endsWithNoCase(filenameToDumpTo, extension)) {
filenameToDumpTo += extension;
}
diff --git a/Core/System.h b/Core/System.h
index f867c746aa..9f7bb84bb5 100644
--- a/Core/System.h
+++ b/Core/System.h
@@ -17,6 +17,7 @@
#pragma once
+#include
#include "Common/CommonTypes.h"
#include "Common/File/Path.h"
#include "Core/CoreParameter.h"
@@ -68,7 +69,7 @@ void ResetUIState();
void UpdateUIState(GlobalUIState newState);
GlobalUIState GetUIState();
-void SetGPUBackend(GPUBackend type, const std::string &device = "");
+void SetGPUBackend(GPUBackend type, std::string_view device = "");
GPUBackend GetGPUBackend();
std::string GetGPUBackendDevice();
@@ -128,4 +129,4 @@ inline CoreParameter &PSP_CoreParameter() {
}
// Centralized place for dumping useful files, also takes care of checking for dupes and creating a clickable UI popup.
-void DumpFileIfEnabled(const u8 *dataPtr, const u32 length, const char *name, DumpFileType type);
+void DumpFileIfEnabled(const u8 *dataPtr, const u32 length, std::string_view name, DumpFileType type);
diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp
index b652116246..eca4bce8c8 100644
--- a/UI/GameSettingsScreen.cpp
+++ b/UI/GameSettingsScreen.cpp
@@ -1995,6 +1995,15 @@ void DeveloperToolsScreen::CreateViews() {
list->Add(new BitCheckBox(&g_Config.iDumpFileTypes, (int)DumpFileType::PRX, dev->T("PRX")));
list->Add(new BitCheckBox(&g_Config.iDumpFileTypes, (int)DumpFileType::Atrac3, dev->T("Atrac3/3+")));
+ list->Add(new ItemHeader("Disable HLE (experimental! Not expected to work yet)"));
+ for (int i = 0; i < (int)DisableHLEFlags::Count; i++) {
+ DisableHLEFlags flag = (DisableHLEFlags)(1 << i);
+ const HLEModuleMeta *meta = GetHLEModuleMetaByFlag(flag);
+ if (meta) {
+ list->Add(new BitCheckBox(&g_Config.iDisableHLE, (int)flag, meta->modname));
+ }
+ }
+
#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
list->Add(new ItemHeader(dev->T("MIPSTracer")));
diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp
index 028b500481..851ecba3bc 100644
--- a/UI/ImDebugger/ImDebugger.cpp
+++ b/UI/ImDebugger/ImDebugger.cpp
@@ -2,6 +2,7 @@
#include "ext/imgui/imgui_internal.h"
+#include "ext/imgui/imgui_extras.h"
#include "Common/StringUtils.h"
#include "Common/File/FileUtil.h"
@@ -25,6 +26,7 @@
#include "Core/HLE/HLE.h"
#include "Core/HLE/SocketManager.h"
#include "Core/HLE/NetInetConstants.h"
+#include "Core/HLE/sceKernelModule.h"
#include "Core/HLE/sceNp.h"
#include "Core/HLE/sceNet.h"
#include "Core/HLE/sceNetApctl.h"
@@ -1419,8 +1421,8 @@ static void DrawUtilityModules(ImConfig &cfg, ImControl &control) {
ImGui::PushID(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
- if (ImGui::Selectable(info->name, cfg.selectedModule == i, ImGuiSelectableFlags_SpanAllColumns)) {
- cfg.selectedModule = i;
+ if (ImGui::Selectable(info->name, cfg.selectedUtilityModule == i, ImGuiSelectableFlags_SpanAllColumns)) {
+ cfg.selectedUtilityModule = i;
}
ImGui::TableNextColumn();
if (loadedAddr) {
@@ -1445,7 +1447,114 @@ static void DrawModules(const MIPSDebugInterface *debug, ImConfig &cfg, ImContro
return;
}
- // Hm, this reads from the symbol map.
+ ImGui::TextUnformatted("This shows modules that have been loaded by the game (not plain HLE)");
+
+ if (ImGui::BeginChild("module_list", ImVec2(170.0f, 0.0), ImGuiChildFlags_ResizeX)) {
+ if (ImGui::BeginTable("modules", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
+ ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
+ ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
+ ImGui::TableHeadersRow();
+
+ // TODO: Add context menu and clickability
+ kernelObjects.Iterate([&cfg, &control](int id, PSPModule *module) -> bool {
+ ImGui::PushID(id);
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ if (ImGui::Selectable(module->GetName(), cfg.selectedModuleId == id, ImGuiSelectableFlags_SpanAllColumns)) {
+ cfg.selectedModuleId = id;
+ }
+ ImGui::TableNextColumn();
+ ImGui::TextUnformatted(module->isFake ? "FAKE/HLE" : "normal");
+ ImGui::PopID();
+ return true;
+ });
+
+ ImGui::EndTable();
+ }
+ ImGui::EndChild();
+ }
+ ImGui::SameLine();
+
+ if (ImGui::BeginChild("info")) {
+ if (kernelObjects.Is(cfg.selectedModuleId)) {
+ PSPModule *mod = kernelObjects.GetFast(cfg.selectedModuleId);
+ if (mod) {
+ if (mod->isFake) {
+ ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 170));
+ }
+ ImGui::Text("%s %d.%d (%s)\n", mod->GetName(), mod->nm.version[1], mod->nm.version[0], mod->isFake ? "FAKE/HLE" : "normal");
+ char buf[512];
+ mod->GetLongInfo(buf, sizeof(buf));
+ ImGui::TextUnformatted(buf);
+ if (mod->isFake) {
+ ImGui::PopStyleColor();
+ }
+ if (!mod->impModuleNames.empty() && ImGui::CollapsingHeader("Imported modules")) {
+ for (auto &name : mod->impModuleNames) {
+ ImGui::TextUnformatted(name);
+ }
+ }
+ if (!mod->expModuleNames.empty() && ImGui::CollapsingHeader("Exported modules")) {
+ for (auto &name : mod->expModuleNames) {
+ ImGui::TextUnformatted(name);
+ }
+ }
+ if (!mod->importedFuncs.empty() || !mod->importedVars.empty()) {
+ if (ImGui::CollapsingHeader("Imports")) {
+ if (!mod->importedVars.empty() && ImGui::CollapsingHeader("Vars")) {
+ for (auto &var : mod->importedVars) {
+ ImGui::TextUnformatted("(some var)"); // TODO
+ }
+ }
+ for (auto &import : mod->importedFuncs) {
+ // Look the name up in our HLE database.
+ const HLEFunction *func = GetHLEFunc(import.moduleName, import.nid);
+ ImGui::TextUnformatted(import.moduleName);
+ if (func) {
+ ImGui::SameLine();
+ ImGui::TextUnformatted(func->name);
+ }
+ ImGui::SameLine(); ImClickableValue("addr", import.stubAddr, control, ImCmd::SHOW_IN_CPU_DISASM);
+ }
+ }
+ }
+ if (!mod->exportedFuncs.empty() || !mod->exportedVars.empty()) {
+ if (ImGui::CollapsingHeader("Exports")) {
+ if (!mod->exportedVars.empty() && ImGui::CollapsingHeader("Vars")) {
+ for (auto &var : mod->importedVars) {
+ ImGui::TextUnformatted("(some var)"); // TODO
+ }
+ }
+ for (auto &exportFunc : mod->exportedFuncs) {
+ // Look the name up in our HLE database.
+ const HLEFunction *func = GetHLEFunc(exportFunc.moduleName, exportFunc.nid);
+ ImGui::TextUnformatted(exportFunc.moduleName);
+ if (func) {
+ ImGui::SameLine();
+ ImGui::TextUnformatted(func->name);
+ }
+ ImGui::SameLine(); ImClickableValue("addr", exportFunc.symAddr, control, ImCmd::SHOW_IN_CPU_DISASM);
+ }
+ }
+ }
+ }
+ } else {
+ ImGui::TextUnformatted("(no module selected)");
+ }
+ ImGui::EndChild();
+ }
+ ImGui::End();
+}
+
+// Started as a module browser but really only draws from the symbols database, so let's
+// evolve it to that.
+static void DrawSymbols(const MIPSDebugInterface *debug, ImConfig &cfg, ImControl &control) {
+ if (!ImGui::Begin("Symbols", &cfg.symbolsOpen) || !g_symbolMap) {
+ ImGui::End();
+ return;
+ }
+
+ // Reads from the symbol map.
std::vector modules = g_symbolMap->getAllModules();
if (ImGui::BeginTable("modules", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
@@ -1461,8 +1570,8 @@ static void DrawModules(const MIPSDebugInterface *debug, ImConfig &cfg, ImContro
ImGui::PushID(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
- if (ImGui::Selectable(module.name.c_str(), cfg.selectedModule == i, ImGuiSelectableFlags_SpanAllColumns)) {
- cfg.selectedModule = i;
+ if (ImGui::Selectable(module.name.c_str(), cfg.selectedSymbolModule == i, ImGuiSelectableFlags_SpanAllColumns)) {
+ cfg.selectedSymbolModule = i;
}
ImGui::TableNextColumn();
ImClickableValue("addr", module.address, control, ImCmd::SHOW_IN_MEMORY_VIEWER);
@@ -1476,7 +1585,7 @@ static void DrawModules(const MIPSDebugInterface *debug, ImConfig &cfg, ImContro
ImGui::EndTable();
}
- if (cfg.selectedModule >= 0 && cfg.selectedModule < (int)modules.size()) {
+ if (cfg.selectedModuleId >= 0 && cfg.selectedModuleId < (int)modules.size()) {
// TODO: Show details
}
ImGui::End();
@@ -1673,6 +1782,9 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Symbols")) {
+ ImGui::MenuItem("Symbol browser", nullptr, &cfg_.symbolsOpen);
+ ImGui::Separator();
+
if (ImGui::MenuItem("Load .ppmap...")) {
System_BrowseForFile(reqToken_, "Load PPSSPP symbol map", BrowseFileType::SYMBOL_MAP, [&](const char *responseString, int) {
Path path(responseString);
@@ -1836,6 +1948,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
DrawModules(mipsDebug, cfg_, control);
}
+ if (cfg_.symbolsOpen) {
+ DrawSymbols(mipsDebug, cfg_, control);
+ }
+
if (cfg_.utilityModulesOpen) {
DrawUtilityModules(cfg_, control);
}
@@ -2302,6 +2418,7 @@ void ImConfig::SyncConfig(IniFile *ini, bool save) {
sync.Sync("threadsOpen", &threadsOpen, false);
sync.Sync("callstackOpen", &callstackOpen, false);
sync.Sync("breakpointsOpen", &breakpointsOpen, false);
+ sync.Sync("symbolsOpen", &symbolsOpen, false);
sync.Sync("modulesOpen", &modulesOpen, false);
sync.Sync("hleModulesOpen", &hleModulesOpen, false);
sync.Sync("audioDecodersOpen", &audioDecodersOpen, false);
diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h
index dd16b07932..dd6f291ab6 100644
--- a/UI/ImDebugger/ImDebugger.h
+++ b/UI/ImDebugger/ImDebugger.h
@@ -124,6 +124,7 @@ struct ImConfig {
bool threadsOpen;
bool callstackOpen;
bool breakpointsOpen;
+ bool symbolsOpen;
bool modulesOpen;
bool hleModulesOpen;
bool audioDecodersOpen;
@@ -158,7 +159,8 @@ struct ImConfig {
// bool filterByUsed = true;
// Various selections
- int selectedModule = 0;
+ int selectedModuleId = 0;
+ int selectedSymbolModule = 0;
int selectedUtilityModule = 0;
int selectedThread = 0;
int selectedKernelObject = 0;
diff --git a/ext/imgui/imgui_extras.h b/ext/imgui/imgui_extras.h
new file mode 100644
index 0000000000..9d6a8f41db
--- /dev/null
+++ b/ext/imgui/imgui_extras.h
@@ -0,0 +1,12 @@
+// Just some string_view and related wrappers.
+
+#include
+#include "ext/imgui/imgui.h"
+
+namespace ImGui {
+
+inline void TextUnformatted(std::string_view str) {
+ TextUnformatted(str.data(), str.data() + str.size());
+}
+
+} // namespace ImGui