add XEX dump tooling and silent audio fallback

This commit is contained in:
mchughalex 2026-06-08 17:06:19 +01:00
parent 97ab3e107d
commit 34accdd241
3 changed files with 133 additions and 4 deletions

View file

@ -37,6 +37,21 @@ REXCVAR_DEFINE_INT32(audio_worker_thread_priority, 0, "Audio",
namespace { 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() { int32_t AudioWorkerNativePriority() {
switch (std::clamp(REXCVAR_GET(audio_worker_thread_priority), -2, 2)) { switch (std::clamp(REXCVAR_GET(audio_worker_thread_priority), -2, 2)) {
case -2: case -2:
@ -262,7 +277,11 @@ X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg, s
AudioDriver* driver; AudioDriver* driver;
auto result = CreateDriver(index, client_semaphore, &driver); auto result = CreateDriver(index, client_semaphore, &driver);
if (XFAILED(result)) { 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); assert_not_null(driver);
@ -371,11 +390,11 @@ bool AudioSystem::Restore(stream::ByteStream* stream) {
AudioDriver* driver = nullptr; AudioDriver* driver = nullptr;
auto status = CreateDriver(id, client_semaphore, &driver); auto status = CreateDriver(id, client_semaphore, &driver);
if (XFAILED(status)) { if (XFAILED(status)) {
REXAPU_ERROR( REXAPU_WARN(
"AudioSystem::Restore - Call to CreateDriver failed with status " "AudioSystem::Restore - Call to CreateDriver failed with status "
"{:08X}", "{:08X}; using silent audio fallback",
status); status);
return false; driver = new SilentAudioDriver(memory_, client_semaphore);
} }
assert_not_null(driver); assert_not_null(driver);

View file

@ -14,6 +14,7 @@
#include <chrono> #include <chrono>
#include <cstdlib> #include <cstdlib>
#include <filesystem>
#include <map> #include <map>
#include <string> #include <string>
@ -21,8 +22,10 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <rex/cvar.h> #include <rex/cvar.h>
#include <rex/kernel/init.h>
#include <rex/logging.h> #include <rex/logging.h>
#include <rex/result.h> #include <rex/result.h>
#include <rex/runtime.h>
#include <rex/version.h> #include <rex/version.h>
#ifdef _WIN32 #ifdef _WIN32
@ -87,6 +90,53 @@ int main(int argc, char** argv) {
rexglue::cli::RegisterInit(app, ctx, pending); rexglue::cli::RegisterInit(app, ctx, pending);
rexglue::cli::RegisterRecompileTests(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<void> {
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<void>(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<void>(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<void>(rex::ErrorCategory::IO,
fmt::format("Failed to load XEX image: {:#x}", status));
}
return rex::Ok();
};
});
CLI11_PARSE(app, argc, argv); CLI11_PARSE(app, argc, argv);
ConfigureLogging(log_level, log_file, verbose); ConfigureLogging(log_level, log_file, verbose);

View file

@ -15,6 +15,9 @@
#include "pe/pe_image.h" #include "pe/pe_image.h"
#include <algorithm> #include <algorithm>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <unordered_map> #include <unordered_map>
#include <fmt/format.h> #include <fmt/format.h>
@ -58,6 +61,62 @@ void aes_decrypt_buffer(const uint8_t* session_key, const uint8_t* input_buffer,
namespace rex::runtime { 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<const char*>(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; using rex::system::KernelState;
XexModule::XexModule(FunctionDispatcher* function_dispatcher, KernelState* kernel_state) 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_, REXLOG_INFO("XEX image loaded at {:08X}-{:08X} (size {:08X})", base_address_,
base_address_ + image_size(), image_size()); base_address_ + image_size(), image_size());
MaybeDumpLoadedXexImage(*this);
// Parse any "unsafe" headers into safer variants // Parse any "unsafe" headers into safer variants
xex2_opt_generic_u32* alternate_titleids; xex2_opt_generic_u32* alternate_titleids;