1 // Copyright (c) 2020- PPSSPP Project.
2
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License 2.0 for more details.
11
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18 #include <set>
19
20 #include "Common/Data/Format/IniFile.h"
21 #include "Common/File/FileUtil.h"
22 #include "Common/File/DirListing.h"
23 #include "Common/Serialize/SerializeFuncs.h"
24 #include "Core/Config.h"
25 #include "Core/MemMap.h"
26 #include "Core/System.h"
27 #include "Core/ELF/ParamSFO.h"
28 #include "Core/HLE/Plugins.h"
29 #include "Core/HLE/sceKernelModule.h"
30
31 namespace HLEPlugins {
32
33 static bool anyEnabled = false;
34 static std::vector<std::string> prxPlugins;
35
36 enum class PluginType {
37 INVALID = 0,
38 PRX,
39 };
40
41 struct PluginInfo {
42 PluginType type;
43 std::string filename;
44 int version;
45 uint32_t memory;
46 };
47
ReadPluginIni(const std::string & subdir,IniFile & ini)48 static PluginInfo ReadPluginIni(const std::string &subdir, IniFile &ini) {
49 PluginInfo info;
50
51 auto options = ini.GetOrCreateSection("options");
52 std::string value;
53
54 if (options->Get("type", &value, "")) {
55 if (value == "prx") {
56 info.type = PluginType::PRX;
57 }
58 }
59
60 if (options->Get("filename", &value, "")) {
61 info.filename = "ms0:/PSP/PLUGINS/" + subdir + "/" + value;
62 } else {
63 info.type = PluginType::INVALID;
64 }
65
66 options->Get("version", &info.version, 0);
67 options->Get("memory", &info.memory, 0);
68 if (info.memory > 93) {
69 ERROR_LOG(SYSTEM, "Plugin memory too high, using 93 MB");
70 info.memory = 93;
71 }
72
73 if (info.version == 0) {
74 ERROR_LOG(SYSTEM, "Plugin without version ignored: %s", subdir.c_str());
75 info.type = PluginType::INVALID;
76 info.memory = 0;
77 } else if (info.type == PluginType::INVALID && !info.filename.empty()) {
78 ERROR_LOG(SYSTEM, "Plugin without valid type: %s", subdir.c_str());
79 }
80
81 return info;
82 }
83
FindPlugins(const std::string & gameID,const std::string & lang)84 static std::vector<PluginInfo> FindPlugins(const std::string &gameID, const std::string &lang) {
85 std::vector<File::FileInfo> pluginDirs;
86 GetFilesInDir(GetSysDirectory(DIRECTORY_PLUGINS), &pluginDirs);
87
88 std::vector<PluginInfo> found;
89 for (const auto &subdir : pluginDirs) {
90 const Path &subdirFullName = subdir.fullName;
91 if (!subdir.isDirectory || !File::Exists(subdirFullName / "plugin.ini"))
92 continue;
93
94 IniFile ini;
95 if (!ini.Load(subdirFullName / "plugin.ini")) {
96 ERROR_LOG(SYSTEM, "Failed to load plugin ini: %s/plugin.ini", subdirFullName.c_str());
97 continue;
98 }
99
100 std::set<std::string> matches;
101
102 std::string gameIni;
103 if (ini.GetOrCreateSection("games")->Get("ALL", &gameIni, "")) {
104 if (!strcasecmp(gameIni.c_str(), "true")) {
105 matches.insert("plugin.ini");
106 } else if (!gameIni.empty()) {
107 matches.insert(gameIni);
108 }
109 }
110
111 if (ini.GetOrCreateSection("games")->Get(gameID.c_str(), &gameIni, "")) {
112 if (!strcasecmp(gameIni.c_str(), "true")) {
113 matches.insert("plugin.ini");
114 } else if (!gameIni.empty()) {
115 matches.insert(gameIni);
116 }
117 }
118
119 std::set<std::string> langMatches;
120 for (const std::string &subini : matches) {
121 if (!ini.Load(subdirFullName / subini)) {
122 ERROR_LOG(SYSTEM, "Failed to load plugin ini: %s/%s", subdirFullName.c_str(), subini.c_str());
123 continue;
124 }
125
126 found.push_back(ReadPluginIni(subdir.name, ini));
127
128 if (ini.GetOrCreateSection("lang")->Get(lang.c_str(), &gameIni, "")) {
129 if (!gameIni.empty() && matches.find(gameIni) == matches.end()) {
130 langMatches.insert(gameIni);
131 }
132 }
133 }
134
135 for (const std::string &subini : langMatches) {
136 if (!ini.Load(subdirFullName / subini)) {
137 ERROR_LOG(SYSTEM, "Failed to load plugin ini: %s/%s", subdirFullName.c_str(), subini.c_str());
138 continue;
139 }
140
141 found.push_back(ReadPluginIni(subdir.name, ini));
142 }
143 }
144
145 return found;
146 }
147
Init()148 void Init() {
149 if (!g_Config.bLoadPlugins) {
150 return;
151 }
152
153 std::vector<PluginInfo> plugins = FindPlugins(g_paramSFO.GetDiscID(), g_Config.sLanguageIni);
154 for (auto &plugin : plugins) {
155 if (plugin.memory << 20 > Memory::g_MemorySize) {
156 Memory::g_MemorySize = plugin.memory << 20;
157 anyEnabled = true;
158 }
159
160 if (plugin.type == PluginType::PRX) {
161 prxPlugins.push_back(plugin.filename);
162 anyEnabled = true;
163 }
164 }
165 }
166
Load()167 bool Load() {
168 bool started = false;
169 for (const std::string &filename : prxPlugins) {
170 std::string error_string = "";
171 SceUID module = KernelLoadModule(filename, &error_string);
172 if (!error_string.empty()) {
173 ERROR_LOG(SYSTEM, "Unable to load plugin %s: %s", filename.c_str(), error_string.c_str());
174 continue;
175 }
176 if (module < 0) {
177 ERROR_LOG(SYSTEM, "Unable to load plugin %s: %08x", filename.c_str(), module);
178 continue;
179 }
180
181 int ret = KernelStartModule(module, 0, 0, 0, nullptr, nullptr);
182 if (ret < 0) {
183 ERROR_LOG(SYSTEM, "Unable to start plugin %s: %08x", filename.c_str(), ret);
184 }
185
186 INFO_LOG(SYSTEM, "Loaded plugin: %s", filename.c_str());
187 started = true;
188 }
189
190 return started;
191 }
192
Unload()193 void Unload() {
194 // Nothing to do here, for now.
195 }
196
Shutdown()197 void Shutdown() {
198 prxPlugins.clear();
199 anyEnabled = false;
200 }
201
DoState(PointerWrap & p)202 void DoState(PointerWrap &p) {
203 auto s = p.Section("Plugins", 0, 1);
204 if (!s)
205 return;
206
207 // Remember if any were enabled.
208 Do(p, anyEnabled);
209 }
210
HasEnabled()211 bool HasEnabled() {
212 return anyEnabled;
213 }
214
215 };
216