- Finish XEX_HEADER_XBOX360_LOGO, XEX_HEADER_FILE_FORMAT_INFO, XEX_HEADER_DELTA_PATCH_DESCRIPTOR, XEX_HEADER_SYSTEM_FLAGS_32, XEX_HEADER_ENABLED_FOR_CALLCAP, and XEX_HEADER_GAME_RATINGS - Add missing xex2_header_keys - Add flag name outputs - Removed magic number - Added X_FILE_CHARACTERISTICS to X_FILE_FS_DEVICE_INFORMATION
1165 lines
44 KiB
C++
1165 lines
44 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2020 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/kernel/user_module.h"
|
|
|
|
#include "xenia/base/byte_stream.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/xxhash.h"
|
|
#include "xenia/cpu/elf_module.h"
|
|
#include "xenia/emulator.h"
|
|
|
|
namespace xe {
|
|
namespace kernel {
|
|
|
|
UserModule::UserModule(KernelState* kernel_state)
|
|
: XModule(kernel_state, ModuleType::kUserModule) {}
|
|
|
|
UserModule::~UserModule() { Unload(); }
|
|
|
|
uint32_t UserModule::title_id() const {
|
|
if (module_format_ != kModuleFormatXex) {
|
|
return 0;
|
|
}
|
|
|
|
xex2_opt_execution_info* opt_exec_info = nullptr;
|
|
if (xex_module()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &opt_exec_info)) {
|
|
return static_cast<uint32_t>(opt_exec_info->title_id);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::string UserModule::bounding_filename() const {
|
|
std::string bounding_filename = "";
|
|
|
|
if (module_format_ != kModuleFormatXex) {
|
|
return bounding_filename;
|
|
}
|
|
|
|
xex2_opt_bound_path* bounding_path = nullptr;
|
|
if (xex_module()->GetOptHeader(XEX_HEADER_BOUNDING_PATH, &bounding_path)) {
|
|
bounding_filename =
|
|
utf8::find_base_name_from_guest_path(std::string(bounding_path->path));
|
|
}
|
|
|
|
return bounding_filename;
|
|
}
|
|
|
|
uint32_t UserModule::disc_number() const {
|
|
if (module_format_ != kModuleFormatXex) {
|
|
return 1;
|
|
}
|
|
|
|
xex2_opt_execution_info* opt_exec_info = nullptr;
|
|
if (xex_module()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &opt_exec_info)) {
|
|
return static_cast<uint32_t>(opt_exec_info->disc_number);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
bool UserModule::is_multi_disc_title() const {
|
|
if (module_format_ != kModuleFormatXex) {
|
|
return false;
|
|
}
|
|
|
|
xex2_opt_execution_info* opt_exec_info = nullptr;
|
|
if (xex_module()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &opt_exec_info)) {
|
|
return opt_exec_info->disc_count > 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
X_STATUS UserModule::LoadFromFile(const std::string_view path) {
|
|
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
|
|
|
// Resolve the file to open.
|
|
// TODO(benvanik): make this code shared?
|
|
auto fs_entry = kernel_state()->file_system()->ResolvePath(path);
|
|
if (!fs_entry) {
|
|
XELOGE("File not found: {}", path);
|
|
return X_STATUS_NO_SUCH_FILE;
|
|
}
|
|
|
|
path_ = fs_entry->absolute_path();
|
|
name_ = utf8::find_base_name_from_guest_path(path_);
|
|
|
|
// If the FS supports mapping, map the file in and load from that.
|
|
if (fs_entry->can_map()) {
|
|
// Map.
|
|
auto mmap = fs_entry->OpenMapped(MappedMemory::Mode::kRead);
|
|
if (!mmap) {
|
|
return result;
|
|
}
|
|
|
|
// Load the module.
|
|
result = LoadFromMemory(mmap->data(), mmap->size());
|
|
} else {
|
|
std::vector<uint8_t> buffer(fs_entry->size());
|
|
|
|
// Open file for reading.
|
|
vfs::File* file = nullptr;
|
|
result = fs_entry->Open(vfs::FileAccess::kGenericRead, &file);
|
|
if (XFAILED(result)) {
|
|
return result;
|
|
}
|
|
|
|
// Read entire file into memory.
|
|
// Ugh.
|
|
size_t bytes_read = 0;
|
|
result = file->ReadSync(std::span<uint8_t>(buffer.data(), buffer.size()), 0,
|
|
&bytes_read);
|
|
if (XFAILED(result)) {
|
|
return result;
|
|
}
|
|
|
|
// Load the module.
|
|
result = LoadFromMemory(buffer.data(), bytes_read);
|
|
|
|
// Close the file.
|
|
file->Destroy();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) {
|
|
auto processor = kernel_state()->processor();
|
|
|
|
be<fourcc_t> magic;
|
|
magic.value = xe::load<fourcc_t>(addr);
|
|
if (magic == xe::cpu::kXEX2Signature || magic == xe::cpu::kXEX1Signature) {
|
|
module_format_ = kModuleFormatXex;
|
|
} else if (magic == xe::cpu::kElfSignature) {
|
|
module_format_ = kModuleFormatElf;
|
|
} else {
|
|
uint8_t M = xe::load<uint8_t>(addr);
|
|
uint8_t Z = xe::load<uint8_t>(reinterpret_cast<void*>(
|
|
reinterpret_cast<uint64_t>(addr) + sizeof(uint8_t)));
|
|
|
|
magic = make_fourcc(M, Z, 0, 0);
|
|
if (magic == kEXESignature) {
|
|
XELOGE("XNA executables are not yet implemented");
|
|
return X_STATUS_NOT_IMPLEMENTED;
|
|
} else {
|
|
XELOGE("Unknown module magic: {:08X}", magic.get());
|
|
return X_STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
}
|
|
|
|
if (module_format_ == kModuleFormatXex) {
|
|
// Prepare the module for execution.
|
|
// Runtime takes ownership.
|
|
auto xex_module =
|
|
std::make_unique<cpu::XexModule>(processor, kernel_state());
|
|
if (!xex_module->Load(name_, path_, addr, length)) {
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
}
|
|
processor_module_ = xex_module.get();
|
|
if (!processor->AddModule(std::move(xex_module))) {
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
// 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;
|
|
|
|
} else if (module_format_ == kModuleFormatElf) {
|
|
auto elf_module =
|
|
std::make_unique<cpu::ElfModule>(processor, kernel_state());
|
|
if (!elf_module->Load(name_, path_, addr, length)) {
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
entry_point_ = elf_module->entry_point();
|
|
stack_size_ = 1024 * 1024; // 1 MB
|
|
is_dll_module_ = false; // Hardcoded not a DLL (for now)
|
|
|
|
processor_module_ = elf_module.get();
|
|
if (!processor->AddModule(std::move(elf_module))) {
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
OnLoad();
|
|
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
X_STATUS UserModule::LoadFromMemoryNamed(const std::string_view raw_name,
|
|
const void* addr,
|
|
const size_t length) {
|
|
name_ = std::string(raw_name);
|
|
return LoadFromMemory(addr, length);
|
|
}
|
|
|
|
X_STATUS UserModule::LoadContinue() {
|
|
// 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_);
|
|
|
|
xe::be<uint32_t>* ws_size = 0;
|
|
this->xex_module()->GetOptHeader(XEX_HEADER_TITLE_WORKSPACE_SIZE, &ws_size);
|
|
// ToDo: Find better way to handle default and mimimal values!
|
|
if (ws_size && *ws_size) {
|
|
workspace_size_ = std::max(ws_size->get(), uint32_t(256 * 1024));
|
|
}
|
|
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())) {
|
|
// Quick abort.
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
if (module_format_ == kModuleFormatXex && processor_module_ &&
|
|
xex_module()->Unload()) {
|
|
OnUnload();
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
uint32_t UserModule::GetProcAddressByOrdinal(uint16_t ordinal) {
|
|
return xex_module()->GetProcAddress(ordinal);
|
|
}
|
|
|
|
uint32_t UserModule::GetProcAddressByName(std::string_view name) {
|
|
return xex_module()->GetProcAddress(name);
|
|
}
|
|
|
|
X_STATUS UserModule::GetSection(const std::string_view name,
|
|
uint32_t* out_section_data,
|
|
uint32_t* out_section_size) {
|
|
xex2_opt_resource_info* resource_header = nullptr;
|
|
if (!cpu::XexModule::GetOptHeader(xex_header(), XEX_HEADER_RESOURCE_INFO,
|
|
&resource_header)) {
|
|
// No resources.
|
|
return X_STATUS_NOT_FOUND;
|
|
}
|
|
uint32_t count = (resource_header->size - 4) / sizeof(xex2_resource);
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
auto& res = resource_header->resources[i];
|
|
if (utf8::equal_z(name, std::string_view(res.name, 8))) {
|
|
// Found!
|
|
*out_section_data = res.address;
|
|
*out_section_size = res.size;
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return X_STATUS_NOT_FOUND;
|
|
}
|
|
|
|
X_STATUS UserModule::GetOptHeader(xex2_header_keys key, void** out_ptr) {
|
|
assert_not_null(out_ptr);
|
|
|
|
if (module_format_ == kModuleFormatElf) {
|
|
// Quick die.
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
bool ret = xex_module()->GetOptHeader(key, out_ptr);
|
|
if (!ret) {
|
|
return X_STATUS_NOT_FOUND;
|
|
}
|
|
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
X_STATUS UserModule::GetOptHeader(xex2_header_keys key,
|
|
uint32_t* out_header_guest_ptr) {
|
|
if (module_format_ == kModuleFormatElf) {
|
|
// Quick die.
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
auto header =
|
|
memory()->TranslateVirtual<const xex2_header*>(guest_xex_header_);
|
|
if (!header) {
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
}
|
|
return GetOptHeader(memory(), header, key, out_header_guest_ptr);
|
|
}
|
|
|
|
X_STATUS UserModule::GetOptHeader(const Memory* memory,
|
|
const xex2_header* header,
|
|
xex2_header_keys key,
|
|
uint32_t* out_header_guest_ptr) {
|
|
assert_not_null(out_header_guest_ptr);
|
|
uint32_t field_value = 0;
|
|
bool field_found = false;
|
|
for (uint32_t i = 0; i < header->header_count; i++) {
|
|
auto& opt_header = header->headers[i];
|
|
if (opt_header.key != key) {
|
|
continue;
|
|
}
|
|
field_found = true;
|
|
switch (opt_header.key & 0xFF) {
|
|
case 0x00:
|
|
// Return data stored in header value.
|
|
field_value = opt_header.value;
|
|
break;
|
|
case 0x01:
|
|
// Return pointer to data stored in header value.
|
|
field_value = memory->HostToGuestVirtual(&opt_header.value);
|
|
break;
|
|
default:
|
|
// Data stored at offset to header.
|
|
field_value = memory->HostToGuestVirtual(header) + opt_header.offset;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
*out_header_guest_ptr = field_value;
|
|
if (!field_found) {
|
|
return X_STATUS_NOT_FOUND;
|
|
}
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
bool UserModule::Save(ByteStream* stream) {
|
|
if (!XModule::Save(stream)) {
|
|
return false;
|
|
}
|
|
|
|
// A lot of the information stored on this class can be reconstructed at
|
|
// runtime.
|
|
|
|
return true;
|
|
}
|
|
|
|
object_ref<UserModule> UserModule::Restore(KernelState* kernel_state,
|
|
ByteStream* stream,
|
|
const std::string_view path) {
|
|
auto module = new UserModule(kernel_state);
|
|
|
|
// XModule::Save took care of this earlier...
|
|
// TODO: Find a nicer way to represent that here.
|
|
if (!module->RestoreObject(stream)) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto result = module->LoadFromFile(path);
|
|
if (XFAILED(result)) {
|
|
XELOGD("UserModule::Restore LoadFromFile({}) FAILED - code {:08X}", path,
|
|
result);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!kernel_state->RegisterUserModule(retain_object(module))) {
|
|
// Already loaded?
|
|
assert_always();
|
|
}
|
|
|
|
return object_ref<UserModule>(module);
|
|
}
|
|
|
|
void UserModule::Dump() {
|
|
if (module_format_ == kModuleFormatElf) {
|
|
// Quick die.
|
|
return;
|
|
}
|
|
|
|
StringBuffer sb;
|
|
|
|
xe::cpu::ExportResolver* export_resolver =
|
|
kernel_state_->emulator()->export_resolver();
|
|
auto header = xex_header();
|
|
|
|
CalculateHash();
|
|
|
|
// XEX header.
|
|
sb.AppendFormat("Module {}:\n", path_);
|
|
|
|
sb.AppendFormat("Module Hash: {:016X}\n", hash_.value_or(UINT64_MAX));
|
|
|
|
sb.AppendFormat(" Module Flags: {:08X}\n", (uint32_t)header->module_flags);
|
|
for (const auto& entry : xex2_module_flags_map) {
|
|
if (header->module_flags & entry.first) {
|
|
sb.AppendFormat(" {}\n", entry.second);
|
|
}
|
|
}
|
|
|
|
// Security header
|
|
auto security_info = xex_module()->xex_security_info();
|
|
sb.Append("Security Header:\n");
|
|
sb.AppendFormat(" Image Flags: {:08X}\n",
|
|
(uint32_t)security_info->image_flags);
|
|
for (const auto& entry : xex2_image_flags_map) {
|
|
if (security_info->image_flags & entry.first) {
|
|
sb.AppendFormat(" {}\n", entry.second);
|
|
}
|
|
}
|
|
sb.AppendFormat(" Load Address: {:08X}\n",
|
|
(uint32_t)security_info->load_address);
|
|
sb.AppendFormat(" Image Size: {:08X}\n",
|
|
(uint32_t)security_info->image_size);
|
|
sb.AppendFormat(" Export Table: {:08X}\n",
|
|
(uint32_t)security_info->export_table);
|
|
|
|
// Optional headers
|
|
sb.AppendFormat("Optional Header Count: {}\n",
|
|
(uint32_t)header->header_count);
|
|
|
|
for (uint32_t i = 0; i < header->header_count; i++) {
|
|
auto& opt_header = header->headers[i];
|
|
|
|
// Stash a pointer (although this isn't used in every case)
|
|
auto opt_header_ptr =
|
|
reinterpret_cast<const uint8_t*>(header) + opt_header.offset;
|
|
switch (opt_header.key) {
|
|
case XEX_HEADER_RESOURCE_INFO: {
|
|
sb.Append(" XEX_HEADER_RESOURCE_INFO:\n");
|
|
auto opt_resource_info =
|
|
reinterpret_cast<const xex2_opt_resource_info*>(opt_header_ptr);
|
|
|
|
uint32_t count = (opt_resource_info->size - 4) / 16;
|
|
for (uint32_t j = 0; j < count; j++) {
|
|
auto& res = opt_resource_info->resources[j];
|
|
|
|
// Manually NULL-terminate the name.
|
|
char name[9];
|
|
std::memcpy(name, res.name, sizeof(res.name));
|
|
name[8] = 0;
|
|
|
|
sb.AppendFormat(
|
|
" {:<8} {:08X}-{:08X}, {}b\n", name, (uint32_t)res.address,
|
|
(uint32_t)res.address + (uint32_t)res.size, (uint32_t)res.size);
|
|
}
|
|
} break;
|
|
case XEX_HEADER_FILE_FORMAT_INFO: {
|
|
sb.Append(" XEX_HEADER_FILE_FORMAT_INFO:\n");
|
|
auto opt_file_format_info =
|
|
reinterpret_cast<const xex2_opt_file_format_info*>(opt_header_ptr);
|
|
|
|
sb.AppendFormat(" Info Size: {}\n",
|
|
static_cast<uint32_t>(opt_file_format_info->info_size));
|
|
const std::string encryption =
|
|
xex2_encryption_type_map.at(opt_file_format_info->encryption_type);
|
|
sb.AppendFormat(" Encryption Type: {}\n", encryption);
|
|
const std::string compression = xex2_compression_type_map.at(
|
|
opt_file_format_info->compression_type);
|
|
sb.AppendFormat(" Compression Type: {}\n", compression);
|
|
if (opt_file_format_info->compression_type == 1) {
|
|
sb.AppendFormat(
|
|
" Data Size: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_file_format_info->compression_info.basic.blocks[0]
|
|
.data_size));
|
|
sb.AppendFormat(
|
|
" Zero Size: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_file_format_info->compression_info.basic.blocks[0]
|
|
.zero_size));
|
|
} else if (opt_file_format_info->compression_type == 2) {
|
|
sb.AppendFormat(
|
|
" Window Size: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_file_format_info->compression_info.normal.window_size));
|
|
sb.AppendFormat(
|
|
" Block Size: {}\n",
|
|
static_cast<uint32_t>(opt_file_format_info->compression_info
|
|
.normal.first_block.block_size));
|
|
sb.Append(" Block Hash:");
|
|
for (int l = 0; l < 0x20; l++) {
|
|
sb.AppendFormat(" {:02X}", opt_file_format_info->compression_info
|
|
.normal.first_block.block_hash[l]);
|
|
}
|
|
sb.Append("\n");
|
|
} else {
|
|
sb.AppendFormat(" Compression Info: Not Implemented\n");
|
|
}
|
|
} break;
|
|
case XEX_HEADER_DELTA_PATCH_DESCRIPTOR: {
|
|
sb.Append(" XEX_HEADER_DELTA_PATCH_DESCRIPTOR:\n");
|
|
auto opt_delta_patch_descriptor =
|
|
reinterpret_cast<const xex2_opt_delta_patch_descriptor*>(
|
|
opt_header_ptr);
|
|
|
|
sb.AppendFormat(
|
|
" Size: {}\n",
|
|
static_cast<uint32_t>(opt_delta_patch_descriptor->size));
|
|
sb.AppendFormat(" Target Version: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_delta_patch_descriptor->target_version_value));
|
|
sb.AppendFormat(" Source Version: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_delta_patch_descriptor->source_version_value));
|
|
sb.Append(" Digest Source:");
|
|
for (int l = 0; l < 0x14; l++) {
|
|
sb.AppendFormat(" {:02X}",
|
|
opt_delta_patch_descriptor->digest_source[l]);
|
|
}
|
|
sb.Append("\n Image Key Source:");
|
|
for (int l = 0; l < 0x14; l++) {
|
|
sb.AppendFormat(" {:02X}",
|
|
opt_delta_patch_descriptor->image_key_source[l]);
|
|
}
|
|
sb.AppendFormat(
|
|
"\n Size Of Target Header: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_delta_patch_descriptor->size_of_target_headers));
|
|
sb.AppendFormat(
|
|
" Delta Header Source Offset: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_delta_patch_descriptor->delta_headers_source_offset));
|
|
sb.AppendFormat(
|
|
" Delta Header Source Size: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_delta_patch_descriptor->delta_headers_source_size));
|
|
sb.AppendFormat(
|
|
" Delta Header Target Offset: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_delta_patch_descriptor->delta_headers_target_offset));
|
|
sb.AppendFormat(
|
|
" Delta Image Source Offset: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_delta_patch_descriptor->delta_image_source_offset));
|
|
sb.AppendFormat(
|
|
" Delta Imge Source Size: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_delta_patch_descriptor->delta_image_source_size));
|
|
sb.AppendFormat(
|
|
" Delta Image Target Offset: {}\n",
|
|
static_cast<uint32_t>(
|
|
opt_delta_patch_descriptor->delta_image_target_offset));
|
|
sb.AppendFormat(
|
|
" Old Address: {}\n",
|
|
static_cast<uint32_t>(opt_delta_patch_descriptor->info.old_addr));
|
|
sb.AppendFormat(
|
|
" New Address: {}\n",
|
|
static_cast<uint32_t>(opt_delta_patch_descriptor->info.new_addr));
|
|
sb.AppendFormat(" Uncompressed Lens: {}\n",
|
|
static_cast<uint16_t>(
|
|
opt_delta_patch_descriptor->info.uncompressed_len));
|
|
sb.AppendFormat(" Compressed Lens: {}\n",
|
|
static_cast<uint16_t>(
|
|
opt_delta_patch_descriptor->info.compressed_len));
|
|
sb.AppendFormat(
|
|
" Patch Data: {}\n",
|
|
static_cast<char>(opt_delta_patch_descriptor->info.patch_data[0]));
|
|
} break;
|
|
case XEX_HEADER_BASE_REFERENCE: {
|
|
sb.Append(" XEX_HEADER_BASE_REFERENCE (TODO):\n");
|
|
} break;
|
|
case XEX_HEADER_DISC_PROFILE_ID: { // 4D530919, 4156091D
|
|
sb.Append(" XEX_HEADER_DISC_PROFILE_ID (TODO):\n");
|
|
} break;
|
|
case XEX_HEADER_BOUNDING_PATH: {
|
|
auto opt_bound_path =
|
|
reinterpret_cast<const xex2_opt_bound_path*>(opt_header_ptr);
|
|
sb.AppendFormat(" XEX_HEADER_BOUNDING_PATH: {}\n",
|
|
opt_bound_path->path);
|
|
} break;
|
|
case XEX_HEADER_DEVICE_ID: {
|
|
sb.Append(" XEX_HEADER_DEVICE_ID (TODO):\n");
|
|
} break;
|
|
case XEX_HEADER_ORIGINAL_BASE_ADDRESS: {
|
|
sb.AppendFormat(" XEX_HEADER_ORIGINAL_BASE_ADDRESS: {:08X}\n",
|
|
(uint32_t)opt_header.value);
|
|
} break;
|
|
case XEX_HEADER_ENTRY_POINT: {
|
|
sb.AppendFormat(" XEX_HEADER_ENTRY_POINT: {:08X}\n",
|
|
(uint32_t)opt_header.value);
|
|
} break;
|
|
case XEX_HEADER_IMAGE_BASE_ADDRESS: {
|
|
sb.AppendFormat(" XEX_HEADER_IMAGE_BASE_ADDRESS: {:08X}\n",
|
|
(uint32_t)opt_header.value);
|
|
} break;
|
|
case XEX_HEADER_IMPORT_LIBRARIES: {
|
|
sb.Append(" XEX_HEADER_IMPORT_LIBRARIES:\n");
|
|
auto opt_import_libraries =
|
|
reinterpret_cast<const xex2_opt_import_libraries*>(opt_header_ptr);
|
|
|
|
// FIXME: Don't know if 32 is the actual limit, but haven't seen more
|
|
// than 2.
|
|
const char* string_table[32];
|
|
std::memset(string_table, 0, sizeof(string_table));
|
|
|
|
// Parse the string table
|
|
for (size_t j = 0, o = 0; j < opt_import_libraries->string_table.size &&
|
|
o < opt_import_libraries->string_table.count;
|
|
o++) {
|
|
assert_true(o < xe::countof(string_table));
|
|
const char* str = &opt_import_libraries->string_table.data[j];
|
|
|
|
string_table[o] = str;
|
|
j += std::strlen(str) + 1;
|
|
|
|
// Padding
|
|
if ((j % 4) != 0) {
|
|
j += 4 - (j % 4);
|
|
}
|
|
}
|
|
|
|
auto library_data =
|
|
reinterpret_cast<const uint8_t*>(opt_import_libraries);
|
|
uint32_t library_offset = opt_import_libraries->string_table.size + 12;
|
|
while (library_offset < opt_import_libraries->size) {
|
|
auto library = reinterpret_cast<const xex2_import_library*>(
|
|
library_data + library_offset);
|
|
if (!library->size) {
|
|
break;
|
|
}
|
|
auto name = string_table[library->name_index & 0xFF];
|
|
assert_not_null(name);
|
|
sb.AppendFormat(" {} - {} imports\n", name,
|
|
(uint16_t)library->count);
|
|
|
|
xex2_version version, version_min;
|
|
version = library->version();
|
|
version_min = library->version_min();
|
|
sb.AppendFormat(" Version: {}.{}.{}.{}\n", version.major,
|
|
version.minor, version.build, version.qfe);
|
|
sb.AppendFormat(" Min Version: {}.{}.{}.{}\n", version_min.major,
|
|
version_min.minor, version_min.build,
|
|
version_min.qfe);
|
|
|
|
library_offset += library->size;
|
|
}
|
|
} break;
|
|
case XEX_HEADER_CHECKSUM_TIMESTAMP: {
|
|
// TODO(Byrom): Relocate parts of this to somewhere more suitable
|
|
// (if possible) to leave only the log printing portion.
|
|
auto opt_checksum_timedatestamp =
|
|
reinterpret_cast<const xex2_opt_checksum_timedatestamp*>(
|
|
opt_header_ptr);
|
|
|
|
// Store the checksum & timedatestamp just in case we need them later.
|
|
mod_checksum_ = opt_checksum_timedatestamp->checksum;
|
|
time_date_stamp_ = opt_checksum_timedatestamp->timedatestamp;
|
|
// Update the ldr data with the timedatestamp only.
|
|
// The checksum field is being used to store the kernel object handle
|
|
// (xmodule.cc XModule::XModule)
|
|
auto ldr_data =
|
|
memory()->TranslateVirtual<X_LDR_DATA_TABLE_ENTRY*>(hmodule_ptr_);
|
|
ldr_data->time_date_stamp = time_date_stamp_;
|
|
|
|
time_t time = (time_t)opt_checksum_timedatestamp->timedatestamp;
|
|
struct tm* timeinfo = localtime(&time);
|
|
sb.AppendFormat(" XEX_HEADER_CHECKSUM_TIMESTAMP:\n");
|
|
sb.AppendFormat(
|
|
" Checksum : {:08X}\n",
|
|
static_cast<uint32_t>(opt_checksum_timedatestamp->checksum));
|
|
sb.AppendFormat(
|
|
" Time Stamp: {:08X} - {}",
|
|
static_cast<uint32_t>(opt_checksum_timedatestamp->timedatestamp),
|
|
asctime(timeinfo));
|
|
} break;
|
|
case XEX_HEADER_ENABLED_FOR_CALLCAP: {
|
|
sb.Append(" XEX_HEADER_ENABLED_FOR_CALLCAP:\n");
|
|
auto opt_call_cap_imports =
|
|
reinterpret_cast<const xex2_opt_call_cap_imports*>(opt_header_ptr);
|
|
sb.AppendFormat(
|
|
" Starting Function Thunk Address: {:08X}\n",
|
|
static_cast<uint32_t>(opt_call_cap_imports->start_func_thunk_addr));
|
|
sb.AppendFormat(
|
|
" Ending Function Thunk Address: {:08X}\n",
|
|
static_cast<uint32_t>(opt_call_cap_imports->end_func_thunk_addr));
|
|
} break;
|
|
case XEX_HEADER_ENABLED_FOR_FASTCAP: {
|
|
sb.Append(" XEX_HEADER_ENABLED_FOR_FASTCAP (TODO):\n");
|
|
} break;
|
|
case XEX_HEADER_ORIGINAL_PE_NAME: {
|
|
auto opt_pe_name =
|
|
reinterpret_cast<const xex2_opt_original_pe_name*>(opt_header_ptr);
|
|
sb.AppendFormat(" XEX_HEADER_ORIGINAL_PE_NAME: {}\n",
|
|
opt_pe_name->name);
|
|
} break;
|
|
case XEX_HEADER_STATIC_LIBRARIES: {
|
|
sb.Append(" XEX_HEADER_STATIC_LIBRARIES:\n");
|
|
auto opt_static_libraries =
|
|
reinterpret_cast<const xex2_opt_static_libraries*>(opt_header_ptr);
|
|
|
|
uint32_t count = (opt_static_libraries->size - 4) / 0x10;
|
|
for (uint32_t l = 0; l < count; l++) {
|
|
auto& library = opt_static_libraries->libraries[l];
|
|
sb.AppendFormat(" {:<8} : {}.{}.{}.{}\n", library.name,
|
|
static_cast<uint16_t>(library.version_major),
|
|
static_cast<uint16_t>(library.version_minor),
|
|
static_cast<uint16_t>(library.version_build),
|
|
static_cast<uint16_t>(library.version_qfe));
|
|
}
|
|
} break;
|
|
case XEX_HEADER_TLS_INFO: {
|
|
sb.Append(" XEX_HEADER_TLS_INFO:\n");
|
|
auto opt_tls_info =
|
|
reinterpret_cast<const xex2_opt_tls_info*>(opt_header_ptr);
|
|
|
|
sb.AppendFormat(" Slot Count: {}\n",
|
|
static_cast<uint32_t>(opt_tls_info->slot_count));
|
|
sb.AppendFormat(" Raw Data Address: {:08X}\n",
|
|
static_cast<uint32_t>(opt_tls_info->raw_data_address));
|
|
sb.AppendFormat(" Data Size: {}\n",
|
|
static_cast<uint32_t>(opt_tls_info->data_size));
|
|
sb.AppendFormat(" Raw Data Size: {}\n",
|
|
static_cast<uint32_t>(opt_tls_info->raw_data_size));
|
|
} break;
|
|
case XEX_HEADER_DEFAULT_STACK_SIZE: {
|
|
sb.AppendFormat(" XEX_HEADER_DEFAULT_STACK_SIZE: {}\n",
|
|
static_cast<uint32_t>(opt_header.value));
|
|
} break;
|
|
case XEX_HEADER_DEFAULT_FILESYSTEM_CACHE_SIZE: {
|
|
sb.AppendFormat(" XEX_HEADER_DEFAULT_FILESYSTEM_CACHE_SIZE: {}\n",
|
|
static_cast<uint32_t>(opt_header.value));
|
|
} break;
|
|
case XEX_HEADER_DEFAULT_HEAP_SIZE: {
|
|
sb.AppendFormat(" XEX_HEADER_DEFAULT_HEAP_SIZE: {}\n",
|
|
static_cast<uint32_t>(opt_header.value));
|
|
} break;
|
|
case XEX_HEADER_PAGE_HEAP_SIZE_AND_FLAGS: {
|
|
sb.Append(" XEX_HEADER_PAGE_HEAP_SIZE_AND_FLAGS (TODO):\n");
|
|
} break;
|
|
case XEX_HEADER_SYSTEM_FLAGS: {
|
|
sb.AppendFormat(" XEX_HEADER_SYSTEM_FLAGS: {:08X}\n",
|
|
static_cast<uint32_t>(opt_header.value));
|
|
|
|
for (const auto& entry : xex2_system_flags_map) {
|
|
if (opt_header.value & entry.first) {
|
|
sb.AppendFormat(" {}\n", entry.second);
|
|
}
|
|
}
|
|
} break;
|
|
case XEX_HEADER_SYSTEM_FLAGS_32: {
|
|
sb.AppendFormat(" XEX_HEADER_SYSTEM_FLAGS_32: {:08X}\n",
|
|
static_cast<uint32_t>(opt_header.value));
|
|
for (const auto& entry : xex2_system_flags_32_map) {
|
|
if (opt_header.value & entry.first) {
|
|
sb.AppendFormat(" {}\n", entry.second);
|
|
}
|
|
}
|
|
} break;
|
|
case XEX_HEADER_SYSTEM_FLAGS_64: {
|
|
sb.Append(" XEX_HEADER_SYSTEM_FLAGS_64 (TODO):\n");
|
|
} break;
|
|
case XEX_HEADER_EXECUTION_INFO: {
|
|
sb.Append(" XEX_HEADER_EXECUTION_INFO:\n");
|
|
auto opt_exec_info =
|
|
reinterpret_cast<const xex2_opt_execution_info*>(opt_header_ptr);
|
|
|
|
sb.AppendFormat(" Media ID: {:08X}\n",
|
|
static_cast<uint32_t>(opt_exec_info->media_id));
|
|
sb.AppendFormat(" Title ID: {:08X}\n",
|
|
static_cast<uint32_t>(opt_exec_info->title_id));
|
|
sb.AppendFormat(" Savegame ID: {:08X}\n",
|
|
static_cast<uint32_t>(opt_exec_info->title_id));
|
|
sb.AppendFormat(" Disc Number / Total: {} / {}\n",
|
|
opt_exec_info->disc_number, opt_exec_info->disc_count);
|
|
} break;
|
|
case XEX_HEADER_TITLE_WORKSPACE_SIZE: {
|
|
sb.AppendFormat(" XEX_HEADER_TITLE_WORKSPACE_SIZE: {}\n",
|
|
uint32_t(opt_header.value));
|
|
} break;
|
|
case XEX_HEADER_GAME_RATINGS: {
|
|
sb.Append(" XEX_HEADER_GAME_RATINGS:\n");
|
|
auto opt_game_ratings =
|
|
reinterpret_cast<const xex2_game_ratings_t*>(opt_header_ptr);
|
|
if (xex2_rating_esrb_value_map.contains(opt_game_ratings->esrb)) {
|
|
sb.AppendFormat(" ESRB: {}\n", xex2_rating_esrb_value_map.at(
|
|
opt_game_ratings->esrb));
|
|
} else {
|
|
sb.AppendFormat(" Unk ESRB: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->esrb));
|
|
}
|
|
if (xex2_rating_pegi_value_map.contains(opt_game_ratings->pegi)) {
|
|
sb.AppendFormat(" PEGI: {}\n", xex2_rating_pegi_value_map.at(
|
|
opt_game_ratings->pegi));
|
|
} else {
|
|
sb.AppendFormat(" Unk PEGI: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->pegi));
|
|
}
|
|
if (xex2_rating_pegi_fi_value_map.contains(opt_game_ratings->pegifi)) {
|
|
sb.AppendFormat(
|
|
" PEGI-FI: {}\n",
|
|
xex2_rating_pegi_fi_value_map.at(opt_game_ratings->pegifi));
|
|
} else {
|
|
sb.AppendFormat(" Unk PEGI - FI: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->pegifi));
|
|
}
|
|
if (xex2_rating_pegi_pt_value_map.contains(opt_game_ratings->pegipt)) {
|
|
sb.AppendFormat(
|
|
" PEGI - PT: {}\n",
|
|
xex2_rating_pegi_pt_value_map.at(opt_game_ratings->pegipt));
|
|
} else {
|
|
sb.AppendFormat(" Unk PEGI-PT: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->pegipt));
|
|
}
|
|
if (xex2_rating_bbfc_value_map.contains(opt_game_ratings->bbfc)) {
|
|
sb.AppendFormat(" BBFC: {}\n", xex2_rating_bbfc_value_map.at(
|
|
opt_game_ratings->bbfc));
|
|
} else {
|
|
sb.AppendFormat(" Unk BBFC: {:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->bbfc));
|
|
}
|
|
if (xex2_rating_cero_value_map.contains(opt_game_ratings->cero)) {
|
|
sb.AppendFormat(" CERO: {}\n", xex2_rating_cero_value_map.at(
|
|
opt_game_ratings->cero));
|
|
} else {
|
|
sb.AppendFormat(" Unk CERO: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->cero));
|
|
}
|
|
if (xex2_rating_usk_value_map.contains(opt_game_ratings->usk)) {
|
|
sb.AppendFormat(" USK: {}\n",
|
|
xex2_rating_usk_value_map.at(opt_game_ratings->usk));
|
|
} else {
|
|
sb.AppendFormat(" Unk USK: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->usk));
|
|
}
|
|
if (xex2_rating_oflc_au_value_map.contains(opt_game_ratings->oflcau)) {
|
|
sb.AppendFormat(
|
|
" OFLC - AU: {}\n",
|
|
xex2_rating_oflc_au_value_map.at(opt_game_ratings->oflcau));
|
|
} else {
|
|
sb.AppendFormat(" Unk OFLC - AU: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->oflcau));
|
|
}
|
|
if (xex2_rating_oflc_nz_value_map.contains(opt_game_ratings->oflcnz)) {
|
|
sb.AppendFormat(
|
|
" OFLC - NZ: {}\n",
|
|
xex2_rating_oflc_nz_value_map.at(opt_game_ratings->oflcnz));
|
|
} else {
|
|
sb.AppendFormat(" Unk OFLC - NZ: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->oflcau));
|
|
}
|
|
if (xex2_rating_kmrb_value_map.contains(opt_game_ratings->kmrb)) {
|
|
sb.AppendFormat(" KMRB: {}\n", xex2_rating_kmrb_value_map.at(
|
|
opt_game_ratings->kmrb));
|
|
} else {
|
|
sb.AppendFormat(" Unk KMRB: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->kmrb));
|
|
}
|
|
if (xex2_rating_brazil_value_map.contains(opt_game_ratings->brazil)) {
|
|
sb.AppendFormat(
|
|
" Brazil: {}\n",
|
|
xex2_rating_brazil_value_map.at(opt_game_ratings->brazil));
|
|
} else {
|
|
sb.AppendFormat(" Unk Brazil: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->brazil));
|
|
}
|
|
if (xex2_rating_fpb_value_map.contains(opt_game_ratings->fpb)) {
|
|
sb.AppendFormat(" FPB: {}\n",
|
|
xex2_rating_fpb_value_map.at(opt_game_ratings->fpb));
|
|
} else {
|
|
sb.AppendFormat(" Unk FPB: 0x{:02x}\n",
|
|
static_cast<uint8_t>(opt_game_ratings->fpb));
|
|
}
|
|
} break;
|
|
case XEX_HEADER_LAN_KEY: {
|
|
sb.Append(" XEX_HEADER_LAN_KEY:");
|
|
auto opt_lan_key =
|
|
reinterpret_cast<const xex2_opt_lan_key*>(opt_header_ptr);
|
|
|
|
for (int l = 0; l < 16; l++) {
|
|
sb.AppendFormat(" {:02X}", opt_lan_key->key[l]);
|
|
}
|
|
sb.Append("\n");
|
|
} break;
|
|
case XEX_HEADER_XBOX360_LOGO: {
|
|
sb.Append(" XEX_HEADER_XBOX360_LOGO:\n");
|
|
auto opt_ms_logo =
|
|
reinterpret_cast<const xex2_opt_ms_logo*>(opt_header_ptr);
|
|
|
|
sb.AppendFormat(" Section Size: {}\n",
|
|
static_cast<uint32_t>(opt_ms_logo->section_size));
|
|
sb.AppendFormat(" Logo Size: {}\n",
|
|
static_cast<uint32_t>(opt_ms_logo->logo_size));
|
|
} break;
|
|
case XEX_HEADER_MULTIDISC_MEDIA_IDS: { // 4D5307DF
|
|
sb.Append(" XEX_HEADER_MULTIDISC_MEDIA_IDS (TODO):\n");
|
|
} break;
|
|
case XEX_HEADER_ALTERNATE_TITLE_IDS: {
|
|
sb.Append(" XEX_HEADER_ALTERNATE_TITLE_IDS:");
|
|
auto opt_alternate_title_id =
|
|
reinterpret_cast<const xex2_opt_generic_u32*>(opt_header_ptr);
|
|
|
|
std::string title_ids = "";
|
|
|
|
for (uint32_t i = 0; i < opt_alternate_title_id->count(); i++) {
|
|
if (opt_alternate_title_id->values[i] != 0) {
|
|
title_ids.append(fmt::format(
|
|
" {:08X},", opt_alternate_title_id->values[i].get()));
|
|
}
|
|
}
|
|
// Remove last character as it is not necessary
|
|
if (!title_ids.empty()) {
|
|
title_ids.pop_back();
|
|
sb.AppendFormat("{}\n", title_ids);
|
|
}
|
|
} break;
|
|
case XEX_HEADER_ADDITIONAL_TITLE_MEMORY: {
|
|
sb.AppendFormat(" XEX_HEADER_ADDITIONAL_TITLE_MEMORY: {}\n",
|
|
uint32_t(opt_header.value));
|
|
} break;
|
|
case XEX_HEADER_EXPORTS_BY_NAME: {
|
|
sb.Append(" XEX_HEADER_EXPORTS_BY_NAME:\n");
|
|
auto dir =
|
|
reinterpret_cast<const xex2_opt_data_directory*>(opt_header_ptr);
|
|
|
|
auto exe_address = xex_module()->base_address();
|
|
auto e = memory()->TranslateVirtual<const X_IMAGE_EXPORT_DIRECTORY*>(
|
|
exe_address + dir->offset);
|
|
auto e_base = reinterpret_cast<uintptr_t>(e);
|
|
|
|
// e->AddressOfX RVAs are relative to the IMAGE_EXPORT_DIRECTORY!
|
|
auto function_table =
|
|
reinterpret_cast<const uint32_t*>(e_base + e->AddressOfFunctions);
|
|
// Names relative to directory.
|
|
auto name_table =
|
|
reinterpret_cast<const uint32_t*>(e_base + e->AddressOfNames);
|
|
// Table of ordinals (by name).
|
|
auto ordinal_table = reinterpret_cast<const uint16_t*>(
|
|
e_base + e->AddressOfNameOrdinals);
|
|
for (uint32_t n = 0; n < e->NumberOfNames; n++) {
|
|
auto name = reinterpret_cast<const char*>(e_base + name_table[n]);
|
|
uint16_t ordinal = ordinal_table[n];
|
|
uint32_t addr = exe_address + function_table[ordinal];
|
|
sb.AppendFormat(" {:<28} - {:03X} - {:08X}\n", name, ordinal,
|
|
addr);
|
|
}
|
|
} break;
|
|
default: {
|
|
sb.AppendFormat(" Unknown Header {:08X}\n", (uint32_t)opt_header.key);
|
|
} break;
|
|
}
|
|
}
|
|
|
|
sb.Append("Sections:\n");
|
|
for (uint32_t i = 0, page = 0; i < security_info->page_descriptor_count;
|
|
i++) {
|
|
// Manually byteswap the bitfield data.
|
|
xex2_page_descriptor page_descriptor;
|
|
page_descriptor.value =
|
|
xe::byte_swap(security_info->page_descriptors[i].value);
|
|
|
|
const char* type = "UNKNOWN";
|
|
switch (page_descriptor.info) {
|
|
case XEX_SECTION_CODE:
|
|
type = "CODE ";
|
|
break;
|
|
case XEX_SECTION_DATA:
|
|
type = "RWDATA ";
|
|
break;
|
|
case XEX_SECTION_READONLY_DATA:
|
|
type = "RODATA ";
|
|
break;
|
|
}
|
|
|
|
const uint32_t page_size =
|
|
xex_module()->base_address() < 0x90000000 ? 64 * 1024 : 4 * 1024;
|
|
uint32_t start_address = xex_module()->base_address() + (page * page_size);
|
|
uint32_t end_address =
|
|
start_address + (page_descriptor.page_count * page_size);
|
|
|
|
sb.AppendFormat(" {:3} {} {:3} pages {:08X} - {:08X} ({} bytes)\n",
|
|
page, type, page_descriptor.page_count, start_address,
|
|
end_address, page_descriptor.page_count * page_size);
|
|
page += page_descriptor.page_count;
|
|
}
|
|
|
|
// Print out imports.
|
|
|
|
auto import_libs = xex_module()->import_libraries();
|
|
|
|
sb.Append("Imports:\n");
|
|
for (std::vector<cpu::XexModule::ImportLibrary>::const_iterator library =
|
|
import_libs->begin();
|
|
library != import_libs->end(); ++library) {
|
|
if (library->imports.size() > 0) {
|
|
sb.AppendFormat(" {} - {} imports\n", library->name,
|
|
library->imports.size());
|
|
sb.AppendFormat(" Version: {}.{}.{}.{}\n", library->version.major,
|
|
library->version.minor, library->version.build,
|
|
library->version.qfe);
|
|
sb.AppendFormat(" Min Version: {}.{}.{}.{}\n",
|
|
library->min_version.major, library->min_version.minor,
|
|
library->min_version.build, library->min_version.qfe);
|
|
sb.Append("\n");
|
|
|
|
// Counts.
|
|
int known_count = 0;
|
|
int unknown_count = 0;
|
|
int impl_count = 0;
|
|
int unimpl_count = 0;
|
|
|
|
for (std::vector<cpu::XexModule::ImportLibraryFn>::const_iterator info =
|
|
library->imports.begin();
|
|
info != library->imports.end(); ++info) {
|
|
if (kernel_state_->IsKernelModule(library->name)) {
|
|
auto kernel_export =
|
|
export_resolver->GetExportByOrdinal(library->name, info->ordinal);
|
|
if (kernel_export) {
|
|
known_count++;
|
|
if (kernel_export->is_implemented()) {
|
|
impl_count++;
|
|
} else {
|
|
unimpl_count++;
|
|
}
|
|
} else {
|
|
unknown_count++;
|
|
unimpl_count++;
|
|
}
|
|
} else {
|
|
auto module = kernel_state_->GetModule(library->name);
|
|
if (module) {
|
|
uint32_t export_addr =
|
|
module->GetProcAddressByOrdinal(info->ordinal);
|
|
if (export_addr) {
|
|
impl_count++;
|
|
known_count++;
|
|
} else {
|
|
unimpl_count++;
|
|
unknown_count++;
|
|
}
|
|
} else {
|
|
unimpl_count++;
|
|
unknown_count++;
|
|
}
|
|
}
|
|
}
|
|
float total_count = static_cast<float>(library->imports.size()) / 100.0f;
|
|
sb.AppendFormat(" Total: {:4}\n", library->imports.size());
|
|
sb.AppendFormat(" Known: {:3}% ({} known, {} unknown)\n",
|
|
static_cast<int>(known_count / total_count), known_count,
|
|
unknown_count);
|
|
sb.AppendFormat(
|
|
" Implemented: {:3}% ({} implemented, {} unimplemented)\n",
|
|
static_cast<int>(impl_count / total_count), impl_count, unimpl_count);
|
|
sb.AppendFormat("\n");
|
|
|
|
// Listing.
|
|
for (std::vector<cpu::XexModule::ImportLibraryFn>::const_iterator info =
|
|
library->imports.begin();
|
|
info != library->imports.end(); ++info) {
|
|
const char* name = "UNKNOWN";
|
|
bool implemented = false;
|
|
|
|
cpu::Export* kernel_export = nullptr;
|
|
if (kernel_state_->IsKernelModule(library->name)) {
|
|
kernel_export =
|
|
export_resolver->GetExportByOrdinal(library->name, info->ordinal);
|
|
if (kernel_export) {
|
|
name = kernel_export->name;
|
|
implemented = kernel_export->is_implemented();
|
|
}
|
|
} else {
|
|
auto module = kernel_state_->GetModule(library->name);
|
|
if (module && module->GetProcAddressByOrdinal(info->ordinal)) {
|
|
// TODO(benvanik): name lookup.
|
|
implemented = true;
|
|
}
|
|
}
|
|
if (kernel_export &&
|
|
kernel_export->get_type() == cpu::Export::Type::kVariable) {
|
|
sb.AppendFormat(" V {:08X} {:03X} ({:4}) {} {}\n",
|
|
info->value_address, info->ordinal, info->ordinal,
|
|
implemented ? " " : "!!", name);
|
|
} else if (info->thunk_address) {
|
|
sb.AppendFormat(" F {:08X} {:08X} {:03X} ({:4}) {} {}\n",
|
|
info->value_address, info->thunk_address,
|
|
info->ordinal, info->ordinal,
|
|
implemented ? " " : "!!", name);
|
|
}
|
|
}
|
|
}
|
|
|
|
sb.Append("\n");
|
|
}
|
|
|
|
xe::logging::AppendLogLine(xe::LogLevel::Info, 'i', sb.to_string_view());
|
|
}
|
|
|
|
void UserModule::CalculateHash() {
|
|
const BaseHeap* module_heap =
|
|
kernel_state_->memory()->LookupHeap(xex_module()->base_address());
|
|
|
|
if (!module_heap) {
|
|
XELOGE("Invalid heap for xex module! Address: {:08X}",
|
|
xex_module()->base_address());
|
|
return;
|
|
}
|
|
|
|
const uint32_t page_size = module_heap->page_size();
|
|
auto security_info = xex_module()->xex_security_info();
|
|
|
|
auto find_code_section_page = [&security_info](bool from_bottom) {
|
|
for (uint32_t i = 0; i < security_info->page_descriptor_count; i++) {
|
|
const uint32_t page_index =
|
|
from_bottom ? i : (security_info->page_descriptor_count - 1) - i;
|
|
xex2_page_descriptor page_descriptor;
|
|
page_descriptor.value =
|
|
xe::byte_swap(security_info->page_descriptors[page_index].value);
|
|
if (page_descriptor.info != XEX_SECTION_CODE) {
|
|
continue;
|
|
}
|
|
return page_index;
|
|
}
|
|
return UINT32_MAX;
|
|
};
|
|
|
|
const uint32_t start_address =
|
|
xex_module()->base_address() + (find_code_section_page(true) * page_size);
|
|
const uint32_t end_address =
|
|
xex_module()->base_address() +
|
|
((find_code_section_page(false) + 1) * page_size);
|
|
|
|
uint8_t* base_code_adr = memory()->TranslateVirtual(start_address);
|
|
|
|
XXH3_state_t hash_state;
|
|
XXH3_64bits_reset(&hash_state);
|
|
XXH3_64bits_update(&hash_state, base_code_adr, end_address - start_address);
|
|
hash_ = XXH3_64bits_digest(&hash_state);
|
|
}
|
|
} // namespace kernel
|
|
} // namespace xe
|