using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ReefCharacters")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+f7579dd2dbea0d4a6b14e0ac5cb78496364dde66")] [assembly: AssemblyProduct("ReefCharacters")] [assembly: AssemblyTitle("ReefCharacters")] [assembly: AssemblyVersion("0.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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 ReefCharacters { internal static class Client { internal enum State { Idle, WaitingServer, Ready, Refused } internal const int ErrLocalProgress = 700002; internal const int ErrServerCopyCorrupt = 700003; internal const int ErrTimeout = 700004; internal static State state = State.Idle; private static float waitingSince; private static float lastPushTime = -1E+09f; private static readonly Transport.Reassembler reassembler = new Transport.Reassembler(); private static readonly FieldInfo connectionStatusField = AccessTools.Field(typeof(ZNet), "m_connectionStatus"); private static readonly FieldInfo worldDataField = AccessTools.Field(typeof(PlayerProfile), "m_worldData"); internal static void Reset() { state = State.Idle; } internal static void Register(ZNetPeer peer) { peer.m_rpc.Register("Reef_ProfileLoad", (Action)OnProfileLoad); } internal static void OnPeerInfo() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)ZNet.GetConnectionStatus() == 2) { if (state == State.Ready || state == State.Refused) { ReefCharactersPlugin.Log.LogInfo((object)$"connected, server copy already handled ({state})"); return; } state = State.WaitingServer; waitingSince = Time.realtimeSinceStartup; ReefCharactersPlugin.Log.LogInfo((object)"connected, waiting for the server copy of the character"); } } private static void OnProfileLoad(ZRpc rpc, ZPackage pkg) { try { byte[] array = reassembler.Receive(rpc, pkg); if (array != null) { Apply(array); } } catch (Exception ex) { ReefCharactersPlugin.Log.LogError((object)("Reef_ProfileLoad: " + ex)); Fail(700003); } } private static int WorldDataCount(PlayerProfile profile) { try { return (worldDataField?.GetValue(profile) as ICollection)?.Count ?? 0; } catch (Exception) { return 0; } } internal static bool IsFresh(PlayerProfile profile) { if (profile.m_firstSpawn) { return WorldDataCount(profile) == 0; } return false; } private static void Apply(byte[] bytes) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) if (state != State.WaitingServer && state != State.Idle) { ReefCharactersPlugin.Log.LogWarning((object)$"Reef_ProfileLoad ignored in state {state}"); return; } if (state == State.Idle) { ReefCharactersPlugin.Log.LogInfo((object)"server copy arrived before PeerInfo (ServerSync buffering), applying now"); } Game instance = Game.instance; PlayerProfile val = ((instance != null) ? instance.GetPlayerProfile() : null); if (val == null) { ReefCharactersPlugin.Log.LogWarning((object)"Reef_ProfileLoad: no local profile object"); return; } if (bytes.Length == 0) { if (IsFresh(val)) { state = State.Ready; ReefCharactersPlugin.Log.LogInfo((object)"no server copy, new character accepted"); } else { ReefCharactersPlugin.Log.LogInfo((object)"no server copy and the local character has progress: refused (D2)"); Fail(700002); } return; } string path = val.GetPath(); FileSource fileSource = val.m_fileSource; string text = "reef-probe_" + val.GetFilename(); string path2 = Transport.CharacterFolder + text + ".fch"; try { Directory.CreateDirectory(Transport.CharacterFolder); Transport.WriteProfileFile(path2, bytes, (FileSource)2); if (!new PlayerProfile(text, (FileSource)2).Load()) { ReefCharactersPlugin.Log.LogError((object)$"server copy refused by the vanilla loader ({bytes.Length} B), local file untouched"); Fail(700003); return; } if (FileSourceHelper.IsLocal(fileSource) && File.Exists(path) && !File.Exists(path + ".beforereef") && !IsFresh(val)) { File.Copy(path, path + ".beforereef"); ReefCharactersPlugin.Log.LogInfo((object)("kept a one-time copy of the local file as " + Path.GetFileName(path) + ".beforereef")); } Transport.WriteProfileFile(path + ".new", bytes, fileSource); FileHelpers.ReplaceOldFile(path, path + ".new", path + ".old", (CloudStorageFileGrouping)1, fileSource); if (!val.Load()) { if (FileSourceHelper.IsLocal(fileSource) && File.Exists(path + ".old")) { try { File.Copy(path + ".old", path, overwrite: true); } catch (Exception ex) { ReefCharactersPlugin.Log.LogWarning((object)("restore .old: " + ex.Message)); } } ReefCharactersPlugin.Log.LogError((object)("server copy written but Load() failed — previous local file restored from " + Path.GetFileName(path) + ".old")); Fail(700003); } else { state = State.Ready; ReefCharactersPlugin.Log.LogInfo((object)$"server copy loaded ({bytes.Length} B) for '{val.GetName()}'"); } } catch (Exception ex2) { ReefCharactersPlugin.Log.LogError((object)("applying the server copy: " + ex2)); Fail(700003); } finally { try { if (File.Exists(path2)) { File.Delete(path2); } } catch (Exception) { } } } private static void Fail(int code) { state = State.Refused; SetStatus(code); try { Game instance = Game.instance; if (instance != null) { instance.Logout(false, true); } } catch (Exception ex) { ReefCharactersPlugin.Log.LogWarning((object)("logout: " + ex.Message)); } SetStatus(code); } private static void SetStatus(int code) { try { connectionStatusField.SetValue(null, (object)(ConnectionStatus)code); } catch (Exception ex) { ReefCharactersPlugin.Log.LogWarning((object)("connection status: " + ex.Message)); } } internal static bool SavePrefix(PlayerProfile profile) { if (state != State.Refused) { return true; } ZNet instance = ZNet.instance; Game instance2 = Game.instance; if ((Object)(object)instance == (Object)null || instance.IsServer() || (Object)(object)instance2 == (Object)null) { return true; } if (profile != instance2.GetPlayerProfile()) { return true; } ReefCharactersPlugin.Log.LogInfo((object)"save skipped: this connection was refused, the local file stays as it was"); return false; } internal static bool UpdateRespawnPrefix() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer()) { return true; } if (state != State.WaitingServer) { return true; } if (Time.realtimeSinceStartup - waitingSince > ReefCharactersPlugin.ProfileTimeout.Value) { ReefCharactersPlugin.Log.LogError((object)$"the server did not send the character in {ReefCharactersPlugin.ProfileTimeout.Value:0} s"); Fail(700004); } return false; } internal static void OnSaved(PlayerProfile profile, bool saved) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Invalid comparison between Unknown and I4 if (!saved) { return; } ZNet instance = ZNet.instance; Game instance2 = Game.instance; if ((Object)(object)instance == (Object)null || instance.IsServer() || (Object)(object)instance2 == (Object)null || state != State.Ready || profile != instance2.GetPlayerProfile()) { return; } ZNetPeer serverPeer = instance.GetServerPeer(); if (serverPeer == null || !serverPeer.IsReady() || serverPeer.m_socket == null || !serverPeer.m_socket.IsConnected()) { return; } try { byte[] array = Transport.ReadProfileFile(profile.GetPath(), profile.m_fileSource); bool flag = instance2.IsShuttingDown() || (int)ZNet.GetConnectionStatus() != 2; IEnumerable steps = Transport.Send(serverPeer, "Reef_Profile", array); if (flag) { Transport.RunSync(steps, serverPeer.m_socket); } else { ((MonoBehaviour)instance).StartCoroutine(Transport.RunAsync(steps)); } lastPushTime = Time.realtimeSinceStartup; ReefCharactersPlugin.Log.LogInfo((object)($"pushed {array.Length} B of '{profile.GetName()}' to the server" + (flag ? " (synchronous)" : ""))); } catch (Exception ex) { ReefCharactersPlugin.Log.LogError((object)("push: " + ex)); } } internal static void BeforeSocketClose(ZSteamSocket socket) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer() || state == State.Idle || Time.realtimeSinceStartup - lastPushTime > 30f) { return; } try { Stopwatch stopwatch = Stopwatch.StartNew(); socket.Flush(); int sendQueueSize = socket.GetSendQueueSize(); while (sendQueueSize > 0 && stopwatch.ElapsedMilliseconds < 3000) { Thread.Sleep(20); socket.Flush(); sendQueueSize = socket.GetSendQueueSize(); } ReefCharactersPlugin.Log.LogInfo((object)$"socket close: waited {stopwatch.ElapsedMilliseconds} ms for the last push, {sendQueueSize} B still pending"); } catch (Exception ex) { ReefCharactersPlugin.Log.LogWarning((object)("socket close wait: " + ex.Message)); } finally { state = State.Idle; } } internal static string ErrorText(int code) { return code switch { 700001 => "This server keeps one character per Steam account. Log in with the character you already have here.", 700002 => "Your character already has progress but the server has no copy of it. Create a new character for this server (or ask the admin to import this one).", 700003 => "The server copy of your character could not be read. Tell the admin; nothing was changed on your side.", 700004 => "The server did not send your character in time. Try again.", _ => null, }; } } internal static class Patches { [HarmonyPatch(typeof(ZNet), "OnNewConnection", new Type[] { typeof(ZNetPeer) })] internal static class NetOnNewConnection { private static void Postfix(ZNet __instance, ZNetPeer peer) { try { if (peer != null && peer.m_rpc != null) { if (__instance.IsServer()) { Server.Register(peer); return; } Client.Reset(); Client.Register(peer); } } catch (Exception ex) { ReefCharactersPlugin.Log.LogError((object)("OnNewConnection: " + ex)); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo", new Type[] { typeof(ZRpc), typeof(ZPackage) })] internal static class NetPeerInfo { private static void Postfix(ZNet __instance, ZRpc rpc) { try { if (__instance.IsServer()) { Server.OnPeerInfo(__instance, rpc); } else { Client.OnPeerInfo(); } } catch (Exception ex) { ReefCharactersPlugin.Log.LogError((object)("RPC_PeerInfo: " + ex)); } } } [HarmonyPatch(typeof(Game), "UpdateRespawn", new Type[] { typeof(float) })] internal static class GameUpdateRespawn { private static bool Prefix() { try { return Client.UpdateRespawnPrefix(); } catch (Exception ex) { ReefCharactersPlugin.Log.LogError((object)("UpdateRespawn gate: " + ex)); return true; } } } [HarmonyPatch(typeof(PlayerProfile), "Save", new Type[] { })] internal static class ProfileSave { private static bool Prefix(PlayerProfile __instance, ref bool __result) { try { if (Client.SavePrefix(__instance)) { return true; } __result = false; return false; } catch (Exception ex) { ReefCharactersPlugin.Log.LogError((object)("Save prefix: " + ex)); return true; } } private static void Postfix(PlayerProfile __instance, bool __result) { try { Client.OnSaved(__instance, __result); } catch (Exception ex) { ReefCharactersPlugin.Log.LogError((object)("Save postfix: " + ex)); } } } [HarmonyPatch(typeof(ZSteamSocket), "Close", new Type[] { })] internal static class SteamSocketClose { private static void Prefix(ZSteamSocket __instance) { try { Client.BeforeSocketClose(__instance); } catch (Exception ex) { ReefCharactersPlugin.Log.LogWarning((object)("Close prefix: " + ex.Message)); } } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError", new Type[] { typeof(ConnectionStatus) })] internal static class ShowConnectError { private static void Postfix(FejdStartup __instance) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected I4, but got Unknown try { string text = Client.ErrorText((int)ZNet.GetConnectionStatus()); if (text != null && !((Object)(object)__instance.m_connectionFailedError == (Object)null)) { __instance.m_connectionFailedError.text = text; if ((Object)(object)__instance.m_connectionFailedPanel != (Object)null) { __instance.m_connectionFailedPanel.SetActive(true); } } } catch (Exception ex) { ReefCharactersPlugin.Log.LogWarning((object)("ShowConnectError: " + ex.Message)); } } } } [BepInPlugin("reef.characters", "Reef Characters", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInIncompatibility("org.bepinex.plugins.servercharacters")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class ReefCharactersPlugin : BaseUnityPlugin { public const string GUID = "reef.characters"; public const string NAME = "Reef Characters"; public const string VERSION = "0.1.0"; public const string BUILD = "reef"; internal static ManualLogSource Log; internal static ReefCharactersPlugin Instance; internal static ConfigEntry BackupsToKeep; internal static ConfigEntry OneCharacterPerAccount; internal static ConfigEntry ProfileTimeout; private Harmony harmony; private void Awake() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; BackupsToKeep = ((BaseUnityPlugin)this).Config.Bind("Server", "Backups to keep", 25, new ConfigDescription("Dated copies kept per character in characters_local/backups// — one is made before every overwrite (autosave, logout, death); the oldest is deleted beyond this number. Server-side.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); OneCharacterPerAccount = ((BaseUnityPlugin)this).Config.Bind("Server", "One character per account", false, "true = a Steam account that already has a character on this server cannot log in with another one (admins in adminlist.txt are exempt). Server-side. Off on The Reef (Julien 2026-09-11: several characters per player are fine; ServerCharacters ran Single Character Mode = Off too)."); ProfileTimeout = ((BaseUnityPlugin)this).Config.Bind("Client", "Profile timeout", 20f, new ConfigDescription("Seconds the client waits for the server to send (or deny) the stored character before giving up with an error. Client-side.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 120f), Array.Empty())); harmony = new Harmony("reef.characters"); harmony.PatchAll(typeof(Patches.NetOnNewConnection)); harmony.PatchAll(typeof(Patches.NetPeerInfo)); harmony.PatchAll(typeof(Patches.GameUpdateRespawn)); harmony.PatchAll(typeof(Patches.ProfileSave)); harmony.PatchAll(typeof(Patches.SteamSocketClose)); harmony.PatchAll(typeof(Patches.ShowConnectError)); Log.LogInfo((object)"Reef Characters 0.1.0 loaded (build: reef)"); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } internal static class Server { internal const int ErrSecondCharacter = 700001; private static readonly Transport.Reassembler reassembler = new Transport.Reassembler(); private static readonly object fileLock = new object(); internal static void Register(ZNetPeer peer) { peer.m_rpc.Register("Reef_Profile", (Action)OnProfile); } private static ZNetPeer PeerOf(ZRpc rpc) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return null; } return ((IEnumerable)instance.GetPeers()).FirstOrDefault((Func)((ZNetPeer p) => p != null && p.m_rpc == rpc)); } private static string HostOf(ZRpc rpc) { try { ISocket socket = rpc.GetSocket(); return ((socket != null) ? socket.GetHostName() : null) ?? ""; } catch (Exception) { return ""; } } private static string FileNameFor(ZNetPeer peer) { return Transport.ReefId(HostOf(peer.m_rpc)) + "_" + (peer.m_playerName ?? "").ToLower(); } private static string FinalPath(string fileName) { return Transport.CharacterFolder + fileName + ".fch"; } private static void OnProfile(ZRpc rpc, ZPackage pkg) { try { byte[] array = reassembler.Receive(rpc, pkg); if (array != null) { StoreProfile(rpc, array); } } catch (Exception ex) { ReefCharactersPlugin.Log.LogError((object)("Reef_Profile from " + Transport.ReefId(HostOf(rpc)) + ": " + ex)); } } private static void StoreProfile(ZRpc rpc, byte[] bytes) { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown string text = Transport.ReefId(HostOf(rpc)); ZNetPeer val = PeerOf(rpc); if (val == null || val.m_uid == 0L) { ReefCharactersPlugin.Log.LogWarning((object)("rejected profile from " + text + ": peer not logged in")); return; } if (bytes.Length == 0) { ReefCharactersPlugin.Log.LogWarning((object)("rejected profile from " + text + ": empty")); return; } if (string.IsNullOrEmpty(val.m_playerName)) { ReefCharactersPlugin.Log.LogWarning((object)("rejected profile from " + text + ": no player name yet")); return; } string text2 = FileNameFor(val); string text3 = "reef-incoming_" + text2; string characterFolder = Transport.CharacterFolder; string text4 = characterFolder + text3 + ".fch"; string text5 = FinalPath(text2); lock (fileLock) { try { Directory.CreateDirectory(characterFolder); Transport.WriteProfileFile(text4, bytes, (FileSource)2); PlayerProfile val2 = new PlayerProfile(text3, (FileSource)2); if (!val2.Load()) { ReefCharactersPlugin.Log.LogWarning((object)$"rejected profile from {text}: vanilla loader refused the bytes ({bytes.Length} B)"); return; } string text6 = (val2.GetName() ?? "").ToLower(); if (text6 != val.m_playerName.ToLower()) { ReefCharactersPlugin.Log.LogWarning((object)("rejected profile from " + text + ": name mismatch (profile '" + text6 + "', peer '" + val.m_playerName.ToLower() + "')")); } else { if (File.Exists(text5)) { Backup(text2, text5); File.Delete(text5); } File.Move(text4, text5); ReefCharactersPlugin.Log.LogInfo((object)$"saved {text2}.fch ({bytes.Length} B) from {text}"); } } catch (Exception ex) { ReefCharactersPlugin.Log.LogError((object)("rejected profile from " + text + ": " + ex.GetType().Name + ": " + ex.Message)); } finally { try { if (File.Exists(text4)) { File.Delete(text4); } } catch (Exception) { } } } } private static void Backup(string fileName, string finalPath) { string text = Path.Combine(Transport.CharacterFolder, "backups", fileName); Directory.CreateDirectory(text); string text2 = DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss"); string text3 = Path.Combine(text, text2 + ".fch"); int num = 2; while (File.Exists(text3)) { text3 = Path.Combine(text, text2 + "-" + num++ + ".fch"); } File.Copy(finalPath, text3); int num2 = Math.Max(1, ReefCharactersPlugin.BackupsToKeep.Value); List list = Directory.GetFiles(text, "*.fch").OrderBy((string f) => f, StringComparer.Ordinal).ToList(); while (list.Count > num2) { try { File.Delete(list[0]); } catch (Exception ex) { ReefCharactersPlugin.Log.LogWarning((object)("backup prune: " + ex.Message)); } list.RemoveAt(0); } ReefCharactersPlugin.Log.LogInfo((object)$"backup {fileName}/{Path.GetFileName(text3)} ({list.Count}/{num2})"); } internal static void OnPeerInfo(ZNet net, ZRpc rpc) { ZNetPeer val = PeerOf(rpc); if (val == null || val.m_uid == 0L) { return; } string text = HostOf(rpc); string text2 = Transport.ReefId(text); string text3 = (val.m_playerName ?? "").ToLower(); string text4 = text2 + "_" + text3; string path = FinalPath(text4); try { if (!File.Exists(path)) { if (ReefCharactersPlugin.OneCharacterPerAccount.Value && !net.IsAdmin(text)) { List list = (Directory.Exists(Transport.CharacterFolder) ? (from n in Directory.GetFiles(Transport.CharacterFolder, text2 + "_*.fch").Select(Path.GetFileNameWithoutExtension) where !n.Contains("_backup_") select n).ToList() : null); if (list != null && list.Count > 0) { rpc.Invoke("Error", new object[1] { 700001 }); ReefCharactersPlugin.Log.LogInfo((object)("refused " + text2 + ": second character '" + text3 + "' (has " + string.Join(", ", list) + ")")); net.Disconnect(val); return; } } ((MonoBehaviour)net).StartCoroutine(Transport.RunAsync(Transport.Send(val, "Reef_ProfileLoad", null))); ReefCharactersPlugin.Log.LogInfo((object)("no profile for " + text4 + ", new character accepted")); } else { byte[] array = Transport.ReadProfileFile(path, (FileSource)2); ((MonoBehaviour)net).StartCoroutine(Transport.RunAsync(Transport.Send(val, "Reef_ProfileLoad", array))); ReefCharactersPlugin.Log.LogInfo((object)$"sent {text4}.fch ({array.Length} B) to {text2}"); } } catch (Exception arg) { ReefCharactersPlugin.Log.LogError((object)$"join of {text4}: {arg}"); } } } internal static class Transport { internal sealed class Reassembler { private sealed class Entry { public readonly SortedDictionary Parts = new SortedDictionary(); public int Total; public float Expires; } private readonly Dictionary cache = new Dictionary(); public byte[] Receive(ZRpc sender, ZPackage pkg) { float now = Time.realtimeSinceStartup; foreach (string item in (from kv in cache where kv.Value.Expires < now select kv.Key).ToList()) { cache.Remove(item); } long num = pkg.ReadLong(); int key = pkg.ReadInt(); int num2 = pkg.ReadInt(); byte[] value = pkg.ReadByteArray(); if (num2 <= 0) { return Array.Empty(); } string key2 = ((object)sender).GetHashCode() + ":" + num; if (!cache.TryGetValue(key2, out var value2)) { value2 = new Entry { Total = num2, Expires = now + 60f }; cache[key2] = value2; } value2.Parts[key] = value; if (value2.Parts.Count < value2.Total) { return null; } cache.Remove(key2); return Inflate(value2.Parts.Values.SelectMany((byte[] a) => a).ToArray()); } } internal const string RpcProfile = "Reef_Profile"; internal const string RpcProfileLoad = "Reef_ProfileLoad"; internal const int ChunkSize = 250000; internal const int SendQueueLimit = 20000; internal const float SendTimeoutSeconds = 30f; internal const float CacheExpirySeconds = 60f; private static long packageCounter = DateTime.UtcNow.Ticks; private static readonly Regex Digits = new Regex("^\\d+$", RegexOptions.Compiled); internal static string CharacterFolder => SaveSystem.GetCharacterFolderPath((FileSource)2); internal static string ReefId(string hostName) { if (string.IsNullOrEmpty(hostName)) { return "Unknown"; } if (!Digits.IsMatch(hostName)) { return hostName; } return "Steam_" + hostName; } internal static byte[] Deflate(byte[] data) { using MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true)) { deflateStream.Write(data, 0, data.Length); } return memoryStream.ToArray(); } internal static byte[] Inflate(byte[] data) { using MemoryStream stream = new MemoryStream(data); using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); deflateStream.CopyTo(memoryStream); return memoryStream.ToArray(); } internal static IEnumerable Send(ZNetPeer peer, string rpcName, byte[] payload) { long id = ++packageCounter; if (payload == null || payload.Length == 0) { ZPackage val = new ZPackage(); val.Write(id); val.Write(0); val.Write(0); val.Write(Array.Empty()); peer.m_rpc.Invoke(rpcName, new object[1] { val }); yield return true; yield break; } byte[] data = Deflate(payload); int fragments = (int)(1 + (data.LongLength - 1) / 250000); float deadline = Time.realtimeSinceStartup + 30f; for (int fragment = 0; fragment < fragments; fragment++) { while (peer.m_socket != null && peer.m_socket.IsConnected() && peer.m_socket.GetSendQueueSize() > 20000) { if (Time.realtimeSinceStartup > deadline) { ReefCharactersPlugin.Log.LogWarning((object)$"{rpcName}: send queue never drained in {30f:0} s, giving up after {fragment}/{fragments} fragments"); yield break; } yield return false; } if (peer.m_socket == null || !peer.m_socket.IsConnected()) { ReefCharactersPlugin.Log.LogWarning((object)$"{rpcName}: peer disconnected after {fragment}/{fragments} fragments"); break; } ZPackage val2 = new ZPackage(); val2.Write(id); val2.Write(fragment); val2.Write(fragments); val2.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); peer.m_rpc.Invoke(rpcName, new object[1] { val2 }); yield return true; } } internal static IEnumerator RunAsync(IEnumerable steps) { foreach (bool step in steps) { if (!step) { yield return null; } } } internal static void RunSync(IEnumerable steps, ISocket socket) { foreach (bool step in steps) { if (step) { continue; } try { if (socket != null) { socket.Flush(); } } catch (Exception) { } Thread.Sleep(10); } try { if (socket != null) { socket.Flush(); } } catch (Exception) { } } internal static void WriteProfileFile(string path, byte[] bytes, FileSource source) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) byte[] array = new ZPackage(bytes).GenerateHash(); FileWriter val = new FileWriter(path, (CloudStorageFileGrouping)1, (FileHelperType)0, source); val.m_binary.Write(bytes.Length); val.m_binary.Write(bytes); val.m_binary.Write(array.Length); val.m_binary.Write(array); val.Finish(); if ((int)val.Status != 2) { throw new IOException("FileWriter status " + ((object)val.Status/*cast due to .constrained prefix*/).ToString() + " for " + path); } } internal static byte[] ReadProfileFile(string path, FileSource source) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Expected O, but got Unknown FileReader val = new FileReader(path, source, (FileHelperType)0); try { int num = val.m_binary.ReadInt32(); if (num <= 0 || num > 67108864) { throw new IOException("implausible profile length " + num); } return val.m_binary.ReadBytes(num); } finally { val.Dispose(); } } } }