/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2023 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/patcher/plugin_loader.h" #include "xenia/base/logging.h" #include "xenia/config.h" #include "xenia/kernel/user_module.h" #include "xenia/kernel/xthread.h" #include "xenia/vfs/devices/host_path_device.h" DEFINE_bool( allow_plugins, false, "Allows loading of plugins/trainers from plugins\\title_id\\plugin.xex. " "Plugin are homebrew xex modules which can be used for making mods. " "This feature is experimental.", "General"); namespace xe { namespace patcher { PluginLoader::PluginLoader(kernel::KernelState* kernel_state, const std::filesystem::path plugins_root) { kernel_state_ = kernel_state; plugins_root_ = plugins_root; is_any_plugin_loaded_ = false; if (!cvars::allow_plugins) { return; } plugin_configs_.clear(); LoadConfigs(); } void PluginLoader::LoadConfigs() { // Iterate through directory and check if .toml file exists std::vector dir_files = xe::filesystem::ListDirectories(plugins_root_); dir_files = xe::filesystem::FilterByName(dir_files, std::regex("[A-Fa-f0-9]{8}")); for (const auto& entry : dir_files) { const uint32_t title_id = string_util::from_string( entry.name.filename().string(), true); LoadTitleConfig(title_id); } XELOGI("Plugins: Loaded plugins for {} titles", dir_files.size()); } void PluginLoader::LoadTitleConfig(const uint32_t title_id) { const std::filesystem::path title_plugins_config = plugins_root_ / fmt::format("{:08X}\\plugins.toml", title_id); if (!std::filesystem::exists(title_plugins_config)) { return; } toml::parse_result plugins_config; try { plugins_config = ParseFile(title_plugins_config); } catch (...) { XELOGE("Plugins: Cannot load plugin file {}", title_plugins_config); return; } const std::string title_name = plugins_config.get_as("title_name")->get(); const std::string plugin_title_id = plugins_config.get_as("title_id")->get(); if (!plugins_config.contains("plugin")) { XELOGE("Plugins: Cannot find [[plugin]] table in {}", title_plugins_config); return; } const auto plugin_array = plugins_config.get_as("plugin"); if (!plugin_array) { return; } for (const auto& plugin_entry : *plugin_array) { PluginInfoEntry entry; if (!plugin_entry.is_table()) { return; } entry.title_id = xe::string_util::from_string(plugin_title_id, true); if (plugin_entry.as_table()->contains("name")) { entry.name = plugin_entry.as_table()->get_as("name")->get(); } if (plugin_entry.as_table()->contains("file")) { entry.file = xe::string_util::trim( plugin_entry.as_table()->get_as("file")->get()); } if (plugin_entry.as_table()->contains("desc")) { entry.desc = plugin_entry.as_table()->get_as("desc")->get(); } if (plugin_entry.as_table()->contains("is_enabled")) { entry.is_enabled = plugin_entry.as_table()->get_as("is_enabled")->get(); } entry.hashes = GetHashes(plugin_entry.as_table()->get("hash")); if (!plugin_entry.as_table()->contains("hash") || entry.hashes.empty()) { const std::string file = fmt::format("plugins\\{:08X}\\plugins.toml", title_id); XELOGE("Hash error! skipping plugin {} in: {}", entry.name, file); continue; } plugin_configs_.push_back(entry); } } std::vector PluginLoader::GetHashes( const toml::node* toml_entry) const { std::vector hashes; if (!toml_entry) { return hashes; } if (toml_entry->is_value()) { const std::string hash = toml_entry->as_string()->get(); if (hash.empty()) { return hashes; } hashes.push_back(xe::string_util::from_string(hash, true)); } if (toml_entry->is_array()) { for (const auto& hash_entry : *toml_entry->as_array()) { if (hash_entry.as_string()->get().empty()) { continue; } hashes.push_back(xe::string_util::from_string( hash_entry.as_string()->get(), true)); } } return hashes; } bool PluginLoader::IsAnyPluginForTitleAvailable( const uint32_t title_id, const uint64_t module_hash) const { const auto result = std::find_if( plugin_configs_.cbegin(), plugin_configs_.cend(), [title_id, module_hash](const PluginInfoEntry& entry) { const auto hash_exists = std::find(entry.hashes.cbegin(), entry.hashes.cend(), module_hash) != entry.hashes.cend(); return entry.is_enabled && entry.title_id == title_id && hash_exists; }); return result != plugin_configs_.cend(); } void PluginLoader::LoadTitlePlugins(const uint32_t title_id, const uint64_t module_hash) { std::vector title_plugins; std::copy_if(plugin_configs_.cbegin(), plugin_configs_.cend(), std::back_inserter(title_plugins), [title_id, module_hash](const PluginInfoEntry& entry) { const auto hash_exists = std::find(entry.hashes.cbegin(), entry.hashes.cend(), module_hash) != entry.hashes.cend(); return entry.is_enabled && entry.title_id == title_id && hash_exists; }); if (title_plugins.empty()) { return; } CreatePluginDevice(title_id); for (const auto& entry : title_plugins) { kernel::object_ref plugin_thread = kernel::object_ref(new xe::kernel::XHostThread( kernel_state_, 128 * 1024, 0, [this, entry]() { LoadTitlePlugin(entry); return 0; })); plugin_thread->set_name( fmt::format("Plugin Loader: {} Thread", entry.name)); plugin_thread->Create(); xe::threading::Wait(plugin_thread->thread(), false); is_any_plugin_loaded_ = true; } } void PluginLoader::LoadTitlePlugin(const PluginInfoEntry& entry) { auto user_module = kernel_state_->LoadUserModule(fmt::format("plugins:\\{}", entry.file)); if (!user_module) { return; } kernel_state_->FinishLoadingUserModule(user_module); const uint32_t hmodule = user_module->hmodule_ptr(); if (hmodule == 0) { return; } kernel_state_->memory() ->TranslateVirtual(hmodule) ->load_count++; XELOGI("Plugin Loader: {} Plugin successfully loaded!", entry.name); } void PluginLoader::CreatePluginDevice(const uint32_t title_id) { constexpr std::string_view mount_plugins = "\\Device\\Plugins"; const std::filesystem::path plugins_host_path = plugins_root_ / fmt::format("{:08X}", title_id); kernel_state_->file_system()->RegisterSymbolicLink("plugins:", mount_plugins); auto device_plugins = std::make_unique( mount_plugins, plugins_host_path, false); if (!device_plugins->Initialize()) { xe::FatalError("Unable to mount {}; file not found or corrupt."); return; } if (!kernel_state_->file_system()->RegisterDevice( std::move(device_plugins))) { xe::FatalError("Unable to register {}."); return; } return; } } // namespace patcher } // namespace xe