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:
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user