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