Files
Commonwealth-Online-Public/docs/FFDEC_MainMenu_Multiplayer_Guide.md
T
andrew d4d3268b12 Add runtime MainMenu injection and docs
Introduce a C++ runtime injection that adds a Multiplayer row to the Fallout 4 main menu instead of patching MainMenu.swf. Adds F4TMainMenuInject.h/cpp implementing injection, ITEM_PRESS handling, accept/button hooks, resync tasks and logging; integrates installation by calling MainMenuInject::Install() from PrismaUI init. Update PrismaUI to use the new helper and remove a duplicate IsMainMenuOnMainPanel implementation. Documentation updated to deprecate FFDec SWF patching (new docs/MainMenu_Injection.md, note in FFDEC guide, and setup.md) and restore-vanilla-mainmenu.ps1 messaging updated to instruct using the runtime injection.
2026-06-09 14:52:27 +12:00

320 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FFDec guide — add Multiplayer button to MainMenu.swf
> **Deprecated:** Use C++ runtime injection instead. See [`MainMenu_Injection.md`](MainMenu_Injection.md). Do not deploy a patched `MainMenu.swf` for Commonwealth Online.
Step-by-step instructions for adding the **Multiplayer** main-menu item using [JPEXS FFDec](https://github.com/jindrapetrik/jpexs-decompiler/releases), without breaking **Settings → Gameplay / Display / Audio**.
This guide is kept for historical reference only.
---
## Before you start
### Restore vanilla SWF
Always patch from the vanilla file, not an old broken copy:
```powershell
.\tools\restore-vanilla-mainmenu.ps1
```
Or manually copy `Interface/MainMenu.swf.vanilla.bak``Interface/MainMenu.swf`.
Vanilla size is **61,959 bytes**. If your file is **64 KB+**, it is probably a bad re-import — restore and start again.
### Requirements
- **FFDec** installed
- Game launched via **F4SE** with `CommonwealthOnline.dll` in `Data/F4SE/Plugins/`
- Loose Interface overrides enabled in `%USERPROFILE%/Documents/My Games/Fallout4/Fallout4Custom.ini`:
```ini
[Archive]
bInvalidateOlderFiles=1
```
### Critical rule
| Do | Don't |
|----|--------|
| Edit **individual methods** (double-click method in traits list) | Paste the full `MainMenu.as` export into the class editor |
| Save after each small edit | Use **Import script** on the whole `MainMenu` class |
| Keep SWF ~6263 KB | Re-export the entire decompiled script |
Full re-import recompiles `RequestOptions` and breaks settings sub-menus. Controls will still work; Gameplay / Display / Audio will crash.
---
## FFDec navigation
1. Open **`Interface/MainMenu.swf`** in FFDec.
2. Left tree: **scripts → MainMenu**.
3. You should see a **traits / members list** (constants, functions).
4. **Double-click a method name** (e.g. `InitList`) to open **only that method** in the editor.
5. After each edit: apply/save the method, then **File → Save** the SWF.
Do **not** use **Edit ActionScript** on the entire `MainMenu` class if FFDec offers a single giant class view — use per-method editing instead.
---
## Summary of changes
| # | Where in FFDec | What |
|---|----------------|------|
| 1 | Class constants | Add `MULTIPLAYER_INDEX` |
| 2 | Method `InitList` | Push Multiplayer row after Load |
| 3 | Method `onMainListItemPress` | Add `case` for Multiplayer |
| 4 | New method `CloseMultiplayerBrowser` | Helper (recommended) |
| 5 | Method `set currentState` | Call close helper when leaving main panel |
| 6 | Method `onMainListItemPress` | Close browser when another item is selected |
Changes **46** are recommended so the HTML browser closes immediately when opening Settings, etc. The plugin also hides the browser when `currentState` leaves `MAIN_STATE`, but the SWF hooks make it feel instant.
---
## Change 1 — Add index constant
**FFDec:** `MainMenu` → constants section (near other `*_INDEX` constants)
**Find this block** (search for `INSTALLED_CONTENT_INDEX`):
```actionscript
private const INSTALLED_CONTENT_INDEX:Number = 14;
private const MAIN_PANEL_BACKGROUND:uint = 0;
```
**Insert between those lines:**
```actionscript
private const MULTIPLAYER_INDEX:Number = 15;
```
Index `15` is unused in vanilla (highest defined index is `14`).
---
## Change 2 — Add menu row in `InitList`
**FFDec:** `MainMenu` → method **`InitList`**
**Find this block** (search for `"$LOAD"` push — it appears after the `$NEW` block on title screen):
```actionscript
this.MainPanel_mc.List_mc.entryList.push({
"text":"$LOAD",
"disabled":!this.HasRecentSave || !param6,
"index":this.LOAD_INDEX
});
if(param8 == true)
```
**Replace with** (add Multiplayer block between Load and `if(param8 == true)`):
```actionscript
this.MainPanel_mc.List_mc.entryList.push({
"text":"$LOAD",
"disabled":!this.HasRecentSave || !param6,
"index":this.LOAD_INDEX
});
if(!this.PauseMode)
{
this.MainPanel_mc.List_mc.entryList.push({
"text":"Multiplayer",
"index":this.MULTIPLAYER_INDEX
});
}
if(param8 == true)
```
**Notes:**
- `"Multiplayer"` is a literal label (no `$` prefix). Strings with `$` are looked up in `Interface/Translate_en.txt`.
- `!this.PauseMode` limits the row to the **title screen**, not the in-game pause menu.
- Row appears between **Load** and **Add-ons** (or PS5 transfer if enabled).
---
## Change 3 — Handle click in `onMainListItemPress`
**FFDec:** `MainMenu` → method **`onMainListItemPress`**
**Find this block** (search for `case this.HELP_INDEX:`):
```actionscript
break;
case this.HELP_INDEX:
this.StartState(this.HELP_STATE);
this.BGSCodeObj.PlayOKSound();
break;
```
**Insert immediately before** `case this.HELP_INDEX:`:
```actionscript
break;
case this.MULTIPLAYER_INDEX:
this.BGSCodeObj.PlayOKSound();
if(root.f4se != null && root.f4se.plugins != null && root.f4se.plugins.commonwealthOnline != null)
{
root.f4se.plugins.commonwealthOnline.openManager();
}
break;
case this.HELP_INDEX:
this.StartState(this.HELP_STATE);
this.BGSCodeObj.PlayOKSound();
break;
```
This calls the C++ API registered by `CommonwealthOnline.dll` (`openManager` → PrismaUI server browser). **F9** still toggles the same browser.
---
## Change 4 — Add `CloseMultiplayerBrowser` method
**FFDec:** `MainMenu`**add new method** (right-click class → Add script / Add method, or paste near other private helpers like `onMainListItemPress`)
**Add this entire method:**
```actionscript
private function CloseMultiplayerBrowser() : *
{
if(root.f4se != null && root.f4se.plugins != null && root.f4se.plugins.commonwealthOnline != null)
{
root.f4se.plugins.commonwealthOnline.closeManager();
}
}
```
---
## Change 5 — Close browser in `set currentState`
**FFDec:** `MainMenu` → method **`set currentState`** (may appear as `currentState` setter)
**Find:**
```actionscript
public function set currentState(param1:String) : *
{
this.strCurrentState = param1;
this.UpdateButtons(param1);
this.UpdateStateFocus(param1);
}
```
**Change to:**
```actionscript
public function set currentState(param1:String) : *
{
this.strCurrentState = param1;
if(param1 != this.MAIN_STATE)
{
this.CloseMultiplayerBrowser();
}
this.UpdateButtons(param1);
this.UpdateStateFocus(param1);
}
```
---
## Change 6 — Close browser when another main-menu item is selected
**FFDec:** `MainMenu` → method **`onMainListItemPress`** (same method as Change 3)
**Find** the start of the `else` branch, after the `while` loop that returns to main state:
```actionscript
while(this.strCurrentState != this.MAIN_STATE)
{
this.onCancelPress(true,false);
}
if(this.MainPanel_mc.List_mc.selectedEntry.index == this.CONTINUE_INDEX && this.PauseMode)
```
**Insert between the `while` loop and the `CONTINUE_INDEX` check:**
```actionscript
while(this.strCurrentState != this.MAIN_STATE)
{
this.onCancelPress(true,false);
}
if(this.MainPanel_mc.List_mc.selectedEntry.index != this.MULTIPLAYER_INDEX)
{
this.CloseMultiplayerBrowser();
}
if(this.MainPanel_mc.List_mc.selectedEntry.index == this.CONTINUE_INDEX && this.PauseMode)
```
---
## Save and deploy
1. **File → Save** in FFDec (overwrite `Interface/MainMenu.swf`).
2. Check file size: should be roughly **62,00063,500 bytes**. If **64,000+**, restore vanilla and redo using **method-only** edits.
3. Copy to game:
```
Fallout 4/Data/Interface/MainMenu.swf
```
4. Launch via **F4SE**.
---
## Test checklist
| Step | Expected |
|------|----------|
| Main menu (title screen) | **Multiplayer** appears after **Load** |
| Pause menu → Settings | **No** Multiplayer row |
| Click **Multiplayer** | Server browser opens |
| **Settings → Gameplay** | No crash |
| **Settings → Display** | No crash |
| **Settings → Audio** | No crash |
| **Settings → Controls** | Still works |
| Open Multiplayer, then **Settings** | Browser closes |
| **F9** | Still toggles browser |
---
## Troubleshooting
### Settings sub-menus crash again
You likely re-imported the full class. Run `.\tools\restore-vanilla-mainmenu.ps1` and redo changes **16** using **per-method** editing only.
### Multiplayer row missing
- Confirm `bInvalidateOlderFiles=1` in `Fallout4Custom.ini`.
- Confirm loose `Data/Interface/MainMenu.swf` exists and is not overridden by another mod (check MO2 load order).
- Confirm you added the `InitList` block with `if(!this.PauseMode)`.
### Multiplayer row visible but click does nothing
- Launch via **F4SE**, not vanilla exe.
- Confirm `CommonwealthOnline.dll` is loading (check `Documents/My Games/Fallout4/F4SE/CommonwealthOnline.log`).
- Confirm Change **3** (`case this.MULTIPLAYER_INDEX`) is present.
### Button shows `$MULTIPLAYER` or wrong text
Use literal `"Multiplayer"` (no `$`), or add a translation key to `Interface/Translate_en.txt`.
---
## Reference files
| File | Purpose |
|------|---------|
| [`MainMenu.swf.vanilla.bak`](../MainMenu.swf.vanilla.bak) | Untouched vanilla backup |
| [`scripts/MainMenu.as`](scripts/MainMenu.as) | Full decompiled reference — **do not import wholesale** |
| [`PATCH_MainMenu_Multiplayer.md`](PATCH_MainMenu_Multiplayer.md) | Short overview |
| [`../../tools/restore-vanilla-mainmenu.ps1`](../../tools/restore-vanilla-mainmenu.ps1) | Restore vanilla SWF from game BA2 |
Plugin API (already implemented in C++):
- `root.f4se.plugins.commonwealthOnline.openManager()`
- `root.f4se.plugins.commonwealthOnline.closeManager()`