From 34accdd2419240d25f7fc9bfe8870886613d39d3 Mon Sep 17 00:00:00 2001 From: mchughalex <25671999+mchughalex@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:06:19 +0100 Subject: [PATCH] add XEX dump tooling and silent audio fallback --- src/audio/audio_system.cpp | 27 ++++++++++++++--- src/rexglue/main.cpp | 50 +++++++++++++++++++++++++++++++ src/system/xex_module.cpp | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 4 deletions(-) diff --git a/src/audio/audio_system.cpp b/src/audio/audio_system.cpp index 2b73f0b..c9f1bd2 100644 --- a/src/audio/audio_system.cpp +++ b/src/audio/audio_system.cpp @@ -37,6 +37,21 @@ REXCVAR_DEFINE_INT32(audio_worker_thread_priority, 0, "Audio", namespace { +class SilentAudioDriver final : public rex::audio::AudioDriver { + public: + SilentAudioDriver(rex::memory::Memory* memory, rex::thread::Semaphore* semaphore) + : AudioDriver(memory), semaphore_(semaphore) {} + + void SubmitFrame(uint32_t /*samples_ptr*/) override { + if (semaphore_) { + semaphore_->Release(1, nullptr); + } + } + + private: + rex::thread::Semaphore* semaphore_; +}; + int32_t AudioWorkerNativePriority() { switch (std::clamp(REXCVAR_GET(audio_worker_thread_priority), -2, 2)) { case -2: @@ -262,7 +277,11 @@ X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg, s AudioDriver* driver; auto result = CreateDriver(index, client_semaphore, &driver); if (XFAILED(result)) { - return result; + REXAPU_WARN( + "AudioSystem::RegisterClient: audio driver creation failed with status {:08X}; " + "using silent audio fallback", + result); + driver = new SilentAudioDriver(memory_, client_semaphore); } assert_not_null(driver); @@ -371,11 +390,11 @@ bool AudioSystem::Restore(stream::ByteStream* stream) { AudioDriver* driver = nullptr; auto status = CreateDriver(id, client_semaphore, &driver); if (XFAILED(status)) { - REXAPU_ERROR( + REXAPU_WARN( "AudioSystem::Restore - Call to CreateDriver failed with status " - "{:08X}", + "{:08X}; using silent audio fallback", status); - return false; + driver = new SilentAudioDriver(memory_, client_semaphore); } assert_not_null(driver); diff --git a/src/rexglue/main.cpp b/src/rexglue/main.cpp index d3f0dfc..84a4f71 100644 --- a/src/rexglue/main.cpp +++ b/src/rexglue/main.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -21,8 +22,10 @@ #include #include +#include #include #include +#include #include #ifdef _WIN32 @@ -87,6 +90,53 @@ int main(int argc, char** argv) { rexglue::cli::RegisterInit(app, ctx, pending); rexglue::cli::RegisterRecompileTests(app, ctx, pending); + std::string dump_xex_path; + std::string dump_output_dir; + auto* dump_xex = + app.add_subcommand("dump-xex", "Load, decrypt/decompress and dump a XEX image"); + dump_xex->add_option("xex", dump_xex_path, "Path to the XEX file")->required(); + dump_xex->add_option("output", dump_output_dir, "Directory for decoded image dumps")->required(); + dump_xex->callback([&] { + pending = [&]() -> rex::Result { + namespace fs = std::filesystem; + std::error_code ec; + fs::path xex_path = fs::weakly_canonical(fs::absolute(dump_xex_path), ec); + if (ec || !fs::is_regular_file(xex_path)) { + return rex::Err(rex::ErrorCategory::IO, + fmt::format("XEX file not found: {}", dump_xex_path)); + } + fs::path output_dir = fs::weakly_canonical(fs::absolute(dump_output_dir), ec); + if (ec) { + output_dir = fs::absolute(dump_output_dir); + } + +#ifdef _WIN32 + _putenv_s("REXGLUE_DUMP_XEX_IMAGE_DIR", output_dir.string().c_str()); +#else + setenv("REXGLUE_DUMP_XEX_IMAGE_DIR", output_dir.string().c_str(), 1); +#endif + + rex::Runtime runtime(xex_path.parent_path(), {}, {}, output_dir); + rex::X_STATUS status = runtime.Setup(rex::RuntimeConfig{ + .kernel_init = rex::kernel::InitializeKernel, + .tool_mode = true, + }); + if (status != 0) { + return rex::Err(rex::ErrorCategory::IO, + fmt::format("Failed to initialize runtime: {:#x}", status)); + } + + const std::string guest_path = "game:\\" + xex_path.filename().string(); + status = runtime.LoadXexImage(guest_path); + runtime.Shutdown(); + if (status != 0) { + return rex::Err(rex::ErrorCategory::IO, + fmt::format("Failed to load XEX image: {:#x}", status)); + } + return rex::Ok(); + }; + }); + CLI11_PARSE(app, argc, argv); ConfigureLogging(log_level, log_file, verbose); diff --git a/src/system/xex_module.cpp b/src/system/xex_module.cpp index 725fc33..c7bcf5d 100644 --- a/src/system/xex_module.cpp +++ b/src/system/xex_module.cpp @@ -15,6 +15,9 @@ #include "pe/pe_image.h" #include +#include +#include +#include #include #include @@ -58,6 +61,62 @@ void aes_decrypt_buffer(const uint8_t* session_key, const uint8_t* input_buffer, namespace rex::runtime { +namespace { + +std::string DumpSafeModuleName(std::string_view name) { + std::string safe; + safe.reserve(name.size()); + for (char ch : name) { + const bool allowed = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || + (ch >= '0' && ch <= '9') || ch == '-' || ch == '_' || ch == '.'; + safe.push_back(allowed ? ch : '_'); + } + return safe.empty() ? "module" : safe; +} + +void MaybeDumpLoadedXexImage(const XexModule& module) { + const char* dump_dir_value = std::getenv("REXGLUE_DUMP_XEX_IMAGE_DIR"); + if (!dump_dir_value || !*dump_dir_value) { + return; + } + + std::error_code ec; + const std::filesystem::path dump_dir = std::filesystem::path(dump_dir_value); + std::filesystem::create_directories(dump_dir, ec); + if (ec) { + REXLOG_WARN("Failed to create XEX dump directory {}: {}", dump_dir.string(), ec.message()); + return; + } + + const std::string safe_name = DumpSafeModuleName(module.name()); + const std::filesystem::path image_path = + dump_dir / fmt::format("{}_{:08X}_{:08X}.bin", safe_name, module.base_address(), + module.image_size()); + std::ofstream image_file(image_path, std::ios::binary); + if (!image_file) { + REXLOG_WARN("Failed to open XEX image dump {}", image_path.string()); + return; + } + + const uint8_t* image_data = module.memory()->TranslateVirtual(module.base_address()); + image_file.write(reinterpret_cast(image_data), module.image_size()); + image_file.close(); + + const std::filesystem::path meta_path = + dump_dir / fmt::format("{}_{:08X}_{:08X}.txt", safe_name, module.base_address(), + module.image_size()); + std::ofstream meta_file(meta_path); + if (meta_file) { + meta_file << "name=" << module.name() << "\n"; + meta_file << fmt::format("base=0x{:08X}\n", module.base_address()); + meta_file << fmt::format("size=0x{:08X}\n", module.image_size()); + meta_file << fmt::format("entry=0x{:08X}\n", module.entry_point()); + } + REXLOG_INFO("Dumped loaded XEX image {} to {}", module.name(), image_path.string()); +} + +} // namespace + using rex::system::KernelState; XexModule::XexModule(FunctionDispatcher* function_dispatcher, KernelState* kernel_state) @@ -932,6 +991,7 @@ bool XexModule::LoadContinue() { } REXLOG_INFO("XEX image loaded at {:08X}-{:08X} (size {:08X})", base_address_, base_address_ + image_size(), image_size()); + MaybeDumpLoadedXexImage(*this); // Parse any "unsafe" headers into safer variants xex2_opt_generic_u32* alternate_titleids;