Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of SteamNetworkLib Il2Cpp v1.2.0
UserLibs/SteamNetworkLib-IL2Cpp.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading.Tasks; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSteamworks; using Microsoft.CodeAnalysis; using SteamNetworkLib.Core; using SteamNetworkLib.Events; using SteamNetworkLib.Exceptions; using SteamNetworkLib.Models; using SteamNetworkLib.Sync; using SteamNetworkLib.Utilities; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SteamNetworkLib")] [assembly: AssemblyConfiguration("Il2cpp")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0+29cbb5d4e578ff91759c8d79f8ca568e202fdda1")] [assembly: AssemblyProduct("SteamNetworkLib")] [assembly: AssemblyTitle("SteamNetworkLib")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SteamNetworkLib { public class SteamNetworkClient : IDisposable { private bool _disposed = false; private bool _isInitialized = false; private bool _versionCheckEnabled = true; private readonly NetworkRules _rules; private readonly List<IDisposable> _syncVars = new List<IDisposable>(); private const string STEAMNETWORKLIB_VERSION_KEY = "__snl_version"; public static string LibraryVersion { get; } = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "Unknown"; public bool VersionCheckEnabled { get { return _versionCheckEnabled; } set { _versionCheckEnabled = value; } } public SteamLobbyManager? LobbyManager { get; private set; } public SteamLobbyData? LobbyData { get; private set; } public SteamMemberData? MemberData { get; private set; } public SteamP2PManager? P2PManager { get; private set; } public bool IsInLobby => LobbyManager?.IsInLobby ?? false; public bool IsHost => LobbyManager?.IsHost ?? false; public CSteamID LocalPlayerId => LobbyManager?.LocalPlayerID ?? CSteamID.Nil; public LobbyInfo? CurrentLobby => LobbyManager?.CurrentLobby; public NetworkRules NetworkRules => _rules; public event EventHandler<VersionMismatchEventArgs>? OnVersionMismatch; public event EventHandler<LobbyJoinedEventArgs>? OnLobbyJoined; public event EventHandler<LobbyCreatedEventArgs>? OnLobbyCreated; public event EventHandler<LobbyLeftEventArgs>? OnLobbyLeft; public event EventHandler<MemberJoinedEventArgs>? OnMemberJoined; public event EventHandler<MemberLeftEventArgs>? OnMemberLeft; public event EventHandler<LobbyDataChangedEventArgs>? OnLobbyDataChanged; public event EventHandler<MemberDataChangedEventArgs>? OnMemberDataChanged; public event EventHandler<P2PMessageReceivedEventArgs>? OnP2PMessageReceived; public void UpdateNetworkRules(NetworkRules rules) { if (rules != null && P2PManager != null) { P2PManager.UpdateRules(rules); } } public SteamNetworkClient() { _rules = new NetworkRules(); } public SteamNetworkClient(NetworkRules rules) { _rules = rules ?? new NetworkRules(); } public bool Initialize() { if (_isInitialized) { return true; } try { if (!SteamNetworkUtils.IsSteamInitialized()) { throw new SteamNetworkException("Steam is not initialized. Make sure Steam is running and SteamAPI.Init() was called."); } LobbyManager = new SteamLobbyManager(); LobbyData = new SteamLobbyData(LobbyManager); MemberData = new SteamMemberData(LobbyManager); P2PManager = new SteamP2PManager(LobbyManager, _rules); SubscribeToEvents(); _isInitialized = true; return true; } catch (Exception ex) { throw new SteamNetworkException("Failed to initialize SteamNetworkClient: " + ex.Message, ex); } } public async Task<LobbyInfo> CreateLobbyAsync(ELobbyType lobbyType = 1, int maxMembers = 4) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) EnsureInitialized(); return await LobbyManager.CreateLobbyAsync(lobbyType, maxMembers); } public async Task<LobbyInfo> JoinLobbyAsync(CSteamID lobbyId) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) EnsureInitialized(); return await LobbyManager.JoinLobbyAsync(lobbyId); } public void LeaveLobby() { EnsureInitialized(); LobbyManager.LeaveLobby(); } public List<SteamNetworkLib.Models.MemberInfo> GetLobbyMembers() { EnsureInitialized(); return LobbyManager.GetLobbyMembers(); } public void InviteFriend(CSteamID friendId) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) EnsureInitialized(); LobbyManager.InviteFriend(friendId); } public void OpenInviteDialog() { EnsureInitialized(); LobbyManager.OpenInviteDialog(); } public void SetLobbyData(string key, string value) { EnsureInitialized(); LobbyData.SetData(key, value); } public string? GetLobbyData(string key) { EnsureInitialized(); return LobbyData.GetData(key); } public void SetMyData(string key, string value) { EnsureInitialized(); MemberData.SetMemberData(key, value); } public string? GetMyData(string key) { EnsureInitialized(); return MemberData.GetMemberData(key); } public string? GetPlayerData(CSteamID playerId, string key) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) EnsureInitialized(); return MemberData.GetMemberData(playerId, key); } public Dictionary<CSteamID, string> GetDataForAllPlayers(string key) { EnsureInitialized(); return MemberData.GetMemberDataForAllPlayers(key); } public void SetMyDataBatch(Dictionary<string, string> data) { EnsureInitialized(); MemberData.SetMemberDataBatch(data); } public async Task<bool> SendMessageToPlayerAsync(CSteamID playerId, P2PMessage message) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) EnsureInitialized(); return await P2PManager.SendMessageAsync(playerId, message, 0, (EP2PSend)2); } public async Task BroadcastMessageAsync(P2PMessage message) { EnsureInitialized(); List<SteamNetworkLib.Models.MemberInfo> members = LobbyManager.GetLobbyMembers(); List<Task<bool>> sendTasks = new List<Task<bool>>(); foreach (SteamNetworkLib.Models.MemberInfo member in members) { if (member.SteamId != LobbyManager.LocalPlayerID) { sendTasks.Add(P2PManager.SendMessageAsync(member.SteamId, message, 0, (EP2PSend)2)); } } if (sendTasks.Count > 0) { await Task.WhenAll(sendTasks); } } public void BroadcastMessage(P2PMessage message) { EnsureInitialized(); BroadcastMessageAsync(message); } public async Task<bool> SendTextMessageAsync(CSteamID playerId, string text) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) EnsureInitialized(); TextMessage message = new TextMessage { Content = text }; return await P2PManager.SendMessageAsync(playerId, message, 0, (EP2PSend)2); } public async Task BroadcastTextMessageAsync(string text) { EnsureInitialized(); TextMessage message = new TextMessage { Content = text }; await BroadcastMessageAsync(message); } public void BroadcastTextMessage(string text) { EnsureInitialized(); BroadcastTextMessageAsync(text); } public async Task<bool> SendDataSyncAsync(CSteamID playerId, string key, string value, string dataType = "string") { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) EnsureInitialized(); DataSyncMessage message = new DataSyncMessage { Key = key, Value = value, DataType = dataType }; return await P2PManager.SendMessageAsync(playerId, message, 0, (EP2PSend)2); } public void RegisterMessageHandler<T>(Action<T, CSteamID> handler) where T : P2PMessage, new() { EnsureInitialized(); P2PManager.RegisterMessageHandler(handler); } public void ProcessIncomingMessages() { if (_isInitialized) { try { SteamAPI.RunCallbacks(); } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] Error running Steam callbacks: " + ex.Message); } P2PManager.ProcessIncomingPackets(); } } public async Task SyncModDataWithAllPlayersAsync(string dataKey, string dataValue, string dataType = "string") { EnsureInitialized(); SetMyData(dataKey, dataValue); DataSyncMessage syncMessage = new DataSyncMessage { Key = dataKey, Value = dataValue, DataType = dataType }; await BroadcastMessageAsync(syncMessage); } public void SyncModDataWithAllPlayers(string dataKey, string dataValue, string dataType = "string") { EnsureInitialized(); SetMyData(dataKey, dataValue); DataSyncMessage message = new DataSyncMessage { Key = dataKey, Value = dataValue, DataType = dataType }; BroadcastMessage(message); } public bool IsModDataCompatible(string dataKey) { EnsureInitialized(); Dictionary<CSteamID, string> dataForAllPlayers = GetDataForAllPlayers(dataKey); if (dataForAllPlayers.Count <= 1) { return true; } string[] array = dataForAllPlayers.Values.Distinct().ToArray(); return array.Length == 1; } internal void SetLibraryVersionData() { EnsureInitialized(); SetMyData("__snl_version", LibraryVersion); } public bool CheckLibraryVersionCompatibility() { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) EnsureInitialized(); if (!_versionCheckEnabled) { return true; } Dictionary<CSteamID, string> dataForAllPlayers = GetDataForAllPlayers("__snl_version"); if (dataForAllPlayers.Count == 0) { return true; } List<string> list = dataForAllPlayers.Values.Distinct().ToList(); if (list.Count <= 1) { return true; } string libraryVersion = LibraryVersion; List<CSteamID> list2 = new List<CSteamID>(); foreach (KeyValuePair<CSteamID, string> item in dataForAllPlayers) { if (item.Value != libraryVersion) { list2.Add(item.Key); } } try { this.OnVersionMismatch?.Invoke(this, new VersionMismatchEventArgs(libraryVersion, dataForAllPlayers, list2)); } catch (Exception ex) { Console.WriteLine("Error in version mismatch event handler: " + ex.Message); } string text = string.Join(", ", dataForAllPlayers.Select((KeyValuePair<CSteamID, string> kvp) => $"{kvp.Key}:{kvp.Value}")); Console.WriteLine("[SteamNetworkLib] WARNING: Version mismatch detected! Local version: " + libraryVersion + ", Player versions: " + text); return false; } public Dictionary<CSteamID, string> GetPlayerLibraryVersions() { EnsureInitialized(); return GetDataForAllPlayers("__snl_version"); } public HostSyncVar<T> CreateHostSyncVar<T>(string key, T defaultValue, NetworkSyncOptions? options = null, ISyncValidator<T>? validator = null) { EnsureInitialized(); HostSyncVar<T> hostSyncVar = new HostSyncVar<T>(this, key, defaultValue, options, validator); _syncVars.Add(hostSyncVar); return hostSyncVar; } public ClientSyncVar<T> CreateClientSyncVar<T>(string key, T defaultValue, NetworkSyncOptions? options = null, ISyncValidator<T>? validator = null) { EnsureInitialized(); ClientSyncVar<T> clientSyncVar = new ClientSyncVar<T>(this, key, defaultValue, options, validator); _syncVars.Add(clientSyncVar); return clientSyncVar; } private void SubscribeToEvents() { LobbyManager.OnLobbyJoined += delegate(object s, LobbyJoinedEventArgs e) { this.OnLobbyJoined?.Invoke(this, e); }; LobbyManager.OnLobbyCreated += delegate(object s, LobbyCreatedEventArgs e) { this.OnLobbyCreated?.Invoke(this, e); }; LobbyManager.OnLobbyLeft += delegate(object s, LobbyLeftEventArgs e) { DisposeSyncVars(); this.OnLobbyLeft?.Invoke(this, e); }; LobbyManager.OnMemberJoined += delegate(object s, MemberJoinedEventArgs e) { this.OnMemberJoined?.Invoke(this, e); }; LobbyManager.OnMemberLeft += delegate(object s, MemberLeftEventArgs e) { this.OnMemberLeft?.Invoke(this, e); }; LobbyData.OnLobbyDataChanged += delegate(object s, LobbyDataChangedEventArgs e) { this.OnLobbyDataChanged?.Invoke(this, e); }; P2PManager.OnMessageReceived += delegate(object s, P2PMessageReceivedEventArgs e) { this.OnP2PMessageReceived?.Invoke(this, e); }; if (!_versionCheckEnabled) { return; } LobbyManager.OnLobbyJoined += delegate { SafeExecute(delegate { SetLibraryVersionData(); }, "setting version data on lobby join"); }; LobbyManager.OnLobbyCreated += delegate { SafeExecute(delegate { SetLibraryVersionData(); }, "setting version data on lobby creation"); }; LobbyManager.OnMemberJoined += delegate { SafeExecute(async delegate { await Task.Delay(500); CheckLibraryVersionCompatibility(); }, "checking version compatibility"); }; MemberData.OnMemberDataChanged += delegate(object s, MemberDataChangedEventArgs e) { if (e.Key == "__snl_version") { SafeExecute(delegate { CheckLibraryVersionCompatibility(); }, "checking version compatibility on data change"); } }; } private void SafeExecute(Action action, string operation) { try { action(); } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] Warning: Failed " + operation + ": " + ex.Message); } } private void SafeExecute(Func<Task> action, string operation) { Func<Task> action2 = action; string operation2 = operation; Task.Run(async delegate { try { await action2(); } catch (Exception ex2) { Exception ex = ex2; Console.WriteLine("[SteamNetworkLib] Warning: Failed " + operation2 + ": " + ex.Message); } }); } private void DisposeSyncVars() { foreach (IDisposable syncVar in _syncVars) { try { syncVar?.Dispose(); } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] Warning: Failed to dispose sync var: " + ex.Message); } } _syncVars.Clear(); } private void UnsubscribeFromEvents() { this.OnLobbyJoined = null; this.OnLobbyCreated = null; this.OnLobbyLeft = null; this.OnMemberJoined = null; this.OnMemberLeft = null; this.OnLobbyDataChanged = null; this.OnMemberDataChanged = null; this.OnP2PMessageReceived = null; this.OnVersionMismatch = null; } private void EnsureInitialized() { if (!_isInitialized) { throw new InvalidOperationException("SteamNetworkClient is not initialized. Call Initialize() first."); } } public void Dispose() { if (!_disposed) { try { DisposeSyncVars(); UnsubscribeFromEvents(); P2PManager?.Dispose(); MemberData?.Dispose(); LobbyData?.Dispose(); LobbyManager?.Dispose(); } catch (Exception ex) { Console.WriteLine("Error disposing SteamNetworkClient: " + ex.Message); } _disposed = true; GC.SuppressFinalize(this); } } } } namespace SteamNetworkLib.Utilities { public static class AudioStreamingCompatibility { public static bool IsSupported => false; public static string RuntimeType => "IL2CPP"; public static void ThrowIfNotSupported() { if (!IsSupported) { throw new PlatformNotSupportedException("Audio streaming is not supported on " + RuntimeType + " runtime. Audio streaming requires Mono runtime due to OpusSharp limitations with IL2CPP. IL2CPP has memory marshalling issues that cause corruption when passing audio data between managed and native code. Please use a Mono-based game/environment for audio streaming features."); } } public static string GetCompatibilityMessage() { if (IsSupported) { return "Audio streaming is supported on " + RuntimeType + " runtime."; } return "Audio streaming is NOT supported on " + RuntimeType + " runtime. Audio streaming requires Mono runtime due to OpusSharp limitations. IL2CPP has memory marshalling issues that cause corruption when passing audio data between managed and native code through the OpusSharp library. The core SteamNetworkLib features (lobbies, P2P messaging, file transfer, etc.) work perfectly on both Mono and IL2CPP - only audio streaming is affected."; } public static void LogCompatibilityWarning() { if (!IsSupported) { Console.WriteLine("[SteamNetworkLib] WARNING: " + GetCompatibilityMessage()); } } } public static class MessageSerializer { private const int MAX_MESSAGE_SIZE = 4096; public const string MESSAGE_HEADER = "SNLM"; public static byte[] SerializeMessage(P2PMessage message) { try { byte[] array = message.Serialize(); byte[] bytes = Encoding.UTF8.GetBytes(message.MessageType); byte[] bytes2 = Encoding.UTF8.GetBytes("SNLM"); int num = bytes2.Length + 1 + bytes.Length + array.Length; if (num > 4096) { throw new P2PException($"Message too large: {num} bytes (max: {4096})"); } byte[] array2 = new byte[num]; int num2 = 0; Array.Copy(bytes2, 0, array2, num2, bytes2.Length); num2 += bytes2.Length; array2[num2] = (byte)bytes.Length; num2++; Array.Copy(bytes, 0, array2, num2, bytes.Length); num2 += bytes.Length; Array.Copy(array, 0, array2, num2, array.Length); return array2; } catch (Exception ex) when (!(ex is P2PException)) { throw new P2PException("Failed to serialize message: " + ex.Message, ex); } } public static (string MessageType, byte[] MessageData) DeserializeMessage(byte[] data) { try { if (data.Length < 6) { string message = $"Invalid message data: too short ({data.Length} bytes)"; throw new P2PException(message); } byte[] bytes = Encoding.UTF8.GetBytes("SNLM"); int num = 0; bool flag = true; for (int i = 0; i < bytes.Length; i++) { if (data[num + i] != bytes[i]) { flag = false; break; } } if (!flag) { byte[] array = new byte[bytes.Length]; Array.Copy(data, num, array, 0, bytes.Length); string message2 = "Invalid message header: expected 'SNLM', got '" + Encoding.UTF8.GetString(array) + "'"; throw new P2PException(message2); } num += bytes.Length; byte b = data[num]; num++; if (b == 0 || num + b > data.Length) { string message3 = $"Invalid message format: type length {b} exceeds data bounds (data length: {data.Length}, offset: {num})"; throw new P2PException(message3); } string @string = Encoding.UTF8.GetString(data, num, b); num += b; int num2 = data.Length - num; byte[] array2 = new byte[num2]; Array.Copy(data, num, array2, 0, num2); return (@string, array2); } catch (Exception ex) when (!(ex is P2PException)) { throw new P2PException("Failed to deserialize message: " + ex.Message, ex); } } public static T CreateMessage<T>(byte[] data) where T : P2PMessage, new() { try { (string MessageType, byte[] MessageData) tuple = DeserializeMessage(data); string item = tuple.MessageType; byte[] item2 = tuple.MessageData; T val = new T(); if (val.MessageType != item) { throw new P2PException("Message type mismatch: expected '" + val.MessageType + "', got '" + item + "'"); } val.Deserialize(item2); return val; } catch (Exception ex) when (!(ex is P2PException)) { throw new P2PException("Failed to create message of type " + typeof(T).Name + ": " + ex.Message, ex); } } public static bool IsValidMessage(byte[] data) { try { if (data.Length < 6) { return false; } byte[] bytes = Encoding.UTF8.GetBytes("SNLM"); bool flag = true; for (int i = 0; i < bytes.Length; i++) { if (data[i] != bytes[i]) { flag = false; break; } } if (!flag) { return false; } byte b = data[bytes.Length]; return bytes.Length + 1 + b < data.Length; } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] MessageSerializer.IsValidMessage ERROR: " + ex.Message); Console.WriteLine("[SteamNetworkLib] MessageSerializer.IsValidMessage Stack Trace: " + ex.StackTrace); return false; } } public static string? GetMessageType(byte[] data) { try { if (!IsValidMessage(data)) { return null; } byte[] bytes = Encoding.UTF8.GetBytes("SNLM"); int num = bytes.Length; byte b = data[num]; num++; if (num + b > data.Length) { return null; } return Encoding.UTF8.GetString(data, num, b); } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] MessageSerializer.GetMessageType ERROR: " + ex.Message); Console.WriteLine("[SteamNetworkLib] MessageSerializer.GetMessageType Stack Trace: " + ex.StackTrace); return null; } } public static string BytesToHexString(byte[] bytes) { StringBuilder stringBuilder = new StringBuilder(bytes.Length * 3); foreach (byte b in bytes) { stringBuilder.Append(b.ToString("X2")); stringBuilder.Append(' '); } return stringBuilder.ToString(); } } public static class SteamNetworkUtils { public static bool IsSteamInitialized() { try { return SteamAPI.IsSteamRunning(); } catch { return false; } } public static string GetLocalPlayerName() { if (!IsSteamInitialized()) { return "Unknown Player"; } return SteamFriends.GetPersonaName(); } public static string GetPlayerName(CSteamID steamId) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (!IsSteamInitialized()) { return "Unknown Player"; } return SteamFriends.GetFriendPersonaName(steamId); } public static bool IsValidSteamID(CSteamID steamId) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (steamId == CSteamID.Nil || steamId.m_SteamID == 0) { return false; } try { if (((CSteamID)(ref steamId)).IsValid()) { return true; } return steamId.m_SteamID != 0L && steamId.m_SteamID != 1; } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] SteamNetworkUtils.IsValidSteamID ERROR: " + ex.Message); Console.WriteLine("[SteamNetworkLib] SteamNetworkUtils.IsValidSteamID Stack Trace: " + ex.StackTrace); return steamId.m_SteamID != 0L && steamId.m_SteamID != 1; } } public static CSteamID? ParseSteamID(string steamIdString) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(steamIdString)) { return null; } if (ulong.TryParse(steamIdString, out var result)) { CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(result); return IsValidSteamID(val) ? new CSteamID?(val) : null; } return null; } public static bool IsFriend(CSteamID steamId) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 if (!IsSteamInitialized() || !IsValidSteamID(steamId)) { return false; } EFriendRelationship friendRelationship = SteamFriends.GetFriendRelationship(steamId); return (int)friendRelationship == 3; } public static string FormatLobbyType(ELobbyType lobbyType) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected I4, but got Unknown if (1 == 0) { } string result = (int)lobbyType switch { 0 => "Private", 1 => "Friends Only", 2 => "Public", 3 => "Invisible", _ => "Unknown", }; if (1 == 0) { } return result; } public static string FormatSteamResult(EResult result) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected I4, but got Unknown //IL_031b: Unknown result type (might be due to invalid IL or missing references) if (1 == 0) { } string result2 = (result - 1) switch { 0 => "Success", 1 => "Generic failure", 2 => "No connection to Steam", 4 => "Invalid password", 5 => "Logged in elsewhere", 6 => "Invalid protocol version", 7 => "Invalid parameter", 8 => "File not found", 9 => "Service busy", 10 => "Invalid state", 11 => "Invalid name", 12 => "Invalid email", 13 => "Duplicate name", 14 => "Access denied", 15 => "Timeout", 16 => "Banned", 17 => "Account not found", 18 => "Invalid Steam ID", 19 => "Service unavailable", 20 => "Not logged on", 21 => "Request pending", 22 => "Encryption failure", 23 => "Insufficient privilege", 24 => "Limit exceeded", 25 => "Access revoked", 26 => "License expired", 27 => "Already redeemed", 28 => "Duplicate request", 29 => "Already owned", 30 => "IP not found", 31 => "Persist failed", 32 => "Locking failed", 33 => "Logon session replaced", 34 => "Connect failed", 35 => "Handshake failed", 36 => "IO failure", 37 => "Remote disconnect", 38 => "Shopping cart not found", 39 => "Blocked", 40 => "Ignored", 41 => "No match", 42 => "Account disabled", 43 => "Service read only", 44 => "Account not featured", 45 => "Administrator OK", 46 => "Content version", 47 => "Try another CM", 48 => "Password required to kick session", 49 => "Already logged in elsewhere", 50 => "Suspended", 51 => "Cancelled", 52 => "Data corruption", 53 => "Disk full", 54 => "Remote call failed", _ => $"Unknown result: {result}", }; if (1 == 0) { } return result2; } } } namespace SteamNetworkLib.Sync { public class ClientSyncVar<T> : IDisposable { private readonly SteamNetworkClient _client; private readonly string _key; private readonly string _fullKey; private readonly NetworkSyncOptions _options; private readonly ISyncSerializer _serializer; private readonly ISyncValidator<T>? _validator; private readonly T _defaultValue; private T _localValue; private readonly Dictionary<ulong, T> _playerValues = new Dictionary<ulong, T>(); private bool _disposed; private bool _isSubscribed; private DateTime _lastSyncTime; private T? _pendingValue; private bool _hasPendingValue; public T Value { get { return _localValue; } set { SetValue(value); } } public string Key => _key; public string FullKey => _fullKey; public bool IsDirty => _hasPendingValue; public event Action<CSteamID, T, T>? OnValueChanged; public event Action<T, T>? OnMyValueChanged; public event Action<Exception>? OnSyncError; internal ClientSyncVar(SteamNetworkClient client, string key, T defaultValue, NetworkSyncOptions? options = null, ISyncValidator<T>? validator = null) { _client = client ?? throw new ArgumentNullException("client"); if (string.IsNullOrEmpty(key)) { throw new ArgumentException("Sync key cannot be null or empty", "key"); } _key = key; _options = options ?? new NetworkSyncOptions(); _fullKey = _options.GetFullKey(key); _defaultValue = defaultValue; _localValue = defaultValue; _serializer = _options.Serializer ?? new JsonSyncSerializer(); _validator = validator; _lastSyncTime = DateTime.MinValue; if (!_serializer.CanSerialize(typeof(T))) { throw new SyncSerializationException("Type '" + typeof(T).Name + "' cannot be serialized. Ensure it has a parameterless constructor and all public properties are of supported types (primitives, strings, List<T>, Dictionary<string, T>, or other serializable custom types).", typeof(T), _fullKey); } SubscribeToChanges(); TryLoadExistingValues(); } private void SetValue(T newValue) { if (!_client.IsInLobby) { if (_options.WarnOnIgnoredWrites) { Console.WriteLine("[SteamNetworkLib] ClientSyncVar '" + _key + "': Write ignored - not in a lobby."); } } else { if (object.Equals(_localValue, newValue)) { return; } if (_validator != null && !_validator.IsValid(newValue)) { string text = _validator.GetErrorMessage(newValue) ?? "Validation failed"; string text2 = "[SteamNetworkLib] ClientSyncVar '" + _key + "': " + text; if (_options.ThrowOnValidationError) { throw new SyncValidationException(text2, _fullKey, newValue); } this.OnSyncError?.Invoke(new SyncValidationException(text2, _fullKey, newValue)); Console.WriteLine(text2); return; } if (_options.MaxSyncsPerSecond > 0) { TimeSpan timeSpan = DateTime.UtcNow - _lastSyncTime; TimeSpan timeSpan2 = TimeSpan.FromSeconds(1.0 / (double)_options.MaxSyncsPerSecond); if (timeSpan < timeSpan2) { _pendingValue = newValue; _hasPendingValue = true; return; } } if (!_options.AutoSync) { _pendingValue = newValue; _hasPendingValue = true; } else { PerformSync(newValue); } } } private void PerformSync(T newValue) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) T localValue = _localValue; try { string value = _serializer.Serialize(newValue); _client.SetMyData(_fullKey, value); _localValue = newValue; _playerValues[_client.LocalPlayerId.m_SteamID] = newValue; _lastSyncTime = DateTime.UtcNow; _hasPendingValue = false; this.OnValueChanged?.Invoke(_client.LocalPlayerId, localValue, newValue); this.OnMyValueChanged?.Invoke(localValue, newValue); } catch (Exception ex) { this.OnSyncError?.Invoke(ex); if (_options.WarnOnIgnoredWrites) { Console.WriteLine("[SteamNetworkLib] ClientSyncVar '" + _key + "': Sync error - " + ex.Message); } } } public void FlushPending() { if (_hasPendingValue && _pendingValue != null) { PerformSync(_pendingValue); } } public T GetValue(CSteamID playerId) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (_playerValues.TryGetValue(playerId.m_SteamID, out var value)) { return value; } if (_client.IsInLobby) { try { string playerData = _client.GetPlayerData(playerId, _fullKey); if (!string.IsNullOrEmpty(playerData)) { T val = _serializer.Deserialize<T>(playerData); _playerValues[playerId.m_SteamID] = val; return val; } } catch (Exception obj) { this.OnSyncError?.Invoke(obj); } } return _defaultValue; } public Dictionary<CSteamID, T> GetAllValues() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) Dictionary<CSteamID, T> dictionary = new Dictionary<CSteamID, T>(); if (!_client.IsInLobby) { return dictionary; } List<SteamNetworkLib.Models.MemberInfo> lobbyMembers = _client.GetLobbyMembers(); foreach (SteamNetworkLib.Models.MemberInfo item in lobbyMembers) { dictionary[item.SteamId] = GetValue(item.SteamId); } return dictionary; } public void Refresh() { _playerValues.Clear(); TryLoadExistingValues(); } private void TryLoadExistingValues() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) if (!_client.IsInLobby) { return; } try { List<SteamNetworkLib.Models.MemberInfo> lobbyMembers = _client.GetLobbyMembers(); foreach (SteamNetworkLib.Models.MemberInfo item in lobbyMembers) { string playerData = _client.GetPlayerData(item.SteamId, _fullKey); if (!string.IsNullOrEmpty(playerData)) { T val = _serializer.Deserialize<T>(playerData); _playerValues[item.SteamId.m_SteamID] = val; if (item.SteamId == _client.LocalPlayerId) { _localValue = val; } } } } catch (Exception obj) { this.OnSyncError?.Invoke(obj); } } private void SubscribeToChanges() { if (!_isSubscribed) { _client.OnMemberDataChanged += HandleMemberDataChanged; _client.OnLobbyJoined += HandleLobbyJoined; _client.OnMemberJoined += HandleMemberJoined; _client.OnMemberLeft += HandleMemberLeft; _isSubscribed = true; } } private void UnsubscribeFromChanges() { if (_isSubscribed) { _client.OnMemberDataChanged -= HandleMemberDataChanged; _client.OnLobbyJoined -= HandleLobbyJoined; _client.OnMemberJoined -= HandleMemberJoined; _client.OnMemberLeft -= HandleMemberLeft; _isSubscribed = false; } } private void HandleMemberDataChanged(object? sender, MemberDataChangedEventArgs e) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) if (e.Key != _fullKey) { return; } try { T value; T val = (_playerValues.TryGetValue(e.MemberId.m_SteamID, out value) ? value : _defaultValue); T val2; if (string.IsNullOrEmpty(e.NewValue)) { val2 = _defaultValue; _playerValues.Remove(e.MemberId.m_SteamID); } else { val2 = _serializer.Deserialize<T>(e.NewValue); _playerValues[e.MemberId.m_SteamID] = val2; } if (e.MemberId == _client.LocalPlayerId) { _localValue = val2; } if (!object.Equals(val, val2)) { this.OnValueChanged?.Invoke(e.MemberId, val, val2); if (e.MemberId == _client.LocalPlayerId) { this.OnMyValueChanged?.Invoke(val, val2); } } } catch (Exception obj) { this.OnSyncError?.Invoke(obj); } } private void HandleLobbyJoined(object? sender, LobbyJoinedEventArgs e) { _playerValues.Clear(); TryLoadExistingValues(); } private void HandleMemberJoined(object? sender, MemberJoinedEventArgs e) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) try { string playerData = _client.GetPlayerData(e.Member.SteamId, _fullKey); if (!string.IsNullOrEmpty(playerData)) { T value = _serializer.Deserialize<T>(playerData); _playerValues[e.Member.SteamId.m_SteamID] = value; } } catch (Exception obj) { this.OnSyncError?.Invoke(obj); } } private void HandleMemberLeft(object? sender, MemberLeftEventArgs e) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) _playerValues.Remove(e.Member.SteamId.m_SteamID); } public void Dispose() { if (!_disposed) { UnsubscribeFromChanges(); this.OnValueChanged = null; this.OnMyValueChanged = null; this.OnSyncError = null; _playerValues.Clear(); _disposed = true; } } } public class HostSyncVar<T> : IDisposable { private readonly SteamNetworkClient _client; private readonly string _key; private readonly string _fullKey; private readonly NetworkSyncOptions _options; private readonly ISyncSerializer _serializer; private readonly ISyncValidator<T>? _validator; private T _value; private T _defaultValue; private bool _disposed; private bool _isSubscribed; private DateTime _lastSyncTime; private T? _pendingValue; private bool _hasPendingValue; public T Value { get { return _value; } set { SetValue(value); } } public bool CanWrite => _client.IsHost; public string Key => _key; public string FullKey => _fullKey; public bool IsDirty => _hasPendingValue; public event Action<T, T>? OnValueChanged; public event Action<T>? OnWriteIgnored; public event Action<Exception>? OnSyncError; internal HostSyncVar(SteamNetworkClient client, string key, T defaultValue, NetworkSyncOptions? options = null, ISyncValidator<T>? validator = null) { _client = client ?? throw new ArgumentNullException("client"); if (string.IsNullOrEmpty(key)) { throw new ArgumentException("Sync key cannot be null or empty", "key"); } _key = key; _options = options ?? new NetworkSyncOptions(); _fullKey = _options.GetFullKey(key); _defaultValue = defaultValue; _value = defaultValue; _serializer = _options.Serializer ?? new JsonSyncSerializer(); _validator = validator; _lastSyncTime = DateTime.MinValue; if (!_serializer.CanSerialize(typeof(T))) { throw new SyncSerializationException("Type '" + typeof(T).Name + "' cannot be serialized. Ensure it has a parameterless constructor and all public properties are of supported types (primitives, strings, List<T>, Dictionary<string, T>, or other serializable custom types).", typeof(T), _fullKey); } SubscribeToChanges(); TryLoadExistingValue(); } private void SetValue(T newValue) { if (!_client.IsHost) { this.OnWriteIgnored?.Invoke(newValue); if (_options.WarnOnIgnoredWrites) { Console.WriteLine("[SteamNetworkLib] HostSyncVar '" + _key + "': Write ignored - only host can modify this value."); } } else { if (object.Equals(_value, newValue)) { return; } if (_validator != null && !_validator.IsValid(newValue)) { string text = _validator.GetErrorMessage(newValue) ?? "Validation failed"; string text2 = "[SteamNetworkLib] HostSyncVar '" + _key + "': " + text; if (_options.ThrowOnValidationError) { throw new SyncValidationException(text2, _fullKey, newValue); } this.OnSyncError?.Invoke(new SyncValidationException(text2, _fullKey, newValue)); Console.WriteLine(text2); return; } if (_options.MaxSyncsPerSecond > 0) { TimeSpan timeSpan = DateTime.UtcNow - _lastSyncTime; TimeSpan timeSpan2 = TimeSpan.FromSeconds(1.0 / (double)_options.MaxSyncsPerSecond); if (timeSpan < timeSpan2) { _pendingValue = newValue; _hasPendingValue = true; return; } } if (!_options.AutoSync) { _pendingValue = newValue; _hasPendingValue = true; } else { PerformSync(newValue); } } } private void PerformSync(T newValue) { T value = _value; try { string value2 = _serializer.Serialize(newValue); _client.SetLobbyData(_fullKey, value2); _value = newValue; _lastSyncTime = DateTime.UtcNow; _hasPendingValue = false; this.OnValueChanged?.Invoke(value, newValue); } catch (Exception ex) { this.OnSyncError?.Invoke(ex); if (_options.WarnOnIgnoredWrites) { Console.WriteLine("[SteamNetworkLib] HostSyncVar '" + _key + "': Sync error - " + ex.Message); } } } public void FlushPending() { if (_hasPendingValue && _pendingValue != null) { PerformSync(_pendingValue); } } public void Refresh() { TryLoadExistingValue(); } private void TryLoadExistingValue() { if (!_client.IsInLobby) { return; } try { string lobbyData = _client.GetLobbyData(_fullKey); if (!string.IsNullOrEmpty(lobbyData)) { T value = _value; _value = _serializer.Deserialize<T>(lobbyData); if (!object.Equals(value, _value)) { this.OnValueChanged?.Invoke(value, _value); } } } catch (Exception obj) { this.OnSyncError?.Invoke(obj); } } private void SubscribeToChanges() { if (!_isSubscribed) { _client.OnLobbyDataChanged += HandleLobbyDataChanged; _client.OnLobbyJoined += HandleLobbyJoined; _isSubscribed = true; } } private void UnsubscribeFromChanges() { if (_isSubscribed) { _client.OnLobbyDataChanged -= HandleLobbyDataChanged; _client.OnLobbyJoined -= HandleLobbyJoined; _isSubscribed = false; } } private void HandleLobbyDataChanged(object? sender, LobbyDataChangedEventArgs e) { if (e.Key != _fullKey) { return; } try { if (string.IsNullOrEmpty(e.NewValue)) { T value = _value; _value = _defaultValue; if (!object.Equals(value, _value)) { this.OnValueChanged?.Invoke(value, _value); } } else { T value2 = _value; _value = _serializer.Deserialize<T>(e.NewValue); if (!object.Equals(value2, _value)) { this.OnValueChanged?.Invoke(value2, _value); } } } catch (Exception obj) { this.OnSyncError?.Invoke(obj); } } private void HandleLobbyJoined(object? sender, LobbyJoinedEventArgs e) { TryLoadExistingValue(); } public void Dispose() { if (!_disposed) { UnsubscribeFromChanges(); this.OnValueChanged = null; this.OnWriteIgnored = null; this.OnSyncError = null; _disposed = true; } } } public interface ISyncSerializer { string Serialize<T>(T value); T Deserialize<T>(string data); bool CanSerialize(Type type); } public interface ISyncValidator<T> { bool IsValid(T value); string? GetErrorMessage(T value); } public class PredicateValidator<T> : ISyncValidator<T> { private readonly Func<T, bool> _predicate; private readonly string _errorMessage; public PredicateValidator(Func<T, bool> predicate, string errorMessage) { _predicate = predicate ?? throw new ArgumentNullException("predicate"); _errorMessage = errorMessage ?? "Validation failed"; } public bool IsValid(T value) { return _predicate(value); } public string? GetErrorMessage(T value) { return _errorMessage; } } public class RangeValidator<T> : ISyncValidator<T> where T : IComparable<T> { private readonly T _min; private readonly T _max; private readonly bool _inclusive; public RangeValidator(T min, T max, bool inclusive = true) { _min = min; _max = max; _inclusive = inclusive; } public bool IsValid(T value) { if (_inclusive) { return value.CompareTo(_min) >= 0 && value.CompareTo(_max) <= 0; } return value.CompareTo(_min) > 0 && value.CompareTo(_max) < 0; } public string? GetErrorMessage(T value) { string text = (_inclusive ? "[]" : "()"); return $"Value {value} must be in range {text[0]}{_min}, {_max}{text[1]}"; } } public class CompositeValidator<T> : ISyncValidator<T> { private readonly ISyncValidator<T>[] _validators; public CompositeValidator(params ISyncValidator<T>[] validators) { _validators = validators ?? throw new ArgumentNullException("validators"); } public bool IsValid(T value) { ISyncValidator<T>[] validators = _validators; foreach (ISyncValidator<T> syncValidator in validators) { if (!syncValidator.IsValid(value)) { return false; } } return true; } public string? GetErrorMessage(T value) { ISyncValidator<T>[] validators = _validators; foreach (ISyncValidator<T> syncValidator in validators) { if (!syncValidator.IsValid(value)) { return syncValidator.GetErrorMessage(value); } } return null; } } public class JsonSyncSerializer : ISyncSerializer { public string Serialize<T>(T value) { try { return SerializeValue(value, typeof(T)); } catch (SyncSerializationException) { throw; } catch (Exception ex2) { throw new SyncSerializationException("Failed to serialize type '" + typeof(T).Name + "': " + ex2.Message, typeof(T), string.Empty, ex2); } } public T Deserialize<T>(string data) { try { object obj = DeserializeValue(data, typeof(T)); return (T)obj; } catch (SyncSerializationException) { throw; } catch (Exception ex2) { throw new SyncSerializationException("Failed to deserialize to type '" + typeof(T).Name + "': " + ex2.Message, typeof(T), string.Empty, ex2); } } public bool CanSerialize(Type type) { return CanSerializeType(type, new HashSet<Type>()); } private bool CanSerializeType(Type type, HashSet<Type> visited) { if (visited.Contains(type)) { return true; } visited.Add(type); Type underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { return CanSerializeType(underlyingType, visited); } if (type == typeof(string) || type == typeof(int) || type == typeof(long) || type == typeof(float) || type == typeof(double) || type == typeof(bool) || type == typeof(byte) || type == typeof(short) || type == typeof(uint) || type == typeof(ulong) || type == typeof(decimal)) { return true; } if (type.IsEnum) { return true; } if (type.IsArray) { Type elementType = type.GetElementType(); return elementType != null && CanSerializeType(elementType, visited); } if (type.IsGenericType) { Type genericTypeDefinition = type.GetGenericTypeDefinition(); Type[] genericArguments = type.GetGenericArguments(); if (genericTypeDefinition == typeof(List<>)) { return CanSerializeType(genericArguments[0], visited); } if (genericTypeDefinition == typeof(Dictionary<, >)) { return genericArguments[0] == typeof(string) && CanSerializeType(genericArguments[1], visited); } } if (type.IsClass || type.IsValueType) { if (type.IsClass) { ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes); if (constructor == null) { return false; } } PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); PropertyInfo[] array = properties; foreach (PropertyInfo propertyInfo in array) { if (propertyInfo.CanRead && propertyInfo.CanWrite && !CanSerializeType(propertyInfo.PropertyType, visited)) { return false; } } return true; } return false; } private string SerializeValue(object? value, Type type) { if (value == null) { return "null"; } Type underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { type = underlyingType; } if (type == typeof(string)) { return EscapeString((string)value); } if (type == typeof(bool)) { return ((bool)value) ? "true" : "false"; } if (type == typeof(int) || type == typeof(long) || type == typeof(byte) || type == typeof(short) || type == typeof(uint) || type == typeof(ulong)) { return value.ToString(); } if (type == typeof(float)) { return ((float)value).ToString(CultureInfo.InvariantCulture); } if (type == typeof(double)) { return ((double)value).ToString(CultureInfo.InvariantCulture); } if (type == typeof(decimal)) { return ((decimal)value).ToString(CultureInfo.InvariantCulture); } if (type.IsEnum) { return Convert.ToInt64(value).ToString(); } if (type.IsArray) { Array array = (Array)value; Type elementType = type.GetElementType(); StringBuilder stringBuilder = new StringBuilder("["); for (int i = 0; i < array.Length; i++) { if (i > 0) { stringBuilder.Append(","); } stringBuilder.Append(SerializeValue(array.GetValue(i), elementType)); } stringBuilder.Append("]"); return stringBuilder.ToString(); } if (type.IsGenericType) { Type genericTypeDefinition = type.GetGenericTypeDefinition(); Type[] genericArguments = type.GetGenericArguments(); if (genericTypeDefinition == typeof(List<>)) { IList list = (IList)value; Type type2 = genericArguments[0]; StringBuilder stringBuilder2 = new StringBuilder("["); for (int j = 0; j < list.Count; j++) { if (j > 0) { stringBuilder2.Append(","); } stringBuilder2.Append(SerializeValue(list[j], type2)); } stringBuilder2.Append("]"); return stringBuilder2.ToString(); } if (genericTypeDefinition == typeof(Dictionary<, >) && genericArguments[0] == typeof(string)) { IDictionary dictionary = (IDictionary)value; Type type3 = genericArguments[1]; StringBuilder stringBuilder3 = new StringBuilder("{"); bool flag = true; foreach (DictionaryEntry item in dictionary) { if (!flag) { stringBuilder3.Append(","); } flag = false; stringBuilder3.Append(EscapeString((string)item.Key)); stringBuilder3.Append(":"); stringBuilder3.Append(SerializeValue(item.Value, type3)); } stringBuilder3.Append("}"); return stringBuilder3.ToString(); } } if (type.IsClass || type.IsValueType) { StringBuilder stringBuilder4 = new StringBuilder("{"); PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); bool flag2 = true; PropertyInfo[] array2 = properties; foreach (PropertyInfo propertyInfo in array2) { if (propertyInfo.CanRead && propertyInfo.CanWrite) { if (!flag2) { stringBuilder4.Append(","); } flag2 = false; stringBuilder4.Append(EscapeString(propertyInfo.Name)); stringBuilder4.Append(":"); stringBuilder4.Append(SerializeValue(propertyInfo.GetValue(value), propertyInfo.PropertyType)); } } stringBuilder4.Append("}"); return stringBuilder4.ToString(); } throw new SyncSerializationException("Cannot serialize type: " + type.Name, type); } private object? DeserializeValue(string json, Type type) { json = json.Trim(); if (json == "null") { return null; } Type underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { type = underlyingType; } if (type == typeof(string)) { return UnescapeString(json); } if (type == typeof(bool)) { return json == "true"; } if (type == typeof(int)) { return int.Parse(json, CultureInfo.InvariantCulture); } if (type == typeof(long)) { return long.Parse(json, CultureInfo.InvariantCulture); } if (type == typeof(float)) { return float.Parse(json, CultureInfo.InvariantCulture); } if (type == typeof(double)) { return double.Parse(json, CultureInfo.InvariantCulture); } if (type == typeof(byte)) { return byte.Parse(json, CultureInfo.InvariantCulture); } if (type == typeof(short)) { return short.Parse(json, CultureInfo.InvariantCulture); } if (type == typeof(uint)) { return uint.Parse(json, CultureInfo.InvariantCulture); } if (type == typeof(ulong)) { return ulong.Parse(json, CultureInfo.InvariantCulture); } if (type == typeof(decimal)) { return decimal.Parse(json, CultureInfo.InvariantCulture); } if (type.IsEnum) { long value = long.Parse(json, CultureInfo.InvariantCulture); return Enum.ToObject(type, value); } if (type.IsArray) { Type elementType = type.GetElementType(); List<string> list = ParseJsonArray(json); Array array = Array.CreateInstance(elementType, list.Count); for (int i = 0; i < list.Count; i++) { array.SetValue(DeserializeValue(list[i], elementType), i); } return array; } if (type.IsGenericType) { Type genericTypeDefinition = type.GetGenericTypeDefinition(); Type[] genericArguments = type.GetGenericArguments(); if (genericTypeDefinition == typeof(List<>)) { Type type2 = genericArguments[0]; IList list2 = (IList)Activator.CreateInstance(type); List<string> list3 = ParseJsonArray(json); foreach (string item in list3) { list2.Add(DeserializeValue(item, type2)); } return list2; } if (genericTypeDefinition == typeof(Dictionary<, >) && genericArguments[0] == typeof(string)) { Type type3 = genericArguments[1]; IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Dictionary<string, string> dictionary2 = ParseJsonObject(json); foreach (KeyValuePair<string, string> item2 in dictionary2) { dictionary[item2.Key] = DeserializeValue(item2.Value, type3); } return dictionary; } } if (type.IsClass || type.IsValueType) { object obj = Activator.CreateInstance(type); Dictionary<string, string> dictionary3 = ParseJsonObject(json); PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); PropertyInfo[] array2 = properties; foreach (PropertyInfo propertyInfo in array2) { if (propertyInfo.CanRead && propertyInfo.CanWrite && dictionary3.ContainsKey(propertyInfo.Name)) { object value2 = DeserializeValue(dictionary3[propertyInfo.Name], propertyInfo.PropertyType); propertyInfo.SetValue(obj, value2); } } return obj; } throw new SyncSerializationException("Cannot deserialize type: " + type.Name, type); } private string EscapeString(string value) { StringBuilder stringBuilder = new StringBuilder("\""); foreach (char c in value) { switch (c) { case '"': stringBuilder.Append("\\\""); break; case '\\': stringBuilder.Append("\\\\"); break; case '\n': stringBuilder.Append("\\n"); break; case '\r': stringBuilder.Append("\\r"); break; case '\t': stringBuilder.Append("\\t"); break; default: stringBuilder.Append(c); break; } } stringBuilder.Append("\""); return stringBuilder.ToString(); } private string UnescapeString(string json) { if (json.Length < 2 || json[0] != '"' || json[json.Length - 1] != '"') { throw new SyncSerializationException("Invalid JSON string format"); } StringBuilder stringBuilder = new StringBuilder(); bool flag = false; for (int i = 1; i < json.Length - 1; i++) { char c = json[i]; if (flag) { switch (c) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; default: stringBuilder.Append(c); break; } flag = false; } else if (c == '\\') { flag = true; } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } private List<string> ParseJsonArray(string json) { List<string> list = new List<string>(); if (json.Length < 2 || json[0] != '[' || json[json.Length - 1] != ']') { throw new SyncSerializationException("Invalid JSON array format"); } int num = 0; bool flag = false; bool flag2 = false; StringBuilder stringBuilder = new StringBuilder(); for (int i = 1; i < json.Length - 1; i++) { char c = json[i]; if (flag2) { stringBuilder.Append(c); flag2 = false; continue; } switch (c) { case '\\': stringBuilder.Append(c); flag2 = true; continue; case '"': flag = !flag; stringBuilder.Append(c); continue; } if (!flag) { if (c == '[' || c == '{') { num++; } else if (c == ']' || c == '}') { num--; } else if (c == ',' && num == 0) { string text = stringBuilder.ToString().Trim(); if (text.Length > 0) { list.Add(text); } stringBuilder.Clear(); continue; } } stringBuilder.Append(c); } string text2 = stringBuilder.ToString().Trim(); if (text2.Length > 0) { list.Add(text2); } return list; } private Dictionary<string, string> ParseJsonObject(string json) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); if (json.Length < 2 || json[0] != '{' || json[json.Length - 1] != '}') { throw new SyncSerializationException("Invalid JSON object format"); } int num = 0; bool flag = false; bool flag2 = false; bool flag3 = true; StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = new StringBuilder(); for (int i = 1; i < json.Length - 1; i++) { char c = json[i]; if (flag2) { if (flag3) { stringBuilder.Append(c); } else { stringBuilder2.Append(c); } flag2 = false; continue; } switch (c) { case '\\': if (flag3) { stringBuilder.Append(c); } else { stringBuilder2.Append(c); } flag2 = true; continue; case '"': flag = !flag; if (flag3) { stringBuilder.Append(c); } else { stringBuilder2.Append(c); } continue; } if (!flag) { if (c == '[' || c == '{') { num++; } else if (c == ']' || c == '}') { num--; } else { if (c == ':' && num == 0 && flag3) { flag3 = false; continue; } if (c == ',' && num == 0) { string text = UnescapeString(stringBuilder.ToString().Trim()); string value = stringBuilder2.ToString().Trim(); if (text.Length > 0) { dictionary[text] = value; } stringBuilder.Clear(); stringBuilder2.Clear(); flag3 = true; continue; } } } if (flag3) { stringBuilder.Append(c); } else { stringBuilder2.Append(c); } } string text2 = stringBuilder.ToString().Trim(); string value2 = stringBuilder2.ToString().Trim(); if (text2.Length > 0) { dictionary[UnescapeString(text2)] = value2; } return dictionary; } } public class NetworkSyncOptions { internal const string ReservedPrefix = "__snl_sync_"; public bool AutoSync { get; set; } = true; public bool SyncOnPlayerJoin { get; set; } = true; public int MaxSyncsPerSecond { get; set; } = 0; public bool ThrowOnValidationError { get; set; } = false; public bool WarnOnIgnoredWrites { get; set; } = false; public ISyncSerializer? Serializer { get; set; } public string? KeyPrefix { get; set; } internal string GetFullKey(string key) { if (string.IsNullOrEmpty(KeyPrefix)) { return key; } return KeyPrefix + key; } } public class SyncSerializationException : Exception { public Type? TargetType { get; } public string? SyncKey { get; } public SyncSerializationException(string message) : base(message) { } public SyncSerializationException(string message, Exception innerException) : base(message, innerException) { } public SyncSerializationException(string message, Type targetType) : base(message) { TargetType = targetType; } public SyncSerializationException(string message, Type targetType, string syncKey) : base(message) { TargetType = targetType; SyncKey = syncKey; } public SyncSerializationException(string message, Type targetType, string syncKey, Exception innerException) : base(message, innerException) { TargetType = targetType; SyncKey = syncKey; } } public class SyncValidationException : Exception { public string? SyncKey { get; } public object? InvalidValue { get; } public SyncValidationException(string message) : base(message) { } public SyncValidationException(string message, string syncKey, object? invalidValue) : base(message) { SyncKey = syncKey; InvalidValue = invalidValue; } } public class SyncException : Exception { public string? SyncKey { get; } public SyncException(string message) : base(message) { } public SyncException(string message, Exception innerException) : base(message, innerException) { } public SyncException(string message, string syncKey) : base(message) { SyncKey = syncKey; } public SyncException(string message, string syncKey, Exception innerException) : base(message, innerException) { SyncKey = syncKey; } } } namespace SteamNetworkLib.Streaming { public abstract class StreamChannel<T> : IDisposable where T : class { protected readonly SortedDictionary<uint, StreamFrame<T>> _jitterBuffer = new SortedDictionary<uint, StreamFrame<T>>(); protected uint _expectedSequence = 0u; protected uint _highestSequenceReceived = 0u; protected bool _isStreaming = false; protected bool _isDisposed = false; protected DateTime _lastFrameTime = DateTime.UtcNow; protected readonly object _bufferLock = new object(); public string StreamId { get; } public int BufferMs { get; set; } = 200; public int MaxBufferMs { get; set; } = 500; public bool EnablePacketLossDetection { get; set; } = true; public bool EnableJitterBuffer { get; set; } = true; public int BufferedFrameCount => _jitterBuffer.Count; public uint TotalFramesReceived { get; private set; } public uint TotalFramesDropped { get; private set; } public uint TotalFramesLate { get; private set; } public double AverageJitter { get; private set; } public event Action<T>? OnFrameReady; public event Action<uint>? OnFrameDropped; public event Action<StreamChannel<T>>? OnStreamStarted; public event Action<StreamChannel<T>>? OnStreamEnded; public event Action<uint, TimeSpan>? OnFrameLate; protected StreamChannel(string streamId) { StreamId = streamId ?? throw new ArgumentNullException("streamId"); } public virtual void ProcessStreamMessage(StreamMessage message, CSteamID senderId) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (_isDisposed || message.StreamId != StreamId) { return; } DateTime utcNow = DateTime.UtcNow; try { if (message.IsStreamStart && !_isStreaming) { Console.WriteLine($"[StreamChannel] Stream {StreamId} started from {senderId}"); StartStream(); } else if (message.IsStreamEnd && _isStreaming) { Console.WriteLine($"[StreamChannel] Stream {StreamId} ended from {senderId}"); EndStream(); return; } TotalFramesReceived++; _highestSequenceReceived = Math.Max(_highestSequenceReceived, message.SequenceNumber); if (_expectedSequence == 0 && message.SequenceNumber != 0) { _expectedSequence = message.SequenceNumber; Console.WriteLine($"[StreamChannel] Setting initial expected sequence to {_expectedSequence}"); } if (message.SequenceNumber < _expectedSequence) { Console.WriteLine($"[StreamChannel] Ignoring old/duplicate frame {message.SequenceNumber} (expected: {_expectedSequence})"); return; } if (message.SequenceNumber > _expectedSequence && TotalFramesReceived > 10) { uint num = message.SequenceNumber - _expectedSequence; Console.WriteLine($"[StreamChannel] Sequence gap detected: got {message.SequenceNumber}, expected {_expectedSequence} (gap: {num})"); } long num2 = (utcNow.Ticks - message.CaptureTimestamp) / 10000; if (message.StreamData == null || message.StreamData.Length == 0) { Console.WriteLine($"[StreamChannel] Warning: Empty StreamData received in frame {message.SequenceNumber}"); } else if (message.StreamData.Length > 4 && message.StreamData[0] == 0 && message.StreamData[1] == 0 && message.StreamData[2] == 0 && message.StreamData[3] == 0) { Console.WriteLine($"[StreamChannel] IL2CPP marshalling issue detected! StreamData contains all zeros in frame {message.SequenceNumber}"); string text = ""; for (int i = 0; i < Math.Min(message.StreamData.Length, 8); i++) { text = text + message.StreamData[i].ToString("X2") + " "; } Console.WriteLine("[StreamChannel] First bytes: " + text); TotalFramesDropped++; return; } DateTime utcNow2 = DateTime.UtcNow; T val = DeserializeFrame(message.StreamData, message); double totalMilliseconds = (DateTime.UtcNow - utcNow2).TotalMilliseconds; if (val == null) { Console.WriteLine($"[StreamChannel] Failed to deserialize frame {message.SequenceNumber}"); return; } StreamFrame<T> streamFrame = new StreamFrame<T> { SequenceNumber = message.SequenceNumber, Data = val, CaptureTimestamp = message.CaptureTimestamp, ReceivedTimestamp = utcNow.Ticks, FrameDurationMs = message.FrameDurationMs, IsRetransmit = message.IsRetransmit }; if (TotalFramesReceived % 50 == 0) { Console.WriteLine($"[StreamChannel] Frame {message.SequenceNumber}: End-to-end latency: {num2}ms, Deserialize time: {totalMilliseconds:F2}ms, Buffer level: {_jitterBuffer.Count}"); } if (EnableJitterBuffer) { AddToJitterBuffer(streamFrame); return; } this.OnFrameReady?.Invoke(streamFrame.Data); _expectedSequence = message.SequenceNumber + 1; } catch (Exception ex) { Console.WriteLine("[StreamChannel] Error processing stream message: " + ex.Message); } } public virtual void Update() { if (_isDisposed || !EnableJitterBuffer) { return; } lock (_bufferLock) { ProcessJitterBuffer(); CleanupOldFrames(); DetectLostFrames(); } } protected abstract T? DeserializeFrame(byte[] data, StreamMessage message); protected virtual T? HandleMissingFrame(uint sequenceNumber) { return null; } private void AddToJitterBuffer(StreamFrame<T> frame) { lock (_bufferLock) { if (frame.SequenceNumber >= _expectedSequence) { _jitterBuffer[frame.SequenceNumber] = frame; int num = Math.Max(5, MaxBufferMs / frame.FrameDurationMs); while (_jitterBuffer.Count > num) { KeyValuePair<uint, StreamFrame<T>> keyValuePair = _jitterBuffer.First(); _jitterBuffer.Remove(keyValuePair.Key); TotalFramesDropped++; this.OnFrameDropped?.Invoke(keyValuePair.Key); Console.WriteLine($"[StreamBuffer] Dropped old frame {keyValuePair.Key} - buffer too large ({_jitterBuffer.Count}/{num})"); } } } } private void ProcessJitterBuffer() { DateTime utcNow = DateTime.UtcNow; int num = ((_jitterBuffer.Count > 0) ? _jitterBuffer.Values.First().FrameDurationMs : 40); int num2 = Math.Max(2, BufferMs / num); if (_jitterBuffer.Count < num2 && _expectedSequence < 10) { if (_expectedSequence == 0 && _jitterBuffer.Count > 0) { Console.WriteLine($"[StreamBuffer] Building initial buffer: {_jitterBuffer.Count}/{num2} frames ({num}ms frames)"); } return; } if (_jitterBuffer.Count == 0 && _isStreaming) { Console.WriteLine("[StreamBuffer] Buffer underrun! Waiting for frames to rebuild buffer..."); return; } int num3 = 0; while (_jitterBuffer.ContainsKey(_expectedSequence) && num3 < 3) { StreamFrame<T> streamFrame = _jitterBuffer[_expectedSequence]; _jitterBuffer.Remove(_expectedSequence); DateTime dateTime = _lastFrameTime.AddMilliseconds(streamFrame.FrameDurationMs); DateTime utcNow2 = DateTime.UtcNow; if (utcNow2 > dateTime.AddMilliseconds(BufferMs)) { TotalFramesLate++; TimeSpan arg = utcNow2 - dateTime; this.OnFrameLate?.Invoke(_expectedSequence, arg); if (arg.TotalMilliseconds > 60.0) { Console.WriteLine($"[StreamBuffer] Frame {_expectedSequence} is {arg.TotalMilliseconds:F1}ms late!"); } } if (_expectedSequence % 100 == 0) { int count = _jitterBuffer.Count; double totalMilliseconds = (DateTime.UtcNow - utcNow).TotalMilliseconds; Console.WriteLine($"[StreamBuffer] Processed frame {_expectedSequence}, buffer level: {count}, processing time: {totalMilliseconds:F2}ms"); } this.OnFrameReady?.Invoke(streamFrame.Data); _lastFrameTime = utcNow2; _expectedSequence++; num3++; } if (!EnablePacketLossDetection || _jitterBuffer.ContainsKey(_expectedSequence)) { return; } uint num4 = _jitterBuffer.Keys.FirstOrDefault((uint seq) => seq > _expectedSequence); if (num4 == 0) { return; } uint num5 = num4 - _expectedSequence; if (num5 <= 3) { T val = HandleMissingFrame(_expectedSequence); if (val != null) { this.OnFrameReady?.Invoke(val); Console.WriteLine($"[StreamBuffer] Used PLC for missing frame {_expectedSequence}"); } else { TotalFramesDropped++; this.OnFrameDropped?.Invoke(_expectedSequence); } _expectedSequence++; } else { Console.WriteLine($"[StreamBuffer] Large gap detected ({num5} frames), skipping to frame {num4}"); for (uint num6 = _expectedSequence; num6 < num4; num6++) { TotalFramesDropped++; this.OnFrameDropped?.Invoke(num6); } _expectedSequence = num4; } } private void CleanupOldFrames() { DateTime cutoffTime = DateTime.UtcNow.AddMilliseconds(-MaxBufferMs); long ticks = cutoffTime.Ticks; List<uint> list = (from kvp in _jitterBuffer where new DateTime(kvp.Value.ReceivedTimestamp) < cutoffTime select kvp.Key).ToList(); foreach (uint item in list) { _jitterBuffer.Remove(item); TotalFramesDropped++; this.OnFrameDropped?.Invoke(item); } } private void DetectLostFrames() { if (_jitterBuffer.Count <= 0) { return; } uint num = _jitterBuffer.Keys.Min(); uint num2 = num - _expectedSequence; int num3 = Math.Max(5, BufferMs / 40); if (num2 > num3) { Console.WriteLine($"[StreamBuffer] Large gap detected: {num2} frames (max: {num3}), jumping to frame {num}"); for (uint num4 = _expectedSequence; num4 < num; num4++) { TotalFramesDropped++; this.OnFrameDropped?.Invoke(num4); } _expectedSequence = num; } } private void StartStream() { _isStreaming = true; _expectedSequence = 0u; _highestSequenceReceived = 0u; _lastFrameTime = DateTime.UtcNow; this.OnStreamStarted?.Invoke(this); } private void EndStream() { _isStreaming = false; this.OnStreamEnded?.Invoke(this); } public virtual void Reset() { lock (_bufferLock) { _jitterBuffer.Clear(); _expectedSequence = 0u; _highestSequenceReceived = 0u; TotalFramesReceived = 0u; TotalFramesDropped = 0u; TotalFramesLate = 0u; _isStreaming = false; } } public virtual void Dispose() { if (!_isDisposed) { lock (_bufferLock) { _jitterBuffer.Clear(); } _isDisposed = true; } } } public class StreamFrame<T> where T : class { public uint SequenceNumber { get; set; } public T Data { get; set; } = null; public long CaptureTimestamp { get; set; } public long ReceivedTimestamp { get; set; } public int FrameDurationMs { get; set; } = 20; public bool IsRetransmit { get; set; } } public abstract class StreamSender<T> : IDisposable where T : class { protected uint _currentSequence = 0u; protected bool _isDisposed = false; protected SteamNetworkClient? _networkClient; public string StreamId { get; } public string StreamType { get; protected set; } = "data"; public string PayloadType { get; protected set; } = "data"; public int FrameDurationMs { get; protected set; } = 20; public bool IsStreaming { get; private set; } public uint TotalFramesSent { get; private set; } public uint TotalBytesSent { get; private set; } public event Action<StreamSender<T>>? OnStreamStarted; public event Action<StreamSender<T>>? OnStreamEnded; public event Action<uint, int>? OnFrameSent; protected StreamSender(string streamId, SteamNetworkClient? networkClient) { StreamId = streamId ?? throw new ArgumentNullException("streamId"); _networkClient = networkClient; } public virtual void StartStream() { if (!IsStreaming && !_isDisposed) { IsStreaming = true; _currentSequence = 0u; StreamMessage message = CreateStreamMessage(new byte[0], isStart: true, isEnd: false); SendStreamMessage(message); this.OnStreamStarted?.Invoke(this); } } public virtual void StopStream() { if (IsStreaming && !_isDisposed) { StreamMessage message = CreateStreamMessage(new byte[0], isStart: false, isEnd: true); SendStreamMessage(message); IsStreaming = false; this.OnStreamEnded?.Invoke(this); } } protected virtual void SendFrame(T frameData) { if (!IsStreaming || _isDisposed || frameData == null) { return; } try { byte[] array = SerializeFrame(frameData); if (array != null && array.Length != 0) { StreamMessage message = CreateStreamMessage(array, isStart: false, isEnd: false); SendStreamMessage(message); TotalFramesSent++; TotalBytesSent += (uint)array.Length; this.OnFrameSent?.Invoke(_currentSequence - 1, array.Length); } } catch (Exception ex) { Console.WriteLine("Error sending frame: " + ex.Message); } } protected abstract byte[]? SerializeFrame(T frameData); protected virtual StreamMessage CreateStreamMessage(byte[] data, bool isStart, bool isEnd) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) return new StreamMessage { StreamType = StreamType, StreamId = StreamId, SequenceNumber = _currentSequence++, CaptureTimestamp = DateTime.UtcNow.Ticks, IsStreamStart = isStart, IsStreamEnd = isEnd, StreamData = data, PayloadType = PayloadType, FrameDurationMs = FrameDurationMs, Priority = 128, RecommendedSendType = GetRecommendedSendType() }; } protected virtual EP2PSend GetRecommendedSendType() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) return (EP2PSend)1; } protected virtual void SendStreamMessage(StreamMessage message) { if (_networkClient == null) { Console.WriteLine("[StreamSender] Warning: No network client available for sending stream message"); return; } try { DateTime utcNow = DateTime.UtcNow; _networkClient.BroadcastMessage(message); double totalMilliseconds = (DateTime.UtcNow - utcNow).TotalMilliseconds; if (totalMilliseconds > 10.0) { Console.WriteLine($"[StreamSender] Network broadcast took {totalMilliseconds:F2}ms (slow!) for frame {message.SequenceNumber}"); } else if (message.SequenceNumber % 100 == 0) { Console.WriteLine($"[StreamSender] Frame {message.SequenceNumber}: Network send took {totalMilliseconds:F2}ms"); } } catch (Exception ex) { Console.WriteLine("[StreamSender] Error sending stream message: " + ex.Message); } } protected virtual async Task<bool> SendFrameToTarget(T frameData, CSteamID targetId) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!IsStreaming || _isDisposed || frameData == null || _networkClient == null) { return false; } try { byte[] encodedData = SerializeFrame(frameData); if (encodedData == null || encodedData.Length == 0) { return false; } StreamMessage message = CreateStreamMessage(encodedData, isStart: false, isEnd: false); return await _networkClient.SendMessageToPlayerAsync(targetId, message); } catch (Exception ex) { Console.WriteLine("Error sending frame to target: " + ex.Message); return false; } } public virtual void Reset() { _currentSequence = 0u; TotalFramesSent = 0u; TotalBytesSent = 0u; IsStreaming = false; } public virtual void Dispose() { if (!_isDisposed) { if (IsStreaming) { StopStream(); } _isDisposed = true; } } } } namespace SteamNetworkLib.Models { public class DataSyncMessage : P2PMessage { public override string MessageType => "DATA_SYNC"; public string Key { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; public string DataType { get; set; } = "string"; public override byte[] Serialize() { string text = Key.Replace("\"", "\\\""); string text2 = Value.Replace("\"", "\\\""); string text3 = DataType.Replace("\"", "\\\""); string s = "{" + CreateJsonBase("\"Key\":\"" + text + "\",\"Value\":\"" + text2 + "\",\"DataType\":\"" + text3 + "\"") + "}"; return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { try { string @string = Encoding.UTF8.GetString(data); ParseJsonBase(@string); Key = ExtractJsonValue(@string, "Key"); Value = ExtractJsonValue(@string, "Value"); DataType = ExtractJsonValue(@string, "DataType"); } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] DataSyncMessage.Deserialize ERROR: " + ex.Message); Console.WriteLine("[SteamNetworkLib] DataSyncMessage.Deserialize Stack Trace: " + ex.StackTrace); try { string string2 = Encoding.UTF8.GetString(data); Console.WriteLine("[SteamNetworkLib] Raw JSON: " + string2); } catch { } throw; } } } public class EventMessage : P2PMessage { public override string MessageType => "EVENT"; public string EventType { get; set; } = "user"; public string EventName { get; set; } = string.Empty; public string EventData { get; set; } = string.Empty; public int Priority { get; set; } = 1; public bool ShouldPersist { get; set; } public string TargetAudience { get; set; } = "all"; public string TargetPlayerIds { get; set; } = string.Empty; public bool RequiresAck { get; set; } public string EventId { get; set; } = Guid.NewGuid().ToString(); public DateTime? ExpiresAt { get; set; } public string Tags { get; set; } = string.Empty; public override byte[] Serialize() { string text = ExpiresAt?.ToString("O") ?? ""; string s = "{" + CreateJsonBase($"\"EventType\":\"{EventType}\",\"EventName\":\"{EventName}\",\"EventData\":\"{EventData}\",\"Priority\":{Priority},\"ShouldPersist\":{ShouldPersist.ToString().ToLower()},\"TargetAudience\":\"{TargetAudience}\",\"TargetPlayerIds\":\"{TargetPlayerIds}\",\"RequiresAck\":{RequiresAck.ToString().ToLower()},\"EventId\":\"{EventId}\",\"ExpiresAt\":\"{text}\",\"Tags\":\"{Tags}\"") + "}"; return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { string @string = Encoding.UTF8.GetString(data); ParseJsonBase(@string); EventType = ExtractJsonValue(@string, "EventType"); EventName = ExtractJsonValue(@string, "EventName"); EventData = ExtractJsonValue(@string, "EventData"); if (int.TryParse(ExtractJsonValue(@string, "Priority"), out var result)) { Priority = result; } ShouldPersist = ExtractJsonValue(@string, "ShouldPersist").ToLower() == "true"; TargetAudience = ExtractJsonValue(@string, "TargetAudience"); TargetPlayerIds = ExtractJsonValue(@string, "TargetPlayerIds"); RequiresAck = ExtractJsonValue(@string, "RequiresAck").ToLower() == "true"; EventId = ExtractJsonValue(@string, "EventId"); string text = ExtractJsonValue(@string, "ExpiresAt"); if (!string.IsNullOrEmpty(text) && DateTime.TryParse(text, out var result2)) { ExpiresAt = result2; } Tags = ExtractJsonValue(@string, "Tags"); } } public class FileTransferMessage : P2PMessage { public override string MessageType => "FILE_TRANSFER"; public string FileName { get; set; } = string.Empty; public int FileSize { get; set; } public int ChunkIndex { get; set; } public int TotalChunks { get; set; } public bool IsFileData { get; set; } public byte[] ChunkData { get; set; } = new byte[0]; public override byte[] Serialize() { string s = "{" + CreateJsonBase($"\"FileName\":\"{FileName}\",\"FileSize\":{FileSize},\"ChunkIndex\":{ChunkIndex},\"TotalChunks\":{TotalChunks},\"IsFileData\":{IsFileData.ToString().ToLower()}") + "}"; byte[] bytes = Encoding.UTF8.GetBytes(s); byte[] array = new byte[4 + bytes.Length + ChunkData.Length]; int num = 0; BitConverter.GetBytes(bytes.Length).CopyTo(array, num); num += 4; bytes.CopyTo(array, num); num += bytes.Length; ChunkData.CopyTo(array, num); return array; } public override void Deserialize(byte[] data) { if (data.Length < 4) { return; } int num = BitConverter.ToInt32(data, 0); if (num > 0 && num <= data.Length - 4) { string @string = Encoding.UTF8.GetString(data, 4, num); ParseJsonBase(@string); FileName = ExtractJsonValue(@string, "FileName"); if (int.TryParse(ExtractJsonValue(@string, "FileSize"), out var result)) { FileSize = result; } if (int.TryParse(ExtractJsonValue(@string, "ChunkIndex"), out var result2)) { ChunkIndex = result2; } if (int.TryParse(ExtractJsonValue(@string, "TotalChunks"), out var result3)) { TotalChunks = result3; } IsFileData = ExtractJsonValue(@string, "IsFileData").ToLower() == "true"; int num2 = data.Length - 4 - num; if (num2 > 0) { ChunkData = new byte[num2]; Array.Copy(data, 4 + num, ChunkData, 0, num2); } } } } public class HeartbeatMessage : P2PMessage { public override string MessageType => "HEARTBEAT"; public string HeartbeatId { get; set; } = Guid.NewGuid().ToString(); public bool IsResponse { get; set; } public long HighPrecisionTimestamp { get; set; } public uint SequenceNumber { get; set; } public float PacketLossPercent { get; set; } public float AverageLatencyMs { get; set; } public int BandwidthUsage { get; set; } public string PlayerStatus { get; set; } = "online"; public string ConnectionInfo { get; set; } = string.Empty; public override byte[] Serialize() { if (HighPrecisionTimestamp == 0) { HighPrecisionTimestamp = DateTime.UtcNow.Ticks; } string s = "{" + CreateJsonBase($"\"HeartbeatId\":\"{HeartbeatId}\",\"IsResponse\":{IsResponse.ToString().ToLower()},\"HighPrecisionTimestamp\":{HighPrecisionTimestamp},\"SequenceNumber\":{SequenceNumber},\"PacketLossPercent\":{PacketLossPercent},\"AverageLatencyMs\":{AverageLatencyMs},\"BandwidthUsage\":{BandwidthUsage},\"PlayerStatus\":\"{PlayerStatus}\",\"ConnectionInfo\":\"{ConnectionInfo}\"") + "}"; return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { try { string @string = Encoding.UTF8.GetString(data); ParseJsonBase(@string); HeartbeatId = ExtractJsonValue(@string, "HeartbeatId"); IsResponse = ExtractJsonValue(@string, "IsResponse").ToLower() == "true"; if (long.TryParse(ExtractJsonValue(@string, "HighPrecisionTimestamp"), out var result)) { HighPrecisionTimestamp = result; } if (uint.TryParse(ExtractJsonValue(@string, "SequenceNumber"), out var result2)) { SequenceNumber = result2; } if (float.TryParse(ExtractJsonValue(@string, "PacketLossPercent"), out var result3)) { PacketLossPercent = result3; } if (float.TryParse(ExtractJsonValue(@string, "AverageLatencyMs"), out var result4)) { AverageLatencyMs = result4; } if (int.TryParse(ExtractJsonValue(@string, "BandwidthUsage"), out var result5)) { BandwidthUsage = result5; } PlayerStatus = ExtractJsonValue(@string, "PlayerStatus"); ConnectionInfo = ExtractJsonValue(@string, "ConnectionInfo"); } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] HeartbeatMessage.Deserialize ERROR: " + ex.Message); Console.WriteLine("[SteamNetworkLib] HeartbeatMessage.Deserialize Stack Trace: " + ex.StackTrace); throw; } } } public class LobbyInfo { public CSteamID LobbyId { get; set; } public CSteamID OwnerId { get; set; } public int MemberCount { get; set; } public int MaxMembers { get; set; } public string? Name { get; set; } public DateTime CreatedAt { get; set; } public LobbyInfo() { CreatedAt = DateTime.Now; } } public class MemberInfo { public CSteamID SteamId { get; set; } public string DisplayName { get; set; } = string.Empty; public bool IsOwner { get; set; } public bool IsLocalPlayer { get; set; } public DateTime JoinedAt { get; set; } public MemberInfo() { JoinedAt = DateTime.Now; } } public abstract class P2PMessage { public abstract string MessageType { get; } public CSteamID SenderId { get; set; } public DateTime Timestamp { get; set; } public abstract byte[] Serialize(); public abstract void Deserialize(byte[] data); protected P2PMessage() { Timestamp = DateTime.UtcNow; } protected string CreateJsonBase(string additionalData = "") { //IL_0002: Unknown result type (might be due to invalid IL or missing references) ulong num = SenderId.m_SteamID; if (num == 0) { num = 76561197960265728uL; } string text = $"\"SenderId\":{num},\"Timestamp\":\"{Timestamp:O}\""; return string.IsNullOrEmpty(additionalData) ? text : (text + "," + additionalData); } protected void ParseJsonBase(string json) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) try { string s = ExtractJsonValue(json, "SenderId"); if (ulong.TryParse(s, out var result)) { SenderId = new CSteamID(result); } string s2 = ExtractJsonValue(json, "Timestamp"); if (DateTime.TryParse(s2, out var result2)) { Timestamp = result2; } else { Timestamp = DateTime.UtcNow; } } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] P2PMessage.ParseJsonBase ERROR: " + ex.Message); Console.WriteLine("[SteamNetworkLib] P2PMessage.ParseJsonBase Stack Trace: " + ex.StackTrace); SenderId = new CSteamID(76561197960265728uL); Timestamp = DateTime.UtcNow; } } protected string ExtractJsonValue(string json, string key) { try { string text = "\"" + key + "\":"; int num = json.IndexOf(text); if (num == -1) { return string.Empty; } for (num += text.Length; num < json.Length && char.IsWhiteSpace(json[num]); num++) { } if (num >= json.Length) { return string.Empty; } if (json[num] == '"') { num++; int num2 = -1; bool flag = false; for (int i = num; i < json.Length; i++) { if (json[i] == '\\' && !flag) { flag = true; continue; } if (json[i] == '"' && !flag) { num2 = i; break; } flag = false; } if (num2 == -1) { return string.Empty; } string text2 = json.Substring(num, num2 - num); return text2.Replace("\\\"", "\"").Replace("\\\\", "\\").Replace("\\/", "/") .Replace("\\b", "\b") .Replace("\\f", "\f") .Replace("\\n", "\n") .Replace("\\r", "\r") .Replace("\\t", "\t"); } int j; for (j = num; j < json.Length && json[j] != ',' && json[j] != '}' && json[j] != ']' && !char.IsWhiteSpace(json[j]); j++) { } return json.Substring(num, j - num); } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] P2PMessage.ExtractJsonValue ERROR: " + ex.Message); Console.WriteLine("[SteamNetworkLib] P2PMessage.ExtractJsonValue Stack Trace: " + ex.StackTrace); return string.Empty; } } } public class StreamMessage : P2PMessage { public override string MessageType => "STREAM"; public string StreamType { get; set; } = "audio"; public string StreamId { get; set; } = string.Empty; public uint SequenceNumber { get; set; } public long CaptureTimestamp { get; set; } public int SampleRate { get; set; } = 44100; public int Channels { get; set; } = 1; public int BitsPerSample { get; set; } = 16; public int FrameSamples { get; set; } = 960; public string Codec { get; set; } = "none"; public int Quality { get; set; } = 75; public bool IsStreamStart { get; set; } public bool IsStreamEnd { get; set; } public EP2PSend RecommendedSendType { get; set; } = (EP2PSend)1; public byte[] StreamData { get; set; } = new byte[0]; public string Metadata { get; set; } = string.Empty; public uint? AckForSequence { get; set; } public bool IsRetransmit { get; set; } = false; public bool IsFecFrame { get; set; } = false; public string PayloadType { get; set; } = "audio"; public int FrameDurationMs { get; set; } = 20; public byte Priority { get; set; } = 128; public override byte[] Serialize() { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected I4, but got Unknown string s = "{" + CreateJsonBase(string.Format("\"StreamType\":\"{0}\",\"StreamId\":\"{1}\",\"SequenceNumber\":{2},\"CaptureTimestamp\":{3},\"SampleRate\":{4},\"Channels\":{5},\"BitsPerSample\":{6},\"FrameSamples\":{7},\"Codec\":\"{8}\",\"Quality\":{9},\"IsStreamStart\":{10},\"IsStreamEnd\":{11},\"RecommendedSendType\":{12},\"Metadata\":\"{13}\",\"AckForSequence\":{14},\"IsRetransmit\":{15},\"IsFecFrame\":{16},\"PayloadType\":\"{17}\",\"FrameDurationMs\":{18},\"Priority\":{19}", StreamType, StreamId, SequenceNumber, CaptureTimestamp, SampleRate, Channels, BitsPerSample, FrameSamples, Codec, Quality, IsStreamStart.ToString().ToLower(), IsStreamEnd.ToString().ToLower(), (int)RecommendedSendType, Metadata, AckForSequence?.ToString() ?? "null", IsRetransmit.ToString().ToLower(), IsFecFrame.ToString().ToLower(), PayloadType, FrameDurationMs, Priority)) + "}"; byte[] bytes = Encoding.UTF8.GetBytes(s); byte[] array = new byte[4 + bytes.Length + StreamData.Length]; int num = 0; BitConverter.GetBytes(bytes.Length).CopyTo(array, num); num += 4; bytes.CopyTo(array, num); num += bytes.Length; StreamData.CopyTo(array, num); return array; } public override void Deserialize(byte[] data) { if (data.Length < 4) { return; } int num = BitConverter.ToInt32(data, 0); if (num <= 0 || num > data.Length - 4) { return; } string @string = Encoding.UTF8.GetString(data, 4, num); ParseJsonBase(@string); StreamType = ExtractJsonValue(@string, "StreamType"); StreamId = ExtractJsonValue(@string, "StreamId"); if (uint.TryParse(ExtractJsonValue(@string, "SequenceNumber"), out var result)) { SequenceNumber = result; } if (long.TryParse(ExtractJsonValue(@string, "CaptureTimestamp"), out var result2)) { CaptureTimestamp = result2; } if (int.TryParse(ExtractJsonValue(@string, "SampleRate"), out var result3)) { SampleRate = result3; } if (int.TryParse(ExtractJsonValue(@string, "Channels"), out var result4)) { Channels = result4; } if (int.TryParse(ExtractJsonValue(@string, "BitsPerSample"), out var result5)) { BitsPerSample = result5; } if (int.TryParse(ExtractJsonValue(@string, "FrameSamples"), out var result6)) { FrameSamples = result6; } Codec = ExtractJsonValue(@string, "Codec"); if (int.TryParse(ExtractJsonValue(@string, "Quality"), out var result7)) { Quality = result7; } IsStreamStart = ExtractJsonValue(@string, "IsStreamStart").ToLower() == "true"; IsStreamEnd = ExtractJsonValue(@string, "IsStreamEnd").ToLower() == "true"; if (int.TryParse(ExtractJsonValue(@string, "RecommendedSendType"), out var result8)) { RecommendedSendType = (EP2PSend)result8; } Metadata = ExtractJsonValue(@string, "Metadata"); string text = ExtractJsonValue(@string, "AckForSequence"); if (!string.IsNullOrEmpty(text) && text != "null" && uint.TryParse(text, out var result9)) { AckForSequence = result9; } IsRetransmit = ExtractJsonValue(@string, "IsRetransmit").ToLower() == "true"; IsFecFrame = ExtractJsonValue(@string, "IsFecFrame").ToLower() == "true"; PayloadType = ExtractJsonValue(@string, "PayloadType"); if (int.TryParse(ExtractJsonValue(@string, "FrameDurationMs"), out var result10)) { FrameDurationMs = result10; } if (byte.TryParse(ExtractJsonValue(@string, "Priority"), out var result11)) { Priority = result11; } int num2 = data.Length - 4 - num; if (num2 > 0) { StreamData = new byte[num2]; for (int i = 0; i < num2; i++) { if (4 + num + i < data.Length) { StreamData[i] = data[4 + num + i]; } } bool flag = num2 > 4; for (int j = 0; j < Math.Min(4, num2) && flag; j++) { if (StreamData[j] != 0) { flag = false; } } if (flag && num2 > 4) { Console.WriteLine($"[StreamMessage] Warning: StreamData may be corrupted (all zeros) for frame {SequenceNumber}"); } } else { StreamData = new byte[0]; } } } public class TextMessage : P2PMessage { public override string MessageType => "TEXT"; public string Content { get; set; } = string.Empty; public override byte[] Serialize() { string text = Content.Replace("\"", "\\\""); string s = "{" + CreateJsonBase("\"Content\":\"" + text + "\"") + "}"; return Encoding.UTF8.GetBytes(s); } public override void Deserialize(byte[] data) { try { string @string = Encoding.UTF8.GetString(data); ParseJsonBase(@string); Content = ExtractJsonValue(@string, "Content"); } catch (Exception ex) { Console.WriteLine("[SteamNetworkLib] TextMessage.Deserialize ERROR: " + ex.Message); Console.WriteLine("[SteamNetworkLib] TextMessage.Deserialize Stack Trace: " + ex.StackTrace); throw; } } } } namespace SteamNetworkLib.Exceptions { public class LobbyException : SteamNetworkException { public EResult? SteamResult { get; } public CSteamID? LobbyId { get; } public LobbyException() { } public LobbyException(string message) : base(message) { } public LobbyException(string message, Exception inner) : base(message, inner) { } public LobbyException(string message, EResult steamResult) : base(message) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) SteamResult = steamResult; } public LobbyException(string message, CSteamID lobbyId) : base(message) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) LobbyId = lobbyId; } public LobbyException(string message, EResult steamResult, CSteamID lobbyId) : base(message) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) SteamResult = steamResult; LobbyId = lobbyId; } } public class P2PException : SteamNetworkException { public CSteamID? TargetId { get; } public EP2PSessionError? SessionError { get; } public int Channel { get; } public P2PException() { } public P2PException(string message) : base(message) { } public P2PException(string message, Exception inner) : base(message, inner) { } public P2PException(string message, CSteamID targetId) : base(message) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) TargetId = targetId; } public P2PException(string message, EP2PSessionError sessionError) : base(message) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) SessionError = sessionError; } public P2PException(string message, CSteamID targetId, int channel) : base(message) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) TargetId = targetId; Channel = channel; } public P2PException(string message, CSteamID targetId, EP2PSessionError sessionError) : base(message) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) TargetId = targetId; SessionError = sessionError; } } public class SteamNetworkException : Exception { public SteamNetworkException() { } public SteamNetworkException(string message) : base(message) { } public SteamNetworkException(string message, Exception inner) : base(message, inner) { } } } namespace SteamNetworkLib.Events { public class LobbyJoinedEventArgs : EventArgs { public LobbyInfo Lobby { get; } public EResult Result { get; } public LobbyJoinedEventArgs(LobbyInfo lobby, EResult result = 1) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) Lobby = lobby; Result = result; } } public class LobbyCreatedEventArgs : EventArgs { public LobbyInfo Lobby { get; } public EResult Result { get; } public LobbyCreatedEventArgs(LobbyInfo lobby, EResult result = 1) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) Lobby = lobby; Result = result; } } public class LobbyLeftEventArgs : EventArgs { public CSteamID LobbyId { get; } public string Reason { get; } public LobbyLeftEventArgs(CSteamID lobbyId, string reason = "") { //IL_0009: Unknown result type (might be due to in