Add controller support and safe main menu hooks

Enable gamepad navigation for the PrismaUI server browser and install MainMenu input hooks safely. Poll XInput each game-thread tick and forward button/stick changes to the browser (ForwardButtonEvent / ForwardThumbstickEvent), allocate an input-enable layer to suppress vanilla kMenu events while the browser is open, and register polling via the F4SE task interface. Replace direct vtable writes that caused a loading-screen crash with REL::Relocation::write_vfunc on the MainMenu BSInputEventUser secondary vtable and install OnButton/OnThumbstick hooks once at plugin Install(). Add MainMenu API to suppress inputEventHandling and call it when showing/hiding the browser. Also update PrismaUI browser JS to support controller focus for direct-connect, filter activation/cycling, improved controller prompts, and add CSS for focused direct-connect fields. Files touched include plugin/src/F4TMenuInput.cpp, plugin/src/F4TMainMenuInject.cpp, plugin/include headers, PrismaUI view JS/components, styles, and documentation.
This commit is contained in:
2026-06-18 14:26:33 +12:00
parent 3d14918ee8
commit f720d35d2a
11 changed files with 626 additions and 102 deletions
+34 -13
View File
@@ -397,25 +397,46 @@ listWidget.Invoke("addEventListener", nullptr, args.data(), args.size());
## Step 5 — Handle gamepad / Enter with `OnButtonEvent`
Mouse clicks go through `ITEM_PRESS`. Keyboard Enter and gamepad A go through `StartMenuBase::OnButtonEvent` (vtable index **`0x08`**).
Mouse clicks go through `ITEM_PRESS`. Keyboard Enter and gamepad A go through `IMenu::OnButtonEvent`, which lives on the **`BSInputEventUser` secondary vtable** (not the primary `StartMenuBase` vtable).
Do **not** hook `StartMenuBase::VTABLE[0x08]` for button input — on the primary vtable that index is `IMenu::SetIsTopButtonBar`, so the main menu will keep receiving navigation while your overlay is open.
Patch the MainMenu input vtable when the plugin loads (once), using `REL::Relocation` and `write_vfunc` so the write goes through `WriteSafeData` (the vtable lives in read-only memory — **do not** assign to vtable slots directly):
```cpp
void OnButtonEventHook(RE::IMenu* a_this, const RE::ButtonEvent* a_event)
constexpr auto kBSInputOnThumbstickVtableIndex = 0x04;
constexpr auto kBSInputOnButtonVtableIndex = 0x08;
void InstallMainMenuInputHooks()
{
if (a_this && a_event && a_event->QJustPressed() && !a_event->QDisabled()) {
if (a_event->QUserEvent() == "Accept"sv) {
auto* ui = RE::UI::GetSingleton();
auto mainMenu = ui ? ui->GetMenu<RE::MainMenu>() : nullptr;
if (mainMenu && mainMenu.get() == a_this &&
TryHandleCustomSelection(mainMenu.get())) {
return; // consumed — do not call vanilla
}
// MainMenu::VTABLE[1] is the BSInputEventUser secondary vtable.
REL::Relocation<std::uintptr_t> inputVtable{ RE::MainMenu::VTABLE[1] };
g_originalOnThumbstick = inputVtable.write_vfunc(
kBSInputOnThumbstickVtableIndex, &MainMenuOnThumbstickEventHook);
g_originalOnButton = inputVtable.write_vfunc(
kBSInputOnButtonVtableIndex, &MainMenuOnButtonEventHook);
}
```
While your overlay is open, also set `mainMenu->inputEventHandlingEnabled = false` so `ProcessUserEvent` does not keep driving the vanilla list.
```cpp
void MainMenuOnButtonEventHook(RE::BSInputEventUser* a_this, const RE::ButtonEvent* a_event)
{
if (BrowserIsVisible()) {
ForwardBrowserButton(a_event);
return;
}
if (a_event && a_event->QJustPressed() && a_event->QUserEvent() == "Accept"sv) {
auto* menu = reinterpret_cast<RE::IMenu*>(reinterpret_cast<char*>(a_this) - kBSInputEventUserOffset);
auto mainMenu = RE::UI::GetSingleton()->GetMenu<RE::MainMenu>();
if (mainMenu && mainMenu.get() == menu && TryHandleCustomSelection(mainMenu.get())) {
return; // consumed — do not call vanilla
}
}
if (g_originalOnButtonEvent) {
g_originalOnButtonEvent(a_this, a_event);
}
CallOriginalOnButtonEvent(a_this, a_event);
}
```
+105
View File
@@ -7,6 +7,111 @@ failed experiments, successful tests, and next steps.
---
## 2026-06-18 - XInput polling for server browser gamepad
### Summary
Gamepad input still did not reach the PrismaUI browser because Fallout routes controller through the main-menu input stack, while keyboard arrows worked via the web view `keydown` handler. Added direct XInput polling while the browser is open.
### Files Changed
- `plugin/src/F4TMenuInput.cpp`
### Details
- Poll `XInputGetState` each game-thread tick while the server browser is active and forward button/stick changes through the existing `controllerInput` JS channel.
- Allocate a `BSInputEnableLayer` that disables `kMenu` user events while the browser is open so the title menu stops consuming gamepad input.
- Menu vtable hooks now only block/supplement menu-translated keyboard events; gamepad is handled by polling.
### Testing
- Plugin rebuild succeeded; in-game controller retest required.
### Known Issues
- None currently known.
### Next Steps
- Verify D-pad, A/B/X/Y, shoulders, and left stick navigate/close the server browser.
---
## 2026-06-18 - Fix loading-screen crash from vtable patch
### Summary
Fixed a crash during the "Please stand by" / main-menu load caused by writing hook pointers directly into the read-only `BSInputEventUser` vtable.
### Files Changed
- `plugin/src/F4TMainMenuInject.cpp`
- `docs/MainMenu_Button_Injection_Guide.md`
### Details
- `CommonwealthOnline.log` stopped after `Running catch-up injection for already-open MainMenu` — crash was in the old `InstallMainMenuInputHooks` before the success log.
- Replaced raw `slots[i] = hook` with `REL::Relocation{ RE::MainMenu::VTABLE[1] }.write_vfunc(...)`.
- Hooks install once at plugin `Install()` instead of per menu open/close.
### Testing
- Plugin rebuild succeeded; in-game retest required.
### Known Issues
- None currently known.
### Next Steps
- Confirm title screen loads and controller drives the server browser.
---
## 2026-06-18 - Fix Multiplayer Menu Controller Input Capture
### Summary
Fixed controller input staying on the vanilla main menu while the server browser was open. The previous hooks targeted the wrong vtable slots on `StartMenuBase` (index `0x08` is `SetIsTopButtonBar`, not `OnButtonEvent`).
### Files Changed
- `plugin/src/F4TMainMenuInject.cpp`
- `plugin/include/F4TMainMenuInject.h`
- `plugin/src/F4TPrismaUI.cpp`
### Details
- Hook `BSInputEventUser::OnButtonEvent` / `OnThumbstickEvent` through the MainMenu secondary vtable at object offset `0x10`.
- While the browser is visible, forward gamepad/keyboard menu events to the PrismaUI browser and suppress vanilla `ProcessUserEvent` via `inputEventHandlingEnabled = false`.
- Restore hooks when MainMenu closes.
### Testing
- Plugin rebuild succeeded; in-game controller test still required.
### Known Issues
- None currently known beyond prior direct-connect text entry limitation.
### Next Steps
- Verify in-game with a controller on the title screen.
---
## 2026-06-18 - Multiplayer Menu Controller Support
### Summary
Wired full gamepad and keyboard-menu navigation for the PrismaUI server browser while it is open over the title-screen main menu. Main-menu input is now consumed so the vanilla list does not steal focus, and the browser UI handles tabs, filters, server list, and direct connect from a controller.
### Files Changed
- `plugin/src/F4TMenuInput.cpp`
- `plugin/include/F4TMenuInput.h`
- `plugin/src/F4TMainMenuInject.cpp`
- `ui/views/CommonwealthOnline/browser/app.js`
- `ui/views/CommonwealthOnline/browser/components/controller-prompts.js`
- `ui/views/CommonwealthOnline/browser/components/direct-connect.js`
- `ui/views/CommonwealthOnline/browser/styles.css`
### Details
- Added `ForwardButtonEvent` / `ForwardThumbstickEvent` helpers that map gamepad buttons, left-stick directions, and menu user events (`Accept`, `Cancel`, `Up`, etc.) into the existing `controllerInput` browser event channel.
- Hooked `StartMenuBase::OnThumbstickEvent` and extended `OnButtonEvent` to capture input while the server browser is visible, blocking vanilla main-menu navigation underneath.
- Browser UI: direct-connect field focus, filter change on A, updated controller prompts, and keyboard nav parity for direct connect.
### Testing
- Not run in-game locally (requires Fallout 4 + F4SE + rebuilt plugin + staged PrismaUI views).
### Known Issues
- Text entry in Direct Connect still requires keyboard/mouse; controller can select fields and press A to connect with existing values.
### Next Steps
- Add on-screen keyboard or character entry flow for direct-connect address editing with a gamepad.
---
## 2026-06-17 - Fallout 4/Data Build Layout + Creation Kit Assets
### Summary