/** ****************************************************************************** * 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(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(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 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(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 magic; magic.value = xe::load(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(addr); uint8_t Z = xe::load(reinterpret_cast( reinterpret_cast(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(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(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* 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(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(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::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(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(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(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(opt_header_ptr); sb.AppendFormat(" Info Size: {}\n", static_cast(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( opt_file_format_info->compression_info.basic.blocks[0] .data_size)); sb.AppendFormat( " Zero Size: {}\n", static_cast( 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( opt_file_format_info->compression_info.normal.window_size)); sb.AppendFormat( " Block Size: {}\n", static_cast(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( opt_header_ptr); sb.AppendFormat( " Size: {}\n", static_cast(opt_delta_patch_descriptor->size)); sb.AppendFormat(" Target Version: {}\n", static_cast( opt_delta_patch_descriptor->target_version_value)); sb.AppendFormat(" Source Version: {}\n", static_cast( 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( opt_delta_patch_descriptor->size_of_target_headers)); sb.AppendFormat( " Delta Header Source Offset: {}\n", static_cast( opt_delta_patch_descriptor->delta_headers_source_offset)); sb.AppendFormat( " Delta Header Source Size: {}\n", static_cast( opt_delta_patch_descriptor->delta_headers_source_size)); sb.AppendFormat( " Delta Header Target Offset: {}\n", static_cast( opt_delta_patch_descriptor->delta_headers_target_offset)); sb.AppendFormat( " Delta Image Source Offset: {}\n", static_cast( opt_delta_patch_descriptor->delta_image_source_offset)); sb.AppendFormat( " Delta Imge Source Size: {}\n", static_cast( opt_delta_patch_descriptor->delta_image_source_size)); sb.AppendFormat( " Delta Image Target Offset: {}\n", static_cast( opt_delta_patch_descriptor->delta_image_target_offset)); sb.AppendFormat( " Old Address: {}\n", static_cast(opt_delta_patch_descriptor->info.old_addr)); sb.AppendFormat( " New Address: {}\n", static_cast(opt_delta_patch_descriptor->info.new_addr)); sb.AppendFormat(" Uncompressed Lens: {}\n", static_cast( opt_delta_patch_descriptor->info.uncompressed_len)); sb.AppendFormat(" Compressed Lens: {}\n", static_cast( opt_delta_patch_descriptor->info.compressed_len)); sb.AppendFormat( " Patch Data: {}\n", static_cast(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(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(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(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( 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( 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(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(opt_checksum_timedatestamp->checksum)); sb.AppendFormat( " Time Stamp: {:08X} - {}", static_cast(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(opt_header_ptr); sb.AppendFormat( " Starting Function Thunk Address: {:08X}\n", static_cast(opt_call_cap_imports->start_func_thunk_addr)); sb.AppendFormat( " Ending Function Thunk Address: {:08X}\n", static_cast(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(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(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(library.version_major), static_cast(library.version_minor), static_cast(library.version_build), static_cast(library.version_qfe)); } } break; case XEX_HEADER_TLS_INFO: { sb.Append(" XEX_HEADER_TLS_INFO:\n"); auto opt_tls_info = reinterpret_cast(opt_header_ptr); sb.AppendFormat(" Slot Count: {}\n", static_cast(opt_tls_info->slot_count)); sb.AppendFormat(" Raw Data Address: {:08X}\n", static_cast(opt_tls_info->raw_data_address)); sb.AppendFormat(" Data Size: {}\n", static_cast(opt_tls_info->data_size)); sb.AppendFormat(" Raw Data Size: {}\n", static_cast(opt_tls_info->raw_data_size)); } break; case XEX_HEADER_DEFAULT_STACK_SIZE: { sb.AppendFormat(" XEX_HEADER_DEFAULT_STACK_SIZE: {}\n", static_cast(opt_header.value)); } break; case XEX_HEADER_DEFAULT_FILESYSTEM_CACHE_SIZE: { sb.AppendFormat(" XEX_HEADER_DEFAULT_FILESYSTEM_CACHE_SIZE: {}\n", static_cast(opt_header.value)); } break; case XEX_HEADER_DEFAULT_HEAP_SIZE: { sb.AppendFormat(" XEX_HEADER_DEFAULT_HEAP_SIZE: {}\n", static_cast(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(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(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(opt_header_ptr); sb.AppendFormat(" Media ID: {:08X}\n", static_cast(opt_exec_info->media_id)); sb.AppendFormat(" Title ID: {:08X}\n", static_cast(opt_exec_info->title_id)); sb.AppendFormat(" Savegame ID: {:08X}\n", static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(opt_game_ratings->fpb)); } } break; case XEX_HEADER_LAN_KEY: { sb.Append(" XEX_HEADER_LAN_KEY:"); auto opt_lan_key = reinterpret_cast(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(opt_header_ptr); sb.AppendFormat(" Section Size: {}\n", static_cast(opt_ms_logo->section_size)); sb.AppendFormat(" Logo Size: {}\n", static_cast(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(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(opt_header_ptr); auto exe_address = xex_module()->base_address(); auto e = memory()->TranslateVirtual( exe_address + dir->offset); auto e_base = reinterpret_cast(e); // e->AddressOfX RVAs are relative to the IMAGE_EXPORT_DIRECTORY! auto function_table = reinterpret_cast(e_base + e->AddressOfFunctions); // Names relative to directory. auto name_table = reinterpret_cast(e_base + e->AddressOfNames); // Table of ordinals (by name). auto ordinal_table = reinterpret_cast( e_base + e->AddressOfNameOrdinals); for (uint32_t n = 0; n < e->NumberOfNames; n++) { auto name = reinterpret_cast(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::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::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(library->imports.size()) / 100.0f; sb.AppendFormat(" Total: {:4}\n", library->imports.size()); sb.AppendFormat(" Known: {:3}% ({} known, {} unknown)\n", static_cast(known_count / total_count), known_count, unknown_count); sb.AppendFormat( " Implemented: {:3}% ({} implemented, {} unimplemented)\n", static_cast(impl_count / total_count), impl_count, unimpl_count); sb.AppendFormat("\n"); // Listing. for (std::vector::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