Decompiled source of ReefCharacters v0.1.0

ReefCharacters.dll

Decompiled 21 hours ago
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<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();
			}
		}
	}
}