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 Redeem v1.1.4
plugins/Redeem.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using LocalizationManager; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: Guid("E74EB49A-461D-48EA-85BC-F462D60C98C4")] [assembly: ComVisible(false)] [assembly: AssemblyTrademark("")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyProduct("Redeem")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyDescription("")] [assembly: AssemblyTitle("Redeem")] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyFileVersion("1.1.4")] [assembly: AssemblyCompany("Radamanto")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.4.0")] [module: <b7536b42-0d8d-4205-861e-affd80e0bfe4>RefSafetyRules(11)] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [<d07cf4b9-5e3d-479c-a83a-3403c4ea4ce4>Embedded] internal sealed class <d07cf4b9-5e3d-479c-a83a-3403c4ea4ce4>EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] [<d07cf4b9-5e3d-479c-a83a-3403c4ea4ce4>Embedded] [CompilerGenerated] internal sealed class <aa5db788-994e-4191-9fac-e846e7f43be2>NullableAttribute : Attribute { public readonly byte[] NullableFlags; public <aa5db788-994e-4191-9fac-e846e7f43be2>NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public <aa5db788-994e-4191-9fac-e846e7f43be2>NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] [<d07cf4b9-5e3d-479c-a83a-3403c4ea4ce4>Embedded] [CompilerGenerated] internal sealed class <e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContextAttribute : Attribute { public readonly byte Flag; public <e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContextAttribute(byte P_0) { Flag = P_0; } } [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] [<d07cf4b9-5e3d-479c-a83a-3403c4ea4ce4>Embedded] [CompilerGenerated] internal sealed class <b7536b42-0d8d-4205-861e-affd80e0bfe4>RefSafetyRulesAttribute : Attribute { public readonly int Version; public <b7536b42-0d8d-4205-861e-affd80e0bfe4>RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Redeem { [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(1)] [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(0)] [BepInPlugin("radamanto.Redeem", "Redeem", "1.1.4")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class RedeemPlugin : BaseUnityPlugin { [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] private sealed class PackagesData { [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(1)] public Dictionary<string, PackageDef> Packages = new Dictionary<string, PackageDef>(StringComparer.OrdinalIgnoreCase); } [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(0)] private sealed class PackageDef { public string Id = ""; public List<ItemDef> Items = new List<ItemDef>(); } [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] private sealed class ItemDef { [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(1)] public string Prefab = ""; public int Amount = 1; public int Quality = 1; public int Variant; } [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] private sealed class CodesData { [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(1)] public Dictionary<string, CodeInfo> Codes = new Dictionary<string, CodeInfo>(StringComparer.OrdinalIgnoreCase); } [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(0)] private sealed class CodeInfo { public string Code = ""; public string Package = ""; } [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] private class ConfigurationManagerAttributes { public bool? Browsable = false; } public const string ModGUID = "radamanto.Redeem"; public const string ModName = "Redeem"; public const string ModVersion = "1.1.4"; private readonly Harmony _harmony = new Harmony("radamanto.Redeem"); private static bool _bootstrapped; internal static ManualLogSource Log = null; private const float DebounceSeconds = 2.5f; private const float SpawnYOffset = 0.5f; private const int AttemptWindowSeconds = 300; private const int MaxAttemptsPerWindow = 5; private const int BlockSeconds = 900; private const int InFlightTimeoutSeconds = 60; private const int MaxCodeLength = 128; private const int RecentClaimedTtlDays = 7; private const string PackagesFile = "packages.yml"; private const string CodesActiveFile = "code.active.yml"; private const string CodesClaimedFile = "code.claimed.yml"; private const string CodeInsertFile = "code.insert.yml"; private const string RPC_RedeemRequest = "VD_RedeemRequest"; private const string RPC_RedeemResult = "VD_RedeemResult"; private const string RPC_GiveItems = "VD_GiveItems"; private const string RPC_AckDelivery = "VD_AckDelivery"; private static readonly Dictionary<long, DateTime> _lastRequestByPeer = new Dictionary<long, DateTime>(); private static readonly Dictionary<string, DateTime> _recentClaimed = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<long, List<DateTime>> _attemptsByPeer = new Dictionary<long, List<DateTime>>(); private static readonly Dictionary<long, DateTime> _peerBlockedUntil = new Dictionary<long, DateTime>(); private static readonly Dictionary<string, long> _inFlightCodes = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, DateTime> _inFlightExpires = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase); private static PackagesData _packages = new PackagesData(); private static CodesData _codes = new CodesData(); [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(2)] internal static ConfigSync ConfigSync; [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(2)] private static ConfigEntry<bool> _serverConfigLocked; [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(2)] private FileSystemWatcher _configWatcher; private long _lastCfgWrite; private bool _reloadScheduled; private bool _watcherReady; [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(2)] private static ZNet _serverDataSession; private static DateTime _codesLastWriteUtc = DateTime.MinValue; private static DateTime _pollPackagesLastWriteUtc = DateTime.MinValue; private static DateTime _pollInsertLastWriteUtc = DateTime.MinValue; private const string SuccessColor = "#008000"; private const string FailureColor = "#FF0000"; [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(2)] private static ZRoutedRpc _rpcSession; private static string DataFolder => Path.Combine(Paths.ConfigPath, "Redeem_data"); private static string PackagesPath => Path.Combine(DataFolder, "packages.yml"); private static string CodesActivePath => Path.Combine(DataFolder, "code.active.yml"); private static string CodesClaimedPath => Path.Combine(DataFolder, "code.claimed.yml"); private static string CodeInsertPath => Path.Combine(DataFolder, "code.insert.yml"); private void Awake() { Localizer.Load(); if (!_bootstrapped) { Log = ((BaseUnityPlugin)this).Logger; bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; try { SetupServerSyncAndWatcher(); _harmony.PatchAll(); _bootstrapped = true; } finally { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } ((BaseUnityPlugin)this).Config.Save(); SetupConfigWatcher(); ((MonoBehaviour)this).StartCoroutine(FilePoller()); ((MonoBehaviour)this).StartCoroutine(InFlightAndAntiFloodSweeper()); } } private void OnDestroy() { _watcherReady = false; _configWatcher?.Dispose(); _configWatcher = null; } public void SetupServerSyncAndWatcher() { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown ConfigSync = new ConfigSync(((BaseUnityPlugin)this).Info.Metadata.GUID) { DisplayName = ((BaseUnityPlugin)this).Info.Metadata.Name, CurrentVersion = ((BaseUnityPlugin)this).Info.Metadata.Version.ToString(), MinimumRequiredVersion = ((BaseUnityPlugin)this).Info.Metadata.Version.ToString(), ModRequired = true }; _serverConfigLocked = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "LockConfiguration", true, new ConfigDescription("If true, the server locks synchronized configs.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Browsable = false } })); ConfigSync.AddLockingConfigEntry<bool>(_serverConfigLocked); } private void SetupConfigWatcher() { try { _watcherReady = false; _configWatcher?.Dispose(); _configWatcher = new FileSystemWatcher(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath)) { IncludeSubdirectories = false, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite), SynchronizingObject = ThreadingHelper.SynchronizingObject }; _configWatcher.Changed += OnConfigFileChanged; _configWatcher.Created += OnConfigFileChanged; _configWatcher.Renamed += [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (object _, RenamedEventArgs args) => { OnConfigFileChanged(_, args); }; _configWatcher.Error += [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (object _, ErrorEventArgs args) => { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Configuration watcher failed: {args.GetException()}"); }; _watcherReady = true; _configWatcher.EnableRaisingEvents = true; } catch (Exception arg) { _watcherReady = false; _configWatcher?.Dispose(); _configWatcher = null; ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Automatic configuration reload is unavailable: {arg}"); } } private void OnConfigFileChanged(object sender, FileSystemEventArgs args) { if (_watcherReady) { _lastCfgWrite = Stopwatch.GetTimestamp(); if (!_reloadScheduled) { _reloadScheduled = true; ((MonoBehaviour)this).StartCoroutine(ReloadConfigAfterQuietPeriod()); } } } private IEnumerator ReloadConfigAfterQuietPeriod() { try { for (int attempt = 0; attempt < 3; attempt++) { if (!_watcherReady) { break; } while (_watcherReady && (double)(Stopwatch.GetTimestamp() - _lastCfgWrite) / (double)Stopwatch.Frequency < 0.75) { yield return null; } if (!_watcherReady || ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer())) { break; } bool saveOnSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; bool retry = false; try { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; ((BaseUnityPlugin)this).Config.Reload(); } catch (IOException ex) { IOException error = ex; retry = true; if (attempt == 2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Configuration could not be read after 3 attempts: {error}"); } } catch (Exception ex2) { Exception error2 = ex2; ((BaseUnityPlugin)this).Logger.LogError((object)$"Configuration reload failed: {error2}"); } finally { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnSet; } if (!retry) { break; } _lastCfgWrite = Stopwatch.GetTimestamp(); } } finally { _reloadScheduled = false; } } private IEnumerator InFlightAndAntiFloodSweeper() { WaitForSeconds wait = new WaitForSeconds(1f); while (true) { DateTime now = DateTime.UtcNow; foreach (KeyValuePair<string, DateTime> kv in _inFlightExpires.ToList()) { if (!(kv.Value > now)) { _inFlightExpires.Remove(kv.Key); _inFlightCodes.Remove(kv.Key); } } DateTime cutoffRecent = now.AddDays(-7.0); foreach (KeyValuePair<string, DateTime> kv2 in _recentClaimed.ToList()) { if (kv2.Value < cutoffRecent) { _recentClaimed.Remove(kv2.Key); } } yield return wait; } } private static string SamplePackagesYml() { return "# Redeem - packages.yml (example)\r\nPackages:\r\n - Id: starter\r\n Items:\r\n - Prefab: Coins, Amount: 500\r\n - Prefab: Wood, Amount: 100\r\n - Prefab: Stone, Amount: 100\r\n - Id: vip_bundle\r\n Items:\r\n - Prefab: Coins, Amount: 5000\r\n - Prefab: Bronze, Amount: 20\r\n - Prefab: SwordIron, Amount: 1, Quality: 3\r\n"; } private static string SampleCodesYml() { return "# Redeem - code.active (example)\r\nCodes:\r\n - Code: ABCD-1234-START\r\n Package: starter\r\n - Code: VIP-ONLY-9999\r\n Package: vip_bundle\r\n"; } private static bool IsRedeemServer() { return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && !ZNet.IsSinglePlayer; } private static bool EnsureServerData() { if (!IsRedeemServer()) { return false; } if (_serverDataSession == ZNet.instance) { return true; } _serverDataSession = ZNet.instance; _codesLastWriteUtc = DateTime.MinValue; Directory.CreateDirectory(DataFolder); EnsureSampleFiles(); LoadAll(); return true; } private static void EnsureSampleFiles() { if (!File.Exists(PackagesPath)) { File.WriteAllText(PackagesPath, SamplePackagesYml(), Encoding.UTF8); } if (!File.Exists(CodesActivePath)) { File.WriteAllText(CodesActivePath, SampleCodesYml(), Encoding.UTF8); } if (!File.Exists(CodesClaimedPath)) { File.WriteAllText(CodesClaimedPath, string.Empty, Encoding.UTF8); } if (!File.Exists(CodeInsertPath)) { string contents = "# Redeem - code.insert.yml\r\n# Edite a seção Codes e SALVE o arquivo para aplicar.\r\n# Se houver QUALQUER erro (duplicata em active, pacote inexistente, formato inválido),\r\n# nada é aplicado e este arquivo permanece intacto.\r\n\r\nCodes:\r\n - Code: NEW-EXAMPLE-0001\r\n Package: starter\r\n"; File.WriteAllText(CodeInsertPath, contents, Encoding.UTF8); } } private static bool IsFileError(Exception ex) { return ex is IOException || ex is UnauthorizedAccessException || ex is FormatException; } private static void LoadAll() { ReloadPackages(); TryReloadCodes(); ReindexClaimedFromFile(); TryProcessSingleInsert(); } private static void ReloadPackages() { _pollPackagesLastWriteUtc = GetLastWriteUtcSafe(PackagesPath); try { _packages = LoadPackagesYml(PackagesPath); } catch (Exception ex) when (IsFileError(ex)) { Log.LogError((object)("packages.yml was not loaded: " + ex.Message)); } } private static void TryReloadCodes() { DateTime lastWriteUtcSafe = GetLastWriteUtcSafe(CodesActivePath); if (lastWriteUtcSafe <= _codesLastWriteUtc) { return; } _codesLastWriteUtc = lastWriteUtcSafe; try { _codes = LoadCodesYml(CodesActivePath); } catch (Exception ex) when (IsFileError(ex)) { Log.LogError((object)("code.active.yml was not loaded: " + ex.Message)); } } private IEnumerator FilePoller() { WaitForSeconds wait = new WaitForSeconds(2f); while (true) { if (EnsureServerData()) { if (GetLastWriteUtcSafe(PackagesPath) > _pollPackagesLastWriteUtc) { ReloadPackages(); } TryReloadCodes(); if (GetLastWriteUtcSafe(CodeInsertPath) > _pollInsertLastWriteUtc) { TryProcessSingleInsert(); } } yield return wait; } } private static void RejectInsert(string reason) { Log.LogWarning((object)("code.insert.yml was not applied: " + reason)); } private static void TryProcessSingleInsert() { _pollInsertLastWriteUtc = GetLastWriteUtcSafe(CodeInsertPath); if (!File.Exists(CodeInsertPath)) { return; } CodesData codesData; try { codesData = LoadCodesYml(CodeInsertPath); } catch (Exception ex) when (IsFileError(ex)) { RejectInsert(ex.Message); return; } if (codesData.Codes.Count == 0) { return; } foreach (string key in codesData.Codes.Keys) { if (string.IsNullOrWhiteSpace(key)) { RejectInsert("a code is empty."); return; } if (_codes.Codes.ContainsKey(key)) { RejectInsert("code '" + key + "' is already active."); return; } } try { if (ClaimedFileContainsAny(codesData.Codes.Keys, out var foundCode)) { RejectInsert("code '" + foundCode + "' was already redeemed."); return; } } catch (Exception ex2) when (IsFileError(ex2)) { RejectInsert("code.claimed.yml could not be read: " + ex2.Message); return; } foreach (CodeInfo value in codesData.Codes.Values) { if (!_packages.Packages.ContainsKey(value.Package)) { RejectInsert("package '" + value.Package + "' of code '" + value.Code + "' does not exist."); return; } } foreach (CodeInfo value2 in codesData.Codes.Values) { _codes.Codes[value2.Code] = new CodeInfo { Code = value2.Code, Package = value2.Package }; } try { SaveCodesAtomic(); } catch (Exception ex3) when (IsFileError(ex3)) { foreach (string key2 in codesData.Codes.Keys) { _codes.Codes.Remove(key2); } RejectInsert("code.active.yml could not be saved: " + ex3.Message); return; } try { string contents = "# Redeem - code.insert.yml\r\n# Edite a seção Codes e SALVE o arquivo para aplicar.\r\nCodes:\r\n"; File.WriteAllText(CodeInsertPath, contents, Encoding.UTF8); _pollInsertLastWriteUtc = GetLastWriteUtcSafe(CodeInsertPath); } catch (Exception ex4) when (IsFileError(ex4)) { Log.LogWarning((object)("code.insert.yml was applied but could not be cleared: " + ex4.Message)); } } private static void RetryFileWrite(Action write) { int num = 0; while (true) { try { write(); break; } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { if (++num >= 3) { throw; } Thread.Sleep((num == 1) ? 50 : 100); } } } private static void SaveCodesAtomic() { string target = CodesActivePath; string tmp = target + ".tmp"; string bak = target + ".bak"; string content = DumpCodesYml(_codes); RetryFileWrite(delegate { File.WriteAllText(tmp, content, Encoding.UTF8); if (File.Exists(target)) { File.Replace(tmp, target, bak); } else { File.Move(tmp, target); } }); _codesLastWriteUtc = GetLastWriteUtcSafe(target); } private static bool ClaimedFileContainsAny(IEnumerable<string> codes, out string foundCode) { foundCode = ""; if (!File.Exists(CodesClaimedPath)) { return false; } HashSet<string> hashSet = new HashSet<string>(codes.Where([<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (string c) => !string.IsNullOrWhiteSpace(c)), StringComparer.OrdinalIgnoreCase); if (hashSet.Count == 0) { return false; } foreach (var item2 in ReadClaimedLines()) { string item = item2.Code; if (hashSet.Contains(item)) { foundCode = item; return true; } } return false; } [return: <aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(new byte[] { 1, 0, 1, 1 })] private static IEnumerable<(string Code, string When)> ReadClaimedLines() { using FileStream fs = new FileStream(CodesClaimedPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using StreamReader sr = new StreamReader(fs, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, 8192); while (true) { string text; string line = (text = sr.ReadLine()); if (text == null) { break; } if (string.IsNullOrWhiteSpace(line)) { continue; } string[] parts = line.Split(new char[1] { '\t' }); if (parts.Length >= 2) { string code = parts[1].Trim(); if (code.Length != 0) { yield return (Code: code, When: parts[0].Trim()); } } } } private static DateTime GetLastWriteUtcSafe(string path) { try { return File.Exists(path) ? File.GetLastWriteTimeUtc(path) : DateTime.MinValue; } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { return DateTime.MinValue; } } private static void AppendClaimedAtomic(string code, string package, string playerName) { code = SanitizeField(code, 128); package = SanitizeField(package, 128); playerName = SanitizeField(playerName, 64); string line = string.Format(CultureInfo.InvariantCulture, "{0:u}\t{1}\t{2}\t{3}\n", DateTime.UtcNow, code, package, playerName); RetryFileWrite(delegate { File.AppendAllText(CodesClaimedPath, line, Encoding.UTF8); }); } private static void ReindexClaimedFromFile() { if (!File.Exists(CodesClaimedPath)) { return; } _recentClaimed.Clear(); try { foreach (var item3 in ReadClaimedLines()) { string item = item3.Code; string item2 = item3.When; DateTime result; DateTime value = (DateTime.TryParse(item2, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out result) ? result : DateTime.UtcNow); _recentClaimed[item] = value; } } catch (Exception ex) when (IsFileError(ex)) { Log.LogWarning((object)("code.claimed.yml could not be read: " + ex.Message)); } } private static FormatException LineError(int lineNumber, string message) { return new FormatException($"line {lineNumber}: {message}"); } private static PackagesData LoadPackagesYml(string path) { PackagesData packagesData = new PackagesData(); if (!File.Exists(path)) { throw new FileNotFoundException("file is missing.", path); } string[] array = File.ReadAllLines(path); PackageDef packageDef = null; bool flag = false; bool flag2 = false; for (int i = 0; i < array.Length; i++) { int lineNumber = i + 1; string text = array[i].Replace("\t", " ").Trim(); if (text.StartsWith("#") || text.Length == 0) { continue; } if (text.StartsWith("Packages:", StringComparison.OrdinalIgnoreCase)) { flag = true; flag2 = false; packageDef = null; continue; } if (!flag) { throw LineError(lineNumber, "expected 'Packages:' header."); } Match match = Regex.Match(text, "^-+\\s*Id\\s*:\\s*(.+)$", RegexOptions.IgnoreCase); if (match.Success) { packageDef = new PackageDef { Id = match.Groups[1].Value.Trim() }; if (packagesData.Packages.ContainsKey(packageDef.Id)) { throw LineError(lineNumber, "duplicated package '" + packageDef.Id + "'."); } packagesData.Packages[packageDef.Id] = packageDef; flag2 = false; continue; } if (packageDef == null) { throw LineError(lineNumber, "entry outside a package."); } if (text.StartsWith("Items:", StringComparison.OrdinalIgnoreCase)) { flag2 = true; continue; } if (!flag2) { throw LineError(lineNumber, "expected 'Items:' header."); } Match match2 = Regex.Match(text, "^-\\s*Prefab\\s*:\\s*([^,]+)(?:,|$)", RegexOptions.IgnoreCase); if (!match2.Success) { throw LineError(lineNumber, "invalid item, expected '- Prefab: Name, Amount: N'."); } ItemDef itemDef = new ItemDef { Prefab = match2.Groups[1].Value.Trim() }; itemDef.Amount = GetIntField(text, "Amount", 1, lineNumber); itemDef.Quality = GetIntField(text, "Quality", 1, lineNumber); itemDef.Variant = GetIntField(text, "Variant", 0, lineNumber); if (itemDef.Amount < 1 || itemDef.Quality < 1 || itemDef.Variant < 0) { throw LineError(lineNumber, "Amount and Quality must be at least 1 and Variant at least 0."); } packageDef.Items.Add(itemDef); } if (!flag) { throw new FormatException("'Packages:' header is missing."); } PackageDef packageDef2 = packagesData.Packages.Values.FirstOrDefault([<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (PackageDef x) => x.Items.Count == 0); if (packageDef2 != null) { throw new FormatException("package '" + packageDef2.Id + "' has no items."); } return packagesData; } private static int GetIntField(string line, string key, int def, int lineNumber) { Match match = Regex.Match(line, "(?:^|,)\\s*" + Regex.Escape(key) + "\\s*:\\s*([^,]+)", RegexOptions.IgnoreCase); if (!match.Success) { return def; } if (int.TryParse(match.Groups[1].Value.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } throw LineError(lineNumber, "invalid number in '" + key + "'."); } private static CodesData LoadCodesYml(string path) { CodesData codesData = new CodesData(); if (!File.Exists(path)) { throw new FileNotFoundException("file is missing.", path); } string[] array = File.ReadAllLines(path); bool flag = false; CodeInfo codeInfo = null; for (int i = 0; i < array.Length; i++) { int lineNumber = i + 1; string text = array[i].Replace("\t", " ").Trim(); if (text.StartsWith("#") || text.Length == 0) { continue; } if (text.StartsWith("Codes:", StringComparison.OrdinalIgnoreCase)) { flag = true; codeInfo = null; continue; } if (!flag) { throw LineError(lineNumber, "expected 'Codes:' header."); } Match match = Regex.Match(text, "^-+\\s*Code\\s*:\\s*(.+)$", RegexOptions.IgnoreCase); if (match.Success) { codeInfo = new CodeInfo { Code = match.Groups[1].Value.Trim() }; if (codesData.Codes.ContainsKey(codeInfo.Code)) { throw LineError(lineNumber, "duplicated code '" + codeInfo.Code + "'."); } if (codeInfo.Code.Length > 128) { throw LineError(lineNumber, $"code is longer than {128} characters."); } codesData.Codes[codeInfo.Code] = codeInfo; } else { if (codeInfo == null) { throw LineError(lineNumber, "entry outside a code."); } Match match2 = Regex.Match(text, "^Package\\s*:\\s*(.+)$", RegexOptions.IgnoreCase); if (!match2.Success) { throw LineError(lineNumber, "invalid entry, expected 'Package: name'."); } codeInfo.Package = match2.Groups[1].Value.Trim(); } } if (!flag) { throw new FormatException("'Codes:' header is missing."); } CodeInfo codeInfo2 = codesData.Codes.Values.FirstOrDefault([<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (CodeInfo x) => string.IsNullOrWhiteSpace(x.Package)); if (codeInfo2 != null) { throw new FormatException("code '" + codeInfo2.Code + "' has no package."); } return codesData; } private static string DumpCodesYml(CodesData data) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# Redeem - code.active"); stringBuilder.AppendLine("Codes:"); foreach (KeyValuePair<string, CodeInfo> item in data.Codes.OrderBy<KeyValuePair<string, CodeInfo>, string>([<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (KeyValuePair<string, CodeInfo> k) => k.Key, StringComparer.OrdinalIgnoreCase)) { CodeInfo value = item.Value; stringBuilder.AppendLine(" - Code: " + value.Code); stringBuilder.AppendLine(" Package: " + value.Package); } return stringBuilder.ToString(); } private static string SanitizeField(string value, int maxLength) { if (string.IsNullOrEmpty(value)) { return ""; } StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { if (c >= ' ' && c != '\t' && c != '\n' && c != '\r') { stringBuilder.Append(c); if (stringBuilder.Length >= maxLength) { break; } } } return stringBuilder.ToString(); } public static void ClientRequestRedeem(string code) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && !((Object)(object)ZNet.instance == (Object)null) && !string.IsNullOrWhiteSpace(code)) { instance.InvokeRoutedRPC(instance.GetServerPeerID(), "VD_RedeemRequest", new object[1] { code.Trim() }); } } private static void OnRpcRedeemResult_Client(long sender, ZPackage package) { if (!IsFromServer(sender)) { return; } bool flag = package.ReadBool(); string text = package.ReadString(); int num = package.ReadInt(); if (num < 0 || num > 16) { return; } string[] array = new string[num]; for (int i = 0; i < num; i++) { array[i] = package.ReadString(); } Localization instance = Localization.instance; if (instance == null) { return; } string text2 = instance.Localize(text); string text3 = text2; if (array.Length != 0) { try { object[] args = array; text3 = string.Format(text2, args); } catch (FormatException) { text3 = text2; } } MessageHud instance2 = MessageHud.instance; if (instance2 != null) { instance2.ShowMessage((MessageType)2, "<color=" + (flag ? "#008000" : "#FF0000") + ">" + text3 + "</color>", 0, (Sprite)null, false, true); } } private static void OnRpcGiveItems_Client(long sender, ZPackage package) { //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02b5: Unknown result type (might be due to invalid IL or missing references) try { if (!IsFromServer(sender)) { return; } string text = package.ReadString(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); if (inventory == null) { return; } ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return; } int num = package.ReadInt(); if (num <= 0) { return; } List<(ItemDrop, GameObject, int, int, int)> list = new List<(ItemDrop, GameObject, int, int, int)>(); for (int i = 0; i < num; i++) { string text2 = package.ReadString(); int num2 = package.ReadInt(); int num3 = package.ReadInt(); int num4 = package.ReadInt(); if (string.IsNullOrWhiteSpace(text2) || num2 <= 0 || num3 <= 0 || num4 < 0) { throw new FormatException("Invalid redeem reward payload."); } GameObject itemPrefab = instance.GetItemPrefab(text2); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val == (Object)null || val.m_itemData?.m_shared == null) { throw new InvalidOperationException("Redeem reward prefab unavailable: " + text2); } list.Add((val, itemPrefab, num2, num3, num4)); } foreach (var item2 in list) { ItemDrop item = item2.Item1; int num5 = item2.Item3; int num6 = Mathf.Max(1, item.m_itemData.m_shared.m_maxStackSize); int num7 = Mathf.Max(1, item.m_itemData.m_shared.m_maxQuality); while (num5 > 0) { int num8 = Mathf.Min(num5, num6); num5 -= num8; ItemData val2 = item.m_itemData.Clone(); val2.m_dropPrefab = item2.Item2; val2.m_stack = num8; val2.m_quality = Mathf.Clamp(item2.Item4, 1, num7); val2.m_variant = Mathf.Max(0, item2.Item5); Vector3 val3 = ((Component)localPlayer).transform.position + ((Component)localPlayer).transform.forward * 1.25f + Vector3.up * 0.5f; if (!inventory.CanAddItem(val2, val2.m_stack)) { ItemDrop.DropItem(val2, num8, val3, Quaternion.identity); } else if (!inventory.AddItem(val2) && val2.m_stack > 0) { ItemDrop.DropItem(val2, val2.m_stack, val3, Quaternion.identity); } } } Localization instance2 = Localization.instance; if (instance2 != null) { ((Character)localPlayer).Message((MessageType)1, instance2.Localize("$redeem_claim_items_received"), 0, (Sprite)null, false); } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "VD_AckDelivery", new object[1] { text }); } catch (Exception arg) { Log.LogError((object)$"Redeem delivery failed; no acknowledgement sent: {arg}"); } } internal static void RegisterRpcs() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _rpcSession) { _rpcSession = instance; instance.Register<string>("VD_RedeemRequest", (Action<long, string>)OnRpcRedeemRequest_Server); instance.Register<string>("VD_AckDelivery", (Action<long, string>)OnRpcAckDelivery_Server); instance.Register<ZPackage>("VD_RedeemResult", (Action<long, ZPackage>)OnRpcRedeemResult_Client); instance.Register<ZPackage>("VD_GiveItems", (Action<long, ZPackage>)OnRpcGiveItems_Client); } } private static bool IsFromServer(long sender) { return ZRoutedRpc.instance != null && sender == ZRoutedRpc.instance.GetServerPeerID(); } private static bool IsLocalSender(long sender) { return ZRoutedRpc.instance != null && sender == ZRoutedRpc.instance.m_id; } private static void OnRpcRedeemRequest_Server(long senderId, string code) { if (EnsureServerData() && !string.IsNullOrWhiteSpace(code)) { HandleRedeemServer(senderId, code.Trim()); } } private static bool IsSenderConnected(long senderId) { int result; if (!IsLocalSender(senderId)) { ZNet instance = ZNet.instance; result = ((((instance != null) ? instance.GetPeer(senderId) : null) != null) ? 1 : 0); } else { result = 1; } return (byte)result != 0; } private static void HandleRedeemServer(long senderId, string codeRaw) { //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Expected O, but got Unknown DateTime utcNow = DateTime.UtcNow; if (_peerBlockedUntil.TryGetValue(senderId, out var value) && utcNow < value) { SendResultTo(senderId, false, "$redeem_claim_rate_limited_wait", ((int)Math.Max(1.0, (value - utcNow).TotalSeconds)).ToString()); return; } if (!_attemptsByPeer.TryGetValue(senderId, out var value2)) { value2 = new List<DateTime>(8); _attemptsByPeer[senderId] = value2; } DateTime cutoff = utcNow.AddSeconds(-300.0); value2.RemoveAll((DateTime t) => t < cutoff); if (value2.Count >= 5) { _peerBlockedUntil[senderId] = utcNow.AddSeconds(900.0); SendResultTo(senderId, false, "$redeem_claim_rate_limited_blocked"); return; } value2.Add(utcNow); if (_lastRequestByPeer.TryGetValue(senderId, out var value3) && (utcNow - value3).TotalSeconds < 2.5) { SendResultTo(senderId, false, "$redeem_claim_rate_limited_retry"); return; } _lastRequestByPeer[senderId] = utcNow; string text = codeRaw.Trim(); if (text.Length == 0 || text.Length > 128) { SendResultTo(senderId, false, "$redeem_claim_code_invalid"); return; } TryReloadCodes(); if (!_codes.Codes.TryGetValue(text, out var value4)) { SendResultTo(senderId, false, _recentClaimed.ContainsKey(text) ? "$redeem_claim_code_already_claimed" : "$redeem_claim_code_not_found"); return; } if (!_packages.Packages.TryGetValue(value4.Package, out var value5)) { SendResultTo(senderId, false, "$redeem_claim_package_missing"); return; } if (!IsSenderConnected(senderId)) { SendResultTo(senderId, false, "$redeem_claim_player_disconnected"); return; } if (_inFlightCodes.ContainsKey(text)) { if (!_inFlightExpires.TryGetValue(text, out var value6) || !(utcNow > value6)) { SendResultTo(senderId, false, "$redeem_claim_code_processing"); return; } _inFlightCodes.Remove(text); _inFlightExpires.Remove(text); } _inFlightCodes[text] = senderId; _inFlightExpires[text] = utcNow.AddSeconds(60.0); ZPackage val = new ZPackage(); val.Write(text); val.Write(value5.Items.Count); foreach (ItemDef item in value5.Items) { val.Write(item.Prefab ?? ""); val.Write(item.Amount); val.Write(item.Quality); val.Write(item.Variant); } ZRoutedRpc.instance.InvokeRoutedRPC(senderId, "VD_GiveItems", new object[1] { val }); } private static string GetSenderName(long senderId) { if (IsLocalSender(senderId)) { return ((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : ""; } ZNet instance = ZNet.instance; return ((instance == null) ? null : instance.GetPeer(senderId)?.m_playerName) ?? ""; } private static void OnRpcAckDelivery_Server(long senderId, string code) { if (!IsRedeemServer() || string.IsNullOrWhiteSpace(code)) { return; } code = code.Trim(); if (!_inFlightCodes.TryGetValue(code, out var value) || value != senderId) { SendResultTo(senderId, false, "$redeem_claim_ack_invalid"); return; } _inFlightCodes.Remove(code); _inFlightExpires.Remove(code); if (!_codes.Codes.TryGetValue(code, out var value2)) { SendResultTo(senderId, false, "$redeem_claim_code_invalid"); return; } string package = value2.Package; _codes.Codes.Remove(code); try { SaveCodesAtomic(); } catch (Exception ex) when (IsFileError(ex)) { _codes.Codes[value2.Code] = value2; Log.LogError((object)("Code '" + code + "' was delivered but code.active.yml could not be saved: " + ex.Message)); SendResultTo(senderId, false, "$redeem_claim_persist_failed"); return; } try { AppendClaimedAtomic(code, package, SanitizeField(GetSenderName(senderId), 64)); } catch (Exception ex2) when (IsFileError(ex2)) { Log.LogWarning((object)("Code '" + code + "' was redeemed but could not be written to code.claimed.yml: " + ex2.Message)); } _recentClaimed[code] = DateTime.UtcNow; _attemptsByPeer.Remove(senderId); _peerBlockedUntil.Remove(senderId); SendResultTo(senderId, true, "$redeem_claim_delivered", package); } private static void SendResultTo(long peerId, bool success, string token, params string[] args) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(success); val.Write(token ?? ""); val.Write(args.Length); foreach (string text in args) { val.Write(text ?? ""); } ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "VD_RedeemResult", new object[1] { val }); } } [HarmonyPatch(typeof(Chat), "SendText")] internal static class Redeem_ChatSendText_Patch { [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(1)] private static bool Prefix(string text) { if (string.IsNullOrWhiteSpace(text) || !text.TrimStart(Array.Empty<char>()).StartsWith("redeem", StringComparison.OrdinalIgnoreCase)) { return true; } string[] array = text.Trim().Split(new char[1] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 2 && array[0].Equals("redeem", StringComparison.OrdinalIgnoreCase)) { RedeemPlugin.ClientRequestRedeem(array[1]); } return false; } } [HarmonyPatch(typeof(Terminal), "Awake")] internal static class Redeem_Terminal_Awake_Patch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__1_0; internal void <Postfix>b__1_0(ConsoleEventArgs args) { if (args.Args != null && args.Args.Length >= 2) { string text = string.Join(" ", args.Args.Skip(1)).Trim(); if (!string.IsNullOrEmpty(text)) { RedeemPlugin.ClientRequestRedeem(text); } } } } private static bool _registered; private static void Postfix() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown if (_registered) { return; } _registered = true; if (Terminal.commands.ContainsKey("redeem")) { return; } object obj = <>c.<>9__1_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { if (args.Args != null && args.Args.Length >= 2) { string text = string.Join(" ", args.Args.Skip(1)).Trim(); if (!string.IsNullOrEmpty(text)) { RedeemPlugin.ClientRequestRedeem(text); } } }; <>c.<>9__1_0 = val; obj = (object)val; } new ConsoleCommand("redeem", "redeem <CODE> - Redeems a Redeem package", (ConsoleEvent)obj, false, true, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } [HarmonyPatch(typeof(Game), "Start")] internal static class Redeem_Game_Start_Patch { private static void Postfix() { RedeemPlugin.RegisterRpcs(); } } } namespace LocalizationManager { [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(0)] [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(1)] [PublicAPI] public class Localizer { private static readonly Dictionary<string, Dictionary<string, Func<string>>> PlaceholderProcessors; private static readonly Dictionary<string, Dictionary<string, string>> loadedTexts; private static readonly ConditionalWeakTable<Localization, string> localizationLanguage; private static readonly List<WeakReference<Localization>> localizationObjects; [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(2)] private static BaseUnityPlugin _plugin; private static readonly List<string> fileExtensions; private static BaseUnityPlugin plugin { get { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown if (_plugin == null) { IEnumerable<TypeInfo> source; try { source = Assembly.GetExecutingAssembly().DefinedTypes.ToList(); } catch (ReflectionTypeLoadException ex) { source = from t in ex.Types where t != null select t.GetTypeInfo(); } _plugin = (BaseUnityPlugin)Chainloader.ManagerObject.GetComponent((Type)source.First([<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (TypeInfo t) => t.IsClass && typeof(BaseUnityPlugin).IsAssignableFrom(t))); } return _plugin; } } [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(2)] [method: <e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(2)] [field: <aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(2)] public static event Action OnLocalizationComplete; private static void UpdatePlaceholderText(Localization localization, string key) { localizationLanguage.TryGetValue(localization, out var value); string text = loadedTexts[value][key]; if (PlaceholderProcessors.TryGetValue(key, out var value2)) { text = value2.Aggregate(text, [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (string current, KeyValuePair<string, Func<string>> kv) => current.Replace("{" + kv.Key + "}", kv.Value())); } localization.AddWord(key, text); } public static void AddPlaceholder<T>(string key, string placeholder, ConfigEntry<T> config, [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(new byte[] { 2, 1, 1 })] Func<T, string> convertConfigValue = null) { if (convertConfigValue == null) { convertConfigValue = (T val) => val.ToString(); } if (!PlaceholderProcessors.ContainsKey(key)) { PlaceholderProcessors[key] = new Dictionary<string, Func<string>>(); } config.SettingChanged += [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (object _, EventArgs _) => { UpdatePlaceholder(); }; if (loadedTexts.ContainsKey(Localization.instance.GetSelectedLanguage())) { UpdatePlaceholder(); } void UpdatePlaceholder() { PlaceholderProcessors[key][placeholder] = () => convertConfigValue(config.Value); UpdatePlaceholderText(Localization.instance, key); } } public static void AddText(string key, string text) { List<WeakReference<Localization>> list = new List<WeakReference<Localization>>(); foreach (WeakReference<Localization> localizationObject in localizationObjects) { if (localizationObject.TryGetTarget(out var target)) { Dictionary<string, string> dictionary = loadedTexts[localizationLanguage.GetOrCreateValue(target)]; if (!target.m_translations.ContainsKey(key)) { dictionary[key] = text; target.AddWord(key, text); } } else { list.Add(localizationObject); } } foreach (WeakReference<Localization> item in list) { localizationObjects.Remove(item); } } public static void Load() { _ = plugin; } public static void LoadLocalizationLater() { LoadLocalization(Localization.instance, Localization.instance.GetSelectedLanguage()); } public static void SafeCallLocalizeComplete() { Localizer.OnLocalizationComplete?.Invoke(); } private static void LoadLocalization(Localization __instance, string language) { if (!localizationLanguage.Remove(__instance)) { localizationObjects.Add(new WeakReference<Localization>(__instance)); } localizationLanguage.Add(__instance, language); Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (string item in from f in Directory.GetFiles(Path.GetDirectoryName(Paths.PluginPath), plugin.Info.Metadata.Name + ".*", SearchOption.AllDirectories) where fileExtensions.IndexOf(Path.GetExtension(f)) >= 0 select f) { string[] array = Path.GetFileNameWithoutExtension(item).Split(new char[1] { '.' }); if (array.Length >= 2) { string text = array[1]; if (dictionary.ContainsKey(text)) { Debug.LogWarning((object)("Duplicate key " + text + " found for " + plugin.Info.Metadata.Name + ". The duplicate file found at " + item + " will be skipped.")); } else { dictionary[text] = item; } } } byte[] array2 = LoadTranslationFromAssembly("English"); if (array2 == null) { throw new Exception("Found no English localizations in mod " + plugin.Info.Metadata.Name + ". Expected an embedded resource translations/English.json or translations/English.yml."); } string input = Encoding.UTF8.GetString(array2).TrimStart(new char[1] { '\ufeff' }); Dictionary<string, string> dictionary2 = new DeserializerBuilder().IgnoreFields().Build().Deserialize<Dictionary<string, string>>(input); if (dictionary2 == null) { throw new Exception("Localization for mod " + plugin.Info.Metadata.Name + " failed: Localization file was empty."); } string text2 = null; if (language != "English") { if (dictionary.TryGetValue(language, out var value)) { text2 = File.ReadAllText(value); } else { byte[] array3 = LoadTranslationFromAssembly(language); if (array3 != null) { text2 = Encoding.UTF8.GetString(array3); } } } if (text2 == null && dictionary.TryGetValue("English", out var value2)) { text2 = File.ReadAllText(value2); } if (text2 != null) { text2 = text2.TrimStart(new char[1] { '\ufeff' }); foreach (KeyValuePair<string, string> item2 in new DeserializerBuilder().IgnoreFields().Build().Deserialize<Dictionary<string, string>>(text2) ?? new Dictionary<string, string>()) { dictionary2[item2.Key] = item2.Value; } } loadedTexts[language] = dictionary2; foreach (KeyValuePair<string, string> item3 in dictionary2) { UpdatePlaceholderText(__instance, item3.Key); } } static Localizer() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown PlaceholderProcessors = new Dictionary<string, Dictionary<string, Func<string>>>(); loadedTexts = new Dictionary<string, Dictionary<string, string>>(); localizationLanguage = new ConditionalWeakTable<Localization, string>(); localizationObjects = new List<WeakReference<Localization>>(); fileExtensions = new List<string>(2) { ".json", ".yml" }; Harmony val = new Harmony("org.bepinex.helpers.LocalizationManager"); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Localization), "SetupLanguage", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(Localizer), "LoadLocalization", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "SetupGui", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(Localizer), "LoadLocalizationLater", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(Localizer), "SafeCallLocalizeComplete", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } [return: <aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(2)] private static byte[] LoadTranslationFromAssembly(string language) { foreach (string fileExtension in fileExtensions) { byte[] array = ReadEmbeddedFileBytes("translations." + language + fileExtension); if (array != null) { return array; } } return null; } [<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(2)] public static byte[] ReadEmbeddedFileBytes([<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(1)] string resourceFileName, Assembly containingAssembly = null) { using MemoryStream memoryStream = new MemoryStream(); if ((object)containingAssembly == null) { containingAssembly = Assembly.GetCallingAssembly(); } string text = containingAssembly.GetManifestResourceNames().FirstOrDefault([<e553260e-36b8-4da0-a748-ca6fe78196a3>NullableContext(0)] (string str) => str.EndsWith(resourceFileName, StringComparison.Ordinal)); if (text != null) { containingAssembly.GetManifestResourceStream(text)?.CopyTo(memoryStream); } return (memoryStream.Length == 0L) ? null : memoryStream.ToArray(); } } public static class LocalizationManagerVersion { [<aa5db788-994e-4191-9fac-e846e7f43be2>Nullable(1)] public const string Version = "1.4.1"; } } namespace Microsoft.CodeAnalysis { [<72145300-8df3-4458-8b52-18df488cdfef>Embedded] [CompilerGenerated] internal sealed class <72145300-8df3-4458-8b52-18df488cdfef>EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [<72145300-8df3-4458-8b52-18df488cdfef>Embedded] [CompilerGenerated] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class <5e1b67c9-2d12-43cc-8902-2832717b9520>NullableAttribute : Attribute { public readonly byte[] NullableFlags; public <5e1b67c9-2d12-43cc-8902-2832717b9520>NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public <5e1b67c9-2d12-43cc-8902-2832717b9520>NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [<72145300-8df3-4458-8b52-18df488cdfef>Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class <7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContextAttribute : Attribute { public readonly byte Flag; public <7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContextAttribute(byte P_0) { Flag = P_0; } } [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] [<72145300-8df3-4458-8b52-18df488cdfef>Embedded] [CompilerGenerated] internal sealed class <c36cbae5-7ece-44d4-8af5-2df07b133196>RefSafetyRulesAttribute : Attribute { public readonly int Version; public <c36cbae5-7ece-44d4-8af5-2df07b133196>RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ServerSync { [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] [PublicAPI] internal abstract class OwnConfigEntryBase { [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] public object LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] [PublicAPI] internal class SyncedConfigEntry<[<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(2)] internal abstract class CustomSyncedValueBase { public object LocalBaseValue; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(1)] public readonly string Identifier; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(1)] public readonly Type Type; private object boxedValue; protected bool localIsOwner; public readonly int Priority; public object BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action ValueChanged; [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] [PublicAPI] internal sealed class CustomSyncedValue<[<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] [PublicAPI] internal class ConfigSync { [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] public static ZRpc currentRpc; [HarmonyPrefix] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] internal static class RegisterRPCPatch { [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] private class ParsedConfigs { [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(new byte[] { 1, 1, 2 })] public readonly Dictionary<OwnConfigEntryBase, object> configValues = new Dictionary<OwnConfigEntryBase, object>(); [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(new byte[] { 1, 1, 2 })] public readonly Dictionary<CustomSyncedValueBase, object> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix([<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(new byte[] { 2, 1, 1 })] ref Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] private class PackageEntry { public string section = null; public string key = null; public Type type = null; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] public object value; } [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] public string DisplayName; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] public string CurrentVersion; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] public string MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] private OwnConfigEntryBase lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(new byte[] { 1, 0, 1 })] private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] [method: <7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(2)] [field: <5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] public event Action<bool> SourceOfTruthChanged; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] [method: <7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(2)] [field: <5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] private event Action lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<[<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] T>(ConfigEntry<T> configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (object _, EventArgs _) => { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(0L, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<[<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (object _, EventArgs _) => { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(0L, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(([<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(new byte[] { 0, 1 })] KeyValuePair<long, string> kv) => { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out var value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (CustomSyncedValueBase c) => c.Identifier, [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { ConfigSync configSync = configSyncs.FirstOrDefault([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (ConfigSync cs) => cs.allConfigs.Contains(config)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config.SynchronizedConfig || config.LocalBaseValue == null || (!configSync.IsLocked && (config != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator<bool> distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer.m_socket.IsConnected()) { ZPackage fragmentedPackage = new ZPackage(); fragmentedPackage.Write((byte)2); fragmentedPackage.Write(packageIdentifier); fragmentedPackage.Write(fragment); fragmentedPackage.Write(fragments); fragmentedPackage.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(fragmentedPackage); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg }); } } IEnumerable<bool> waitForQueue() { float timeout = Time.time + 30f; while (peer.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer.m_uid} after 30 seconds config sending timeout"); peer.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty<object>().GetEnumerator(); } List<ZNetPeer> list = (List<ZNetPeer>)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != 0) { list = list.Where([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List<ZNetPeer> peers, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] rawData = package.GetArray(); if (rawData != null && rawData.LongLength > 10000) { ZPackage compressedPackage = new ZPackage(); compressedPackage.Write((byte)4); MemoryStream output = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(output, CompressionLevel.Optimal)) { deflateStream.Write(rawData, 0, rawData.Length); } compressedPackage.Write(output.ToArray()); package = compressedPackage; } List<IEnumerator<bool>> writers = (from p in peers where p.IsReady() select distributeConfigToPeers(p, package)).ToList(); writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); } } private void Broadcast(long target, params ConfigEntryBase[] configs) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(configs); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private void Broadcast(long target, params CustomSyncedValueBase[] customValues) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(null, customValues); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } [return: <5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] private static OwnConfigEntryBase configData(ConfigEntryBase config) { return config.Description.Tags?.OfType<OwnConfigEntryBase>().SingleOrDefault(); } [return: <5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(new byte[] { 2, 1 })] public static SyncedConfigEntry<T> ConfigData<[<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] T>(ConfigEntry<T> config) { return ((ConfigEntryBase)config).Description.Tags?.OfType<SyncedConfigEntry<T>>().SingleOrDefault(); } private static T configAttribute<[<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] T>(ConfigEntryBase config) { return config.Description.Tags.OfType<T>().First(); } private static Type configType(ConfigEntryBase config) { return configType(config.SettingType); } private static Type configType(Type type) { return type.IsEnum ? Enum.GetUnderlyingType(type) : type; } private static ZPackage ConfigsToPackage([<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(new byte[] { 2, 1 })] IEnumerable<ConfigEntryBase> configs = null, [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(new byte[] { 2, 1 })] IEnumerable<CustomSyncedValueBase> customValues = null, [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(new byte[] { 2, 1 })] IEnumerable<PackageEntry> packageEntries = null, bool partial = true) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List<ConfigEntryBase> list = configs?.Where([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List<ConfigEntryBase>(); List<CustomSyncedValueBase> list2 = customValues?.ToList() ?? new List<CustomSyncedValueBase>(); ZPackage val = new ZPackage(); val.Write((byte)(partial ? 1 : 0)); val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0)); foreach (PackageEntry item in packageEntries ?? Array.Empty<PackageEntry>()) { AddEntryToPackage(val, item); } foreach (CustomSyncedValueBase item2 in list2) { AddEntryToPackage(val, new PackageEntry { section = "Internal", key = item2.Identifier, type = item2.Type, value = item2.BoxedValue }); } foreach (ConfigEntryBase item3 in list) { AddEntryToPackage(val, new PackageEntry { section = item3.Definition.Section, key = item3.Definition.Key, type = configType(item3), value = item3.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { package.Write(entry.section); package.Write(entry.key); package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type)); AddValueToZPackage(package, entry.value); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void AddValueToZPackage(ZPackage package, [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] object value) { Type type = value?.GetType(); if (value is Enum) { value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture); } else { if (value is ICollection collection) { package.Write(collection.Count); { foreach (object item in collection) { AddValueToZPackage(package, item); } return; } } if ((object)type != null && type.IsValueType && !type.IsPrimitive) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); package.Write(fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { package.Write(GetZPackageTypeString(fieldInfo.FieldType)); AddValueToZPackage(package, fieldInfo.GetValue(value)); } return; } } ZRpc.Serialize(new object[1] { value }, ref package); } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = package.ReadInt(); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = package.ReadString(); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = package.ReadInt(); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments); FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj = ReadValueWithTypeFromZPackage(package, type2); dictionary.Add(field.GetValue(obj), field2.GetValue(obj)); } return dictionary; } if (type != typeof(List<string>) && type.IsGenericType) { Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]); if ((object)type3 != null && type3.IsAssignableFrom(type)) { int num3 = package.ReadInt(); object obj2 = Activator.CreateInstance(type); MethodInfo method = type3.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) }); } return obj2; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type); List<object> source = new List<object>(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [HarmonyPatch] [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(0)] [<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(1)] [PublicAPI] internal class VersionCheck { private static readonly HashSet<VersionCheck> versionChecks; private static readonly Dictionary<string, string> notProcessedNames; public string Name; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] private string displayName; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] private string currentVersion; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] private string minimumRequiredVersion; public bool ModRequired = true; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] private string ReceivedCurrentVersion; [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] private string ReceivedMinimumRequiredVersion; private readonly List<ZRpc> ValidatedClients = new List<ZRpc>(); [<5e1b67c9-2d12-43cc-8902-2832717b9520>Nullable(2)] private ConfigSync ConfigSync; public string DisplayName { get { return displayName ?? Name; } set { displayName = value; } } public string CurrentVersion { get { return currentVersion ?? "0.0.0"; } set { currentVersion = value; } } public string MinimumRequiredVersion { get { return minimumRequiredVersion ?? (ModRequired ? CurrentVersion : "0.0.0"); } set { minimumRequiredVersion = value; } } private static void PatchServerSync() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null)); if (patchInfo != null && patchInfo.Postfixes.Count([<7b5bd9d6-72e2-4ba8-b7ce-1b5f10527b0b>NullableContext(0)] (Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0) { return; } Harmony val = new Harmony("org.bep