diff --git a/Launcher/Test-Recompilation.ps1 b/Launcher/Test-Recompilation.ps1 index f30a77b..ab3ae91 100644 --- a/Launcher/Test-Recompilation.ps1 +++ b/Launcher/Test-Recompilation.ps1 @@ -54,13 +54,14 @@ foreach ($relative in $sourceFiles | Sort-Object -Unique) { } # One synthetic text section: li r3,40; addi r3,r3,2; nop; blr. -# Native HLE wrappers also call these eight guest symbols directly. Give each +# Native HLE wrappers also call these guest symbols directly. Give each # its own generated blr function so the real product can link without game code. # Keep this list explicit: a new unresolved guest dependency must fail the test. [uint32]$entry = 0x80001000L [uint32[]]$guestCallbacks = @( 0x8012B830L, 0x801A0620L, 0x801A1ED8L, 0x801A961CL, - 0x801AADE0L, 0x801D8D30L, 0x801D9E94L, 0x8055531CL + 0x801AADE0L, 0x801D8D30L, 0x801D9E94L, 0x8055531CL, + 0x80601C64L, 0x8061E348L, 0x8061E3B4L ) $textSize = [int]($guestCallbacks[-1] - $entry + 4) $dataOffset = 0x100 + $textSize @@ -75,9 +76,9 @@ Write-BigEndian32 0x00 0x100 # text[0] file offset Write-BigEndian32 0x48 $entry # text[0] guest address Write-BigEndian32 0x90 $textSize # text[0] length (unreachable gaps are zero) Write-BigEndian32 0x1C $dataOffset # data[0] file offset -Write-BigEndian32 0x64 0x80600000L # data[0] guest address +Write-BigEndian32 0x64 0x80700000L # data[0] guest address Write-BigEndian32 0xAC 4 # data[0] length -Write-BigEndian32 0xD8 0x80601000L # BSS address +Write-BigEndian32 0xD8 0x80701000L # BSS address Write-BigEndian32 0xDC 32 # BSS length Write-BigEndian32 0xE0 $entry # entry point Write-BigEndian32 0x100 0x38600028 # li r3,40 @@ -102,8 +103,8 @@ project: memory: base: 0x80000000 size: 0x01800000 - sda_base: 0x80600000 - sda2_base: 0x80600000 + sda_base: 0x80700000 + sda2_base: 0x80700000 inputs: dol: path: synthetic.dol diff --git a/runtime/include/aurora_events.h b/runtime/include/aurora_events.h index d76450b..211ebf6 100644 --- a/runtime/include/aurora_events.h +++ b/runtime/include/aurora_events.h @@ -1,5 +1,6 @@ #pragma once +#include "friend_code_keyboard.h" #include "settings_overlay.h" #include "runtime_config.h" #include "gx_worker.h" @@ -118,6 +119,7 @@ inline void ProcessAuroraEvents(const AuroraEvent* events) { g_mkwDynamicAspectSurfacePending.store(true, std::memory_order_release); } settings_overlay::HandleEvents(events); + FriendCodeKeyboard::HandleEvents(events); } inline void ApplyPendingMkwDynamicAspectSurface() { diff --git a/runtime/include/friend_code_keyboard.h b/runtime/include/friend_code_keyboard.h new file mode 100644 index 0000000..f0b8387 --- /dev/null +++ b/runtime/include/friend_code_keyboard.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +// Lets the keyboard type into Retro Rewind's friend code entry page. +namespace FriendCodeKeyboard { +void HandleEvents(const AuroraEvent* events) noexcept; +} // namespace FriendCodeKeyboard diff --git a/runtime/include/guest_abi/guest_address_table.inc b/runtime/include/guest_abi/guest_address_table.inc index 8cd5f76..9abf45c 100644 --- a/runtime/include/guest_abi/guest_address_table.inc +++ b/runtime/include/guest_abi/guest_address_table.inc @@ -729,6 +729,10 @@ MKW_GUEST_ADDRESS(805529A8, 8054CA50, 80552328) // THP::VideoDecoder MKW_GUEST_ADDRESS(80552A74, 8054CB1C, 805523F4) // THP::VideoDecoderOnMemory MKW_GUEST_ADDRESS(8055531C, 8054F2FC, 80554C9C) // RelProlog MKW_GUEST_ADDRESS(805653D0, 80561050, 80564D50) +MKW_GUEST_ADDRESS(80601C64, 805DD384, 806013D8) // Page::CheckActions +MKW_GUEST_ADDRESS(8061E348, 805EE30C, 8061DABC) // Pages::RegisterFriend::OnDigitClick +MKW_GUEST_ADDRESS(8061E3B4, 805EE378, 8061DB28) // Pages::RegisterFriend::OnBackSpaceClick +MKW_GUEST_ADDRESS(8061E804, 805EE7C8, 8061DF78) // Pages::RegisterFriend::GetRuntimeTypeInfo MKW_GUEST_ADDRESS(8088F400, 8088AFD0, 8088EA6C) MKW_GUEST_ADDRESS(8088F704, 8088B2D4, 8088ED70) MKW_GUEST_ADDRESS(8088FDB8, 8088B960, 8088F418) diff --git a/runtime/include/guest_function_imports.inc b/runtime/include/guest_function_imports.inc index e229163..4768345 100644 --- a/runtime/include/guest_function_imports.inc +++ b/runtime/include/guest_function_imports.inc @@ -8,3 +8,6 @@ MKW_GUEST_FUNCTION_IMPORT(801AADE0, 801AAD40, 801AAD00) // OS::__TimeToSystemTi MKW_GUEST_FUNCTION_IMPORT(801D8D30, 801D8C90, 801D8C50) // NHTTPStartup MKW_GUEST_FUNCTION_IMPORT(801D9E94, 801D9DF4, 801D9DB4) // NHTTPi_GetSystemInfoP MKW_GUEST_FUNCTION_IMPORT(8055531C, 8054F2FC, 80554C9C) // RelProlog +MKW_GUEST_FUNCTION_IMPORT(80601C64, 805DD384, 806013D8) // Page::CheckActions +MKW_GUEST_FUNCTION_IMPORT(8061E348, 805EE30C, 8061DABC) // Pages::RegisterFriend::OnDigitClick +MKW_GUEST_FUNCTION_IMPORT(8061E3B4, 805EE378, 8061DB28) // Pages::RegisterFriend::OnBackSpaceClick diff --git a/runtime/src/friend_code_keyboard.cpp b/runtime/src/friend_code_keyboard.cpp new file mode 100644 index 0000000..b4cc810 --- /dev/null +++ b/runtime/src/friend_code_keyboard.cpp @@ -0,0 +1,135 @@ +#include "friend_code_keyboard.h" + +#include "abi_bridge.h" +#include "guest_abi/guest_region.h" +#include "guest_function_imports.h" +#include "input_bindings.h" +#include "memory.h" +#include "runtime_product.h" + +#include + +#include +#include +#include +#include +#include + +namespace { + +// UI::RegisterFriendPage +constexpr uint32_t kDigitButtonsOffset = 0x44; +constexpr uint32_t kPushButtonSize = 0x254; +constexpr uint32_t kDigitButtonCount = 10; +constexpr uint32_t kButtonDigitOffset = 0x240; +constexpr uint32_t kPageStateOffset = 0x8; +constexpr uint32_t kPageStateActive = 4; +constexpr uint32_t kVtableGetTypeInfoOffset = 0x60; +constexpr int kBackspace = -1; +constexpr size_t kMaxPendingKeys = 32; + +std::mutex g_pendingMutex; +std::vector g_pending; +std::atomic_bool g_pageFocused{false}; + +std::optional KeyValue(const SDL_KeyboardEvent& key) { + if ((key.mod & (SDL_KMOD_CTRL | SDL_KMOD_ALT | SDL_KMOD_GUI)) != 0) { + return std::nullopt; + } + if (key.scancode == SDL_SCANCODE_BACKSPACE) { + return kBackspace; + } + if (key.repeat) { + return std::nullopt; + } + if (key.scancode == SDL_SCANCODE_0 || key.scancode == SDL_SCANCODE_KP_0) { + return 0; + } + if (key.scancode >= SDL_SCANCODE_1 && key.scancode <= SDL_SCANCODE_9) { + return 1 + (key.scancode - SDL_SCANCODE_1); + } + if (key.scancode >= SDL_SCANCODE_KP_1 && key.scancode <= SDL_SCANCODE_KP_9) { + return 1 + (key.scancode - SDL_SCANCODE_KP_1); + } + return std::nullopt; +} + +bool IsFocusedRegisterFriendPage(uint32_t page) { + try { + const uint32_t vtable = Memory::Read32(page); + return Memory::Read32(vtable + kVtableGetTypeInfoOffset) == MKW_GADDR(8061E804) && + Memory::Read32(page + kPageStateOffset) == kPageStateActive; + } catch (const Memory::AccessViolation&) { + return false; + } +} + +std::optional DigitButton(uint32_t page, int digit) { + for (uint32_t i = 0; i < kDigitButtonCount; ++i) { + const uint32_t button = page + kDigitButtonsOffset + i * kPushButtonSize; + if (static_cast(Memory::Read32(button + kButtonDigitOffset)) == digit) { + return button; + } + } + return std::nullopt; +} + +void Type(CpuContext* ctx, uint32_t page, int key) { + if (key == kBackspace) { + ctx->gpr[3] = page; + MKW_GUEST_FUNC(8061E3B4)(ctx); + return; + } + const std::optional button = DigitButton(page, key); + if (!button) { + return; + } + ctx->gpr[3] = page; + ctx->gpr[4] = *button; + ctx->gpr[5] = 0; + MKW_GUEST_FUNC(8061E348)(ctx); +} + +} // namespace + +namespace FriendCodeKeyboard { + +void HandleEvents(const AuroraEvent* events) noexcept { + if (!events || !g_pageFocused.load(std::memory_order_acquire) || InputBindings::InputBlocked()) { + return; + } + std::lock_guard lock(g_pendingMutex); + for (const AuroraEvent* ev = events; ev->type != AURORA_NONE; ++ev) { + if (ev->type != AURORA_SDL_EVENT || ev->sdl.type != SDL_EVENT_KEY_DOWN) { + continue; + } + const std::optional key = KeyValue(ev->sdl.key); + if (key && g_pending.size() < kMaxPendingKeys) { + g_pending.push_back(*key); + } + } +} + +} // namespace FriendCodeKeyboard + +// Page::CheckActions, only ever called for the topmost page. +extern "C" void Page_CheckActions_80601c64(CpuContext* ctx) { + const uint32_t page = ctx->gpr[3]; + MKW_GUEST_FUNC(80601C64)(ctx); + + const bool focused = RuntimeProduct::IsRetroRewind() && IsFocusedRegisterFriendPage(page); + g_pageFocused.store(focused, std::memory_order_release); + std::vector keys; + { + std::lock_guard lock(g_pendingMutex); + keys.swap(g_pending); + } + if (!focused || InputBindings::InputBlocked()) { + return; + } + for (const int key : keys) { + Type(ctx, page, key); + } +} + +REGISTER_NATIVE_FUNCTION_AS(0x80601C64, Page_CheckActions_80601c64, "Page_CheckActions_80601c64");