[Patcher] Plugin Loader
Added a plugin loader which can be enabled with allow_plugins in the config.
This commit is contained in:
parent
6e86eacf5a
commit
91f976e524
|
@ -1100,6 +1100,12 @@ void EmulatorWindow::UpdateTitle() {
|
||||||
if (patcher && patcher->IsAnyPatchApplied()) {
|
if (patcher && patcher->IsAnyPatchApplied()) {
|
||||||
sb.Append(u8" [Patches Applied]");
|
sb.Append(u8" [Patches Applied]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patcher::PluginLoader* pluginloader = emulator()->plugin_loader();
|
||||||
|
if (pluginloader && pluginloader->IsAnyPluginLoaded()) {
|
||||||
|
sb.Append(u8" [Plugins Loaded]");
|
||||||
|
}
|
||||||
|
|
||||||
window_->SetTitle(sb.to_string_view());
|
window_->SetTitle(sb.to_string_view());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1429,10 +1435,6 @@ bool EmulatorWindow::IsUseNexusForGameBarEnabled() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string EmulatorWindow::BoolToString(bool value) {
|
|
||||||
return std::string(value ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmulatorWindow::DisplayHotKeysConfig() {
|
void EmulatorWindow::DisplayHotKeysConfig() {
|
||||||
std::string msg = "";
|
std::string msg = "";
|
||||||
std::string msg_passthru = "";
|
std::string msg_passthru = "";
|
||||||
|
@ -1472,14 +1474,16 @@ void EmulatorWindow::DisplayHotKeysConfig() {
|
||||||
msg.insert(0, msg_passthru);
|
msg.insert(0, msg_passthru);
|
||||||
msg += "\n";
|
msg += "\n";
|
||||||
|
|
||||||
msg += "Readback Resolve: " + BoolToString(cvars::d3d12_readback_resolve);
|
msg += "Readback Resolve: " +
|
||||||
|
xe::string_util::BoolToString(cvars::d3d12_readback_resolve);
|
||||||
msg += "\n";
|
msg += "\n";
|
||||||
|
|
||||||
msg += "Clear Memory Page State: " +
|
msg += "Clear Memory Page State: " +
|
||||||
BoolToString(cvars::d3d12_clear_memory_page_state);
|
xe::string_util::BoolToString(cvars::d3d12_clear_memory_page_state);
|
||||||
msg += "\n";
|
msg += "\n";
|
||||||
|
|
||||||
msg += "Controller Hotkeys: " + BoolToString(cvars::controller_hotkeys);
|
msg += "Controller Hotkeys: " +
|
||||||
|
xe::string_util::BoolToString(cvars::controller_hotkeys);
|
||||||
|
|
||||||
imgui_drawer_.get()->ClearDialogs();
|
imgui_drawer_.get()->ClearDialogs();
|
||||||
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Controller Hotkeys",
|
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Controller Hotkeys",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <regex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
|
@ -141,6 +142,20 @@ inline bool hex_string_to_array(std::vector<uint8_t>& output_array,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::string BoolToString(bool value) { return value ? "true" : "false"; }
|
||||||
|
|
||||||
|
inline std::string ltrim(const std::string& value) {
|
||||||
|
return std::regex_replace(value, std::regex("^\\s+"), std::string(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string rtrim(const std::string& value) {
|
||||||
|
return std::regex_replace(value, std::regex("\\s+$"), std::string(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string trim(const std::string& value) {
|
||||||
|
return ltrim(rtrim(value));
|
||||||
|
}
|
||||||
|
|
||||||
inline std::string to_hex_string(uint32_t value) {
|
inline std::string to_hex_string(uint32_t value) {
|
||||||
return fmt::format("{:08X}", value);
|
return fmt::format("{:08X}", value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@ DEFINE_bool(
|
||||||
"finding/stress testing with the JIT",
|
"finding/stress testing with the JIT",
|
||||||
"CPU");
|
"CPU");
|
||||||
|
|
||||||
|
DECLARE_bool(allow_plugins);
|
||||||
|
|
||||||
static const uint8_t xe_xex2_retail_key[16] = {
|
static const uint8_t xe_xex2_retail_key[16] = {
|
||||||
0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3,
|
0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3,
|
||||||
0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91};
|
0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91};
|
||||||
|
@ -1073,6 +1075,11 @@ bool XexModule::LoadContinue() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable write protection if plugins are enabled
|
||||||
|
if (cvars::allow_plugins && !cvars::writable_code_segments) {
|
||||||
|
OVERRIDE_bool(writable_code_segments, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Setup memory protection.
|
// Setup memory protection.
|
||||||
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
|
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
|
||||||
// Byteswap the bitfield manually.
|
// Byteswap the bitfield manually.
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include "xenia/cpu/backend/null_backend.h"
|
#include "xenia/cpu/backend/null_backend.h"
|
||||||
#include "xenia/cpu/cpu_flags.h"
|
#include "xenia/cpu/cpu_flags.h"
|
||||||
#include "xenia/cpu/thread_state.h"
|
#include "xenia/cpu/thread_state.h"
|
||||||
|
#include "xenia/gpu/command_processor.h"
|
||||||
#include "xenia/gpu/graphics_system.h"
|
#include "xenia/gpu/graphics_system.h"
|
||||||
#include "xenia/hid/input_driver.h"
|
#include "xenia/hid/input_driver.h"
|
||||||
#include "xenia/hid/input_system.h"
|
#include "xenia/hid/input_system.h"
|
||||||
|
@ -56,23 +57,27 @@
|
||||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||||
#endif // XE_ARCH
|
#endif // XE_ARCH
|
||||||
|
|
||||||
DECLARE_int32(user_language);
|
|
||||||
|
|
||||||
DEFINE_double(time_scalar, 1.0,
|
DEFINE_double(time_scalar, 1.0,
|
||||||
"Scalar used to speed or slow time (1x, 2x, 1/2x, etc).",
|
"Scalar used to speed or slow time (1x, 2x, 1/2x, etc).",
|
||||||
"General");
|
"General");
|
||||||
|
|
||||||
DEFINE_string(
|
DEFINE_string(
|
||||||
launch_module, "",
|
launch_module, "",
|
||||||
"Executable to launch from the .iso or the package instead of default.xex "
|
"Executable to launch from the .iso or the package instead of default.xex "
|
||||||
"or the module specified by the game. Leave blank to launch the default "
|
"or the module specified by the game. Leave blank to launch the default "
|
||||||
"module.",
|
"module.",
|
||||||
"General");
|
"General");
|
||||||
|
|
||||||
DEFINE_bool(allow_game_relative_writes, false,
|
DEFINE_bool(allow_game_relative_writes, false,
|
||||||
"Not useful to non-developers. Allows code to write to paths "
|
"Not useful to non-developers. Allows code to write to paths "
|
||||||
"relative to game://. Used for "
|
"relative to game://. Used for "
|
||||||
"generating test data to compare with original hardware. ",
|
"generating test data to compare with original hardware. ",
|
||||||
"General");
|
"General");
|
||||||
|
|
||||||
|
DECLARE_int32(user_language);
|
||||||
|
|
||||||
|
DECLARE_bool(allow_plugins);
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
using namespace xe::literals;
|
using namespace xe::literals;
|
||||||
|
|
||||||
|
@ -261,6 +266,9 @@ X_STATUS Emulator::Setup(
|
||||||
// Shared kernel state.
|
// Shared kernel state.
|
||||||
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
|
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
|
||||||
|
|
||||||
|
plugin_loader_ = std::make_unique<xe::patcher::PluginLoader>(
|
||||||
|
kernel_state_.get(), storage_root() / "plugins");
|
||||||
|
|
||||||
// Setup the core components.
|
// Setup the core components.
|
||||||
result = graphics_system_->Setup(
|
result = graphics_system_->Setup(
|
||||||
processor_.get(), kernel_state_.get(),
|
processor_.get(), kernel_state_.get(),
|
||||||
|
@ -388,9 +396,12 @@ X_STATUS Emulator::MountPath(const std::filesystem::path& path,
|
||||||
|
|
||||||
file_system_->UnregisterSymbolicLink("d:");
|
file_system_->UnregisterSymbolicLink("d:");
|
||||||
file_system_->UnregisterSymbolicLink("game:");
|
file_system_->UnregisterSymbolicLink("game:");
|
||||||
|
file_system_->UnregisterSymbolicLink("plugins:");
|
||||||
|
|
||||||
// Create symlinks to the device.
|
// Create symlinks to the device.
|
||||||
file_system_->RegisterSymbolicLink("game:", mount_path);
|
file_system_->RegisterSymbolicLink("game:", mount_path);
|
||||||
file_system_->RegisterSymbolicLink("d:", mount_path);
|
file_system_->RegisterSymbolicLink("d:", mount_path);
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -932,7 +943,7 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||||
title_version_ = format_version(title_version);
|
title_version_ = format_version(title_version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try and load the resource database (xex only).
|
// Try and load the resource database (xex only).
|
||||||
if (module->title_id()) {
|
if (module->title_id()) {
|
||||||
auto title_id = fmt::format("{:08X}", module->title_id());
|
auto title_id = fmt::format("{:08X}", module->title_id());
|
||||||
|
@ -993,6 +1004,16 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||||
main_thread_ = main_thread;
|
main_thread_ = main_thread;
|
||||||
on_launch(title_id_.value(), title_name_);
|
on_launch(title_id_.value(), title_name_);
|
||||||
|
|
||||||
|
// Plugins must be loaded after calling LaunchModule() and
|
||||||
|
// FinishLoadingUserModule() which will apply TUs and patching to the main
|
||||||
|
// xex.
|
||||||
|
if (cvars::allow_plugins) {
|
||||||
|
if (plugin_loader_->IsAnyPluginForTitleAvailable(title_id_.value(),
|
||||||
|
module->hash().value())) {
|
||||||
|
plugin_loader_->LoadTitlePlugins(title_id_.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/memory.h"
|
#include "xenia/memory.h"
|
||||||
#include "xenia/patcher/patcher.h"
|
#include "xenia/patcher/patcher.h"
|
||||||
|
#include "xenia/patcher/plugin_loader.h"
|
||||||
#include "xenia/vfs/device.h"
|
#include "xenia/vfs/device.h"
|
||||||
#include "xenia/vfs/virtual_file_system.h"
|
#include "xenia/vfs/virtual_file_system.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
@ -158,6 +159,8 @@ class Emulator {
|
||||||
|
|
||||||
patcher::Patcher* patcher() const { return patcher_.get(); }
|
patcher::Patcher* patcher() const { return patcher_.get(); }
|
||||||
|
|
||||||
|
patcher::PluginLoader* plugin_loader() const { return plugin_loader_.get(); }
|
||||||
|
|
||||||
// Initializes the emulator and configures all components.
|
// Initializes the emulator and configures all components.
|
||||||
// The given window is used for display and the provided functions are used
|
// The given window is used for display and the provided functions are used
|
||||||
// to create subsystems as required.
|
// to create subsystems as required.
|
||||||
|
@ -259,6 +262,7 @@ class Emulator {
|
||||||
std::unique_ptr<cpu::ExportResolver> export_resolver_;
|
std::unique_ptr<cpu::ExportResolver> export_resolver_;
|
||||||
std::unique_ptr<vfs::VirtualFileSystem> file_system_;
|
std::unique_ptr<vfs::VirtualFileSystem> file_system_;
|
||||||
std::unique_ptr<patcher::Patcher> patcher_;
|
std::unique_ptr<patcher::Patcher> patcher_;
|
||||||
|
std::unique_ptr<patcher::PluginLoader> plugin_loader_;
|
||||||
|
|
||||||
std::unique_ptr<kernel::KernelState> kernel_state_;
|
std::unique_ptr<kernel::KernelState> kernel_state_;
|
||||||
|
|
||||||
|
@ -272,6 +276,7 @@ class Emulator {
|
||||||
size_t game_config_load_callback_loop_next_index_ = SIZE_MAX;
|
size_t game_config_load_callback_loop_next_index_ = SIZE_MAX;
|
||||||
|
|
||||||
kernel::object_ref<kernel::XThread> main_thread_;
|
kernel::object_ref<kernel::XThread> main_thread_;
|
||||||
|
kernel::object_ref<kernel::XHostThread> plugin_loader_thread_;
|
||||||
std::optional<uint32_t> title_id_; // Currently running title ID
|
std::optional<uint32_t> title_id_; // Currently running title ID
|
||||||
|
|
||||||
bool paused_;
|
bool paused_;
|
||||||
|
|
|
@ -117,7 +117,7 @@ bool XThread::IsInThread(XThread* other) {
|
||||||
XThread* XThread::GetCurrentThread() {
|
XThread* XThread::GetCurrentThread() {
|
||||||
XThread* thread = reinterpret_cast<XThread*>(current_xthread_tls_);
|
XThread* thread = reinterpret_cast<XThread*>(current_xthread_tls_);
|
||||||
if (!thread) {
|
if (!thread) {
|
||||||
assert_always("Attempting to use kernel stuff from a non-kernel thread");
|
assert_always("Attempting to use guest stuff from a non-guest thread.");
|
||||||
}
|
}
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,235 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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 <string>
|
||||||
|
|
||||||
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/config.h"
|
||||||
|
#include "xenia/kernel/user_module.h"
|
||||||
|
#include "xenia/kernel/xthread.h"
|
||||||
|
#include "xenia/patcher/plugin_loader.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;
|
||||||
|
|
||||||
|
if (!cvars::allow_plugins) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin_configs_.clear();
|
||||||
|
LoadConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginLoader::LoadConfigs() {
|
||||||
|
// Iterate through directory and check if .toml file exists
|
||||||
|
std::vector<xe::filesystem::FileInfo> 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 =
|
||||||
|
std::stoi(entry.name.filename().string(), nullptr, 16);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<cpptoml::table> plugins_config;
|
||||||
|
|
||||||
|
try {
|
||||||
|
plugins_config = ParseFile(title_plugins_config);
|
||||||
|
} catch (...) {
|
||||||
|
XELOGE("Plugins: Cannot load plugin file {}",
|
||||||
|
path_to_utf8(title_plugins_config));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string title_name =
|
||||||
|
*plugins_config->get_as<std::string>("title_name");
|
||||||
|
|
||||||
|
const std::string patch_title_id =
|
||||||
|
*plugins_config->get_as<std::string>("title_id");
|
||||||
|
|
||||||
|
const auto plugin_tabels = plugins_config->get_table_array("plugin");
|
||||||
|
|
||||||
|
if (!plugin_tabels) {
|
||||||
|
XELOGE("Plugins: Cannot find [[plugin]] table in {}",
|
||||||
|
path_to_utf8(title_plugins_config));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& plugin : *plugin_tabels) {
|
||||||
|
PluginInfoEntry entry;
|
||||||
|
|
||||||
|
const std::string name = *plugin->get_as<std::string>("name");
|
||||||
|
const std::string file = *plugin->get_as<std::string>("file");
|
||||||
|
const std::string desc = *plugin->get_as<std::string>("desc");
|
||||||
|
const bool is_enabled = *plugin->get_as<bool>("is_enabled");
|
||||||
|
|
||||||
|
if (!plugin->contains("hash")) {
|
||||||
|
XELOGE("Hash error! skipping plugin {} in: {}", name,
|
||||||
|
path_to_utf8(title_plugins_config));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.hashes = GetHashes(plugin->get("hash"));
|
||||||
|
entry.name = name;
|
||||||
|
entry.file = xe::string_util::trim(file);
|
||||||
|
entry.desc = desc;
|
||||||
|
entry.title_id = title_id;
|
||||||
|
entry.is_enabled = is_enabled;
|
||||||
|
plugin_configs_.push_back(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint64_t> PluginLoader::GetHashes(
|
||||||
|
const std::shared_ptr<cpptoml::base> toml_entry) {
|
||||||
|
std::vector<uint64_t> hashes;
|
||||||
|
|
||||||
|
if (!toml_entry) {
|
||||||
|
return hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toml_entry->is_array()) {
|
||||||
|
const auto arr = toml_entry->as_array();
|
||||||
|
|
||||||
|
for (cpptoml::array::const_iterator itr = arr->begin(); itr != arr->end();
|
||||||
|
itr++) {
|
||||||
|
const std::string hash_entry = itr->get()->as<std::string>()->get();
|
||||||
|
hashes.push_back(strtoull(hash_entry.c_str(), NULL, 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toml_entry->is_value()) {
|
||||||
|
const std::string hash = toml_entry->as<std::string>()->get();
|
||||||
|
hashes.push_back(strtoull(hash.c_str(), NULL, 16));
|
||||||
|
}
|
||||||
|
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.title_id == title_id && hash_exists;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result != plugin_configs_.cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginLoader::LoadTitlePlugins(const uint32_t title_id) {
|
||||||
|
std::vector<PluginInfoEntry> title_plugins;
|
||||||
|
|
||||||
|
std::copy_if(plugin_configs_.cbegin(), plugin_configs_.cend(),
|
||||||
|
std::back_inserter(title_plugins),
|
||||||
|
[title_id](const PluginInfoEntry& entry) {
|
||||||
|
return entry.title_id == title_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (title_plugins.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreatePluginDevice(title_id);
|
||||||
|
|
||||||
|
for (const auto& entry : title_plugins) {
|
||||||
|
kernel::object_ref<kernel::XHostThread> plugin_thread =
|
||||||
|
kernel::object_ref<kernel::XHostThread>(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<xe::kernel::X_LDR_DATA_TABLE_ENTRY*>(hmodule)
|
||||||
|
->load_count++;
|
||||||
|
|
||||||
|
XELOGI("Plugin Loader: {} Plugin successfully loaded!", entry.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginLoader::CreatePluginDevice(const uint32_t title_id) {
|
||||||
|
const std::string 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<xe::vfs::HostPathDevice>(
|
||||||
|
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
|
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_PLUGIN_LOADER_H_
|
||||||
|
#define XENIA_PLUGIN_LOADER_H_
|
||||||
|
|
||||||
|
#include "xenia/kernel/kernel_state.h"
|
||||||
|
#include "xenia/memory.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace patcher {
|
||||||
|
|
||||||
|
struct PluginInfoEntry {
|
||||||
|
std::string name;
|
||||||
|
std::string file;
|
||||||
|
std::vector<uint64_t> hashes;
|
||||||
|
uint32_t title_id;
|
||||||
|
std::string desc;
|
||||||
|
bool is_enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PluginLoader {
|
||||||
|
public:
|
||||||
|
PluginLoader(kernel::KernelState* kernel_state,
|
||||||
|
const std::filesystem::path plugins_root);
|
||||||
|
|
||||||
|
void LoadTitlePlugins(const uint32_t title_id);
|
||||||
|
bool IsAnyPluginForTitleAvailable(const uint32_t title_id,
|
||||||
|
const uint64_t module_hash) const;
|
||||||
|
bool IsAnyPluginLoaded() { return is_any_plugin_loaded_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void LoadConfigs();
|
||||||
|
void LoadTitleConfig(const uint32_t title_id);
|
||||||
|
void CreatePluginDevice(const uint32_t title_id);
|
||||||
|
void LoadTitlePlugin(const PluginInfoEntry& entry);
|
||||||
|
|
||||||
|
std::vector<uint64_t> GetHashes(
|
||||||
|
const std::shared_ptr<cpptoml::base> toml_entry);
|
||||||
|
|
||||||
|
kernel::KernelState* kernel_state_;
|
||||||
|
std::filesystem::path plugins_root_;
|
||||||
|
|
||||||
|
std::vector<PluginInfoEntry> plugin_configs_;
|
||||||
|
|
||||||
|
bool is_any_plugin_loaded_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace patcher
|
||||||
|
} // namespace xe
|
||||||
|
#endif
|
Loading…
Reference in New Issue