Add FFDec docs/tools and server-browser fixes

Add comprehensive FFDec walkthrough and short patch docs for safely adding a Multiplayer item to MainMenu.swf, plus helper scripts to extract the vanilla SWF and restore it (tools/extract_ba2.py, tools/restore-vanilla-mainmenu.ps1). Update exported MainMenu.as with a warning comment and remove an outdated exported PATCH markdown. Patch binary Interface/MainMenu.swf (updated).

Hardening fixes in the plugin: include GFx headers, avoid leaving the PrismaUI overlay focused after menu changes, only call OnBrowserHidden when the browser was actually visible, and add IsMainMenuOnMainPanel() so WatchMainMenuState ignores MainMenu when not on the MAIN_STATE. Ensure events are only dispatched when the browser is valid & visible (CanDispatchToBrowser) and make Scaleform UI hide the browser immediately during transitions. These changes prevent the overlay from staying focused during Settings transitions and stop JS events from being sent to a hidden/invalid view.
This commit is contained in:
2026-06-08 22:26:00 +12:00
parent 50d48f1f99
commit 9c84c9d4bc
10 changed files with 636 additions and 165 deletions
+35 -12
View File
@@ -6,7 +6,11 @@
#include "F4TServerBrowserBridge.h"
#include "PrismaUI_F4_API.h"
#include "Scaleform/G/GFx_Movie.h"
#include "Scaleform/G/GFx_Value.h"
#include <cstdio>
#include <cstring>
#include <nlohmann/json.hpp>
#include <string_view>
@@ -73,24 +77,43 @@ namespace F4T::PrismaUI
void HideServerBrowserInternal()
{
if (!g_visible) {
return;
}
if (!g_api || !g_api->IsValid(g_serverBrowserView)) {
g_visible = false;
F4T::MenuInput::SetBrowserActive(false);
return;
}
const bool wasVisible = g_visible;
g_visible = false;
F4T::MenuInput::SetBrowserActive(false);
F4T::ServerBrowser::ServerBrowserBridge::OnBrowserHidden();
if (!g_api || !g_api->IsValid(g_serverBrowserView)) {
return;
}
if (wasVisible) {
F4T::ServerBrowser::ServerBrowserBridge::OnBrowserHidden();
}
g_api->Unfocus(g_serverBrowserView);
g_api->Hide(g_serverBrowserView);
RequestNativeCursorRestore();
}
bool IsMainMenuOnMainPanel(const RE::UI* a_ui)
{
if (!a_ui) {
return false;
}
const auto mainMenu = a_ui->GetMenu<RE::MainMenu>();
if (!mainMenu || !mainMenu->uiMovie) {
return false;
}
Scaleform::GFx::Value state;
if (!mainMenu->uiMovie->GetVariable(&state, "currentState") || !state.IsString()) {
return true;
}
const char* currentState = state.GetString();
return currentState == nullptr || std::strcmp(currentState, "MAIN_STATE") == 0;
}
void WatchMainMenuState()
{
if (!g_visible) {
@@ -98,7 +121,7 @@ namespace F4T::PrismaUI
}
const auto* ui = RE::UI::GetSingleton();
if (!ui || !ui->GetMenuOpen<RE::MainMenu>()) {
if (!ui || !ui->GetMenuOpen<RE::MainMenu>() || !IsMainMenuOnMainPanel(ui)) {
HideServerBrowserInternal();
}
}
+4 -3
View File
@@ -40,15 +40,16 @@ namespace F4T::ScaleformUI
{
(void)a_params;
// Hide immediately so the overlay cannot stay focused while the
// main menu transitions into Settings sub-panels (Display, Audio, etc.).
F4T::PrismaUI::HideServerBrowser();
const auto* taskInterface = F4SE::GetTaskInterface();
if (taskInterface) {
taskInterface->AddTask([]() {
F4T::PrismaUI::HideServerBrowser();
});
return;
}
F4T::PrismaUI::HideServerBrowser();
}
};
+12 -2
View File
@@ -1,6 +1,7 @@
#include "pch.h"
#include "F4TNetworking.h"
#include "F4TPrismaUI.h"
#include "F4TServerBrowserBridge.h"
#include "F4TServerBrowserData.h"
@@ -30,9 +31,18 @@ namespace F4T::ServerBrowser
REX::WARN("{}{}", GetLogPrefix(), a_message);
}
bool CanDispatchToBrowser()
{
return g_api &&
g_view != 0 &&
g_api->IsValid(g_view) &&
!g_api->IsHidden(g_view) &&
F4T::PrismaUI::IsBrowserVisible();
}
void DispatchEvent(const nlohmann::json& a_event)
{
if (!g_api || g_view == 0 || !g_api->IsValid(g_view)) {
if (!CanDispatchToBrowser()) {
return;
}
@@ -59,7 +69,7 @@ namespace F4T::ServerBrowser
{ "serverCount", g_data.GetFilteredServers().size() }
});
if (g_api && g_view != 0 && g_api->IsValid(g_view)) {
if (CanDispatchToBrowser()) {
const auto prefix = F4T::Networking::GetLocalPlayerLogPrefix();
const auto script = std::format(
"if(window.setConnectionStatus){{window.setConnectionStatus({},{});}}",