[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:
parent
0fd578cafd
commit
c65f240c0b
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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()});
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue