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