| Date uploaded | 7 months ago |
| Version | 1.0.6 |
| Download link | DAa-PEAKNetworkingLibrary-1.0.6.zip |
| Downloads | 1485 |
| Dependency string | DAa-PEAKNetworkingLibrary-1.0.6 |
This mod requires the following mods to function
BepInEx-BepInExPack_PEAK
BepInEx pack for PEAK. Preconfigured and ready to use.
Preferred version: 5.4.75301README
PEAKNetworkingLibrary -> Update? 

A library mod for networking specifically with Steam with other platform compatibility.
PEAKNetworkingLibrary has its own NuGet package, you can find it here.
Update Info • FAQ • Features • Examples • Install • Security • [Efficiency Guidance](#efficiency guidance) • Support
Update
Contains information related to the current update.
Details → (click to expand)
- Made the Photon utility of mapping Photon users to Steam available.
- Implemented simpler caller ID fetching for easier response logic.
FAQ
Contains commonly asked questions.
FAQ → (click to expand)
- Q: Do I need to create or initialize the networking service?
No. The library auto-instantiates and initializes the correct service (Steam or Offline) on load and auto-creates a poller. Mod authors should access the service via Net.Service and do not call Initialize() or PollReceive() themselves.
Features
Contains information related to features that do function.
Feature list [DOES NOT INCLUDE EVERYTHING] → (click to expand)
AS OF VERSION 1.0.1:
- Automatic initialization & poller. Library auto-creates service and runs receive poller; mod authors only consume Net.Service.
- ModId helpers. ModId.FromGuid(string).
- Wire-efficient keys. API accepts human-friendly strings but sends small 32-bit keys on the wire (local map + fallback).
- All old functions are still usable and can be used, though it is recommended to swap over to new system.
- Backend
INetworkingServiceprovides one surface mods use. Swap Steam vs Offline without code changes. - Lobby key sync (host -> clients)
Host sets small authoritative strings viaSetLobbyData. Clients read withGetLobbyDataandLobbyDataChangedevent. - RPC discovery & invocation
Discover methods marked[CustomRPC]withRegisterNetworkObjectand callRPC,RPCTarget, orRPCToHost. - Message serialization
Messageclass supports: byte, int, uint, long, ulong, float, bool, string, byte[], Vector3, Quaternion, CSteamID. - Reliable vs Unreliable
ReliableTypeenum 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
IncomingValidatorlets consumers drop or accept messages before handler invocation. - Poll-based receive loop
PollReceive()is required for Steam adapter; Offline shim is immediate.
API References [DOES NOT INCLUDE EVERYTHING] → (click to expand)
| Key | Type | Meaning |
|---|---|---|
| NetworkingPhotonExtensions | Method | Dictionary<int, ulong> MapPhotonActorsToSteam |
| ModId | Method | FromGuid(string guid) |
| NetworkingServiceFactory | Method | CreateDefaultService() – auto-chosen by runtime |
| INetworkingService | Info | Service is created & initialized automatically by the library. Mods should access via Net.Service and must not call Initialize() except for test harnesses. |
| Method | bool IsHost() |
|
| Method | ulong GetLocalSteam64() |
|
| Method | ulong[] GetLobbyMemberSteamIds() |
|
| Method | Initialize() |
|
| Method | Shutdown() |
|
| Method | CreateLobby(maxPlayers) |
|
| Method | JoinLobby(lobbySteamId64) |
|
| Method | LeaveLobby() |
|
| Method | RegisterLobbyDataKey(string key) |
|
| Method | SetLobbyData(string key, object value) |
|
| Method | T GetLobbyData<T>(string key) |
|
| Method | RegisterPlayerDataKey(string key) |
|
| Method | SetPlayerData(string key, object value) |
|
| Method | T GetPlayerData<T>(ulong steam64, string key) |
|
| Method | IDisposable RegisterNetworkObject(object instance, uint modId, int mask = 0) |
|
| Method | void DeregisterNetworkObject(object instance, uint modId, int mask = 0) |
|
| Method | void RPC(uint modId, string methodName, ReliableType reliable, params object[] parameters) |
|
| Method | void RPCTarget(uint modId, string methodName, ulong targetSteamId64, ReliableType reliable, params object[] parameters) |
|
| Method | void RPCToHost(uint modId, string methodName, ReliableType reliable, params object[] parameters) |
|
| Method | void PollReceive() |
|
| Events | LobbyCreated, LobbyEntered, LobbyLeft, PlayerEntered(ulong), PlayerLeft(ulong), LobbyDataChanged(string[] keys), PlayerDataChanged(ulong steam64, string[] keys) |
|
| Property | Func<Message, ulong, bool>? IncomingValidator { get; set; } |
Examples
Contains examples of features.
Examples [DOES NOT INCLUDE EVERYTHING] → (click to expand)
- All examples assume the networking service is already present (Steam or Offline). They show the smallest usable code for each feature.
[FULL EXAMPLE] PEAKTest by off_grid
using System;
using System.Linq;
using BepInEx;
using BepInEx.Logging;
using UnityEngine;
using NetworkingLibrary.Services;
using NetworkingLibrary.Modules;
using NetworkingLibrary;
using Steamworks;
namespace PEAKTest
{
[BepInDependency("off_grid.NetworkingLibrary")]
[BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]
public class Plugin : BaseUnityPlugin
{
readonly static uint MOD_ID = ModId.FromGuid(MyPluginInfo.PLUGIN_GUID); // Use this when you do not want to compute bytes.
const string LOBBY_KEY_PACK_COUNT = "peak_test.pack_count";
const string PLAYER_KEY_STATUS = "peak_test.player_status";
static ManualLogSource Log => Instance.Logger;
public static Plugin Instance { get; private set; } = null!;
IDisposable? registrationToken;
internal static INetworkingService Service = Net.Service!;
void Awake()
{
Instance = this;
Log.LogInfo($"{MyPluginInfo.PLUGIN_NAME} Awake");
// Register keys
Service.RegisterLobbyDataKey(LOBBY_KEY_PACK_COUNT);
Service.RegisterPlayerDataKey(PLAYER_KEY_STATUS);
// Register RPC handlers by reflecting this instance's [CustomRPC] methods.
registrationToken = Service.RegisterNetworkObject(this, MOD_ID);
// Subscribe to a few events, none of these are required.
Service.LobbyEntered += OnLobbyEntered;
Service.LobbyCreated += () => Log.LogInfo("LobbyCreated event");
Service.PlayerEntered += id => Log.LogInfo($"PlayerEntered: {id}");
Service.PlayerLeft += id => Log.LogInfo($"PlayerLeft: {id}");
Service.LobbyDataChanged += keys => Log.LogInfo("LobbyDataChanged: " + string.Join(",", keys));
Service.PlayerDataChanged += (steam, keys) => Log.LogInfo($"PlayerDataChanged: {steam} -> {string.Join(',', keys)}");
// If already in a lobby at load time, run quick checks
if (Service.InLobby) OnLobbyEntered();
}
void OnDestroy()
{
Service.LobbyEntered -= OnLobbyEntered;
Service.LobbyCreated -= () => { }; //
registrationToken?.Dispose();
Log.LogInfo($"{MyPluginInfo.PLUGIN_NAME} destroyed");
}
// Called when we enter a lobby (host or client).
void OnLobbyEntered()
{
Log.LogInfo($"OnLobbyEntered: InLobby={Service.InLobby}, HostSteamId64={Service.HostSteamId64}");
// Host will announce a small package list via RPC to all clients.
// Non-host clients will request the package list from host (RPCToHost).
if (Service.IsHost)
{
Log.LogInfo("We are host. Announcing packages to clients.");
// pretend-loaded packages, fill with your actual data.
string[] loaded = new[] { "pivo1", "pivo2" };
// set a lobby value
Service.SetLobbyData(LOBBY_KEY_PACK_COUNT, loaded.Length);
// set player data (host status)
Service.SetPlayerData(PLAYER_KEY_STATUS, "host_ready");
// send package list as a single joined string
string payload = string.Join("|", loaded);
Service.RPC(MOD_ID, nameof(HandlePackagesRpc), ReliableType.Reliable, payload);
Log.LogInfo($"Host RPC broadcast sent with {loaded.Length} packages.");
}
else
{
Log.LogInfo("We are client. Requesting package list from host.");
// ask host to send packages to everyone (host will handle RequestPackagesRpc)
Service.RPCToHost(MOD_ID, nameof(RequestPackagesRpc), ReliableType.Reliable);
}
}
// Host broadcasts packages with this RPC. Clients receive here.
// Signature shows a single string parameter.
[CustomRPC]
void HandlePackagesRpc(string joined)
{
try
{
var list = string.IsNullOrEmpty(joined) ? Array.Empty<string>() : joined.Split('|');
Log.LogInfo($"HandlePackagesRpc: received {list.Length} packages: {string.Join(", ", list)}");
// Set player key to indicate we received packages, not required.
var svc = Net.Service;
svc?.SetPlayerData(PLAYER_KEY_STATUS, "packages_received");
}
catch (Exception ex)
{
Log.LogError($"HandlePackagesRpc exception: {ex}");
}
}
// Clients call this (RPCToHost) to request the host's package list.
// Host will respond by performing an RPC broadcast (see OnLobbyEntered path).
[CustomRPC]
void RequestPackagesRpc()
{
try
{
// Only the host should act on this. We do a host check.
if (!Service.IsHost)
{
Log.LogInfo("RequestPackagesRpc called on non-host.");
return;
}
Log.LogInfo("Host handling RequestPackagesRpc; responding with package list.");
// Example data
var loaded = new[] { "pivo1", "pivo2" };
string payload = string.Join("|", loaded);
Service.RPC(MOD_ID, nameof(HandlePackagesRpc), ReliableType.Reliable, payload);
}
catch (Exception ex)
{
Log.LogError($"RequestPackagesRpc exception: {ex}");
}
}
}
}
[LEGACY] 1) Minimal host -> clients: Lobby key (recommended for package lists)
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:
- Lobby metadata is small and best for modest lists (tens of items).
- If payload grows large, compress it (gzip) before base64.
2) Minimal RPC broadcast (general notifications)
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:
- [LEGACY] Must call
PollReceive()each frame for Steam adapter. RegisterNetworkObjectdiscovers methods tagged[CustomRPC].
3) Targeted RPC to specific player
svc.RPCTarget(MOD, "PrivateMessage", targetSteamId64, ReliableType.Reliable, "hello");
Handler:
[CustomRPC]
void PrivateMessage(string text) { Debug.Log(text); }
4) IncomingValidator usage (drop unwanted messages)
svc.IncomingValidator = (msg, fromSteam64) =>
{
// drop messages with a specific method name
if (msg.MethodName == "DropMe") return false;
return true;
};
5) Offline shim for local tests
// 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.
6) ModId.FromGuid & mapping
> Use uint MOD = ModId.FromGuid("<your-mod-guid>"); it produces a stable 32-bit id from your mod GUID so you do not hand-pick hex values.
7) Getting local & lobby Steam IDs + Photon mapping guidance
var svc = Net.Service;
ulong local = svc.GetLocalSteam64();
ulong[] members = svc.GetLobbyMemberSteamIds(); // empty when not in a lobby
If you use Photon and need actor -> steam mappings, then you can use svc.GetLobbyMemberSteamIds().
Install
Contains information related to installing the mod.
Install instructions → (click to expand)
Mod Manager
- Install from Thunderstore or add via your mod manager.
Manual install
- Copy the release files into the game directory.
- See Usage: for information related to configuration.
Security
Contains information related to security.
Security → (click to expand)
SetSharedSecret,RegisterModPublicKey,RegisterModSignerare privileged/global. Misuse affects all mods.
Efficiency Guidance
Contains information related to best practices.
Efficiency Guide → (click to expand)
- Lobby data: cheap for small text. Keep per-key payload < ~2–4 KB.
- RPC: use for immediate messages and structured params. Use Unreliable for high-rate telemetry.
String keys are readable in code but cost bytes on the wire. The library maps strings to a stable 32-bit hash locally and sends the 32-bit value, the full string is sent only when the peer does not have the mapping. If your payloads are large, compress or use chunked RPCs.
- Compress large payloads (gzip) before base64. Or use chunked RPC transfer.
- HMAC/signing is optional. Enable when you need tamper detection.
Support
Contains information about where to find support.
Get help → (click to expand)
- Discord: https://discord.gg/SAw86z24rB → Mod Thread
- Issues: Include mod version, list of other mods, and optional
BepInExlogs.
Contains information related to debugging issues.
Troubleshooting → (click to expand)
- [LEGACY] Always call
PollReceive()inUpdate()when using Steam adapter. - Register lobby/player keys before calling
Get/Setto avoid warnings. - Use
RegisterNetworkObjectand keep the returnedIDisposablefor safe deregistration. - For very large lists prefer chunked RPC or a request-on-join RPC rather than putting everything in lobby metadata.
- If the library or poller stops, check BepInEx logs for an uncaught exception in NetworkingPoller or signer delegate. The library will now disable a failing signer delegate and log a warning.
- If you see duplicate / colliding RPCs, verify you used ModId.FromGuid() and that no two mods share the same GUID.
- If RPC handlers never run for a disposed object, ensure you keep the returned IDisposable registration token and Dispose() it in OnDestroy, the library also prunes destroyed Unity objects periodically.
CHANGELOG
Changelog
The format is based on Keep a Changelog.
1.0.7 23/11/2025
Added
- Table value support for messages.
Changed
- null
Fixed
- Patched issues with RPC events.
1.0.6 23/11/2025
Added
- null.
Changed
- null
Fixed
- I forgot to change reference link to the actual one.
1.0.5 22/11/2025
Added
- null.
Changed
- Simplified RPC structure for sending back messages to the caller / fetching the caller.
Fixed
- Made photon user to steam user mapping usable.
v1.0.4
- Patched something.
- Implemented IsHost bool.
v1.0.3
- Implemented helper functions to simplify usage of Library.
v1.0.2
- Fixed the README
v1.0.1
- Made it mote consumer friendly.
v1.0.0
- Release

