Keyboard input for friend code entry (#327)

* keyboard input for friend code entry

* add new guest imports to synthetic test
This commit is contained in:
patchzyy
2026-10-21 18:54:05 +02:00
committed by GitHub
parent e9f1fdd77c
commit 0ae0d6391c
6 changed files with 159 additions and 6 deletions
+7 -6
View File
@@ -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
+2
View File
@@ -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() {
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <aurora/event.h>
// Lets the keyboard type into Retro Rewind's friend code entry page.
namespace FriendCodeKeyboard {
void HandleEvents(const AuroraEvent* events) noexcept;
} // namespace FriendCodeKeyboard
+4
View File
@@ -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)
+3
View File
@@ -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
+135
View File
@@ -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 <SDL3/SDL_events.h>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <optional>
#include <vector>
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<int> g_pending;
std::atomic_bool g_pageFocused{false};
std::optional<int> 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<uint32_t> 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<int>(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<uint32_t> 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<int> 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<int> 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");