mirror of
https://github.com/xenia-project/xenia.git
synced 2025-12-06 07:12:03 +01:00
1027 lines
36 KiB
C++
1027 lines
36 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 "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"
|
|
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/math.h"
|
|
#include "xenia/base/memory.h"
|
|
#include "xenia/base/platform.h"
|
|
|
|
// TODO(benvanik): remove.
|
|
#define XEEXPECTZERO(expr) \
|
|
if ((expr) != 0) { \
|
|
goto XECLEANUP; \
|
|
}
|
|
#define XEEXPECTNOTNULL(expr) \
|
|
if ((expr) == NULL) { \
|
|
goto XECLEANUP; \
|
|
}
|
|
|
|
DEFINE_bool(xex_dev_key, false, "Use the devkit key.");
|
|
|
|
typedef struct xe_xex2 {
|
|
xe::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 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<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, uint32_t(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_dealloc(xex);
|
|
return nullptr;
|
|
}
|
|
|
|
void xe_xex2_dealloc(xe_xex2_ref xex) {
|
|
if (!xex) {
|
|
return;
|
|
}
|
|
|
|
for (std::vector<PESection *>::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<uint32_t>(p + 0x00);
|
|
if (header->xex2 != 0x58455832) {
|
|
return 1;
|
|
}
|
|
|
|
header->module_flags =
|
|
(xe_xex2_module_flags)xe::load_and_swap<uint32_t>(p + 0x04);
|
|
header->exe_offset = xe::load_and_swap<uint32_t>(p + 0x08);
|
|
header->unknown0 = xe::load_and_swap<uint32_t>(p + 0x0C);
|
|
header->certificate_offset = xe::load_and_swap<uint32_t>(p + 0x10);
|
|
header->header_count = xe::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 = xe::load_and_swap<uint32_t>(ph + 0x00);
|
|
const uint32_t data_offset = xe::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 = xe::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 *)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<uint32_t>(phi + 0x08);
|
|
res.size = xe::load_and_swap<uint32_t>(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<uint32_t>(pp + 0x00);
|
|
ex->version.value = xe::load_and_swap<uint32_t>(pp + 0x04);
|
|
ex->base_version.value = xe::load_and_swap<uint32_t>(pp + 0x08);
|
|
ex->title_id = xe::load_and_swap<uint32_t>(pp + 0x0C);
|
|
ex->platform = xe::load_and_swap<uint8_t>(pp + 0x10);
|
|
ex->executable_table = xe::load_and_swap<uint8_t>(pp + 0x11);
|
|
ex->disc_number = xe::load_and_swap<uint8_t>(pp + 0x12);
|
|
ex->disc_count = xe::load_and_swap<uint8_t>(pp + 0x13);
|
|
ex->savegame_id = xe::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)xe::load_and_swap<uint8_t>(pp + 0x00);
|
|
ratings->pegi =
|
|
(xe_xex2_rating_pegi_value)xe::load_and_swap<uint8_t>(pp + 0x01);
|
|
ratings->pegifi =
|
|
(xe_xex2_rating_pegi_fi_value)xe::load_and_swap<uint8_t>(pp + 0x02);
|
|
ratings->pegipt =
|
|
(xe_xex2_rating_pegi_pt_value)xe::load_and_swap<uint8_t>(pp + 0x03);
|
|
ratings->bbfc =
|
|
(xe_xex2_rating_bbfc_value)xe::load_and_swap<uint8_t>(pp + 0x04);
|
|
ratings->cero =
|
|
(xe_xex2_rating_cero_value)xe::load_and_swap<uint8_t>(pp + 0x05);
|
|
ratings->usk =
|
|
(xe_xex2_rating_usk_value)xe::load_and_swap<uint8_t>(pp + 0x06);
|
|
ratings->oflcau =
|
|
(xe_xex2_rating_oflc_au_value)xe::load_and_swap<uint8_t>(pp + 0x07);
|
|
ratings->oflcnz =
|
|
(xe_xex2_rating_oflc_nz_value)xe::load_and_swap<uint8_t>(pp + 0x08);
|
|
ratings->kmrb =
|
|
(xe_xex2_rating_kmrb_value)xe::load_and_swap<uint8_t>(pp + 0x09);
|
|
ratings->brazil =
|
|
(xe_xex2_rating_brazil_value)xe::load_and_swap<uint8_t>(pp + 0x0A);
|
|
ratings->fpb =
|
|
(xe_xex2_rating_fpb_value)xe::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 = xe::load_and_swap<uint32_t>(pp + 0x00);
|
|
tls->raw_data_address = xe::load_and_swap<uint32_t>(pp + 0x04);
|
|
tls->data_size = xe::load_and_swap<uint32_t>(pp + 0x08);
|
|
tls->raw_data_size = xe::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_EXPORTS_BY_NAME: {
|
|
// IMAGE_DATA_DIRECTORY (w/ offset from PE file base)
|
|
header->export_table_offset = xe::load_and_swap<uint32_t>(pp);
|
|
// size = xe::load_and_swap<uint32_t>(pp + 0x04);
|
|
} break;
|
|
case XEX_HEADER_IMPORT_LIBRARIES: {
|
|
const size_t max_count = xe::countof(header->import_libraries);
|
|
size_t count = xe::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 = xe::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];
|
|
memcpy(library->digest, pp + 0x04, 20);
|
|
library->import_id = xe::load_and_swap<uint32_t>(pp + 0x18);
|
|
library->version.value = xe::load_and_swap<uint32_t>(pp + 0x1C);
|
|
library->min_version.value = xe::load_and_swap<uint32_t>(pp + 0x20);
|
|
|
|
const uint16_t name_index =
|
|
xe::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) {
|
|
std::strncpy(library->name, string_table + i,
|
|
xe::countof(library->name));
|
|
break;
|
|
}
|
|
if (string_table[i] == 0) {
|
|
i++;
|
|
if (i % 4) {
|
|
i += 4 - (i % 4);
|
|
}
|
|
j++;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
|
|
library->record_count = xe::load_and_swap<uint16_t>(pp + 0x26);
|
|
library->records =
|
|
(uint32_t *)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] = xe::load_and_swap<uint32_t>(pp);
|
|
pp += 4;
|
|
}
|
|
}
|
|
} 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<uint16_t>(pp + 0x08);
|
|
library->minor = xe::load_and_swap<uint16_t>(pp + 0x0A);
|
|
library->build = xe::load_and_swap<uint16_t>(pp + 0x0C);
|
|
uint16_t qfeapproval = xe::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)xe::load_and_swap<uint16_t>(pp + 0x04);
|
|
fmt->compression_type =
|
|
(xe_xex2_compression_type)xe::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 = xe::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 *)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 =
|
|
xe::load_and_swap<uint32_t>(pp + 0x08 + (m * 8));
|
|
block->zero_size =
|
|
xe::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 = xe::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 = xe::load_and_swap<uint32_t>(pp + 0x08);
|
|
comp_info->window_bits = window_bits;
|
|
comp_info->block_size = xe::load_and_swap<uint32_t>(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<uint32_t>(pc + 0x000);
|
|
ldr->image_size = xe::load_and_swap<uint32_t>(pc + 0x004);
|
|
memcpy(ldr->rsa_signature, pc + 0x008, 256);
|
|
ldr->unklength = xe::load_and_swap<uint32_t>(pc + 0x108);
|
|
ldr->image_flags =
|
|
(xe_xex2_image_flags)xe::load_and_swap<uint32_t>(pc + 0x10C);
|
|
ldr->load_address = xe::load_and_swap<uint32_t>(pc + 0x110);
|
|
memcpy(ldr->section_digest, pc + 0x114, 20);
|
|
ldr->import_table_count = xe::load_and_swap<uint32_t>(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<uint32_t>(pc + 0x160);
|
|
memcpy(ldr->header_digest, pc + 0x164, 20);
|
|
ldr->game_regions =
|
|
(xe_xex2_region_flags)xe::load_and_swap<uint32_t>(pc + 0x178);
|
|
ldr->media_flags =
|
|
(xe_xex2_media_flags)xe::load_and_swap<uint32_t>(pc + 0x17C);
|
|
|
|
// Section info follows loader info.
|
|
ps = p + header->certificate_offset + 0x180;
|
|
header->section_count = xe::load_and_swap<uint32_t>(ps + 0x000);
|
|
ps += 4;
|
|
header->sections = (xe_xex2_section_t *)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->page_size =
|
|
header->exe_address <= 0x90000000 ? 64 * 1024 : 4 * 1024;
|
|
section->info.value = xe::load_and_swap<uint32_t>(ps);
|
|
ps += 4;
|
|
memcpy(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 *)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<off_t>(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<off_t>(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;
|
|
uint32_t alloc_result = memory->HeapAlloc(
|
|
header->exe_address, uncompressed_size, xe::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->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;
|
|
}
|
|
|
|
// Allocate in-place the XEX memory.
|
|
uint32_t alloc_result = memory->HeapAlloc(
|
|
header->exe_address, uncompressed_size, xe::MEMORY_FLAG_ZERO);
|
|
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);
|
|
uint8_t *d = buffer;
|
|
std::memset(buffer, 0, uncompressed_size);
|
|
|
|
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 (exe_length - (p - source_buffer) >
|
|
uncompressed_size - (d - buffer)) {
|
|
// Overflow.
|
|
return 1;
|
|
}
|
|
memcpy(d, p, exe_length - (p - source_buffer));
|
|
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);
|
|
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 *)calloc(1, exe_length);
|
|
XEEXPECTNOTNULL(compress_buffer);
|
|
|
|
p = input_buffer;
|
|
d = compress_buffer;
|
|
|
|
// De-block.
|
|
deblock_buffer = (uint8_t *)calloc(1, 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 = xe::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;
|
|
}
|
|
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.
|
|
uint32_t alloc_result = memory->HeapAlloc(
|
|
header->exe_address, uncompressed_size, xe::MEMORY_FLAG_ZERO);
|
|
if (!alloc_result) {
|
|
XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", header->exe_address,
|
|
uncompressed_size);
|
|
result_code = 2;
|
|
goto XECLEANUP;
|
|
}
|
|
uint8_t *buffer = memory->TranslateVirtual(header->exe_address);
|
|
std::memset(buffer, 0, uncompressed_size);
|
|
|
|
// 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;
|
|
}
|
|
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
|
|
|
|
// 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 *)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<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) {
|
|
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 =
|
|
xe::load_and_swap<uint32_t>(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<uint32_t>(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 = -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;
|
|
}
|
|
|
|
int xe_xex2_lookup_export(xe_xex2_ref xex, const char *name,
|
|
PEExport &peexport) {
|
|
auto header = xe_xex2_get_header(xex);
|
|
|
|
// no exports :(
|
|
if (!header->export_table_offset) {
|
|
return 1;
|
|
}
|
|
|
|
uint64_t baseaddr = (uint64_t)xex->memory->TranslateVirtual(header->exe_address);
|
|
IMAGE_EXPORT_DIRECTORY *e = (PIMAGE_EXPORT_DIRECTORY)(baseaddr + header->export_table_offset);
|
|
|
|
// e->AddressOfX RVAs are relative to the IMAGE_EXPORT_DIRECTORY!
|
|
uint32_t* function_table = (uint32_t*)((uint64_t)e + e->AddressOfFunctions); // Functions relative to base
|
|
uint32_t* name_table = (uint32_t*)((uint64_t)e + e->AddressOfNames); // Names relative to directory
|
|
uint16_t* ordinal_table = (uint16_t*)((uint64_t)e + e->AddressOfNameOrdinals); // Table of ordinals
|
|
|
|
const char* mod_name = (const char*)((uint64_t)e + e->Name);
|
|
|
|
for (int i = 0; i < e->NumberOfNames; i++) {
|
|
const char *fn_name = (const char *)((uint64_t)e + name_table[i]);
|
|
uint16_t ordinal = ordinal_table[i];
|
|
uint64_t addr = (uint64_t)(baseaddr + function_table[ordinal]);
|
|
|
|
if (!strcmp(name, fn_name)) {
|
|
// We have a match!
|
|
peexport.name = fn_name;
|
|
peexport.addr = addr;
|
|
peexport.ordinal = ordinal;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// No match
|
|
return 1;
|
|
} |