diff --git a/src/xenia/cpu/xex_module.cc b/src/xenia/cpu/xex_module.cc index 772a2439a..73028d92b 100644 --- a/src/xenia/cpu/xex_module.cc +++ b/src/xenia/cpu/xex_module.cc @@ -230,18 +230,6 @@ int XexModule::ApplyPatch(XexModule* module) { reinterpret_cast(&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; if (patch_header->delta_headers_source_offset > size) { 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); } + uint8_t digest[0x14]; + sha1::SHA1 s; // Now loop through each block and apply the delta patches inside while (cur_block->block_size) { const auto* next_block = (const xex2_compressed_block_info*)p; diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index b8c1cdc5e..55a14adf0 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -31,6 +31,9 @@ #include "xenia/kernel/xnotifylistener.h" #include "xenia/kernel/xobject.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"); @@ -550,28 +553,59 @@ X_RESULT KernelState::FinishLoadingUserModule( return result; } -X_RESULT KernelState::ApplyTitleUpdate(const object_ref module) { - X_RESULT result = X_STATUS_SUCCESS; +X_RESULT KernelState::ApplyTitleUpdate( + const object_ref 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 KernelState::FindTitleUpdate( + const uint32_t title_id) const { if (!cvars::apply_title_update) { - return result; + return {}; } - std::vector tu_list = - content_manager()->ListContent(1, xe::XContentType::kInstaller, - module->title_id()); - - if (tu_list.empty()) { - return result; - } + return content_manager()->ListContent(1, xe::XContentType::kInstaller, + title_id); +} +const object_ref KernelState::LoadTitleUpdate( + const xam::XCONTENT_AGGREGATE_DATA* title_update, + const object_ref module) { 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); + content_manager()->OpenContent("UPDATE", *title_update, disc_number); // Use the corresponding patch for the launch module std::filesystem::path patch_xexp; @@ -582,7 +616,7 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref module) { auto is_relative = std::filesystem::relative(module->path(), mount_path); if (is_relative.empty()) { - return X_STATUS_UNSUCCESSFUL; + return nullptr; } patch_xexp = @@ -593,22 +627,66 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref module) { xe::vfs::Entry* patch_entry = kernel_state()->file_system()->ResolvePath( resolved_path + patch_xexp.generic_string()); - if (patch_entry) { - const std::string patch_path = patch_entry->absolute_path(); - XELOGI("Loading XEX patch from {}", patch_path); - auto patch_module = object_ref(new UserModule(this)); + if (!patch_entry) { + return nullptr; + } - result = patch_module->LoadFromFile(patch_path); - if (result != X_STATUS_SUCCESS) { - XELOGE("Failed to load XEX patch, code: {}", result); - return X_STATUS_UNSUCCESSFUL; - } + const std::string patch_path = patch_entry->absolute_path(); + XELOGI("Loading XEX patch from {}", patch_path); + auto patch_module = object_ref(new UserModule(this)); - 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; - } + X_RESULT result = patch_module->LoadFromFile(patch_path); + if (result != X_STATUS_SUCCESS) { + XELOGE("Failed to load XEX patch, code: {}", result); + return nullptr; + } + + return patch_module; +} + +bool KernelState::IsPatchSignatureProper( + const object_ref title_module, + const object_ref patch_module) const { + xex2_opt_delta_patch_descriptor* patch_header = nullptr; + patch_module->GetOptHeader(XEX_HEADER_DELTA_PATCH_DESCRIPTOR, + reinterpret_cast(&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 title_module, + const object_ref patch_module) { + if (!title_module) { + XELOGE("{}: No title_module provided!", __FUNCTION__); + return X_STATUS_UNSUCCESSFUL; + } + + if (!patch_module) { + XELOGE("{}: No patch_module provided!", __FUNCTION__); + 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; } diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index 691155756..b4097061b 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -279,7 +279,7 @@ class KernelState { return object_ref(reinterpret_cast(module.release())); } - X_RESULT ApplyTitleUpdate(const object_ref module); + X_RESULT ApplyTitleUpdate(const object_ref title_module); // Terminates a title: Unloads all modules, and kills all guest threads. // This DOES NOT RETURN if called from a guest thread! void TerminateTitle(); @@ -352,6 +352,18 @@ class KernelState { void SetProcessTLSVars(X_KPROCESS* process, int num_slots, int tls_data_size, int tls_static_data_address); void InitializeKernelGuestGlobals(); + + std::vector FindTitleUpdate( + const uint32_t title_id) const; + const object_ref LoadTitleUpdate( + const xam::XCONTENT_AGGREGATE_DATA* title_update, + const object_ref module); + bool IsPatchSignatureProper(const object_ref title_module, + const object_ref patch_module) const; + + X_RESULT ApplyTitleUpdate(const object_ref title_module, + const object_ref patch_module); + Emulator* emulator_; Memory* memory_; cpu::Processor* processor_;