[Kernel] Improved TUs Support

- Changed name of config option to apply_title_update to better reflect what that option does
- Mount TU package to UPDATE: partition
- Simplified UserModule::title_id()
- Splitted loading module into two parts to allow applying TUs and custom patches
This commit is contained in:
Gliniak 2022-04-30 14:38:42 +02:00
parent 0fd578cafd
commit c65f240c0b
8 changed files with 132 additions and 59 deletions

View File

@ -751,13 +751,25 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
// Allow xam to request module loads.
auto xam = kernel_state()->GetKernelModule<kernel::xam::XamModule>("xam.xex");
XELOGI("Launching module {}", module_path);
XELOGI("Loading module {}", module_path);
auto module = kernel_state_->LoadUserModule(module_path);
if (!module) {
XELOGE("Failed to load user module {}", xe::path_to_utf8(path));
return X_STATUS_NOT_FOUND;
}
X_RESULT result = kernel_state_->ApplyTitleUpdate(module);
if (XFAILED(result)) {
XELOGE("Failed to apply title update! Cannot run module {}",
xe::path_to_utf8(path));
return result;
}
result = kernel_state_->FinishLoadingUserModule(module);
if (XFAILED(result)) {
XELOGE("Failed to initialize user module {}", xe::path_to_utf8(path));
return result;
}
// Grab the current title ID.
xex2_opt_execution_info* info = nullptr;
module->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &info);

View File

@ -28,6 +28,8 @@
#include "xenia/kernel/xobject.h"
#include "xenia/kernel/xthread.h"
DEFINE_bool(apply_title_update, true, "Apply title updates.", "Kernel");
namespace xe {
namespace kernel {
@ -404,7 +406,16 @@ object_ref<UserModule> KernelState::LoadUserModule(
// Putting into the listing automatically retains.
user_modules_.push_back(module);
}
return module;
}
X_RESULT KernelState::FinishLoadingUserModule(
const object_ref<UserModule> module, bool call_entry) {
// TODO(Gliniak): Apply custom patches here
X_RESULT result = module->LoadContinue();
if (XFAILED(result)) {
return result;
}
module->Dump();
if (module->is_dll_module() && module->entry_point() && call_entry) {
@ -419,8 +430,55 @@ object_ref<UserModule> KernelState::LoadUserModule(
processor()->Execute(thread_state, module->entry_point(), args,
xe::countof(args));
}
return result;
}
return module;
X_RESULT KernelState::ApplyTitleUpdate(const object_ref<UserModule> module) {
X_RESULT result = X_STATUS_SUCCESS;
if (!cvars::apply_title_update) {
return result;
}
std::vector<xam::XCONTENT_AGGREGATE_DATA> tu_list =
content_manager()->ListContent(1, xe::XContentType::kInstaller,
module->title_id());
if (tu_list.empty()) {
return result;
}
uint32_t disc_number = -1;
if (module->is_multi_disc_title()) {
disc_number = module->disc_number();
}
// TODO(Gliniak): Support for selecting from multiple TUs
const xam::XCONTENT_AGGREGATE_DATA& title_update = tu_list.front();
X_RESULT open_status =
content_manager()->OpenContent("UPDATE", title_update, disc_number);
std::string resolved_path = "";
file_system()->FindSymbolicLink("UPDATE:", resolved_path);
xe::vfs::Entry* patch_entry = kernel_state()->file_system()->ResolvePath(
resolved_path + "default.xexp");
if (patch_entry) {
const std::string patch_path = patch_entry->absolute_path();
XELOGI("Loading XEX patch from {}", patch_path);
auto patch_module = object_ref<UserModule>(new UserModule(this));
result = patch_module->LoadFromFile(patch_path);
if (result != X_STATUS_SUCCESS) {
XELOGE("Failed to load XEX patch, code: {}", result);
return X_STATUS_UNSUCCESSFUL;
}
result = patch_module->xex_module()->ApplyPatch(module->xex_module());
if (result != X_STATUS_SUCCESS) {
XELOGE("Failed to apply XEX patch, code: {}", result);
return X_STATUS_UNSUCCESSFUL;
}
}
return result;
}
void KernelState::UnloadUserModule(const object_ref<UserModule>& module,

View File

@ -134,6 +134,8 @@ class KernelState {
void SetExecutableModule(object_ref<UserModule> module);
object_ref<UserModule> LoadUserModule(const std::string_view name,
bool call_entry = true);
X_RESULT FinishLoadingUserModule(const object_ref<UserModule> module,
bool call_entry = true);
void UnloadUserModule(const object_ref<UserModule>& module,
bool call_entry = true);
@ -150,6 +152,7 @@ class KernelState {
return object_ref<T>(reinterpret_cast<T*>(module.release()));
}
X_RESULT ApplyTitleUpdate(const object_ref<UserModule> module);
// Terminates a title: Unloads all modules, and kills all guest threads.
// This DOES NOT RETURN if called from a guest thread!
void TerminateTitle();

View File

@ -20,8 +20,6 @@
#include "xenia/kernel/xfile.h"
#include "xenia/kernel/xthread.h"
DEFINE_bool(xex_apply_patches, true, "Apply XEX patches.", "Kernel");
namespace xe {
namespace kernel {
@ -34,20 +32,38 @@ uint32_t UserModule::title_id() const {
if (module_format_ != kModuleFormatXex) {
return 0;
}
auto header = xex_header();
for (uint32_t i = 0; i < header->header_count; i++) {
auto& opt_header = header->headers[i];
if (opt_header.key == XEX_HEADER_EXECUTION_INFO) {
auto opt_header_ptr =
reinterpret_cast<const uint8_t*>(header) + opt_header.offset;
auto opt_exec_info =
reinterpret_cast<const xex2_opt_execution_info*>(opt_header_ptr);
return static_cast<uint32_t>(opt_exec_info->title_id);
}
xex2_opt_execution_info* opt_exec_info = nullptr;
if (xex_module()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &opt_exec_info)) {
return static_cast<uint32_t>(opt_exec_info->title_id);
}
return 0;
}
uint32_t UserModule::disc_number() const {
if (module_format_ != kModuleFormatXex) {
return 1;
}
xex2_opt_execution_info* opt_exec_info = nullptr;
if (xex_module()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &opt_exec_info)) {
return static_cast<uint32_t>(opt_exec_info->disc_number);
}
return 1;
}
bool UserModule::is_multi_disc_title() const {
if (module_format_ != kModuleFormatXex) {
return false;
}
xex2_opt_execution_info* opt_exec_info = nullptr;
if (xex_module()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &opt_exec_info)) {
return opt_exec_info->disc_count > 1;
}
return false;
}
X_STATUS UserModule::LoadFromFile(const std::string_view path) {
X_STATUS result = X_STATUS_UNSUCCESSFUL;
@ -97,38 +113,7 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) {
file->Destroy();
}
// Only XEX returns X_STATUS_PENDING
if (result != X_STATUS_PENDING) {
return result;
}
if (cvars::xex_apply_patches) {
// Search for xexp patch file
auto patch_entry = kernel_state()->file_system()->ResolvePath(path_ + "p");
if (patch_entry) {
auto patch_path = patch_entry->absolute_path();
XELOGI("Loading XEX patch from {}", patch_path);
auto patch_module = object_ref<UserModule>(new UserModule(kernel_state_));
result = patch_module->LoadFromFile(patch_path);
if (!result) {
result = patch_module->xex_module()->ApplyPatch(xex_module());
if (result) {
XELOGE("Failed to apply XEX patch, code: {}", result);
}
} else {
XELOGE("Failed to load XEX patch, code: {}", result);
}
if (result) {
return X_STATUS_UNSUCCESSFUL;
}
}
}
return LoadXexContinue();
return result;
}
X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) {
@ -194,7 +179,7 @@ X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) {
return X_STATUS_SUCCESS;
}
X_STATUS UserModule::LoadXexContinue() {
X_STATUS UserModule::LoadContinue() {
// LoadXexContinue: finishes loading XEX after a patch has been applied (or
// patch wasn't found)

View File

@ -58,6 +58,9 @@ class UserModule : public XModule {
uint32_t guest_xex_header() const { return guest_xex_header_; }
// The title ID in the xex header or 0 if this is not a xex.
uint32_t title_id() const;
uint32_t disc_number() const;
bool is_multi_disc_title() const;
bool is_executable() const { return processor_module_->is_executable(); }
bool is_dll_module() const { return is_dll_module_; }
@ -66,6 +69,7 @@ class UserModule : public XModule {
X_STATUS LoadFromFile(const std::string_view path);
X_STATUS LoadFromMemory(const void* addr, const size_t length);
X_STATUS LoadContinue();
X_STATUS Unload();
uint32_t GetProcAddressByOrdinal(uint16_t ordinal) override;
@ -96,8 +100,6 @@ class UserModule : public XModule {
const std::string_view path);
private:
X_STATUS LoadXexContinue();
std::string name_;
std::string path_;

View File

@ -71,11 +71,18 @@ std::filesystem::path ContentManager::ResolvePackageRoot(
}
std::filesystem::path ContentManager::ResolvePackagePath(
const XCONTENT_AGGREGATE_DATA& data) {
const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) {
// Content path:
// content_root/title_id/content_type/data_file_name/
auto package_root = ResolvePackageRoot(data.content_type, data.title_id);
return package_root / xe::to_path(data.file_name());
std::string disc_directory = "";
std::filesystem::path package_path =
package_root / xe::to_path(data.file_name());
if (disc_number != -1) {
package_path /= fmt::format("disc{:03}", disc_number);
}
return package_path;
}
std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
@ -108,8 +115,9 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
}
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data) {
auto package_path = ResolvePackagePath(data);
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number) {
auto package_path = ResolvePackagePath(data, disc_number);
if (!std::filesystem::exists(package_path)) {
return nullptr;
}
@ -154,7 +162,8 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
}
X_RESULT ContentManager::OpenContent(const std::string_view root_name,
const XCONTENT_AGGREGATE_DATA& data) {
const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number) {
auto global_lock = global_critical_region_.Acquire();
if (open_packages_.count(string_key(root_name))) {
@ -162,14 +171,14 @@ X_RESULT ContentManager::OpenContent(const std::string_view root_name,
return X_ERROR_ALREADY_EXISTS;
}
auto package_path = ResolvePackagePath(data);
auto package_path = ResolvePackagePath(data, disc_number);
if (!std::filesystem::exists(package_path)) {
// Does not exist, must be created.
return X_ERROR_FILE_NOT_FOUND;
}
// Open package.
auto package = ResolvePackage(root_name, data);
auto package = ResolvePackage(root_name, data, disc_number);
assert_not_null(package);
open_packages_.insert({string_key::create(root_name), package.release()});

View File

@ -146,13 +146,15 @@ class ContentManager {
uint32_t title_id = -1);
std::unique_ptr<ContentPackage> ResolvePackage(
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data);
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number = -1);
bool ContentExists(const XCONTENT_AGGREGATE_DATA& data);
X_RESULT CreateContent(const std::string_view root_name,
const XCONTENT_AGGREGATE_DATA& data);
X_RESULT OpenContent(const std::string_view root_name,
const XCONTENT_AGGREGATE_DATA& data);
const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number = -1);
X_RESULT CloseContent(const std::string_view root_name);
X_RESULT GetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t>* buffer);
@ -166,7 +168,8 @@ class ContentManager {
private:
std::filesystem::path ResolvePackageRoot(XContentType content_type,
uint32_t title_id = -1);
std::filesystem::path ResolvePackagePath(const XCONTENT_AGGREGATE_DATA& data);
std::filesystem::path ResolvePackagePath(const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number = -1);
KernelState* kernel_state_;
std::filesystem::path root_path_;

View File

@ -97,6 +97,7 @@ dword_result_t XexLoadImage_entry(lpstring_t module_name, dword_t module_flags,
// Not found; attempt to load as a user module.
auto user_module = kernel_state()->LoadUserModule(module_name.value());
if (user_module) {
kernel_state()->FinishLoadingUserModule(user_module);
// Give up object ownership, this reference will be released by the last
// XexUnloadImage call
auto user_module_raw = user_module.release();