diff --git a/src/xenia/cpu/xex_module.cc b/src/xenia/cpu/xex_module.cc index 2c27628c8..ccc6b81f5 100644 --- a/src/xenia/cpu/xex_module.cc +++ b/src/xenia/cpu/xex_module.cc @@ -29,68 +29,12 @@ #include "third_party/mspack/mspack.h" #include "third_party/pe/pe_image.h" -namespace xe { -namespace cpu { - -using xe::kernel::KernelState; - -void UndefinedImport(ppc::PPCContext* ppc_context, KernelState* kernel_state) { - XELOGE("call to undefined import"); -} - -XexModule::XexModule(Processor* processor, KernelState* kernel_state) - : Module(processor), processor_(processor), kernel_state_(kernel_state) {} - -XexModule::~XexModule() {} - -bool XexModule::GetOptHeader(const xex2_header* header, xex2_header_keys key, - void** out_ptr) { - assert_not_null(header); - assert_not_null(out_ptr); - - for (uint32_t i = 0; i < header->header_count; i++) { - const xex2_opt_header& opt_header = header->headers[i]; - if (opt_header.key == key) { - // Match! - switch (key & 0xFF) { - case 0x00: { - // We just return the value of the optional header. - // Assume that the output pointer points to a uint32_t. - *reinterpret_cast(out_ptr) = - static_cast(opt_header.value); - } break; - case 0x01: { - // Pointer to the value on the optional header. - *out_ptr = const_cast( - reinterpret_cast(&opt_header.value)); - } break; - default: { - // Pointer to the header. - *out_ptr = - reinterpret_cast(uintptr_t(header) + opt_header.offset); - } break; - } - - return true; - } - } - - return false; -} - -bool XexModule::GetOptHeader(xex2_header_keys key, void** out_ptr) const { - return XexModule::GetOptHeader(xex_header(), key, out_ptr); -} - -const PESection* XexModule::GetPESection(const char* name) { - for (std::vector::iterator it = pe_sections_.begin(); - it != pe_sections_.end(); ++it) { - if (!strcmp(it->name, name)) { - return &(*it); - } - } - return nullptr; -} +static const uint8_t xe_xex2_retail_key[16] = { + 0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3, + 0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91}; +static const uint8_t xe_xex2_devkit_key[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; typedef struct mspack_memory_file_t { struct mspack_system sys; @@ -156,10 +100,104 @@ struct mspack_system* mspack_memory_sys_create() { } void mspack_memory_sys_destroy(struct mspack_system* sys) { free(sys); } -void XexModule::DecryptBuffer(const uint8_t* session_key, - const uint8_t* input_buffer, - const size_t input_size, uint8_t* output_buffer, - const size_t output_size) { +int lzx_decompress(const void* lzx_data, size_t lzx_len, void* dest, + size_t dest_len, int window_size, void* window_data, + size_t window_data_len) { + uint32_t window_bits = 0; + uint32_t temp_sz = window_size; + for (size_t m = 0; m < 32; m++, window_bits++) { + temp_sz >>= 1; + if (temp_sz == 0x00000000) { + break; + } + } + + int result_code = 1; + + mspack_system* sys = mspack_memory_sys_create(); + mspack_memory_file* lzxsrc = + mspack_memory_open(sys, (void*)lzx_data, lzx_len); + mspack_memory_file* lzxdst = mspack_memory_open(sys, dest, dest_len); + lzxd_stream* lzxd = + lzxd_init(sys, (struct mspack_file*)lzxsrc, (struct mspack_file*)lzxdst, + window_bits, 0, 0x8000, (off_t)dest_len); + + if (lzxd) { + if (window_data) { + // zero the window and then copy window_data to the end of it + memset(lzxd->window, 0, window_data_len); + memcpy(lzxd->window + (window_size - window_data_len), window_data, + window_data_len); + } + + result_code = lzxd_decompress(lzxd, (off_t)dest_len); + + lzxd_free(lzxd); + lzxd = NULL; + } + if (lzxsrc) { + mspack_memory_close(lzxsrc); + lzxsrc = NULL; + } + if (lzxdst) { + mspack_memory_close(lzxdst); + lzxdst = NULL; + } + if (sys) { + mspack_memory_sys_destroy(sys); + sys = NULL; + } + + return result_code; +} + +int lzxdelta_apply_patch(xe::xex2_delta_patch* patch, size_t patch_len, + void* dest) { + void* patch_end = (char*)patch + patch_len; + auto* cur_patch = patch; + + while (patch_end > cur_patch) { + int patch_sz = -4; // 0 byte patches need us to remove 4 byte from next + // patch addr because of patch_data field + if (cur_patch->compressed_len == 0 && cur_patch->uncompressed_len == 0 && + cur_patch->new_addr == 0 && cur_patch->old_addr == 0) + break; + switch (cur_patch->compressed_len) { + case 0: // fill with 0 + memset((char*)dest + cur_patch->new_addr, 0, + cur_patch->uncompressed_len); + break; + case 1: // copy from old -> new + memcpy((char*)dest + cur_patch->new_addr, + (char*)dest + cur_patch->old_addr, cur_patch->uncompressed_len); + break; + default: // delta patch + patch_sz = + cur_patch->compressed_len - 4; // -4 because of patch_data field + + int result = lzx_decompress( + cur_patch->patch_data, cur_patch->compressed_len, + (char*)dest + cur_patch->new_addr, cur_patch->uncompressed_len, + 0x8000, (char*)dest + cur_patch->old_addr, + cur_patch->uncompressed_len); + + if (result) { + return result; + } + break; + } + + cur_patch++; + cur_patch = (xe::xex2_delta_patch*)((char*)cur_patch + + patch_sz); // TODO: make this less ugly + } + + return 0; +} + +void aes_decrypt_buffer(const uint8_t* session_key, const uint8_t* input_buffer, + const size_t input_size, uint8_t* output_buffer, + const size_t output_size) { uint32_t rk[4 * (MAXNR + 1)]; uint8_t ivec[16] = {0}; int32_t Nr = rijndaelKeySetupDec(rk, session_key, 128); @@ -177,6 +215,71 @@ void XexModule::DecryptBuffer(const uint8_t* session_key, } } +namespace xe { +namespace cpu { + +using xe::kernel::KernelState; + +XexModule::XexModule(Processor* processor, KernelState* kernel_state) + : Module(processor), processor_(processor), kernel_state_(kernel_state) {} + +XexModule::~XexModule() {} + +bool XexModule::GetOptHeader(const xex2_header* header, xex2_header_keys key, + void** out_ptr) { + assert_not_null(header); + assert_not_null(out_ptr); + + for (uint32_t i = 0; i < header->header_count; i++) { + const xex2_opt_header& opt_header = header->headers[i]; + if (opt_header.key == key) { + // Match! + switch (key & 0xFF) { + case 0x00: { + // We just return the value of the optional header. + // Assume that the output pointer points to a uint32_t. + *reinterpret_cast(out_ptr) = + static_cast(opt_header.value); + } break; + case 0x01: { + // Pointer to the value on the optional header. + *out_ptr = const_cast( + reinterpret_cast(&opt_header.value)); + } break; + default: { + // Pointer to the header. + *out_ptr = + reinterpret_cast(uintptr_t(header) + opt_header.offset); + } break; + } + + return true; + } + } + + return false; +} + +bool XexModule::GetOptHeader(xex2_header_keys key, void** out_ptr) const { + return XexModule::GetOptHeader(xex_header(), key, out_ptr); +} + +const xex2_security_info* XexModule::GetSecurityInfo( + const xex2_header* header) { + return reinterpret_cast(uintptr_t(header) + + header->security_offset); +} + +const PESection* XexModule::GetPESection(const char* name) { + for (std::vector::iterator it = pe_sections_.begin(); + it != pe_sections_.end(); ++it) { + if (!strcmp(it->name, name)) { + return &(*it); + } + } + return nullptr; +} + uint32_t XexModule::GetProcAddress(uint16_t ordinal) const { // First: Check the xex2 export table. if (xex_security_info()->export_table) { @@ -216,14 +319,14 @@ uint32_t XexModule::GetProcAddress(uint16_t ordinal) const { } uint32_t XexModule::GetProcAddress(const char* name) const { + assert_not_zero(base_address_); + xex2_opt_data_directory* pe_export_directory = 0; if (!GetOptHeader(XEX_HEADER_EXPORTS_BY_NAME, &pe_export_directory)) { // No exports by name. return 0; } - assert_not_zero(base_address_); - auto e = memory()->TranslateVirtual( base_address_ + pe_export_directory->offset); assert_not_null(e); @@ -254,63 +357,259 @@ uint32_t XexModule::GetProcAddress(const char* name) const { return 0; } -bool XexModule::ApplyPatch(XexModule* module) { - auto header = reinterpret_cast(module->xex_header()); - if (!(header->module_flags & - (XEX_MODULE_MODULE_PATCH | XEX_MODULE_PATCH_DELTA | - XEX_MODULE_PATCH_FULL))) { +int XexModule::ApplyPatch(XexModule* module) { + if (!is_patch()) { // This isn't a XEX2 patch. - return false; + return 1; } // Grab the delta descriptor and get to work. xex2_opt_delta_patch_descriptor* patch_header = nullptr; - GetOptHeader(header, XEX_HEADER_DELTA_PATCH_DESCRIPTOR, + GetOptHeader(XEX_HEADER_DELTA_PATCH_DESCRIPTOR, reinterpret_cast(&patch_header)); assert_not_null(patch_header); - // TODO(benvanik): patching code! + // Compare hash inside delta descriptor to our loaded base XEX + uint8_t digest[0x14]; + sha1::SHA1 s; + s.processBytes(module->xex_security_info()->rsa_signature, 0x100); + s.finalize(digest); - return true; + 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"); + return 2; + } + + uint32_t header_size_available = + size - patch_header->delta_headers_source_offset; + if (patch_header->delta_headers_source_size > header_size_available) { + XELOGE("XEX header patch source is too large"); + return 3; + } + + if (patch_header->delta_headers_target_offset > + patch_header->size_of_target_headers) { + XELOGE("XEX header patch target is outside base XEX header area"); + return 4; + } + + uint32_t delta_target_size = patch_header->size_of_target_headers - + patch_header->delta_headers_target_offset; + if (patch_header->delta_headers_source_size > delta_target_size) { + return 5; // ? unsure what the point of this test is, kernel checks for it + // though + } + + // TODO: actually use delta_*_offset / delta_*_size etc + assert_zero(patch_header->delta_headers_source_offset); + assert_zero(patch_header->delta_headers_target_offset); + + // Patch base XEX's header + module->xex_header_mem_.resize(patch_header->size_of_target_headers); + + uint32_t original_image_size = module->image_size(); + uint32_t headerpatch_size = patch_header->info.compressed_len; + + auto dest_header = module->xex_header(); + int result_code = lzxdelta_apply_patch( + &patch_header->info, headerpatch_size + 0xC, (void*)dest_header); + if (result_code) { + XELOGE("XEX header patch application failed, error code %d", result_code); + return result_code; + } + + // Check if we need to alloc new memory for the patched xex + uint32_t new_image_size = module->image_size(); + if (new_image_size > original_image_size) { + uint32_t size_delta = new_image_size - original_image_size; + uint32_t addr_new_mem = module->base_address_ + original_image_size; + + bool alloc_result = + memory() + ->LookupHeap(addr_new_mem) + ->AllocFixed( + addr_new_mem, size_delta, 4096, + xe::kMemoryAllocationReserve | xe::kMemoryAllocationCommit, + xe::kMemoryProtectRead | xe::kMemoryProtectWrite); + + if (!alloc_result) { + XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", addr_new_mem, + size_delta); + assert_always(); + return 6; + } + } + + uint8_t orig_session_key[0x10]; + memcpy(orig_session_key, module->session_key_, 0x10); + + // Header patch updated the base XEX key, need to redecrypt it + aes_decrypt_buffer( + module->is_dev_kit_ ? xe_xex2_devkit_key : xe_xex2_retail_key, + reinterpret_cast(module->xex_security_info()->aes_key), + 16, module->session_key_, 16); + + // Decrypt the patch XEX's key using base XEX key + aes_decrypt_buffer( + module->session_key_, + reinterpret_cast(xex_security_info()->aes_key), 16, + session_key_, 16); + + // Test delta key against our decrypted keys + // (kernel doesn't seem to check this, but it's the one use for the + // image_key_source field I can think of...) + uint8_t test_delta_key[0x10]; + aes_decrypt_buffer(module->session_key_, patch_header->image_key_source, 0x10, + test_delta_key, 0x10); + + if (memcmp(test_delta_key, orig_session_key, 0x10) != 0) { + XELOGE("XEX patch image key doesn't match original XEX!"); + return 7; + } + + // Decrypt (if needed). + bool free_input = false; + const uint8_t* exe_buffer = xexp_data_mem_.data(); + const size_t exe_length = xexp_data_mem_.size(); + + const uint8_t* input_buffer = exe_buffer; + + switch (opt_file_format_info()->encryption_type) { + case XEX_ENCRYPTION_NONE: + // No-op. + break; + case XEX_ENCRYPTION_NORMAL: + // TODO: a way to do without a copy/alloc? + free_input = true; + input_buffer = (const uint8_t*)calloc(1, exe_length); + aes_decrypt_buffer(session_key_, exe_buffer, exe_length, + (uint8_t*)input_buffer, exe_length); + break; + default: + assert_always(); + return 8; + } + + // Now loop through each block and apply the delta patches inside + + const xex2_compressed_block_info* cur_block = + &opt_file_format_info()->compression_info.normal.first_block; + + const uint8_t* p = input_buffer; + uint8_t* base_exe = memory()->TranslateVirtual(module->base_address_); + + while (cur_block->block_size) { + const auto* next_block = (const xex2_compressed_block_info*)p; + + // Compare block hash, if no match we probably used wrong decrypt key + s.reset(); + s.processBytes(p, cur_block->block_size); + s.finalize(digest); + + if (memcmp(digest, cur_block->block_hash, 0x14) != 0) { + result_code = 9; + XELOGE("XEX patch block hash doesn't match hash inside block info!"); + break; + } + + // skip block info + p += 20; + p += 4; + + uint32_t block_data_size = cur_block->block_size - 20 - 4; + + result_code = + lzxdelta_apply_patch((xex2_delta_patch*)p, block_data_size, base_exe); + if (result_code) { + break; + } + + p += block_data_size; + cur_block = next_block; + } + + if (!result_code) { + // byteswap versions because of bitfields... + xex2_version source_ver, target_ver; + source_ver.value = + xe::byte_swap(patch_header->source_version.value); + + target_ver.value = + xe::byte_swap(patch_header->target_version.value); + + XELOGI( + "XEX patch applied successfully: base version: %d.%d.%d.%d, new " + "version: %d.%d.%d.%d", + source_ver.major, source_ver.minor, source_ver.build, source_ver.qfe, + target_ver.major, target_ver.minor, target_ver.build, target_ver.qfe); + } else { + XELOGE("XEX patch application failed, error code %d", result_code); + } + + if (free_input) { + free((void*)input_buffer); + } + return result_code; } -void XexModule::DecryptSessionKey(bool useDevkit) { - static const uint8_t xe_xex2_retail_key[16] = { - 0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3, - 0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91}; - static const uint8_t xe_xex2_devkit_key[16] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - const uint8_t* xexkey = useDevkit ? xe_xex2_devkit_key : xe_xex2_retail_key; - - // Decrypt the header key. - uint32_t rk[4 * (MAXNR + 1)]; - int32_t Nr = rijndaelKeySetupDec(rk, xexkey, 128); - rijndaelDecrypt(rk, Nr, - reinterpret_cast(xex_security_info()->aes_key), - session_key_); -} - -int XexModule::ReadImage(const void* xex_addr, size_t xex_length) { +int XexModule::ReadImage(const void* xex_addr, size_t xex_length, + bool use_dev_key) { if (!opt_file_format_info()) { return 1; } - auto* ff = opt_file_format_info(); + is_dev_kit_ = use_dev_key; + if (is_patch()) { + // Make a copy of patch data for other XEX's to use with ApplyPatch() + const uint32_t data_len = + static_cast(xex_length - xex_header()->header_size); + xexp_data_mem_.resize(data_len); + std::memcpy(xexp_data_mem_.data(), + (uint8_t*)xex_addr + xex_header()->header_size, data_len); + return 0; + } + + memory()->LookupHeap(base_address_)->Reset(); + + aes_decrypt_buffer( + use_dev_key ? xe_xex2_devkit_key : xe_xex2_retail_key, + reinterpret_cast(xex_security_info()->aes_key), 16, + session_key_, 16); + + int result_code = 0; switch (opt_file_format_info()->compression_type) { case XEX_COMPRESSION_NONE: - return ReadImageUncompressed(xex_addr, xex_length); + result_code = ReadImageUncompressed(xex_addr, xex_length); + break; case XEX_COMPRESSION_BASIC: - return ReadImageBasicCompressed(xex_addr, xex_length); + result_code = ReadImageBasicCompressed(xex_addr, xex_length); + break; case XEX_COMPRESSION_NORMAL: - case XEX_COMPRESSION_DELTA: - return ReadImageCompressed(xex_addr, xex_length); + result_code = ReadImageCompressed(xex_addr, xex_length); + break; default: assert_always(); - return 1; + return 2; } + + if (result_code) { + return result_code; + } + + if (is_patch() || is_valid_executable()) { + return 0; + } + + // Not a patch and image doesn't have proper PE header, return 3 + return 3; } int XexModule::ReadImageUncompressed(const void* xex_addr, size_t xex_length) { @@ -344,7 +643,8 @@ int XexModule::ReadImageUncompressed(const void* xex_addr, size_t xex_length) { memcpy(buffer, p, exe_length); return 0; case XEX_ENCRYPTION_NORMAL: - DecryptBuffer(session_key_, p, exe_length, buffer, uncompressed_size); + aes_decrypt_buffer(session_key_, p, exe_length, buffer, + uncompressed_size); return 0; default: assert_always(); @@ -461,13 +761,7 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) { uint8_t* compress_buffer = NULL; const uint8_t* p = NULL; uint8_t* d = NULL; - uint8_t* deblock_buffer = NULL; - // size_t block_size = 0; uint32_t uncompressed_size = 0; - struct mspack_system* sys = NULL; - mspack_memory_file* lzxsrc = NULL; - mspack_memory_file* lzxdst = NULL; - struct lzxd_stream* lzxd = NULL; sha1::SHA1 s; // Decrypt (if needed). @@ -483,8 +777,8 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) { // TODO: a way to do without a copy/alloc? free_input = true; input_buffer = (const uint8_t*)calloc(1, exe_length); - DecryptBuffer(session_key_, exe_buffer, exe_length, - (uint8_t*)input_buffer, exe_length); + aes_decrypt_buffer(session_key_, exe_buffer, exe_length, + (uint8_t*)input_buffer, exe_length); break; default: assert_always(); @@ -501,8 +795,6 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) { d = compress_buffer; // De-block. - deblock_buffer = (uint8_t*)calloc(1, input_size); - int result_code = 0; uint8_t block_calced_digest[0x14]; @@ -519,8 +811,9 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) { break; } + // skip block info p += 4; - p += 20; // skip 20b hash + p += 20; while (true) { const size_t chunk_size = (p[0] << 8) | p[1]; @@ -552,29 +845,12 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) { if (alloc_result) { uint8_t* buffer = memory()->TranslateVirtual(base_address_); - - // Reset buffer if this isn't a patch std::memset(buffer, 0, uncompressed_size); - // Setup decompressor and decompress. - uint32_t window_size = compression_info->normal.window_size; - uint32_t window_bits = 0; - for (size_t m = 0; m < 32; m++, window_bits++) { - window_size >>= 1; - if (window_size == 0x00000000) { - break; - } - } - - sys = mspack_memory_sys_create(); - lzxsrc = - mspack_memory_open(sys, (void*)compress_buffer, d - compress_buffer); - lzxdst = mspack_memory_open(sys, buffer, uncompressed_size); - lzxd = lzxd_init(sys, (struct mspack_file*)lzxsrc, - (struct mspack_file*)lzxdst, window_bits, 0, 32768, - (off_t)xex_security_info()->image_size); - result_code = - lzxd_decompress(lzxd, (off_t)xex_security_info()->image_size); + // Decompress into XEX base + result_code = lzx_decompress( + compress_buffer, d - compress_buffer, buffer, uncompressed_size, + compression_info->normal.window_size, nullptr, 0); } else { XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", base_address_, uncompressed_size); @@ -582,28 +858,9 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) { } } - if (lzxd) { - lzxd_free(lzxd); - lzxd = NULL; - } - if (lzxsrc) { - mspack_memory_close(lzxsrc); - lzxsrc = NULL; - } - if (lzxdst) { - mspack_memory_close(lzxdst); - lzxdst = NULL; - } - if (sys) { - mspack_memory_sys_destroy(sys); - sys = NULL; - } if (compress_buffer) { free((void*)compress_buffer); } - if (deblock_buffer) { - free((void*)deblock_buffer); - } if (free_input) { free((void*)input_buffer); } @@ -694,7 +951,6 @@ int XexModule::ReadPEHeaders() { section.size = sechdr->Misc.VirtualSize; section.flags = sechdr->Characteristics; pe_sections_.push_back(section); - // pe_sections_.push_back(section); } // DumpTLSDirectory(pImageBase, pNTHeader, (PIMAGE_TLS_DIRECTORY32)0); @@ -721,7 +977,7 @@ bool XexModule::Load(const std::string& name, const std::string& path, // Try setting our base_address based on XEX_HEADER_IMAGE_BASE_ADDRESS, fall // back to xex_security_info otherwise - base_address_ = sec_header->load_address; + base_address_ = xex_security_info()->load_address; xe::be* base_addr_opt = nullptr; if (GetOptHeader(XEX_HEADER_IMAGE_BASE_ADDRESS, &base_addr_opt)) base_address_ = *base_addr_opt; @@ -734,24 +990,37 @@ bool XexModule::Load(const std::string& name, const std::string& path, // Load in the XEX basefile // We'll try using both XEX2 keys to see if any give a valid PE - while (true) { - memory()->LookupHeap(base_address_)->Reset(); + int result_code = ReadImage(xex_addr, xex_length, false); + if (result_code) { + XELOGW("XEX load failed with code %d, trying with devkit encryption key...", + result_code); - DecryptSessionKey(is_dev_kit_); - - if (!ReadImage(xex_addr, xex_length) && !ReadPEHeaders()) { - break; - } - - is_dev_kit_ = !is_dev_kit_; - - // is_dev_kit starts as false, then flips to true if load failed, if it's - // back to false again this must be invalid - if (!is_dev_kit_) { + result_code = ReadImage(xex_addr, xex_length, true); + if (result_code) { + XELOGE("XEX load failed with code %d, tried both encryption keys", + result_code); return false; } + } - XELOGW("XEX load failed, trying with devkit encryption key..."); + // Note: caller will have to call LoadContinue once it's determined whether a + // patch file exists or not! + return true; +} + +bool XexModule::LoadContinue() { + // Second part of image load + // Split from Load() so that we can patch the XEX before loading this data + assert_false(finished_load_); + if (finished_load_) { + return true; + } + + finished_load_ = true; + + if (ReadPEHeaders()) { + XELOGE("Failed to load XEX PE headers!"); + return false; } // Scan and find the low/high addresses. @@ -762,6 +1031,7 @@ bool XexModule::Load(const std::string& name, const std::string& path, low_address_ = UINT_MAX; high_address_ = 0; + auto sec_header = xex_security_info(); for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) { // Byteswap the bitfield manually. xex2_page_descriptor desc; @@ -863,10 +1133,13 @@ bool XexModule::Unload() { } loaded_ = false; - // Just deallocate the memory occupied by the exe - assert_not_zero(base_address_); + // If this isn't a patch, just deallocate the memory occupied by the exe + if (!is_patch()) { + assert_not_zero(base_address_); + + memory()->LookupHeap(base_address_)->Release(base_address_); + } - memory()->LookupHeap(base_address_)->Release(base_address_); xex_header_mem_.resize(0); return true; @@ -1233,7 +1506,8 @@ bool XexModule::FindSaveRest() { auto page_size = base_address_ <= 0x90000000 ? 64 * 1024 : 4 * 1024; auto sec_header = xex_security_info(); for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) { - const xex2_page_descriptor* section = &sec_header->page_descriptors[i]; + const xex2_page_descriptor* section = + &xex_security_info()->page_descriptors[i]; const auto start_address = base_address_ + (page * page_size); const auto end_address = start_address + (section->size * page_size); diff --git a/src/xenia/cpu/xex_module.h b/src/xenia/cpu/xex_module.h index 5dee7dbf3..3bc5bc732 100644 --- a/src/xenia/cpu/xex_module.h +++ b/src/xenia/cpu/xex_module.h @@ -52,8 +52,24 @@ class XexModule : public xe::cpu::Module { return reinterpret_cast(xex_header_mem_.data()); } const xex2_security_info* xex_security_info() const { - return reinterpret_cast( - uintptr_t(xex_header()) + xex_header()->security_offset); + return GetSecurityInfo(xex_header()); + } + + uint32_t image_size() const { + assert_not_zero(base_address_); + + // Calculate the new total size of the XEX image from its headers. + auto heap = memory()->LookupHeap(base_address_); + uint32_t total_size = 0; + for (uint32_t i = 0; i < xex_security_info()->page_descriptor_count; i++) { + // Byteswap the bitfield manually. + xex2_page_descriptor desc; + desc.value = + xe::byte_swap(xex_security_info()->page_descriptors[i].value); + + total_size += desc.size * heap->page_size(); + } + return total_size; } const std::vector* import_libraries() const { @@ -73,6 +89,7 @@ class XexModule : public xe::cpu::Module { } const uint32_t base_address() const { return base_address_; } + const bool is_dev_kit() const { return is_dev_kit_; } // Gets an optional header. Returns NULL if not found. // Special case: if key & 0xFF == 0x00, this function will return the value, @@ -95,42 +112,50 @@ class XexModule : public xe::cpu::Module { return GetOptHeader(key, reinterpret_cast(out_ptr)); } + static const xex2_security_info* GetSecurityInfo(const xex2_header* header); + const PESection* GetPESection(const char* name); uint32_t GetProcAddress(uint16_t ordinal) const; uint32_t GetProcAddress(const char* name) const; - bool ApplyPatch(XexModule* module); + int ApplyPatch(XexModule* module); bool Load(const std::string& name, const std::string& path, const void* xex_addr, size_t xex_length); + bool LoadContinue(); bool Unload(); + bool ContainsAddress(uint32_t address) override; + const std::string& name() const override { return name_; } bool is_executable() const override { return (xex_header()->module_flags & XEX_MODULE_TITLE) != 0; } - bool ContainsAddress(uint32_t address) override; + bool is_valid_executable() const { + assert_not_zero(base_address_); + if (!base_address_) { + return false; + } + uint8_t* buffer = memory()->TranslateVirtual(base_address_); + return *(uint32_t*)buffer == 0x905A4D; + } - static void DecryptBuffer(const uint8_t* session_key, - const uint8_t* input_buffer, - const size_t input_size, uint8_t* output_buffer, - const size_t output_size); - - uint8_t* HostData() { - if (base_address_) - return memory()->TranslateVirtual(base_address_); - else - return nullptr; + bool is_patch() const { + assert_not_null(xex_header()); + if (!xex_header()) { + return false; + } + return (xex_header()->module_flags & + (XEX_MODULE_MODULE_PATCH | XEX_MODULE_PATCH_DELTA | + XEX_MODULE_PATCH_FULL)); } protected: std::unique_ptr CreateFunction(uint32_t address) override; private: - void DecryptSessionKey(bool useDevkit = false); - - int ReadImage(const void* xex_addr, size_t xex_length); + int ReadImage(const void* xex_addr, size_t xex_length, bool use_dev_key); int ReadImageUncompressed(const void* xex_addr, size_t xex_length); int ReadImageBasicCompressed(const void* xex_addr, size_t xex_length); int ReadImageCompressed(const void* xex_addr, size_t xex_length); @@ -146,22 +171,21 @@ class XexModule : public xe::cpu::Module { std::string name_; std::string path_; std::vector xex_header_mem_; // Holds the xex header + std::vector xexp_data_mem_; // Holds XEXP patch data - // various optional headers std::vector import_libs_; // pre-loaded import libraries for ease of use - std::vector pe_sections_; uint8_t session_key_[0x10]; + bool is_dev_kit_ = false; - bool loaded_ = false; // Loaded into memory? + bool loaded_ = false; // Loaded into memory? + bool finished_load_ = false; // PE/imports/symbols/etc all loaded? uint32_t base_address_ = 0; uint32_t low_address_ = 0; uint32_t high_address_ = 0; - - bool is_dev_kit_ = false; }; } // namespace cpu diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index f4c42caa2..60fcb71fe 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -95,7 +95,36 @@ X_STATUS UserModule::LoadFromFile(std::string path) { file->Destroy(); } - return result; + // Only XEX returns X_STATUS_PENDING + if (result != X_STATUS_PENDING) { + return result; + } + + // 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 %s", patch_path.c_str()); + + auto patch_module = object_ref(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: %d", result); + } + } else { + XELOGE("Failed to load XEX patch, code: %d", result); + } + + if (result) { + return X_STATUS_UNSUCCESSFUL; + } + } + + return LoadXexContinue(); } X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) { @@ -130,29 +159,13 @@ X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) { return X_STATUS_UNSUCCESSFUL; } - // Copy the xex2 header into guest memory. - auto header = this->xex_module()->xex_header(); - auto security_header = this->xex_module()->xex_security_info(); - guest_xex_header_ = memory()->SystemHeapAlloc(header->header_size); + // Only XEX headers + image are loaded right now + // Caller will have to call LoadXexContinue after they've loaded in a patch + // (or after patch isn't found anywhere) + // or if this is an XEXP being loaded return success since there's nothing + // else to load + return this->xex_module()->is_patch() ? X_STATUS_SUCCESS : X_STATUS_PENDING; - uint8_t* xex_header_ptr = memory()->TranslateVirtual(guest_xex_header_); - std::memcpy(xex_header_ptr, header, header->header_size); - - // Cache some commonly used headers... - this->xex_module()->GetOptHeader(XEX_HEADER_ENTRY_POINT, &entry_point_); - this->xex_module()->GetOptHeader(XEX_HEADER_DEFAULT_STACK_SIZE, - &stack_size_); - is_dll_module_ = !!(header->module_flags & XEX_MODULE_DLL_MODULE); - - // Setup the loader data entry - auto ldr_data = - memory()->TranslateVirtual(hmodule_ptr_); - - ldr_data->dll_base = 0; // GetProcAddress will read this. - ldr_data->xex_header_base = guest_xex_header_; - ldr_data->full_image_size = security_header->image_size; - ldr_data->image_base = this->xex_module()->base_address(); - ldr_data->entry_point = entry_point_; } else if (module_format_ == kModuleFormatElf) { auto elf_module = std::make_unique(processor, kernel_state()); @@ -175,6 +188,52 @@ X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) { return X_STATUS_SUCCESS; } +X_STATUS UserModule::LoadXexContinue() { + // LoadXexContinue: finishes loading XEX after a patch has been applied (or + // patch wasn't found) + + if (!this->xex_module()) { + return X_STATUS_UNSUCCESSFUL; + } + + // If guest_xex_header is set we must have already loaded the XEX + if (guest_xex_header_) { + return X_STATUS_SUCCESS; + } + + // Finish XexModule load (PE sections/imports/symbols...) + if (!xex_module()->LoadContinue()) { + return X_STATUS_UNSUCCESSFUL; + } + + // Copy the xex2 header into guest memory. + auto header = this->xex_module()->xex_header(); + auto security_header = this->xex_module()->xex_security_info(); + guest_xex_header_ = memory()->SystemHeapAlloc(header->header_size); + + uint8_t* xex_header_ptr = memory()->TranslateVirtual(guest_xex_header_); + std::memcpy(xex_header_ptr, header, header->header_size); + + // Cache some commonly used headers... + this->xex_module()->GetOptHeader(XEX_HEADER_ENTRY_POINT, &entry_point_); + this->xex_module()->GetOptHeader(XEX_HEADER_DEFAULT_STACK_SIZE, &stack_size_); + is_dll_module_ = !!(header->module_flags & XEX_MODULE_DLL_MODULE); + + // Setup the loader data entry + auto ldr_data = + memory()->TranslateVirtual(hmodule_ptr_); + + ldr_data->dll_base = 0; // GetProcAddress will read this. + ldr_data->xex_header_base = guest_xex_header_; + ldr_data->full_image_size = security_header->image_size; + ldr_data->image_base = this->xex_module()->base_address(); + ldr_data->entry_point = entry_point_; + + OnLoad(); + + return X_STATUS_SUCCESS; +} + X_STATUS UserModule::Unload() { if (module_format_ == kModuleFormatXex && (!processor_module_ || !xex_module()->loaded())) { diff --git a/src/xenia/kernel/user_module.h b/src/xenia/kernel/user_module.h index bd03853da..bb00255d6 100644 --- a/src/xenia/kernel/user_module.h +++ b/src/xenia/kernel/user_module.h @@ -92,6 +92,8 @@ class UserModule : public XModule { ByteStream* stream, std::string path); private: + X_STATUS LoadXexContinue(); + uint32_t guest_xex_header_ = 0; ModuleFormat module_format_ = kModuleFormatUndefined; diff --git a/src/xenia/kernel/util/xex2_info.h b/src/xenia/kernel/util/xex2_info.h index 02163fb34..4a7d85d2f 100644 --- a/src/xenia/kernel/util/xex2_info.h +++ b/src/xenia/kernel/util/xex2_info.h @@ -436,12 +436,20 @@ struct xex2_opt_resource_info { xex2_resource resources[1]; // 0x4 }; +struct xex2_delta_patch { + xe::be old_addr; + xe::be new_addr; + xe::be uncompressed_len; + xe::be compressed_len; + char patch_data[1]; +}; + struct xex2_opt_delta_patch_descriptor { xe::be size; // 0x0 xex2_version target_version; // 0x4 xex2_version source_version; // 0x8 - char digest_source[0x14]; // 0xC - char image_key_source[0x10]; // 0x20 + uint8_t digest_source[0x14]; // 0xC + uint8_t image_key_source[0x10]; // 0x20 xe::be size_of_target_headers; // 0x30 xe::be delta_headers_source_offset; // 0x34 xe::be delta_headers_source_size; // 0x38 @@ -449,9 +457,8 @@ struct xex2_opt_delta_patch_descriptor { xe::be delta_image_source_offset; // 0x40 xe::be delta_image_source_size; // 0x44 xe::be delta_image_target_offset; // 0x48 - char delta_header_patch_data[1]; + xex2_delta_patch info; // 0x4C }; -// static_assert_size(xex2_opt_delta_patch_descriptor, 0x4D); struct xex2_opt_execution_info { xe::be media_id; // 0x0