Ark Server API (ASE) - Wiki
Loading...
Searching...
No Matches
PluginManager.cpp
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <sstream>
6
7#include <Logger/Logger.h>
8#include <Tools.h>
9
10#include "../Helpers.h"
11#include "../IBaseApi.h"
12
13namespace API
14{
16 {
17 static PluginManager instance;
18 return instance;
19 }
20
22 {
23 namespace fs = std::filesystem;
24
25 const std::string dir_path = Tools::GetCurrentDir() + "/" + game_api->GetApiName() + "/Plugins";
26
27 auto result = nlohmann::json({});
28
29 for (const auto& dir_name : fs::directory_iterator(dir_path))
30 {
31 const auto& path = dir_name.path();
32 const auto filename = path.filename().stem().generic_string();
33
34 try
35 {
36 const auto plugin_pdb_config = ReadPluginPDBConfig(filename);
37 MergePdbConfig(result, plugin_pdb_config);
38 }
39 catch (const std::exception& error)
40 {
41 Log::GetLog()->warn("({}) {}", __FUNCTION__, error.what());
42 }
43 }
44
45 return result;
46 }
47
48 nlohmann::json PluginManager::ReadPluginPDBConfig(const std::string& plugin_name)
49 {
50 namespace fs = std::filesystem;
51
52 auto plugin_pdb_config = nlohmann::json({});
53
54 const std::string dir_path = Tools::GetCurrentDir() + "/" + game_api->GetApiName() + "/Plugins/" + plugin_name;
55 const std::string config_path = dir_path + "/PdbConfig.json";
56
57 if (!fs::exists(config_path))
58 {
59 return plugin_pdb_config;
60 }
61
62 std::ifstream file{config_path};
63 if (file.is_open())
64 {
65 file >> plugin_pdb_config;
66 file.close();
67 }
68
69 return plugin_pdb_config;
70 }
71
73 {
74 nlohmann::json config = nlohmann::json::object({});
75
76 const std::string config_path = Tools::GetCurrentDir() + "/config.json";
77 std::ifstream file{config_path};
78 if (!file.is_open())
79 {
80 return config;
81 }
82
83 file >> config;
84 file.close();
85
86 return config;
87 }
88
90 {
91 namespace fs = std::filesystem;
92
93 const std::string dir_path = Tools::GetCurrentDir() + "/" + game_api->GetApiName() + "/Plugins";
94
95 for (const auto& dir_name : fs::directory_iterator(dir_path))
96 {
97 const auto& path = dir_name.path();
98 if (!is_directory(path))
99 {
100 continue;
101 }
102
103 const auto filename = path.filename().stem().generic_string();
104
105 const std::string dir_file_path = Tools::GetCurrentDir() + "/" + game_api->GetApiName() + "/Plugins/" +
106 filename;
107 const std::string full_dll_path = dir_file_path + "/" + filename + ".dll";
108 const std::string new_full_dll_path = dir_file_path + "/" + filename + ".dll.ArkApi";
109
110 try
111 {
112 // Loads the new .dll.ArkApi if it exists on startup as well
113 if (fs::exists(new_full_dll_path))
114 {
115 copy_file(new_full_dll_path, full_dll_path, fs::copy_options::overwrite_existing);
116 fs::remove(new_full_dll_path);
117 }
118
119 std::stringstream stream;
120
121 std::shared_ptr<Plugin>& plugin = LoadPlugin(filename);
122
123 stream << "Loaded plugin " << (plugin->full_name.empty() ? plugin->name : plugin->full_name) << " V" <<
124 plugin->version << " (" << plugin->description << ")";
125
126 Log::GetLog()->info(stream.str());
127 }
128 catch (const std::exception& error)
129 {
130 Log::GetLog()->warn("({}) {}", __FUNCTION__, error.what());
131 }
132 }
133
135
136 // Set auto plugins reloading
137 auto settings = ReadSettingsConfig();
138
139 enable_plugin_reload_ = settings["settings"].value("AutomaticPluginReloading", false);
141 {
142 reload_sleep_seconds_ = settings["settings"].value("AutomaticPluginReloadSeconds", 5);
143 save_world_before_reload_ = settings["settings"].value("SaveWorldBeforePluginReload", true);
144 }
145
146 Log::GetLog()->info("Loaded all plugins\n");
147 }
148
149 std::shared_ptr<Plugin>& PluginManager::LoadPlugin(const std::string& plugin_name) noexcept(false)
150 {
151 namespace fs = std::filesystem;
152
153 const std::string dir_path = Tools::GetCurrentDir() + "/" + game_api->GetApiName() + "/Plugins/" + plugin_name;
154 const std::string full_dll_path = dir_path + "/" + plugin_name + ".dll";
155
156 if (!fs::exists(full_dll_path))
157 {
158 throw std::runtime_error("Plugin " + plugin_name + " does not exist");
159 }
160
161 if (IsPluginLoaded(plugin_name))
162 {
163 throw std::runtime_error("Plugin " + plugin_name + " was already loaded");
164 }
165
166 auto plugin_info = ReadPluginInfo(plugin_name);
167
168 // Check version
169 const auto required_version = static_cast<float>(plugin_info["MinApiVersion"]);
170 if (required_version != .0f && game_api->GetVersion() < required_version)
171 {
172 throw std::runtime_error("Plugin " + plugin_name + " requires newer API version!");
173 }
174
175 HINSTANCE h_module = LoadLibraryA(full_dll_path.c_str());
176 if (h_module == nullptr)
177 {
178 throw std::runtime_error(
179 "Failed to load plugin - " + plugin_name + "\nError code: " + std::to_string(GetLastError()));
180 }
181
182 // Calls Plugin_Init (if found) after loading DLL
183 // Note: DllMain callbacks during LoadLibrary is load-locked so we cannot do things like WaitForMultipleObjects on threads
184 using pfnPluginInit = void(__fastcall*)();
185 const auto pfn_init = reinterpret_cast<pfnPluginInit>(GetProcAddress(h_module, "Plugin_Init"));
186 if (pfn_init != nullptr)
187 {
188 pfn_init();
189 }
190
191 return loaded_plugins_.emplace_back(std::make_shared<Plugin>(h_module, plugin_name, plugin_info["FullName"],
192 plugin_info["Description"], plugin_info["Version"],
193 plugin_info["MinApiVersion"],
194 plugin_info["Dependencies"]));
195 }
196
197 void PluginManager::UnloadPlugin(const std::string& plugin_name) noexcept(false)
198 {
199 namespace fs = std::filesystem;
200
201 const auto iter = FindPlugin(plugin_name);
202 if (iter == loaded_plugins_.end())
203 {
204 throw std::runtime_error("Plugin " + plugin_name + " is not loaded");
205 }
206
207 const std::string dir_path = Tools::GetCurrentDir() + "/" + game_api->GetApiName() + "/Plugins/" + plugin_name;
208 const std::string full_dll_path = dir_path + "/" + plugin_name + ".dll";
209
210 if (!fs::exists(full_dll_path.c_str()))
211 {
212 throw std::runtime_error("Plugin " + plugin_name + " does not exist");
213 }
214
215 // Calls Plugin_Unload (if found) just before unloading DLL to let DLL gracefully clean up
216 // Note: DllMain callbacks during FreeLibrary is load-locked so we cannot do things like WaitForMultipleObjects on threads
217 using pfnPluginUnload = void(__fastcall*)();
218 const auto pfn_unload = reinterpret_cast<pfnPluginUnload>(GetProcAddress((*iter)->h_module, "Plugin_Unload"));
219 if (pfn_unload != nullptr)
220 {
221 pfn_unload();
222 }
223
224 const BOOL result = FreeLibrary((*iter)->h_module);
225 if (result == 0)
226 {
227 throw std::runtime_error(
228 "Failed to unload plugin - " + plugin_name + "\nError code: " + std::to_string(GetLastError()));
229 }
230
231 loaded_plugins_.erase(remove(loaded_plugins_.begin(), loaded_plugins_.end(), *iter), loaded_plugins_.end());
232 }
233
234 nlohmann::json PluginManager::ReadPluginInfo(const std::string& plugin_name)
235 {
236 nlohmann::json plugin_info_result({});
237 nlohmann::json plugin_info({});
238
239 const std::string dir_path = Tools::GetCurrentDir() + "/" + game_api->GetApiName() + "/Plugins/" + plugin_name;
240 const std::string config_path = dir_path + "/PluginInfo.json";
241
242 std::ifstream file{config_path};
243 if (file.is_open())
244 {
245 file >> plugin_info;
246 file.close();
247 }
248
249 try
250 {
251 plugin_info_result["FullName"] = plugin_info.value("FullName", "");
252 plugin_info_result["Description"] = plugin_info.value("Description", "No description");
253 plugin_info_result["Version"] = plugin_info.value("Version", 1.00f);
254 plugin_info_result["MinApiVersion"] = plugin_info.value("MinApiVersion", .0f);
255 plugin_info_result["Dependencies"] = plugin_info.value("Dependencies", std::vector<std::string>{});
256 }
257 catch (const std::exception& error)
258 {
259 Log::GetLog()->warn("({}) {}", __FUNCTION__, error.what());
260 }
261
262 return plugin_info_result;
263 }
264
266 {
267 for (const auto& plugin : loaded_plugins_)
268 {
269 if (plugin->dependencies.empty())
270 {
271 continue;
272 }
273
274 for (const std::string& dependency : plugin->dependencies)
275 {
276 if (!IsPluginLoaded(dependency))
277 {
278 Log::GetLog()->error("Plugin {} is missing! {} might not work correctly", dependency,
279 plugin->name);
280 }
281 }
282 }
283 }
284
285 std::vector<std::shared_ptr<Plugin>>::const_iterator PluginManager::FindPlugin(const std::string& plugin_name)
286 {
287 const auto iter = std::find_if(loaded_plugins_.begin(), loaded_plugins_.end(),
288 [plugin_name](const std::shared_ptr<Plugin>& plugin) -> bool
289 {
290 return plugin->name == plugin_name;
291 });
292
293 return iter;
294 }
295
296 bool PluginManager::IsPluginLoaded(const std::string& plugin_name)
297 {
298 return FindPlugin(plugin_name) != loaded_plugins_.end();
299 }
300
302 {
303 auto& pluginManager = Get();
304
305 const time_t now = time(nullptr);
306 if (now < pluginManager.next_reload_check_
307 || !pluginManager.enable_plugin_reload_)
308 {
309 return;
310 }
311
312 pluginManager.next_reload_check_ = now + pluginManager.reload_sleep_seconds_;
313
314 pluginManager.DetectPluginChanges();
315 }
316
318 {
319 namespace fs = std::filesystem;
320
321 // Prevents saving world multiple times if multiple plugins are queued to be reloaded
322 bool save_world = save_world_before_reload_;
323
324 for (const auto& dir_name : fs::directory_iterator(
325 Tools::GetCurrentDir() + "/" + game_api->GetApiName() + "/Plugins"))
326 {
327 const auto& path = dir_name.path();
328 if (!is_directory(path))
329 {
330 continue;
331 }
332
333 const auto filename = path.filename().stem().generic_string();
334
335 const std::string plugin_folder = path.generic_string() + "/";
336
337 const std::string plugin_file_path = plugin_folder + filename + ".dll";
338 const std::string new_plugin_file_path = plugin_folder + filename + ".dll.ArkApi";
339
340 if (fs::exists(new_plugin_file_path) && FindPlugin(filename) != loaded_plugins_.end())
341 {
342#ifndef ATLAS_GAME // not on ATLAS
343 // Save the world in case the unload/load procedure causes crash
344 if (save_world)
345 {
346 Log::GetLog()->info("Saving world before reloading plugins ...");
347 ArkApi::GetApiUtils().GetShooterGameMode()->SaveWorld(true);
348 Log::GetLog()->info("World saved.");
349
350 save_world = false; // do not save again if multiple plugins are reloaded in this loop
351 }
352#endif
353 try
354 {
355 UnloadPlugin(filename);
356
357 copy_file(new_plugin_file_path, plugin_file_path, fs::copy_options::overwrite_existing);
358 fs::remove(new_plugin_file_path);
359
360 // Wait 1 second before loading to let things clean up correctly...
361 // This will load the plugin in the next timer callback
362 //auto_reload_pending_plugins_.emplace_back(filename);
363
364 LoadPlugin(filename);
365
366 Log::GetLog()->info("Reloaded plugin - {}", filename);
367 }
368 catch (const std::exception& error)
369 {
370 Log::GetLog()->warn("({}) {}", __FUNCTION__, error.what());
371 continue;
372 }
373 }
374 }
375 }
376} // namespace API
#define ATLAS_GAME
Definition Atlas.h:6
virtual float GetVersion()=0
virtual std::string GetApiName()=0
std::vector< std::shared_ptr< Plugin > > loaded_plugins_
static nlohmann::json GetAllPDBConfigs()
Get all plugin pdb configs.
std::vector< std::shared_ptr< Plugin > >::const_iterator FindPlugin(const std::string &plugin_name)
Find plugin by it's name.
static nlohmann::json ReadPluginPDBConfig(const std::string &plugin_name)
void LoadAllPlugins()
Find and load all plugins.
static PluginManager & Get()
static nlohmann::json ReadPluginInfo(const std::string &plugin_name)
bool IsPluginLoaded(const std::string &plugin_name)
Returns true if plugin was loaded, false otherwise.
std::shared_ptr< Plugin > & LoadPlugin(const std::string &plugin_name) noexcept(false)
Load plugin by it's name.
static void DetectPluginChangesTimerCallback()
Checks for auto plugin reloads.
void UnloadPlugin(const std::string &plugin_name) noexcept(false)
Unload plugin by it's name. Plugin must free all used resources.
static nlohmann::json ReadSettingsConfig()
Definition Logger.h:9
static std::shared_ptr< spdlog::logger > & GetLog()
Definition Logger.h:22
void warn(const char *fmt, const Arg1 &, const Args &... args)
void error(const char *fmt, const Arg1 &, const Args &... args)
void info(const T &)
Definition IBaseApi.h:9
std::unique_ptr< IBaseApi > game_api
Definition IBaseApi.h:25
ARK_API std::string GetCurrentDir()
Definition Tools.cpp:7
namespace for Niels Lohmann
Definition json.hpp:89
Definition json.hpp:4518
std::vector< std::string > dependencies
HINSTANCE h_module
std::string name