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