xenia-canary/src/xenia/kernel/util/xex2.cc

1014 lines
35 KiB
C++

/**
******************************************************************************
* 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 <algorithm>
#include <vector>
#include <gflags/gflags.h>
#include <poly/math.h>
#include <third_party/crypto/rijndael-alg-fst.h>
#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>
using namespace alloy;
DEFINE_bool(xex_dev_key, false, "Use the devkit key.");
typedef struct xe_xex2 {
xe_ref_t ref;
Memory* memory;
xe_xex2_header_t header;
std::vector<PESection*>* 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 size_t xex_length,
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(Memory* memory,
const void* addr, const size_t length,
xe_xex2_options_t options) {
xe_xex2_ref xex = (xe_xex2_ref)xe_calloc(sizeof(xe_xex2));
xe_ref_init((xe_ref)xex);
xex->memory = memory;
xex->sections = new std::vector<PESection*>();
XEEXPECTZERO(xe_xex2_read_header((const uint8_t*)addr, length, &xex->header));
XEEXPECTZERO(xe_xex2_decrypt_key(&xex->header));
XEEXPECTZERO(xe_xex2_read_image(xex, (const uint8_t*)addr, length, memory));
XEEXPECTZERO(xe_xex2_load_pe(xex));
for (size_t n = 0; n < xex->header.import_library_count; n++) {
auto library = &xex->header.import_libraries[n];
XEEXPECTZERO(xe_xex2_find_import_infos(xex, library));
}
return xex;
XECLEANUP:
xe_xex2_release(xex);
return NULL;
}
void xe_xex2_dealloc(xe_xex2_ref xex) {
for (std::vector<PESection*>::iterator it = xex->sections->begin();
it != xex->sections->end(); ++it) {
delete *it;
}
xe_xex2_header_t *header = &xex->header;
xe_free(header->sections);
xe_free(header->resource_infos);
if (header->file_format_info.compression_type == XEX_COMPRESSION_BASIC) {
xe_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];
xe_free(library->records);
}
xex->memory = NULL;
}
xe_xex2_ref xe_xex2_retain(xe_xex2_ref xex) {
xe_ref_retain((xe_ref)xex);
return xex;
}
void xe_xex2_release(xe_xex2_ref xex) {
xe_ref_release((xe_ref)xex, (xe_ref_dealloc_t)xe_xex2_dealloc);
}
const xechar_t* xe_xex2_get_name(xe_xex2_ref xex) {
// TODO(benvanik): get name.
return 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 = poly::load_and_swap<uint32_t>(p + 0x00);
if (header->xex2 != 0x58455832) {
return 1;
}
header->module_flags = (xe_xex2_module_flags)poly::load_and_swap<uint32_t>(p + 0x04);
header->exe_offset = poly::load_and_swap<uint32_t>(p + 0x08);
header->unknown0 = poly::load_and_swap<uint32_t>(p + 0x0C);
header->certificate_offset = poly::load_and_swap<uint32_t>(p + 0x10);
header->header_count = poly::load_and_swap<uint32_t>(p + 0x14);
for (size_t n = 0; n < header->header_count; n++) {
const uint8_t *ph = p + 0x18 + (n * 8);
const uint32_t key = poly::load_and_swap<uint32_t>(ph + 0x00);
const uint32_t data_offset = poly::load_and_swap<uint32_t>(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 = poly::load_and_swap<uint32_t>(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*)xe_calloc(
sizeof(xe_xex2_resource_info_t) * header->resource_info_count);
const uint8_t* ph = pp + 0x04;
for (size_t n = 0; n < header->resource_info_count; n++) {
auto& res = header->resource_infos[n];
XEEXPECTZERO(xe_copy_memory(res.name,
sizeof(res.name), ph + 0x00, 8));
res.address = poly::load_and_swap<uint32_t>(ph + 0x08);
res.size = poly::load_and_swap<uint32_t>(ph + 0x0C);
ph += 16;
}
}
break;
case XEX_HEADER_EXECUTION_INFO:
{
xe_xex2_execution_info_t *ex = &header->execution_info;
ex->media_id = poly::load_and_swap<uint32_t>(pp + 0x00);
ex->version.value = poly::load_and_swap<uint32_t>(pp + 0x04);
ex->base_version.value = poly::load_and_swap<uint32_t>(pp + 0x08);
ex->title_id = poly::load_and_swap<uint32_t>(pp + 0x0C);
ex->platform = poly::load_and_swap<uint8_t>(pp + 0x10);
ex->executable_table = poly::load_and_swap<uint8_t>(pp + 0x11);
ex->disc_number = poly::load_and_swap<uint8_t>(pp + 0x12);
ex->disc_count = poly::load_and_swap<uint8_t>(pp + 0x13);
ex->savegame_id = poly::load_and_swap<uint32_t>(pp + 0x14);
}
break;
case XEX_HEADER_GAME_RATINGS:
{
xe_xex2_game_ratings_t *ratings = &header->game_ratings;
ratings->esrb = (xe_xex2_rating_esrb_value)poly::load_and_swap<uint8_t>(pp + 0x00);
ratings->pegi = (xe_xex2_rating_pegi_value)poly::load_and_swap<uint8_t>(pp + 0x01);
ratings->pegifi = (xe_xex2_rating_pegi_fi_value)poly::load_and_swap<uint8_t>(pp + 0x02);
ratings->pegipt = (xe_xex2_rating_pegi_pt_value)poly::load_and_swap<uint8_t>(pp + 0x03);
ratings->bbfc = (xe_xex2_rating_bbfc_value)poly::load_and_swap<uint8_t>(pp + 0x04);
ratings->cero = (xe_xex2_rating_cero_value)poly::load_and_swap<uint8_t>(pp + 0x05);
ratings->usk = (xe_xex2_rating_usk_value)poly::load_and_swap<uint8_t>(pp + 0x06);
ratings->oflcau = (xe_xex2_rating_oflc_au_value)poly::load_and_swap<uint8_t>(pp + 0x07);
ratings->oflcnz = (xe_xex2_rating_oflc_nz_value)poly::load_and_swap<uint8_t>(pp + 0x08);
ratings->kmrb = (xe_xex2_rating_kmrb_value)poly::load_and_swap<uint8_t>(pp + 0x09);
ratings->brazil = (xe_xex2_rating_brazil_value)poly::load_and_swap<uint8_t>(pp + 0x0A);
ratings->fpb = (xe_xex2_rating_fpb_value)poly::load_and_swap<uint8_t>(pp + 0x0B);
}
break;
case XEX_HEADER_TLS_INFO:
{
xe_xex2_tls_info_t *tls = &header->tls_info;
tls->slot_count = poly::load_and_swap<uint32_t>(pp + 0x00);
tls->raw_data_address = poly::load_and_swap<uint32_t>(pp + 0x04);
tls->data_size = poly::load_and_swap<uint32_t>(pp + 0x08);
tls->raw_data_size = poly::load_and_swap<uint32_t>(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_IMPORT_LIBRARIES:
{
const size_t max_count = poly::countof(header->import_libraries);
size_t count = poly::load_and_swap<uint32_t>(pp + 0x08);
assert_true(count <= max_count);
if (count > max_count) {
XELOGW("ignoring %zu extra entries in XEX_HEADER_IMPORT_LIBRARIES",
(max_count - count));
count = max_count;
}
header->import_library_count = count;
uint32_t string_table_size = poly::load_and_swap<uint32_t>(pp + 0x04);
const char *string_table = (const char*)(pp + 0x0C);
pp += 12 + string_table_size;
for (size_t m = 0; m < count; m++) {
xe_xex2_import_library_t *library = &header->import_libraries[m];
XEEXPECTZERO(xe_copy_memory(library->digest, sizeof(library->digest),
pp + 0x04, 20));
library->import_id = poly::load_and_swap<uint32_t>(pp + 0x18);
library->version.value = poly::load_and_swap<uint32_t>(pp + 0x1C);
library->min_version.value = poly::load_and_swap<uint32_t>(pp + 0x20);
const uint16_t name_index = poly::load_and_swap<uint16_t>(pp + 0x24) & 0xFF;
for (size_t i = 0, j = 0; i < string_table_size;) {
assert_true(j <= 0xFF);
if (j == name_index) {
xestrcpya(library->name, poly::countof(library->name),
string_table + i);
break;
}
if (string_table[i] == 0) {
i++;
if (i % 4) {
i += 4 - (i % 4);
}
j++;
} else {
i++;
}
}
library->record_count = poly::load_and_swap<uint16_t>(pp + 0x26);
library->records = (uint32_t*)xe_calloc(
library->record_count * sizeof(uint32_t));
XEEXPECTNOTNULL(library->records);
pp += 0x28;
for (size_t i = 0; i < library->record_count; i++) {
library->records[i] = poly::load_and_swap<uint32_t>(pp);
pp += 4;
}
}
}
break;
case XEX_HEADER_STATIC_LIBRARIES:
{
const size_t max_count = poly::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];
XEEXPECTZERO(xe_copy_memory(library->name, sizeof(library->name),
pp + 0x00, 8));
library->name[8] = 0;
library->major = poly::load_and_swap<uint16_t>(pp + 0x08);
library->minor = poly::load_and_swap<uint16_t>(pp + 0x0A);
library->build = poly::load_and_swap<uint16_t>(pp + 0x0C);
uint16_t qfeapproval = poly::load_and_swap<uint16_t>(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)poly::load_and_swap<uint16_t>(pp + 0x04);
fmt->compression_type =
(xe_xex2_compression_type)poly::load_and_swap<uint16_t>(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 = poly::load_and_swap<uint32_t>(pp + 0x00);
comp_info->block_count = (info_size - 8) / 8;
comp_info->blocks = (xe_xex2_file_basic_compression_block_t*)
xe_calloc(comp_info->block_count *
sizeof(xe_xex2_file_basic_compression_block_t));
XEEXPECTNOTNULL(comp_info->blocks);
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 = poly::load_and_swap<uint32_t>(pp + 0x08 + (m * 8));
block->zero_size = poly::load_and_swap<uint32_t>(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 = poly::load_and_swap<uint32_t>(pp + 0x08);
uint32_t window_bits = 0;
for (size_t m = 0; m < 32; m++, window_bits++) {
window_size <<= 1;
if (window_size == 0x80000000) {
break;
}
}
comp_info->window_size = poly::load_and_swap<uint32_t>(pp + 0x08);
comp_info->window_bits = window_bits;
comp_info->block_size = poly::load_and_swap<uint32_t>(pp + 0x0C);
XEEXPECTZERO(xe_copy_memory(comp_info->block_hash,
sizeof(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 = poly::load_and_swap<uint32_t>(pc + 0x000);
ldr->image_size = poly::load_and_swap<uint32_t>(pc + 0x004);
XEEXPECTZERO(xe_copy_memory(ldr->rsa_signature, sizeof(ldr->rsa_signature),
pc + 0x008, 256));
ldr->unklength = poly::load_and_swap<uint32_t>(pc + 0x108);
ldr->image_flags = (xe_xex2_image_flags)poly::load_and_swap<uint32_t>(pc + 0x10C);
ldr->load_address = poly::load_and_swap<uint32_t>(pc + 0x110);
XEEXPECTZERO(xe_copy_memory(ldr->section_digest, sizeof(ldr->section_digest),
pc + 0x114, 20));
ldr->import_table_count = poly::load_and_swap<uint32_t>(pc + 0x128);
XEEXPECTZERO(xe_copy_memory(ldr->import_table_digest,
sizeof(ldr->import_table_digest),
pc + 0x12C, 20));
XEEXPECTZERO(xe_copy_memory(ldr->media_id, sizeof(ldr->media_id),
pc + 0x140, 16));
XEEXPECTZERO(xe_copy_memory(ldr->file_key, sizeof(ldr->file_key),
pc + 0x150, 16));
ldr->export_table = poly::load_and_swap<uint32_t>(pc + 0x160);
XEEXPECTZERO(xe_copy_memory(ldr->header_digest, sizeof(ldr->header_digest),
pc + 0x164, 20));
ldr->game_regions = (xe_xex2_region_flags)poly::load_and_swap<uint32_t>(pc + 0x178);
ldr->media_flags = (xe_xex2_media_flags)poly::load_and_swap<uint32_t>(pc + 0x17C);
// Section info follows loader info.
ps = p + header->certificate_offset + 0x180;
header->section_count = poly::load_and_swap<uint32_t>(ps + 0x000);
ps += 4;
header->sections = (xe_xex2_section_t*)xe_calloc(
header->section_count * sizeof(xe_xex2_section_t));
XEEXPECTNOTNULL(header->sections);
for (size_t n = 0; n < header->section_count; n++) {
xe_xex2_section_t *section = &header->sections[n];
section->info.value = poly::load_and_swap<uint32_t>(ps);
ps += 4;
XEEXPECTZERO(xe_copy_memory(section->digest, sizeof(section->digest), ps,
sizeof(section->digest)));
ps += sizeof(section->digest);
}
return 0;
XECLEANUP:
return 1;
}
int xe_xex2_decrypt_key(xe_xex2_header_t *header) {
const static uint8_t xe_xex2_retail_key[16] = {
0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3,
0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91
};
const static 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*)xe_calloc(
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;
xe_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<off_t>(chars), remaining);
if (xe_copy_memory(buffer, total,
(uint8_t*)memfile->buffer + memfile->offset, total)) {
return -1;
}
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<off_t>(chars), remaining);
if (xe_copy_memory((uint8_t*)memfile->buffer + memfile->offset,
memfile->buffer_size - memfile->offset, buffer, total)) {
return -1;
}
memfile->offset += total;
return (int)total;
}
void *mspack_memory_alloc(struct mspack_system *sys, size_t chars) {
return xe_calloc(chars);
}
void mspack_memory_free(void *ptr) {
xe_free(ptr);
}
void mspack_memory_copy(void *src, void *dest, size_t chars) {
xe_copy_memory(dest, chars, src, chars);
}
struct mspack_system *mspack_memory_sys_create() {
struct mspack_system *sys = (struct mspack_system *)xe_calloc(
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) {
xe_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 size_t xex_length,
Memory* memory) {
// Allocate in-place the XEX memory.
const size_t exe_length = xex_length - header->exe_offset;
size_t uncompressed_size = exe_length;
uint32_t alloc_result = (uint32_t)memory->HeapAlloc(
header->exe_address, uncompressed_size,
MEMORY_FLAG_ZERO);
if (!alloc_result) {
XELOGE("Unable to allocate XEX memory at %.8X-%.8X.",
header->exe_address, uncompressed_size);
return 2;
}
uint8_t *buffer = memory->Translate(header->exe_address);
const uint8_t *p = (const uint8_t*)xex_addr + header->exe_offset;
switch (header->file_format_info.encryption_type) {
case XEX_ENCRYPTION_NONE:
return xe_copy_memory(buffer, uncompressed_size, p, exe_length);
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 size_t xex_length,
Memory* memory) {
const size_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.
size_t uncompressed_size = 0;
const xe_xex2_file_basic_compression_info_t* comp_info =
&header->file_format_info.compression_info.basic;
for (size_t n = 0; n < comp_info->block_count; n++) {
const size_t data_size = comp_info->blocks[n].data_size;
const size_t zero_size = comp_info->blocks[n].zero_size;
uncompressed_size += data_size + zero_size;
}
// Allocate in-place the XEX memory.
uint32_t alloc_result = (uint32_t)memory->HeapAlloc(
header->exe_address, uncompressed_size,
MEMORY_FLAG_ZERO);
if (!alloc_result) {
XELOGE("Unable to allocate XEX memory at %.8X-%.8X.",
header->exe_address, uncompressed_size);
XEFAIL();
}
uint8_t *buffer = memory->Translate(header->exe_address);
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 size_t data_size = comp_info->blocks[n].data_size;
const size_t zero_size = comp_info->blocks[n].zero_size;
switch (header->file_format_info.encryption_type) {
case XEX_ENCRYPTION_NONE:
XEEXPECTZERO(xe_copy_memory(d, uncompressed_size - (d - buffer), p,
exe_length - (p - source_buffer)));
break;
case XEX_ENCRYPTION_NORMAL:
{
const uint8_t *ct = p;
uint8_t* pt = d;
for (size_t n = 0; n < data_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];
}
}
}
break;
default:
assert_always();
return 1;
}
p += data_size;
d += data_size + zero_size;
}
return 0;
XECLEANUP:
return 1;
}
int xe_xex2_read_image_compressed(const xe_xex2_header_t *header,
const uint8_t *xex_addr,
const size_t xex_length,
Memory* memory) {
const size_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;
size_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*)xe_calloc(input_size);
XEEXPECTNOTNULL(input_buffer);
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*)xe_calloc(exe_length);
XEEXPECTNOTNULL(compress_buffer);
p = input_buffer;
d = compress_buffer;
// De-block.
deblock_buffer = (uint8_t*)xe_calloc(input_size);
XEEXPECTNOTNULL(deblock_buffer);
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 = poly::load_and_swap<int32_t>(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;
}
xe_copy_memory(d, exe_length - (d - compress_buffer), p, chunk_size);
p += chunk_size;
d += chunk_size;
uncompressed_size += 0x8000;
}
p = pnext;
block_size = next_size;
}
// Allocate in-place the XEX memory.
uint32_t alloc_result = (uint32_t)memory->HeapAlloc(
header->exe_address, uncompressed_size,
MEMORY_FLAG_ZERO);
if (!alloc_result) {
XELOGE("Unable to allocate XEX memory at %.8X-%.8X.",
header->exe_address, uncompressed_size);
result_code = 2;
XEFAIL();
}
uint8_t *buffer = memory->Translate(header->exe_address);
// Setup decompressor and decompress.
sys = mspack_memory_sys_create();
XEEXPECTNOTNULL(sys);
lzxsrc = mspack_memory_open(sys, (void*)compress_buffer, d - compress_buffer);
XEEXPECTNOTNULL(lzxsrc);
lzxdst = mspack_memory_open(sys, buffer, uncompressed_size);
XEEXPECTNOTNULL(lzxdst);
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);
XEEXPECTNOTNULL(lzxd);
XEEXPECTZERO(lzxd_decompress(lzxd, (off_t)header->loader_info.image_size));
result_code = 0;
XECLEANUP:
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;
}
xe_free(compress_buffer);
xe_free(deblock_buffer);
if (free_input) {
xe_free((void*)input_buffer);
}
return result_code;
}
int xe_xex2_read_image(xe_xex2_ref xex, const uint8_t *xex_addr,
const size_t xex_length, 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->Translate(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
// Quick scan to determine bounds of sections.
size_t upper_address = 0;
const IMAGE_SECTION_HEADER* sechdr = IMAGE_FIRST_SECTION(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_SECTION(nthdr);
for (size_t n = 0; n < filehdr->NumberOfSections; n++, sechdr++) {
PESection* section = (PESection*)xe_calloc(sizeof(PESection));
xe_copy_memory(section->name, sizeof(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<PESection*>::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) {
uint8_t* mem = xex->memory->membase();
auto header = xe_xex2_get_header(xex);
// Find library index for verification.
size_t library_index = -1;
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 = poly::load_and_swap<uint32_t>(mem + 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*)xe_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 = poly::load_and_swap<uint32_t>(mem + 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 = -1;
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;
}