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 Quads Map Share v1.1.5
plugins/MapShare.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; 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 BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using DHack.Shared; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI (Claude Code).")] [assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.0.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 MapShare { [BepInPlugin("com.quad.mapshare", "MapShare", "1.1.5")] public class Plugin : BaseUnityPlugin { private class Assembly_ { public int Total; public byte[][] Parts; public int Have; public float Started; } private class Answer { public int Rank; public float Due; public byte[] Seen; } public const string Guid = "com.quad.mapshare"; public const string OldGuid = "com.dhack.mapshare"; public const string Name = "MapShare"; public const string Version = "1.1.5"; private const string RpcCells = "DHack_MapCells"; private const string RpcHello = "DHack_MapHello"; private const string RpcFull = "DHack_MapFull"; internal static Plugin Instance; internal static readonly List<int> Pending = new List<int>(); internal static ServerSettings Synced; private ConfigEntry<bool> _send; private ConfigEntry<bool> _receive; private ConfigEntry<bool> _allowed; private ConfigEntry<float> _interval; private Harmony _harmony; private static readonly FieldInfo Explored = AccessTools.Field(typeof(Minimap), "m_explored"); private static readonly FieldInfo ExploredOthers = AccessTools.Field(typeof(Minimap), "m_exploredOthers"); private static readonly FieldInfo Fog = AccessTools.Field(typeof(Minimap), "m_fogTexture"); private static readonly Func<Minimap, int, int, bool> ExploreOthers = (Func<Minimap, int, int, bool>)Delegate.CreateDelegate(typeof(Func<Minimap, int, int, bool>), AccessTools.Method(typeof(Minimap), "ExploreOthers", new Type[2] { typeof(int), typeof(int) }, (Type[])null)); private int _awakeFrame; private ZRoutedRpc _registeredOn; private bool _helloPending; private float _nextSend; private float _helloAt; private float _helloSentAt = -1000f; private readonly Dictionary<long, Assembly_> _incoming = new Dictionary<long, Assembly_>(); private readonly Dictionary<long, Answer> _answers = new Dictionary<long, Answer>(); private const int MaxParts = 64; private const int MaxPiece = 65536; private const int MaxSets = 16; private const float SetTimeout = 60f; private const float PieceGap = 0.25f; private float _nextPiece; private bool Sending { get { if (_send.Value) { return _allowed.Value; } return false; } } private bool Receiving { get { if (_receive.Value) { return _allowed.Value; } return false; } } private void Awake() { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown Migration.FromOldGuid((BaseUnityPlugin)(object)this, "com.dhack.mapshare"); Translations.Load((BaseUnityPlugin)(object)this); Instance = this; _send = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShareMyMap", true, "Send the parts of the map you uncover to the other players in the world."); _receive = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ReceiveSharedMap", true, "Show the parts of the map other players uncover on your own map."); _interval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SendInterval", 2f, "Seconds between sending newly uncovered map cells."); Synced = new ServerSettings("com.quad.mapshare", ((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); _allowed = Synced.Add<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("General", "AllowSharing", true, "Players with this mod share the map they uncover with each other. A server that wants everyone to explore for themselves turns this off. In multiplayer the server's value applies.")); _awakeFrame = Time.frameCount; _harmony = new Harmony("com.quad.mapshare"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"MapShare 1.1.5 loaded"); if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString("[Mod]", Translations.T("mapshare_chat_reloaded", "MapShare", "1.1.5"), (Type)1, false); } } private void OnDestroy() { Translations.Unload(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } UnregisterRpc(); Pending.Clear(); _incoming.Clear(); _answers.Clear(); Synced?.Dispose(); if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private void Update() { Synced?.Update(); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { _registeredOn = null; } else { if (Time.frameCount <= _awakeFrame + 2) { return; } if (instance != _registeredOn) { _registeredOn = instance; UnregisterRpc(); instance.Register<ZPackage>("DHack_MapCells", (Action<long, ZPackage>)OnCells); instance.Register("DHack_MapHello", (Action<long>)OnHello); instance.Register<ZPackage>("DHack_MapFull", (Action<long, ZPackage>)OnFull); _helloPending = true; _helloAt = Time.unscaledTime + 4f; Pending.Clear(); _answers.Clear(); } if ((Object)(object)Minimap.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } if (_helloPending && Time.unscaledTime >= _helloAt) { _helloPending = false; if (Receiving) { Send("DHack_MapHello", null, 0L); _helloSentAt = Time.unscaledTime; } ((MonoBehaviour)this).StartCoroutine(SendOwnMapLater()); } if (_answers.Count > 0) { AnswerDue(); } if (Sending && Pending.Count > 0 && Time.unscaledTime >= _nextSend) { _nextSend = Time.unscaledTime + Mathf.Max(0.5f, _interval.Value); FlushPending(); } else if (!Sending) { Pending.Clear(); } } } private static void UnregisterRpc() { if (ZRoutedRpc.instance != null && AccessTools.Field(typeof(ZRoutedRpc), "m_functions").GetValue(ZRoutedRpc.instance) is IDictionary dictionary) { string[] array = new string[3] { "DHack_MapCells", "DHack_MapHello", "DHack_MapFull" }; foreach (string text in array) { dictionary.Remove(StringExtensionMethods.GetStableHashCode(text)); } } } private void Send(string rpc, ZPackage package, long target = 0L) { if (ZRoutedRpc.instance == null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } try { if (package == null) { ZRoutedRpc.instance.InvokeRoutedRPC(target, rpc, Array.Empty<object>()); return; } ZRoutedRpc.instance.InvokeRoutedRPC(target, rpc, new object[1] { package }); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not share the map: " + ex.Message)); } } private void FlushPending() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown while (Pending.Count > 0) { int num = Mathf.Min(Pending.Count, 4000); ZPackage val = new ZPackage(); val.Write(1); val.Write(Minimap.instance.m_textureSize); val.Write(num); for (int i = 0; i < num; i++) { val.Write(Pending[i]); } Pending.RemoveRange(0, num); Send("DHack_MapCells", val, 0L); } } private void OnCells(long sender, ZPackage package) { if (!Receiving || (Object)(object)Minimap.instance == (Object)null || sender == ZDOMan.GetSessionID()) { return; } try { package.ReadInt(); int num = package.ReadInt(); int num2 = package.ReadInt(); if (num != Minimap.instance.m_textureSize || num2 < 0 || num2 > 200000) { return; } bool flag = false; for (int i = 0; i < num2; i++) { int num3 = package.ReadInt(); if (num3 >= 0 && num3 < num * num && ExploreOthers(Minimap.instance, num3 % num, num3 / num)) { flag = true; } } if (flag) { Finish(); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Bad map update: " + ex.Message)); } } private static void Finish() { object? value = Fog.GetValue(Minimap.instance); Texture2D val = (Texture2D)((value is Texture2D) ? value : null); if (val != null) { val.Apply(); } if ((Object)(object)Minimap.instance.m_sharedMapHint != (Object)null) { Minimap.instance.m_sharedMapHint.SetActive(false); } } private void OnHello(long sender) { if (Sending && sender != ZDOMan.GetSessionID() && !((Object)(object)Minimap.instance == (Object)null) && !_answers.ContainsKey(sender) && _answers.Count < 16) { int num = Rank(sender); if (num == 0) { ((MonoBehaviour)this).StartCoroutine(SendFullMap(sender)); return; } _answers[sender] = new Answer { Rank = num, Due = Time.unscaledTime + 20f + (float)num * 4f }; } } private static int Rank(long joiner) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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) long sessionID = ZDOMan.GetSessionID(); int num = 0; if ((Object)(object)ZNet.instance == (Object)null) { return num; } foreach (PlayerInfo player in ZNet.instance.GetPlayerList()) { ZDOID characterID = player.m_characterID; long userID = ((ZDOID)(ref characterID)).UserID; if (userID != 0L && userID != sessionID && userID != joiner && userID < sessionID) { num++; } } return num; } private void AnswerDue() { foreach (KeyValuePair<long, Answer> item in _answers.Where((KeyValuePair<long, Answer> kv) => Time.unscaledTime >= kv.Value.Due).ToList()) { _answers.Remove(item.Key); if (Sending && !((Object)(object)Minimap.instance == (Object)null)) { if (item.Value.Seen == null) { ((MonoBehaviour)this).StartCoroutine(SendFullMap(item.Key)); } else { ((MonoBehaviour)this).StartCoroutine(SendFullMap(0L, item.Value.Seen)); } } } } private void Heard(long sender, byte[] bits) { bool flag = Time.unscaledTime - _helloSentAt < 120f; foreach (KeyValuePair<long, Answer> answer in _answers) { Answer value = answer.Value; if (answer.Key == sender && value.Seen == null) { value.Seen = (byte[])bits.Clone(); value.Due = Time.unscaledTime + (float)(value.Rank - 1) * 4f; } else if (value.Seen != null && (answer.Key == sender || !flag)) { for (int i = 0; i < value.Seen.Length && i < bits.Length; i++) { value.Seen[i] |= bits[i]; } } } } private IEnumerator SendOwnMapLater() { yield return (object)new WaitForSeconds(12f); if (Sending && (Object)(object)Minimap.instance != (Object)null) { yield return SendFullMap(0L); } } private IEnumerator SendFullMap(long target, byte[] minus = null) { Minimap map = Minimap.instance; BitArray mine = (BitArray)Explored.GetValue(map); BitArray others = (BitArray)ExploredOthers.GetValue(map); int n = mine.Length; byte[] bits = new byte[(n + 7) / 8]; if (minus != null && minus.Length < bits.Length) { yield break; } bool any = false; for (int start = 0; start < n; start += 250000) { int num = Mathf.Min(n, start + 250000); for (int i = start; i < num; i++) { if ((mine[i] || others[i]) && (minus == null || (minus[i >> 3] & (1 << (i & 7))) == 0)) { bits[i >> 3] |= (byte)(1 << (i & 7)); any = true; } } yield return null; } if (minus != null && !any) { yield break; } byte[] packed = Utils.Compress(bits); int parts = (packed.Length + 30000 - 1) / 30000; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Sending " + ((minus != null) ? "what a newcomer lacked" : "the whole map") + " to " + ((target == 0L) ? "everyone" : "one player") + ": " + $"{packed.Length / 1024} KB in {parts} piece(s)")); int id = Random.Range(1, int.MaxValue); for (int start = 0; start < parts; start++) { int num2 = Mathf.Min(30000, packed.Length - start * 30000); byte[] array = new byte[num2]; Buffer.BlockCopy(packed, start * 30000, array, 0, num2); ZPackage package = new ZPackage(); package.Write(id); package.Write(start); package.Write(parts); package.Write(map.m_textureSize); package.Write(array); while (Time.unscaledTime < _nextPiece) { yield return null; } _nextPiece = Time.unscaledTime + 0.25f; Send("DHack_MapFull", package, target); } } private void OnFull(long sender, ZPackage package) { if ((!Receiving && _answers.Count == 0) || (Object)(object)Minimap.instance == (Object)null || sender == ZDOMan.GetSessionID()) { return; } try { int num = package.ReadInt(); int num2 = package.ReadInt(); int num3 = package.ReadInt(); int num4 = package.ReadInt(); byte[] array = package.ReadByteArray(); if (num4 != Minimap.instance.m_textureSize || num3 <= 0 || num3 > 64 || num2 < 0 || num2 >= num3 || array == null || array.Length > 65536) { return; } foreach (long item in (from kv in _incoming where Time.unscaledTime - kv.Value.Started > 60f select kv.Key).ToList()) { _incoming.Remove(item); } long key = sender * 31 + num; if (!_incoming.TryGetValue(key, out var value)) { if (_incoming.Count >= 16) { return; } value = (_incoming[key] = new Assembly_ { Total = num3, Parts = new byte[num3][], Started = Time.unscaledTime }); } if (value.Total != num3) { return; } if (value.Parts[num2] == null) { value.Parts[num2] = array; value.Have++; } if (value.Have < value.Total) { return; } _incoming.Remove(key); byte[] packed = value.Parts.SelectMany((byte[] p) => p).ToArray(); byte[] array2 = Unpack(packed, (num4 * num4 + 7) / 8); if (array2 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"A shared map was not the size of this world's map: ignored"); return; } Heard(sender, array2); if (Receiving) { ((MonoBehaviour)this).StartCoroutine(Merge(array2)); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Bad map piece: " + ex.Message)); } } private static byte[] Unpack(byte[] packed, int expected) { using MemoryStream stream = new MemoryStream(packed); using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress); byte[] array = new byte[expected]; int i; int num; for (i = 0; i < expected; i += num) { if ((num = gZipStream.Read(array, i, expected - i)) <= 0) { break; } } if (i < expected || gZipStream.ReadByte() >= 0) { return null; } return array; } private IEnumerator Merge(byte[] bits) { Minimap map = Minimap.instance; BitArray mine = (BitArray)Explored.GetValue(map); BitArray others = (BitArray)ExploredOthers.GetValue(map); int size = map.m_textureSize; int n = size * size; int added = 0; if (bits.Length * 8 < n) { yield break; } for (int start = 0; start < n; start += 150000) { int num = Mathf.Min(n, start + 150000); for (int i = start; i < num; i++) { if ((bits[i >> 3] & (1 << (i & 7))) != 0 && !mine[i] && !others[i] && ExploreOthers(map, i % size, i / size)) { added++; } } yield return null; } if (added > 0) { Finish(); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Added {added} map cells shared by other players"); } } } [HarmonyPatch(typeof(Minimap), "Explore", new Type[] { typeof(int), typeof(int) })] internal static class Minimap_Explore { private static void Postfix(int x, int y, bool __result) { if (__result && !((Object)(object)Plugin.Instance == (Object)null) && !((Object)(object)Minimap.instance == (Object)null) && Plugin.Pending.Count < 200000) { Plugin.Pending.Add(y * Minimap.instance.m_textureSize + x); } } } } namespace DHack.Shared { internal static class Migration { private sealed class Notice : MonoBehaviour { public string Text; private float _next; private void Update() { if (!(Time.unscaledTime < _next) && !((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)MessageHud.instance == (Object)null)) { ((Character)Player.m_localPlayer).Message((MessageType)2, Text, 0, (Sprite)null, false); _next = Time.unscaledTime + 60f; } } } public static void FromOldGuid(BaseUnityPlugin plugin, string oldGuid) { BepInPlugin metadata = MetadataHelper.GetMetadata((object)plugin); string text = ((metadata != null) ? metadata.GUID : null); if (string.IsNullOrEmpty(oldGuid) || string.IsNullOrEmpty(text) || oldGuid == text) { return; } ManualLogSource val = Logger.CreateLogSource(text); try { bool flag = false; if (Directory.Exists(Paths.ConfigPath)) { string[] files = Directory.GetFiles(Paths.ConfigPath, oldGuid + ".*"); foreach (string text2 in files) { string fileName = Path.GetFileName(text2); if (!fileName.EndsWith(".cfg") && !fileName.EndsWith(".json") && !fileName.EndsWith(".txt")) { continue; } string text3 = Path.Combine(Paths.ConfigPath, text + fileName.Substring(oldGuid.Length)); if (!File.Exists(text3)) { File.Copy(text2, text3); val.LogInfo((object)("Settings moved over from " + fileName + " (the mod's id is now " + text + "; the old file is kept as a backup)")); if (fileName == oldGuid + ".cfg") { flag = true; } } } } if (flag) { plugin.Config.Reload(); } } catch (Exception ex) { val.LogWarning((object)("Couldn't move the settings over from " + oldGuid + ".cfg: " + ex.Message)); } finally { Logger.Sources.Remove((ILogSource)(object)val); } ((MonoBehaviour)plugin).StartCoroutine(StandDownIfTwin(plugin, oldGuid)); } private static IEnumerator StandDownIfTwin(BaseUnityPlugin plugin, string oldGuid) { yield return null; yield return null; if (!((Object)(object)plugin == (Object)null) && Chainloader.PluginInfos.TryGetValue(oldGuid, out var value) && !((Object)(object)value.Instance == (Object)null) && !((Object)(object)value.Instance == (Object)(object)plugin)) { BepInPlugin metadata = MetadataHelper.GetMetadata((object)plugin); string text = ((metadata != null) ? metadata.Name : null) ?? oldGuid; string text2 = (string.IsNullOrEmpty(value.Location) ? "an older copy" : value.Location); string text3 = text + ": two copies are installed, an old one (" + text2 + ") and this one. Both would run and do everything twice, so this one stands down. Delete the old copy, then restart the game."; Debug.LogError((object)("[" + text + "] " + text3)); Object.Destroy((Object)(object)plugin); GameObject val = new GameObject("Migration_TwoCopies_" + text); Object.DontDestroyOnLoad((Object)(object)val); val.AddComponent<Notice>().Text = text3; } } } internal static class Translations { private sealed class Blank { public string Tag; public Func<string> Value; public Action Unhook; } public static readonly string[] Shipped = new string[9] { "English", "Russian", "German", "Chinese", "Portuguese_Brazilian", "Spanish", "French", "Polish", "Turkish" }; private static readonly Dictionary<string, Dictionary<string, string>> Languages = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); private static Dictionary<string, string> _english = new Dictionary<string, string>(); private static string _overrideDir; private static bool _hooked; private static readonly MethodInfo AddWord = AccessTools.Method(typeof(Localization), "AddWord", (Type[])null, (Type[])null); private static readonly FieldInfo Cache = AccessTools.Field(typeof(Localization), "m_cache"); private static readonly FieldInfo GameWords = AccessTools.Field(typeof(Localization), "m_translations"); private static Dictionary<string, string> _given = new Dictionary<string, string>(); private static Dictionary<string, string> _files = new Dictionary<string, string>(); private static readonly Dictionary<string, Dictionary<string, Dictionary<string, string>>> Runtime = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>(); private static readonly HashSet<string> _runtimeGiven = new HashSet<string>(); private static readonly Dictionary<string, List<Blank>> Fills = new Dictionary<string, List<Blank>>(); public static void Load(BaseUnityPlugin plugin) { Languages.Clear(); Assembly assembly = ((object)plugin).GetType().Assembly; foreach (string item in from n in assembly.GetManifestResourceNames() where n.StartsWith("translations/") && n.EndsWith(".json") select n) { using StreamReader streamReader = new StreamReader(assembly.GetManifestResourceStream(item)); Languages[Path.GetFileNameWithoutExtension(item.Substring("translations/".Length))] = Parse(streamReader.ReadToEnd()); } _english = (Languages.TryGetValue("English", out var value) ? value : new Dictionary<string, string>()); _overrideDir = Path.Combine(Paths.ConfigPath, "translations", plugin.Info.Metadata.GUID); if (!_hooked) { Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(Apply)); _hooked = true; } Apply(); } public static void Unload() { if (_hooked) { Localization.OnLanguageChange = (Action)Delegate.Remove(Localization.OnLanguageChange, new Action(Apply)); _hooked = false; } foreach (List<Blank> value2 in Fills.Values) { foreach (Blank item in value2) { item.Unhook?.Invoke(); } } Fills.Clear(); Runtime.Clear(); Localization val = ((_runtimeGiven.Count > 0) ? Localization.instance : null); if (val != null && AddWord != null) { Dictionary<string, string> dictionary = GameWords?.GetValue(val) as Dictionary<string, string>; foreach (string item2 in _runtimeGiven) { if (_files.TryGetValue(item2, out var value)) { AddWord.Invoke(val, new object[2] { item2, value }); } else { dictionary?.Remove(item2); } } EvictCache(val); } _runtimeGiven.Clear(); } public static string T(string key, params object[] args) { string text = ((Localization.instance != null) ? Localization.instance.Localize("$" + key) : null); if (string.IsNullOrEmpty(text) || text == "[" + key + "]") { text = (_english.TryGetValue(key, out var value) ? FillIn(key, value) : key); } if (args == null || args.Length == 0) { return text; } try { return string.Format(text, args); } catch (FormatException) { string value2; return _english.TryGetValue(key, out value2) ? SafeFormat(FillIn(key, value2), args) : text; } } private static string SafeFormat(string text, object[] args) { try { return string.Format(text, args); } catch (FormatException) { return text; } } public static void Fill<TValue>(string key, string placeholder, ConfigEntry<TValue> setting, Func<TValue, string> format = null) { if (setting != null) { Fill(key, placeholder, () => (format == null) ? Text(setting.Value) : format(setting.Value), (ConfigEntryBase)setting); } } public static void Fill(string key, string placeholder, Func<string> value, params ConfigEntryBase[] settings) { if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(placeholder) || value == null) { return; } key = key.TrimStart(new char[1] { '$' }); string tag = (placeholder.StartsWith("{") ? placeholder : ("{" + placeholder + "}")); if (!Fills.TryGetValue(key, out var value2)) { value2 = (Fills[key] = new List<Blank>()); } int num = value2.FindIndex((Blank b) => b.Tag == tag); if (num >= 0) { value2[num].Unhook?.Invoke(); value2.RemoveAt(num); } Blank blank = new Blank { Tag = tag, Value = value }; if (settings != null) { EventHandler changed = delegate { Show(key); }; foreach (ConfigEntryBase setting in settings.Where((ConfigEntryBase s) => s != null).Distinct()) { EventInfo ev = ((object)setting).GetType().GetEvent("SettingChanged"); if (!(ev == null)) { ev.AddEventHandler(setting, changed); blank.Unhook = (Action)Delegate.Combine(blank.Unhook, (Action)delegate { ev.RemoveEventHandler(setting, changed); }); } } } value2.Add(blank); Show(key); } public static void Refresh() { Localization instance = Localization.instance; if (instance == null || AddWord == null) { return; } foreach (string key in Fills.Keys) { if (_given.TryGetValue(key, out var value)) { AddWord.Invoke(instance, new object[2] { key, FillIn(key, value) }); } } EvictCache(instance); } private static void Show(string key) { Localization instance = Localization.instance; if (instance != null && !(AddWord == null) && _given.TryGetValue(key, out var value)) { AddWord.Invoke(instance, new object[2] { key, FillIn(key, value) }); EvictCache(instance); } } private static string FillIn(string key, string text) { if (text == null || !Fills.TryGetValue(key, out var value)) { return text; } foreach (Blank item in value) { if (text.IndexOf(item.Tag, StringComparison.Ordinal) >= 0) { try { text = text.Replace(item.Tag, item.Value() ?? ""); } catch (Exception ex) { Debug.LogWarning((object)("Translations: filling " + item.Tag + " in " + key + ": " + ex.Message)); } } } return text; } private static string Text(object value) { object obj; if (!(value is IFormattable formattable)) { obj = value?.ToString(); if (obj == null) { return ""; } } else { obj = formattable.ToString(null, CultureInfo.InvariantCulture); } return (string)obj; } public static void AddWords(string group, string language, IDictionary<string, string> words) { if (!string.IsNullOrEmpty(group) && !string.IsNullOrEmpty(language)) { SetWords(group, language, words); Apply(); } } public static void AddWords(string group, IDictionary<string, Dictionary<string, string>> languages) { if (string.IsNullOrEmpty(group)) { return; } Runtime.Remove(group); if (languages != null) { foreach (KeyValuePair<string, Dictionary<string, string>> language in languages) { if (!string.IsNullOrEmpty(language.Key)) { SetWords(group, language.Key, language.Value); } } } Apply(); } public static void RemoveWords(string group) { if (group != null && Runtime.Remove(group)) { Apply(); } } public static Dictionary<string, string> ParseJson(string json) { if (!string.IsNullOrEmpty(json)) { return Parse(json); } return new Dictionary<string, string>(); } private static void SetWords(string group, string language, IDictionary<string, string> words) { if (!Runtime.TryGetValue(group, out var value)) { value = (Runtime[group] = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase)); } if (words == null || words.Count == 0) { value.Remove(language); return; } Dictionary<string, string> dictionary2 = new Dictionary<string, string>(); foreach (KeyValuePair<string, string> word in words) { if (!string.IsNullOrEmpty(word.Key) && word.Value != null) { dictionary2[word.Key.TrimStart(new char[1] { '$' })] = word.Value; } } value[language] = dictionary2; } private static void AddRuntime(Dictionary<string, string> words, string language) { foreach (Dictionary<string, Dictionary<string, string>> value2 in Runtime.Values) { if (!value2.TryGetValue(language, out var value)) { continue; } foreach (KeyValuePair<string, string> item in value) { words[item.Key] = item.Value; } } } private static void Apply() { Localization instance = Localization.instance; if (instance == null || AddWord == null) { return; } string selectedLanguage = instance.GetSelectedLanguage(); Dictionary<string, string> dictionary = new Dictionary<string, string>(_english); Dictionary<string, string> dictionary2 = new Dictionary<string, string>(_english); AddRuntime(dictionary, "English"); if (!string.Equals(selectedLanguage, "English", StringComparison.OrdinalIgnoreCase)) { if (Languages.TryGetValue(selectedLanguage, out var value)) { foreach (KeyValuePair<string, string> item in value) { dictionary[item.Key] = item.Value; dictionary2[item.Key] = item.Value; } } AddRuntime(dictionary, selectedLanguage); } try { string path = Path.Combine(_overrideDir ?? "", selectedLanguage + ".json"); if (_overrideDir != null && File.Exists(path)) { foreach (KeyValuePair<string, string> item2 in Parse(File.ReadAllText(path))) { dictionary[item2.Key] = item2.Value; dictionary2[item2.Key] = item2.Value; } } } catch (Exception ex) { Debug.LogWarning((object)("Translations: couldn't read the player's " + selectedLanguage + " file: " + ex.Message)); } Dictionary<string, string> dictionary3 = GameWords?.GetValue(instance) as Dictionary<string, string>; foreach (string item3 in _runtimeGiven) { if (!dictionary.ContainsKey(item3)) { dictionary3?.Remove(item3); } } _runtimeGiven.Clear(); foreach (Dictionary<string, Dictionary<string, string>> value2 in Runtime.Values) { foreach (Dictionary<string, string> value3 in value2.Values) { foreach (string key in value3.Keys) { _runtimeGiven.Add(key); } } } _given = dictionary; _files = dictionary2; foreach (KeyValuePair<string, string> item4 in dictionary) { AddWord.Invoke(instance, new object[2] { item4.Key, FillIn(item4.Key, item4.Value) }); } EvictCache(instance); } private static void EvictCache(Localization loc) { object obj = Cache?.GetValue(loc); obj?.GetType().GetMethod("EvictAll")?.Invoke(obj, null); } private static Dictionary<string, string> Parse(string json) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (Match item in Regex.Matches(json, "\"((?:[^\"\\\\]|\\\\.)*)\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"")) { dictionary[Unescape(item.Groups[1].Value)] = Unescape(item.Groups[2].Value); } return dictionary; } private static string Unescape(string s) { return Regex.Replace(s, "\\\\(u[0-9a-fA-F]{4}|.)", delegate(Match m) { string value = m.Groups[1].Value; return value[0] switch { 'n' => "\n", 't' => "\t", 'u' => ((char)Convert.ToInt32(value.Substring(1), 16)).ToString(), _ => value, }; }); } } internal sealed class ServerSettings : IDisposable { public enum ServerMod { NotConnected, Checking, Installed, Missing } private sealed class ConfigurationManagerAttributes { public bool? ReadOnly; } private sealed class SyncedText { public string Key; public Func<string> Own; public Action<string> Use; public string Given; public bool FromServer; public bool Pending; public byte[] Packed; public bool TooBig; public MemoryStream Incoming; public int NextPart; } private sealed class Sending { public ZRpc Rpc; public string Key; public byte[] Data; public int Next; public int Parts; } private const int Protocol = 2; private const float ChangeSendGap = 0.5f; private const float AnswerWait = 20f; private const float FileWait = 1f; private const int PartSize = 65536; private const int CompressFrom = 1024; private const int WarnBytes = 262144; private const int MaxBytes = 8388608; private const int PaceBelow = 10240; private readonly string _send; private readonly string _ask; private readonly string _dAsk; private readonly string _dHello; private readonly string _dSend; private readonly string _edit; private readonly string _dTextAsk; private readonly string _dText; private readonly string _dAgain; private readonly ConfigFile _config; private readonly ManualLogSource _log; private readonly string _name; private readonly List<ConfigEntryBase> _entries = new List<ConfigEntryBase>(); private readonly Dictionary<ConfigEntryBase, object> _own = new Dictionary<ConfigEntryBase, object>(); private readonly Dictionary<ConfigEntryBase, object> _server = new Dictionary<ConfigEntryBase, object>(); private readonly List<GameObject> _guardedPieces = new List<GameObject>(); private readonly List<Recipe> _guardedRecipes = new List<Recipe>(); private ZRoutedRpc _registeredOn; private bool _applying; private bool _reloading; private bool _changePending; private float _nextChangeSend; private float _askedAt = -1f; private int _serverProtocol; private bool _directSeen; private readonly HashSet<ZRpc> _directPeers = new HashSet<ZRpc>(); private readonly List<SyncedText> _texts = new List<SyncedText>(); private readonly Dictionary<ZRpc, HashSet<string>> _textPeers = new Dictionary<ZRpc, HashSet<string>>(); private bool _textPending; private float _nextTextSend; private bool _readOnlyShown; private float _nextAdminCheck; private FileSystemWatcher _watcher; private volatile bool _fileTouched; private float _reloadAt = -1f; private string _fileText; private readonly List<Sending> _outbox = new List<Sending>(); private static readonly MethodInfo UpdateAvailablePieces = AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null); private static readonly List<ServerSettings> Live = new List<ServerSettings>(); private static Harmony _saveHarmony; public string Version { get; } public bool FromServer => _server.Count > 0; public ServerMod Server { get; private set; } public string ServerVersion { get; private set; } public bool CanEditServer { get { if (!IsServer) { if (Server == ServerMod.Installed && _serverProtocol >= 1 && (Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.LocalPlayerIsAdminOrHost(); } return false; } return true; } } private static bool IsServer { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public event Action ServerChanged; public ServerSettings(string guid, ConfigFile config, ManualLogSource log) { _send = guid + ".settings"; _ask = guid + ".settings.ask"; _dAsk = guid + ".settings.d.ask"; _dHello = guid + ".settings.d.hello"; _dSend = guid + ".settings.d.values"; _edit = guid + ".settings.d.edit"; _dTextAsk = guid + ".settings.d.textask"; _dAgain = guid + ".settings.d.again"; _dText = guid + ".settings.d.text"; _config = config; _log = log; BepInPlugin val = PluginOf(guid); _name = ((val != null) ? val.Name : null) ?? guid; Version = ((val == null) ? null : val.Version?.ToString()) ?? ""; Live.Add(this); if (Live.Count == 1) { PatchSave(); } WatchFile(); } public ConfigEntry<T> Add<T>(ConfigEntry<T> entry) { if (entry == null || _entries.Contains((ConfigEntryBase)(object)entry)) { return entry; } _entries.Add((ConfigEntryBase)(object)entry); entry.SettingChanged += OnChanged; GiveManagerAttributes((ConfigEntryBase)(object)entry); return entry; } private static void GiveManagerAttributes(ConfigEntryBase entry) { ConfigDescription description = entry.Description; if (description == null || description == ConfigDescription.Empty || (description.Tags ?? new object[0]).Any(IsManagerAttributes)) { return; } try { object[] value = new object[1] { new ConfigurationManagerAttributes() }.Concat(description.Tags ?? new object[0]).ToArray(); AccessTools.Field(typeof(ConfigDescription), "<Tags>k__BackingField")?.SetValue(description, value); } catch (Exception) { } } public ConfigEntry<T> Bind<T>(string section, string key, T value, string description, AcceptableValueBase range = null, params object[] tags) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown if (!description.Contains("[Synced with Server]")) { description += " [Synced with Server]"; } return Add<T>(_config.Bind<T>(section, key, value, new ConfigDescription(description, range, tags ?? new object[0]))); } public void NeedsServer(GameObject piecePrefab) { if (!((Object)(object)piecePrefab == (Object)null)) { _guardedPieces.RemoveAll((GameObject p) => (Object)(object)p == (Object)null || ((Object)p).name == ((Object)piecePrefab).name); _guardedPieces.Add(piecePrefab); Guard(); } } public void NeedsServer(Recipe recipe) { if (!((Object)(object)recipe == (Object)null)) { _guardedRecipes.RemoveAll((Recipe r) => (Object)(object)r == (Object)null || (Object)(object)r == (Object)(object)recipe || (!string.IsNullOrEmpty(((Object)recipe).name) && ((Object)r).name == ((Object)recipe).name)); _guardedRecipes.Add(recipe); Guard(); } } public void AddText(string key, Func<string> own, Action<string> use) { if (string.IsNullOrEmpty(key) || own == null || use == null) { ManualLogSource log = _log; if (log != null) { log.LogError((object)"AddText needs a key, own and use"); } return; } _texts.RemoveAll((SyncedText x) => x.Key == key); SyncedText syncedText = new SyncedText { Key = key, Own = own, Use = use }; _texts.Add(syncedText); Give(syncedText, OwnText(syncedText)); ZRpc val = ((!((Object)(object)ZNet.instance != (Object)null) || IsServer) ? null : ZNet.instance.GetServerPeer()?.m_rpc); if (_registeredOn != null && val != null) { val.Register<ZPackage>(_dText, (Action<ZRpc, ZPackage>)OnText); AskTexts(val); } } public void TextChanged(string key) { SyncedText syncedText = _texts.FirstOrDefault((SyncedText x) => x.Key == key); if (syncedText != null) { syncedText.Packed = null; if (!syncedText.FromServer) { Give(syncedText, OwnText(syncedText)); } if (IsServer) { syncedText.Pending = true; _textPending = true; } } } public bool TextFromServer(string key) { return _texts.Any((SyncedText x) => x.Key == key && x.FromServer); } public void Update() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != _registeredOn) { Unregister(); Restore(); RestoreTexts(give: true); _changePending = false; _textPending = false; SetServer(ServerMod.NotConnected, null, 0); if (instance != null) { Register(instance); } } float unscaledTime = Time.unscaledTime; if (_changePending && unscaledTime >= _nextChangeSend) { SendChange(); } if (_textPending && unscaledTime >= _nextTextSend) { SendTexts(); } if (_outbox.Count > 0) { Pump(); } if (Server == ServerMod.Checking && _askedAt >= 0f && unscaledTime - _askedAt > 20f) { if (_guardedPieces.Count + _guardedRecipes.Count > 0) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("The server didn't answer: " + _name + " isn't installed there (or its copy is older than 2026-10-11), so its pieces and items can't be made on this server.")); } } else { ManualLogSource log2 = _log; if (log2 != null) { log2.LogInfo((object)("The server didn't answer: " + _name + " isn't installed there, or its copy is older" + (FromServer ? " (its settings apply)." : " (your own settings apply)."))); } } SetServer(ServerMod.Missing, null, 0); } if (_fileTouched) { _fileTouched = false; _reloadAt = unscaledTime + 1f; } if (_reloadAt >= 0f && unscaledTime >= _reloadAt) { _reloadAt = -1f; ReloadFile(); } if (FromServer && unscaledTime >= _nextAdminCheck) { _nextAdminCheck = unscaledTime + 1f; ShowReadOnly(); } } public void Dispose() { Unregister(); Restore(); RestoreTexts(give: false); SetServer(ServerMod.NotConnected, null, 0); foreach (ConfigEntryBase entry in _entries) { Unsubscribe(entry); } _entries.Clear(); _texts.Clear(); try { _watcher?.Dispose(); } catch (Exception) { } _watcher = null; Live.Remove(this); if (Live.Count == 0) { UnpatchSave(); } } private void Register(ZRoutedRpc rpc) { Forget(rpc, _send); Forget(rpc, _ask); rpc.Register<ZPackage>(_send, (Action<long, ZPackage>)OnRoutedValues); rpc.Register(_ask, (Action<long>)OnRoutedAsk); rpc.m_onNewPeer = (Action<long>)Delegate.Combine(rpc.m_onNewPeer, new Action<long>(OnNewPeer)); _registeredOn = rpc; if (IsServer) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { ListenTo(peer); if (peer != null) { ZRpc rpc2 = peer.m_rpc; if (rpc2 != null) { rpc2.Invoke(_dAgain, Array.Empty<object>()); } } } return; } if ((Object)(object)ZNet.instance != (Object)null) { SetServer(ServerMod.Checking, null, 0); if (ZNet.instance.GetServerPeer() != null) { AskServer(); } } } private void Unregister() { if (_registeredOn == null) { return; } ZRoutedRpc registeredOn = _registeredOn; registeredOn.m_onNewPeer = (Action<long>)Delegate.Remove(registeredOn.m_onNewPeer, new Action<long>(OnNewPeer)); Forget(_registeredOn, _send); Forget(_registeredOn, _ask); if ((Object)(object)ZNet.instance != (Object)null) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer?.m_rpc != null) { string[] array = new string[7] { _dAsk, _dHello, _dSend, _edit, _dTextAsk, _dText, _dAgain }; foreach (string name in array) { ForgetDirect(peer.m_rpc, name); } } } } _directPeers.Clear(); _textPeers.Clear(); _outbox.Clear(); _directSeen = false; _registeredOn = null; } private static void Forget(ZRoutedRpc rpc, string name) { if (AccessTools.Field(typeof(ZRoutedRpc), "m_functions")?.GetValue(rpc) is IDictionary dictionary) { dictionary.Remove(StringExtensionMethods.GetStableHashCode(name)); } } private static void ForgetDirect(ZRpc rpc, string name) { if (AccessTools.Field(typeof(ZRpc), "m_functions")?.GetValue(rpc) is IDictionary dictionary) { dictionary.Remove(StringExtensionMethods.GetStableHashCode(name)); } } private void OnNewPeer(long uid) { if (IsServer) { ZNetPeer peer = ZNet.instance.GetPeer(uid); ListenTo(peer); if (peer?.m_rpc != null) { SendDirect(peer.m_rpc); } SendRouted(uid); } else if ((Object)(object)ZNet.instance != (Object)null) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_uid == uid) { AskServer(); } } } private void ListenTo(ZNetPeer peer) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown if (peer?.m_rpc != null) { peer.m_rpc.Register(_dAsk, new Method(OnDirectAsk)); peer.m_rpc.Register<ZPackage>(_edit, (Action<ZRpc, ZPackage>)OnEdit); peer.m_rpc.Register<ZPackage>(_dTextAsk, (Action<ZRpc, ZPackage>)OnTextAsk); } } private void OnDirectAsk(ZRpc rpc) { if (IsServer) { _directPeers.Add(rpc); SendDirect(rpc); } } private void OnRoutedAsk(long sender) { if (IsServer) { SendRouted(sender); } } private void SendDirect(ZRpc rpc) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(2); val.Write(Version); rpc.Invoke(_dHello, new object[1] { val }); if (_entries.Count > 0) { rpc.Invoke(_dSend, new object[1] { Values() }); } } private void SendRouted(long target) { if (ZRoutedRpc.instance != null && _entries.Count > 0) { ZRoutedRpc.instance.InvokeRoutedRPC(target, _send, new object[1] { Values() }); } } private ZPackage Values() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(_entries.Count); foreach (ConfigEntryBase entry in _entries) { val.Write(entry.Definition.Section); val.Write(entry.Definition.Key); val.Write(TomlTypeConverter.ConvertToString(entry.BoxedValue, entry.SettingType)); } return val; } private void AskServer() { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null); if (val?.m_rpc == null) { return; } _askedAt = Time.unscaledTime; val.m_rpc.Register<ZPackage>(_dHello, (Action<ZRpc, ZPackage>)OnDirectHello); val.m_rpc.Register<ZPackage>(_dSend, (Action<ZRpc, ZPackage>)OnDirectValues); val.m_rpc.Register<ZPackage>(_dText, (Action<ZRpc, ZPackage>)OnText); val.m_rpc.Register(_dAgain, new Method(OnAskAgain)); val.m_rpc.Invoke(_dAsk, Array.Empty<object>()); AskTexts(val.m_rpc); try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(val.m_uid, _ask, Array.Empty<object>()); } } catch (Exception) { } } private static bool IsServerConnection(ZRpc rpc) { if (!IsServer) { ZNet instance = ZNet.instance; return ((instance == null) ? null : instance.GetServerPeer()?.m_rpc) == rpc; } return false; } private void OnAskAgain(ZRpc rpc) { if (IsServerConnection(rpc)) { rpc.Invoke(_dAsk, Array.Empty<object>()); AskTexts(rpc); } } private void OnDirectHello(ZRpc rpc, ZPackage pkg) { if (!IsServerConnection(rpc)) { return; } try { int protocol = pkg.ReadInt(); string text = pkg.ReadString(); if (text != Version && text != ServerVersion) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("The server has " + _name + " " + text + ", you have " + Version + ": some things may not match. Use the same version as the server.")); } } _directSeen = true; SetServer(ServerMod.Installed, text, protocol); } catch (Exception ex) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("Could not read the server's hello: " + ex.Message)); } } } private void OnDirectValues(ZRpc rpc, ZPackage pkg) { if (IsServerConnection(rpc)) { TakeValues(pkg); } } private void OnRoutedValues(long sender, ZPackage pkg) { if (!_directSeen && !IsServer) { ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null); if (val != null && val.m_uid == sender) { TakeValues(pkg); } } } private void TakeValues(ZPackage pkg) { try { int num = pkg.ReadInt(); List<string> list = new List<string>(); for (int i = 0; i < num; i++) { string section = pkg.ReadString(); string key = pkg.ReadString(); string text = pkg.ReadString(); ConfigEntryBase val = ((IEnumerable<ConfigEntryBase>)_entries).FirstOrDefault((Func<ConfigEntryBase, bool>)((ConfigEntryBase x) => x.Definition.Section == section && x.Definition.Key == key)); if (val != null) { object obj; try { obj = TomlTypeConverter.ConvertToValue(text, val.SettingType); } catch (Exception) { continue; } if (!_own.ContainsKey(val)) { _own[val] = val.BoxedValue; } _server[val] = obj; if (!object.Equals(val.BoxedValue, obj)) { Apply(val, obj); list.Add(section + "/" + key + " = " + text); } } } ManualLogSource log = _log; if (log != null) { log.LogInfo((object)($"Settings from the server apply while you play here ({_server.Count})" + ((list.Count > 0) ? (": " + string.Join(", ", list.ToArray())) : ""))); } ShowReadOnly(); } catch (Exception ex2) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("Could not read the server's settings: " + ex2.Message)); } } } private void Apply(ConfigEntryBase e, object value) { bool saveOnConfigSet = _config.SaveOnConfigSet; _applying = true; try { _config.SaveOnConfigSet = false; e.BoxedValue = value; } finally { _config.SaveOnConfigSet = saveOnConfigSet; _applying = false; } } private void Restore() { if (_server.Count == 0) { return; } foreach (KeyValuePair<ConfigEntryBase, object> item in _own) { Apply(item.Key, item.Value); } _own.Clear(); _server.Clear(); ShowReadOnly(); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)"Your own settings apply again"); } } private void OnChanged(object sender, EventArgs args) { if (_applying) { return; } ConfigEntryBase val = (ConfigEntryBase)((sender is ConfigEntryBase) ? sender : null); if (val == null) { return; } if (_server.TryGetValue(val, out var value)) { if (!_reloading && CanEditServer) { SendEdit(val); } else { _own[val] = val.BoxedValue; } Apply(val, value); } else if (IsServer) { _changePending = true; if (Time.unscaledTime >= _nextChangeSend) { SendChange(); } } } private void SendChange() { _changePending = false; _nextChangeSend = Time.unscaledTime + 0.5f; if (!IsServer) { return; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer?.m_rpc == null) { continue; } if (_directPeers.Contains(peer.m_rpc)) { if (_entries.Count > 0) { peer.m_rpc.Invoke(_dSend, new object[1] { Values() }); } } else { SendRouted(peer.m_uid); } } } private void SendEdit(ConfigEntryBase e) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null); if (val?.m_rpc != null) { ZPackage val2 = new ZPackage(); val2.Write(e.Definition.Section); val2.Write(e.Definition.Key); val2.Write(TomlTypeConverter.ConvertToString(e.BoxedValue, e.SettingType)); val.m_rpc.Invoke(_edit, new object[1] { val2 }); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)("Asked the server to set " + e.Definition.Section + "/" + e.Definition.Key)); } } } private void OnEdit(ZRpc rpc, ZPackage pkg) { try { if (!IsServer) { return; } ISocket socket = rpc.GetSocket(); string text = ((socket != null) ? socket.GetHostName() : null) ?? ""; ZNetPeer val = ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_rpc == rpc)); string section = pkg.ReadString(); string key = pkg.ReadString(); string text2 = pkg.ReadString(); if (!ZNet.instance.IsAdmin(text)) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)((val?.m_playerName ?? text) + " tried to change " + section + "/" + key + " but isn't on the admin list")); } if (_entries.Count > 0) { rpc.Invoke(_dSend, new object[1] { Values() }); } return; } ConfigEntryBase val2 = ((IEnumerable<ConfigEntryBase>)_entries).FirstOrDefault((Func<ConfigEntryBase, bool>)((ConfigEntryBase x) => x.Definition.Section == section && x.Definition.Key == key)); if (val2 != null) { val2.BoxedValue = TomlTypeConverter.ConvertToValue(text2, val2.SettingType); ManualLogSource log2 = _log; if (log2 != null) { log2.LogInfo((object)("Admin " + (val?.m_playerName ?? text) + " set " + section + "/" + key + " = " + text2)); } } } catch (Exception ex) { ManualLogSource log3 = _log; if (log3 != null) { log3.LogWarning((object)("Could not take an admin's change: " + ex.Message)); } } } private void ShowReadOnly() { bool flag = FromServer && !CanEditServer; if (flag == _readOnlyShown) { return; } _readOnlyShown = flag; foreach (ConfigEntryBase entry in _entries) { ConfigDescription description = entry.Description; object[] array = ((description != null) ? description.Tags : null) ?? new object[0]; foreach (object obj in array) { if (IsManagerAttributes(obj)) { try { AccessTools.Field(obj.GetType(), "ReadOnly")?.SetValue(obj, flag ? ((object)true) : null); } catch (Exception) { } } } } } private static bool IsManagerAttributes(object tag) { if (tag != null) { return tag.GetType().Name == "ConfigurationManagerAttributes"; } return false; } private string OwnText(SyncedText t) { try { return t.Own() ?? ""; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not read " + t.Key + ": " + ex.Message)); } return t.Given ?? ""; } } private void Give(SyncedText t, string text) { if (t.Given != null && t.Given == text) { return; } t.Given = text; try { t.Use(text); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not use " + t.Key + ": " + ex)); } } } private void RestoreTexts(bool give) { foreach (SyncedText text in _texts) { text.Incoming = null; if (!text.FromServer) { continue; } text.FromServer = false; if (give) { Give(text, OwnText(text)); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)("Your own " + text.Key + " applies again")); } } } } private void AskTexts(ZRpc server) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown if (_texts.Count == 0 || server == null) { return; } ZPackage val = new ZPackage(); val.Write(_texts.Count); foreach (SyncedText text in _texts) { val.Write(text.Key); } server.Invoke(_dTextAsk, new object[1] { val }); } private void OnTextAsk(ZRpc rpc, ZPackage pkg) { try { if (!IsServer) { return; } int num = pkg.ReadInt(); HashSet<string> hashSet = new HashSet<string>(); for (int i = 0; i < num && i < 1000; i++) { string key = pkg.ReadString(); if (_texts.Any((SyncedText x) => x.Key == key)) { hashSet.Add(key); } } if (_textPeers.TryGetValue(rpc, out var value)) { hashSet.UnionWith(value); } foreach (ZRpc item in _textPeers.Keys.Where((ZRpc r) => !r.IsConnected()).ToList()) { _textPeers.Remove(item); } _textPeers[rpc] = hashSet; foreach (SyncedText text in _texts) { if (hashSet.Contains(text.Key)) { SendText(rpc, text); } } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not answer a player's ask for server data: " + ex.Message)); } } } private void SendTexts() { _textPending = false; _nextTextSend = Time.unscaledTime + 0.5f; if (!IsServer) { return; } foreach (SyncedText text in _texts) { if (!text.Pending) { continue; } text.Pending = false; foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer?.m_rpc != null && _textPeers.TryGetValue(peer.m_rpc, out var value) && value.Contains(text.Key)) { SendText(peer.m_rpc, text); } } } } private void SendText(ZRpc rpc, SyncedText t) { byte[] array = Packed(t); if (array != null) { _outbox.RemoveAll((Sending s) => s.Rpc == rpc && s.Key == t.Key); _outbox.Add(new Sending { Rpc = rpc, Key = t.Key, Data = array, Parts = Math.Max(1, (array.Length + 65536 - 1) / 65536) }); Pump(); } } private void Pump() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown for (int num = _outbox.Count - 1; num >= 0; num--) { Sending sending = _outbox[num]; try { while (sending.Next < sending.Parts && sending.Rpc.IsConnected() && sending.Rpc.GetSocket().GetSendQueueSize() < 10240) { int num2 = sending.Next * 65536; int num3 = Math.Min(65536, sending.Data.Length - num2); byte[] array = new byte[num3]; Buffer.BlockCopy(sending.Data, num2, array, 0, num3); ZPackage val = new ZPackage(); val.Write(sending.Key); val.Write(sending.Next); val.Write(sending.Parts); val.Write(array); sending.Rpc.Invoke(_dText, new object[1] { val }); sending.Next++; } if (sending.Next >= sending.Parts || !sending.Rpc.IsConnected()) { _outbox.RemoveAt(num); } } catch (Exception ex) { _outbox.RemoveAt(num); ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not send " + sending.Key + " to a player: " + ex.Message)); } } } } private byte[] Packed(SyncedText t) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown if (t.Packed == null) { string text = OwnText(t); ZPackage val = new ZPackage(); val.Write(text); ZPackage val2 = new ZPackage(); bool flag = text.Length > 1024; val2.Write(flag); if (flag) { val2.WriteCompressed(val); } else { val2.Write(val); } t.Packed = val2.GetArray(); t.TooBig = t.Packed.Length > 8388608; string arg = $"{t.Packed.Length / 1024} KB" + (flag ? $" gzipped ({text.Length / 1024} KB of text)" : ""); if (t.TooBig) { ManualLogSource log = _log; if (log != null) { log.LogError((object)$"{t.Key} is too big to send to players ({arg}, at most {8} MB): they keep their own."); } } else if (t.Packed.Length > 262144) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)$"{t.Key} is big to send to every player who joins ({arg}): it takes about {(float)t.Packed.Length / 153600f:0} s of their connection, and their world loads slower meanwhile."); } } } if (!t.TooBig) { return t.Packed; } return null; } private void OnText(ZRpc rpc, ZPackage pkg) { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Expected O, but got Unknown if (!IsServerConnection(rpc)) { return; } SyncedText syncedText = null; try { string key = pkg.ReadString(); int num = pkg.ReadInt(); int num2 = pkg.ReadInt(); byte[] array = pkg.ReadByteArray(); syncedText = _texts.FirstOrDefault((SyncedText x) => x.Key == key); if (syncedText == null) { return; } if (num2 < 1 || num2 > 129 || num < 0 || num >= num2) { throw new InvalidDataException($"part {num} of {num2}"); } if (num == 0) { syncedText.Incoming = new MemoryStream(); syncedText.NextPart = 0; } if (syncedText.Incoming == null || num != syncedText.NextPart) { throw new InvalidDataException($"part {num} came out of order"); } syncedText.Incoming.Write(array, 0, array.Length); syncedText.NextPart = num + 1; if (num >= num2 - 1) { ZPackage val = new ZPackage(syncedText.Incoming.ToArray()); syncedText.Incoming = null; ZPackage val2 = (val.ReadBool() ? val.ReadCompressedPackage() : val.ReadPackage()); string text = val2.ReadString(); syncedText.FromServer = true; Give(syncedText, text); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"The server's {key} applies while you play here ({text.Length / 1024} KB)"); } } } catch (Exception ex) { if (syncedText != null) { syncedText.Incoming = null; } ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("Could not read the server's data" + ((syncedText != null) ? (" for " + syncedText.Key) : "") + ": " + ex.Message)); } } } private void SetServer(ServerMod state, string version, int protocol) { if (state != ServerMod.Checking) { _askedAt = -1f; } bool flag = state != Server || version != ServerVersion; Server = state; ServerVersion = version; _serverProtocol = protocol; if (!flag) { return; } Guard(); try { this.ServerChanged?.Invoke(); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("ServerChanged: " + ex)); } } } private void Guard() { bool flag = Server == ServerMod.NotConnected || Server == ServerMod.Installed; bool flag2 = false; foreach (GameObject guardedPiece in _guardedPieces) { Piece val = (((Object)(object)guardedPiece != (Object)null) ? guardedPiece.GetComponent<Piece>() : null); if ((Object)(object)val != (Object)null && val.m_enabled != flag) { val.m_enabled = flag; flag2 = true; } } foreach (Recipe guardedRecipe in _guardedRecipes) { if ((Object)(object)guardedRecipe != (Object)null && guardedRecipe.m_enabled != flag) { guardedRecipe.m_enabled = flag; flag2 = true; } } if (flag2 && (Object)(object)Player.m_localPlayer != (Object)null) { try { UpdateAvailablePieces?.Invoke(Player.m_localPlayer, null); } catch (Exception) { } } } private void WatchFile() { try { string configFilePath = _config.ConfigFilePath; _watcher = new FileSystemWatcher(Path.GetDirectoryName(configFilePath), Path.GetFileName(configFilePath)) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite) }; FileSystemEventHandler value = delegate { _fileTouched = true; }; _watcher.Changed += value; _watcher.Created += value; _watcher.Renamed += delegate { _fileTouched = true; }; _watcher.EnableRaisingEvents = true; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Edits to the config file apply after a restart (can't watch it: " + ex.Message + ")")); } } } private void ReloadFile() { string text = ReadFile(_config.ConfigFilePath); if (text == null) { if (File.Exists(_config.ConfigFilePath)) { _reloadAt = Time.unscaledTime + 1f; } } else { if (text == _fileText) { return; } _fileText = text; bool saveOnConfigSet = _config.SaveOnConfigSet; _reloading = true; try { _config.SaveOnConfigSet = false; _config.Reload(); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not read the edited config file (check the line you changed): " + ex.Message)); } } finally { _config.SaveOnConfigSet = saveOnConfigSet; _reloading = false; } } } private void PatchSave() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_0067: Expected O, but got Unknown try { _saveHarmony = new Harmony(_send + ".save"); _saveHarmony.Patch((MethodBase)AccessTools.Method(typeof(ConfigFile), "Save", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ServerSettings), "BeforeSave", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ServerSettings), "AfterSave", (Type[])null), (HarmonyMethod)null); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Could not keep the server's values out of your config file: " + ex.Message)); } } } private static void UnpatchSave() { try { Harmony saveHarmony = _saveHarmony; if (saveHarmony != null) { saveHarmony.UnpatchSelf(); } } catch (Exception) { } _saveHarmony = null; } private static void BeforeSave(ConfigFile __instance) { foreach (ServerSettings item in Live) { if (item._config != __instance) { continue; } foreach (KeyValuePair<ConfigEntryBase, object> item2 in item._own) { SetQuietly(item2.Key, item2.Value); } } } private static Exception AfterSave(ConfigFile __instance, Exception __exception) { foreach (ServerSettings item in Live) { if (item._config != __instance) { continue; } foreach (KeyValuePair<ConfigEntryBase, object> item2 in item._server) { SetQuietly(item2.Key, item2.Value); } item._fileText = ReadFile(__instance.ConfigFilePath); } return __exception; } private static string ReadFile(string path) { try { return File.Exists(path) ? File.ReadAllText(path) : null; } catch (Exception) { return null; } } private static void SetQuietly(ConfigEntryBase e, object value) { AccessTools.Field(((object)e).GetType(), "_typedValue")?.SetValue(e, value); } private static BepInPlugin PluginOf(string guid) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown Type[] array; try { array = typeof(ServerSettings).Assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).ToArray(); } Type[] array2 = array; foreach (Type type in array2) { object[] customAttributes = type.GetCustomAttributes(typeof(BepInPlugin), inherit: false); for (int num2 = 0; num2 < customAttributes.Length; num2++) { BepInPlugin val = (BepInPlugin)customAttributes[num2]; if (val.GUID == guid || guid.StartsWith(val.GUID + ".")) { return val; } } } return null; } private void Unsubscribe(ConfigEntryBase e) { ((object)e).GetType().GetEvent("SettingChanged")?.RemoveEventHandler(e, new EventHandler(OnChanged)); } } }