/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2013 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/kernel/util/xex2.h" #include #include #include #include #include "third_party/crypto/rijndael-alg-fst.c" #include "third_party/mspack/lzx.h" #include "third_party/mspack/lzxd.c" #include "third_party/mspack/mspack.h" #include "third_party/pe/pe_image.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/memory.h" #include "xenia/base/platform.h" namespace xe {} // namespace xe DEFINE_bool(xex_dev_key, false, "Use the devkit key."); typedef struct xe_xex2 { xe::Memory* memory; xe_xex2_header_t header; std::vector* sections; struct { size_t count; xe_xex2_import_info_t* infos; } library_imports[16]; } xe_xex2_t; int xe_xex2_read_header(const uint8_t* addr, const size_t length, xe_xex2_header_t* header); int xe_xex2_decrypt_key(xe_xex2_header_t* header); int xe_xex2_read_image(xe_xex2_ref xex, const uint8_t* xex_addr, const uint32_t xex_length, xe::Memory* memory); int xe_xex2_load_pe(xe_xex2_ref xex); int xe_xex2_find_import_infos(xe_xex2_ref xex, const xe_xex2_import_library_t* library); xe_xex2_ref xe_xex2_load(xe::Memory* memory, const void* addr, const size_t length, xe_xex2_options_t options) { xe_xex2_ref xex = (xe_xex2_ref)calloc(1, sizeof(xe_xex2)); xex->memory = memory; xex->sections = new std::vector(); if (xe_xex2_read_header((const uint8_t*)addr, length, &xex->header)) { xe_xex2_dealloc(xex); return nullptr; } if (xe_xex2_decrypt_key(&xex->header)) { xe_xex2_dealloc(xex); return nullptr; } if (xe_xex2_read_image(xex, (const uint8_t*)addr, uint32_t(length), memory)) { xe_xex2_dealloc(xex); return nullptr; } if (xe_xex2_load_pe(xex)) { xe_xex2_dealloc(xex); return nullptr; } for (size_t n = 0; n < xex->header.import_library_count; n++) { auto library = &xex->header.import_libraries[n]; if (xe_xex2_find_import_infos(xex, library)) { xe_xex2_dealloc(xex); return nullptr; } } return xex; } void xe_xex2_dealloc(xe_xex2_ref xex) { if (!xex) { return; } for (std::vector::iterator it = xex->sections->begin(); it != xex->sections->end(); ++it) { delete *it; } xe_xex2_header_t* header = &xex->header; free(header->sections); free(header->resource_infos); if (header->file_format_info.compression_type == XEX_COMPRESSION_BASIC) { free(header->file_format_info.compression_info.basic.blocks); } for (size_t n = 0; n < header->import_library_count; n++) { xe_xex2_import_library_t* library = &header->import_libraries[n]; free(library->records); } xex->memory = NULL; } const xe_xex2_header_t* xe_xex2_get_header(xe_xex2_ref xex) { return &xex->header; } int xe_xex2_read_header(const uint8_t* addr, const size_t length, xe_xex2_header_t* header) { const uint8_t* p = addr; const uint8_t* pc; const uint8_t* ps; xe_xex2_loader_info_t* ldr; header->xex2 = xe::load_and_swap(p + 0x00); if (header->xex2 != 'XEX2') { return 1; } header->module_flags = (xe_xex2_module_flags)xe::load_and_swap(p + 0x04); header->exe_offset = xe::load_and_swap(p + 0x08); header->unknown0 = xe::load_and_swap(p + 0x0C); header->certificate_offset = xe::load_and_swap(p + 0x10); header->header_count = xe::load_and_swap(p + 0x14); for (size_t n = 0; n < header->header_count; n++) { const uint8_t* ph = p + 0x18 + (n * 8); const uint32_t key = xe::load_and_swap(ph + 0x00); const uint32_t data_offset = xe::load_and_swap(ph + 0x04); xe_xex2_opt_header_t* opt_header = &header->headers[n]; opt_header->key = key; switch (key & 0xFF) { case 0x01: // dataOffset = data opt_header->length = 0; opt_header->value = data_offset; break; case 0xFF: // dataOffset = offset (first dword in data is size) opt_header->length = xe::load_and_swap(p + data_offset); opt_header->offset = data_offset; break; default: // dataOffset = size in dwords opt_header->length = (key & 0xFF) * 4; opt_header->offset = data_offset; break; } const uint8_t* pp = p + opt_header->offset; switch (opt_header->key) { case XEX_HEADER_SYSTEM_FLAGS: header->system_flags = (xe_xex2_system_flags)data_offset; break; case XEX_HEADER_RESOURCE_INFO: { header->resource_info_count = (opt_header->length - 4) / 16; header->resource_infos = (xe_xex2_resource_info_t*)calloc( header->resource_info_count, sizeof(xe_xex2_resource_info_t)); const uint8_t* phi = pp + 0x04; for (size_t m = 0; m < header->resource_info_count; m++) { auto& res = header->resource_infos[m]; memcpy(res.name, phi + 0x00, 8); res.address = xe::load_and_swap(phi + 0x08); res.size = xe::load_and_swap(phi + 0x0C); phi += 16; } } break; case XEX_HEADER_EXECUTION_INFO: { xe_xex2_execution_info_t* ex = &header->execution_info; ex->media_id = xe::load_and_swap(pp + 0x00); ex->version.value = xe::load_and_swap(pp + 0x04); ex->base_version.value = xe::load_and_swap(pp + 0x08); ex->title_id = xe::load_and_swap(pp + 0x0C); ex->platform = xe::load_and_swap(pp + 0x10); ex->executable_table = xe::load_and_swap(pp + 0x11); ex->disc_number = xe::load_and_swap(pp + 0x12); ex->disc_count = xe::load_and_swap(pp + 0x13); ex->savegame_id = xe::load_and_swap(pp + 0x14); } break; case XEX_HEADER_GAME_RATINGS: { xe_xex2_game_ratings_t* ratings = &header->game_ratings; ratings->esrb = (xe_xex2_rating_esrb_value)xe::load_and_swap(pp + 0x00); ratings->pegi = (xe_xex2_rating_pegi_value)xe::load_and_swap(pp + 0x01); ratings->pegifi = (xe_xex2_rating_pegi_fi_value)xe::load_and_swap(pp + 0x02); ratings->pegipt = (xe_xex2_rating_pegi_pt_value)xe::load_and_swap(pp + 0x03); ratings->bbfc = (xe_xex2_rating_bbfc_value)xe::load_and_swap(pp + 0x04); ratings->cero = (xe_xex2_rating_cero_value)xe::load_and_swap(pp + 0x05); ratings->usk = (xe_xex2_rating_usk_value)xe::load_and_swap(pp + 0x06); ratings->oflcau = (xe_xex2_rating_oflc_au_value)xe::load_and_swap(pp + 0x07); ratings->oflcnz = (xe_xex2_rating_oflc_nz_value)xe::load_and_swap(pp + 0x08); ratings->kmrb = (xe_xex2_rating_kmrb_value)xe::load_and_swap(pp + 0x09); ratings->brazil = (xe_xex2_rating_brazil_value)xe::load_and_swap(pp + 0x0A); ratings->fpb = (xe_xex2_rating_fpb_value)xe::load_and_swap(pp + 0x0B); } break; case XEX_HEADER_TLS_INFO: { xe_xex2_tls_info_t* tls = &header->tls_info; tls->slot_count = xe::load_and_swap(pp + 0x00); tls->raw_data_address = xe::load_and_swap(pp + 0x04); tls->data_size = xe::load_and_swap(pp + 0x08); tls->raw_data_size = xe::load_and_swap(pp + 0x0C); } break; case XEX_HEADER_IMAGE_BASE_ADDRESS: header->exe_address = opt_header->value; break; case XEX_HEADER_ENTRY_POINT: header->exe_entry_point = opt_header->value; break; case XEX_HEADER_DEFAULT_STACK_SIZE: header->exe_stack_size = opt_header->value; break; case XEX_HEADER_DEFAULT_HEAP_SIZE: header->exe_heap_size = opt_header->value; break; case XEX_HEADER_EXPORTS_BY_NAME: { // IMAGE_DATA_DIRECTORY (w/ offset from PE file base) header->pe_export_table_offset = xe::load_and_swap(pp); // size = xe::load_and_swap(pp + 0x04); } break; case XEX_HEADER_IMPORT_LIBRARIES: { auto import_libraries = reinterpret_cast(pp); const uint32_t max_count = (uint32_t)xe::countof(header->import_libraries); uint32_t count = import_libraries->library_count; assert_true(count <= max_count); if (count > max_count) { XELOGW("ignoring %zu extra entries in XEX_HEADER_IMPORT_LIBRARIES", (max_count - import_libraries->library_count)); count = max_count; } header->import_library_count = count; uint32_t string_table_size = import_libraries->string_table_size; const char* string_table[32]; // Pretend 32 is max_count std::memset(string_table, 0, sizeof(string_table)); // Parse the string table for (size_t i = 0, j = 0; i < string_table_size; j++) { const char* str = import_libraries->string_table + i; string_table[j] = str; i += std::strlen(str) + 1; // Padding if ((i % 4) != 0) { i += 4 - (i % 4); } } pp += 12 + import_libraries->string_table_size; for (size_t m = 0; m < count; m++) { xe_xex2_import_library_t* library = &header->import_libraries[m]; auto src_library = (xe::xex2_import_library*)pp; memcpy(library->digest, pp + 0x04, 20); library->import_id = src_library->id; library->version.value = src_library->version.value; library->min_version.value = src_library->version_min.value; std::strncpy(library->name, string_table[src_library->name_index & 0xFF], xe::countof(library->name)); library->record_count = src_library->count; library->records = (uint32_t*)calloc(library->record_count, sizeof(uint32_t)); if (!library->records) { return 1; } for (size_t i = 0; i < library->record_count; i++) { library->records[i] = src_library->import_table[i]; } pp += src_library->size; } } break; case XEX_HEADER_STATIC_LIBRARIES: { const size_t max_count = xe::countof(header->static_libraries); size_t count = (opt_header->length - 4) / 16; assert_true(count <= max_count); if (count > max_count) { XELOGW("ignoring %zu extra entries in XEX_HEADER_STATIC_LIBRARIES", (max_count - count)); count = max_count; } header->static_library_count = count; pp += 4; for (size_t m = 0; m < count; m++) { xe_xex2_static_library_t* library = &header->static_libraries[m]; memcpy(library->name, pp + 0x00, 8); library->name[8] = 0; library->major = xe::load_and_swap(pp + 0x08); library->minor = xe::load_and_swap(pp + 0x0A); library->build = xe::load_and_swap(pp + 0x0C); uint16_t qfeapproval = xe::load_and_swap(pp + 0x0E); library->approval = (xe_xex2_approval_type)(qfeapproval & 0x8000); library->qfe = qfeapproval & ~0x8000; pp += 16; } } break; case XEX_HEADER_FILE_FORMAT_INFO: { xe_xex2_file_format_info_t* fmt = &header->file_format_info; fmt->encryption_type = (xe_xex2_encryption_type)xe::load_and_swap(pp + 0x04); fmt->compression_type = (xe_xex2_compression_type)xe::load_and_swap(pp + 0x06); switch (fmt->compression_type) { case XEX_COMPRESSION_NONE: // TODO: XEX_COMPRESSION_NONE assert_always(); break; case XEX_COMPRESSION_BASIC: { xe_xex2_file_basic_compression_info_t* comp_info = &fmt->compression_info.basic; uint32_t info_size = xe::load_and_swap(pp + 0x00); comp_info->block_count = (info_size - 8) / 8; comp_info->blocks = (xe_xex2_file_basic_compression_block_t*)calloc( comp_info->block_count, sizeof(xe_xex2_file_basic_compression_block_t)); if (!comp_info->blocks) { return 1; } for (size_t m = 0; m < comp_info->block_count; m++) { xe_xex2_file_basic_compression_block_t* block = &comp_info->blocks[m]; block->data_size = xe::load_and_swap(pp + 0x08 + (m * 8)); block->zero_size = xe::load_and_swap(pp + 0x0C + (m * 8)); } } break; case XEX_COMPRESSION_NORMAL: { xe_xex2_file_normal_compression_info_t* comp_info = &fmt->compression_info.normal; uint32_t window_size = xe::load_and_swap(pp + 0x08); uint32_t window_bits = 0; for (size_t m = 0; m < 32; m++, window_bits++) { window_size >>= 1; if (window_size == 0x00000000) { break; } } comp_info->window_size = xe::load_and_swap(pp + 0x08); comp_info->window_bits = window_bits; comp_info->block_size = xe::load_and_swap(pp + 0x0C); memcpy(comp_info->block_hash, pp + 0x10, 20); } break; case XEX_COMPRESSION_DELTA: // TODO: XEX_COMPRESSION_DELTA assert_always(); break; } } break; } } // Loader info. pc = p + header->certificate_offset; ldr = &header->loader_info; ldr->header_size = xe::load_and_swap(pc + 0x000); ldr->image_size = xe::load_and_swap(pc + 0x004); memcpy(ldr->rsa_signature, pc + 0x008, 256); ldr->unklength = xe::load_and_swap(pc + 0x108); ldr->image_flags = (xe_xex2_image_flags)xe::load_and_swap(pc + 0x10C); ldr->load_address = xe::load_and_swap(pc + 0x110); memcpy(ldr->section_digest, pc + 0x114, 20); ldr->import_table_count = xe::load_and_swap(pc + 0x128); memcpy(ldr->import_table_digest, pc + 0x12C, 20); memcpy(ldr->media_id, pc + 0x140, 16); memcpy(ldr->file_key, pc + 0x150, 16); ldr->export_table = xe::load_and_swap(pc + 0x160); memcpy(ldr->header_digest, pc + 0x164, 20); ldr->game_regions = (xe_xex2_region_flags)xe::load_and_swap(pc + 0x178); ldr->media_flags = (xe_xex2_media_flags)xe::load_and_swap(pc + 0x17C); // Section info follows loader info. ps = p + header->certificate_offset + 0x180; header->section_count = xe::load_and_swap(ps + 0x000); ps += 4; header->sections = (xe_xex2_section_t*)calloc(header->section_count, sizeof(xe_xex2_section_t)); if (!header->sections) { return 1; } for (size_t n = 0; n < header->section_count; n++) { xe_xex2_section_t* section = &header->sections[n]; section->page_size = header->exe_address <= 0x90000000 ? 64 * 1024 : 4 * 1024; section->info.value = xe::load_and_swap(ps); ps += 4; memcpy(section->digest, ps, sizeof(section->digest)); ps += sizeof(section->digest); } return 0; } int xe_xex2_decrypt_key(xe_xex2_header_t* header) { 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}; // Guess key based on file info. // TODO: better way to finding out which key to use? const uint8_t* xexkey; if (header->execution_info.title_id && !FLAGS_xex_dev_key) { xexkey = xe_xex2_retail_key; } else { xexkey = xe_xex2_devkit_key; } // Decrypt the header key. uint32_t rk[4 * (MAXNR + 1)]; int32_t Nr = rijndaelKeySetupDec(rk, xexkey, 128); rijndaelDecrypt(rk, Nr, header->loader_info.file_key, header->session_key); return 0; } typedef struct mspack_memory_file_t { struct mspack_system sys; void* buffer; off_t buffer_size; off_t offset; } mspack_memory_file; mspack_memory_file* mspack_memory_open(struct mspack_system* sys, void* buffer, const size_t buffer_size) { assert_true(buffer_size < INT_MAX); if (buffer_size >= INT_MAX) { return NULL; } mspack_memory_file* memfile = (mspack_memory_file*)calloc(1, sizeof(mspack_memory_file)); if (!memfile) { return NULL; } memfile->buffer = buffer; memfile->buffer_size = (off_t)buffer_size; memfile->offset = 0; return memfile; } void mspack_memory_close(mspack_memory_file* file) { mspack_memory_file* memfile = (mspack_memory_file*)file; free(memfile); } int mspack_memory_read(struct mspack_file* file, void* buffer, int chars) { mspack_memory_file* memfile = (mspack_memory_file*)file; const off_t remaining = memfile->buffer_size - memfile->offset; const off_t total = std::min(static_cast(chars), remaining); memcpy(buffer, (uint8_t*)memfile->buffer + memfile->offset, total); memfile->offset += total; return (int)total; } int mspack_memory_write(struct mspack_file* file, void* buffer, int chars) { mspack_memory_file* memfile = (mspack_memory_file*)file; const off_t remaining = memfile->buffer_size - memfile->offset; const off_t total = std::min(static_cast(chars), remaining); memcpy((uint8_t*)memfile->buffer + memfile->offset, buffer, total); memfile->offset += total; return (int)total; } void* mspack_memory_alloc(struct mspack_system* sys, size_t chars) { return calloc(chars, 1); } void mspack_memory_free(void* ptr) { free(ptr); } void mspack_memory_copy(void* src, void* dest, size_t chars) { memcpy(dest, src, chars); } struct mspack_system* mspack_memory_sys_create() { struct mspack_system* sys = (struct mspack_system*)calloc(1, sizeof(struct mspack_system)); if (!sys) { return NULL; } sys->read = mspack_memory_read; sys->write = mspack_memory_write; sys->alloc = mspack_memory_alloc; sys->free = mspack_memory_free; sys->copy = mspack_memory_copy; return sys; } void mspack_memory_sys_destroy(struct mspack_system* sys) { free(sys); } void xe_xex2_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); const uint8_t* ct = input_buffer; uint8_t* pt = output_buffer; for (size_t n = 0; n < input_size; n += 16, ct += 16, pt += 16) { // Decrypt 16 uint8_ts from input -> output. rijndaelDecrypt(rk, Nr, ct, pt); for (size_t i = 0; i < 16; i++) { // XOR with previous. pt[i] ^= ivec[i]; // Set previous. ivec[i] = ct[i]; } } } int xe_xex2_read_image_uncompressed(const xe_xex2_header_t* header, const uint8_t* xex_addr, const uint32_t xex_length, xe::Memory* memory) { // Allocate in-place the XEX memory. const uint32_t exe_length = xex_length - header->exe_offset; uint32_t uncompressed_size = exe_length; bool alloc_result = memory->LookupHeap(header->exe_address) ->AllocFixed( header->exe_address, uncompressed_size, 4096, xe::kMemoryAllocationReserve | xe::kMemoryAllocationCommit, xe::kMemoryProtectRead | xe::kMemoryProtectWrite); if (!alloc_result) { XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", header->exe_address, uncompressed_size); return 2; } uint8_t* buffer = memory->TranslateVirtual(header->exe_address); std::memset(buffer, 0, uncompressed_size); const uint8_t* p = (const uint8_t*)xex_addr + header->exe_offset; switch (header->file_format_info.encryption_type) { case XEX_ENCRYPTION_NONE: if (exe_length > uncompressed_size) { return 1; } memcpy(buffer, p, exe_length); return 0; case XEX_ENCRYPTION_NORMAL: xe_xex2_decrypt_buffer(header->session_key, p, exe_length, buffer, uncompressed_size); return 0; default: assert_always(); return 1; } return 0; } int xe_xex2_read_image_basic_compressed(const xe_xex2_header_t* header, const uint8_t* xex_addr, const uint32_t xex_length, xe::Memory* memory) { const uint32_t exe_length = xex_length - header->exe_offset; const uint8_t* source_buffer = (const uint8_t*)xex_addr + header->exe_offset; const uint8_t* p = source_buffer; // Calculate uncompressed length. uint32_t uncompressed_size = 0; const xe_xex2_file_basic_compression_info_t* comp_info = &header->file_format_info.compression_info.basic; for (uint32_t n = 0; n < comp_info->block_count; n++) { const uint32_t data_size = comp_info->blocks[n].data_size; const uint32_t zero_size = comp_info->blocks[n].zero_size; uncompressed_size += data_size + zero_size; } // Calculate the total size of the XEX image from its headers. uint32_t total_size = 0; for (uint32_t i = 0; i < header->section_count; i++) { xe_xex2_section_t& section = header->sections[i]; total_size += section.info.page_count * section.page_size; } // Allocate in-place the XEX memory. bool alloc_result = memory->LookupHeap(header->exe_address) ->AllocFixed( header->exe_address, total_size, 4096, xe::kMemoryAllocationReserve | xe::kMemoryAllocationCommit, xe::kMemoryProtectRead | xe::kMemoryProtectWrite); if (!alloc_result) { XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", header->exe_address, uncompressed_size); return 1; } uint8_t* buffer = memory->TranslateVirtual(header->exe_address); std::memset(buffer, 0, total_size); // Quickly zero the contents. uint8_t* d = buffer; uint32_t rk[4 * (MAXNR + 1)]; uint8_t ivec[16] = {0}; int32_t Nr = rijndaelKeySetupDec(rk, header->session_key, 128); for (size_t n = 0; n < comp_info->block_count; n++) { const uint32_t data_size = comp_info->blocks[n].data_size; const uint32_t zero_size = comp_info->blocks[n].zero_size; switch (header->file_format_info.encryption_type) { case XEX_ENCRYPTION_NONE: if (data_size > uncompressed_size - (d - buffer)) { // Overflow. return 1; } memcpy(d, p, data_size); break; case XEX_ENCRYPTION_NORMAL: { const uint8_t* ct = p; uint8_t* pt = d; for (size_t m = 0; m < data_size; m += 16, ct += 16, pt += 16) { // Decrypt 16 uint8_ts from input -> output. rijndaelDecrypt(rk, Nr, ct, pt); for (size_t i = 0; i < 16; i++) { // XOR with previous. pt[i] ^= ivec[i]; // Set previous. ivec[i] = ct[i]; } } } break; default: assert_always(); return 1; } p += data_size; d += data_size + zero_size; } return 0; } int xe_xex2_read_image_compressed(const xe_xex2_header_t* header, const uint8_t* xex_addr, const uint32_t xex_length, xe::Memory* memory) { const uint32_t exe_length = xex_length - header->exe_offset; const uint8_t* exe_buffer = (const uint8_t*)xex_addr + header->exe_offset; // src -> dest: // - decrypt (if encrypted) // - de-block: // 4b total size of next block in uint8_ts // 20b hash of entire next block (including size/hash) // Nb block uint8_ts // - decompress block contents int result_code = 1; 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; // Decrypt (if needed). bool free_input = false; const uint8_t* input_buffer = exe_buffer; const size_t input_size = exe_length; switch (header->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, input_size); xe_xex2_decrypt_buffer(header->session_key, exe_buffer, exe_length, (uint8_t*)input_buffer, input_size); break; default: assert_always(); return false; } compress_buffer = (uint8_t*)calloc(1, exe_length); p = input_buffer; d = compress_buffer; // De-block. deblock_buffer = (uint8_t*)calloc(1, input_size); block_size = header->file_format_info.compression_info.normal.block_size; while (block_size) { const uint8_t* pnext = p + block_size; const size_t next_size = xe::load_and_swap(p); p += 4; p += 20; // skip 20b hash while (true) { const size_t chunk_size = (p[0] << 8) | p[1]; p += 2; if (!chunk_size) { break; } memcpy(d, p, chunk_size); p += chunk_size; d += chunk_size; uncompressed_size += 0x8000; } p = pnext; block_size = next_size; } // Allocate in-place the XEX memory. bool alloc_result = memory->LookupHeap(header->exe_address) ->AllocFixed( header->exe_address, uncompressed_size, 4096, xe::kMemoryAllocationReserve | xe::kMemoryAllocationCommit, xe::kMemoryProtectRead | xe::kMemoryProtectWrite); if (!alloc_result) { XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", header->exe_address, uncompressed_size); result_code = 2; // Not doing any cleanup here; // TODO(benvanik): rewrite this entire file using RAII. assert_always(); return 1; } uint8_t* buffer = memory->TranslateVirtual(header->exe_address); std::memset(buffer, 0, uncompressed_size); // Setup decompressor and decompress. 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, header->file_format_info.compression_info.normal.window_bits, 0, 32768, (off_t)header->loader_info.image_size); result_code = lzxd_decompress(lzxd, (off_t)header->loader_info.image_size); 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; } free(compress_buffer); free(deblock_buffer); if (free_input) { free((void*)input_buffer); } return result_code; } int xe_xex2_read_image(xe_xex2_ref xex, const uint8_t* xex_addr, const uint32_t xex_length, xe::Memory* memory) { const xe_xex2_header_t* header = &xex->header; switch (header->file_format_info.compression_type) { case XEX_COMPRESSION_NONE: return xe_xex2_read_image_uncompressed(header, xex_addr, xex_length, memory); case XEX_COMPRESSION_BASIC: return xe_xex2_read_image_basic_compressed(header, xex_addr, xex_length, memory); case XEX_COMPRESSION_NORMAL: return xe_xex2_read_image_compressed(header, xex_addr, xex_length, memory); default: assert_always(); return 1; } } int xe_xex2_load_pe(xe_xex2_ref xex) { const xe_xex2_header_t* header = &xex->header; const uint8_t* p = xex->memory->TranslateVirtual(header->exe_address); // Verify DOS signature (MZ). const IMAGE_DOS_HEADER* doshdr = (const IMAGE_DOS_HEADER*)p; if (doshdr->e_magic != IMAGE_DOS_SIGNATURE) { XELOGE("PE signature mismatch; likely bad decryption/decompression"); return 1; } // Move to the NT header offset from the DOS header. p += doshdr->e_lfanew; // Verify NT signature (PE\0\0). const IMAGE_NT_HEADERS32* nthdr = (const IMAGE_NT_HEADERS32*)(p); if (nthdr->Signature != IMAGE_NT_SIGNATURE) { return 1; } // Verify matches an Xbox PE. const IMAGE_FILE_HEADER* filehdr = &nthdr->FileHeader; if ((filehdr->Machine != IMAGE_FILE_MACHINE_POWERPCBE) || !(filehdr->Characteristics & IMAGE_FILE_32BIT_MACHINE)) { return 1; } // Verify the expected size. if (filehdr->SizeOfOptionalHeader != IMAGE_SIZEOF_NT_OPTIONAL_HEADER) { return 1; } // Verify optional header is 32bit. const IMAGE_OPTIONAL_HEADER32* opthdr = &nthdr->OptionalHeader; if (opthdr->Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC) { return 1; } // Verify subsystem. if (opthdr->Subsystem != IMAGE_SUBSYSTEM_XBOX) { return 1; } // Linker version - likely 8+ // Could be useful for recognizing certain patterns // opthdr->MajorLinkerVersion; opthdr->MinorLinkerVersion; // Data directories of interest: // EXPORT IMAGE_EXPORT_DIRECTORY // IMPORT IMAGE_IMPORT_DESCRIPTOR[] // EXCEPTION IMAGE_CE_RUNTIME_FUNCTION_ENTRY[] // BASERELOC // DEBUG IMAGE_DEBUG_DIRECTORY[] // ARCHITECTURE /IMAGE_ARCHITECTURE_HEADER/ ----- import thunks! // TLS IMAGE_TLS_DIRECTORY // IAT Import Address Table ptr // opthdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_X].VirtualAddress / .Size // The macros in pe_image.h don't work with clang, for some reason. // offsetof seems to be unable to find OptionalHeader. #define offsetof1(type, member) ((std::size_t) & (((type*)0)->member)) #define IMAGE_FIRST_SECTION1(ntheader) \ ((PIMAGE_SECTION_HEADER)( \ (uint8_t*)ntheader + offsetof1(IMAGE_NT_HEADERS, OptionalHeader) + \ ((PIMAGE_NT_HEADERS)(ntheader))->FileHeader.SizeOfOptionalHeader)) // Quick scan to determine bounds of sections. size_t upper_address = 0; const IMAGE_SECTION_HEADER* sechdr = IMAGE_FIRST_SECTION1(nthdr); for (size_t n = 0; n < filehdr->NumberOfSections; n++, sechdr++) { const size_t physical_address = opthdr->ImageBase + sechdr->VirtualAddress; upper_address = std::max(upper_address, physical_address + sechdr->Misc.VirtualSize); } // Setup/load sections. sechdr = IMAGE_FIRST_SECTION1(nthdr); for (size_t n = 0; n < filehdr->NumberOfSections; n++, sechdr++) { PESection* section = (PESection*)calloc(1, sizeof(PESection)); memcpy(section->name, sechdr->Name, sizeof(sechdr->Name)); section->name[8] = 0; section->raw_address = sechdr->PointerToRawData; section->raw_size = sechdr->SizeOfRawData; section->address = header->exe_address + sechdr->VirtualAddress; section->size = sechdr->Misc.VirtualSize; section->flags = sechdr->Characteristics; xex->sections->push_back(section); } // DumpTLSDirectory(pImageBase, pNTHeader, (PIMAGE_TLS_DIRECTORY32)0); // DumpExportsSection(pImageBase, pNTHeader); return 0; } const PESection* xe_xex2_get_pe_section(xe_xex2_ref xex, const char* name) { for (std::vector::iterator it = xex->sections->begin(); it != xex->sections->end(); ++it) { if (!strcmp((*it)->name, name)) { return *it; } } return NULL; } int xe_xex2_find_import_infos(xe_xex2_ref xex, const xe_xex2_import_library_t* library) { auto header = xe_xex2_get_header(xex); // Find library index for verification. size_t library_index = ~0ull; for (size_t n = 0; n < header->import_library_count; n++) { if (&header->import_libraries[n] == library) { library_index = n; break; } } assert_true(library_index != (size_t)-1); // Records: // The number of records does not correspond to the number of imports! // Each record points at either a location in text or data - dereferencing the // pointer will yield a value that & 0xFFFF = the import ordinal, // >> 16 & 0xFF = import library index, and >> 24 & 0xFF = 0 if a variable // (just get address) or 1 if a thunk (needs rewrite). // Calculate real count. size_t info_count = 0; for (size_t n = 0; n < library->record_count; n++) { const uint32_t record = library->records[n]; const uint32_t value = xe::load_and_swap(xex->memory->TranslateVirtual(record)); if (value & 0xFF000000) { // Thunk for previous record - ignore. } else { // Variable/thunk. info_count++; } } // Allocate storage. xe_xex2_import_info_t* infos = (xe_xex2_import_info_t*)calloc(info_count, sizeof(xe_xex2_import_info_t)); assert_not_null(infos); assert_not_zero(info_count); // Construct infos. for (size_t n = 0, i = 0; n < library->record_count; n++) { const uint32_t record = library->records[n]; const uint32_t value = xe::load_and_swap(xex->memory->TranslateVirtual(record)); const uint32_t type = (value & 0xFF000000) >> 24; // Verify library index matches given library. // assert_true(library_index == ((value >> 16) & 0xFF)); switch (type) { case 0x00: { xe_xex2_import_info_t* info = &infos[i++]; info->ordinal = value & 0xFFFF; info->value_address = record; } break; case 0x01: { // Thunk for previous record. assert_true(i > 0); xe_xex2_import_info_t* info = &infos[i - 1]; assert_true(info->ordinal == (value & 0xFFFF)); info->thunk_address = record; } break; default: assert_always(); break; } } xex->library_imports[library_index].count = info_count; xex->library_imports[library_index].infos = infos; return 0; } int xe_xex2_get_import_infos(xe_xex2_ref xex, const xe_xex2_import_library_t* library, xe_xex2_import_info_t** out_import_infos, size_t* out_import_info_count) { auto header = xe_xex2_get_header(xex); // Find library index for verification. size_t library_index = ~0ull; for (size_t n = 0; n < header->import_library_count; n++) { if (&header->import_libraries[n] == library) { library_index = n; break; } } if (library_index == (size_t)-1) { return 1; } *out_import_info_count = xex->library_imports[library_index].count; *out_import_infos = xex->library_imports[library_index].infos; return 0; } uint32_t xe_xex2_lookup_export(xe_xex2_ref xex, const char* name) { auto header = xe_xex2_get_header(xex); // No exports. if (!header->pe_export_table_offset) { XELOGE("xe_xex2_lookup_export(%s) failed: no PE export table", name); return 0; } auto e = reinterpret_cast( xex->memory->TranslateVirtual(header->exe_address + header->pe_export_table_offset)); // e->AddressOfX RVAs are relative to the IMAGE_EXPORT_DIRECTORY! uint32_t* function_table = (uint32_t*)((uint64_t)e + e->AddressOfFunctions); // Names relative to directory. uint32_t* name_table = (uint32_t*)((uint64_t)e + e->AddressOfNames); // Table of ordinals (by name). uint16_t* ordinal_table = (uint16_t*)((uint64_t)e + e->AddressOfNameOrdinals); // Module name (sometimes). // const char* mod_name = (const char*)((uint64_t)e + e->Name); for (uint32_t i = 0; i < e->NumberOfNames; i++) { const char* fn_name = (const char*)((uint64_t)e + name_table[i]); uint16_t ordinal = ordinal_table[i]; uint32_t addr = header->exe_address + function_table[ordinal]; if (!strcmp(name, fn_name)) { // We have a match! return addr; } } // No match. return 0; } uint32_t xe_xex2_lookup_export(xe_xex2_ref xex, uint16_t ordinal) { auto header = xe_xex2_get_header(xex); // XEX-style export table. if (header->loader_info.export_table) { auto export_table = reinterpret_cast( xex->memory->TranslateVirtual(header->loader_info.export_table)); uint32_t ordinal_count = export_table->count; uint32_t ordinal_base = export_table->base; if (ordinal > ordinal_count) { XELOGE("xe_xex2_lookup_export: ordinal out of bounds"); return 0; } uint32_t i = ordinal - ordinal_base; uint32_t ordinal_offset = export_table->ordOffset[i]; ordinal_offset += export_table->imagebaseaddr << 16; return ordinal_offset; } // Check for PE-style export table. if (!header->pe_export_table_offset) { XELOGE("xe_xex2_lookup_export(%.4X) failed: no XEX or PE export table"); return 0; } auto e = reinterpret_cast( xex->memory->TranslateVirtual(header->exe_address + header->pe_export_table_offset)); // e->AddressOfX RVAs are relative to the IMAGE_EXPORT_DIRECTORY! // Functions relative to base. uint32_t* function_table = (uint32_t*)((uint64_t)e + e->AddressOfFunctions); if (ordinal < e->NumberOfFunctions) { return header->exe_address + function_table[ordinal]; } // No match. return 0; }