align module and memory behavior for Skate 3

This commit is contained in:
mchughalex 2026-05-21 02:14:09 +01:00
parent 7325992255
commit 1081b0ea27
12 changed files with 131 additions and 73 deletions

View file

@ -63,6 +63,7 @@ struct RuntimeConfig {
std::function<std::unique_ptr<system::IAudioSystem>(runtime::FunctionDispatcher*)> audio_factory; std::function<std::unique_ptr<system::IAudioSystem>(runtime::FunctionDispatcher*)> audio_factory;
std::function<std::unique_ptr<system::IInputSystem>(bool tool_mode)> input_factory; std::function<std::unique_ptr<system::IInputSystem>(bool tool_mode)> input_factory;
std::function<void(Runtime*, system::KernelState*)> kernel_init; std::function<void(Runtime*, system::KernelState*)> kernel_init;
std::filesystem::path config_path;
bool tool_mode = false; bool tool_mode = false;
}; };

View file

@ -300,23 +300,24 @@ class KernelState {
void CompleteOverlappedImmediateEx(uint32_t overlapped_ptr, X_RESULT result, void CompleteOverlappedImmediateEx(uint32_t overlapped_ptr, X_RESULT result,
uint32_t extended_error, uint32_t length); uint32_t extended_error, uint32_t length);
void CompleteOverlappedDeferred(std::function<void()> completion_callback, void CompleteOverlappedDeferred(std::move_only_function<void()> completion_callback,
uint32_t overlapped_ptr, X_RESULT result, uint32_t overlapped_ptr, X_RESULT result,
std::function<void()> pre_callback = nullptr, std::move_only_function<void()> pre_callback = nullptr,
std::function<void()> post_callback = nullptr); std::move_only_function<void()> post_callback = nullptr);
void CompleteOverlappedDeferredEx(std::function<void()> completion_callback, void CompleteOverlappedDeferredEx(std::move_only_function<void()> completion_callback,
uint32_t overlapped_ptr, X_RESULT result, uint32_t overlapped_ptr, X_RESULT result,
uint32_t extended_error, uint32_t length, uint32_t extended_error, uint32_t length,
std::function<void()> pre_callback = nullptr, std::move_only_function<void()> pre_callback = nullptr,
std::function<void()> post_callback = nullptr); std::move_only_function<void()> post_callback = nullptr);
void CompleteOverlappedDeferred(std::function<X_RESULT()> completion_callback, void CompleteOverlappedDeferred(std::move_only_function<X_RESULT()> completion_callback,
uint32_t overlapped_ptr, uint32_t overlapped_ptr,
std::function<void()> pre_callback = nullptr, std::move_only_function<void()> pre_callback = nullptr,
std::function<void()> post_callback = nullptr); std::move_only_function<void()> post_callback = nullptr);
void CompleteOverlappedDeferredEx( void CompleteOverlappedDeferredEx(
std::function<X_RESULT(uint32_t&, uint32_t&)> completion_callback, uint32_t overlapped_ptr, std::move_only_function<X_RESULT(uint32_t&, uint32_t&)> completion_callback,
std::function<void()> pre_callback = nullptr, std::function<void()> post_callback = nullptr); uint32_t overlapped_ptr, std::move_only_function<void()> pre_callback = nullptr,
std::move_only_function<void()> post_callback = nullptr);
bool Save(stream::ByteStream* stream); bool Save(stream::ByteStream* stream);
bool Restore(stream::ByteStream* stream); bool Restore(stream::ByteStream* stream);
@ -373,7 +374,7 @@ class KernelState {
// Must be guarded by the global critical region. // Must be guarded by the global critical region.
util::NativeList dpc_list_; util::NativeList dpc_list_;
std::condition_variable_any dispatch_cond_; std::condition_variable_any dispatch_cond_;
std::list<std::function<void()>> dispatch_queue_; std::list<std::move_only_function<void()>> dispatch_queue_;
friend class XObject; friend class XObject;
}; };

View file

@ -26,8 +26,8 @@ class TimerQueue;
struct TimerQueueWaitItem { struct TimerQueueWaitItem {
using clock = std::chrono::steady_clock; using clock = std::chrono::steady_clock;
TimerQueueWaitItem(std::function<void(void*)> callback, void* userdata, TimerQueue* parent_queue, TimerQueueWaitItem(std::move_only_function<void(void*)> callback, void* userdata,
clock::time_point due, clock::duration interval) TimerQueue* parent_queue, clock::time_point due, clock::duration interval)
: callback_(std::move(callback)), : callback_(std::move(callback)),
userdata_(userdata), userdata_(userdata),
parent_queue_(parent_queue), parent_queue_(parent_queue),
@ -55,7 +55,7 @@ struct TimerQueueWaitItem {
}; };
static_assert(std::atomic<State>::is_always_lock_free); static_assert(std::atomic<State>::is_always_lock_free);
std::function<void(void*)> callback_; std::move_only_function<void(void*)> callback_;
void* userdata_; void* userdata_;
TimerQueue* parent_queue_; TimerQueue* parent_queue_;
clock::time_point due_; clock::time_point due_;
@ -63,14 +63,14 @@ struct TimerQueueWaitItem {
std::atomic<State> state_; std::atomic<State> state_;
}; };
std::weak_ptr<TimerQueueWaitItem> QueueTimerOnce(std::function<void(void*)> callback, std::weak_ptr<TimerQueueWaitItem> QueueTimerOnce(std::move_only_function<void(void*)> callback,
void* userdata, void* userdata,
TimerQueueWaitItem::clock::time_point due); TimerQueueWaitItem::clock::time_point due);
// Callback is first executed at due, then again repeatedly after interval // Callback is first executed at due, then again repeatedly after interval
// passes (unless interval == 0). The first callback will be scheduled at // passes (unless interval == 0). The first callback will be scheduled at
// `max(now() - interval, due)` to mitigate callback flooding. // `max(now() - interval, due)` to mitigate callback flooding.
std::weak_ptr<TimerQueueWaitItem> QueueTimerRecurring(std::function<void(void*)> callback, std::weak_ptr<TimerQueueWaitItem> QueueTimerRecurring(std::move_only_function<void(void*)> callback,
void* userdata, void* userdata,
TimerQueueWaitItem::clock::time_point due, TimerQueueWaitItem::clock::time_point due,
TimerQueueWaitItem::clock::duration interval); TimerQueueWaitItem::clock::duration interval);

View file

@ -537,20 +537,76 @@ bool IsFinalized() {
} }
void SaveConfig(const std::filesystem::path& config_path) { 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 { 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<uint64_t>(std::numeric_limits<int64_t>::max())) {
config.insert_or_assign(entry.name, static_cast<int64_t>(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); std::ofstream file(config_path);
if (!file) { if (!file) {
REXLOG_ERROR("SaveConfig: failed to open {}", config_path.string()); REXLOG_ERROR("SaveConfig: failed to open {}", config_path.string());
return; return;
} }
file << "# Auto-generated cvar configuration\n"; file << "# Auto-generated cvar configuration\n";
file << content; file << config;
REXLOG_INFO("Saved config to {}", config_path.string()); REXLOG_INFO("Saved config to {}", config_path.string());
} catch (const std::exception& e) { } catch (const std::exception& e) {
REXLOG_ERROR("SaveConfig: {}", e.what()); REXLOG_ERROR("SaveConfig: {}", e.what());

View file

@ -187,14 +187,14 @@ void TimerQueueWaitItem::Disarm() {
} }
} }
std::weak_ptr<WaitItem> QueueTimerOnce(std::function<void(void*)> callback, void* userdata, std::weak_ptr<WaitItem> QueueTimerOnce(std::move_only_function<void(void*)> callback,
WaitItem::clock::time_point due) { void* userdata, WaitItem::clock::time_point due) {
return timer_queue_.QueueTimer(std::make_shared<WaitItem>( return timer_queue_.QueueTimer(std::make_shared<WaitItem>(
std::move(callback), userdata, &timer_queue_, due, WaitItem::clock::duration::zero())); std::move(callback), userdata, &timer_queue_, due, WaitItem::clock::duration::zero()));
} }
std::weak_ptr<WaitItem> QueueTimerRecurring(std::function<void(void*)> callback, void* userdata, std::weak_ptr<WaitItem> QueueTimerRecurring(std::move_only_function<void(void*)> callback,
WaitItem::clock::time_point due, void* userdata, WaitItem::clock::time_point due,
WaitItem::clock::duration interval) { WaitItem::clock::duration interval) {
return timer_queue_.QueueTimer( return timer_queue_.QueueTimer(
std::make_shared<WaitItem>(std::move(callback), userdata, &timer_queue_, due, interval)); std::make_shared<WaitItem>(std::move(callback), userdata, &timer_queue_, due, interval));

View file

@ -296,7 +296,7 @@ u32 FindFirstFileA_entry(mapped_string lpFileName, mapped_void lpFindFileData) {
} }
FillFindData(lpFindFileData, entry); 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()); xfile->handle());
return 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) { u32 MoveFileA_entry(mapped_string lpExistingFileName, mapped_string lpNewFileName) {
const char* src = static_cast<const char*>(lpExistingFileName); REXKRNL_WARN("rexcrt_MoveFileA: STUB '{}' -> '{}'", static_cast<const char*>(lpExistingFileName),
const char* dst = static_cast<const char*>(lpNewFileName); static_cast<const char*>(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);
return 1; return 1;
} }
@ -360,7 +344,7 @@ u32 GetFileAttributesA_entry(mapped_string lpFileName) {
REXKRNL_DEBUG("rexcrt_GetFileAttributesA: not found '{}'", path); REXKRNL_DEBUG("rexcrt_GetFileAttributesA: not found '{}'", path);
return kInvalidHandleValue; // INVALID_FILE_ATTRIBUTES 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(); return entry->attributes();
} }
@ -505,7 +489,7 @@ u32 CopyFileA_entry(mapped_string lpExistingFileName, mapped_string lpNewFileNam
dst_file->Destroy(); dst_file->Destroy();
src_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; return ok ? 1u : 0u;
} }
@ -560,7 +544,7 @@ u32 GetDiskFreeSpaceExA_entry(mapped_string lpDirectoryName,
out[1] = static_cast<uint32_t>(free_bytes >> 32); out[1] = static_cast<uint32_t>(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)); total_bytes / (1024 * 1024), free_bytes / (1024 * 1024));
return 1; return 1;
} }

View file

@ -1346,7 +1346,7 @@ u32 InterlockedPushEntrySList_entry(ppc_ptr_t<X_SLIST_HEADER> plist_ptr,
assert_not_null(entry); assert_not_null(entry);
alignas(8) X_SLIST_HEADER old_hdr = *plist_ptr; 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; uint32_t old_head = 0;
do { do {
old_hdr = *plist_ptr; old_hdr = *plist_ptr;

View file

@ -1017,54 +1017,57 @@ void KernelState::CompleteOverlappedImmediateEx(uint32_t overlapped_ptr, X_RESUL
CompleteOverlappedEx(overlapped_ptr, result, extended_error, length); CompleteOverlappedEx(overlapped_ptr, result, extended_error, length);
} }
void KernelState::CompleteOverlappedDeferred(std::function<void()> completion_callback, void KernelState::CompleteOverlappedDeferred(std::move_only_function<void()> completion_callback,
uint32_t overlapped_ptr, X_RESULT result, uint32_t overlapped_ptr, X_RESULT result,
std::function<void()> pre_callback, std::move_only_function<void()> pre_callback,
std::function<void()> post_callback) { std::move_only_function<void()> post_callback) {
CompleteOverlappedDeferredEx(std::move(completion_callback), overlapped_ptr, result, result, 0, 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<void()> completion_callback, void KernelState::CompleteOverlappedDeferredEx(std::move_only_function<void()> completion_callback,
uint32_t overlapped_ptr, X_RESULT result, uint32_t overlapped_ptr, X_RESULT result,
uint32_t extended_error, uint32_t length, uint32_t extended_error, uint32_t length,
std::function<void()> pre_callback, std::move_only_function<void()> pre_callback,
std::function<void()> post_callback) { std::move_only_function<void()> post_callback) {
CompleteOverlappedDeferredEx( CompleteOverlappedDeferredEx(
[completion_callback, result, extended_error, length](uint32_t& cb_extended_error, [completion_callback = std::move(completion_callback), result, extended_error, length](
uint32_t& cb_length) -> X_RESULT { uint32_t& cb_extended_error, uint32_t& cb_length) mutable -> X_RESULT {
completion_callback(); completion_callback();
cb_extended_error = extended_error; cb_extended_error = extended_error;
cb_length = length; cb_length = length;
return result; return result;
}, },
overlapped_ptr, pre_callback, post_callback); overlapped_ptr, std::move(pre_callback), std::move(post_callback));
} }
void KernelState::CompleteOverlappedDeferred(std::function<X_RESULT()> completion_callback, void KernelState::CompleteOverlappedDeferred(std::move_only_function<X_RESULT()> completion_callback,
uint32_t overlapped_ptr, uint32_t overlapped_ptr,
std::function<void()> pre_callback, std::move_only_function<void()> pre_callback,
std::function<void()> post_callback) { std::move_only_function<void()> post_callback) {
CompleteOverlappedDeferredEx( 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(); auto result = completion_callback();
extended_error = static_cast<uint32_t>(result); extended_error = static_cast<uint32_t>(result);
length = 0; length = 0;
return result; return result;
}, },
overlapped_ptr, pre_callback, post_callback); overlapped_ptr, std::move(pre_callback), std::move(post_callback));
} }
void KernelState::CompleteOverlappedDeferredEx( void KernelState::CompleteOverlappedDeferredEx(
std::function<X_RESULT(uint32_t&, uint32_t&)> completion_callback, uint32_t overlapped_ptr, std::move_only_function<X_RESULT(uint32_t&, uint32_t&)> completion_callback,
std::function<void()> pre_callback, std::function<void()> post_callback) { uint32_t overlapped_ptr, std::move_only_function<void()> pre_callback,
std::move_only_function<void()> post_callback) {
REXSYS_DEBUG("CompleteOverlappedDeferredEx: queuing for overlapped {:08X}", overlapped_ptr); REXSYS_DEBUG("CompleteOverlappedDeferredEx: queuing for overlapped {:08X}", overlapped_ptr);
auto ptr = memory()->TranslateVirtual(overlapped_ptr); auto ptr = memory()->TranslateVirtual(overlapped_ptr);
XOverlappedSetResult(ptr, X_ERROR_IO_PENDING); XOverlappedSetResult(ptr, X_ERROR_IO_PENDING);
XOverlappedSetContext(ptr, XThread::GetCurrentThreadHandle()); XOverlappedSetContext(ptr, XThread::GetCurrentThreadHandle());
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
dispatch_queue_.push_back( 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); REXSYS_DEBUG("Deferred overlapped {:08X}: running pre_callback", overlapped_ptr);
if (pre_callback) { if (pre_callback) {
pre_callback(); pre_callback();

View file

@ -62,6 +62,11 @@ X_STATUS Runtime::Setup(RuntimeConfig config) {
} }
instance_ = this; 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) { auto fail = [this](X_STATUS status, std::string_view reason) {
REXSYS_ERROR("Runtime::Setup failed: {}", reason); REXSYS_ERROR("Runtime::Setup failed: {}", reason);
Shutdown(); Shutdown();

View file

@ -23,8 +23,7 @@
#include <rex/system/xfile.h> #include <rex/system/xfile.h>
#include <rex/system/xthread.h> #include <rex/system/xthread.h>
REXCVAR_DEFINE_BOOL(xex_apply_patches, false, "Kernel", REXCVAR_DEFINE_BOOL(xex_apply_patches, true, "Kernel", "Apply XEX patches");
"Search for and apply XEX patches (path + 'p') on module load");
namespace rex::system { namespace rex::system {

View file

@ -59,7 +59,7 @@ XdbfBlock XdbfWrapper::GetEntry(XdbfSection section, uint64_t id) const {
return block; return block;
} }
} }
return {}; return {0};
} }
std::string XdbfWrapper::GetStringTableEntry(XLanguage language, uint16_t string_id) const { std::string XdbfWrapper::GetStringTableEntry(XLanguage language, uint16_t string_id) const {

View file

@ -159,7 +159,6 @@ bool Memory::Initialize() {
} }
virtual_membase_ = mapping_base_; virtual_membase_ = mapping_base_;
physical_membase_ = mapping_base_ + 0x100000000ull; physical_membase_ = mapping_base_ + 0x100000000ull;
// Prepare virtual heaps. // Prepare virtual heaps.
heaps_.v00000000.Initialize(this, virtual_membase_, memory::HeapType::kGuestVirtual, 0x00000000, heaps_.v00000000.Initialize(this, virtual_membase_, memory::HeapType::kGuestVirtual, 0x00000000,
0x40000000, 4096); 0x40000000, 4096);
@ -186,6 +185,16 @@ bool Memory::Initialize() {
!REXCVAR_GET(protect_zero) !REXCVAR_GET(protect_zero)
? memory::kMemoryProtectRead | memory::kMemoryProtectWrite ? memory::kMemoryProtectRead | memory::kMemoryProtectWrite
: memory::kMemoryProtectNoAccess); : 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, heaps_.physical.AllocFixed(0x1FFF0000, 0x10000, 0x10000, memory::kMemoryAllocationReserve,
memory::kMemoryProtectNoAccess); memory::kMemoryProtectNoAccess);