[Kernel] Cleanup in TU applying code.

Externally it is still applied via: ApplyTitleUpdate
But internally it is splitted into:
 - FindTitleUpdate
 - LoadTitleUpdate
 - IsPatchSignatureProper
 - ApplyTitleUpdate

Added host window with warning about TU incompatibility
This commit is contained in:
Gliniak 2024-09-07 18:26:00 +02:00
parent 0ee6c77d91
commit 71de56416d
3 changed files with 121 additions and 41 deletions

View File

@ -230,18 +230,6 @@ int XexModule::ApplyPatch(XexModule* module) {
reinterpret_cast<void**>(&patch_header)); reinterpret_cast<void**>(&patch_header));
assert_not_null(patch_header); assert_not_null(patch_header);
// Compare hash inside delta descriptor to base XEX signature
uint8_t digest[0x14];
sha1::SHA1 s;
s.processBytes(module->xex_security_info()->rsa_signature, 0x100);
s.finalize(digest);
if (memcmp(digest, patch_header->digest_source, 0x14) != 0) {
XELOGW(
"XEX patch signature hash doesn't match base XEX signature hash, patch "
"will likely fail!");
}
uint32_t size = module->xex_header()->header_size; uint32_t size = module->xex_header()->header_size;
if (patch_header->delta_headers_source_offset > size) { if (patch_header->delta_headers_source_offset > size) {
XELOGE("XEX header patch source is outside base XEX header area"); XELOGE("XEX header patch source is outside base XEX header area");
@ -419,6 +407,8 @@ int XexModule::ApplyPatch(XexModule* module) {
original_image_size - image_target_size); original_image_size - image_target_size);
} }
uint8_t digest[0x14];
sha1::SHA1 s;
// Now loop through each block and apply the delta patches inside // Now loop through each block and apply the delta patches inside
while (cur_block->block_size) { while (cur_block->block_size) {
const auto* next_block = (const xex2_compressed_block_info*)p; const auto* next_block = (const xex2_compressed_block_info*)p;

View File

@ -31,6 +31,9 @@
#include "xenia/kernel/xnotifylistener.h" #include "xenia/kernel/xnotifylistener.h"
#include "xenia/kernel/xobject.h" #include "xenia/kernel/xobject.h"
#include "xenia/kernel/xthread.h" #include "xenia/kernel/xthread.h"
#include "xenia/ui/imgui_host_notification.h"
#include "third_party/crypto/TinySHA1.hpp"
DEFINE_bool(apply_title_update, true, "Apply title updates.", "Kernel"); DEFINE_bool(apply_title_update, true, "Apply title updates.", "Kernel");
@ -550,28 +553,59 @@ X_RESULT KernelState::FinishLoadingUserModule(
return result; return result;
} }
X_RESULT KernelState::ApplyTitleUpdate(const object_ref<UserModule> module) { X_RESULT KernelState::ApplyTitleUpdate(
X_RESULT result = X_STATUS_SUCCESS; const object_ref<UserModule> title_module) {
const auto title_updates = FindTitleUpdate(title_module->title_id());
if (title_updates.empty()) {
return X_STATUS_SUCCESS;
}
auto patch_module = LoadTitleUpdate(&title_updates.front(), title_module);
if (!patch_module) {
return X_STATUS_SUCCESS;
}
if (!patch_module->xex_module()->is_patch()) {
return X_STATUS_UNSUCCESSFUL;
}
if (!IsPatchSignatureProper(title_module, patch_module)) {
// First module that is loaded is always main executable. That way we can
// prevent random message spam in case of loading/unloading.
if (!GetExecutableModule()) {
emulator_->display_window()->app_context().CallInUIThread([&]() {
new xe::ui::HostNotificationWindow(
emulator_->imgui_drawer(), "Warning!",
"Title Update signature doesn't match. This can cause unexpected "
"issues or crashes!",
0);
});
}
}
return ApplyTitleUpdate(title_module, patch_module);
}
std::vector<xam::XCONTENT_AGGREGATE_DATA> KernelState::FindTitleUpdate(
const uint32_t title_id) const {
if (!cvars::apply_title_update) { if (!cvars::apply_title_update) {
return result; return {};
} }
std::vector<xam::XCONTENT_AGGREGATE_DATA> tu_list = return content_manager()->ListContent(1, xe::XContentType::kInstaller,
content_manager()->ListContent(1, xe::XContentType::kInstaller, title_id);
module->title_id()); }
if (tu_list.empty()) {
return result;
}
const object_ref<UserModule> KernelState::LoadTitleUpdate(
const xam::XCONTENT_AGGREGATE_DATA* title_update,
const object_ref<UserModule> module) {
uint32_t disc_number = -1; uint32_t disc_number = -1;
if (module->is_multi_disc_title()) { if (module->is_multi_disc_title()) {
disc_number = module->disc_number(); 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 = X_RESULT open_status =
content_manager()->OpenContent("UPDATE", title_update, disc_number); content_manager()->OpenContent("UPDATE", *title_update, disc_number);
// Use the corresponding patch for the launch module // Use the corresponding patch for the launch module
std::filesystem::path patch_xexp; std::filesystem::path patch_xexp;
@ -582,7 +616,7 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref<UserModule> module) {
auto is_relative = std::filesystem::relative(module->path(), mount_path); auto is_relative = std::filesystem::relative(module->path(), mount_path);
if (is_relative.empty()) { if (is_relative.empty()) {
return X_STATUS_UNSUCCESSFUL; return nullptr;
} }
patch_xexp = patch_xexp =
@ -593,22 +627,66 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref<UserModule> module) {
xe::vfs::Entry* patch_entry = kernel_state()->file_system()->ResolvePath( xe::vfs::Entry* patch_entry = kernel_state()->file_system()->ResolvePath(
resolved_path + patch_xexp.generic_string()); resolved_path + patch_xexp.generic_string());
if (patch_entry) { if (!patch_entry) {
return nullptr;
}
const std::string patch_path = patch_entry->absolute_path(); const std::string patch_path = patch_entry->absolute_path();
XELOGI("Loading XEX patch from {}", patch_path); XELOGI("Loading XEX patch from {}", patch_path);
auto patch_module = object_ref<UserModule>(new UserModule(this)); auto patch_module = object_ref<UserModule>(new UserModule(this));
result = patch_module->LoadFromFile(patch_path); X_RESULT result = patch_module->LoadFromFile(patch_path);
if (result != X_STATUS_SUCCESS) { if (result != X_STATUS_SUCCESS) {
XELOGE("Failed to load XEX patch, code: {}", result); XELOGE("Failed to load XEX patch, code: {}", result);
return nullptr;
}
return patch_module;
}
bool KernelState::IsPatchSignatureProper(
const object_ref<UserModule> title_module,
const object_ref<UserModule> patch_module) const {
xex2_opt_delta_patch_descriptor* patch_header = nullptr;
patch_module->GetOptHeader(XEX_HEADER_DELTA_PATCH_DESCRIPTOR,
reinterpret_cast<void**>(&patch_header));
assert_not_null(patch_header);
// Compare hash inside delta descriptor to base XEX signature
uint8_t digest[0x14];
sha1::SHA1 s;
s.processBytes(title_module->xex_module()->xex_security_info()->rsa_signature,
0x100);
s.finalize(digest);
if (memcmp(digest, patch_header->digest_source, 0x14) != 0) {
XELOGW(
"XEX patch signature hash doesn't match base XEX signature hash, patch "
"will likely fail!");
return false;
}
return true;
}
X_RESULT KernelState::ApplyTitleUpdate(
const object_ref<UserModule> title_module,
const object_ref<UserModule> patch_module) {
if (!title_module) {
XELOGE("{}: No title_module provided!", __FUNCTION__);
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;
} }
result = patch_module->xex_module()->ApplyPatch(module->xex_module()); if (!patch_module) {
if (result != X_STATUS_SUCCESS) { XELOGE("{}: No patch_module provided!", __FUNCTION__);
XELOGE("Failed to apply XEX patch, code: {}", result);
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;
} }
X_STATUS result =
patch_module->xex_module()->ApplyPatch(title_module->xex_module());
if (result != X_STATUS_SUCCESS) {
XELOGE("Failed to apply XEX patch, code: {}", result);
} }
return result; return result;
} }

View File

@ -279,7 +279,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); X_RESULT ApplyTitleUpdate(const object_ref<UserModule> title_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();
@ -352,6 +352,18 @@ class KernelState {
void SetProcessTLSVars(X_KPROCESS* process, int num_slots, int tls_data_size, void SetProcessTLSVars(X_KPROCESS* process, int num_slots, int tls_data_size,
int tls_static_data_address); int tls_static_data_address);
void InitializeKernelGuestGlobals(); void InitializeKernelGuestGlobals();
std::vector<xam::XCONTENT_AGGREGATE_DATA> FindTitleUpdate(
const uint32_t title_id) const;
const object_ref<UserModule> LoadTitleUpdate(
const xam::XCONTENT_AGGREGATE_DATA* title_update,
const object_ref<UserModule> module);
bool IsPatchSignatureProper(const object_ref<UserModule> title_module,
const object_ref<UserModule> patch_module) const;
X_RESULT ApplyTitleUpdate(const object_ref<UserModule> title_module,
const object_ref<UserModule> patch_module);
Emulator* emulator_; Emulator* emulator_;
Memory* memory_; Memory* memory_;
cpu::Processor* processor_; cpu::Processor* processor_;