/* * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG * Copyright (C) 2020-2025 Rosalie Wanders * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define CORE_INTERNAL #include "CachedRomHeaderAndSettings.hpp" #include "Directories.hpp" #include "RomSettings.hpp" #include "Emulation.hpp" #include "RomHeader.hpp" #include "Callback.hpp" #include "Settings.hpp" #include "Library.hpp" #include "Plugins.hpp" #include "Error.hpp" #include "m64p/PluginApi.hpp" #include "m64p/Api.hpp" #include #include #include #include #include // // Local Variables // static m64p::PluginApi l_Plugins[4]; static std::string l_PluginFiles[4]; static char l_PluginContext[4][20]; // // Local Functions // static m64p::PluginApi& get_plugin(CorePluginType type) { if (static_cast(type) < 1 || static_cast(type) > 4) { throw std::runtime_error("get_plugin: called with invalid type"); } return l_Plugins[static_cast(type) - 1]; } static CorePluginType get_plugin_type(m64p::PluginApi& plugin) { m64p_error ret; m64p_plugin_type m64p_type = M64PLUGIN_NULL; ret = plugin.GetVersion(&m64p_type, nullptr, nullptr, nullptr, nullptr); if (ret != M64ERR_SUCCESS) { return CorePluginType::Invalid; } if (m64p_type < static_cast(CorePluginType::Rsp) || m64p_type > static_cast(CorePluginType::Input)) { return CorePluginType::Invalid; } return static_cast(m64p_type); } static std::string get_plugin_name(m64p::PluginApi& plugin, const std::string& filename) { m64p_error ret; const char* name = nullptr; ret = plugin.GetVersion(nullptr, nullptr, nullptr, &name, nullptr); if (ret != M64ERR_SUCCESS || name == nullptr) { return filename; } return std::string(name); } static std::string get_plugin_type_name(CorePluginType type) { std::string name; switch (type) { default: name = "Unknown"; break; case CorePluginType::Rsp: name = "Rsp"; break; case CorePluginType::Gfx: name = "Gfx"; break; case CorePluginType::Audio: name = "Audio"; break; case CorePluginType::Input: name = "Input"; break; } return name + " Plugin"; } static std::string get_plugin_context_name(CorePluginType type) { std::string name; switch (type) { default: name = "[UNKNOWN]"; break; case CorePluginType::Rsp: name = "[RSP] "; break; case CorePluginType::Gfx: name = "[GFX] "; break; case CorePluginType::Audio: name = "[AUDIO] "; break; case CorePluginType::Input: name = "[INPUT] "; break; } return name; } static std::string get_plugin_path(CorePluginType type, std::string settingsValue) { std::string pluginPath; std::string path; std::string typeName; // return an empty string when the value is empty if (settingsValue.empty()) { return std::string(); } pluginPath = CoreGetPluginDirectory().string(); // if the full plugin path is in the settings value, // we know it's the old type if (settingsValue.find(pluginPath) != std::string::npos) { return settingsValue; } switch (type) { case CorePluginType::Rsp: typeName = "RSP"; break; case CorePluginType::Gfx: typeName = "GFX"; break; case CorePluginType::Audio: typeName = "Audio"; break; case CorePluginType::Input: typeName = "Input"; break; default: return path; } path = pluginPath; path += CORE_DIR_SEPERATOR_STR; path += typeName; path += CORE_DIR_SEPERATOR_STR; path += settingsValue; return path; } static bool apply_plugin_settings(const std::array& pluginSettings) { std::string error; std::string settingValue; CorePluginType pluginType; CoreLibraryHandle handle; m64p_error ret; for (int i = 0; i < static_cast(CorePluginType::Count); i++) { pluginType = static_cast(i + 1); settingValue = get_plugin_path(pluginType, pluginSettings[i]); if (settingValue.empty()) { // skip invalid setting value continue; } // copy context string to a c string using strcpy std::strcpy(l_PluginContext[i], get_plugin_context_name(pluginType).c_str()); if (settingValue != l_PluginFiles[i]) { m64p::PluginApi& plugin = l_Plugins[i]; // shutdown plugin when hooked if (plugin.IsHooked()) { ret = plugin.Shutdown(); if (ret != M64ERR_SUCCESS) { error = "apply_plugin_settings ("; error += get_plugin_type_name(pluginType); error += ")->Shutdown() Failed: "; error += m64p::Core.ErrorMessage(ret); CoreSetError(error); return false; } // reset plugin plugin.Unhook(); } // ensure library file exists if (!std::filesystem::is_regular_file(settingValue)) { // force a re-load next time, // because we've unhooked // the existing one l_PluginFiles[i].clear(); continue; } // attempt to open the library handle = CoreOpenLibrary(settingValue.c_str()); if (handle == nullptr) { error = "apply_plugin_settings CoreOpenLibrary Failed: "; error += CoreGetLibraryError(); CoreSetError(error); return false; } // attempt to hook the library if (!plugin.Hook(handle)) { error = "apply_plugin_settings ("; error += get_plugin_type_name(pluginType); error += ")->Hook() Failed: "; error += plugin.GetLastError(); CoreSetError(error); plugin.Unhook(); return false; } // make sure the plugin type is the expected type if (get_plugin_type(plugin) != pluginType) { error = "apply_plugin_settings plugin type "; error += get_plugin_type_name(get_plugin_type(plugin)); error += " doesn't match expected type "; error += get_plugin_type_name(pluginType); error += "!"; CoreSetError(error); plugin.Unhook(); return false; } // attempt to start plugin ret = plugin.Startup(m64p::Core.GetHandle(), l_PluginContext[i], CoreDebugCallback); if (ret != M64ERR_SUCCESS) { error = "apply_plugin_settings ("; error += get_plugin_type_name(pluginType); error += ")->Startup() Failed: "; error += m64p::Core.ErrorMessage(ret); CoreSetError(error); plugin.Shutdown(); plugin.Unhook(); // force a re-load next time, // because this plugin isn't // the one we've stored in // l_PluginFiles[i] l_PluginFiles[i].clear(); return false; } l_PluginFiles[i] = settingValue; CoreAddCallbackMessage(CoreDebugMessageType::Info, "Loaded plugin " + std::filesystem::path(settingValue).filename().string()); } } return true; } static bool open_plugin_config(CorePluginType type, void* parent, bool romConfig, std::filesystem::path file) { std::string error; m64p_error ret; bool resumeEmulation = false; m64p::PluginApi& plugin = get_plugin(type); std::string functionName; CoreRomHeader romHeader; CoreRomSettings romSettings; if (!romConfig && !CorePluginsHasConfig(type)) { error = "open_plugin_config Failed: "; error += get_plugin_type_name(type); error += " doesn't have the config or config2 function!"; CoreSetError(error); return false; } else if (romConfig && !CorePluginsHasROMConfig(type)) { error = "open_plugin_config Failed: "; error += get_plugin_type_name(type); error += " doesn't support ROM specific configuration!"; CoreSetError(error); return false; } // try to pause emulation, // when emulation is running // and try to resume afterwards if (CoreIsEmulationRunning()) { // only resume emulation // after running the config function // when pausing succeeds resumeEmulation = CorePauseEmulation(); } if (romConfig && !file.empty()) { // try to retrieve cached rom header & settings if (!CoreGetCachedRomHeaderAndSettings(file, nullptr, &romHeader, nullptr, &romSettings)) { return false; } } // check if the plugin has the ConfigWithRomConfig // or Config function, the ConfigWithRomConfig function // has priority if (plugin.ConfigWithRomConfig != nullptr) { ret = plugin.ConfigWithRomConfig(parent, romConfig ? 1 : 0, &romHeader, &romSettings); functionName = "Config2"; } else { ret = plugin.Config(parent); functionName = "Config"; } if (ret != M64ERR_SUCCESS) { error = "open_plugin_config ("; error += get_plugin_type_name(type); error += ")->"; error += functionName; error += "() Failed: "; error += m64p::Core.ErrorMessage(ret); CoreSetError(error); } // try to resume emulation when needed if (resumeEmulation) { CoreResumeEmulation(); } return ret == M64ERR_SUCCESS; } // // Exported Functions // CORE_EXPORT std::vector CoreGetAllPlugins(void) { std::vector plugins; std::string plugin_name; CorePluginType plugin_type; CoreLibraryHandle handle; m64p::PluginApi plugin; for (const auto& entry : std::filesystem::recursive_directory_iterator(CoreGetPluginDirectory())) { std::string path = entry.path().string(); std::string file = entry.path().filename().string(); if (!entry.is_directory() && path.ends_with(CORE_LIBRARY_EXT_STR)) { handle = CoreOpenLibrary(path.c_str()); if (handle == nullptr || !plugin.Hook(handle)) { // skip invalid libs continue; } plugin_name = get_plugin_name(plugin, entry.path().filename().string()); plugin_type = get_plugin_type(plugin); plugin.Unhook(); CoreCloseLibrary(handle); if (plugin_type == CorePluginType::Invalid) { // skip unsupported plugin types continue; } CorePlugin corePlugin = {file, plugin_name, plugin_type}; plugins.emplace_back(corePlugin); } } std::sort(plugins.begin(), plugins.end(), [](CorePlugin& a, CorePlugin& b) { return a.Name > b.Name; }); return plugins; } CORE_EXPORT bool CoreApplyPluginSettings(void) { const std::array settings = { CoreSettingsGetStringValue(SettingsID::Core_RSP_Plugin), CoreSettingsGetStringValue(SettingsID::Core_GFX_Plugin), CoreSettingsGetStringValue(SettingsID::Core_AUDIO_Plugin), CoreSettingsGetStringValue(SettingsID::Core_INPUT_Plugin) }; return apply_plugin_settings(settings); } CORE_EXPORT bool CoreApplyRomPluginSettings(void) { CoreRomSettings romSettings; if (!CoreGetCurrentDefaultRomSettings(romSettings)) { return false; } const std::array settings = { CoreSettingsGetStringValue(SettingsID::Game_RSP_Plugin, romSettings.MD5), CoreSettingsGetStringValue(SettingsID::Game_GFX_Plugin, romSettings.MD5), CoreSettingsGetStringValue(SettingsID::Game_AUDIO_Plugin, romSettings.MD5), CoreSettingsGetStringValue(SettingsID::Game_INPUT_Plugin, romSettings.MD5) }; return apply_plugin_settings(settings); } CORE_EXPORT bool CoreArePluginsReady(void) { std::string error; for (int i = 0; i < static_cast(CorePluginType::Count); i++) { if (!l_Plugins[i].IsHooked()) { error = "CoreArePluginsReady Failed: "; error += "("; error += get_plugin_type_name(static_cast(i + 1)); error += ")->IsHooked() returned false!"; CoreSetError(error); return false; } } return true; } CORE_EXPORT bool CorePluginsHasConfig(CorePluginType type) { std::string error; m64p::PluginApi& plugin = get_plugin(type); return plugin.Config != nullptr || plugin.ConfigWithRomConfig != nullptr; } CORE_EXPORT bool CorePluginsOpenConfig(CorePluginType type, void* parent) { return open_plugin_config(type, parent, false, ""); } CORE_EXPORT bool CorePluginsHasROMConfig(CorePluginType type) { m64p::PluginApi& plugin = get_plugin(type); return plugin.ConfigWithRomConfig != nullptr; } CORE_EXPORT bool CorePluginsOpenROMConfig(CorePluginType type, void* parent, std::filesystem::path file) { return open_plugin_config(type, parent, true, file); } CORE_EXPORT bool CoreAttachPlugins(void) { std::string error; m64p_error ret; const m64p_plugin_type plugin_types[] = { M64PLUGIN_GFX, M64PLUGIN_AUDIO, M64PLUGIN_INPUT, M64PLUGIN_RSP }; if (!m64p::Core.IsHooked()) { return false; } for (int i = 0; i < static_cast(CorePluginType::Count); i++) { ret = m64p::Core.AttachPlugin(plugin_types[i], get_plugin(static_cast(plugin_types[i])).GetHandle()); if (ret != M64ERR_SUCCESS) { error = "CoreAttachPlugins m64p::Core.AttachPlugin("; error += get_plugin_type_name(static_cast(plugin_types[i])); error += ") Failed: "; error += m64p::Core.ErrorMessage(ret); CoreSetError(error); break; } } return ret == M64ERR_SUCCESS; } CORE_EXPORT bool CoreDetachPlugins(void) { std::string error; m64p_error ret; if (!m64p::Core.IsHooked()) { return false; } for (int i = 0; i < static_cast(CorePluginType::Count); i++) { ret = m64p::Core.DetachPlugin(static_cast(i + 1)); if (ret != M64ERR_SUCCESS) { error = "CoreDetachPlugins m64p::Core.DetachPlugin("; error += get_plugin_type_name((CorePluginType)(i + 1)); error += ") Failed: "; error += m64p::Core.ErrorMessage(ret); CoreSetError(error); break; } } return ret == M64ERR_SUCCESS; } CORE_EXPORT bool CorePluginsShutdown(void) { std::string error; m64p::PluginApi* plugin; m64p_error ret; for (int i = 0; i < static_cast(CorePluginType::Count); i++) { plugin = &l_Plugins[i]; // shutdown plugin when hooked if (plugin->IsHooked()) { ret = plugin->Shutdown(); if (ret != M64ERR_SUCCESS) { error = "CorePluginsShutdown ("; error += get_plugin_type_name(static_cast(i + 1)); error += ")->Shutdown() Failed: "; error += m64p::Core.ErrorMessage(ret); CoreSetError(error); break; } // reset plugin plugin->Unhook(); } } return ret == M64ERR_SUCCESS; }