[CPU] Add XEXP support to XexModule, if XEXP is in same folder as XEX

This was a headache to work out, big thanks to the lack of documentation on .xexp files... a ton of guesswork was involved here but luckily it turned out well.

I did have to make some pretty major changes to the way XEX files are loaded though.
Previously it'd just load everything in one go: XEX headers -> decrypt/decompress data -> load imports/symbols -> set loader data table entries, etc...

Now it's changed to something like this:
- Load base XEX headers + decrypted/decompressed image data, return X_STATUS_PENDING
- In the LoadFromFile call used to load the XEX, search for XEXP patch file (only .xexp in same folder atm)
- If patch exists: load XEXP, decrypt headers/data, apply patch to base XEX, dispose of XEXP
- Finish XEX load via LoadXexContinue() (handles imports/symbols/loader data...)

This saves us from needing to reset the imports/function/symbol stuff after patching (since all the XEX code will be a lot different), but I'm not really sure if I went about it the best way.
This commit is contained in:
emoose 2018-10-20 04:36:21 +01:00
parent 0b7f7e1657
commit 265903fe66
5 changed files with 589 additions and 223 deletions

View File

@ -29,68 +29,12 @@
#include "third_party/mspack/mspack.h"
#include "third_party/pe/pe_image.h"
namespace xe {
namespace cpu {
using xe::kernel::KernelState;
void UndefinedImport(ppc::PPCContext* ppc_context, KernelState* kernel_state) {
XELOGE("call to undefined import");
}
XexModule::XexModule(Processor* processor, KernelState* kernel_state)
: Module(processor), processor_(processor), kernel_state_(kernel_state) {}
XexModule::~XexModule() {}
bool XexModule::GetOptHeader(const xex2_header* header, xex2_header_keys key,
void** out_ptr) {
assert_not_null(header);
assert_not_null(out_ptr);
for (uint32_t i = 0; i < header->header_count; i++) {
const xex2_opt_header& opt_header = header->headers[i];
if (opt_header.key == key) {
// Match!
switch (key & 0xFF) {
case 0x00: {
// We just return the value of the optional header.
// Assume that the output pointer points to a uint32_t.
*reinterpret_cast<uint32_t*>(out_ptr) =
static_cast<uint32_t>(opt_header.value);
} break;
case 0x01: {
// Pointer to the value on the optional header.
*out_ptr = const_cast<void*>(
reinterpret_cast<const void*>(&opt_header.value));
} break;
default: {
// Pointer to the header.
*out_ptr =
reinterpret_cast<void*>(uintptr_t(header) + opt_header.offset);
} break;
}
return true;
}
}
return false;
}
bool XexModule::GetOptHeader(xex2_header_keys key, void** out_ptr) const {
return XexModule::GetOptHeader(xex_header(), key, out_ptr);
}
const PESection* XexModule::GetPESection(const char* name) {
for (std::vector<PESection>::iterator it = pe_sections_.begin();
it != pe_sections_.end(); ++it) {
if (!strcmp(it->name, name)) {
return &(*it);
}
}
return nullptr;
}
static const uint8_t xe_xex2_retail_key[16] = {
0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3,
0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91};
static const uint8_t xe_xex2_devkit_key[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
typedef struct mspack_memory_file_t {
struct mspack_system sys;
@ -156,10 +100,104 @@ struct mspack_system* mspack_memory_sys_create() {
}
void mspack_memory_sys_destroy(struct mspack_system* sys) { free(sys); }
void XexModule::DecryptBuffer(const uint8_t* session_key,
const uint8_t* input_buffer,
const size_t input_size, uint8_t* output_buffer,
const size_t output_size) {
int lzx_decompress(const void* lzx_data, size_t lzx_len, void* dest,
size_t dest_len, int window_size, void* window_data,
size_t window_data_len) {
uint32_t window_bits = 0;
uint32_t temp_sz = window_size;
for (size_t m = 0; m < 32; m++, window_bits++) {
temp_sz >>= 1;
if (temp_sz == 0x00000000) {
break;
}
}
int result_code = 1;
mspack_system* sys = mspack_memory_sys_create();
mspack_memory_file* lzxsrc =
mspack_memory_open(sys, (void*)lzx_data, lzx_len);
mspack_memory_file* lzxdst = mspack_memory_open(sys, dest, dest_len);
lzxd_stream* lzxd =
lzxd_init(sys, (struct mspack_file*)lzxsrc, (struct mspack_file*)lzxdst,
window_bits, 0, 0x8000, (off_t)dest_len);
if (lzxd) {
if (window_data) {
// zero the window and then copy window_data to the end of it
memset(lzxd->window, 0, window_data_len);
memcpy(lzxd->window + (window_size - window_data_len), window_data,
window_data_len);
}
result_code = lzxd_decompress(lzxd, (off_t)dest_len);
lzxd_free(lzxd);
lzxd = NULL;
}
if (lzxsrc) {
mspack_memory_close(lzxsrc);
lzxsrc = NULL;
}
if (lzxdst) {
mspack_memory_close(lzxdst);
lzxdst = NULL;
}
if (sys) {
mspack_memory_sys_destroy(sys);
sys = NULL;
}
return result_code;
}
int lzxdelta_apply_patch(xe::xex2_delta_patch* patch, size_t patch_len,
void* dest) {
void* patch_end = (char*)patch + patch_len;
auto* cur_patch = patch;
while (patch_end > cur_patch) {
int patch_sz = -4; // 0 byte patches need us to remove 4 byte from next
// patch addr because of patch_data field
if (cur_patch->compressed_len == 0 && cur_patch->uncompressed_len == 0 &&
cur_patch->new_addr == 0 && cur_patch->old_addr == 0)
break;
switch (cur_patch->compressed_len) {
case 0: // fill with 0
memset((char*)dest + cur_patch->new_addr, 0,
cur_patch->uncompressed_len);
break;
case 1: // copy from old -> new
memcpy((char*)dest + cur_patch->new_addr,
(char*)dest + cur_patch->old_addr, cur_patch->uncompressed_len);
break;
default: // delta patch
patch_sz =
cur_patch->compressed_len - 4; // -4 because of patch_data field
int result = lzx_decompress(
cur_patch->patch_data, cur_patch->compressed_len,
(char*)dest + cur_patch->new_addr, cur_patch->uncompressed_len,
0x8000, (char*)dest + cur_patch->old_addr,
cur_patch->uncompressed_len);
if (result) {
return result;
}
break;
}
cur_patch++;
cur_patch = (xe::xex2_delta_patch*)((char*)cur_patch +
patch_sz); // TODO: make this less ugly
}
return 0;
}
void aes_decrypt_buffer(const uint8_t* session_key, const uint8_t* input_buffer,
const size_t input_size, uint8_t* output_buffer,
const size_t output_size) {
uint32_t rk[4 * (MAXNR + 1)];
uint8_t ivec[16] = {0};
int32_t Nr = rijndaelKeySetupDec(rk, session_key, 128);
@ -177,6 +215,71 @@ void XexModule::DecryptBuffer(const uint8_t* session_key,
}
}
namespace xe {
namespace cpu {
using xe::kernel::KernelState;
XexModule::XexModule(Processor* processor, KernelState* kernel_state)
: Module(processor), processor_(processor), kernel_state_(kernel_state) {}
XexModule::~XexModule() {}
bool XexModule::GetOptHeader(const xex2_header* header, xex2_header_keys key,
void** out_ptr) {
assert_not_null(header);
assert_not_null(out_ptr);
for (uint32_t i = 0; i < header->header_count; i++) {
const xex2_opt_header& opt_header = header->headers[i];
if (opt_header.key == key) {
// Match!
switch (key & 0xFF) {
case 0x00: {
// We just return the value of the optional header.
// Assume that the output pointer points to a uint32_t.
*reinterpret_cast<uint32_t*>(out_ptr) =
static_cast<uint32_t>(opt_header.value);
} break;
case 0x01: {
// Pointer to the value on the optional header.
*out_ptr = const_cast<void*>(
reinterpret_cast<const void*>(&opt_header.value));
} break;
default: {
// Pointer to the header.
*out_ptr =
reinterpret_cast<void*>(uintptr_t(header) + opt_header.offset);
} break;
}
return true;
}
}
return false;
}
bool XexModule::GetOptHeader(xex2_header_keys key, void** out_ptr) const {
return XexModule::GetOptHeader(xex_header(), key, out_ptr);
}
const xex2_security_info* XexModule::GetSecurityInfo(
const xex2_header* header) {
return reinterpret_cast<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;
}
uint32_t XexModule::GetProcAddress(uint16_t ordinal) const {
// First: Check the xex2 export table.
if (xex_security_info()->export_table) {
@ -216,14 +319,14 @@ uint32_t XexModule::GetProcAddress(uint16_t ordinal) const {
}
uint32_t XexModule::GetProcAddress(const char* name) const {
assert_not_zero(base_address_);
xex2_opt_data_directory* pe_export_directory = 0;
if (!GetOptHeader(XEX_HEADER_EXPORTS_BY_NAME, &pe_export_directory)) {
// No exports by name.
return 0;
}
assert_not_zero(base_address_);
auto e = memory()->TranslateVirtual<const X_IMAGE_EXPORT_DIRECTORY*>(
base_address_ + pe_export_directory->offset);
assert_not_null(e);
@ -254,63 +357,259 @@ uint32_t XexModule::GetProcAddress(const char* name) const {
return 0;
}
bool XexModule::ApplyPatch(XexModule* module) {
auto header = reinterpret_cast<const xex2_header*>(module->xex_header());
if (!(header->module_flags &
(XEX_MODULE_MODULE_PATCH | XEX_MODULE_PATCH_DELTA |
XEX_MODULE_PATCH_FULL))) {
int XexModule::ApplyPatch(XexModule* module) {
if (!is_patch()) {
// This isn't a XEX2 patch.
return false;
return 1;
}
// Grab the delta descriptor and get to work.
xex2_opt_delta_patch_descriptor* patch_header = nullptr;
GetOptHeader(header, XEX_HEADER_DELTA_PATCH_DESCRIPTOR,
GetOptHeader(XEX_HEADER_DELTA_PATCH_DESCRIPTOR,
reinterpret_cast<void**>(&patch_header));
assert_not_null(patch_header);
// TODO(benvanik): patching code!
// Compare hash inside delta descriptor to our loaded base XEX
uint8_t digest[0x14];
sha1::SHA1 s;
s.processBytes(module->xex_security_info()->rsa_signature, 0x100);
s.finalize(digest);
return true;
if (memcmp(digest, patch_header->digest_source, 0x14) != 0) {
XELOGW(
"XEX patch signature hash doesn't match base XEX signature hash, patch "
"will likely fail!");
}
uint32_t size = module->xex_header()->header_size;
if (patch_header->delta_headers_source_offset > size) {
XELOGE("XEX header patch source is outside base XEX header area");
return 2;
}
uint32_t header_size_available =
size - patch_header->delta_headers_source_offset;
if (patch_header->delta_headers_source_size > header_size_available) {
XELOGE("XEX header patch source is too large");
return 3;
}
if (patch_header->delta_headers_target_offset >
patch_header->size_of_target_headers) {
XELOGE("XEX header patch target is outside base XEX header area");
return 4;
}
uint32_t delta_target_size = patch_header->size_of_target_headers -
patch_header->delta_headers_target_offset;
if (patch_header->delta_headers_source_size > delta_target_size) {
return 5; // ? unsure what the point of this test is, kernel checks for it
// though
}
// TODO: actually use delta_*_offset / delta_*_size etc
assert_zero(patch_header->delta_headers_source_offset);
assert_zero(patch_header->delta_headers_target_offset);
// Patch base XEX's header
module->xex_header_mem_.resize(patch_header->size_of_target_headers);
uint32_t original_image_size = module->image_size();
uint32_t headerpatch_size = patch_header->info.compressed_len;
auto dest_header = module->xex_header();
int result_code = lzxdelta_apply_patch(
&patch_header->info, headerpatch_size + 0xC, (void*)dest_header);
if (result_code) {
XELOGE("XEX header patch application failed, error code %d", result_code);
return result_code;
}
// Check if we need to alloc new memory for the patched xex
uint32_t new_image_size = module->image_size();
if (new_image_size > original_image_size) {
uint32_t size_delta = new_image_size - original_image_size;
uint32_t addr_new_mem = module->base_address_ + original_image_size;
bool alloc_result =
memory()
->LookupHeap(addr_new_mem)
->AllocFixed(
addr_new_mem, size_delta, 4096,
xe::kMemoryAllocationReserve | xe::kMemoryAllocationCommit,
xe::kMemoryProtectRead | xe::kMemoryProtectWrite);
if (!alloc_result) {
XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", addr_new_mem,
size_delta);
assert_always();
return 6;
}
}
uint8_t orig_session_key[0x10];
memcpy(orig_session_key, module->session_key_, 0x10);
// Header patch updated the base XEX key, need to redecrypt it
aes_decrypt_buffer(
module->is_dev_kit_ ? xe_xex2_devkit_key : xe_xex2_retail_key,
reinterpret_cast<const uint8_t*>(module->xex_security_info()->aes_key),
16, module->session_key_, 16);
// Decrypt the patch XEX's key using base XEX key
aes_decrypt_buffer(
module->session_key_,
reinterpret_cast<const uint8_t*>(xex_security_info()->aes_key), 16,
session_key_, 16);
// Test delta key against our decrypted keys
// (kernel doesn't seem to check this, but it's the one use for the
// image_key_source field I can think of...)
uint8_t test_delta_key[0x10];
aes_decrypt_buffer(module->session_key_, patch_header->image_key_source, 0x10,
test_delta_key, 0x10);
if (memcmp(test_delta_key, orig_session_key, 0x10) != 0) {
XELOGE("XEX patch image key doesn't match original XEX!");
return 7;
}
// Decrypt (if needed).
bool free_input = false;
const uint8_t* exe_buffer = xexp_data_mem_.data();
const size_t exe_length = xexp_data_mem_.size();
const uint8_t* input_buffer = exe_buffer;
switch (opt_file_format_info()->encryption_type) {
case XEX_ENCRYPTION_NONE:
// No-op.
break;
case XEX_ENCRYPTION_NORMAL:
// TODO: a way to do without a copy/alloc?
free_input = true;
input_buffer = (const uint8_t*)calloc(1, exe_length);
aes_decrypt_buffer(session_key_, exe_buffer, exe_length,
(uint8_t*)input_buffer, exe_length);
break;
default:
assert_always();
return 8;
}
// Now loop through each block and apply the delta patches inside
const xex2_compressed_block_info* cur_block =
&opt_file_format_info()->compression_info.normal.first_block;
const uint8_t* p = input_buffer;
uint8_t* base_exe = memory()->TranslateVirtual(module->base_address_);
while (cur_block->block_size) {
const auto* next_block = (const xex2_compressed_block_info*)p;
// Compare block hash, if no match we probably used wrong decrypt key
s.reset();
s.processBytes(p, cur_block->block_size);
s.finalize(digest);
if (memcmp(digest, cur_block->block_hash, 0x14) != 0) {
result_code = 9;
XELOGE("XEX patch block hash doesn't match hash inside block info!");
break;
}
// skip block info
p += 20;
p += 4;
uint32_t block_data_size = cur_block->block_size - 20 - 4;
result_code =
lzxdelta_apply_patch((xex2_delta_patch*)p, block_data_size, base_exe);
if (result_code) {
break;
}
p += block_data_size;
cur_block = next_block;
}
if (!result_code) {
// byteswap versions because of bitfields...
xex2_version source_ver, target_ver;
source_ver.value =
xe::byte_swap<uint32_t>(patch_header->source_version.value);
target_ver.value =
xe::byte_swap<uint32_t>(patch_header->target_version.value);
XELOGI(
"XEX patch applied successfully: base version: %d.%d.%d.%d, new "
"version: %d.%d.%d.%d",
source_ver.major, source_ver.minor, source_ver.build, source_ver.qfe,
target_ver.major, target_ver.minor, target_ver.build, target_ver.qfe);
} else {
XELOGE("XEX patch application failed, error code %d", result_code);
}
if (free_input) {
free((void*)input_buffer);
}
return result_code;
}
void XexModule::DecryptSessionKey(bool useDevkit) {
static const uint8_t xe_xex2_retail_key[16] = {
0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3,
0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91};
static const uint8_t xe_xex2_devkit_key[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t* xexkey = useDevkit ? xe_xex2_devkit_key : xe_xex2_retail_key;
// Decrypt the header key.
uint32_t rk[4 * (MAXNR + 1)];
int32_t Nr = rijndaelKeySetupDec(rk, xexkey, 128);
rijndaelDecrypt(rk, Nr,
reinterpret_cast<const u8*>(xex_security_info()->aes_key),
session_key_);
}
int XexModule::ReadImage(const void* xex_addr, size_t xex_length) {
int XexModule::ReadImage(const void* xex_addr, size_t xex_length,
bool use_dev_key) {
if (!opt_file_format_info()) {
return 1;
}
auto* ff = opt_file_format_info();
is_dev_kit_ = use_dev_key;
if (is_patch()) {
// Make a copy of patch data for other XEX's to use with ApplyPatch()
const uint32_t data_len =
static_cast<uint32_t>(xex_length - xex_header()->header_size);
xexp_data_mem_.resize(data_len);
std::memcpy(xexp_data_mem_.data(),
(uint8_t*)xex_addr + xex_header()->header_size, data_len);
return 0;
}
memory()->LookupHeap(base_address_)->Reset();
aes_decrypt_buffer(
use_dev_key ? xe_xex2_devkit_key : xe_xex2_retail_key,
reinterpret_cast<const uint8_t*>(xex_security_info()->aes_key), 16,
session_key_, 16);
int result_code = 0;
switch (opt_file_format_info()->compression_type) {
case XEX_COMPRESSION_NONE:
return ReadImageUncompressed(xex_addr, xex_length);
result_code = ReadImageUncompressed(xex_addr, xex_length);
break;
case XEX_COMPRESSION_BASIC:
return ReadImageBasicCompressed(xex_addr, xex_length);
result_code = ReadImageBasicCompressed(xex_addr, xex_length);
break;
case XEX_COMPRESSION_NORMAL:
case XEX_COMPRESSION_DELTA:
return ReadImageCompressed(xex_addr, xex_length);
result_code = ReadImageCompressed(xex_addr, xex_length);
break;
default:
assert_always();
return 1;
return 2;
}
if (result_code) {
return result_code;
}
if (is_patch() || is_valid_executable()) {
return 0;
}
// Not a patch and image doesn't have proper PE header, return 3
return 3;
}
int XexModule::ReadImageUncompressed(const void* xex_addr, size_t xex_length) {
@ -344,7 +643,8 @@ int XexModule::ReadImageUncompressed(const void* xex_addr, size_t xex_length) {
memcpy(buffer, p, exe_length);
return 0;
case XEX_ENCRYPTION_NORMAL:
DecryptBuffer(session_key_, p, exe_length, buffer, uncompressed_size);
aes_decrypt_buffer(session_key_, p, exe_length, buffer,
uncompressed_size);
return 0;
default:
assert_always();
@ -461,13 +761,7 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) {
uint8_t* compress_buffer = NULL;
const uint8_t* p = NULL;
uint8_t* d = NULL;
uint8_t* deblock_buffer = NULL;
// size_t block_size = 0;
uint32_t uncompressed_size = 0;
struct mspack_system* sys = NULL;
mspack_memory_file* lzxsrc = NULL;
mspack_memory_file* lzxdst = NULL;
struct lzxd_stream* lzxd = NULL;
sha1::SHA1 s;
// Decrypt (if needed).
@ -483,8 +777,8 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) {
// TODO: a way to do without a copy/alloc?
free_input = true;
input_buffer = (const uint8_t*)calloc(1, exe_length);
DecryptBuffer(session_key_, exe_buffer, exe_length,
(uint8_t*)input_buffer, exe_length);
aes_decrypt_buffer(session_key_, exe_buffer, exe_length,
(uint8_t*)input_buffer, exe_length);
break;
default:
assert_always();
@ -501,8 +795,6 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) {
d = compress_buffer;
// De-block.
deblock_buffer = (uint8_t*)calloc(1, input_size);
int result_code = 0;
uint8_t block_calced_digest[0x14];
@ -519,8 +811,9 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) {
break;
}
// skip block info
p += 4;
p += 20; // skip 20b hash
p += 20;
while (true) {
const size_t chunk_size = (p[0] << 8) | p[1];
@ -552,29 +845,12 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) {
if (alloc_result) {
uint8_t* buffer = memory()->TranslateVirtual(base_address_);
// Reset buffer if this isn't a patch
std::memset(buffer, 0, uncompressed_size);
// Setup decompressor and decompress.
uint32_t window_size = compression_info->normal.window_size;
uint32_t window_bits = 0;
for (size_t m = 0; m < 32; m++, window_bits++) {
window_size >>= 1;
if (window_size == 0x00000000) {
break;
}
}
sys = mspack_memory_sys_create();
lzxsrc =
mspack_memory_open(sys, (void*)compress_buffer, d - compress_buffer);
lzxdst = mspack_memory_open(sys, buffer, uncompressed_size);
lzxd = lzxd_init(sys, (struct mspack_file*)lzxsrc,
(struct mspack_file*)lzxdst, window_bits, 0, 32768,
(off_t)xex_security_info()->image_size);
result_code =
lzxd_decompress(lzxd, (off_t)xex_security_info()->image_size);
// Decompress into XEX base
result_code = lzx_decompress(
compress_buffer, d - compress_buffer, buffer, uncompressed_size,
compression_info->normal.window_size, nullptr, 0);
} else {
XELOGE("Unable to allocate XEX memory at %.8X-%.8X.", base_address_,
uncompressed_size);
@ -582,28 +858,9 @@ int XexModule::ReadImageCompressed(const void* xex_addr, size_t xex_length) {
}
}
if (lzxd) {
lzxd_free(lzxd);
lzxd = NULL;
}
if (lzxsrc) {
mspack_memory_close(lzxsrc);
lzxsrc = NULL;
}
if (lzxdst) {
mspack_memory_close(lzxdst);
lzxdst = NULL;
}
if (sys) {
mspack_memory_sys_destroy(sys);
sys = NULL;
}
if (compress_buffer) {
free((void*)compress_buffer);
}
if (deblock_buffer) {
free((void*)deblock_buffer);
}
if (free_input) {
free((void*)input_buffer);
}
@ -694,7 +951,6 @@ int XexModule::ReadPEHeaders() {
section.size = sechdr->Misc.VirtualSize;
section.flags = sechdr->Characteristics;
pe_sections_.push_back(section);
// pe_sections_.push_back(section);
}
// DumpTLSDirectory(pImageBase, pNTHeader, (PIMAGE_TLS_DIRECTORY32)0);
@ -721,7 +977,7 @@ bool XexModule::Load(const std::string& name, const std::string& path,
// Try setting our base_address based on XEX_HEADER_IMAGE_BASE_ADDRESS, fall
// back to xex_security_info otherwise
base_address_ = sec_header->load_address;
base_address_ = xex_security_info()->load_address;
xe::be<uint32_t>* base_addr_opt = nullptr;
if (GetOptHeader(XEX_HEADER_IMAGE_BASE_ADDRESS, &base_addr_opt))
base_address_ = *base_addr_opt;
@ -734,24 +990,37 @@ bool XexModule::Load(const std::string& name, const std::string& path,
// Load in the XEX basefile
// We'll try using both XEX2 keys to see if any give a valid PE
while (true) {
memory()->LookupHeap(base_address_)->Reset();
int result_code = ReadImage(xex_addr, xex_length, false);
if (result_code) {
XELOGW("XEX load failed with code %d, trying with devkit encryption key...",
result_code);
DecryptSessionKey(is_dev_kit_);
if (!ReadImage(xex_addr, xex_length) && !ReadPEHeaders()) {
break;
}
is_dev_kit_ = !is_dev_kit_;
// is_dev_kit starts as false, then flips to true if load failed, if it's
// back to false again this must be invalid
if (!is_dev_kit_) {
result_code = ReadImage(xex_addr, xex_length, true);
if (result_code) {
XELOGE("XEX load failed with code %d, tried both encryption keys",
result_code);
return false;
}
}
XELOGW("XEX load failed, trying with devkit encryption key...");
// Note: caller will have to call LoadContinue once it's determined whether a
// patch file exists or not!
return true;
}
bool XexModule::LoadContinue() {
// Second part of image load
// Split from Load() so that we can patch the XEX before loading this data
assert_false(finished_load_);
if (finished_load_) {
return true;
}
finished_load_ = true;
if (ReadPEHeaders()) {
XELOGE("Failed to load XEX PE headers!");
return false;
}
// Scan and find the low/high addresses.
@ -762,6 +1031,7 @@ bool XexModule::Load(const std::string& name, const std::string& path,
low_address_ = UINT_MAX;
high_address_ = 0;
auto sec_header = xex_security_info();
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
// Byteswap the bitfield manually.
xex2_page_descriptor desc;
@ -863,10 +1133,13 @@ bool XexModule::Unload() {
}
loaded_ = false;
// Just deallocate the memory occupied by the exe
assert_not_zero(base_address_);
// If this isn't a patch, just deallocate the memory occupied by the exe
if (!is_patch()) {
assert_not_zero(base_address_);
memory()->LookupHeap(base_address_)->Release(base_address_);
}
memory()->LookupHeap(base_address_)->Release(base_address_);
xex_header_mem_.resize(0);
return true;
@ -1233,7 +1506,8 @@ bool XexModule::FindSaveRest() {
auto page_size = base_address_ <= 0x90000000 ? 64 * 1024 : 4 * 1024;
auto sec_header = xex_security_info();
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
const xex2_page_descriptor* section = &sec_header->page_descriptors[i];
const xex2_page_descriptor* section =
&xex_security_info()->page_descriptors[i];
const auto start_address = base_address_ + (page * page_size);
const auto end_address = start_address + (section->size * page_size);

View File

@ -52,8 +52,24 @@ class XexModule : public xe::cpu::Module {
return reinterpret_cast<const xex2_header*>(xex_header_mem_.data());
}
const xex2_security_info* xex_security_info() const {
return reinterpret_cast<const xex2_security_info*>(
uintptr_t(xex_header()) + xex_header()->security_offset);
return GetSecurityInfo(xex_header());
}
uint32_t image_size() const {
assert_not_zero(base_address_);
// Calculate the new total size of the XEX image from its headers.
auto heap = memory()->LookupHeap(base_address_);
uint32_t total_size = 0;
for (uint32_t i = 0; i < xex_security_info()->page_descriptor_count; i++) {
// Byteswap the bitfield manually.
xex2_page_descriptor desc;
desc.value =
xe::byte_swap(xex_security_info()->page_descriptors[i].value);
total_size += desc.size * heap->page_size();
}
return total_size;
}
const std::vector<ImportLibrary>* import_libraries() const {
@ -73,6 +89,7 @@ class XexModule : public xe::cpu::Module {
}
const uint32_t base_address() const { return base_address_; }
const bool is_dev_kit() const { return is_dev_kit_; }
// Gets an optional header. Returns NULL if not found.
// Special case: if key & 0xFF == 0x00, this function will return the value,
@ -95,42 +112,50 @@ class XexModule : public xe::cpu::Module {
return GetOptHeader(key, reinterpret_cast<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;
bool ApplyPatch(XexModule* module);
int ApplyPatch(XexModule* module);
bool Load(const std::string& name, const std::string& path,
const void* xex_addr, size_t xex_length);
bool LoadContinue();
bool Unload();
bool ContainsAddress(uint32_t address) override;
const std::string& name() const override { return name_; }
bool is_executable() const override {
return (xex_header()->module_flags & XEX_MODULE_TITLE) != 0;
}
bool ContainsAddress(uint32_t address) override;
bool is_valid_executable() const {
assert_not_zero(base_address_);
if (!base_address_) {
return false;
}
uint8_t* buffer = memory()->TranslateVirtual(base_address_);
return *(uint32_t*)buffer == 0x905A4D;
}
static void DecryptBuffer(const uint8_t* session_key,
const uint8_t* input_buffer,
const size_t input_size, uint8_t* output_buffer,
const size_t output_size);
uint8_t* HostData() {
if (base_address_)
return memory()->TranslateVirtual(base_address_);
else
return nullptr;
bool is_patch() const {
assert_not_null(xex_header());
if (!xex_header()) {
return false;
}
return (xex_header()->module_flags &
(XEX_MODULE_MODULE_PATCH | XEX_MODULE_PATCH_DELTA |
XEX_MODULE_PATCH_FULL));
}
protected:
std::unique_ptr<Function> CreateFunction(uint32_t address) override;
private:
void DecryptSessionKey(bool useDevkit = false);
int ReadImage(const void* xex_addr, size_t xex_length);
int ReadImage(const void* xex_addr, size_t xex_length, bool use_dev_key);
int ReadImageUncompressed(const void* xex_addr, size_t xex_length);
int ReadImageBasicCompressed(const void* xex_addr, size_t xex_length);
int ReadImageCompressed(const void* xex_addr, size_t xex_length);
@ -146,22 +171,21 @@ class XexModule : public xe::cpu::Module {
std::string name_;
std::string path_;
std::vector<uint8_t> xex_header_mem_; // Holds the xex header
std::vector<uint8_t> xexp_data_mem_; // Holds XEXP patch data
// 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 is_dev_kit_ = false;
bool loaded_ = false; // Loaded into memory?
bool loaded_ = false; // Loaded into memory?
bool finished_load_ = false; // PE/imports/symbols/etc all loaded?
uint32_t base_address_ = 0;
uint32_t low_address_ = 0;
uint32_t high_address_ = 0;
bool is_dev_kit_ = false;
};
} // namespace cpu

View File

@ -95,7 +95,36 @@ X_STATUS UserModule::LoadFromFile(std::string path) {
file->Destroy();
}
return result;
// Only XEX returns X_STATUS_PENDING
if (result != X_STATUS_PENDING) {
return result;
}
// Search for xexp patch file
auto patch_entry = kernel_state()->file_system()->ResolvePath(path_ + "p");
if (patch_entry) {
auto patch_path = patch_entry->absolute_path();
XELOGI("Loading XEX patch from %s", patch_path.c_str());
auto patch_module = object_ref<UserModule>(new UserModule(kernel_state_));
result = patch_module->LoadFromFile(patch_path);
if (!result) {
result = patch_module->xex_module()->ApplyPatch(xex_module());
if (result) {
XELOGE("Failed to apply XEX patch, code: %d", result);
}
} else {
XELOGE("Failed to load XEX patch, code: %d", result);
}
if (result) {
return X_STATUS_UNSUCCESSFUL;
}
}
return LoadXexContinue();
}
X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) {
@ -130,29 +159,13 @@ X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) {
return X_STATUS_UNSUCCESSFUL;
}
// Copy the xex2 header into guest memory.
auto header = this->xex_module()->xex_header();
auto security_header = this->xex_module()->xex_security_info();
guest_xex_header_ = memory()->SystemHeapAlloc(header->header_size);
// Only XEX headers + image are loaded right now
// Caller will have to call LoadXexContinue after they've loaded in a patch
// (or after patch isn't found anywhere)
// or if this is an XEXP being loaded return success since there's nothing
// else to load
return this->xex_module()->is_patch() ? X_STATUS_SUCCESS : X_STATUS_PENDING;
uint8_t* xex_header_ptr = memory()->TranslateVirtual(guest_xex_header_);
std::memcpy(xex_header_ptr, header, header->header_size);
// Cache some commonly used headers...
this->xex_module()->GetOptHeader(XEX_HEADER_ENTRY_POINT, &entry_point_);
this->xex_module()->GetOptHeader(XEX_HEADER_DEFAULT_STACK_SIZE,
&stack_size_);
is_dll_module_ = !!(header->module_flags & XEX_MODULE_DLL_MODULE);
// Setup the loader data entry
auto ldr_data =
memory()->TranslateVirtual<X_LDR_DATA_TABLE_ENTRY*>(hmodule_ptr_);
ldr_data->dll_base = 0; // GetProcAddress will read this.
ldr_data->xex_header_base = guest_xex_header_;
ldr_data->full_image_size = security_header->image_size;
ldr_data->image_base = this->xex_module()->base_address();
ldr_data->entry_point = entry_point_;
} else if (module_format_ == kModuleFormatElf) {
auto elf_module =
std::make_unique<cpu::ElfModule>(processor, kernel_state());
@ -175,6 +188,52 @@ X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) {
return X_STATUS_SUCCESS;
}
X_STATUS UserModule::LoadXexContinue() {
// LoadXexContinue: finishes loading XEX after a patch has been applied (or
// patch wasn't found)
if (!this->xex_module()) {
return X_STATUS_UNSUCCESSFUL;
}
// If guest_xex_header is set we must have already loaded the XEX
if (guest_xex_header_) {
return X_STATUS_SUCCESS;
}
// Finish XexModule load (PE sections/imports/symbols...)
if (!xex_module()->LoadContinue()) {
return X_STATUS_UNSUCCESSFUL;
}
// Copy the xex2 header into guest memory.
auto header = this->xex_module()->xex_header();
auto security_header = this->xex_module()->xex_security_info();
guest_xex_header_ = memory()->SystemHeapAlloc(header->header_size);
uint8_t* xex_header_ptr = memory()->TranslateVirtual(guest_xex_header_);
std::memcpy(xex_header_ptr, header, header->header_size);
// Cache some commonly used headers...
this->xex_module()->GetOptHeader(XEX_HEADER_ENTRY_POINT, &entry_point_);
this->xex_module()->GetOptHeader(XEX_HEADER_DEFAULT_STACK_SIZE, &stack_size_);
is_dll_module_ = !!(header->module_flags & XEX_MODULE_DLL_MODULE);
// Setup the loader data entry
auto ldr_data =
memory()->TranslateVirtual<X_LDR_DATA_TABLE_ENTRY*>(hmodule_ptr_);
ldr_data->dll_base = 0; // GetProcAddress will read this.
ldr_data->xex_header_base = guest_xex_header_;
ldr_data->full_image_size = security_header->image_size;
ldr_data->image_base = this->xex_module()->base_address();
ldr_data->entry_point = entry_point_;
OnLoad();
return X_STATUS_SUCCESS;
}
X_STATUS UserModule::Unload() {
if (module_format_ == kModuleFormatXex &&
(!processor_module_ || !xex_module()->loaded())) {

View File

@ -92,6 +92,8 @@ class UserModule : public XModule {
ByteStream* stream, std::string path);
private:
X_STATUS LoadXexContinue();
uint32_t guest_xex_header_ = 0;
ModuleFormat module_format_ = kModuleFormatUndefined;

View File

@ -436,12 +436,20 @@ struct xex2_opt_resource_info {
xex2_resource resources[1]; // 0x4
};
struct xex2_delta_patch {
xe::be<uint32_t> old_addr;
xe::be<uint32_t> new_addr;
xe::be<uint16_t> uncompressed_len;
xe::be<uint16_t> compressed_len;
char patch_data[1];
};
struct xex2_opt_delta_patch_descriptor {
xe::be<uint32_t> size; // 0x0
xex2_version target_version; // 0x4
xex2_version source_version; // 0x8
char digest_source[0x14]; // 0xC
char image_key_source[0x10]; // 0x20
uint8_t digest_source[0x14]; // 0xC
uint8_t image_key_source[0x10]; // 0x20
xe::be<uint32_t> size_of_target_headers; // 0x30
xe::be<uint32_t> delta_headers_source_offset; // 0x34
xe::be<uint32_t> delta_headers_source_size; // 0x38
@ -449,9 +457,8 @@ struct xex2_opt_delta_patch_descriptor {
xe::be<uint32_t> delta_image_source_offset; // 0x40
xe::be<uint32_t> delta_image_source_size; // 0x44
xe::be<uint32_t> delta_image_target_offset; // 0x48
char delta_header_patch_data[1];
xex2_delta_patch info; // 0x4C
};
// static_assert_size(xex2_opt_delta_patch_descriptor, 0x4D);
struct xex2_opt_execution_info {
xe::be<uint32_t> media_id; // 0x0