Skip to main content

Multiplayer

ModKit includes a mod verification system for multiplayer games. The goal is consistency — ensuring all players in a session have the same mods loaded — not anti-cheat.

Trust Model

The system is honest-client consistency: clients self-report their mod manifest, the server compares and kicks on mismatch. A malicious client could lie about hashes. Gameplay integrity for replicated actors relies on server authority, not on client verification.

How It Works​

Client                                    Server
└─ BeginPlay (PlayerController)
└─ ServerSubmitManifest() ──RPC──────> Diff client vs required mods
│
Match ─────> ClientHandshakeResult(true)
│
Mismatch ──> ClientHandshakeResult(false, Diff)
└─ KickPlayer() after delay

The handshake runs once at join. Post-handshake mod changes (enable/disable) are locked for the duration of the session.

Setup​

1 — StartLoadMods Before PlayerController​

StartLoadMods() must complete on both server and client before any PlayerController::BeginPlay runs.

A typical flow: both the listen server and the client load their mods from the main menu, before the session is created or joined. Only once OnAllModsLoaded fires do they proceed to host or join.

Main Menu (Listen Server)          Main Menu (Client)
└─ StartLoadMods() └─ StartLoadMods()
└─ OnAllModsLoaded └─ OnAllModsLoaded
└─ Create Session └─ Join Session
└─ PlayerController └─ PlayerController
└─ BeginPlay └─ BeginPlay
└─ Handshake ◄──────────────────┘

2 — Attach ModHandshakeComponent to PlayerController​

ModHandshakeComponent

Add UModHandshakeComponent to your PlayerController class:

#include "ModHandshakeComponent.h"

AMyPlayerController::AMyPlayerController()
{
HandshakeComponent = CreateDefaultSubobject<UModHandshakeComponent>(TEXT("ModHandshake"));
}

The component replicates automatically and drives the full handshake flow on BeginPlay.

3 — Bind the Result Delegate​

HandshakeComponent->OnModVerificationComplete.AddUObject(
this, &AMyPlayerController::OnModVerificationComplete);
void AMyPlayerController::OnModVerificationComplete(bool bSuccess, FModManifestDiff Diff)
{
if (!bSuccess)
{
// Show mismatch UI to player before kick
// Diff.Missing — mods required by server that client doesn't have
// Diff.Extra — mods client has that server doesn't require
// Diff.Mismatched — mods present on both but with different versions/hashes
}
}

The delegate fires on both server and client when verification completes.

FModManifestDiff​

Result of comparing two mod manifests:

FieldTypeDescription
MissingTArray<FModNetworkEntry>Server requires these mods — client doesn't have them
ExtraTArray<FModNetworkEntry>Client has these mods — server doesn't require them
MismatchedTArray<FModNetworkEntry>Present on both but version or hash differs
IsMatch()boolReturns true if all three arrays are empty

FModNetworkEntry​

Each entry in a manifest:

FieldTypeDescription
ModIdFStringUnique mod identifier
VersionFStringSemVer string from ModDescriptor.json
PakHashFStringMD5 hash computed at mount time

UModNetworkSubsystem​

GameInstance subsystem that builds and compares manifests. Available for advanced use:

UModNetworkSubsystem* NetSub = GetGameInstance()->GetSubsystem<UModNetworkSubsystem>();

// Build the local manifest (all loaded mods, sorted by ModId)
FModNetworkManifest Local = NetSub->BuildLocalManifest();

// Compare client manifest against server's required set
FModManifestDiff Diff = NetSub->DiffAgainst(ServerManifest);

// Or use the static method directly
FModManifestDiff Diff = UModNetworkSubsystem::DiffManifests(ClientManifest, ServerManifest);

UModHandshakeComponent Settings​

PropertyDefaultDescription
KickDelaySeconds1.0Delay before kicking a client after a failed handshake — gives time to display a message

Content Registry Network Guards​

UModContentRegistry automatically skips actor respawn logic on clients:

  • RespawnActorsForClassOverrides() — server only
  • OnActorSpawnedInWorld() — server only

Clients receive the correct modded actors via normal UE replication. No extra setup needed.

Testing​

Two Separate Processes Required

You cannot test multiplayer mod verification in single-process PIE — server and client share the same FModRegistry singleton. Use two separate Editor instances or a packaged build + Editor.

Useful log categories:

  • LogModKitLoader — shows Mod registered: ... | hash: ... at mount time
  • LogModKitLoader — shows ModHandshake: client verified or ModHandshake: client REJECTED

Known Limitations​

  • No anti-cheat — a client can lie about PakHash
  • No mod download system — rejected clients must manually install the missing mods