diff --git a/include/rex/runtime.h b/include/rex/runtime.h index 1f316d2..3e47c9a 100644 --- a/include/rex/runtime.h +++ b/include/rex/runtime.h @@ -63,6 +63,7 @@ struct RuntimeConfig { std::function(runtime::FunctionDispatcher*)> audio_factory; std::function(bool tool_mode)> input_factory; std::function kernel_init; + std::filesystem::path config_path; bool tool_mode = false; }; diff --git a/include/rex/system/kernel_state.h b/include/rex/system/kernel_state.h index 92e7f2f..18a328f 100644 --- a/include/rex/system/kernel_state.h +++ b/include/rex/system/kernel_state.h @@ -300,23 +300,24 @@ class KernelState { void CompleteOverlappedImmediateEx(uint32_t overlapped_ptr, X_RESULT result, uint32_t extended_error, uint32_t length); - void CompleteOverlappedDeferred(std::function completion_callback, + void CompleteOverlappedDeferred(std::move_only_function completion_callback, uint32_t overlapped_ptr, X_RESULT result, - std::function pre_callback = nullptr, - std::function post_callback = nullptr); - void CompleteOverlappedDeferredEx(std::function completion_callback, + std::move_only_function pre_callback = nullptr, + std::move_only_function post_callback = nullptr); + void CompleteOverlappedDeferredEx(std::move_only_function completion_callback, uint32_t overlapped_ptr, X_RESULT result, uint32_t extended_error, uint32_t length, - std::function pre_callback = nullptr, - std::function post_callback = nullptr); + std::move_only_function pre_callback = nullptr, + std::move_only_function post_callback = nullptr); - void CompleteOverlappedDeferred(std::function completion_callback, + void CompleteOverlappedDeferred(std::move_only_function completion_callback, uint32_t overlapped_ptr, - std::function pre_callback = nullptr, - std::function post_callback = nullptr); + std::move_only_function pre_callback = nullptr, + std::move_only_function post_callback = nullptr); void CompleteOverlappedDeferredEx( - std::function completion_callback, uint32_t overlapped_ptr, - std::function pre_callback = nullptr, std::function post_callback = nullptr); + std::move_only_function completion_callback, + uint32_t overlapped_ptr, std::move_only_function pre_callback = nullptr, + std::move_only_function post_callback = nullptr); bool Save(stream::ByteStream* stream); bool Restore(stream::ByteStream* stream); @@ -373,7 +374,7 @@ class KernelState { // Must be guarded by the global critical region. util::NativeList dpc_list_; std::condition_variable_any dispatch_cond_; - std::list> dispatch_queue_; + std::list> dispatch_queue_; friend class XObject; }; diff --git a/include/rex/thread/timer_queue.h b/include/rex/thread/timer_queue.h index b1f9ab7..cb2e84a 100644 --- a/include/rex/thread/timer_queue.h +++ b/include/rex/thread/timer_queue.h @@ -26,8 +26,8 @@ class TimerQueue; struct TimerQueueWaitItem { using clock = std::chrono::steady_clock; - TimerQueueWaitItem(std::function callback, void* userdata, TimerQueue* parent_queue, - clock::time_point due, clock::duration interval) + TimerQueueWaitItem(std::move_only_function callback, void* userdata, + TimerQueue* parent_queue, clock::time_point due, clock::duration interval) : callback_(std::move(callback)), userdata_(userdata), parent_queue_(parent_queue), @@ -55,7 +55,7 @@ struct TimerQueueWaitItem { }; static_assert(std::atomic::is_always_lock_free); - std::function callback_; + std::move_only_function callback_; void* userdata_; TimerQueue* parent_queue_; clock::time_point due_; @@ -63,14 +63,14 @@ struct TimerQueueWaitItem { std::atomic state_; }; -std::weak_ptr QueueTimerOnce(std::function callback, +std::weak_ptr QueueTimerOnce(std::move_only_function callback, void* userdata, TimerQueueWaitItem::clock::time_point due); // Callback is first executed at due, then again repeatedly after interval // passes (unless interval == 0). The first callback will be scheduled at // `max(now() - interval, due)` to mitigate callback flooding. -std::weak_ptr QueueTimerRecurring(std::function callback, +std::weak_ptr QueueTimerRecurring(std::move_only_function callback, void* userdata, TimerQueueWaitItem::clock::time_point due, TimerQueueWaitItem::clock::duration interval); diff --git a/src/core/cvar.cpp b/src/core/cvar.cpp index fa69487..6c1badf 100644 --- a/src/core/cvar.cpp +++ b/src/core/cvar.cpp @@ -537,20 +537,76 @@ bool IsFinalized() { } void SaveConfig(const std::filesystem::path& config_path) { - std::string content = SerializeToTOML(); - if (content.empty()) { - REXLOG_DEBUG("SaveConfig: no modified flags to save"); - return; - } - try { + toml::table config; + if (std::filesystem::exists(config_path)) { + config = toml::parse_file(config_path.string()); + } + + { + std::lock_guard lock(GetRegistryMutex()); + bool modified = false; + for (const auto& entry : GetRegistryStorage()) { + if (entry.getter() == entry.default_value) { + continue; + } + + const std::string value = entry.getter(); + switch (entry.type) { + case FlagType::Boolean: + config.insert_or_assign(entry.name, value == "true" || value == "1" || value == "yes"); + break; + case FlagType::Int32: + case FlagType::Int64: { + int64_t parsed = 0; + auto [ptr, ec] = std::from_chars(value.data(), value.data() + value.size(), parsed); + if (ec == std::errc()) { + config.insert_or_assign(entry.name, parsed); + } + break; + } + case FlagType::Uint32: + case FlagType::Uint64: { + uint64_t parsed = 0; + auto [ptr, ec] = std::from_chars(value.data(), value.data() + value.size(), parsed); + if (ec == std::errc()) { + if (parsed <= static_cast(std::numeric_limits::max())) { + config.insert_or_assign(entry.name, static_cast(parsed)); + } else { + config.insert_or_assign(entry.name, value); + } + } + break; + } + case FlagType::Double: { + double parsed = 0.0; + if (ParseDouble(value, parsed)) { + config.insert_or_assign(entry.name, parsed); + } + break; + } + case FlagType::String: + config.insert_or_assign(entry.name, value); + break; + case FlagType::Command: + continue; + } + modified = true; + } + + if (!modified) { + REXLOG_DEBUG("SaveConfig: no modified flags to save"); + return; + } + } + std::ofstream file(config_path); if (!file) { REXLOG_ERROR("SaveConfig: failed to open {}", config_path.string()); return; } file << "# Auto-generated cvar configuration\n"; - file << content; + file << config; REXLOG_INFO("Saved config to {}", config_path.string()); } catch (const std::exception& e) { REXLOG_ERROR("SaveConfig: {}", e.what()); diff --git a/src/core/timer_queue.cpp b/src/core/timer_queue.cpp index 5cfea05..d079ec7 100644 --- a/src/core/timer_queue.cpp +++ b/src/core/timer_queue.cpp @@ -187,14 +187,14 @@ void TimerQueueWaitItem::Disarm() { } } -std::weak_ptr QueueTimerOnce(std::function callback, void* userdata, - WaitItem::clock::time_point due) { +std::weak_ptr QueueTimerOnce(std::move_only_function callback, + void* userdata, WaitItem::clock::time_point due) { return timer_queue_.QueueTimer(std::make_shared( std::move(callback), userdata, &timer_queue_, due, WaitItem::clock::duration::zero())); } -std::weak_ptr QueueTimerRecurring(std::function callback, void* userdata, - WaitItem::clock::time_point due, +std::weak_ptr QueueTimerRecurring(std::move_only_function callback, + void* userdata, WaitItem::clock::time_point due, WaitItem::clock::duration interval) { return timer_queue_.QueueTimer( std::make_shared(std::move(callback), userdata, &timer_queue_, due, interval)); diff --git a/src/kernel/crt/file.cpp b/src/kernel/crt/file.cpp index 39bda8c..3360a4f 100644 --- a/src/kernel/crt/file.cpp +++ b/src/kernel/crt/file.cpp @@ -296,7 +296,7 @@ u32 FindFirstFileA_entry(mapped_string lpFileName, mapped_void lpFindFileData) { } FillFindData(lpFindFileData, entry); - REXKRNL_TRACE("rexcrt_FindFirstFileA: '{}' first match='{}' handle={:#x}", path, entry->name(), + REXKRNL_DEBUG("rexcrt_FindFirstFileA: '{}' first match='{}' handle={:#x}", path, entry->name(), xfile->handle()); return xfile->handle(); } @@ -326,24 +326,8 @@ u32 CreateDirectoryA_entry(mapped_string lpPathName, mapped_void lpSecurityAttri } u32 MoveFileA_entry(mapped_string lpExistingFileName, mapped_string lpNewFileName) { - const char* src = static_cast(lpExistingFileName); - const char* dst = static_cast(lpNewFileName); - - auto* fs = REX_KERNEL_FS(); - auto* src_entry = fs->ResolvePath(src); - if (!src_entry) { - REXKRNL_DEBUG("rexcrt_MoveFileA: source not found '{}'", src); - return 0; - } - // Win32 MoveFileA fails if the destination already exists; callers wanting - // overwrite semantics use MoveFileExA with MOVEFILE_REPLACE_EXISTING. - if (fs->ResolvePath(dst)) { - REXKRNL_DEBUG("rexcrt_MoveFileA: destination exists '{}'", dst); - return 0; - } - - src_entry->Rename(rex::to_path(dst)); - REXKRNL_TRACE("rexcrt_MoveFileA: '{}' -> '{}'", src, dst); + REXKRNL_WARN("rexcrt_MoveFileA: STUB '{}' -> '{}'", static_cast(lpExistingFileName), + static_cast(lpNewFileName)); return 1; } @@ -360,7 +344,7 @@ u32 GetFileAttributesA_entry(mapped_string lpFileName) { REXKRNL_DEBUG("rexcrt_GetFileAttributesA: not found '{}'", path); return kInvalidHandleValue; // INVALID_FILE_ATTRIBUTES } - REXKRNL_TRACE("rexcrt_GetFileAttributesA: '{}' -> attrs={:#x}", path, entry->attributes()); + REXKRNL_DEBUG("rexcrt_GetFileAttributesA: '{}' -> attrs={:#x}", path, entry->attributes()); return entry->attributes(); } @@ -505,7 +489,7 @@ u32 CopyFileA_entry(mapped_string lpExistingFileName, mapped_string lpNewFileNam dst_file->Destroy(); src_file->Destroy(); - REXKRNL_TRACE("rexcrt_CopyFileA: '{}' -> '{}' {}", src, dst, ok ? "OK" : "FAILED"); + REXKRNL_DEBUG("rexcrt_CopyFileA: '{}' -> '{}' {}", src, dst, ok ? "OK" : "FAILED"); return ok ? 1u : 0u; } @@ -560,7 +544,7 @@ u32 GetDiskFreeSpaceExA_entry(mapped_string lpDirectoryName, out[1] = static_cast(free_bytes >> 32); } - REXKRNL_TRACE("rexcrt_GetDiskFreeSpaceExA: '{}' total={}MB free={}MB", path, + REXKRNL_DEBUG("rexcrt_GetDiskFreeSpaceExA: '{}' total={}MB free={}MB", path, total_bytes / (1024 * 1024), free_bytes / (1024 * 1024)); return 1; } diff --git a/src/kernel/xboxkrnl/xboxkrnl_threading.cpp b/src/kernel/xboxkrnl/xboxkrnl_threading.cpp index 4351404..ab2afbc 100644 --- a/src/kernel/xboxkrnl/xboxkrnl_threading.cpp +++ b/src/kernel/xboxkrnl/xboxkrnl_threading.cpp @@ -1346,7 +1346,7 @@ u32 InterlockedPushEntrySList_entry(ppc_ptr_t plist_ptr, assert_not_null(entry); alignas(8) X_SLIST_HEADER old_hdr = *plist_ptr; - alignas(8) X_SLIST_HEADER new_hdr = {}; + alignas(8) X_SLIST_HEADER new_hdr = {0}; uint32_t old_head = 0; do { old_hdr = *plist_ptr; diff --git a/src/system/kernel_state.cpp b/src/system/kernel_state.cpp index 6e14ffe..5dc922d 100644 --- a/src/system/kernel_state.cpp +++ b/src/system/kernel_state.cpp @@ -1017,54 +1017,57 @@ void KernelState::CompleteOverlappedImmediateEx(uint32_t overlapped_ptr, X_RESUL CompleteOverlappedEx(overlapped_ptr, result, extended_error, length); } -void KernelState::CompleteOverlappedDeferred(std::function completion_callback, +void KernelState::CompleteOverlappedDeferred(std::move_only_function completion_callback, uint32_t overlapped_ptr, X_RESULT result, - std::function pre_callback, - std::function post_callback) { + std::move_only_function pre_callback, + std::move_only_function post_callback) { CompleteOverlappedDeferredEx(std::move(completion_callback), overlapped_ptr, result, result, 0, - pre_callback, post_callback); + std::move(pre_callback), std::move(post_callback)); } -void KernelState::CompleteOverlappedDeferredEx(std::function completion_callback, +void KernelState::CompleteOverlappedDeferredEx(std::move_only_function completion_callback, uint32_t overlapped_ptr, X_RESULT result, uint32_t extended_error, uint32_t length, - std::function pre_callback, - std::function post_callback) { + std::move_only_function pre_callback, + std::move_only_function post_callback) { CompleteOverlappedDeferredEx( - [completion_callback, result, extended_error, length](uint32_t& cb_extended_error, - uint32_t& cb_length) -> X_RESULT { + [completion_callback = std::move(completion_callback), result, extended_error, length]( + uint32_t& cb_extended_error, uint32_t& cb_length) mutable -> X_RESULT { completion_callback(); cb_extended_error = extended_error; cb_length = length; return result; }, - overlapped_ptr, pre_callback, post_callback); + overlapped_ptr, std::move(pre_callback), std::move(post_callback)); } -void KernelState::CompleteOverlappedDeferred(std::function completion_callback, +void KernelState::CompleteOverlappedDeferred(std::move_only_function completion_callback, uint32_t overlapped_ptr, - std::function pre_callback, - std::function post_callback) { + std::move_only_function pre_callback, + std::move_only_function post_callback) { CompleteOverlappedDeferredEx( - [completion_callback](uint32_t& extended_error, uint32_t& length) -> X_RESULT { + [completion_callback = std::move(completion_callback)](uint32_t& extended_error, + uint32_t& length) mutable -> X_RESULT { auto result = completion_callback(); extended_error = static_cast(result); length = 0; return result; }, - overlapped_ptr, pre_callback, post_callback); + overlapped_ptr, std::move(pre_callback), std::move(post_callback)); } void KernelState::CompleteOverlappedDeferredEx( - std::function completion_callback, uint32_t overlapped_ptr, - std::function pre_callback, std::function post_callback) { + std::move_only_function completion_callback, + uint32_t overlapped_ptr, std::move_only_function pre_callback, + std::move_only_function post_callback) { REXSYS_DEBUG("CompleteOverlappedDeferredEx: queuing for overlapped {:08X}", overlapped_ptr); auto ptr = memory()->TranslateVirtual(overlapped_ptr); XOverlappedSetResult(ptr, X_ERROR_IO_PENDING); XOverlappedSetContext(ptr, XThread::GetCurrentThreadHandle()); auto global_lock = global_critical_region_.Acquire(); dispatch_queue_.push_back( - [this, completion_callback, overlapped_ptr, pre_callback, post_callback]() { + [this, overlapped_ptr, completion_callback = std::move(completion_callback), + pre_callback = std::move(pre_callback), post_callback = std::move(post_callback)]() mutable { REXSYS_DEBUG("Deferred overlapped {:08X}: running pre_callback", overlapped_ptr); if (pre_callback) { pre_callback(); diff --git a/src/system/runtime.cpp b/src/system/runtime.cpp index f9eac23..5d66ca3 100644 --- a/src/system/runtime.cpp +++ b/src/system/runtime.cpp @@ -62,6 +62,11 @@ X_STATUS Runtime::Setup(RuntimeConfig config) { } instance_ = this; + if (!config.config_path.empty() && std::filesystem::exists(config.config_path)) { + REXSYS_INFO("Runtime applying config: {}", config.config_path.string()); + rex::cvar::LoadConfig(config.config_path); + } + auto fail = [this](X_STATUS status, std::string_view reason) { REXSYS_ERROR("Runtime::Setup failed: {}", reason); Shutdown(); diff --git a/src/system/user_module.cpp b/src/system/user_module.cpp index 6255e3c..1d26f0a 100644 --- a/src/system/user_module.cpp +++ b/src/system/user_module.cpp @@ -23,8 +23,7 @@ #include #include -REXCVAR_DEFINE_BOOL(xex_apply_patches, false, "Kernel", - "Search for and apply XEX patches (path + 'p') on module load"); +REXCVAR_DEFINE_BOOL(xex_apply_patches, true, "Kernel", "Apply XEX patches"); namespace rex::system { diff --git a/src/system/util/xdbf_utils.cpp b/src/system/util/xdbf_utils.cpp index 8250565..086672f 100644 --- a/src/system/util/xdbf_utils.cpp +++ b/src/system/util/xdbf_utils.cpp @@ -59,7 +59,7 @@ XdbfBlock XdbfWrapper::GetEntry(XdbfSection section, uint64_t id) const { return block; } } - return {}; + return {0}; } std::string XdbfWrapper::GetStringTableEntry(XLanguage language, uint16_t string_id) const { diff --git a/src/system/xmemory.cpp b/src/system/xmemory.cpp index ca7f4e8..35f81a9 100644 --- a/src/system/xmemory.cpp +++ b/src/system/xmemory.cpp @@ -159,7 +159,6 @@ bool Memory::Initialize() { } virtual_membase_ = mapping_base_; physical_membase_ = mapping_base_ + 0x100000000ull; - // Prepare virtual heaps. heaps_.v00000000.Initialize(this, virtual_membase_, memory::HeapType::kGuestVirtual, 0x00000000, 0x40000000, 4096); @@ -186,6 +185,16 @@ bool Memory::Initialize() { !REXCVAR_GET(protect_zero) ? memory::kMemoryProtectRead | memory::kMemoryProtectWrite : memory::kMemoryProtectNoAccess); + // Some titles read through a null base with a positive offset above the + // protected zero page during startup. Keep the guest heap metadata unchanged, + // but commit this host page so raw recompiled loads don't crash before guest + // initialization can finish. + if (!rex::memory::AllocFixed(virtual_membase_ + 0x10000, 0x10000, + rex::memory::AllocationType::kCommit, + rex::memory::PageAccess::kReadWrite)) { + REXSYS_ERROR("Unable to commit low null-offset page at 0x00010000"); + return false; + } heaps_.physical.AllocFixed(0x1FFF0000, 0x10000, 0x10000, memory::kMemoryAllocationReserve, memory::kMemoryProtectNoAccess);