[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.
|
// Allow xam to request module loads.
|
||||||
auto xam = kernel_state()->GetKernelModule<kernel::xam::XamModule>("xam.xex");
|
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);
|
auto module = kernel_state_->LoadUserModule(module_path);
|
||||||
if (!module) {
|
if (!module) {
|
||||||
XELOGE("Failed to load user module {}", xe::path_to_utf8(path));
|
XELOGE("Failed to load user module {}", xe::path_to_utf8(path));
|
||||||
return X_STATUS_NOT_FOUND;
|
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.
|
// Grab the current title ID.
|
||||||
xex2_opt_execution_info* info = nullptr;
|
xex2_opt_execution_info* info = nullptr;
|
||||||
module->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &info);
|
module->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &info);
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
#include "xenia/kernel/xobject.h"
|
#include "xenia/kernel/xobject.h"
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/kernel/xthread.h"
|
||||||
|
|
||||||
|
DEFINE_bool(apply_title_update, true, "Apply title updates.", "Kernel");
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
|
|
||||||
|
@ -404,7 +406,16 @@ object_ref<UserModule> KernelState::LoadUserModule(
|
||||||
// Putting into the listing automatically retains.
|
// Putting into the listing automatically retains.
|
||||||
user_modules_.push_back(module);
|
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();
|
module->Dump();
|
||||||
|
|
||||||
if (module->is_dll_module() && module->entry_point() && call_entry) {
|
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,
|
processor()->Execute(thread_state, module->entry_point(), args,
|
||||||
xe::countof(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,
|
void KernelState::UnloadUserModule(const object_ref<UserModule>& module,
|
||||||
|
|
|
@ -134,6 +134,8 @@ class KernelState {
|
||||||
void SetExecutableModule(object_ref<UserModule> module);
|
void SetExecutableModule(object_ref<UserModule> module);
|
||||||
object_ref<UserModule> LoadUserModule(const std::string_view name,
|
object_ref<UserModule> LoadUserModule(const std::string_view name,
|
||||||
bool call_entry = true);
|
bool call_entry = true);
|
||||||
|
X_RESULT FinishLoadingUserModule(const object_ref<UserModule> module,
|
||||||
|
bool call_entry = true);
|
||||||
void UnloadUserModule(const object_ref<UserModule>& module,
|
void UnloadUserModule(const object_ref<UserModule>& module,
|
||||||
bool call_entry = true);
|
bool call_entry = true);
|
||||||
|
|
||||||
|
@ -150,6 +152,7 @@ class KernelState {
|
||||||
return object_ref<T>(reinterpret_cast<T*>(module.release()));
|
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.
|
// Terminates a title: Unloads all modules, and kills all guest threads.
|
||||||
// This DOES NOT RETURN if called from a guest thread!
|
// This DOES NOT RETURN if called from a guest thread!
|
||||||
void TerminateTitle();
|
void TerminateTitle();
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
#include "xenia/kernel/xfile.h"
|
#include "xenia/kernel/xfile.h"
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/kernel/xthread.h"
|
||||||
|
|
||||||
DEFINE_bool(xex_apply_patches, true, "Apply XEX patches.", "Kernel");
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
|
|
||||||
|
@ -34,20 +32,38 @@ uint32_t UserModule::title_id() const {
|
||||||
if (module_format_ != kModuleFormatXex) {
|
if (module_format_ != kModuleFormatXex) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
auto header = xex_header();
|
|
||||||
for (uint32_t i = 0; i < header->header_count; i++) {
|
xex2_opt_execution_info* opt_exec_info = nullptr;
|
||||||
auto& opt_header = header->headers[i];
|
if (xex_module()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &opt_exec_info)) {
|
||||||
if (opt_header.key == XEX_HEADER_EXECUTION_INFO) {
|
return static_cast<uint32_t>(opt_exec_info->title_id);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0;
|
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 UserModule::LoadFromFile(const std::string_view path) {
|
||||||
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
||||||
|
|
||||||
|
@ -97,38 +113,7 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) {
|
||||||
file->Destroy();
|
file->Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only XEX returns X_STATUS_PENDING
|
return result;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) {
|
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;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
X_STATUS UserModule::LoadXexContinue() {
|
X_STATUS UserModule::LoadContinue() {
|
||||||
// LoadXexContinue: finishes loading XEX after a patch has been applied (or
|
// LoadXexContinue: finishes loading XEX after a patch has been applied (or
|
||||||
// patch wasn't found)
|
// patch wasn't found)
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,9 @@ class UserModule : public XModule {
|
||||||
uint32_t guest_xex_header() const { return guest_xex_header_; }
|
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.
|
// The title ID in the xex header or 0 if this is not a xex.
|
||||||
uint32_t title_id() const;
|
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_executable() const { return processor_module_->is_executable(); }
|
||||||
bool is_dll_module() const { return is_dll_module_; }
|
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 LoadFromFile(const std::string_view path);
|
||||||
X_STATUS LoadFromMemory(const void* addr, const size_t length);
|
X_STATUS LoadFromMemory(const void* addr, const size_t length);
|
||||||
|
X_STATUS LoadContinue();
|
||||||
X_STATUS Unload();
|
X_STATUS Unload();
|
||||||
|
|
||||||
uint32_t GetProcAddressByOrdinal(uint16_t ordinal) override;
|
uint32_t GetProcAddressByOrdinal(uint16_t ordinal) override;
|
||||||
|
@ -96,8 +100,6 @@ class UserModule : public XModule {
|
||||||
const std::string_view path);
|
const std::string_view path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
X_STATUS LoadXexContinue();
|
|
||||||
|
|
||||||
std::string name_;
|
std::string name_;
|
||||||
std::string path_;
|
std::string path_;
|
||||||
|
|
||||||
|
|
|
@ -71,11 +71,18 @@ std::filesystem::path ContentManager::ResolvePackageRoot(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path ContentManager::ResolvePackagePath(
|
std::filesystem::path ContentManager::ResolvePackagePath(
|
||||||
const XCONTENT_AGGREGATE_DATA& data) {
|
const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) {
|
||||||
// Content path:
|
// Content path:
|
||||||
// content_root/title_id/content_type/data_file_name/
|
// content_root/title_id/content_type/data_file_name/
|
||||||
auto package_root = ResolvePackageRoot(data.content_type, data.title_id);
|
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(
|
std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
|
||||||
|
@ -108,8 +115,9 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
|
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
|
||||||
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data) {
|
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data,
|
||||||
auto package_path = ResolvePackagePath(data);
|
const uint32_t disc_number) {
|
||||||
|
auto package_path = ResolvePackagePath(data, disc_number);
|
||||||
if (!std::filesystem::exists(package_path)) {
|
if (!std::filesystem::exists(package_path)) {
|
||||||
return nullptr;
|
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,
|
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();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
if (open_packages_.count(string_key(root_name))) {
|
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;
|
return X_ERROR_ALREADY_EXISTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
auto package_path = ResolvePackagePath(data, disc_number);
|
||||||
if (!std::filesystem::exists(package_path)) {
|
if (!std::filesystem::exists(package_path)) {
|
||||||
// Does not exist, must be created.
|
// Does not exist, must be created.
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open package.
|
// Open package.
|
||||||
auto package = ResolvePackage(root_name, data);
|
auto package = ResolvePackage(root_name, data, disc_number);
|
||||||
assert_not_null(package);
|
assert_not_null(package);
|
||||||
|
|
||||||
open_packages_.insert({string_key::create(root_name), package.release()});
|
open_packages_.insert({string_key::create(root_name), package.release()});
|
||||||
|
|
|
@ -146,13 +146,15 @@ class ContentManager {
|
||||||
uint32_t title_id = -1);
|
uint32_t title_id = -1);
|
||||||
|
|
||||||
std::unique_ptr<ContentPackage> ResolvePackage(
|
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);
|
bool ContentExists(const XCONTENT_AGGREGATE_DATA& data);
|
||||||
X_RESULT CreateContent(const std::string_view root_name,
|
X_RESULT CreateContent(const std::string_view root_name,
|
||||||
const XCONTENT_AGGREGATE_DATA& data);
|
const XCONTENT_AGGREGATE_DATA& data);
|
||||||
X_RESULT OpenContent(const std::string_view root_name,
|
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 CloseContent(const std::string_view root_name);
|
||||||
X_RESULT GetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data,
|
X_RESULT GetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data,
|
||||||
std::vector<uint8_t>* buffer);
|
std::vector<uint8_t>* buffer);
|
||||||
|
@ -166,7 +168,8 @@ class ContentManager {
|
||||||
private:
|
private:
|
||||||
std::filesystem::path ResolvePackageRoot(XContentType content_type,
|
std::filesystem::path ResolvePackageRoot(XContentType content_type,
|
||||||
uint32_t title_id = -1);
|
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_;
|
KernelState* kernel_state_;
|
||||||
std::filesystem::path root_path_;
|
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.
|
// Not found; attempt to load as a user module.
|
||||||
auto user_module = kernel_state()->LoadUserModule(module_name.value());
|
auto user_module = kernel_state()->LoadUserModule(module_name.value());
|
||||||
if (user_module) {
|
if (user_module) {
|
||||||
|
kernel_state()->FinishLoadingUserModule(user_module);
|
||||||
// Give up object ownership, this reference will be released by the last
|
// Give up object ownership, this reference will be released by the last
|
||||||
// XexUnloadImage call
|
// XexUnloadImage call
|
||||||
auto user_module_raw = user_module.release();
|
auto user_module_raw = user_module.release();
|
||||||
|
|
Loading…
Reference in New Issue