Skip to content

Commit 495e7f6

Browse files
committed
[MCI] Add LAN play
1 parent f1ebd5f commit 495e7f6

14 files changed

Lines changed: 2097 additions & 12 deletions

File tree

buildtools/fnapatches/BUILD.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,18 @@ The approach follows [JohnnyonFlame/FNAPatches](https://github.com/JohnnyonFlame
66
| Artifact | Built from | Deploys to |
77
|---|---|---|
88
| `libFNA3D.so.0` | `FNA3D/build_fna3d_astc.sh` + `FNA3D/fna3d-astc-r8.patch` | `<port>/libs/` |
9-
| `ParisEngine.MCIPatches.mm.dll` | `MarvelCosmicInvasion/ParisEngine.MCIPatches.mm.cs` | `<port>/patches/` |
9+
| `ParisEngine.MCIPatches.mm.dll` | `MarvelCosmicInvasion/ParisEngine.MCIPatches.mm.cs` + `lan/` sources (section 4) | `<port>/patches/` |
1010
| `Game.MCIPatches.mm.dll` | `MarvelCosmicInvasion/Game.MCIPatches.mm.cs` | `<port>/patches/` |
1111
| `MCIRepacker.exe` | `MarvelCosmicInvasion/MCIRepacker.cs` | `<port>/tools/` |
1212

1313
## Prerequisites
1414

1515
- **Docker** with aarch64 emulation — for `libFNA3D` (native ARM64).
1616
- **A mono 6.12.x toolchain** for the managed builds. The official `mono:latest` container (6.12.0.182) works; the device runtime is 6.12.0.122. Either is fine because MonoMod reads the compiled IL - **but it must be `mcs`, not `csc`**. csc records the publicized reference with a different assembly identity and MonoMod then rejects it (`MapDependency` failure).
17-
- **Game reference assemblies**, from your own MCI copy (never redistributed):
18-
- `ParisEngine.dll` and `Game.exe` - these must come from the game installation, not the port.
19-
- `FNA.dll` - Tribute's custom FNA, harvested from the game.
17+
- **Game reference assemblies**, from your own MCI copy:
18+
- `ParisEngine.dll`, `FNA.dll`, and `Game.exe` - these must come from the game installation.
2019
- the game's other managed deps, so `mcs`/MonoMod can resolve ParisEngine's references: `Steamworks.NET.dll`, `Newtonsoft.Json.dll`, `NLog.dll`, `NVorbis.dll`, `Ionic.Zlib.dll`, `ParisSerializers.dll`, `NBug.dll`.
21-
- **MonoMod** copy the port's `monomod/` (MonoMod.exe + Mono.Cecil*.dll + MonoMod.*.dll). This is the same patcher the device applies.
20+
- **MonoMod** - copy the port's `monomod/` (MonoMod.exe + Mono.Cecil*.dll + MonoMod.*.dll). This is the same patcher the device applies.
2221

2322
## 1. libFNA3D.so.0 (native, aarch64)
2423

@@ -27,7 +26,7 @@ docker run --rm --platform linux/aarch64 \
2726
-v "$PWD/FNA3D:/host" ubuntu:24.04 bash /host/build_fna3d_astc.sh
2827
```
2928

30-
Clones FNA3D 24.04, applies `fna3d-astc-r8.patch` (adds ASTC 4x4/5x5/6x6/8x8 + R8 surface formats, GL/GLES only), disables the Vulkan driver (MCI forces `FNA3D_FORCE_DRIVER=OpenGL`), builds, and drops `libFNA3D.so.0.24.04` next to the script. Copy it to `<port>/libs/libFNA3D.so.0`.
29+
Clones FNA3D 24.04, applies `fna3d-astc-r8.patch` (adds ASTC 4x4/5x5/6x6/8x8 + R8 surface formats, GL/GLES only), disables the Vulkan driver (the port forces `FNA3D_FORCE_DRIVER=OpenGL`), builds, and drops `libFNA3D.so.0.24.04` next to the script. Copy it to `<port>/libs/libFNA3D.so.0`.
3130

3231
## 2. Mixins + MCIRepacker (managed, architecture-independent)
3332

@@ -47,7 +46,7 @@ mcs -target:library -out:ParisEngine.MCIPatches.mm.dll -lib:. \
4746

4847
# Game mixin (references the pristine Game.exe + the publicized ParisEngine)
4948
mcs -target:library -out:Game.MCIPatches.mm.dll -lib:. \
50-
-r:Game.exe -r:ParisEngine.pub.dll -r:FNA.dll -r:MonoMod.exe Game.MCIPatches.mm.cs
49+
-r:Game.exe -r:ParisEngine.pub.dll -r:FNA.dll -r:NBug.dll -r:MonoMod.exe Game.MCIPatches.mm.cs
5150

5251
# Texture repacker (standalone)
5352
mcs -unsafe -optimize -out:MCIRepacker.exe MCIRepacker.cs
@@ -68,6 +67,25 @@ MONOMOD_DEPDIRS=.:/usr/lib/mono/4.5 mono MonoMod.exe Game.exe # -> MON
6867

6968
The device patchscript re-runs this on the user's own assemblies at first launch, then AOT-compiles both results.
7069

70+
## 4. LAN co-op build
71+
72+
`MarvelCosmicInvasion/lan/` holds an EOS-over-LAN layer that replaces Epic Online Services with local-network discovery, so two devices on the same network can play co-op (see the folder's NOTICE.md for licensing and provenance). The shipped `patches/*.mm.dll` are built this way: same pipeline as section 2, but define `MCI_LAN` and feed the engine mixin two extra sources. The define also compiles out the base mixin's EOS/networking no-ops, so a mixin pair is either fully stock or fully LAN - don't mix them. Section 2's commands as written produce the stock, online-disabled build.
73+
74+
```bash
75+
# Engine mixin, LAN build (base + EOS shim + LAN core in one dll)
76+
mcs -target:library -d:MCI_LAN -out:ParisEngine.MCIPatches.mm.dll -lib:. \
77+
-r:ParisEngine.pub.dll -r:FNA.dll -r:MonoMod.exe \
78+
ParisEngine.MCIPatches.mm.cs lan/ParisEngine.EOSLan.mm.cs lan/libEOSSDK-LAN.cs
79+
80+
# Game mixin, LAN build (adds the multiplayer menu trim)
81+
mcs -target:library -d:MCI_LAN -out:Game.MCIPatches.mm.dll -lib:. \
82+
-r:Game.exe -r:ParisEngine.pub.dll -r:FNA.dll -r:NBug.dll -r:MonoMod.exe Game.MCIPatches.mm.cs
83+
```
84+
85+
Weave and AOT exactly as in section 3; the AOT-pair rule below applies unchanged. Every
86+
device in a session needs the LAN build (a stock build can't see or join LAN lobbies),
87+
the full game data, and UDP port 55123 open between them - discovery is subnet broadcast.
88+
7189
## Notes
7290

7391
- **AOT `ParisEngine.dll.so` and `Game.exe.so` together.** MonoMod preserves the MVID, so mono can't tell a re-patched assembly from a stale AOT image, so a mismatched pair crashes at startup in seemingly random Renderer accessors.

buildtools/fnapatches/MarvelCosmicInvasion/Game.MCIPatches.mm.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,55 @@ public override void Tick(float deltaTime)
4949
}
5050
}
5151

52+
// LAN build: trim the online menu down to what works without Steam/Epic
53+
// (Create Party, Join Party Code, Quick Join).
54+
#if MCI_LAN
55+
namespace Paris.Game.Menu
56+
{
57+
class patch_MultiplayerDash : MultiplayerDash
58+
{
59+
private global::Paris.Engine.Menu.Control.TextSelectionItem _invite;
60+
private global::Paris.Engine.Menu.Control.SelectionMenuControl _mainMenuSelection;
61+
private global::Paris.Engine.Menu.Control.SelectionMenuControl _lobbyMode;
62+
63+
// RefreshMainMenu re-shows these each call and runs before input in the same
64+
// tick, so the fix has to live inside it.
65+
public extern void orig_RefreshMainMenu();
66+
private void RefreshMainMenu()
67+
{
68+
orig_RefreshMainMenu();
69+
if (this._mainMenuSelection != null && this._mainMenuSelection.Items.Count > 6)
70+
{
71+
this._mainMenuSelection.Items[1].Hidden = true; // Invites
72+
this._mainMenuSelection.Items[1].CanAccept = false;
73+
this._mainMenuSelection.Items[2].Hidden = true; // Join Friends
74+
this._mainMenuSelection.Items[2].CanAccept = false;
75+
this._mainMenuSelection.Items[5].Hidden = true; // Crossplay
76+
this._mainMenuSelection.Items[5].CanAccept = false;
77+
this._mainMenuSelection.Items[6].Hidden = true; // Link Epic Account (un-gated by the LAN login, label unresolved)
78+
this._mainMenuSelection.Items[6].CanAccept = false;
79+
}
80+
}
81+
82+
public extern void orig_Tick(float deltaTime);
83+
public override void Tick(float deltaTime)
84+
{
85+
orig_Tick(deltaTime);
86+
if (this._invite != null)
87+
{
88+
this._invite.Disabled = true;
89+
this._invite.CanAccept = false;
90+
}
91+
if (this._lobbyMode != null && this._lobbyMode.Items.Count > 1)
92+
{
93+
this._lobbyMode.Items[1].Hidden = true; // party type: Friends (Public/Private only on LAN)
94+
this._lobbyMode.Items[1].CanAccept = false;
95+
}
96+
}
97+
}
98+
}
99+
#endif
100+
52101
// Bypass NBUG and get crash reports directly
53102
namespace Paris
54103
{

buildtools/fnapatches/MarvelCosmicInvasion/MCIRepacker.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ static void Main(string[] args)
381381
finally
382382
{
383383
count_done++;
384+
GC.Collect();
384385
}
385386
}
386387
Console.Out.WriteLine("Repack finished: " + count_done + " files visited, " + failed + " failures.");

buildtools/fnapatches/MarvelCosmicInvasion/ParisEngine.MCIPatches.mm.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,9 @@ class patch_GameAnalytics : GameAnalytics
197197
}
198198
}
199199

200-
// Halt networking, EOSSDK is stubbed
200+
// Halt networking, EOSSDK is stubbed.
201+
// (The LAN build needs the real Disconnect/ChangeLobbyLocked - its lobby calls are shimmed.)
202+
#if !MCI_LAN
201203
namespace Paris.Engine.Networking
202204
{
203205
class patch_NetworkManager : NetworkManager
@@ -211,8 +213,12 @@ class patch_NetworkManager : NetworkManager
211213
}
212214
}
213215
}
216+
#endif
214217

215-
// Halt EpicHelper, EOSSDK is stubbed
218+
// Halt EpicHelper, EOSSDK is stubbed.
219+
// (The LAN build defines MCI_LAN and supplies its own patch_EpicHelper that un-gates
220+
// EOS onto the clean-room LAN backend instead of stubbing it out.)
221+
#if !MCI_LAN
216222
namespace Paris.Engine
217223
{
218224
[MonoModPatch("Paris.Engine.EpicHelper")]
@@ -224,6 +230,7 @@ public static void Init()
224230
}
225231
}
226232
}
233+
#endif
227234

228235
// Mono 6.12 LLVM AOT miscompiles Vector3.CatmullRom/Hermite,
229236
// so recompute in scalar double
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# EOS-over-LAN layer - license and provenance
2+
3+
The code in this folder (`ParisEngine.EOSLan.mm.cs`, `libEOSSDK-LAN.cs`) is original work, MIT licensed, same terms as the repo's top-level LICENSE.
4+
5+
## Provenance
6+
7+
- The LAN protocol, lobby model, and packet transport in `libEOSSDK-LAN.cs` were designed and written from scratch for this project.
8+
- The shim in `ParisEngine.EOSLan.mm.cs` binds to the Epic Online Services C# wrapper types that the game itself ships inside `ParisEngine.dll`. Reproducing those type and method signatures is required for interoperability; the method bodies are RHH.
9+
- No Epic SDK code, headers, or documentation text is included here or in the built artifacts.
10+
- Game behavior (join flow, member sync, packet framing expectations) was understood by reading the game's own assemblies, the same basis as every other patch in this repo.
11+
- The port's `libs/libEOSSDK-Linux-Shipping.so` is the stub from JohnnyonFlame's FNAPatches (credited in the port README, `licenses/johnnyonflame.LICENSE`), not Epic's SDK. This shim overrides the EOS methods used for LAN play; the stub safely no-ops the rest of the EOS surface the game still calls (achievements, stats, etc.), so it remains required.
12+
13+
## Isolation from the real online services
14+
15+
- This layer never contacts Epic. All traffic is UDP on the local subnet (port 55123) between the participating devices; nothing leaves the local network.
16+
- No Epic or Steam account is used, read, or modified. Player identity is a local device name that exists only for the duration of a session.
17+
- Only the port's local copy of the game is patched, on the device, from the user's own files. Installations elsewhere and their online play are untouched - this cannot interact with, impersonate, or disrupt real online sessions.
18+
19+
## What this is and isn't
20+
21+
- It replaces the game's online matchmaking with local-network discovery so two owned copies can play co-op on the same LAN. Both devices need the full game data, supplied by the user.
22+
- It does not bypass any purchase, entitlement, or DLC check, and it does not defeat any copy protection. Accounts and ownership checks it skips gate an online service, not access to the game.
23+
24+
"Epic Online Services" and "EOS" are trademarks of Epic Games, Inc. The names appear here only to identify what the layer is compatible with; this project is not affiliated with or endorsed by Epic Games or Tribute Games.

0 commit comments

Comments
 (0)