diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8ac71497648d..e83e590f44b7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1565,6 +1565,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/HLE/KernelWaitHelpers.h
Core/HLE/KUBridge.h
Core/HLE/KUBridge.cpp
+ Core/HLE/Plugins.h
+ Core/HLE/Plugins.cpp
Core/HLE/ThreadQueueList.h
Core/HLE/__sceAudio.cpp
Core/HLE/__sceAudio.h
diff --git a/Core/Config.cpp b/Core/Config.cpp
index 3164c9af9b5e..48e8750fd695 100644
--- a/Core/Config.cpp
+++ b/Core/Config.cpp
@@ -501,6 +501,7 @@ static ConfigSetting generalSettings[] = {
ConfigSetting("FullscreenOnDoubleclick", &g_Config.bFullscreenOnDoubleclick, true, false, false),
ReportedConfigSetting("MemStickInserted", &g_Config.bMemStickInserted, true, true, true),
+ ConfigSetting("LoadPlugins", &g_Config.bLoadPlugins, false, true, true),
ConfigSetting(false),
};
diff --git a/Core/Config.h b/Core/Config.h
index 3072efdbc333..b8eb5399dd59 100644
--- a/Core/Config.h
+++ b/Core/Config.h
@@ -123,6 +123,7 @@ struct Config {
std::string sRemoteISOSubdir;
bool bRemoteDebuggerOnStartup;
bool bMemStickInserted;
+ bool bLoadPlugins;
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/Core.vcxproj b/Core/Core.vcxproj
index d690042fb949..d7e7d3343e5b 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -384,6 +384,7 @@
+
@@ -918,6 +919,7 @@
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 1a63c8b4a267..53b8252fe349 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -758,6 +758,9 @@
HW
+
+ HLE
+
@@ -1406,6 +1409,9 @@
Core
+
+ HLE
+
diff --git a/Core/HLE/Plugins.cpp b/Core/HLE/Plugins.cpp
new file mode 100644
index 000000000000..5f22e9fc37a3
--- /dev/null
+++ b/Core/HLE/Plugins.cpp
@@ -0,0 +1,213 @@
+// Copyright (c) 2020- PPSSPP Project.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 2.0 or later versions.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License 2.0 for more details.
+
+// A copy of the GPL 2.0 should have been included with the program.
+// If not, see http://www.gnu.org/licenses/
+
+// Official git repository and contact information can be found at
+// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#include
+#include "file/file_util.h"
+#include "file/ini_file.h"
+#include "Common/FileUtil.h"
+#include "Common/Serialize/SerializeFuncs.h"
+#include "Core/Config.h"
+#include "Core/MemMap.h"
+#include "Core/System.h"
+#include "Core/ELF/ParamSFO.h"
+#include "Core/HLE/Plugins.h"
+#include "Core/HLE/sceKernelModule.h"
+
+namespace HLEPlugins {
+
+static bool anyEnabled = false;
+static std::vector prxPlugins;
+
+enum class PluginType {
+ INVALID = 0,
+ PRX,
+};
+
+struct PluginInfo {
+ PluginType type;
+ std::string filename;
+ int version;
+ uint32_t memory;
+};
+
+static PluginInfo ReadPluginIni(const std::string &subdir, IniFile &ini) {
+ PluginInfo info;
+
+ auto options = ini.GetOrCreateSection("options");
+ std::string value;
+
+ if (options->Get("type", &value, "")) {
+ if (value == "prx") {
+ info.type = PluginType::PRX;
+ }
+ }
+
+ if (options->Get("filename", &value, "")) {
+ info.filename = "ms0:/PSP/PLUGINS/" + subdir + "/" + value;
+ } else {
+ info.type = PluginType::INVALID;
+ }
+
+ options->Get("version", &info.version, 0);
+ options->Get("memory", &info.memory, 0);
+ if (info.memory > 93) {
+ ERROR_LOG(SYSTEM, "Plugin memory too high, using 93 MB");
+ info.memory = 93;
+ }
+
+ if (info.version == 0) {
+ ERROR_LOG(SYSTEM, "Plugin without version ignored: %s", subdir.c_str());
+ info.type = PluginType::INVALID;
+ info.memory = 0;
+ } else if (info.type == PluginType::INVALID && !info.filename.empty()) {
+ ERROR_LOG(SYSTEM, "Plugin without valid type: %s", subdir.c_str());
+ }
+
+ return info;
+}
+
+static std::vector FindPlugins(const std::string &gameID, const std::string &lang) {
+ std::vector pluginDirs;
+ getFilesInDir(GetSysDirectory(DIRECTORY_PLUGINS).c_str(), &pluginDirs);
+
+ std::vector found;
+ for (auto subdir : pluginDirs) {
+ if (!subdir.isDirectory || !File::Exists(subdir.fullName + "/plugin.ini"))
+ continue;
+
+ IniFile ini;
+ if (!ini.Load(subdir.fullName + "/plugin.ini")) {
+ ERROR_LOG(SYSTEM, "Failed to load plugin ini: %s/plugin.ini", subdir.fullName.c_str());
+ continue;
+ }
+
+ std::set matches;
+
+ std::string gameIni;
+ if (ini.GetOrCreateSection("games")->Get("ALL", &gameIni, "")) {
+ if (!strcasecmp(gameIni.c_str(), "true")) {
+ matches.insert("plugin.ini");
+ } else if (!gameIni.empty()) {
+ matches.insert(gameIni);
+ }
+ }
+
+ if (ini.GetOrCreateSection("games")->Get(gameID.c_str(), &gameIni, "")) {
+ if (!strcasecmp(gameIni.c_str(), "true")) {
+ matches.insert("plugin.ini");
+ } else if (!gameIni.empty()) {
+ matches.insert(gameIni);
+ }
+ }
+
+ std::set langMatches;
+ for (const std::string &subini : matches) {
+ if (!ini.Load(subdir.fullName + "/" + subini)) {
+ ERROR_LOG(SYSTEM, "Failed to load plugin ini: %s/%s", subdir.fullName.c_str(), subini.c_str());
+ continue;
+ }
+
+ found.push_back(ReadPluginIni(subdir.name, ini));
+
+ if (ini.GetOrCreateSection("lang")->Get(lang.c_str(), &gameIni, "")) {
+ if (!gameIni.empty() && matches.find(gameIni) == matches.end()) {
+ langMatches.insert(gameIni);
+ }
+ }
+ }
+
+ for (const std::string &subini : langMatches) {
+ if (!ini.Load(subdir.fullName + "/" + subini)) {
+ ERROR_LOG(SYSTEM, "Failed to load plugin ini: %s/%s", subdir.fullName.c_str(), subini.c_str());
+ continue;
+ }
+
+ found.push_back(ReadPluginIni(subdir.name, ini));
+ }
+ }
+
+ return found;
+}
+
+void Init() {
+ if (!g_Config.bLoadPlugins) {
+ return;
+ }
+
+ std::vector plugins = FindPlugins(g_paramSFO.GetDiscID(), g_Config.sLanguageIni);
+ for (auto &plugin : plugins) {
+ if (plugin.memory << 20 > Memory::g_MemorySize) {
+ Memory::g_MemorySize = plugin.memory << 20;
+ anyEnabled = true;
+ }
+
+ if (plugin.type == PluginType::PRX) {
+ prxPlugins.push_back(plugin.filename);
+ anyEnabled = true;
+ }
+ }
+}
+
+bool Load() {
+ bool started = false;
+ for (const std::string &filename : prxPlugins) {
+ std::string error_string = "";
+ SceUID module = KernelLoadModule(filename, &error_string);
+ if (!error_string.empty()) {
+ ERROR_LOG(SYSTEM, "Unable to load plugin %s: %s", filename.c_str(), error_string.c_str());
+ continue;
+ }
+ if (module < 0) {
+ ERROR_LOG(SYSTEM, "Unable to load plugin %s: %08x", filename.c_str(), module);
+ continue;
+ }
+
+ int ret = KernelStartModule(module, 0, 0, 0, nullptr, nullptr);
+ if (ret < 0) {
+ ERROR_LOG(SYSTEM, "Unable to start plugin %s: %08x", filename.c_str(), ret);
+ }
+
+ INFO_LOG(SYSTEM, "Loaded plugin: %s", filename.c_str());
+ started = true;
+ }
+
+ return started;
+}
+
+void Unload() {
+ // Nothing to do here, for now.
+}
+
+void Shutdown() {
+ prxPlugins.clear();
+ anyEnabled = false;
+}
+
+void DoState(PointerWrap &p) {
+ auto s = p.Section("Plugins", 0, 1);
+ if (!s)
+ return;
+
+ // Remember if any were enabled.
+ Do(p, anyEnabled);
+}
+
+bool HasEnabled() {
+ return anyEnabled;
+}
+
+};
diff --git a/Core/HLE/Plugins.h b/Core/HLE/Plugins.h
new file mode 100644
index 000000000000..6db2966d9867
--- /dev/null
+++ b/Core/HLE/Plugins.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2020- PPSSPP Project.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 2.0 or later versions.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License 2.0 for more details.
+
+// A copy of the GPL 2.0 should have been included with the program.
+// If not, see http://www.gnu.org/licenses/
+
+// Official git repository and contact information can be found at
+// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#pragma once
+
+class PointerWrap;
+
+namespace HLEPlugins {
+
+void Init();
+void Shutdown();
+
+bool Load();
+void Unload();
+
+void DoState(PointerWrap &p);
+
+bool HasEnabled();
+
+};
diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp
index 5f792b10332f..3f34ea3d388c 100644
--- a/Core/HLE/sceKernelModule.cpp
+++ b/Core/HLE/sceKernelModule.cpp
@@ -32,6 +32,7 @@
#include "Core/HLE/HLE.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/HLETables.h"
+#include "Core/HLE/Plugins.h"
#include "Core/HLE/ReplaceTables.h"
#include "Core/Reporting.h"
#include "Core/Host.h"
@@ -331,6 +332,8 @@ class PSPModule : public KernelObject {
}
}
+ HLEPlugins::DoState(p);
+
RebuildImpExpModuleNames();
}
@@ -529,6 +532,7 @@ void __KernelModuleShutdown()
{
loadedModules.clear();
MIPSAnalyst::Reset();
+ HLEPlugins::Unload();
}
// Sometimes there are multiple LO16's or HI16's per pair, even though the ABI says nothing of this.
@@ -1541,6 +1545,27 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
return module;
}
+SceUID KernelLoadModule(const std::string &filename, std::string *error_string) {
+ PSPFileInfo info = pspFileSystem.GetFileInfo(filename);
+ if (!info.exists)
+ return SCE_KERNEL_ERROR_NOFILE;
+
+ std::vector buffer;
+ buffer.resize((size_t)info.size);
+
+ u32 handle = pspFileSystem.OpenFile(filename, FILEACCESS_READ);
+ pspFileSystem.ReadFile(handle, &buffer[0], info.size);
+ pspFileSystem.CloseFile(handle);
+
+ u32 error = SCE_KERNEL_ERROR_ILLEGAL_OBJECT;
+ u32 magic;
+ PSPModule *module = __KernelLoadELFFromPtr(&buffer[0], buffer.size(), 0, false, error_string, &magic, error);
+
+ if (module == nullptr)
+ return error;
+ return module->GetUID();
+}
+
static PSPModule *__KernelLoadModule(u8 *fileptr, size_t fileSize, SceKernelLMOption *options, std::string *error_string) {
PSPModule *module = nullptr;
// Check for PBP
@@ -1600,6 +1625,11 @@ static void __KernelStartModule(PSPModule *m, int args, const char *argp, SceKer
SceUID threadID = __KernelSetupRootThread(m->GetUID(), args, argp, options->priority, options->stacksize, options->attribute);
__KernelSetThreadRA(threadID, NID_MODULERETURN);
+
+ if (HLEPlugins::Load()) {
+ KernelRotateThreadReadyQueue(0);
+ __KernelReSchedule("Started plugins");
+ }
}
@@ -1960,12 +1990,71 @@ static u32 sceKernelLoadModuleNpDrm(const char *name, u32 flags, u32 optionAddr)
return sceKernelLoadModule(name, flags, optionAddr);
}
+int KernelStartModule(SceUID moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, SceKernelSMOption *smoption, bool *needsWait) {
+ if (needsWait) {
+ *needsWait = false;
+ }
+
+ u32 error;
+ PSPModule *module = kernelObjects.Get(moduleId, error);
+ if (!module) {
+ return error;
+ }
+
+ u32 priority = 0x20;
+ u32 stacksize = 0x40000;
+ int attribute = module->nm.attribute;
+ u32 entryAddr = module->nm.entry_addr;
+
+ if (module->nm.module_start_func != 0 && module->nm.module_start_func != (u32)-1) {
+ entryAddr = module->nm.module_start_func;
+ if (module->nm.module_start_thread_attr != 0)
+ attribute = module->nm.module_start_thread_attr;
+ } else if (entryAddr == (u32)-1 || entryAddr == module->memoryBlockAddr - 1) {
+ if (smoption) {
+ // TODO: Does sceKernelStartModule() really give an error when no entry only if you pass options?
+ attribute = smoption->attribute;
+ } else {
+ // TODO: Why are we just returning the module ID in this case?
+ WARN_LOG(SCEMODULE, "sceKernelStartModule(): module has no start or entry func");
+ module->nm.status = MODULE_STATUS_STARTED;
+ return moduleId;
+ }
+ }
+
+ if (Memory::IsValidAddress(entryAddr)) {
+ if (smoption && smoption->priority > 0) {
+ priority = smoption->priority;
+ } else if (module->nm.module_start_thread_priority > 0) {
+ priority = module->nm.module_start_thread_priority;
+ }
+
+ if (smoption && smoption->stacksize > 0) {
+ stacksize = smoption->stacksize;
+ } else if (module->nm.module_start_thread_stacksize > 0) {
+ stacksize = module->nm.module_start_thread_stacksize;
+ }
+
+ SceUID threadID = __KernelCreateThread(module->nm.name, moduleId, entryAddr, priority, stacksize, attribute, 0, (module->nm.attribute & 0x1000) != 0);
+ __KernelStartThreadValidate(threadID, argsize, argAddr);
+ __KernelSetThreadRA(threadID, NID_MODULERETURN);
+
+ if (needsWait) {
+ *needsWait = true;
+ }
+ } else if (entryAddr == 0) {
+ INFO_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x): no entry address", moduleId, argsize, argAddr, returnValueAddr);
+ module->nm.status = MODULE_STATUS_STARTED;
+ } else {
+ ERROR_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x): invalid entry address", moduleId, argsize, argAddr, returnValueAddr);
+ return -1;
+ }
+
+ return moduleId;
+}
+
static void sceKernelStartModule(u32 moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, u32 optionAddr)
{
- u32 priority = 0x20;
- u32 stacksize = 0x40000;
- u32 attr = 0;
- int stackPartition = 0;
SceKernelSMOption smoption = {0};
if (optionAddr) {
Memory::ReadStruct(optionAddr, &smoption);
@@ -1994,71 +2083,19 @@ static void sceKernelStartModule(u32 moduleId, u32 argsize, u32 argAddr, u32 ret
INFO_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x,%08x)",
moduleId,argsize,argAddr,returnValueAddr,optionAddr);
- int attribute = module->nm.attribute;
- u32 entryAddr = module->nm.entry_addr;
-
- if (module->nm.module_start_func != 0 && module->nm.module_start_func != (u32)-1)
- {
- entryAddr = module->nm.module_start_func;
- if (module->nm.module_start_thread_attr != 0)
- attribute = module->nm.module_start_thread_attr;
- }
- else if ((entryAddr == (u32)-1) || entryAddr == module->memoryBlockAddr - 1)
- {
- if (optionAddr)
- {
- // TODO: Does sceKernelStartModule() really give an error when no entry only if you pass options?
- attribute = smoption.attribute;
- }
- else
- {
- // TODO: Why are we just returning the module ID in this case?
- WARN_LOG(SCEMODULE, "sceKernelStartModule(): module has no start or entry func");
- module->nm.status = MODULE_STATUS_STARTED;
- RETURN(moduleId);
- return;
- }
- }
-
- if (Memory::IsValidAddress(entryAddr))
- {
- if ((optionAddr) && smoption.priority > 0) {
- priority = smoption.priority;
- } else if (module->nm.module_start_thread_priority > 0) {
- priority = module->nm.module_start_thread_priority;
- }
-
- if ((optionAddr) && (smoption.stacksize > 0)) {
- stacksize = smoption.stacksize;
- } else if (module->nm.module_start_thread_stacksize > 0) {
- stacksize = module->nm.module_start_thread_stacksize;
- }
+ bool needsWait;
+ int ret = KernelStartModule(moduleId, argsize, argAddr, returnValueAddr, optionAddr ? &smoption : nullptr, &needsWait);
- SceUID threadID = __KernelCreateThread(module->nm.name, moduleId, entryAddr, priority, stacksize, attribute, 0, (module->nm.attribute & 0x1000) != 0);
- __KernelStartThreadValidate(threadID, argsize, argAddr);
- __KernelSetThreadRA(threadID, NID_MODULERETURN);
+ if (needsWait) {
__KernelWaitCurThread(WAITTYPE_MODULE, moduleId, 1, 0, false, "started module");
const ModuleWaitingThread mwt = {__KernelGetCurThread(), returnValueAddr};
module->nm.status = MODULE_STATUS_STARTING;
module->waitingThreads.push_back(mwt);
}
- else if (entryAddr == 0)
- {
- INFO_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x,%08x): no entry address",
- moduleId,argsize,argAddr,returnValueAddr,optionAddr);
- module->nm.status = MODULE_STATUS_STARTED;
- }
- else
- {
- ERROR_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x,%08x): invalid entry address",
- moduleId,argsize,argAddr,returnValueAddr,optionAddr);
- RETURN(-1);
- return;
- }
- }
- RETURN(moduleId);
+ RETURN(ret);
+ }
}
static u32 sceKernelStopModule(u32 moduleId, u32 argSize, u32 argAddr, u32 returnValueAddr, u32 optionAddr)
diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h
index 99e5473b056d..a22715347776 100644
--- a/Core/HLE/sceKernelModule.h
+++ b/Core/HLE/sceKernelModule.h
@@ -33,6 +33,7 @@ struct PspModuleInfo {
};
class PointerWrap;
+struct SceKernelSMOption;
KernelObject *__KernelModuleObject();
void __KernelModuleDoState(PointerWrap &p);
@@ -44,6 +45,8 @@ bool __KernelLoadGEDump(const std::string &base_filename, std::string *error_str
bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_string);
void __KernelGPUReplay();
void __KernelReturnFromModuleFunc();
+SceUID KernelLoadModule(const std::string &filename, std::string *error_string);
+int KernelStartModule(SceUID moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, SceKernelSMOption *smoption, bool *needsWait);
u32 hleKernelStopUnloadSelfModuleWithOrWithoutStatus(u32 exitCode, u32 argSize, u32 argp, u32 statusAddr, u32 optionAddr, bool WithStatus);
u32 sceKernelFindModuleByUID(u32 uid);
diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp
index cade794e0808..6bd3d2d43414 100644
--- a/Core/HLE/sceKernelThread.cpp
+++ b/Core/HLE/sceKernelThread.cpp
@@ -2246,10 +2246,7 @@ bool __KernelIsDispatchEnabled()
return dispatchEnabled && __InterruptsEnabled();
}
-int sceKernelRotateThreadReadyQueue(int priority)
-{
- VERBOSE_LOG(SCEKERNEL, "sceKernelRotateThreadReadyQueue(%x)", priority);
-
+int KernelRotateThreadReadyQueue(int priority) {
PSPThread *cur = __GetCurrentThread();
// 0 is special, it means "my current priority."
@@ -2259,11 +2256,9 @@ int sceKernelRotateThreadReadyQueue(int priority)
if (priority <= 0x07 || priority > 0x77)
return SCE_KERNEL_ERROR_ILLEGAL_PRIORITY;
- if (!threadReadyQueue.empty(priority))
- {
+ if (!threadReadyQueue.empty(priority)) {
// In other words, yield to everyone else.
- if (cur->nt.currentPriority == priority)
- {
+ if (cur->nt.currentPriority == priority) {
threadReadyQueue.push_back(priority, currentThread);
cur->nt.status = (cur->nt.status & ~THREADSTATUS_RUNNING) | THREADSTATUS_READY;
}
@@ -2272,11 +2267,18 @@ int sceKernelRotateThreadReadyQueue(int priority)
threadReadyQueue.rotate(priority);
}
- hleReSchedule("rotatethreadreadyqueue");
- hleEatCycles(250);
return 0;
}
+int sceKernelRotateThreadReadyQueue(int priority) {
+ int result = KernelRotateThreadReadyQueue(priority);
+ if (result == 0) {
+ hleReSchedule("rotatethreadreadyqueue");
+ hleEatCycles(250);
+ }
+ return hleLogSuccessVerboseI(SCEKERNEL, result);
+}
+
int sceKernelDeleteThread(int threadID) {
if (threadID == 0 || threadID == currentThread) {
ERROR_LOG(SCEKERNEL, "sceKernelDeleteThread(%i): cannot delete current thread", threadID);
diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h
index bb08bb3b6687..65dce5ff54f4 100644
--- a/Core/HLE/sceKernelThread.h
+++ b/Core/HLE/sceKernelThread.h
@@ -57,6 +57,7 @@ u32 sceKernelReferThreadRunStatus(u32 uid, u32 statusPtr);
int sceKernelReleaseWaitThread(SceUID threadID);
int sceKernelChangeCurrentThreadAttr(u32 clearAttr, u32 setAttr);
int sceKernelRotateThreadReadyQueue(int priority);
+int KernelRotateThreadReadyQueue(int priority);
int sceKernelCheckThreadStack();
int sceKernelSuspendThread(SceUID threadID);
int sceKernelResumeThread(SceUID threadID);
diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp
index 28c9609b01b7..1829fd8a9725 100644
--- a/Core/Reporting.cpp
+++ b/Core/Reporting.cpp
@@ -34,6 +34,7 @@
#include "Core/System.h"
#include "Core/FileSystems/BlockDevices.h"
#include "Core/FileSystems/MetaFileSystem.h"
+#include "Core/HLE/Plugins.h"
#include "Core/HLE/sceDisplay.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/ELF/ParamSFO.h"
@@ -497,7 +498,7 @@ namespace Reporting
bool IsSupported()
{
// Disabled when using certain hacks, because they make for poor reports.
- if (CheatsInEffect())
+ if (CheatsInEffect() || HLEPlugins::HasEnabled())
return false;
if (g_Config.iLockedCPUSpeed != 0)
return false;
diff --git a/Core/System.cpp b/Core/System.cpp
index b3b1e30756d3..ebf3eaaaa215 100644
--- a/Core/System.cpp
+++ b/Core/System.cpp
@@ -48,6 +48,7 @@
#include "Core/Host.h"
#include "Core/System.h"
#include "Core/HLE/HLE.h"
+#include "Core/HLE/Plugins.h"
#include "Core/HLE/ReplaceTables.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelMemory.h"
@@ -286,6 +287,7 @@ bool CPU_Init() {
// likely to collide with any commercial ones.
coreParameter.compat.Load(discID);
+ HLEPlugins::Init();
if (!Memory::Init()) {
// We're screwed.
return false;
@@ -351,6 +353,7 @@ void CPU_Shutdown() {
pspFileSystem.Shutdown();
mipsr4k.Shutdown();
Memory::Shutdown();
+ HLEPlugins::Shutdown();
delete loadedFile;
loadedFile = nullptr;
@@ -592,6 +595,8 @@ std::string GetSysDirectory(PSPDirectories directoryType) {
return g_Config.memStickDirectory + "PSP/SYSTEM/CACHE/";
case DIRECTORY_TEXTURES:
return g_Config.memStickDirectory + "PSP/TEXTURES/";
+ case DIRECTORY_PLUGINS:
+ return g_Config.memStickDirectory + "PSP/PLUGINS/";
case DIRECTORY_APP_CACHE:
if (!g_Config.appCacheDirectory.empty()) {
return g_Config.appCacheDirectory;
diff --git a/Core/System.h b/Core/System.h
index 0d21b0d1bb05..fac6b1676712 100644
--- a/Core/System.h
+++ b/Core/System.h
@@ -46,6 +46,7 @@ enum PSPDirectories {
DIRECTORY_SAVESTATE,
DIRECTORY_CACHE,
DIRECTORY_TEXTURES,
+ DIRECTORY_PLUGINS,
DIRECTORY_APP_CACHE, // Use the OS app cache if available
DIRECTORY_VIDEO,
DIRECTORY_AUDIO
diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp
index 047111665019..ffd0051677d8 100644
--- a/UI/NativeApp.cpp
+++ b/UI/NativeApp.cpp
@@ -361,6 +361,7 @@ void CreateDirectoriesAndroid() {
File::CreateFullPath(GetSysDirectory(DIRECTORY_GAME));
File::CreateFullPath(GetSysDirectory(DIRECTORY_SYSTEM));
File::CreateFullPath(GetSysDirectory(DIRECTORY_TEXTURES));
+ File::CreateFullPath(GetSysDirectory(DIRECTORY_PLUGINS));
// Avoid media scanners in PPSSPP_STATE and SAVEDATA directories,
// and in the root PSP directory as well.
@@ -368,6 +369,7 @@ void CreateDirectoriesAndroid() {
File::CreateEmptyFile(GetSysDirectory(DIRECTORY_SAVEDATA) + ".nomedia");
File::CreateEmptyFile(GetSysDirectory(DIRECTORY_SYSTEM) + ".nomedia");
File::CreateEmptyFile(GetSysDirectory(DIRECTORY_TEXTURES) + ".nomedia");
+ File::CreateEmptyFile(GetSysDirectory(DIRECTORY_PLUGINS) + ".nomedia");
}
static void CheckFailedGPUBackends() {
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj
index 31f2327547d7..7090fca69587 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj
+++ b/UWP/CoreUWP/CoreUWP.vcxproj
@@ -561,6 +561,7 @@
+
@@ -804,6 +805,7 @@
+
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters
index 6538bd1df148..1082a1416050 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj.filters
+++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters
@@ -89,6 +89,7 @@
+
@@ -738,6 +739,7 @@
+
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 36a29682f0c3..3e2c6367c0f6 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -346,6 +346,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/HLE/ReplaceTables.cpp \
$(SRC)/Core/HLE/HLE.cpp \
$(SRC)/Core/HLE/KUBridge.cpp \
+ $(SRC)/Core/HLE/Plugins.cpp \
$(SRC)/Core/HLE/sceAdler.cpp \
$(SRC)/Core/HLE/sceAtrac.cpp \
$(SRC)/Core/HLE/__sceAudio.cpp.arm \
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index d92fd5446828..bf327b35a318 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -368,6 +368,7 @@ SOURCES_CXX += $(NATIVEDIR)/math/dataconv.cpp \
$(COREDIR)/Font/PGF.cpp \
$(COREDIR)/HLE/HLE.cpp \
$(COREDIR)/HLE/KUBridge.cpp \
+ $(COREDIR)/HLE/Plugins.cpp \
$(COREDIR)/HLE/sceSha256.cpp \
$(COREDIR)/HLE/sceG729.cpp \
$(COREDIR)/HLE/sceSfmt19937.cpp \