// 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; int 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; } } } void Load() { 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()); return; } if (module < 0) { ERROR_LOG(SYSTEM, "Unable to load plugin %s: %08x", filename.c_str(), module); return; } 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()); } } 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; } };