



Minimal, backend-agnostic networking for PEAK modders.
Works with the Steam adapter or the built-in Offline shim for testing.
Backend
INetworkingService provides one surface mods use. Swap Steam vs Offline without code changes.
Lobby key sync (host -> clients)
Host sets small authoritative strings via SetLobbyData. Clients read with GetLobbyData and LobbyDataChanged event.
RPC discovery & invocation
Discover methods marked [CustomRPC] with RegisterNetworkObject and call RPC, RPCTarget, or RPCToHost.
Message serialization
Message class supports: byte, int, uint, long, ulong, float, bool, string, byte[], Vector3, Quaternion, CSteamID.
Reliable vs Unreliable
ReliableType enum supports Reliable, Unreliable, UnreliableNoDelay semantics.
Security (optional)
Optional HMAC signing, per-mod signer hooks, sequence numbers, replay protection.
Framing & priority
Per-message flags, msg-id, sequence, fragment metadata, priority queues.
Offline shim for CI
Full in-process simulator to run tests without Steam.
Incoming validation hook
IncomingValidator lets consumers drop or accept messages before handler invocation.
Poll-based receive loop
PollReceive() is required for Steam adapter; Offline shim is immediate.
INetworkingService
- Initialize()
- Shutdown()
- CreateLobby(maxPlayers)
- JoinLobby(lobbySteamId64)
- LeaveLobby()
- RegisterLobbyDataKey(string key)
- SetLobbyData(string key, object value)
- T GetLobbyData<T>(string key)
- RegisterPlayerDataKey(string key)
- SetPlayerData(string key, object value)
- T GetPlayerData<T>(ulong steam64, string key)
- IDisposable RegisterNetworkObject(object instance, uint modId, int mask = 0)
- void DeregisterNetworkObject(object instance, uint modId, int mask = 0)
- void RPC(uint modId, string methodName, ReliableType reliable, params object[] parameters)
- void RPCTarget(uint modId, string methodName, ulong targetSteamId64, ReliableType reliable, params object[] parameters)
- void RPCToHost(uint modId, string methodName, ReliableType reliable, params object[] parameters)
- void PollReceive()
- Events: LobbyCreated, LobbyEntered, LobbyLeft, PlayerEntered(ulong), PlayerLeft(ulong),
LobbyDataChanged(string[] keys), PlayerDataChanged(ulong steam64, string[] keys)
- Func<Message, ulong, bool>? IncomingValidator { get; set; }
All examples assume the networking service is already present (Steam or Offline). They show the smallest usable code for each feature.
Host sets one string key. Clients read it on change.
Host: broadcast package list
// host only
void BroadcastLoadedPackages(INetworkingService svc, IEnumerable<string> packages)
{
const string KEY = "PEAK_PACKAGES_V1";
svc.RegisterLobbyDataKey(KEY); // safe to call on all peers
// serialize: escape '|' then join and compress+base64 option
string joined = string.Join("|", packages.Select(p => p.Replace("|","||")));
string payload = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(joined));
svc.SetLobbyData(KEY, payload); // host-only action
}
Client: receive update
void SubscribeToPackageUpdates(INetworkingService svc)
{
const string KEY = "PEAK_PACKAGES_V1";
svc.RegisterLobbyDataKey(KEY); // register the key used by host
svc.LobbyDataChanged += keys =>
{
if (!keys.Contains(KEY)) return;
var payload = svc.GetLobbyData<string>(KEY);
if (string.IsNullOrEmpty(payload)) return;
var joined = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(payload));
var packages = joined.Length == 0 ? Array.Empty<string>() :
joined.Split('|').Select(s => s.Replace("||","|")).ToArray();
// now packages[] contains the host list
};
}
Notes:
Use RPC when you need immediate notification or structured params.
Register and broadcast
const uint MOD = 0xDEADBEEF;
// on plugin init
IDisposable token = svc.RegisterNetworkObject(this, MOD);
// broadcast
svc.RPC(MOD, "NotifyPackages", ReliableType.Reliable, "GAME_PACKAGES_READY");
In same class: RPC handler
[CustomRPC]
void NotifyPackages(string tag)
{
// runs on all peers (including host by loopback)
Logger.LogInfo($"NotifyPackages received: {tag}");
}
Notes:
PollReceive() each frame for Steam adapter.RegisterNetworkObject discovers methods tagged [CustomRPC].svc.RPCTarget(MOD, "PrivateMessage", targetSteamId64, ReliableType.Reliable, "hello");
Handler:
[CustomRPC]
void PrivateMessage(string text) { Debug.Log(text); }
svc.IncomingValidator = (msg, fromSteam64) =>
{
// drop messages with a specific method name
if (msg.MethodName == "DropMe") return false;
return true;
};
// Use this for unit tests or local dev when Steam not available.
INetworkingService svc = new OfflineNetworkingService();
svc.Initialize();
// Offline shim delivers messages immediately. PollReceive() is a no-op.
PollReceive() in Update() when using Steam adapter.Get/Set to avoid warnings.RegisterNetworkObject and keep the returned IDisposable for safe deregistration.
