[CPU] Move XEX2 code into XexModule class, autodetect XEX key

Code is mainly just copy/pasted from kernel/util/xex2.cc, I've tried fixing it up to work better in a class, but there's probably some things I missed.

Also includes some minor improvements to the XEX loader, like being able to try both XEX keys (retail/devkit) automatically, and some fixes to how the base address is determined.

(Previously there was code that would get base address from optional header, code that'd get it from xex_security_info, code that'd use a stored base address value...
Now everything reads it from a single stored value instead, which is set either from the xex_security_info, or if it exists from the optional header instead.
Maybe this can help improve compatibility with any weird XEX's that don't have a base address optional header?)

Compressed XEX loader also has some extra checks to make sure the compressed data hash matches what's expected.
Might increase loading times by a fraction, but could save reports from people unknowingly using corrupt XEXs.
(still no checks for non-compressed data though, maybe need to compare data with xex_security_info->ImageHash?)
This commit is contained in:
emoose
2018-10-20 04:18:18 +01:00
parent 2fa7607547
commit 0b7f7e1657
13 changed files with 939 additions and 1579 deletions

View File

@@ -21,7 +21,13 @@
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/xmodule.h"
#include "third_party/crypto/TinySHA1.hpp"
#include "third_party/crypto/rijndael-alg-fst.c"
#include "third_party/crypto/rijndael-alg-fst.h"
#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"
namespace xe {
namespace cpu {
@@ -35,9 +41,9 @@ void UndefinedImport(ppc::PPCContext* ppc_context, KernelState* kernel_state) {
XexModule::XexModule(Processor* processor, KernelState* kernel_state)
: Module(processor), processor_(processor), kernel_state_(kernel_state) {}
XexModule::~XexModule() { xe_xex2_dealloc(xex_); }
XexModule::~XexModule() {}
bool XexModule::GetOptHeader(const xex2_header* header, xe_xex2_header_keys key,
bool XexModule::GetOptHeader(const xex2_header* header, xex2_header_keys key,
void** out_ptr) {
assert_not_null(header);
assert_not_null(out_ptr);
@@ -72,14 +78,103 @@ bool XexModule::GetOptHeader(const xex2_header* header, xe_xex2_header_keys key,
return false;
}
bool XexModule::GetOptHeader(xe_xex2_header_keys key, void** out_ptr) const {
bool XexModule::GetOptHeader(xex2_header_keys key, void** out_ptr) const {
return XexModule::GetOptHeader(xex_header(), key, out_ptr);
}
const xex2_security_info* XexModule::GetSecurityInfo(
const xex2_header* header) {
return reinterpret_cast<const xex2_security_info*>(uintptr_t(header) +
header->security_offset);
const PESection* XexModule::GetPESection(const char* name) {
for (std::vector<PESection>::iterator it = pe_sections_.begin();
it != pe_sections_.end(); ++it) {
if (!strcmp(it->name, name)) {
return &(*it);
}
}
return nullptr;
}
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 XexModule::DecryptBuffer(const uint8_t* session_key,
const uint8_t* input_buffer,
const size_t input_size, uint8_t* output_buffer,
const size_t output_size) {
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];
}
}
}
uint32_t XexModule::GetProcAddress(uint16_t ordinal) const {
@@ -101,21 +196,19 @@ uint32_t XexModule::GetProcAddress(uint16_t ordinal) const {
}
// Second: Check the PE exports.
xe::be<uint32_t>* exe_address = nullptr;
GetOptHeader(XEX_HEADER_IMAGE_BASE_ADDRESS, &exe_address);
assert_not_null(exe_address);
assert_not_zero(base_address_);
xex2_opt_data_directory* pe_export_directory = 0;
if (GetOptHeader(XEX_HEADER_EXPORTS_BY_NAME, &pe_export_directory)) {
auto e = memory()->TranslateVirtual<const X_IMAGE_EXPORT_DIRECTORY*>(
*exe_address + pe_export_directory->offset);
base_address_ + pe_export_directory->offset);
assert_not_null(e);
uint32_t* function_table =
reinterpret_cast<uint32_t*>(uintptr_t(e) + e->AddressOfFunctions);
if (ordinal < e->NumberOfFunctions) {
return xex_security_info()->load_address + function_table[ordinal];
return base_address_ + function_table[ordinal];
}
}
@@ -123,18 +216,16 @@ uint32_t XexModule::GetProcAddress(uint16_t ordinal) const {
}
uint32_t XexModule::GetProcAddress(const char* name) const {
xe::be<uint32_t>* exe_address = nullptr;
GetOptHeader(XEX_HEADER_IMAGE_BASE_ADDRESS, &exe_address);
assert_not_null(exe_address);
xex2_opt_data_directory* pe_export_directory = 0;
if (!GetOptHeader(XEX_HEADER_EXPORTS_BY_NAME, &pe_export_directory)) {
// No exports by name.
return 0;
}
assert_not_zero(base_address_);
auto e = memory()->TranslateVirtual<const X_IMAGE_EXPORT_DIRECTORY*>(
*exe_address + pe_export_directory->offset);
base_address_ + pe_export_directory->offset);
assert_not_null(e);
// e->AddressOfX RVAs are relative to the IMAGE_EXPORT_DIRECTORY!
@@ -152,7 +243,7 @@ uint32_t XexModule::GetProcAddress(const char* name) const {
for (uint32_t i = 0; i < e->NumberOfNames; i++) {
auto fn_name = reinterpret_cast<const char*>(uintptr_t(e) + name_table[i]);
uint16_t ordinal = ordinal_table[i];
uint32_t addr = *exe_address + function_table[ordinal];
uint32_t addr = base_address_ + function_table[ordinal];
if (!std::strcmp(name, fn_name)) {
// We have a match!
return addr;
@@ -183,52 +274,507 @@ bool XexModule::ApplyPatch(XexModule* module) {
return true;
}
bool XexModule::Load(const std::string& name, const std::string& path,
const void* xex_addr, size_t xex_length) {
// TODO(DrChat): Move loading code here.
xex_ = xe_xex2_load(memory(), xex_addr, xex_length, {0});
if (!xex_) {
return false;
void XexModule::DecryptSessionKey(bool useDevkit) {
static const uint8_t xe_xex2_retail_key[16] = {
0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3,
0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91};
static const uint8_t xe_xex2_devkit_key[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t* xexkey = useDevkit ? xe_xex2_devkit_key : xe_xex2_retail_key;
// Decrypt the header key.
uint32_t rk[4 * (MAXNR + 1)];
int32_t Nr = rijndaelKeySetupDec(rk, xexkey, 128);
rijndaelDecrypt(rk, Nr,
reinterpret_cast<const u8*>(xex_security_info()->aes_key),
session_key_);
}
int XexModule::ReadImage(const void* xex_addr, size_t xex_length) {
if (!opt_file_format_info()) {
return 1;
}
// Make a copy of the xex header.
auto src_header = reinterpret_cast<const xex2_header*>(xex_addr);
xex_header_mem_.resize(src_header->header_size);
auto* ff = opt_file_format_info();
std::memcpy(xex_header_mem_.data(), src_header, src_header->header_size);
switch (opt_file_format_info()->compression_type) {
case XEX_COMPRESSION_NONE:
return ReadImageUncompressed(xex_addr, xex_length);
case XEX_COMPRESSION_BASIC:
return ReadImageBasicCompressed(xex_addr, xex_length);
case XEX_COMPRESSION_NORMAL:
case XEX_COMPRESSION_DELTA:
return ReadImageCompressed(xex_addr, xex_length);
default:
assert_always();
return 1;
}
}
return Load(name, path, xex_);
int XexModule::ReadImageUncompressed(const void* xex_addr, size_t xex_length) {
// Allocate in-place the XEX memory.
const uint32_t exe_length =
static_cast<uint32_t>(xex_length - xex_header()->header_size);
uint32_t uncompressed_size = exe_length;
bool alloc_result =
memory()
->LookupHeap(base_address_)
->AllocFixed(
base_address_, uncompressed_size, 4096,
xe::kMemoryAllocationReserve | xe::kMemoryAllocationCommit,
xe::kMemoryProtectRead | xe::kMemoryProtectWrite);
if (!alloc_result) {
XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", base_address_,
uncompressed_size);
return 2;
}
uint8_t* buffer = memory()->TranslateVirtual(base_address_);
std::memset(buffer, 0, uncompressed_size);
const uint8_t* p = (const uint8_t*)xex_addr + xex_header()->header_size;
switch (opt_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:
DecryptBuffer(session_key_, p, exe_length, buffer, uncompressed_size);
return 0;
default:
assert_always();
return 1;
}
return 0;
}
int XexModule::ReadImageBasicCompressed(const void* xex_addr,
size_t xex_length) {
const uint32_t exe_length =
static_cast<uint32_t>(xex_length - xex_header()->header_size);
const uint8_t* source_buffer =
(const uint8_t*)xex_addr + xex_header()->header_size;
const uint8_t* p = source_buffer;
auto heap = memory()->LookupHeap(base_address_);
// Calculate uncompressed length.
uint32_t uncompressed_size = 0;
auto* file_info = opt_file_format_info();
auto& comp_info = file_info->compression_info.basic;
uint32_t block_count = (file_info->info_size - 8) / 8;
for (uint32_t n = 0; n < 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 < xex_security_info()->page_descriptor_count; i++) {
// Byteswap the bitfield manually.
xex2_page_descriptor desc;
desc.value = xe::byte_swap(xex_security_info()->page_descriptors[i].value);
total_size += desc.size * heap->page_size();
}
// Allocate in-place the XEX memory.
bool alloc_result = heap->AllocFixed(
base_address_, total_size, 4096,
xe::kMemoryAllocationReserve | xe::kMemoryAllocationCommit,
xe::kMemoryProtectRead | xe::kMemoryProtectWrite);
if (!alloc_result) {
XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", base_address_,
uncompressed_size);
return 1;
}
uint8_t* buffer = memory()->TranslateVirtual(base_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, session_key_, 128);
for (size_t n = 0; n < 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 (opt_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 XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) {
const uint32_t exe_length =
static_cast<uint32_t>(xex_length - xex_header()->header_size);
const uint8_t* exe_buffer =
(const uint8_t*)xex_addr + xex_header()->header_size;
// 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
uint8_t* compress_buffer = NULL;
const uint8_t* p = NULL;
uint8_t* d = NULL;
uint8_t* deblock_buffer = NULL;
// size_t block_size = 0;
uint32_t uncompressed_size = 0;
struct mspack_system* sys = NULL;
mspack_memory_file* lzxsrc = NULL;
mspack_memory_file* lzxdst = NULL;
struct lzxd_stream* lzxd = NULL;
sha1::SHA1 s;
// Decrypt (if needed).
bool free_input = false;
const uint8_t* input_buffer = exe_buffer;
size_t input_size = exe_length;
switch (opt_file_format_info()->encryption_type) {
case XEX_ENCRYPTION_NONE:
// No-op.
break;
case XEX_ENCRYPTION_NORMAL:
// TODO: a way to do without a copy/alloc?
free_input = true;
input_buffer = (const uint8_t*)calloc(1, exe_length);
DecryptBuffer(session_key_, exe_buffer, exe_length,
(uint8_t*)input_buffer, exe_length);
break;
default:
assert_always();
return 1;
}
const auto* compression_info = &opt_file_format_info()->compression_info;
const xex2_compressed_block_info* cur_block =
&compression_info->normal.first_block;
compress_buffer = (uint8_t*)calloc(1, exe_length);
p = input_buffer;
d = compress_buffer;
// De-block.
deblock_buffer = (uint8_t*)calloc(1, input_size);
int result_code = 0;
uint8_t block_calced_digest[0x14];
while (cur_block->block_size) {
const uint8_t* pnext = p + cur_block->block_size;
const auto* next_block = (const xex2_compressed_block_info*)p;
// Compare block hash, if no match we probably used wrong decrypt key
s.reset();
s.processBytes(p, cur_block->block_size);
s.finalize(block_calced_digest);
if (memcmp(block_calced_digest, cur_block->block_hash, 0x14) != 0) {
result_code = 2;
break;
}
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;
cur_block = next_block;
}
if (!result_code) {
// Allocate in-place the XEX memory.
bool alloc_result =
memory()
->LookupHeap(base_address_)
->AllocFixed(
base_address_, uncompressed_size, 4096,
xe::kMemoryAllocationReserve | xe::kMemoryAllocationCommit,
xe::kMemoryProtectRead | xe::kMemoryProtectWrite);
if (alloc_result) {
uint8_t* buffer = memory()->TranslateVirtual(base_address_);
// Reset buffer if this isn't a patch
std::memset(buffer, 0, uncompressed_size);
// Setup decompressor and decompress.
uint32_t window_size = compression_info->normal.window_size;
uint32_t window_bits = 0;
for (size_t m = 0; m < 32; m++, window_bits++) {
window_size >>= 1;
if (window_size == 0x00000000) {
break;
}
}
sys = mspack_memory_sys_create();
lzxsrc =
mspack_memory_open(sys, (void*)compress_buffer, d - compress_buffer);
lzxdst = mspack_memory_open(sys, buffer, uncompressed_size);
lzxd = lzxd_init(sys, (struct mspack_file*)lzxsrc,
(struct mspack_file*)lzxdst, window_bits, 0, 32768,
(off_t)xex_security_info()->image_size);
result_code =
lzxd_decompress(lzxd, (off_t)xex_security_info()->image_size);
} else {
XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", base_address_,
uncompressed_size);
result_code = 3;
}
}
if (lzxd) {
lzxd_free(lzxd);
lzxd = NULL;
}
if (lzxsrc) {
mspack_memory_close(lzxsrc);
lzxsrc = NULL;
}
if (lzxdst) {
mspack_memory_close(lzxdst);
lzxdst = NULL;
}
if (sys) {
mspack_memory_sys_destroy(sys);
sys = NULL;
}
if (compress_buffer) {
free((void*)compress_buffer);
}
if (deblock_buffer) {
free((void*)deblock_buffer);
}
if (free_input) {
free((void*)input_buffer);
}
return result_code;
}
int XexModule::ReadPEHeaders() {
const uint8_t* p = memory()->TranslateVirtual(base_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;
memcpy(section.name, sechdr->Name, sizeof(sechdr->Name));
section.name[8] = 0;
section.raw_address = sechdr->PointerToRawData;
section.raw_size = sechdr->SizeOfRawData;
section.address = base_address_ + sechdr->VirtualAddress;
section.size = sechdr->Misc.VirtualSize;
section.flags = sechdr->Characteristics;
pe_sections_.push_back(section);
// pe_sections_.push_back(section);
}
// DumpTLSDirectory(pImageBase, pNTHeader, (PIMAGE_TLS_DIRECTORY32)0);
// DumpExportsSection(pImageBase, pNTHeader);
return 0;
}
bool XexModule::Load(const std::string& name, const std::string& path,
xe_xex2_ref xex) {
const void* xex_addr, size_t xex_length) {
auto src_header = reinterpret_cast<const xex2_header*>(xex_addr);
if (src_header->magic != 'XEX2') {
return false;
}
assert_false(loaded_);
loaded_ = true;
xex_ = xex;
auto old_header = xe_xex2_get_header(xex_);
// Read in XEX headers
xex_header_mem_.resize(src_header->header_size);
std::memcpy(xex_header_mem_.data(), src_header, src_header->header_size);
auto sec_header = xex_security_info();
// Try setting our base_address based on XEX_HEADER_IMAGE_BASE_ADDRESS, fall
// back to xex_security_info otherwise
base_address_ = sec_header->load_address;
xe::be<uint32_t>* base_addr_opt = nullptr;
if (GetOptHeader(XEX_HEADER_IMAGE_BASE_ADDRESS, &base_addr_opt))
base_address_ = *base_addr_opt;
// Setup debug info.
name_ = std::string(name);
path_ = std::string(path);
// TODO(benvanik): debug info
uint8_t* data = memory()->TranslateVirtual(base_address_);
// Load in the XEX basefile
// We'll try using both XEX2 keys to see if any give a valid PE
while (true) {
memory()->LookupHeap(base_address_)->Reset();
DecryptSessionKey(is_dev_kit_);
if (!ReadImage(xex_addr, xex_length) && !ReadPEHeaders()) {
break;
}
is_dev_kit_ = !is_dev_kit_;
// is_dev_kit starts as false, then flips to true if load failed, if it's
// back to false again this must be invalid
if (!is_dev_kit_) {
return false;
}
XELOGW("XEX load failed, trying with devkit encryption key...");
}
// Scan and find the low/high addresses.
// All code sections are continuous, so this should be easy.
// TODO(DrChat): Use the new xex header to do this.
auto heap = memory()->LookupHeap(base_address_);
auto page_size = heap->page_size();
low_address_ = UINT_MAX;
high_address_ = 0;
for (uint32_t n = 0, i = 0; n < old_header->section_count; n++) {
const xe_xex2_section_t* section = &old_header->sections[n];
const uint32_t start_address =
old_header->exe_address + (i * section->page_size);
const uint32_t end_address =
start_address + (section->info.page_count * section->page_size);
if (section->info.type == XEX_SECTION_CODE) {
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
// Byteswap the bitfield manually.
xex2_page_descriptor desc;
desc.value = xe::byte_swap(sec_header->page_descriptors[i].value);
const auto start_address = base_address_ + (page * page_size);
const auto end_address = start_address + (desc.size * page_size);
if (desc.info == XEX_SECTION_CODE) {
low_address_ = std::min(low_address_, start_address);
high_address_ = std::max(high_address_, end_address);
}
i += section->info.page_count;
page += desc.size;
}
// Notify backend that we have an executable range.
@@ -288,15 +834,12 @@ bool XexModule::Load(const std::string& name, const std::string& path,
}
// Setup memory protection.
auto sec_header = xex_security_info();
auto heap = memory()->LookupHeap(sec_header->load_address);
auto page_size = heap->page_size();
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
// Byteswap the bitfield manually.
xex2_page_descriptor desc;
desc.value = xe::byte_swap(sec_header->page_descriptors[i].value);
auto address = sec_header->load_address + (page * page_size);
auto address = base_address_ + (page * page_size);
auto size = desc.size * page_size;
switch (desc.info) {
case XEX_SECTION_CODE:
@@ -321,11 +864,9 @@ bool XexModule::Unload() {
loaded_ = false;
// Just deallocate the memory occupied by the exe
xe::be<uint32_t>* exe_address = 0;
GetOptHeader(XEX_HEADER_IMAGE_BASE_ADDRESS, &exe_address);
assert_not_zero(exe_address);
assert_not_zero(base_address_);
memory()->LookupHeap(*exe_address)->Release(*exe_address);
memory()->LookupHeap(base_address_)->Release(base_address_);
xex_header_mem_.resize(0);
return true;
@@ -346,6 +887,12 @@ bool XexModule::SetupLibraryImports(const char* name,
libbasename = libbasename.substr(0, dot);
}
ImportLibrary library_info;
library_info.Name = libbasename;
library_info.ID = library->id;
library_info.Version.value = library->version.value;
library_info.MinVersion.value = library->version_min.value;
// Imports are stored as {import descriptor, thunk addr, import desc, ...}
// Even thunks have an import descriptor (albeit unused/useless)
for (uint32_t i = 0; i < library->count; i++) {
@@ -379,6 +926,12 @@ bool XexModule::SetupLibraryImports(const char* name,
StringBuffer import_name;
if (record_type == 0) {
// Variable.
ImportLibraryFn import_info;
import_info.Ordinal = ordinal;
import_info.ValueAddress = record_addr;
library_info.Imports.push_back(import_info);
import_name.AppendFormat("__imp__");
if (kernel_export) {
import_name.AppendFormat("%s", kernel_export->name);
@@ -418,6 +971,11 @@ bool XexModule::SetupLibraryImports(const char* name,
var_info->set_status(Symbol::Status::kDefined);
} else if (record_type == 1) {
// Thunk.
assert_true(library_info.Imports.size() > 0);
auto& prev_import = library_info.Imports[library_info.Imports.size() - 1];
assert_true(prev_import.Ordinal == ordinal);
prev_import.ThunkAddress = record_addr;
if (kernel_export) {
import_name.AppendFormat("%s", kernel_export->name);
} else {
@@ -488,6 +1046,8 @@ bool XexModule::SetupLibraryImports(const char* name,
}
}
import_libs_.push_back(library_info);
return true;
}
@@ -669,14 +1229,15 @@ bool XexModule::FindSaveRest() {
uint32_t gplr_start = 0;
uint32_t fpr_start = 0;
uint32_t vmx_start = 0;
const xe_xex2_header_t* header = xe_xex2_get_header(xex_);
for (uint32_t n = 0, i = 0; n < header->section_count; n++) {
const xe_xex2_section_t* section = &header->sections[n];
const uint32_t start_address =
header->exe_address + (i * section->page_size);
const uint32_t end_address =
start_address + (section->info.page_count * section->page_size);
if (section->info.type == XEX_SECTION_CODE) {
auto page_size = base_address_ <= 0x90000000 ? 64 * 1024 : 4 * 1024;
auto sec_header = xex_security_info();
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
const xex2_page_descriptor* section = &sec_header->page_descriptors[i];
const auto start_address = base_address_ + (page * page_size);
const auto end_address = start_address + (section->size * page_size);
if (section->info == XEX_SECTION_CODE) {
if (!gplr_start) {
gplr_start = memory_->SearchAligned(start_address, end_address,
gprlr_code_values,
@@ -696,7 +1257,8 @@ bool XexModule::FindSaveRest() {
break;
}
}
i += section->info.page_count;
page += section->size;
}
// Add function stubs.

View File

@@ -14,7 +14,6 @@
#include <vector>
#include "xenia/cpu/module.h"
#include "xenia/kernel/util/xex2.h"
#include "xenia/kernel/util/xex2_info.h"
namespace xe {
@@ -30,40 +29,73 @@ class Runtime;
class XexModule : public xe::cpu::Module {
public:
struct ImportLibraryFn {
public:
uint32_t Ordinal;
uint32_t ValueAddress;
uint32_t ThunkAddress;
};
struct ImportLibrary {
public:
std::string Name;
uint32_t ID;
xe_xex2_version_t Version;
xe_xex2_version_t MinVersion;
std::vector<ImportLibraryFn> Imports;
};
XexModule(Processor* processor, kernel::KernelState* kernel_state);
virtual ~XexModule();
xe_xex2_ref xex() const { return xex_; }
bool loaded() const { return loaded_; }
const xex2_header* xex_header() const {
return reinterpret_cast<const xex2_header*>(xex_header_mem_.data());
}
const xex2_security_info* xex_security_info() const {
return GetSecurityInfo(xex_header());
return reinterpret_cast<const xex2_security_info*>(
uintptr_t(xex_header()) + xex_header()->security_offset);
}
const std::vector<ImportLibrary>* import_libraries() const {
return &import_libs_;
}
const xex2_opt_execution_info* opt_execution_info() const {
xex2_opt_execution_info* retval = nullptr;
GetOptHeader(XEX_HEADER_EXECUTION_INFO, &retval);
return retval;
}
const xex2_opt_file_format_info* opt_file_format_info() const {
xex2_opt_file_format_info* retval = nullptr;
GetOptHeader(XEX_HEADER_FILE_FORMAT_INFO, &retval);
return retval;
}
const uint32_t base_address() const { return base_address_; }
// Gets an optional header. Returns NULL if not found.
// Special case: if key & 0xFF == 0x00, this function will return the value,
// not a pointer! This assumes out_ptr points to uint32_t.
static bool GetOptHeader(const xex2_header* header, xe_xex2_header_keys key,
static bool GetOptHeader(const xex2_header* header, xex2_header_keys key,
void** out_ptr);
bool GetOptHeader(xe_xex2_header_keys key, void** out_ptr) const;
bool GetOptHeader(xex2_header_keys key, void** out_ptr) const;
// Ultra-cool templated version
// Special case: if key & 0xFF == 0x00, this function will return the value,
// not a pointer! This assumes out_ptr points to uint32_t.
template <typename T>
static bool GetOptHeader(const xex2_header* header, xe_xex2_header_keys key,
static bool GetOptHeader(const xex2_header* header, xex2_header_keys key,
T* out_ptr) {
return GetOptHeader(header, key, reinterpret_cast<void**>(out_ptr));
}
template <typename T>
bool GetOptHeader(xe_xex2_header_keys key, T* out_ptr) const {
bool GetOptHeader(xex2_header_keys key, T* out_ptr) const {
return GetOptHeader(key, reinterpret_cast<void**>(out_ptr));
}
static const xex2_security_info* GetSecurityInfo(const xex2_header* header);
const PESection* GetPESection(const char* name);
uint32_t GetProcAddress(uint16_t ordinal) const;
uint32_t GetProcAddress(const char* name) const;
@@ -71,7 +103,6 @@ class XexModule : public xe::cpu::Module {
bool ApplyPatch(XexModule* module);
bool Load(const std::string& name, const std::string& path,
const void* xex_addr, size_t xex_length);
bool Load(const std::string& name, const std::string& path, xe_xex2_ref xex);
bool Unload();
const std::string& name() const override { return name_; }
@@ -81,10 +112,31 @@ class XexModule : public xe::cpu::Module {
bool ContainsAddress(uint32_t address) override;
static void DecryptBuffer(const uint8_t* session_key,
const uint8_t* input_buffer,
const size_t input_size, uint8_t* output_buffer,
const size_t output_size);
uint8_t* HostData() {
if (base_address_)
return memory()->TranslateVirtual(base_address_);
else
return nullptr;
}
protected:
std::unique_ptr<Function> CreateFunction(uint32_t address) override;
private:
void DecryptSessionKey(bool useDevkit = false);
int ReadImage(const void* xex_addr, size_t xex_length);
int ReadImageUncompressed(const void* xex_addr, size_t xex_length);
int ReadImageBasicCompressed(const void* xex_addr, size_t xex_length);
int ReadImageCompressed(const void* xex_addr, size_t xex_length);
int ReadPEHeaders();
bool SetupLibraryImports(const char* name,
const xex2_import_library* library);
bool FindSaveRest();
@@ -93,13 +145,23 @@ class XexModule : public xe::cpu::Module {
kernel::KernelState* kernel_state_ = nullptr;
std::string name_;
std::string path_;
xe_xex2_ref xex_ = nullptr;
std::vector<uint8_t> xex_header_mem_; // Holds the xex header
bool loaded_ = false; // Loaded into memory?
// various optional headers
std::vector<ImportLibrary>
import_libs_; // pre-loaded import libraries for ease of use
std::vector<PESection> pe_sections_;
uint8_t session_key_[0x10];
bool loaded_ = false; // Loaded into memory?
uint32_t base_address_ = 0;
uint32_t low_address_ = 0;
uint32_t high_address_ = 0;
bool is_dev_kit_ = false;
};
} // namespace cpu