Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ReefCharacters v0.1.0
ReefCharacters.dll
Decompiled 21 hours agousing 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<ZPackage>("Reef_ProfileLoad", (Action<ZRpc, ZPackage>)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<bool> 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<int> BackupsToKeep; internal static ConfigEntry<bool> OneCharacterPerAccount; internal static ConfigEntry<float> 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<int>("Server", "Backups to keep", 25, new ConfigDescription("Dated copies kept per character in characters_local/backups/<file>/ — one is made before every overwrite (autosave, logout, death); the oldest is deleted beyond this number. Server-side.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); OneCharacterPerAccount = ((BaseUnityPlugin)this).Config.Bind<bool>("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<float>("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<float>(5f, 120f), Array.Empty<object>())); 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<ZPackage>("Reef_Profile", (Action<ZRpc, ZPackage>)OnProfile); } private static ZNetPeer PeerOf(ZRpc rpc) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return null; } return ((IEnumerable<ZNetPeer>)instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((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<string> list = Directory.GetFiles(text, "*.fch").OrderBy<string, string>((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<string> 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<int, byte[]> Parts = new SortedDictionary<int, byte[]>(); public int Total; public float Expires; } private readonly Dictionary<string, Entry> cache = new Dictionary<string, Entry>(); 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<byte>(); } 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<bool> 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<byte>()); 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<bool> steps) { foreach (bool step in steps) { if (!step) { yield return null; } } } internal static void RunSync(IEnumerable<bool> 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(); } } } }