using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using JetBrains.Annotations; using LitJson2; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("BalrondExtraSkills")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BalrondExtraSkills")] [assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("f6d55ca3-92c3-4d6a-bf40-9e8f52c5a331")] [assembly: AssemblyFileVersion("0.1.4.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.4.0")] [module: UnverifiableCode] public static class BalrondHashCompat { private static readonly MethodInfo _getStableHashCodeStringBool; private static readonly MethodInfo _getStableHashCodeString; private static readonly bool _initialized; static BalrondHashCompat() { try { Type typeFromHandle = typeof(StringExtensionMethods); _getStableHashCodeStringBool = typeFromHandle.GetMethod("GetStableHashCode", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(bool) }, null); _getStableHashCodeString = typeFromHandle.GetMethod("GetStableHashCode", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null); _initialized = true; } catch { _initialized = false; } } public static int StableHash(string value) { if (value == null) { return 0; } try { if (_getStableHashCodeStringBool != null) { return (int)_getStableHashCodeStringBool.Invoke(null, new object[2] { value, false }); } if (_getStableHashCodeString != null) { return (int)_getStableHashCodeString.Invoke(null, new object[1] { value }); } } catch { } return FallbackStableHash(value); } private static int FallbackStableHash(string value) { int num = 5381; int num2 = num; for (int i = 0; i < value.Length; i += 2) { num = ((num << 5) + num) ^ value[i]; if (i == value.Length - 1) { break; } num2 = ((num2 << 5) + num2) ^ value[i + 1]; } return num + num2 * 1566083941; } } namespace BalrondExtraSkills { public static class BalrondDamageHub { private sealed class Box { public T Value; public Box(T v) { Value = v; } } [HarmonyPatch(typeof(Character), "ApplyDamage")] private static class Character_ApplyDamage_Patch { [HarmonyPrefix] private static void Prefix(Character __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { float health = ((Character)val).GetHealth(); s_preHealth.Remove(__instance); s_preHealth.Add(__instance, new Box(health)); } } [HarmonyPostfix] private static void Postfix(Character __instance, HitData hit) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && s_preHealth.TryGetValue(__instance, out var value)) { float value2 = value.Value; float health = ((Character)val).GetHealth(); s_preHealth.Remove(__instance); float num = value2 - health; if (!(num <= 0f)) { FortitudeSkill.OnDamageTaken(val, hit, num); ArcanaSkill.OnDamageTaken(val, hit, num, wasBlocking: false, wasPerfectBlock: false); } } } } private static readonly ConditionalWeakTable> s_preHealth = new ConditionalWeakTable>(); internal static bool IsElemental(HitData hit) { if (hit == null) { return false; } return hit.m_damage.m_fire > 0f || hit.m_damage.m_frost > 0f || hit.m_damage.m_lightning > 0f; } } internal static class BalrondIconUtil { private static Sprite _placeholder; internal static Sprite Placeholder { get { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_placeholder != (Object)null) { return _placeholder; } try { Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, new Color(0f, 0f, 0f, 0f)); val.Apply(false, true); _placeholder = Sprite.Create(val, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f)); ((Object)_placeholder).name = "BalrondExtraSkills_PlaceholderIcon"; } catch { _placeholder = Sprite.Create(Texture2D.blackTexture, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f)); } return _placeholder; } } } internal static class BalrondSkillLevelupMessagePatch { [HarmonyPatch(typeof(Player), "Message", new Type[] { typeof(MessageType), typeof(string), typeof(int), typeof(Sprite) })] private static class Player_Message_SkillupTokenFix { [HarmonyPrefix] private static void Prefix(ref string msg) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(msg) || !msg.StartsWith("$msg_skillup")) { return; } int num = msg.IndexOf("$skill_"); if (num < 0) { return; } int i = num + 7; int num2 = i; for (; i < msg.Length; i++) { char c = msg[i]; if (c < '0' || c > '9') { break; } } if (i == num2) { return; } string s = msg.Substring(num2, i - num2); if (!int.TryParse(s, out var result)) { return; } SkillType key = (SkillType)result; if (BalrondSkillSystem.Registered.TryGetValue(key, out var value) && value != null) { PropertyInfo propertyInfo = AccessTools.Property(value.GetType(), "NameToken"); string text; if (propertyInfo != null) { text = propertyInfo.GetValue(value, null) as string; } else { PropertyInfo propertyInfo2 = AccessTools.Property(value.GetType(), "NameTokenKey"); text = ((propertyInfo2 != null) ? (propertyInfo2.GetValue(value, null) as string) : null); } if (string.IsNullOrEmpty(text)) { text = "$tag_skill_" + (value.InternalKey ?? "skill") + "_bal"; } msg = msg.Substring(0, num) + text + msg.Substring(i); } } } } public static class BalrondSkillSystem { [HarmonyPatch(typeof(SkillsDialog), "Setup")] private static class SkillsDialog_Setup_CustomNames_Patch { [HarmonyPostfix] private static void Postfix(SkillsDialog __instance) { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)__instance == (Object)null) { return; } FieldInfo fieldInfo = AccessTools.Field(typeof(SkillsDialog), "m_elements"); if (fieldInfo == null || !(fieldInfo.GetValue(__instance) is List list)) { return; } foreach (GameObject item in list) { if ((Object)(object)item == (Object)null || !item.activeInHierarchy) { continue; } Transform val = Utils.FindChild(item.transform, "name", (IterativeSearchType)0); if ((Object)(object)val == (Object)null) { continue; } TMP_Text component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null) { continue; } int num = TryParseBracketedSkillId(component.text); if (num == -1) { continue; } SkillType key = (SkillType)num; if (!Registered.TryGetValue(key, out var value) || value == null) { continue; } string text = ((Localization.instance != null) ? Localization.instance.Localize(value.NameToken) : value.NameToken); if (!string.IsNullOrEmpty(text) && text.StartsWith("[") && text.EndsWith("]")) { text = value.SkillNameEnglish; } component.text = text; Transform val2 = Utils.FindChild(item.transform, "icon", (IterativeSearchType)0); if ((Object)(object)val2 != (Object)null) { Image component2 = ((Component)val2).GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.sprite = value.Icon; } } } } catch { } } private static int TryParseBracketedSkillId(string text) { if (string.IsNullOrEmpty(text)) { return -1; } int num = text.IndexOf("[skill_", StringComparison.OrdinalIgnoreCase); if (num < 0) { return -1; } int num2 = num + "[skill_".Length; int num3 = text.IndexOf(']', num2); if (num3 < 0) { return -1; } string s = text.Substring(num2, num3 - num2); int result; return int.TryParse(s, out result) ? result : (-1); } } [HarmonyPatch(typeof(Skills), "IsSkillValid")] private static class Skills_IsSkillValid_Patch { [HarmonyPrefix] private static bool Prefix(SkillType type, ref bool __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (!IsCustomSkill(type)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(Skills), "GetSkillDef")] private static class Skills_GetSkillDef_Patch { [HarmonyPostfix] private static void Postfix(Skills __instance, SkillType type, ref SkillDef __result) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown if (__result == null && Registered.TryGetValue(type, out var value)) { __result = new SkillDef { m_skill = type, m_icon = value.Icon, m_description = value.DescToken, m_increseStep = 1f }; EnsureDefsInSkillsList(__instance); } } } [HarmonyPatch(typeof(Skills), "Awake")] private static class Skills_Awake_Patch { [HarmonyPostfix] private static void Postfix(Skills __instance) { try { EnsureDefsInSkillsList(__instance); } catch { } } } [HarmonyPatch(typeof(Player), "Awake")] private static class Player_Awake_SeedCustomSkills_Patch { [HarmonyPostfix] private static void Postfix(Player __instance) { EnsureAllSeeded(__instance); } } [HarmonyPatch(typeof(Player), "Load")] private static class Player_Load_SeedCustomSkills_Patch { [HarmonyPostfix] private static void Postfix(Player __instance) { EnsureAllSeeded(__instance); } } [HarmonyPatch(typeof(Skills), "CheatRaiseSkill")] private static class Skills_CheatRaiseSkill_Patch { [HarmonyPrefix] private static bool Prefix(Skills __instance, string name, float value, bool showMessage) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Expected O, but got Unknown //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) try { if (string.IsNullOrEmpty(name)) { return true; } string key = SanitizeKey(name); if (!ByAlias.TryGetValue(key, out var value2)) { return true; } if (!Registered.TryGetValue(value2, out var value3)) { return true; } object? obj = FI_Skills_m_player?.GetValue(__instance); Player val = (Player)((obj is Player) ? obj : null); if ((Object)(object)val != (Object)null) { EnsureDefsInSkillsList(__instance, forceRefresh: true); EnsureSkillSeeded(__instance, value2); } if (!(FI_Skills_m_skillData?.GetValue(__instance) is Dictionary dictionary)) { return false; } if (!dictionary.TryGetValue(value2, out var value4) || value4 == null) { if (MI_Skills_GetSkill != null) { MI_Skills_GetSkill.Invoke(__instance, new object[1] { value2 }); if (!(FI_Skills_m_skillData.GetValue(__instance) is Dictionary dictionary2) || !dictionary2.TryGetValue(value2, out value4) || value4 == null) { return false; } } else { SkillDef skillDef = __instance.GetSkillDef(value2); if (skillDef == null) { return false; } value4 = (dictionary[value2] = new Skill(skillDef)); FI_Skills_m_skillData.SetValue(__instance, dictionary); } } value4.m_level = Mathf.Clamp(value4.m_level + value, 0f, 100f); value4.m_accumulator = 0f; if (showMessage && (Object)(object)val != (Object)null) { ((Character)val).Message((MessageType)1, $"Skill increased {value3.SkillNameEnglish}: {(int)value4.m_level}", 0, value3.Icon); } Console instance = Console.instance; if (instance != null) { instance.Print($"Skill {value3.SkillNameEnglish} = {(int)value4.m_level}"); } return false; } catch { return true; } } } [HarmonyPatch(typeof(Skills), "CheatResetSkill")] private static class Skills_CheatResetSkill_Patch { [HarmonyPrefix] private static bool Prefix(Skills __instance, string name) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) try { if (string.IsNullOrEmpty(name)) { return true; } string key = SanitizeKey(name); if (!ByAlias.TryGetValue(key, out var value)) { return true; } if (!Registered.TryGetValue(value, out var value2)) { return true; } __instance.ResetSkill(value); Console instance = Console.instance; if (instance != null) { instance.Print("Skill " + value2.SkillNameEnglish + " reset"); } return false; } catch { return true; } } } public static readonly Dictionary Registered = new Dictionary(); private static readonly Dictionary ByAlias = new Dictionary(); private static readonly FieldInfo FI_Skills_m_skillData = AccessTools.Field(typeof(Skills), "m_skillData"); private static readonly FieldInfo FI_Skills_m_player = AccessTools.Field(typeof(Skills), "m_player"); private static readonly MethodInfo MI_Skills_GetSkill = AccessTools.Method(typeof(Skills), "GetSkill", new Type[1] { typeof(SkillType) }, (Type[])null); public static void RegisterSkill(BaseSkill skill) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected I4, but got Unknown //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected I4, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected I4, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) if (skill == null) { throw new ArgumentNullException("skill"); } string text = SanitizeKey(skill.InternalKey); if (string.IsNullOrEmpty(text)) { text = SanitizeKey(skill.SkillNameEnglish); } if ((int)skill.SkillType == 0) { skill.SkillType = GetFixedSkillType(skill); } if (Registered.TryGetValue(skill.SkillType, out var value)) { throw new InvalidOperationException($"[BalrondExtraSkills] Skill ID collision: {(int)skill.SkillType} is already used by '{value.InternalKey}', cannot register '{skill.InternalKey}'."); } Registered.Add(skill.SkillType, skill); AddAlias(text, skill.SkillType); AddAlias(skill.SkillNameEnglish, skill.SkillType); AddAlias(skill.NameToken, skill.SkillType); AddAlias("skill_" + (int)skill.SkillType, skill.SkillType); Debug.Log((object)$"[BalrondExtraSkills] Registered skill: {skill.SkillNameEnglish} (internal: {text}, type {(int)skill.SkillType})"); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { EnsureDefsInSkillsList(((Character)localPlayer).GetSkills()); EnsureSkillSeeded(((Character)localPlayer).GetSkills(), skill.SkillType); } } public static bool IsCustomSkill(SkillType type) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return Registered.ContainsKey(type); } internal static bool IsSkillEnabled(SkillType type) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (!Registered.TryGetValue(type, out var value) || value == null) { return true; } return Launch.Settings?.IsSkillEnabled(value.FixedSkillId) ?? true; } internal static float GetSkillGainMultiplier(SkillType type) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (!Registered.TryGetValue(type, out var value) || value == null) { return 1f; } ExtraSkillsConfig settings = Launch.Settings; if (settings == null) { return 1f; } return Mathf.Max(0f, settings.GetSkillGainMultiplier(value.FixedSkillId)); } internal static float GetSkillEffectMultiplier(SkillType type) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (!Registered.TryGetValue(type, out var value) || value == null) { return 1f; } ExtraSkillsConfig settings = Launch.Settings; if (settings == null) { return 1f; } return Mathf.Max(0f, settings.GetSkillEffectMultiplier(value.FixedSkillId)); } public static void RaiseSkill(Player player, SkillType type, float amount) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || amount <= 0f) { return; } if (IsCustomSkill(type)) { if (!IsSkillEnabled(type)) { return; } float skillGainMultiplier = GetSkillGainMultiplier(type); if (skillGainMultiplier <= 0f) { return; } amount *= skillGainMultiplier; if (amount <= 0f || float.IsNaN(amount) || float.IsInfinity(amount)) { return; } } Skills skills = ((Character)player).GetSkills(); if (!((Object)(object)skills == (Object)null)) { EnsureDefsInSkillsList(skills); EnsureSkillSeeded(skills, type); skills.RaiseSkill(type, amount); } } public static void NotifyAssetsReady() { try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { EnsureDefsInSkillsList(((Character)localPlayer).GetSkills(), forceRefresh: true); } Player[] array = Object.FindObjectsOfType(); foreach (Player val in array) { if (!((Object)(object)val == (Object)null)) { EnsureDefsInSkillsList(((Character)val).GetSkills(), forceRefresh: true); } } } catch { } } private static SkillType GetFixedSkillType(BaseSkill skill) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) int fixedSkillId = skill.FixedSkillId; if (fixedSkillId < 10000 || fixedSkillId > 39999) { Debug.LogWarning((object)$"[BalrondExtraSkills] Skill '{skill.InternalKey}' uses ID outside recommended range 10000-39999: {fixedSkillId}"); } return (SkillType)fixedSkillId; } private static void AddAlias(string raw, SkillType type) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) string text = SanitizeKey(raw); if (string.IsNullOrEmpty(text)) { return; } if (ByAlias.TryGetValue(text, out var value)) { if (value != type) { Debug.LogWarning((object)("[BalrondExtraSkills] Alias collision for '" + raw + "' -> '" + text + "'")); } } else { ByAlias.Add(text, type); } } private static string SanitizeKey(string s) { if (string.IsNullOrEmpty(s)) { return ""; } s = s.Trim().ToLowerInvariant(); s = s.Replace("$", ""); s = s.Replace(" ", ""); s = s.Replace("-", ""); s = s.Replace("_", ""); return s; } private static void EnsureDefsInSkillsList(Skills skills, bool forceRefresh = false) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown if ((Object)(object)skills == (Object)null) { return; } List skills2 = skills.m_skills; if (skills2 == null) { return; } foreach (KeyValuePair item in Registered) { SkillType key = item.Key; BaseSkill value = item.Value; SkillDef val = null; for (int i = 0; i < skills2.Count; i++) { SkillDef val2 = skills2[i]; if (val2 != null && val2.m_skill == key) { val = val2; break; } } if (val == null) { skills2.Add(new SkillDef { m_skill = key, m_icon = value.Icon, m_description = value.DescToken, m_increseStep = 1f }); } else if (forceRefresh) { val.m_icon = value.Icon; val.m_description = value.DescToken; } } } private static void EnsureSkillSeeded(Skills skills, SkillType type) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown if ((Object)(object)skills == (Object)null || FI_Skills_m_skillData == null) { return; } try { if (!(FI_Skills_m_skillData.GetValue(skills) is Dictionary dictionary) || dictionary.ContainsKey(type)) { return; } if (MI_Skills_GetSkill != null) { MI_Skills_GetSkill.Invoke(skills, new object[1] { type }); return; } SkillDef skillDef = skills.GetSkillDef(type); if (skillDef != null) { dictionary[type] = new Skill(skillDef); FI_Skills_m_skillData.SetValue(skills, dictionary); } } catch { } } private static void EnsureAllSeeded(Player player) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return; } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null) { return; } EnsureDefsInSkillsList(skills); foreach (KeyValuePair item in Registered) { EnsureSkillSeeded(skills, item.Key); } } } public class BalrondTranslator { public static Dictionary> translations = new Dictionary>(); public static Dictionary getLanguage(string language) { Dictionary result = null; try { result = translations[language]; } catch (Exception) { } return result; } } internal static class PickaxeStaminaReduction { internal static class Patches { [HarmonyPatch] private static class Attack_GetAttackStamina_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Attack), "GetAttackStamina", (Type[])null, (Type[])null); } private static bool Prepare() { return TargetMethod() != null; } [HarmonyPostfix] private static void Postfix(Attack __instance, ref float __result) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Invalid comparison between Unknown and I4 if (__instance == null || __result <= 0f) { return; } object? obj = FI_AttackCharacter?.GetValue(__instance); Humanoid val = (Humanoid)((obj is Humanoid) ? obj : null); Player val2 = (Player)(object)((val is Player) ? val : null); if ((Object)(object)val2 == (Object)null || (Object)(object)val2 != (Object)(object)Player.m_localPlayer) { return; } object? obj2 = FI_AttackWeapon?.GetValue(__instance); ItemData val3 = (ItemData)((obj2 is ItemData) ? obj2 : null); if (val3?.m_shared != null && (int)val3.m_shared.m_skillType == 12) { float pickaxeAttackCostMultiplier = GetPickaxeAttackCostMultiplier(val2); __result *= pickaxeAttackCostMultiplier; if (__result < 0f) { __result = 0f; } } } } [HarmonyPatch(typeof(Player), "UseStamina")] private static class Player_UseStamina_Fallback_Patch { private static bool _useFallback; private static bool _initialized; private static void EnsureInit() { if (!_initialized) { _initialized = true; _useFallback = AccessTools.Method(typeof(Attack), "GetAttackStamina", (Type[])null, (Type[])null) == null; } } [HarmonyPrefix] private static void Prefix(Player __instance, ref float v) { EnsureInit(); if (_useFallback && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !(v <= 0f)) { } } } private static readonly FieldInfo FI_AttackCharacter = AccessTools.Field(typeof(Attack), "m_character"); private static readonly FieldInfo FI_AttackWeapon = AccessTools.Field(typeof(Attack), "m_weapon"); } private static float GetPickaxeAttackCostMultiplier(Player player) { if ((Object)(object)player == (Object)null) { return 1f; } ExtraSkillsConfig settings = Launch.Settings; if (settings != null && !settings.PickaxeStaminaReductionEnabled.Value) { return 1f; } Skills skills = ((Character)player).GetSkills(); float num = ((skills != null) ? skills.GetSkillLevel((SkillType)12) : 0f); if (num <= 0f) { return 1f; } float num2 = Mathf.Clamp01(num / 100f); float num3 = settings?.PickaxeStaminaReductionMultiplier.Value ?? 1f; float num4 = settings?.PickaxeMaxReductionAt100.Value ?? 0.25f; float num5 = settings?.PickaxeMinCostMultiplier.Value ?? 0.2f; float num6 = num2 * Mathf.Clamp01(num4) * Mathf.Max(0f, num3); float num7 = 1f - num6; if (num7 < Mathf.Clamp01(num5)) { num7 = Mathf.Clamp01(num5); } return num7; } } public abstract class BaseSkill { public readonly int FixedSkillId; public readonly string InternalKey; public readonly string SkillNameEnglish; public readonly string DescriptionEnglish; private Sprite _icon; private readonly string _iconAssetName; public SkillType SkillType { get; internal set; } = (SkillType)0; public Sprite Icon => ((Object)(object)_icon != (Object)null) ? _icon : BalrondIconUtil.Placeholder; public string IconAssetName => _iconAssetName; public string NameToken => "$tag_skill_" + InternalKey + "_bal"; public string DescToken => "$tag_skill_" + InternalKey + "_bal_description"; protected BaseSkill(int fixedSkillId, string internalKey, string englishName, string englishDescription, string iconAssetName) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) if (fixedSkillId <= 0) { throw new ArgumentOutOfRangeException("fixedSkillId", "Skill ID must be greater than 0."); } FixedSkillId = fixedSkillId; InternalKey = internalKey ?? throw new ArgumentNullException("internalKey"); SkillNameEnglish = englishName ?? throw new ArgumentNullException("englishName"); DescriptionEnglish = englishDescription ?? ""; _iconAssetName = iconAssetName ?? ""; if (!string.IsNullOrEmpty(_iconAssetName)) { _icon = ModResourceLoader.skillIcons.Find((Sprite x) => ((Object)x).name == _iconAssetName); } try { BalrondSkillSystem.RegisterSkill(this); } catch (Exception arg) { Debug.LogWarning((object)$"[BalrondExtraSkills] Failed to auto-register skill '{englishName}': {arg}"); } } public void SetIcon(Sprite icon) { if ((Object)(object)icon != (Object)null) { _icon = icon; } } } public class DatabaseAddMethods { public void AddItems(List items) { foreach (GameObject item in items) { AddItem(item); } } public void AddRecipes(List recipes) { foreach (Recipe recipe in recipes) { AddRecipe(recipe); } } public void AddStatuseffects(List statusEffects) { foreach (StatusEffect statusEffect in statusEffects) { AddStatus(statusEffect); } } private bool IsObjectDBValid() { return (Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_items.Count != 0 && ObjectDB.instance.m_recipes.Count != 0 && (Object)(object)ObjectDB.instance.GetItemPrefab("Amber") != (Object)null; } private void AddStatus(StatusEffect status) { if (!IsObjectDBValid()) { return; } if ((Object)(object)status != (Object)null) { if ((Object)(object)ObjectDB.instance.GetStatusEffect(status.m_nameHash) == (Object)null) { ObjectDB.instance.m_StatusEffects.Add(status); } else { Debug.Log((object)(Launch.projectName + ": " + ((Object)status).name + " - Status already in the game")); } } else { Debug.LogError((object)(Launch.projectName + ": " + ((Object)status).name + " - Status not found")); } } private void AddRecipe(Recipe recipe) { if (!IsObjectDBValid()) { return; } if ((Object)(object)recipe != (Object)null) { if ((Object)(object)ObjectDB.instance.m_recipes.Find((Recipe x) => ((Object)x).name == ((Object)recipe).name) == (Object)null) { if ((Object)(object)recipe.m_item != (Object)null) { ObjectDB.instance.m_recipes.Add(recipe); } } else { Debug.Log((object)(Launch.projectName + ": " + ((Object)recipe).name + " - Recipe with this name already in the Game")); } } else { Debug.LogError((object)(Launch.projectName + ": " + ((Object)recipe).name + " - Recipe not found")); } } private void AddItem(GameObject newPrefab) { if (!IsObjectDBValid()) { return; } ItemDrop component = newPrefab.GetComponent(); if ((Object)(object)component != (Object)null) { if ((Object)(object)ObjectDB.instance.GetItemPrefab(((Object)newPrefab).name) == (Object)null) { ObjectDB.instance.m_items.Add(newPrefab); Dictionary dictionary = (Dictionary)typeof(ObjectDB).GetField("m_itemByHash", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(ObjectDB.instance); dictionary[((Object)newPrefab).name.GetHashCode()] = newPrefab; } else { Debug.Log((object)(Launch.projectName + ": " + ((Object)newPrefab).name + " - ItemDrop already exist")); } } else { Debug.LogError((object)(Launch.projectName + ": " + ((Object)newPrefab).name + " - ItemDrop not found on prefab")); } } } [Serializable] public class MappedEffectList { public List pieceEffect; public List destroyedEffects; public List hitEffects; public List switchEffect; public List blockEffect; public List equipEffect; public List hitEffect; public List hitTerrainEffect; public List holdStartEffect; public List startEffect; public List trailStartEffect; public List triggerEffect; public List unequipEffect; public EffectList createEffectListFromInfo(List list) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown EffectList val = new EffectList(); List list2 = new List(); val.m_effectPrefabs = list2.ToArray(); return val; } private EffectData createEffectData(EffectInfo info) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown EffectData val = new EffectData(); val.m_inheritParentRotation = info.inheritRotation; val.m_multiplyParentVisualScale = info.multiplyScale; val.m_childTransform = info.childName; val.m_inheritParentScale = info.inheritScale; val.m_variant = info.variant; val.m_scale = info.scale; val.m_attach = info.attach; val.m_follow = info.follow; val.m_prefab = ZNetScene.instance.m_prefabs.Find((GameObject x) => ((Object)x).name == info.name); return val; } } [Serializable] public struct EffectInfo { public string name; public bool enabled; public int variant; public bool attach; public bool follow; public bool inheritRotation; public bool inheritScale; public bool multiplyScale; public bool radnomRotation; public bool scale; public string childName; public EffectInfo(string name, bool enabled = true, int variant = -1, bool attach = false, bool follow = false, bool inheritRotation = false, bool inheritScale = false, bool multiplyScale = false, bool radnomRotation = false, bool scale = false, string childName = null) { this.name = name; this.enabled = enabled; this.variant = variant; this.attach = attach; this.follow = follow; this.inheritRotation = inheritRotation; this.inheritScale = inheritScale; this.multiplyScale = multiplyScale; this.radnomRotation = radnomRotation; this.scale = scale; this.childName = childName; } } public sealed class ModResourceLoader { public AssetBundle assetBundle; public readonly List itemPrefabs = new List(); public readonly List otherPrefabs = new List(); public static List skillIcons = new List(); public void loadAssets() { assetBundle = GetAssetBundleFromResources("balrondextraskills"); if ((Object)(object)assetBundle == (Object)null) { Debug.LogWarning((object)(Launch.projectName + " AssetBundle not found (embedded resource endswith 'BalrondExtraSkills').")); return; } string basePath = "Assets/Custom/BalrondExtraSkills/"; LoadSkillIcons(); loadItems(basePath); loadOther(basePath); } public void AddPrefabsToZnetScene(ZNetScene zNetScene) { if (!((Object)(object)zNetScene == (Object)null)) { validateAddedPrefabs(itemPrefabs, zNetScene); validateAddedPrefabs(otherPrefabs, zNetScene); zNetScene.m_prefabs.RemoveAll((GameObject x) => (Object)(object)x == (Object)null); } } public void AddItemsToObjectDB() { if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items == null) { return; } for (int i = 0; i < itemPrefabs.Count; i++) { GameObject go = itemPrefabs[i]; if (!((Object)(object)go == (Object)null) && !((Object)(object)ObjectDB.instance.m_items.Find((GameObject x) => (Object)(object)x != (Object)null && ((Object)x).name == ((Object)go).name) != (Object)null)) { ObjectDB.instance.m_items.Add(go); } } } private void loadItems(string basePath) { string mainPath = basePath + "Items/"; string[] nameList = new string[0]; addNewPrefabToCollection(nameList, mainPath, itemPrefabs, "item", requireZNetView: false); } private void loadOther(string basePath) { string mainPath = basePath + "Other/"; string[] nameList = new string[0]; addNewPrefabToCollection(nameList, mainPath, otherPrefabs, "other", requireZNetView: false); } private void validateAddedPrefabs(List list, ZNetScene zNetScene) { if (list == null || (Object)(object)zNetScene == (Object)null) { return; } for (int i = 0; i < list.Count; i++) { GameObject val = list[i]; if (!((Object)(object)val == (Object)null)) { int key = BalrondHashCompat.StableHash(((Object)val).name); if (zNetScene.m_namedPrefabs.ContainsKey(key)) { Debug.LogWarning((object)("DUPLICATE: " + ((Object)val).name)); } else { zNetScene.m_prefabs.Add(val); } } } } private void addNewPrefabToCollection(string[] nameList, string mainPath, List prefabList, string typeName, bool requireZNetView) { if ((Object)(object)assetBundle == (Object)null || nameList == null || nameList.Length == 0) { return; } foreach (string text in nameList) { if (!string.IsNullOrEmpty(text)) { GameObject val = assetBundle.LoadAsset(mainPath + text + ".prefab"); if ((Object)(object)val == (Object)null) { Debug.LogWarning((object)("Could not find " + typeName + " with name: " + text + " at " + mainPath)); } else if (requireZNetView && (Object)(object)val.GetComponent() == (Object)null) { Debug.LogWarning((object)("Prefab has no ZNetView (skipped): " + text)); } else { ShaderReplacment.Replace(val); prefabList.Add(val); } } } } public void LoadSkillIcons() { skillIcons.Clear(); if ((Object)(object)assetBundle == (Object)null) { return; } try { Sprite[] array = assetBundle.LoadAllAssets(); if (array == null || array.Length == 0) { Debug.LogWarning((object)(Launch.projectName + " No Sprite assets found in assetbundle.")); return; } foreach (Sprite val in array) { if (!((Object)(object)val == (Object)null)) { skillIcons.Add(val); } } } catch (Exception ex) { Debug.LogWarning((object)(Launch.projectName + " LoadSkillIcons failed: " + ex)); } } public static Sprite GetSkillIconByName(string iconName) { if (string.IsNullOrEmpty(iconName)) { return null; } for (int i = 0; i < skillIcons.Count; i++) { Sprite val = skillIcons[i]; if ((Object)(object)val != (Object)null && ((Object)val).name == iconName) { return val; } } return null; } private static AssetBundle GetAssetBundleFromResources(string filenameSuffix) { Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = null; string[] manifestResourceNames = executingAssembly.GetManifestResourceNames(); foreach (string text2 in manifestResourceNames) { if (text2.EndsWith(filenameSuffix, StringComparison.OrdinalIgnoreCase)) { text = text2; break; } } if (text == null) { return null; } using Stream stream = executingAssembly.GetManifestResourceStream(text); if (stream == null) { return null; } return AssetBundle.LoadFromStream(stream); } } public class ShaderReplacment { public static List prefabsToReplaceShader = new List(); public static List materialsInPrefabs = new List(); public string[] shaderlist = new string[49] { "Custom/AlphaParticle", "Custom/Blob", "Custom/Bonemass", "Custom/Clouds", "Custom/Creature", "Custom/Decal", "Custom/Distortion", "Custom/Flow", "Custom/FlowOpaque", "Custom/Grass", "Custom/GuiScroll", "Custom/Heightmap", "Custom/icon", "Custom/InteriorSide", "Custom/LitGui", "Custom/LitParticles", "Custom/mapshader", "Custom/ParticleDecal", "Custom/Piece", "Custom/Player", "Custom/Rug", "Custom/ShadowBlob", "Custom/SkyboxProcedural", "Custom/SkyObject", "Custom/StaticRock", "Custom/Tar", "Custom/Trilinearmap", "Custom/UI/BGBlur", "Custom/Vegetation", "Custom/Water", "Custom/WaterBottom", "Custom/WaterMask", "Custom/Yggdrasil", "Custom/Yggdrasil/root", "Hidden/BlitCopyHDRTonemap", "Hidden/Dof/DepthOfFieldHdr", "Hidden/Dof/DX11Dof", "Hidden/Internal-Loading", "Hidden/Internal-UIRDefaultWorld", "Hidden/SimpleClear", "Hidden/SunShaftsComposite", "Lux Lit Particles/ Bumped", "Lux Lit Particles/ Tess Bumped", "Particles/Standard Surface2", "Particles/Standard Unlit2", "Standard TwoSided", "ToonDeferredShading2017", "Unlit/DepthWrite", "Unlit/Lighting" }; public static List shaders = new List(); private static readonly HashSet CachedShaders = new HashSet(); public static bool debug = true; public static Shader findShader(string name) { Shader[] array = Resources.FindObjectsOfTypeAll(); if (array.Length == 0) { Debug.LogWarning((object)"SHADER LIST IS EMPTY!"); return null; } if (debug) { } return shaders.Find((Shader x) => ((Object)x).name == name); } public static Shader GetShaderByName(string name) { return shaders.Find((Shader x) => ((Object)x).name == name.Trim()); } public static void debugShaderList(List shadersRes) { foreach (Shader shadersRe in shadersRes) { Debug.LogWarning((object)("SHADER NAME IS: " + ((Object)shadersRe).name)); } debug = false; } public static void Replace(GameObject gameObject) { prefabsToReplaceShader.Add(gameObject); GetMaterialsInPrefab(gameObject); } public static void GetMaterialsInPrefab(GameObject gameObject) { Renderer[] componentsInChildren = gameObject.GetComponentsInChildren(true); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { Material[] sharedMaterials = val.sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0) { continue; } Material[] array2 = sharedMaterials; foreach (Material val2 in array2) { if ((Object)(object)val2 != (Object)null) { materialsInPrefabs.Add(val2); } } } } public static void getMeShaders() { AssetBundle[] array = Resources.FindObjectsOfTypeAll(); AssetBundle[] array2 = array; foreach (AssetBundle val in array2) { IEnumerable enumerable3; try { IEnumerable enumerable2; if (!val.isStreamedSceneAssetBundle || !Object.op_Implicit((Object)(object)val)) { IEnumerable enumerable = val.LoadAllAssets(); enumerable2 = enumerable; } else { enumerable2 = from shader in ((IEnumerable)val.GetAllAssetNames()).Select((Func)val.LoadAsset) where (Object)(object)shader != (Object)null select shader; } enumerable3 = enumerable2; } catch (Exception) { continue; } if (enumerable3 == null) { continue; } foreach (Shader item in enumerable3) { CachedShaders.Add(item); } } } public static void runMaterialFix() { getMeShaders(); shaders.AddRange(CachedShaders); foreach (Material materialsInPrefab in materialsInPrefabs) { Shader shader = materialsInPrefab.shader; if (!((Object)(object)shader == (Object)null)) { string name = ((Object)shader).name; if (!(name == "Standard") && !name.Contains("ScrollingTex") && name.Contains("Balrond")) { setProperValue(materialsInPrefab, name); } } } } private static void setProperValue(Material material, string shaderName) { string name = shaderName.Replace("Balrond", "Custom"); name = checkNaming(name); Shader shaderByName = GetShaderByName(name); if (!((Object)(object)shaderByName == (Object)null)) { material.shader = shaderByName; } } private static string checkNaming(string name) { string result = name; if (name.Contains("Bumped")) { result = name.Replace("Custom", "Lux Lit Particles"); } if (name.Contains("Tess Bumped")) { result = name.Replace("Custom", "Lux Lit Particles"); } if (name.Contains("Standard Surface")) { result = name.Replace("Custom", "Particles"); result = result.Replace("Standard Surface2", "Standard Surface"); } if (name.Contains("Standard Unlit")) { result = name.Replace("Custom", "Particles"); result = result.Replace("Standard Unlit", "Standard Unlit2"); result = result.Replace("Standard Unlit22", "Standard Unlit2"); } return result; } } public class TableMapper { public static CraftingStation cauldron; public static CraftingStation workbench; public static CraftingStation heavyWorkbench; public static CraftingStation forge; public static CraftingStation ironworks; public static CraftingStation blackforge; public static CraftingStation stoneCutter; public static CraftingStation artisian; public static CraftingStation magetable; public static CraftingStation runeforge; public static CraftingStation tannery; public static CraftingStation fletcher; public static CraftingStation grill; public static CraftingStation alchemylab; public static CraftingStation shamantable; public static CraftingStation foodtable; public static List pieces = new List(); public static void setupTables(List list) { pieces = list; prepareTables(); } private static CraftingStation FindStation(List list, string name, string replacement = "piece_workbench") { GameObject val = list.Find((GameObject x) => ((Object)x).name == name); if ((Object)(object)val != (Object)null) { return val.GetComponent(); } if ((Object)(object)val == (Object)null) { val = list.Find((GameObject x) => ((Object)x).name == replacement); if ((Object)(object)val != (Object)null) { return val.GetComponent(); } } Debug.LogWarning((object)("TableMapper - Station not found: " + name)); return null; } private static void prepareTables() { cauldron = FindStation(pieces, "piece_cauldron"); workbench = FindStation(pieces, "piece_workbench"); heavyWorkbench = FindStation(pieces, "piece_heavy_workbench_bal"); forge = FindStation(pieces, "forge"); ironworks = FindStation(pieces, "piece_metalworks_bal", "forge"); blackforge = FindStation(pieces, "blackforge"); stoneCutter = FindStation(pieces, "piece_stonecutter"); artisian = FindStation(pieces, "piece_artisanstation"); runeforge = FindStation(pieces, "piece_runeforge_bal", "blackforge"); magetable = FindStation(pieces, "piece_magetable"); fletcher = FindStation(pieces, "piece_fletcher_bal"); shamantable = FindStation(pieces, "piece_shamantable_bal", "piece_magetable"); foodtable = FindStation(pieces, "piece_preptable"); alchemylab = FindStation(pieces, "piece_MeadCauldron"); } } public class JsonLoader { public string defaultPath = string.Empty; public void loadJson() { LoadTranslations(); justDefaultPath(); } public void justDefaultPath() { string configPath = Paths.ConfigPath; string text = Path.Combine(configPath, "BalrondExtraSkills-translation/"); defaultPath = text; } public void createDefaultPath() { string configPath = Paths.ConfigPath; string text = Path.Combine(configPath, "BalrondExtraSkills-translation/"); if (!Directory.Exists(text)) { CreateFolder(text); } else { Debug.Log((object)("BalrondExtraSkills: Folder already exists: " + text)); } defaultPath = text; } private string[] jsonFilePath(string folderName, string extension) { string configPath = Paths.ConfigPath; string text = Path.Combine(configPath, "BalrondExtraSkills-translation/"); if (!Directory.Exists(text)) { CreateFolder(text); } else { Debug.Log((object)("BalrondExtraSkills: Folder already exists: " + text)); } string[] files = Directory.GetFiles(text, extension); Debug.Log((object)("BalrondExtraSkills:" + folderName + " Json Files Found: " + files.Length)); return files; } private static void CreateFolder(string path) { try { Directory.CreateDirectory(path); Debug.Log((object)"BalrondExtraSkills: Folder created successfully."); } catch (Exception ex) { Debug.Log((object)("BalrondExtraSkills: Error creating folder: " + ex.Message)); } } private void LoadTranslations() { int num = 0; string[] array = jsonFilePath("Translation", "*.json"); foreach (string text in array) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); string json = File.ReadAllText(text); JsonData jsonData = JsonMapper.ToObject(json); Dictionary dictionary = new Dictionary(); foreach (string key in jsonData.Keys) { dictionary[key] = jsonData[key].ToString(); } if (dictionary != null) { BalrondTranslator.translations.Add(fileNameWithoutExtension, dictionary); Debug.Log((object)("BalrondExtraSkills: Json Files Language: " + fileNameWithoutExtension)); num++; } else { Debug.LogError((object)("BalrondExtraSkills: Loading FAILED file: " + text)); } } Debug.Log((object)("BalrondExtraSkills: Translation JsonFiles Loaded: " + num)); } } [BepInPlugin("balrond.astafaraios.BalrondExtraSkills", "BalrondExtraSkills", "0.1.4")] public sealed class Launch : BaseUnityPlugin { [HarmonyPatch(typeof(ZNetScene), "Awake")] private static class ZNetScene_Awake_Path { private static bool ranFix; private static void Prefix(ZNetScene __instance) { try { if ((Object)(object)__instance == (Object)null || modResourceLoader == null) { return; } modResourceLoader.AddPrefabsToZnetScene(__instance); if (!ranFix) { ZNet val = Object.FindObjectOfType(); if (!((Object)(object)val != (Object)null) || !val.IsDedicated()) { ShaderReplacment.runMaterialFix(); ranFix = true; } } } catch (Exception arg) { Debug.LogError((object)string.Format("[{0}] ZNetScene_Awake_Path failed: {1}", "BalrondExtraSkills", arg)); } } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class ObjectDB_Awake_Path { private static void Postfix() { try { if (IsObjectDBValid() && modResourceLoader != null) { modResourceLoader.AddItemsToObjectDB(); } } catch (Exception arg) { Debug.LogError((object)string.Format("[{0}] ObjectDB_Awake_Path failed: {1}", "BalrondExtraSkills", arg)); } } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class Object_CopyOtherDB_Path { private static void Postfix() { try { if (IsObjectDBValid() && modResourceLoader != null) { modResourceLoader.AddItemsToObjectDB(); } } catch (Exception arg) { Debug.LogError((object)string.Format("[{0}] Object_CopyOtherDB_Path failed: {1}", "BalrondExtraSkills", arg)); } } } private Harmony harmony; internal static Launch Instance; internal static Harmony HarmonyInstance; internal static ConfigSync ConfigSyncInstance; internal static ExtraSkillsConfig Settings; public const string PluginGUID = "balrond.astafaraios.BalrondExtraSkills"; public const string PluginName = "BalrondExtraSkills"; public const string PluginVersion = "0.1.4"; public static readonly ModResourceLoader modResourceLoader = new ModResourceLoader(); public static readonly JsonLoader jsonLoader = new JsonLoader(); public static readonly string projectName = "[BalrondExtraSkills]"; public const bool Enabled = true; public const bool DebugLog = false; private void Awake() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown Instance = this; try { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[BES bootstrap 1/8] Launch.Awake entered."); bool flag = false; ((BaseUnityPlugin)this).Logger.LogInfo((object)"[BES bootstrap 2/8] Initializing Harmony."); harmony = new Harmony("balrond.astafaraios.BalrondExtraSkills"); HarmonyInstance = harmony; ((BaseUnityPlugin)this).Logger.LogInfo((object)"[BES bootstrap 3/8] Initializing ServerSync and configuration."); ConfigSyncInstance = new ConfigSync("balrond.astafaraios.BalrondExtraSkills") { DisplayName = "BalrondExtraSkills", CurrentVersion = "0.1.4", MinimumRequiredVersion = "0.1.4" }; Settings = new ExtraSkillsConfig((BaseUnityPlugin)(object)this, ConfigSyncInstance); ConfigSyncInstance.AddLockingConfigEntry(Settings.LockConfiguration); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[BES bootstrap 4/8] Loading translation data."); try { if (jsonLoader != null) { jsonLoader.loadJson(); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"BalrondExtraSkills: jsonLoader is null."); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("BalrondExtraSkills: Failed to load json data. " + ex)); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"[BES bootstrap 5/8] Applying Harmony patches."); harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[BES bootstrap 6/8] Loading embedded assets."); try { if (modResourceLoader != null) { modResourceLoader.loadAssets(); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"BalrondExtraSkills: modResourceLoader is null."); } } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogError((object)("BalrondExtraSkills: Failed to load assets. " + ex2)); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"[BES bootstrap 7/8] Registering custom skills."); RegisterSkillsSafe(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[BES bootstrap 8/8] Refreshing skill assets and definitions."); try { BalrondSkillSystem.NotifyAssetsReady(); } catch (Exception ex3) { ((BaseUnityPlugin)this).Logger.LogError((object)("BalrondExtraSkills: Failed during NotifyAssetsReady. " + ex3)); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"BalrondExtraSkills 0.1.4 loaded with ServerSync."); } catch (Exception ex4) { ((BaseUnityPlugin)this).Logger.LogError((object)("[BES bootstrap FATAL] Startup failed after Launch was instantiated. Full exception follows:\n" + ex4)); throw; } } private void OnDestroy() { try { if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("BalrondExtraSkills: Failed during OnDestroy unpatch. " + ex)); } finally { Settings = null; ConfigSyncInstance = null; HarmonyInstance = null; harmony = null; Instance = null; } } private void RegisterSkillsSafe() { TryRegisterSkill(() => VitalitySkill.Instance, "VitalitySkill"); TryRegisterSkill(() => ArcanaSkill.Instance, "ArcanaSkill"); TryRegisterSkill(() => FortitudeSkill.Instance, "FortitudeSkill"); TryRegisterSkill(() => EnduranceSkill.Instance, "EnduranceSkill"); TryRegisterSkill(() => HaulingSkill.Instance, "HaulingSkill"); TryRegisterSkill(() => ExplorationSkill.Instance, "ExplorationSkill"); } private void TryRegisterSkill(Func getter, string skillName) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Invalid comparison between Unknown and I4 try { if (getter == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("BalrondExtraSkills: Skill getter for " + skillName + " is null.")); return; } BaseSkill baseSkill = getter(); if (baseSkill == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("BalrondExtraSkills: Skill instance for " + skillName + " is null.")); } else if ((int)baseSkill.SkillType == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("BalrondExtraSkills: Skill " + skillName + " has SkillType.None after initialization. Check FixedSkillId / registration.")); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("BalrondExtraSkills: Failed to initialize skill " + skillName + ". " + ex)); } } public static bool IsObjectDBValid() { try { return (Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_items != null && ObjectDB.instance.m_items.Count != 0 && (Object)(object)ObjectDB.instance.GetItemPrefab("Amber") != (Object)null; } catch { return false; } } } internal sealed class ExtraSkillsConfig { internal const float DefaultVitalityHealthBonusAt100 = 10f; internal const float DefaultVitalityFoodXpPerHealth = 0.01f; internal const float DefaultVitalityMaxFoodXpPerEat = 2f; internal const float DefaultVitalitySleepXpPerWake = 0.25f; internal const float DefaultVitalitySleepAwardCooldownSeconds = 2f; internal const float DefaultArcanaEitrBonusAt100 = 10f; internal const float DefaultArcanaFoodXpPerEitr = 0.02f; internal const float DefaultArcanaMaxFoodXpPerEat = 2f; internal const float DefaultArcanaXpPerElementalDamage = 0.01f; internal const float DefaultArcanaMaxXpPerHit = 1f; internal const float DefaultArcanaDotEnvironmentXpMultiplier = 0.5f; internal const float DefaultEnduranceStaminaBonusAt100 = 20f; internal const float DefaultEnduranceMinStaminaUseToTrigger = 5f; internal const float DefaultEnduranceXpFractionOfLastUse = 0.02f; internal const float DefaultEnduranceMaxXpPerExhaust = 5f; internal const float DefaultEnduranceRecoverThresholdFraction = 0.25f; internal const float DefaultEnduranceExhaustedStaminaThreshold = 1f; internal const float DefaultExplorationBaseRadius = 100f; internal const float DefaultExplorationBonusRadiusAt100 = 100f; internal const float DefaultExplorationMinRadius = 50f; internal const float DefaultExplorationMaxRadius = 300f; internal const float DefaultExplorationXpPerSuccessfulPass = 0.08f; internal const float DefaultExplorationMinTimeBetweenAwards = 1f; internal const float DefaultFortitudeArmorBonusAt100 = 10f; internal const float DefaultFortitudeXpPerHpLost = 0.016f; internal const float DefaultFortitudeMaxXpPerHit = 1f; internal const float DefaultFortitudeElementalXpMultiplier = 0.5f; internal const float DefaultHaulingCarryWeightBonusAt100 = 50f; internal const float DefaultHaulingWeightRatioThreshold = 0.9f; internal const float DefaultHaulingRunXpFraction = 0.2f; internal const float DefaultHaulingSneakXpFraction = 0.25f; internal const float DefaultHaulingJumpXpFraction = 0.35f; internal const float DefaultHaulingMaxFactorPerTrigger = 0.35f; internal const float DefaultHaulingFallbackMaxCarryWeight = 300f; internal const float DefaultMiningPickaxeBonusAt100 = 0.25f; internal const float DefaultWoodcuttingChopBonusAt100 = 0.25f; internal const float DefaultWoodcuttingMinChopToConsider = 0.001f; internal const float DefaultWoodcuttingVirtualChopMaxConvertAt100 = 0.12f; internal const bool DefaultWoodcuttingVirtualChopOnlyIfNoChopDamage = true; internal const float DefaultPickaxeMaxReductionAt100 = 0.25f; internal const float DefaultPickaxeMinCostMultiplier = 0.2f; private readonly BaseUnityPlugin _plugin; private readonly ConfigSync _configSync; internal ConfigEntry LockConfiguration { get; private set; } internal ConfigEntry VitalityEnabled { get; private set; } internal ConfigEntry VitalityGainMultiplier { get; private set; } internal ConfigEntry VitalityEffectMultiplier { get; private set; } internal ConfigEntry VitalityHealthBonusAt100 { get; private set; } internal ConfigEntry VitalityFoodXpPerHealth { get; private set; } internal ConfigEntry VitalityMaxFoodXpPerEat { get; private set; } internal ConfigEntry VitalitySleepXpPerWake { get; private set; } internal ConfigEntry VitalitySleepAwardCooldownSeconds { get; private set; } internal ConfigEntry ArcanaEnabled { get; private set; } internal ConfigEntry ArcanaGainMultiplier { get; private set; } internal ConfigEntry ArcanaEffectMultiplier { get; private set; } internal ConfigEntry ArcanaEitrBonusAt100 { get; private set; } internal ConfigEntry ArcanaFoodXpPerEitr { get; private set; } internal ConfigEntry ArcanaMaxFoodXpPerEat { get; private set; } internal ConfigEntry ArcanaXpPerElementalDamage { get; private set; } internal ConfigEntry ArcanaMaxXpPerHit { get; private set; } internal ConfigEntry ArcanaCountDotAndEnvironment { get; private set; } internal ConfigEntry ArcanaDotEnvironmentXpMultiplier { get; private set; } internal ConfigEntry EnduranceEnabled { get; private set; } internal ConfigEntry EnduranceGainMultiplier { get; private set; } internal ConfigEntry EnduranceEffectMultiplier { get; private set; } internal ConfigEntry EnduranceStaminaBonusAt100 { get; private set; } internal ConfigEntry EnduranceMinStaminaUseToTrigger { get; private set; } internal ConfigEntry EnduranceXpFractionOfLastUse { get; private set; } internal ConfigEntry EnduranceMaxXpPerExhaust { get; private set; } internal ConfigEntry EnduranceRecoverThresholdFraction { get; private set; } internal ConfigEntry EnduranceExhaustedStaminaThreshold { get; private set; } internal ConfigEntry ExplorationEnabled { get; private set; } internal ConfigEntry ExplorationGainMultiplier { get; private set; } internal ConfigEntry ExplorationEffectMultiplier { get; private set; } internal ConfigEntry ExplorationBaseRadius { get; private set; } internal ConfigEntry ExplorationBonusRadiusAt100 { get; private set; } internal ConfigEntry ExplorationMinRadius { get; private set; } internal ConfigEntry ExplorationMaxRadius { get; private set; } internal ConfigEntry ExplorationXpPerSuccessfulPass { get; private set; } internal ConfigEntry ExplorationMinTimeBetweenAwards { get; private set; } internal ConfigEntry FortitudeEnabled { get; private set; } internal ConfigEntry FortitudeGainMultiplier { get; private set; } internal ConfigEntry FortitudeEffectMultiplier { get; private set; } internal ConfigEntry FortitudeArmorBonusAt100 { get; private set; } internal ConfigEntry FortitudeXpPerHpLost { get; private set; } internal ConfigEntry FortitudeMaxXpPerHit { get; private set; } internal ConfigEntry FortitudeElementalXpMultiplier { get; private set; } internal ConfigEntry HaulingEnabled { get; private set; } internal ConfigEntry HaulingGainMultiplier { get; private set; } internal ConfigEntry HaulingEffectMultiplier { get; private set; } internal ConfigEntry HaulingCarryWeightBonusAt100 { get; private set; } internal ConfigEntry HaulingWeightRatioThreshold { get; private set; } internal ConfigEntry HaulingRunXpFraction { get; private set; } internal ConfigEntry HaulingSneakXpFraction { get; private set; } internal ConfigEntry HaulingJumpXpFraction { get; private set; } internal ConfigEntry HaulingMaxFactorPerTrigger { get; private set; } internal ConfigEntry HaulingFallbackMaxCarryWeight { get; private set; } internal ConfigEntry MiningBonusEnabled { get; private set; } internal ConfigEntry MiningBonusMultiplier { get; private set; } internal ConfigEntry MiningPickaxeBonusAt100 { get; private set; } internal ConfigEntry WoodcuttingBonusEnabled { get; private set; } internal ConfigEntry WoodcuttingBonusMultiplier { get; private set; } internal ConfigEntry WoodcuttingChopBonusAt100 { get; private set; } internal ConfigEntry WoodcuttingMinChopToConsider { get; private set; } internal ConfigEntry WoodcuttingVirtualChopEnabled { get; private set; } internal ConfigEntry WoodcuttingVirtualChopMultiplier { get; private set; } internal ConfigEntry WoodcuttingVirtualChopMaxConvertAt100 { get; private set; } internal ConfigEntry WoodcuttingVirtualChopOnlyIfNoChopDamage { get; private set; } internal ConfigEntry PickaxeStaminaReductionEnabled { get; private set; } internal ConfigEntry PickaxeStaminaReductionMultiplier { get; private set; } internal ConfigEntry PickaxeMaxReductionAt100 { get; private set; } internal ConfigEntry PickaxeMinCostMultiplier { get; private set; } internal ExtraSkillsConfig(BaseUnityPlugin plugin, ConfigSync configSync) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown _plugin = plugin; _configSync = configSync; LockConfiguration = _plugin.Config.Bind("1 - General", "Lock Configuration", true, new ConfigDescription("If enabled, synchronized settings are controlled by the server while connected.", (AcceptableValueBase)null, Array.Empty())); VitalityEnabled = Synced("2 - Vitality", "Enabled", value: true, "Enable Vitality gameplay effects and XP gain. The skill remains registered so existing save data is preserved."); VitalityGainMultiplier = SyncedMultiplier("2 - Vitality", "XP Gain Multiplier", 1f, "Global multiplier applied after Vitality's event-specific XP calculation. 1 = original rate, 0 = no XP gain."); VitalityEffectMultiplier = SyncedMultiplier("2 - Vitality", "Effect Multiplier", 1f, "Multiplier applied to Vitality's configured health bonus. 1 = configured base bonus."); VitalityHealthBonusAt100 = SyncedRange("2 - Vitality", "Health Bonus At Skill 100", 10f, 0f, 10000f, "Base maximum-health bonus at Vitality 100 before Effect Multiplier. Original value: 10."); VitalityFoodXpPerHealth = SyncedRange("2 - Vitality", "Food XP Per Health", 0.01f, 0f, 100f, "Vitality XP factor per point of health on successfully eaten food, before XP Gain Multiplier. Original value: 0.01."); VitalityMaxFoodXpPerEat = SyncedRange("2 - Vitality", "Max Food XP Per Eat", 2f, 0f, 10000f, "Maximum raw Vitality XP awarded by one food consumption before XP Gain Multiplier. Original value: 2."); VitalitySleepXpPerWake = SyncedRange("2 - Vitality", "Sleep XP Per Wake", 0.25f, 0f, 10000f, "Raw Vitality XP awarded after waking, before XP Gain Multiplier. Original value: 0.25."); VitalitySleepAwardCooldownSeconds = SyncedRange("2 - Vitality", "Sleep Award Cooldown Seconds", 2f, 0f, 3600f, "Minimum time between wake XP awards. Original value: 2 seconds."); ArcanaEnabled = Synced("3 - Arcana", "Enabled", value: true, "Enable Arcana gameplay effects and XP gain. The skill remains registered so existing save data is preserved."); ArcanaGainMultiplier = SyncedMultiplier("3 - Arcana", "XP Gain Multiplier", 1f, "Global multiplier applied after Arcana's event-specific XP calculation. 1 = original rate, 0 = no XP gain."); ArcanaEffectMultiplier = SyncedMultiplier("3 - Arcana", "Effect Multiplier", 1f, "Multiplier applied to Arcana's configured Eitr bonus. 1 = configured base bonus."); ArcanaEitrBonusAt100 = SyncedRange("3 - Arcana", "Eitr Bonus At Skill 100", 10f, 0f, 10000f, "Base maximum-Eitr bonus at Arcana 100 before Effect Multiplier. Original value: 10."); ArcanaFoodXpPerEitr = SyncedRange("3 - Arcana", "Food XP Per Eitr", 0.02f, 0f, 100f, "Arcana XP factor per point of Eitr on successfully eaten food, before XP Gain Multiplier. Original value: 0.02."); ArcanaMaxFoodXpPerEat = SyncedRange("3 - Arcana", "Max Food XP Per Eat", 2f, 0f, 10000f, "Maximum raw Arcana XP from one Eitr food before XP Gain Multiplier. Original value: 2."); ArcanaXpPerElementalDamage = SyncedRange("3 - Arcana", "XP Per Elemental Damage", 0.01f, 0f, 100f, "Raw Arcana XP per point of qualifying elemental damage before DoT/environment and global XP multipliers. Original value: 0.01."); ArcanaMaxXpPerHit = SyncedRange("3 - Arcana", "Max XP Per Hit", 1f, 0f, 10000f, "Maximum raw Arcana XP from one qualifying damage event before global XP Gain Multiplier. Original value: 1."); ArcanaCountDotAndEnvironment = Synced("3 - Arcana", "Count DoT And Environment", value: true, "Allow Arcana XP from configured elemental damage-over-time and environmental hit types."); ArcanaDotEnvironmentXpMultiplier = SyncedRange("3 - Arcana", "DoT And Environment XP Multiplier", 0.5f, 0f, 1f, "Multiplier used only for Arcana DoT/environment hits. Original value: 0.5. 0 disables those awards, 1 grants full direct-hit XP."); EnduranceEnabled = Synced("4 - Endurance", "Enabled", value: true, "Enable Endurance gameplay effects and XP gain. The skill remains registered so existing save data is preserved."); EnduranceGainMultiplier = SyncedMultiplier("4 - Endurance", "XP Gain Multiplier", 1f, "Global multiplier applied after Endurance's exhaustion XP calculation. 1 = original rate, 0 = no XP gain."); EnduranceEffectMultiplier = SyncedMultiplier("4 - Endurance", "Effect Multiplier", 1f, "Multiplier applied to Endurance's configured stamina bonus. 1 = configured base bonus."); EnduranceStaminaBonusAt100 = SyncedRange("4 - Endurance", "Stamina Bonus At Skill 100", 20f, 0f, 10000f, "Base maximum-stamina bonus at Endurance 100 before Effect Multiplier. Original value: 20."); EnduranceMinStaminaUseToTrigger = SyncedRange("4 - Endurance", "Minimum Stamina Use To Trigger", 5f, 0f, 10000f, "An exhaustion-causing action must request more than this much stamina to award Endurance XP. Original value: 5."); EnduranceXpFractionOfLastUse = SyncedRange("4 - Endurance", "XP Fraction Of Last Use", 0.02f, 0f, 10f, "Fraction of the exhaustion-causing stamina cost converted to raw Endurance XP. Original value: 0.02."); EnduranceMaxXpPerExhaust = SyncedRange("4 - Endurance", "Max XP Per Exhaustion", 5f, 0f, 10000f, "Maximum raw Endurance XP per exhaustion event before XP Gain Multiplier. Original value: 5."); EnduranceRecoverThresholdFraction = SyncedRange("4 - Endurance", "Recovery Threshold Fraction", 0.25f, 0f, 1f, "Fraction of maximum stamina that must be recovered before another exhaustion award can occur. Original value: 0.25."); EnduranceExhaustedStaminaThreshold = SyncedRange("4 - Endurance", "Exhausted Stamina Threshold", 1f, 0f, 100f, "Stamina at or below this value counts as exhaustion when crossing down from a positive value. Original value: 1."); ExplorationEnabled = Synced("5 - Exploration", "Enabled", value: true, "Enable Exploration gameplay effects and XP gain. Disabled means the minimap radius is left untouched by this mod."); ExplorationGainMultiplier = SyncedMultiplier("5 - Exploration", "XP Gain Multiplier", 1f, "Global multiplier applied to Exploration XP. 1 = original rate, 0 = no XP gain."); ExplorationEffectMultiplier = SyncedMultiplier("5 - Exploration", "Effect Multiplier", 1f, "Multiplier applied to Exploration's configured bonus radius. 1 = configured base bonus."); ExplorationBaseRadius = SyncedRange("5 - Exploration", "Base Explore Radius", 100f, 0f, 10000f, "Base radius used by the Exploration calculation while the skill is enabled. Original value: 100."); ExplorationBonusRadiusAt100 = SyncedRange("5 - Exploration", "Bonus Radius At Skill 100", 100f, 0f, 10000f, "Additional discovery radius at Exploration 100 before Effect Multiplier. Original value: 100."); ExplorationMinRadius = SyncedRange("5 - Exploration", "Minimum Explore Radius", 50f, 0f, 10000f, "Lower clamp for the radius produced by this mod. Original value: 50."); ExplorationMaxRadius = SyncedRange("5 - Exploration", "Maximum Explore Radius", 300f, 0f, 10000f, "Upper clamp for the radius produced by this mod. Original value: 300."); ExplorationXpPerSuccessfulPass = SyncedRange("5 - Exploration", "XP Per Successful Explore Pass", 0.08f, 0f, 10000f, "Raw XP awarded when an exploration pass reveals at least one new map pixel, before XP Gain Multiplier. Original value: 0.08."); ExplorationMinTimeBetweenAwards = SyncedRange("5 - Exploration", "Minimum Time Between Awards", 1f, 0f, 3600f, "Anti-spam cooldown in seconds between Exploration XP awards. Original value: 1 second."); FortitudeEnabled = Synced("6 - Fortitude", "Enabled", value: true, "Enable Fortitude gameplay effects and XP gain. The skill remains registered so existing save data is preserved."); FortitudeGainMultiplier = SyncedMultiplier("6 - Fortitude", "XP Gain Multiplier", 1f, "Global multiplier applied after Fortitude's damage XP calculation. 1 = original rate, 0 = no XP gain."); FortitudeEffectMultiplier = SyncedMultiplier("6 - Fortitude", "Effect Multiplier", 1f, "Multiplier applied to Fortitude's configured armor bonus. 1 = configured base bonus."); FortitudeArmorBonusAt100 = SyncedRange("6 - Fortitude", "Armor Bonus At Skill 100", 10f, 0f, 10000f, "Base armor bonus at Fortitude 100 before Effect Multiplier. Original value: 10."); FortitudeXpPerHpLost = SyncedRange("6 - Fortitude", "XP Per HP Lost", 0.016f, 0f, 100f, "Raw Fortitude XP per point of actual post-mitigation HP lost, before other multipliers. Original value: 0.016."); FortitudeMaxXpPerHit = SyncedRange("6 - Fortitude", "Max XP Per Hit", 1f, 0f, 10000f, "Maximum raw Fortitude XP from one damage event before global XP Gain Multiplier. Original value: 1."); FortitudeElementalXpMultiplier = SyncedRange("6 - Fortitude", "Elemental Damage XP Multiplier", 0.5f, 0f, 10f, "Additional multiplier applied to Fortitude XP for elemental hits. Original value: 0.5."); HaulingEnabled = Synced("7 - Hauling", "Enabled", value: true, "Enable Hauling gameplay effects and XP gain. The skill remains registered so existing save data is preserved."); HaulingGainMultiplier = SyncedMultiplier("7 - Hauling", "XP Gain Multiplier", 1f, "Global multiplier applied after Hauling's piggy-backed XP calculation. 1 = original rate, 0 = no XP gain."); HaulingEffectMultiplier = SyncedMultiplier("7 - Hauling", "Effect Multiplier", 1f, "Multiplier applied to Hauling's configured carry-weight bonus. 1 = configured base bonus."); HaulingCarryWeightBonusAt100 = SyncedRange("7 - Hauling", "Carry Weight Bonus At Skill 100", 50f, 0f, 10000f, "Base carry-weight bonus at Hauling 100 before Effect Multiplier. Original behavior equals 50 at skill 100."); HaulingWeightRatioThreshold = SyncedRange("7 - Hauling", "Weight Ratio Threshold", 0.9f, 0f, 10f, "Current weight / max carry weight required to gain Hauling XP. Original value: 0.90."); HaulingRunXpFraction = SyncedRange("7 - Hauling", "Run XP Fraction", 0.2f, 0f, 10f, "Fraction of a qualifying Run skill accumulator gain converted into Hauling gain. Original value: 0.20."); HaulingSneakXpFraction = SyncedRange("7 - Hauling", "Sneak XP Fraction", 0.25f, 0f, 10f, "Fraction of a qualifying Sneak skill accumulator gain converted into Hauling gain. Original value: 0.25."); HaulingJumpXpFraction = SyncedRange("7 - Hauling", "Jump XP Fraction", 0.35f, 0f, 10f, "Fraction of a qualifying Jump skill accumulator gain converted into Hauling gain. Original value: 0.35."); HaulingMaxFactorPerTrigger = SyncedRange("7 - Hauling", "Max Factor Per Trigger", 0.35f, 0f, 10000f, "Safety cap on the raw factor passed to Hauling RaiseSkill for one piggy-backed trigger. Original value: 0.35."); HaulingFallbackMaxCarryWeight = SyncedRange("7 - Hauling", "Fallback Max Carry Weight", 300f, 1f, 100000f, "Fallback used only if no compatible Player GetMaxCarry* method can be resolved. Original vanilla fallback: 300."); MiningBonusEnabled = Synced("8 - Vanilla Skill Bonuses", "Mining Damage Bonus Enabled", value: true, "Enable the extra pickaxe damage scaling based on the vanilla Pickaxes skill."); MiningBonusMultiplier = SyncedMultiplier("8 - Vanilla Skill Bonuses", "Mining Damage Bonus Multiplier", 1f, "Multiplier applied to the configured Mining damage bonus. 1 = configured base bonus."); MiningPickaxeBonusAt100 = SyncedRange("8 - Vanilla Skill Bonuses", "Mining Pickaxe Bonus At Skill 100", 0.25f, 0f, 10f, "Additional pickaxe damage fraction at Pickaxes 100 before the bonus multiplier. Original value: 0.25 (+25%)."); WoodcuttingBonusEnabled = Synced("8 - Vanilla Skill Bonuses", "Woodcutting Chop Bonus Enabled", value: true, "Enable the extra chop damage scaling based on the vanilla WoodCutting skill."); WoodcuttingBonusMultiplier = SyncedMultiplier("8 - Vanilla Skill Bonuses", "Woodcutting Chop Bonus Multiplier", 1f, "Multiplier applied to the configured Woodcutting chop bonus. 1 = configured base bonus."); WoodcuttingChopBonusAt100 = SyncedRange("8 - Vanilla Skill Bonuses", "Woodcutting Chop Bonus At Skill 100", 0.25f, 0f, 10f, "Additional chop damage fraction at WoodCutting 100 before the bonus multiplier. Original value: 0.25 (+25%)."); WoodcuttingMinChopToConsider = SyncedRange("8 - Vanilla Skill Bonuses", "Woodcutting Minimum Chop To Scale", 0.001f, 0f, 1000f, "Minimum existing chop damage required before the direct Woodcutting chop bonus is applied. Original value: 0.001."); WoodcuttingVirtualChopEnabled = Synced("8 - Vanilla Skill Bonuses", "Woodcutting Virtual Chop Enabled", value: true, "Enable conversion of physical damage into tree-only chop damage based on the vanilla WoodCutting skill."); WoodcuttingVirtualChopMultiplier = SyncedMultiplier("8 - Vanilla Skill Bonuses", "Woodcutting Virtual Chop Multiplier", 1f, "Multiplier applied to the configured virtual-chop conversion. 1 = configured base conversion."); WoodcuttingVirtualChopMaxConvertAt100 = SyncedRange("8 - Vanilla Skill Bonuses", "Woodcutting Virtual Chop At Skill 100", 0.12f, 0f, 10f, "Fraction of qualifying physical damage converted to extra chop damage at WoodCutting 100 before multiplier. Original value: 0.12 (12%)."); WoodcuttingVirtualChopOnlyIfNoChopDamage = Synced("8 - Vanilla Skill Bonuses", "Woodcutting Virtual Chop Only If No Chop Damage", value: true, "If true, virtual chop is added only when the hit has no existing chop damage. Original value: true."); PickaxeStaminaReductionEnabled = Synced("8 - Vanilla Skill Bonuses", "Pickaxe Stamina Reduction Enabled", value: true, "Enable pickaxe attack stamina reduction based on the vanilla Pickaxes skill."); PickaxeStaminaReductionMultiplier = SyncedMultiplier("8 - Vanilla Skill Bonuses", "Pickaxe Stamina Reduction Multiplier", 1f, "Multiplier applied to the configured pickaxe stamina reduction. 1 = configured base reduction."); PickaxeMaxReductionAt100 = SyncedRange("8 - Vanilla Skill Bonuses", "Pickaxe Max Reduction At Skill 100", 0.25f, 0f, 1f, "Base stamina-cost reduction fraction at Pickaxes 100 before multiplier. Original value: 0.25 (25%)."); PickaxeMinCostMultiplier = SyncedRange("8 - Vanilla Skill Bonuses", "Pickaxe Minimum Cost Multiplier", 0.2f, 0f, 1f, "Lowest allowed final pickaxe stamina-cost multiplier. Original value: 0.20, meaning cost can never fall below 20% of base."); } private ConfigEntry Synced(string group, string name, T value, string description) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown ConfigEntry val = _plugin.Config.Bind(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty())); SyncedConfigEntry syncedConfigEntry = _configSync.AddConfigEntry(val); syncedConfigEntry.SynchronizedConfig = true; return val; } private ConfigEntry SyncedMultiplier(string group, string name, float value, string description) { return SyncedRange(group, name, value, 0f, 10f, description); } private ConfigEntry SyncedRange(string group, string name, float value, float min, float max, string description) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(min, max), Array.Empty()); ConfigEntry val2 = _plugin.Config.Bind(group, name, value, val); SyncedConfigEntry syncedConfigEntry = _configSync.AddConfigEntry(val2); syncedConfigEntry.SynchronizedConfig = true; return val2; } internal bool IsSkillEnabled(int fixedSkillId) { return fixedSkillId switch { 15001 => VitalityEnabled.Value, 15002 => ArcanaEnabled.Value, 15003 => EnduranceEnabled.Value, 15004 => ExplorationEnabled.Value, 15005 => FortitudeEnabled.Value, 15006 => HaulingEnabled.Value, _ => true, }; } internal float GetSkillGainMultiplier(int fixedSkillId) { return fixedSkillId switch { 15001 => VitalityGainMultiplier.Value, 15002 => ArcanaGainMultiplier.Value, 15003 => EnduranceGainMultiplier.Value, 15004 => ExplorationGainMultiplier.Value, 15005 => FortitudeGainMultiplier.Value, 15006 => HaulingGainMultiplier.Value, _ => 1f, }; } internal float GetSkillEffectMultiplier(int fixedSkillId) { return fixedSkillId switch { 15001 => VitalityEffectMultiplier.Value, 15002 => ArcanaEffectMultiplier.Value, 15003 => EnduranceEffectMultiplier.Value, 15004 => ExplorationEffectMultiplier.Value, 15005 => FortitudeEffectMultiplier.Value, 15006 => HaulingEffectMultiplier.Value, _ => 1f, }; } } public sealed class ArcanaSkill : BaseSkill { internal static class ArcanaPatches { [HarmonyPatch(typeof(Player), "GetTotalFoodValue")] public static class HealthIncreasePatches { public static void Postfix(Player __instance, ref float hp, ref float stamina, ref float eitr) { int bonusEitr = GetBonusEitr(__instance); if (bonusEitr > 0) { eitr += bonusEitr; } } } [HarmonyPatch(typeof(Player), "EatFood")] [HarmonyPostfix] private static void Player_EatFood_Postfix(Player __instance, ItemData item, ref bool __result) { if (__result && !((Object)(object)__instance == (Object)null) && item != null && item.m_shared != null) { float foodEitr = item.m_shared.m_foodEitr; if (!(foodEitr <= 0f)) { RaiseFromFood(__instance, foodEitr); } } } } private static ArcanaSkill _instance; public static ArcanaSkill Instance => _instance ?? (_instance = new ArcanaSkill()); internal static bool CountDotAndEnvironment => Launch.Settings == null || Launch.Settings.ArcanaCountDotAndEnvironment.Value; internal static float DotEnvironmentXpMultiplier => (Launch.Settings != null) ? Launch.Settings.ArcanaDotEnvironmentXpMultiplier.Value : 0.5f; private ArcanaSkill() : base(15002, "arcanaskill", "Arcana", "Increases your base maximum Eitr. Improves by eating Eitr food and taking elemental damage.", "arcana") { } internal static int GetBonusEitr(Player player) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0; } if (!BalrondSkillSystem.IsSkillEnabled(Instance.SkillType)) { return 0; } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null) { return 0; } float skillLevel = skills.GetSkillLevel(Instance.SkillType); if (skillLevel <= 0f) { return 0; } float num = Launch.Settings?.ArcanaEitrBonusAt100.Value ?? 10f; float skillEffectMultiplier = BalrondSkillSystem.GetSkillEffectMultiplier(Instance.SkillType); float num2 = Mathf.Max(0f, num) / 10f; return Mathf.FloorToInt(skillLevel / 10f * num2 * skillEffectMultiplier); } private static void RaiseFromFood(Player player, float foodEitr) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && BalrondSkillSystem.IsSkillEnabled(Instance.SkillType) && !(foodEitr <= 0f)) { ExtraSkillsConfig settings = Launch.Settings; float num = settings?.ArcanaFoodXpPerEitr.Value ?? 0.02f; float num2 = settings?.ArcanaMaxFoodXpPerEat.Value ?? 2f; float num3 = foodEitr * Mathf.Max(0f, num); num3 = Mathf.Min(num3, Mathf.Max(0f, num2)); if (!(num3 <= 0f)) { BalrondSkillSystem.RaiseSkill(player, Instance.SkillType, num3); } } } private static float GetElementalXpMultiplier(HitData hit) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected I4, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Invalid comparison between Unknown and I4 //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Invalid comparison between Unknown and I4 //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Invalid comparison between Unknown and I4 if (hit == null) { return 0f; } if (!(hit.m_damage.m_fire > 0f) && !(hit.m_damage.m_frost > 0f) && !(hit.m_damage.m_lightning > 0f)) { return 0f; } HitType hitType = hit.m_hitType; HitType val = hitType; switch (val - 3) { case 0: case 1: case 5: case 7: case 8: case 9: case 10: case 11: case 12: case 14: case 15: return 0f; default: { HitType hitType2 = hit.m_hitType; HitType val2 = hitType2; if (val2 - 5 <= 2 || (int)val2 == 9 || (int)val2 == 21) { if (!CountDotAndEnvironment) { return 0f; } return Mathf.Clamp01(DotEnvironmentXpMultiplier); } if (hit.HaveAttacker() && (Object)(object)hit.GetAttacker() != (Object)null) { return 1f; } return 0f; } } } internal static void OnDamageTaken(Player player, HitData originalHit, float hpLost, bool wasBlocking, bool wasPerfectBlock) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !BalrondSkillSystem.IsSkillEnabled(Instance.SkillType)) { return; } float elementalXpMultiplier = GetElementalXpMultiplier(originalHit); if (elementalXpMultiplier <= 0f) { return; } float num = originalHit.m_damage.m_fire + originalHit.m_damage.m_frost + originalHit.m_damage.m_lightning; if (!(num <= 0f)) { ExtraSkillsConfig settings = Launch.Settings; float num2 = settings?.ArcanaXpPerElementalDamage.Value ?? 0.01f; float num3 = settings?.ArcanaMaxXpPerHit.Value ?? 1f; float num4 = num * Mathf.Max(0f, num2) * elementalXpMultiplier; num4 = Mathf.Min(num4, Mathf.Max(0f, num3)); if (!(num4 <= 0f)) { BalrondSkillSystem.RaiseSkill(player, Instance.SkillType, num4); } } } } public sealed class EnduranceSkill : BaseSkill { internal static class EndurancePatches { [HarmonyPatch(typeof(Player), "GetTotalFoodValue")] public static class StaminaIncreasePatch { public static void Postfix(Player __instance, ref float hp, ref float stamina, ref float eitr) { float bonusStamina = GetBonusStamina(__instance); if (bonusStamina > 0f) { stamina += bonusStamina; } } } [HarmonyPatch(typeof(Player), "UseStamina")] private static class Player_UseStamina_Patch { [HarmonyPrefix] private static void Prefix(Player __instance, [HarmonyArgument(0)] float v, out float __state) { __state = 0f; if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { __state = __instance.GetStamina(); } } [HarmonyPostfix] private static void Postfix(Player __instance, [HarmonyArgument(0)] float v, float __state) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { float stamina = __instance.GetStamina(); OnStaminaUsed(__instance, __state, stamina, v); } } } } private static EnduranceSkill _instance; private static bool _lockedOut; public static EnduranceSkill Instance => _instance ?? (_instance = new EnduranceSkill()); private EnduranceSkill() : base(15003, "enduranceskill", "Endurance", "Increases your base maximum stamina. Improves when you push yourself to exhaustion.", "endurance") { } internal static float GetBonusStamina(Player player) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0f; } if (!BalrondSkillSystem.IsSkillEnabled(Instance.SkillType)) { return 0f; } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null) { return 0f; } float skillLevel = skills.GetSkillLevel(Instance.SkillType); if (skillLevel <= 0f) { return 0f; } float num = Launch.Settings?.EnduranceStaminaBonusAt100.Value ?? 20f; float skillEffectMultiplier = BalrondSkillSystem.GetSkillEffectMultiplier(Instance.SkillType); return skillLevel / 100f * Mathf.Max(0f, num) * skillEffectMultiplier; } internal static void OnStaminaUsed(Player player, float staminaBefore, float staminaAfter, float amountUsedRequested) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !BalrondSkillSystem.IsSkillEnabled(Instance.SkillType)) { return; } ExtraSkillsConfig settings = Launch.Settings; float num = settings?.EnduranceRecoverThresholdFraction.Value ?? 0.25f; float num2 = settings?.EnduranceExhaustedStaminaThreshold.Value ?? 1f; float num3 = settings?.EnduranceMinStaminaUseToTrigger.Value ?? 5f; float num4 = settings?.EnduranceXpFractionOfLastUse.Value ?? 0.02f; float num5 = settings?.EnduranceMaxXpPerExhaust.Value ?? 5f; num = Mathf.Clamp01(num); num2 = Mathf.Max(0f, num2); num3 = Mathf.Max(0f, num3); num4 = Mathf.Max(0f, num4); num5 = Mathf.Max(0f, num5); if (_lockedOut) { float maxStamina = ((Character)player).GetMaxStamina(); if (!(maxStamina > 0f) || !(staminaAfter >= maxStamina * num)) { return; } _lockedOut = false; } if (!(staminaBefore > 0f) || !(staminaAfter <= num2)) { return; } float num6 = Mathf.Max(0f, amountUsedRequested); num6 = Mathf.Min(num6, staminaBefore); if (num6 <= num3) { _lockedOut = true; return; } float num7 = num6 * num4; if (num7 > num5) { num7 = num5; } if (num7 > 0f) { BalrondSkillSystem.RaiseSkill(player, Instance.SkillType, num7); } _lockedOut = true; } } public sealed class ExplorationSkill : BaseSkill { internal static class ExplorationPatches { [HarmonyPatch(typeof(Minimap), "Explore", new Type[] { typeof(Vector3), typeof(float) })] private static class Minimap_Explore_VectorRadius_Patch { [HarmonyPrefix] private static void Prefix(Minimap __instance, Vector3 p, ref float radius) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) s_passForLocalPlayer = false; s_anyNewPixelThisPass = false; Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { Vector3 val = p - ((Component)localPlayer).transform.position; if (!(((Vector3)(ref val)).sqrMagnitude > 4f) && BalrondSkillSystem.IsSkillEnabled(Instance.SkillType)) { s_passForLocalPlayer = true; radius = GetExploreRadiusFor(localPlayer); } } } [HarmonyPostfix] private static void Postfix(Minimap __instance, Vector3 p, float radius) { //IL_00f6: Unknown result type (might be due to invalid IL or missing references) if (!s_passForLocalPlayer || !s_anyNewPixelThisPass) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || ((Character)localPlayer).InCutscene() || ((Character)localPlayer).InIntro() || s_inAward || Time.time < s_nextAllowedAwardTime) { return; } try { s_inAward = true; ExtraSkillsConfig settings = Launch.Settings; float num = settings?.ExplorationXpPerSuccessfulPass.Value ?? 0.08f; float num2 = settings?.ExplorationMinTimeBetweenAwards.Value ?? 1f; num = Mathf.Max(0f, num); num2 = Mathf.Max(0f, num2); if (num > 0f) { BalrondSkillSystem.RaiseSkill(localPlayer, Instance.SkillType, num); } s_nextAllowedAwardTime = Time.time + num2; } finally { s_inAward = false; } } } [HarmonyPatch(typeof(Minimap), "Explore", new Type[] { typeof(int), typeof(int) })] private static class Minimap_Explore_Pixel_Patch { [HarmonyPostfix] private static void Postfix(bool __result) { if (s_passForLocalPlayer && __result) { s_anyNewPixelThisPass = true; } } } } private static ExplorationSkill _instance; private static bool s_inAward; private static bool s_passForLocalPlayer; private static bool s_anyNewPixelThisPass; private static float s_nextAllowedAwardTime; public static ExplorationSkill Instance => _instance ?? (_instance = new ExplorationSkill()); private ExplorationSkill() : base(15004, "explorationskill", "Exploration", "Increases your map discovery range. Improves when you uncover new areas.", "exploration") { } private static float GetExploreRadiusFor(Player player) { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) ExtraSkillsConfig settings = Launch.Settings; float num = settings?.ExplorationBaseRadius.Value ?? 100f; float num2 = settings?.ExplorationBonusRadiusAt100.Value ?? 100f; float num3 = settings?.ExplorationMinRadius.Value ?? 50f; float num4 = settings?.ExplorationMaxRadius.Value ?? 300f; num = Mathf.Max(0f, num); num2 = Mathf.Max(0f, num2); num3 = Mathf.Max(0f, num3); num4 = Mathf.Max(0f, num4); if (num4 < num3) { float num5 = num4; num4 = num3; num3 = num5; } if ((Object)(object)player == (Object)null) { return Mathf.Clamp(num, num3, num4); } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null) { return Mathf.Clamp(num, num3, num4); } float skillLevel = skills.GetSkillLevel(Instance.SkillType); float num6 = Mathf.Clamp01(skillLevel / 100f); float skillEffectMultiplier = BalrondSkillSystem.GetSkillEffectMultiplier(Instance.SkillType); float num7 = num + num2 * num6 * skillEffectMultiplier; return Mathf.Clamp(num7, num3, num4); } } public sealed class FortitudeSkill : BaseSkill { [HarmonyPatch(typeof(Player))] internal static class FortitudeArmorPatch { [HarmonyPatch("GetBodyArmor")] [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Player __instance, ref float __result) { float armorBonus = GetArmorBonus(__instance); __result += armorBonus; } } private static FortitudeSkill _instance; public static FortitudeSkill Instance => _instance ?? (_instance = new FortitudeSkill()); private FortitudeSkill() : base(15005, "fortitudeskill", "Fortitude", "Increases your base armor. Gains experience by surviving damage.", "fortitude") { } internal static float GetArmorBonus(Player player) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0f; } if (!BalrondSkillSystem.IsSkillEnabled(Instance.SkillType)) { return 0f; } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null) { return 0f; } float skillLevel = skills.GetSkillLevel(Instance.SkillType); if (skillLevel <= 0f) { return 0f; } float num = Launch.Settings?.FortitudeArmorBonusAt100.Value ?? 10f; float skillEffectMultiplier = BalrondSkillSystem.GetSkillEffectMultiplier(Instance.SkillType); return skillLevel / 100f * Mathf.Max(0f, num) * skillEffectMultiplier; } internal static void OnDamageTaken(Player player, HitData originalHit, float hpLost) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && BalrondSkillSystem.IsSkillEnabled(Instance.SkillType) && !(hpLost <= 0f) && !((Character)player).IsDead() && !((Character)player).InCutscene() && !((Character)player).InIntro()) { ExtraSkillsConfig settings = Launch.Settings; float num = settings?.FortitudeXpPerHpLost.Value ?? 0.016f; float num2 = settings?.FortitudeMaxXpPerHit.Value ?? 1f; float num3 = settings?.FortitudeElementalXpMultiplier.Value ?? 0.5f; float num4 = hpLost * Mathf.Max(0f, num); if (BalrondDamageHub.IsElemental(originalHit)) { num4 *= Mathf.Max(0f, num3); } num4 = Mathf.Min(num4, Mathf.Max(0f, num2)); if (!(num4 <= 0f)) { BalrondSkillSystem.RaiseSkill(player, Instance.SkillType, num4); } } } } public sealed class HaulingSkill : BaseSkill { internal static class HaulingPatches { [HarmonyPatch] private static class Player_GetMaxCarryWeight_Patch { private static MethodBase TargetMethod() { return ResolveMaxCarryWeightMethod(); } [HarmonyPostfix] private static void Postfix(Player __instance, ref float __result) { if (!((Object)(object)__instance == (Object)null)) { int bonusCarryWeight = GetBonusCarryWeight(__instance); if (bonusCarryWeight > 0) { __result += bonusCarryWeight; } } } } [HarmonyPatch] private static class Skills_RaiseSkill_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Skills), "RaiseSkill", new Type[2] { typeof(SkillType), typeof(float) }, (Type[])null); } [HarmonyPostfix] private static void Postfix(Skills __instance, SkillType skillType, float factor) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) if (s_inHaulingRaise || factor <= 0f || !BalrondSkillSystem.IsSkillEnabled(Instance.SkillType) || !TryGetPiggybackMultiplier(skillType, out var mult) || mult <= 0f) { return; } Player skillsOwner = GetSkillsOwner(__instance); if ((Object)(object)skillsOwner == (Object)null || (Object)(object)skillsOwner != (Object)(object)Player.m_localPlayer || ((Character)skillsOwner).IsDead() || ((Character)skillsOwner).InCutscene() || ((Character)skillsOwner).InIntro() || !IsHeavilyLoaded(skillsOwner)) { return; } float num = ComputeHaulingFactorFromSource(__instance, skillType, factor, mult); if (num <= 0f) { return; } try { s_inHaulingRaise = true; BalrondSkillSystem.RaiseSkill(skillsOwner, Instance.SkillType, num); } finally { s_inHaulingRaise = false; } } } } private static HaulingSkill _instance; private static bool s_inHaulingRaise; private static FieldInfo s_skillsPlayerField; private static MethodInfo s_getMaxCarryWeightMethod; private static bool s_getMaxCarryWeightMethodResolved; public static HaulingSkill Instance => _instance ?? (_instance = new HaulingSkill()); private HaulingSkill() : base(15006, "haulingskill", "Hauling", "Increases your carry weight. Improves by running, sneaking, and jumping while heavily loaded.", "hauling") { } internal static int GetBonusCarryWeight(Player player) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0; } if (!BalrondSkillSystem.IsSkillEnabled(Instance.SkillType)) { return 0; } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null) { return 0; } float skillLevel = skills.GetSkillLevel(Instance.SkillType); if (skillLevel <= 0f) { return 0; } float num = Launch.Settings?.HaulingCarryWeightBonusAt100.Value ?? 50f; float skillEffectMultiplier = BalrondSkillSystem.GetSkillEffectMultiplier(Instance.SkillType); float num2 = Mathf.Max(0f, num) / 50f; return Mathf.FloorToInt(skillLevel / 2f * num2 * skillEffectMultiplier); } private static Player GetSkillsOwner(Skills skills) { if ((Object)(object)skills == (Object)null) { return null; } if (s_skillsPlayerField == null) { s_skillsPlayerField = AccessTools.Field(typeof(Skills), "m_player") ?? AccessTools.Field(typeof(Skills), "m_character") ?? AccessTools.Field(typeof(Skills), "m_owner"); } if (s_skillsPlayerField != null) { try { object? value = s_skillsPlayerField.GetValue(skills); return (Player)((value is Player) ? value : null); } catch { } } return Player.m_localPlayer; } private static float GetCurrentWeight(Player player) { if ((Object)(object)player == (Object)null) { return 0f; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return 0f; } return inventory.GetTotalWeight(); } private static MethodInfo ResolveMaxCarryWeightMethod() { if (s_getMaxCarryWeightMethodResolved) { return s_getMaxCarryWeightMethod; } s_getMaxCarryWeightMethodResolved = true; s_getMaxCarryWeightMethod = AccessTools.Method(typeof(Player), "GetMaxCarryWeight", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Player), "GetMaxCarryCapacity", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Player), "GetMaxCarry", (Type[])null, (Type[])null); return s_getMaxCarryWeightMethod; } private static float GetMaxCarryWeight(Player player) { if ((Object)(object)player == (Object)null) { return 1f; } MethodInfo methodInfo = ResolveMaxCarryWeightMethod(); if (methodInfo != null) { try { if (methodInfo.Invoke(player, null) is float num && num > 0f) { return num; } } catch { } } ExtraSkillsConfig settings = Launch.Settings; return (settings != null) ? Mathf.Max(1f, settings.HaulingFallbackMaxCarryWeight.Value) : 300f; } private static bool IsHeavilyLoaded(Player player) { float maxCarryWeight = GetMaxCarryWeight(player); if (maxCarryWeight <= 0f) { return false; } float currentWeight = GetCurrentWeight(player); float num = Launch.Settings?.HaulingWeightRatioThreshold.Value ?? 0.9f; return currentWeight / maxCarryWeight >= Mathf.Max(0f, num); } private static bool TryGetPiggybackMultiplier(SkillType skillType, out float mult) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected I4, but got Unknown ExtraSkillsConfig settings = Launch.Settings; switch (skillType - 100) { case 2: mult = settings?.HaulingRunXpFraction.Value ?? 0.2f; return true; case 1: mult = settings?.HaulingSneakXpFraction.Value ?? 0.25f; return true; case 0: mult = settings?.HaulingJumpXpFraction.Value ?? 0.35f; return true; default: mult = 0f; return false; } } private static float ComputeHaulingFactorFromSource(Skills skills, SkillType srcSkillType, float srcFactor, float fraction) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)skills == (Object)null) { return 0f; } if (srcFactor <= 0f) { return 0f; } if (fraction <= 0f) { return 0f; } Skill skill = skills.GetSkill(srcSkillType); float num = ((skill != null && skill.m_info != null) ? skill.m_info.m_increseStep : 1f); if (num <= 0f) { num = 1f; } Skill skill2 = skills.GetSkill(Instance.SkillType); float num2 = ((skill2 != null && skill2.m_info != null) ? skill2.m_info.m_increseStep : 1f); if (num2 <= 0f) { num2 = 1f; } float num3 = srcFactor * fraction * (num / num2); float num4 = Launch.Settings?.HaulingMaxFactorPerTrigger.Value ?? 0.35f; num4 = Mathf.Max(0f, num4); if (num3 > num4) { num3 = num4; } return num3; } } public sealed class VitalitySkill : BaseSkill { internal static class VitalityPatches { [HarmonyPatch(typeof(Player), "GetTotalFoodValue")] public static class HealthIncreasePatches { public static void Postfix(Player __instance, ref float hp, ref float stamina, ref float eitr) { int bonusHp = GetBonusHp(__instance); if (bonusHp > 0) { hp += bonusHp; } } } [HarmonyPatch(typeof(Player), "EatFood")] private static class Player_EatFood_Patch { [HarmonyPostfix] private static void Postfix(Player __instance, ItemData item, ref bool __result) { if (__result && !((Object)(object)__instance == (Object)null) && item != null && item.m_shared != null) { float food = item.m_shared.m_food; if (!(food <= 0f)) { RaiseFromFood(__instance, food); } } } } [HarmonyPatch(typeof(Player), "SetSleeping")] private static class Player_SetSleeping_Patch { [HarmonyPostfix] private static void Postfix(Player __instance, bool sleep) { if (!((Object)(object)__instance == (Object)null) && !sleep) { RaiseFromWake(__instance); } } } } private static VitalitySkill _instance; private static float LastSleepAwardTime = float.NegativeInfinity; public static VitalitySkill Instance => _instance ?? (_instance = new VitalitySkill()); private VitalitySkill() : base(15001, "vitalityskill", "Vitality", "Increases your base maximum health. Improves by eating healthy food and sleeping.", "vitality") { } internal static int GetBonusHp(Player player) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0; } if (!BalrondSkillSystem.IsSkillEnabled(Instance.SkillType)) { return 0; } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null) { return 0; } float skillLevel = skills.GetSkillLevel(Instance.SkillType); if (skillLevel <= 0f) { return 0; } float num = Launch.Settings?.VitalityHealthBonusAt100.Value ?? 10f; float skillEffectMultiplier = BalrondSkillSystem.GetSkillEffectMultiplier(Instance.SkillType); float num2 = Mathf.Max(0f, num) / 10f; return Mathf.FloorToInt(skillLevel / 10f * num2 * skillEffectMultiplier); } private static void RaiseFromFood(Player player, float foodHealth) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && BalrondSkillSystem.IsSkillEnabled(Instance.SkillType) && !(foodHealth <= 0f)) { ExtraSkillsConfig settings = Launch.Settings; float num = settings?.VitalityFoodXpPerHealth.Value ?? 0.01f; float num2 = settings?.VitalityMaxFoodXpPerEat.Value ?? 2f; float num3 = foodHealth * Mathf.Max(0f, num); num3 = Mathf.Min(num3, Mathf.Max(0f, num2)); if (!(num3 <= 0f)) { BalrondSkillSystem.RaiseSkill(player, Instance.SkillType, num3); } } } private static void RaiseFromWake(Player player) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !BalrondSkillSystem.IsSkillEnabled(Instance.SkillType)) { return; } ExtraSkillsConfig settings = Launch.Settings; float num = settings?.VitalitySleepAwardCooldownSeconds.Value ?? 2f; num = Mathf.Max(0f, num); if (!(Time.time - LastSleepAwardTime < num)) { float num2 = settings?.VitalitySleepXpPerWake.Value ?? 0.25f; num2 = Mathf.Max(0f, num2); LastSleepAwardTime = Time.time; if (num2 > 0f) { BalrondSkillSystem.RaiseSkill(player, Instance.SkillType, num2); } } } } internal static class MiningSkillBonus { [HarmonyPatch(typeof(Destructible), "RPC_Damage")] private static class Destructible_RPC_Damage_Patch { [HarmonyPrefix] private static void Prefix(ref HitData hit) { TryApplyMiningPickaxeBonus(ref hit); } } [HarmonyPatch(typeof(MineRock), "RPC_Hit")] private static class MineRock_RPC_Hit_Patch { [HarmonyPrefix] private static void Prefix(ref HitData hit) { TryApplyMiningPickaxeBonus(ref hit); } } [HarmonyPatch(typeof(MineRock5), "RPC_Damage")] private static class MineRock5_RPC_Damage_Patch { [HarmonyPrefix] private static void Prefix(ref HitData hit) { TryApplyMiningPickaxeBonus(ref hit); } } private static float GetMiningPickaxeBonusMultiplier(Player p) { if ((Object)(object)p == (Object)null) { return 1f; } ExtraSkillsConfig settings = Launch.Settings; if (settings != null && !settings.MiningBonusEnabled.Value) { return 1f; } Skills skills = ((Character)p).GetSkills(); float num = ((skills != null) ? skills.GetSkillLevel((SkillType)12) : 0f); if (num <= 0f) { return 1f; } float num2 = settings?.MiningBonusMultiplier.Value ?? 1f; float num3 = settings?.MiningPickaxeBonusAt100.Value ?? 0.25f; float num4 = num / 100f * Mathf.Max(0f, num3) * Mathf.Max(0f, num2); return 1f + num4; } private static bool TryApplyMiningPickaxeBonus(ref HitData hit) { if (hit == null) { return false; } if (hit.m_damage.m_pickaxe <= 0f) { return false; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if ((Object)(object)val == (Object)null) { return false; } if ((Object)(object)val != (Object)(object)Player.m_localPlayer) { return false; } float miningPickaxeBonusMultiplier = GetMiningPickaxeBonusMultiplier(val); if (miningPickaxeBonusMultiplier <= 1.0001f) { return false; } hit.m_damage.m_pickaxe *= miningPickaxeBonusMultiplier; return true; } } internal static class WoodcuttingSkillBonus { [HarmonyPatch(typeof(TreeBase), "RPC_Damage")] private static class TreeBase_RPC_Damage_Patch { [HarmonyPrefix] private static void Prefix(ref HitData hit) { ApplyChopBonusIfNeeded(ref hit); } } [HarmonyPatch(typeof(TreeLog), "RPC_Damage")] private static class TreeLog_RPC_Damage_Patch { [HarmonyPrefix] private static void Prefix(ref HitData hit) { ApplyChopBonusIfNeeded(ref hit); } } [HarmonyPatch(typeof(Destructible), "RPC_Damage")] private static class Destructible_RPC_Damage_Patch { [HarmonyPrefix] private static void Prefix(Destructible __instance, ref HitData hit) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 if (!((Object)(object)__instance == (Object)null) && (int)__instance.m_destructibleType == 2) { ApplyChopBonusIfNeeded(ref hit); } } } private static float GetMultiplierFromWoodcutting(Player p) { if ((Object)(object)p == (Object)null) { return 1f; } ExtraSkillsConfig settings = Launch.Settings; if (settings != null && !settings.WoodcuttingBonusEnabled.Value) { return 1f; } Skills skills = ((Character)p).GetSkills(); float num = ((skills != null) ? skills.GetSkillLevel((SkillType)13) : 0f); float num2 = Mathf.Clamp01(num / 100f); float num3 = settings?.WoodcuttingBonusMultiplier.Value ?? 1f; float num4 = settings?.WoodcuttingChopBonusAt100.Value ?? 0.25f; return 1f + Mathf.Max(0f, num4) * num2 * Mathf.Max(0f, num3); } private static bool TryGetAttackerPlayer(HitData hit, out Player player) { player = null; if (hit == null) { return false; } Character attacker = hit.GetAttacker(); player = (Player)(object)((attacker is Player) ? attacker : null); return (Object)(object)player != (Object)null; } private static void ApplyChopBonusIfNeeded(ref HitData hit) { if (hit != null) { float num = Launch.Settings?.WoodcuttingMinChopToConsider.Value ?? 0.001f; if (!(hit.m_damage.m_chop <= Mathf.Max(0f, num)) && TryGetAttackerPlayer(hit, out var player)) { float multiplierFromWoodcutting = GetMultiplierFromWoodcutting(player); hit.m_damage.m_chop *= multiplierFromWoodcutting; } } } } internal static class WoodcuttingVirtualChop { internal static class Patches { [HarmonyPatch(typeof(TreeBase), "RPC_Damage")] private static class TreeBase_RPC_Damage_Patch { [HarmonyPrefix] private static void Prefix(HitData hit) { TryAddVirtualChop(hit); } } [HarmonyPatch(typeof(TreeLog), "RPC_Damage")] private static class TreeLog_RPC_Damage_Patch { [HarmonyPrefix] private static void Prefix(HitData hit) { TryAddVirtualChop(hit); } } [HarmonyPatch(typeof(Destructible), "RPC_Damage")] private static class Destructible_RPC_Damage_Patch { [HarmonyPrefix] private static void Prefix(Destructible __instance, HitData hit) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 if (!((Object)(object)__instance == (Object)null) && (int)__instance.m_destructibleType == 2) { TryAddVirtualChop(hit); } } } } private static void TryAddVirtualChop(HitData hit) { if (hit == null) { return; } ExtraSkillsConfig settings = Launch.Settings; if (settings != null && !settings.WoodcuttingVirtualChopEnabled.Value) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return; } Skills skills = ((Character)val).GetSkills(); float num = ((skills != null) ? skills.GetSkillLevel((SkillType)13) : 0f); if (num <= 0f || ((settings == null || settings.WoodcuttingVirtualChopOnlyIfNoChopDamage.Value) && hit.m_damage.m_chop > 0f)) { return; } float num2 = Mathf.Clamp01(num / 100f); float num3 = settings?.WoodcuttingVirtualChopMultiplier.Value ?? 1f; float num4 = settings?.WoodcuttingVirtualChopMaxConvertAt100.Value ?? 0.12f; float num5 = num2 * Mathf.Max(0f, num4) * Mathf.Max(0f, num3); if (num5 <= 0f) { return; } float num6 = hit.m_damage.m_damage + hit.m_damage.m_blunt + hit.m_damage.m_slash + hit.m_damage.m_pierce; if (!(num6 <= 0f)) { float num7 = num6 * num5; if (!(num7 <= 0f)) { hit.m_damage.m_chop += num7; } } } } [HarmonyPatch] internal static class TranslationPatches { [HarmonyPatch(typeof(FejdStartup), "SetupGui")] private class FejdStartup_SetupGUI { private static void Postfix() { string selectedLanguage = Localization.instance.GetSelectedLanguage(); Dictionary translations = GetTranslations(selectedLanguage); AddTranslations(translations); } } [HarmonyPriority(800)] [HarmonyPatch(typeof(Localization), "SetupLanguage")] private class Translation_SetupLanguage { private static void Prefix(Localization __instance, string language) { Dictionary translations = GetTranslations(language); AddTranslations(translations, __instance); } } [HarmonyPriority(800)] [HarmonyPatch(typeof(Localization), "LoadCSV")] private class Translation_LoadCSV { private static void Prefix(Localization __instance, string language) { Dictionary translations = GetTranslations(language); AddTranslations(translations, __instance); } } private static Dictionary GetTranslations(string language) { Dictionary result = BalrondTranslator.getLanguage("English"); if (!string.Equals(language, "English", StringComparison.OrdinalIgnoreCase)) { Dictionary language2 = BalrondTranslator.getLanguage(language); if (language2 != null) { result = language2; } else { Debug.Log((object)("BalrondExtraSkills: Did not find translation file for '" + language + "', loading English")); } } return result; } private static void AddTranslations(Dictionary translations, Localization localizationInstance = null) { if (translations == null) { Debug.LogWarning((object)"BalrondExtraSkills: No translation file found!"); return; } if (localizationInstance != null) { foreach (KeyValuePair translation in translations) { localizationInstance.AddWord(translation.Key, translation.Value); } return; } foreach (KeyValuePair translation2 in translations) { Localization.instance.AddWord(translation2.Key, translation2.Value); } } } } namespace LitJson2 { internal enum JsonType { None, Object, Array, String, Int, Long, Double, Boolean } internal interface IJsonWrapper : IList, IOrderedDictionary, IDictionary, ICollection, IEnumerable { bool IsArray { get; } bool IsBoolean { get; } bool IsDouble { get; } bool IsInt { get; } bool IsLong { get; } bool IsObject { get; } bool IsString { get; } bool GetBoolean(); double GetDouble(); int GetInt(); JsonType GetJsonType(); long GetLong(); string GetString(); void SetBoolean(bool val); void SetDouble(double val); void SetInt(int val); void SetJsonType(JsonType type); void SetLong(long val); void SetString(string val); string ToJson(); void ToJson(JsonWriter writer); } internal class JsonData : IJsonWrapper, IList, IOrderedDictionary, IDictionary, ICollection, IEnumerable, IEquatable { private IList inst_array; private bool inst_boolean; private double inst_double; private int inst_int; private long inst_long; private IDictionary inst_object; private string inst_string; private string json; private JsonType type; private IList> object_list; public int Count => EnsureCollection().Count; public bool IsArray => type == JsonType.Array; public bool IsBoolean => type == JsonType.Boolean; public bool IsDouble => type == JsonType.Double; public bool IsInt => type == JsonType.Int; public bool IsLong => type == JsonType.Long; public bool IsObject => type == JsonType.Object; public bool IsString => type == JsonType.String; public ICollection Keys { get { EnsureDictionary(); return inst_object.Keys; } } int ICollection.Count => Count; bool ICollection.IsSynchronized => EnsureCollection().IsSynchronized; object ICollection.SyncRoot => EnsureCollection().SyncRoot; bool IDictionary.IsFixedSize => EnsureDictionary().IsFixedSize; bool IDictionary.IsReadOnly => EnsureDictionary().IsReadOnly; ICollection IDictionary.Keys { get { EnsureDictionary(); IList list = new List(); foreach (KeyValuePair item in object_list) { list.Add(item.Key); } return (ICollection)list; } } ICollection IDictionary.Values { get { EnsureDictionary(); IList list = new List(); foreach (KeyValuePair item in object_list) { list.Add(item.Value); } return (ICollection)list; } } bool IJsonWrapper.IsArray => IsArray; bool IJsonWrapper.IsBoolean => IsBoolean; bool IJsonWrapper.IsDouble => IsDouble; bool IJsonWrapper.IsInt => IsInt; bool IJsonWrapper.IsLong => IsLong; bool IJsonWrapper.IsObject => IsObject; bool IJsonWrapper.IsString => IsString; bool IList.IsFixedSize => EnsureList().IsFixedSize; bool IList.IsReadOnly => EnsureList().IsReadOnly; object IDictionary.this[object key] { get { return EnsureDictionary()[key]; } set { if (!(key is string)) { throw new ArgumentException("The key has to be a string"); } JsonData value2 = ToJsonData(value); this[(string)key] = value2; } } object IOrderedDictionary.this[int idx] { get { EnsureDictionary(); return object_list[idx].Value; } set { EnsureDictionary(); JsonData value2 = ToJsonData(value); KeyValuePair keyValuePair = object_list[idx]; inst_object[keyValuePair.Key] = value2; KeyValuePair value3 = new KeyValuePair(keyValuePair.Key, value2); object_list[idx] = value3; } } object IList.this[int index] { get { return EnsureList()[index]; } set { EnsureList(); JsonData value2 = ToJsonData(value); this[index] = value2; } } public JsonData this[string prop_name] { get { EnsureDictionary(); return inst_object[prop_name]; } set { EnsureDictionary(); KeyValuePair keyValuePair = new KeyValuePair(prop_name, value); if (inst_object.ContainsKey(prop_name)) { for (int i = 0; i < object_list.Count; i++) { if (object_list[i].Key == prop_name) { object_list[i] = keyValuePair; break; } } } else { object_list.Add(keyValuePair); } inst_object[prop_name] = value; json = null; } } public JsonData this[int index] { get { EnsureCollection(); if (type == JsonType.Array) { return inst_array[index]; } return object_list[index].Value; } set { EnsureCollection(); if (type == JsonType.Array) { inst_array[index] = value; } else { KeyValuePair keyValuePair = object_list[index]; KeyValuePair value2 = new KeyValuePair(keyValuePair.Key, value); object_list[index] = value2; inst_object[keyValuePair.Key] = value; } json = null; } } public JsonData() { } public JsonData(bool boolean) { type = JsonType.Boolean; inst_boolean = boolean; } public JsonData(double number) { type = JsonType.Double; inst_double = number; } public JsonData(int number) { type = JsonType.Int; inst_int = number; } public JsonData(long number) { type = JsonType.Long; inst_long = number; } public JsonData(object obj) { if (obj is bool) { type = JsonType.Boolean; inst_boolean = (bool)obj; return; } if (obj is double) { type = JsonType.Double; inst_double = (double)obj; return; } if (obj is int) { type = JsonType.Int; inst_int = (int)obj; return; } if (obj is long) { type = JsonType.Long; inst_long = (long)obj; return; } if (obj is string) { type = JsonType.String; inst_string = (string)obj; return; } throw new ArgumentException("Unable to wrap the given object with JsonData"); } public JsonData(string str) { type = JsonType.String; inst_string = str; } public static implicit operator JsonData(bool data) { return new JsonData(data); } public static implicit operator JsonData(double data) { return new JsonData(data); } public static implicit operator JsonData(int data) { return new JsonData(data); } public static implicit operator JsonData(long data) { return new JsonData(data); } public static implicit operator JsonData(string data) { return new JsonData(data); } public static explicit operator bool(JsonData data) { if (data.type != JsonType.Boolean) { throw new InvalidCastException("Instance of JsonData doesn't hold a double"); } return data.inst_boolean; } public static explicit operator double(JsonData data) { if (data.type != JsonType.Double) { throw new InvalidCastException("Instance of JsonData doesn't hold a double"); } return data.inst_double; } public static explicit operator int(JsonData data) { if (data.type != JsonType.Int) { throw new InvalidCastException("Instance of JsonData doesn't hold an int"); } return data.inst_int; } public static explicit operator long(JsonData data) { if (data.type != JsonType.Long) { throw new InvalidCastException("Instance of JsonData doesn't hold an int"); } return data.inst_long; } public static explicit operator string(JsonData data) { if (data.type != JsonType.String) { throw new InvalidCastException("Instance of JsonData doesn't hold a string"); } return data.inst_string; } void ICollection.CopyTo(Array array, int index) { EnsureCollection().CopyTo(array, index); } void IDictionary.Add(object key, object value) { JsonData value2 = ToJsonData(value); EnsureDictionary().Add(key, value2); KeyValuePair item = new KeyValuePair((string)key, value2); object_list.Add(item); json = null; } void IDictionary.Clear() { EnsureDictionary().Clear(); object_list.Clear(); json = null; } bool IDictionary.Contains(object key) { return EnsureDictionary().Contains(key); } IDictionaryEnumerator IDictionary.GetEnumerator() { return ((IOrderedDictionary)this).GetEnumerator(); } void IDictionary.Remove(object key) { EnsureDictionary().Remove(key); for (int i = 0; i < object_list.Count; i++) { if (object_list[i].Key == (string)key) { object_list.RemoveAt(i); break; } } json = null; } IEnumerator IEnumerable.GetEnumerator() { return EnsureCollection().GetEnumerator(); } bool IJsonWrapper.GetBoolean() { if (type != JsonType.Boolean) { throw new InvalidOperationException("JsonData instance doesn't hold a boolean"); } return inst_boolean; } double IJsonWrapper.GetDouble() { if (type != JsonType.Double) { throw new InvalidOperationException("JsonData instance doesn't hold a double"); } return inst_double; } int IJsonWrapper.GetInt() { if (type != JsonType.Int) { throw new InvalidOperationException("JsonData instance doesn't hold an int"); } return inst_int; } long IJsonWrapper.GetLong() { if (type != JsonType.Long) { throw new InvalidOperationException("JsonData instance doesn't hold a long"); } return inst_long; } string IJsonWrapper.GetString() { if (type != JsonType.String) { throw new InvalidOperationException("JsonData instance doesn't hold a string"); } return inst_string; } void IJsonWrapper.SetBoolean(bool val) { type = JsonType.Boolean; inst_boolean = val; json = null; } void IJsonWrapper.SetDouble(double val) { type = JsonType.Double; inst_double = val; json = null; } void IJsonWrapper.SetInt(int val) { type = JsonType.Int; inst_int = val; json = null; } void IJsonWrapper.SetLong(long val) { type = JsonType.Long; inst_long = val; json = null; } void IJsonWrapper.SetString(string val) { type = JsonType.String; inst_string = val; json = null; } string IJsonWrapper.ToJson() { return ToJson(); } void IJsonWrapper.ToJson(JsonWriter writer) { ToJson(writer); } int IList.Add(object value) { return Add(value); } void IList.Clear() { EnsureList().Clear(); json = null; } bool IList.Contains(object value) { return EnsureList().Contains(value); } int IList.IndexOf(object value) { return EnsureList().IndexOf(value); } void IList.Insert(int index, object value) { EnsureList().Insert(index, value); json = null; } void IList.Remove(object value) { EnsureList().Remove(value); json = null; } void IList.RemoveAt(int index) { EnsureList().RemoveAt(index); json = null; } IDictionaryEnumerator IOrderedDictionary.GetEnumerator() { EnsureDictionary(); return new OrderedDictionaryEnumerator(object_list.GetEnumerator()); } void IOrderedDictionary.Insert(int idx, object key, object value) { string text = (string)key; JsonData value2 = (this[text] = ToJsonData(value)); KeyValuePair item = new KeyValuePair(text, value2); object_list.Insert(idx, item); } void IOrderedDictionary.RemoveAt(int idx) { EnsureDictionary(); inst_object.Remove(object_list[idx].Key); object_list.RemoveAt(idx); } private ICollection EnsureCollection() { if (type == JsonType.Array) { return (ICollection)inst_array; } if (type == JsonType.Object) { return (ICollection)inst_object; } throw new InvalidOperationException("The JsonData instance has to be initialized first"); } private IDictionary EnsureDictionary() { if (type == JsonType.Object) { return (IDictionary)inst_object; } if (type != JsonType.None) { throw new InvalidOperationException("Instance of JsonData is not a dictionary"); } type = JsonType.Object; inst_object = new Dictionary(); object_list = new List>(); return (IDictionary)inst_object; } private IList EnsureList() { if (type == JsonType.Array) { return (IList)inst_array; } if (type != JsonType.None) { throw new InvalidOperationException("Instance of JsonData is not a list"); } type = JsonType.Array; inst_array = new List(); return (IList)inst_array; } private JsonData ToJsonData(object obj) { if (obj == null) { return null; } if (obj is JsonData) { return (JsonData)obj; } return new JsonData(obj); } private static void WriteJson(IJsonWrapper obj, JsonWriter writer) { if (obj == null) { writer.Write(null); } else if (obj.IsString) { writer.Write(obj.GetString()); } else if (obj.IsBoolean) { writer.Write(obj.GetBoolean()); } else if (obj.IsDouble) { writer.Write(obj.GetDouble()); } else if (obj.IsInt) { writer.Write(obj.GetInt()); } else if (obj.IsLong) { writer.Write(obj.GetLong()); } else if (obj.IsArray) { writer.WriteArrayStart(); foreach (object item in (IEnumerable)obj) { WriteJson((JsonData)item, writer); } writer.WriteArrayEnd(); } else { if (!obj.IsObject) { return; } writer.WriteObjectStart(); foreach (DictionaryEntry item2 in (IDictionary)obj) { writer.WritePropertyName((string)item2.Key); WriteJson((JsonData)item2.Value, writer); } writer.WriteObjectEnd(); } } public int Add(object value) { JsonData value2 = ToJsonData(value); json = null; return EnsureList().Add(value2); } public void Clear() { if (IsObject) { ((IDictionary)this).Clear(); } else if (IsArray) { ((IList)this).Clear(); } } public bool Equals(JsonData x) { if (x == null) { return false; } if (x.type != type) { return false; } return type switch { JsonType.None => true, JsonType.Object => inst_object.Equals(x.inst_object), JsonType.Array => inst_array.Equals(x.inst_array), JsonType.String => inst_string.Equals(x.inst_string), JsonType.Int => inst_int.Equals(x.inst_int), JsonType.Long => inst_long.Equals(x.inst_long), JsonType.Double => inst_double.Equals(x.inst_double), JsonType.Boolean => inst_boolean.Equals(x.inst_boolean), _ => false, }; } public JsonType GetJsonType() { return type; } public void SetJsonType(JsonType type) { if (this.type != type) { switch (type) { case JsonType.Object: inst_object = new Dictionary(); object_list = new List>(); break; case JsonType.Array: inst_array = new List(); break; case JsonType.String: inst_string = null; break; case JsonType.Int: inst_int = 0; break; case JsonType.Long: inst_long = 0L; break; case JsonType.Double: inst_double = 0.0; break; case JsonType.Boolean: inst_boolean = false; break; } this.type = type; } } public string ToJson() { if (json != null) { return json; } StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); jsonWriter.Validate = false; WriteJson(this, jsonWriter); json = stringWriter.ToString(); return json; } public void ToJson(JsonWriter writer) { bool validate = writer.Validate; writer.Validate = false; WriteJson(this, writer); writer.Validate = validate; } public override string ToString() { return type switch { JsonType.Array => "JsonData array", JsonType.Boolean => inst_boolean.ToString(), JsonType.Double => inst_double.ToString(), JsonType.Int => inst_int.ToString(), JsonType.Long => inst_long.ToString(), JsonType.Object => "JsonData object", JsonType.String => inst_string, _ => "Uninitialized JsonData", }; } } internal class OrderedDictionaryEnumerator : IDictionaryEnumerator, IEnumerator { private IEnumerator> list_enumerator; public object Current => Entry; public DictionaryEntry Entry { get { KeyValuePair current = list_enumerator.Current; return new DictionaryEntry(current.Key, current.Value); } } public object Key => list_enumerator.Current.Key; public object Value => list_enumerator.Current.Value; public OrderedDictionaryEnumerator(IEnumerator> enumerator) { list_enumerator = enumerator; } public bool MoveNext() { return list_enumerator.MoveNext(); } public void Reset() { list_enumerator.Reset(); } } internal class JsonException : ApplicationException { public JsonException() { } internal JsonException(ParserToken token) : base($"Invalid token '{token}' in input string") { } internal JsonException(ParserToken token, Exception inner_exception) : base($"Invalid token '{token}' in input string", inner_exception) { } internal JsonException(int c) : base($"Invalid character '{(char)c}' in input string") { } internal JsonException(int c, Exception inner_exception) : base($"Invalid character '{(char)c}' in input string", inner_exception) { } public JsonException(string message) : base(message) { } public JsonException(string message, Exception inner_exception) : base(message, inner_exception) { } } internal struct PropertyMetadata { public MemberInfo Info; public bool IsField; public Type Type; } internal struct ArrayMetadata { private Type element_type; private bool is_array; private bool is_list; public Type ElementType { get { if (element_type == null) { return typeof(JsonData); } return element_type; } set { element_type = value; } } public bool IsArray { get { return is_array; } set { is_array = value; } } public bool IsList { get { return is_list; } set { is_list = value; } } } internal struct ObjectMetadata { private Type element_type; private bool is_dictionary; private IDictionary properties; public Type ElementType { get { if (element_type == null) { return typeof(JsonData); } return element_type; } set { element_type = value; } } public bool IsDictionary { get { return is_dictionary; } set { is_dictionary = value; } } public IDictionary Properties { get { return properties; } set { properties = value; } } } internal delegate void ExporterFunc(object obj, JsonWriter writer); internal delegate void ExporterFunc(T obj, JsonWriter writer); internal delegate object ImporterFunc(object input); internal delegate TValue ImporterFunc(TJson input); internal delegate IJsonWrapper WrapperFactory(); internal class JsonMapper { private static int max_nesting_depth; private static IFormatProvider datetime_format; private static IDictionary base_exporters_table; private static IDictionary custom_exporters_table; private static IDictionary> base_importers_table; private static IDictionary> custom_importers_table; private static IDictionary array_metadata; private static readonly object array_metadata_lock; private static IDictionary> conv_ops; private static readonly object conv_ops_lock; private static IDictionary object_metadata; private static readonly object object_metadata_lock; private static IDictionary> type_properties; private static readonly object type_properties_lock; private static JsonWriter static_writer; private static readonly object static_writer_lock; static JsonMapper() { array_metadata_lock = new object(); conv_ops_lock = new object(); object_metadata_lock = new object(); type_properties_lock = new object(); static_writer_lock = new object(); max_nesting_depth = 100; array_metadata = new Dictionary(); conv_ops = new Dictionary>(); object_metadata = new Dictionary(); type_properties = new Dictionary>(); static_writer = new JsonWriter(); datetime_format = DateTimeFormatInfo.InvariantInfo; base_exporters_table = new Dictionary(); custom_exporters_table = new Dictionary(); base_importers_table = new Dictionary>(); custom_importers_table = new Dictionary>(); RegisterBaseExporters(); RegisterBaseImporters(); } private static void AddArrayMetadata(Type type) { if (array_metadata.ContainsKey(type)) { return; } ArrayMetadata value = new ArrayMetadata { IsArray = type.IsArray }; if (type.GetInterface("System.Collections.IList") != null) { value.IsList = true; } PropertyInfo[] properties = type.GetProperties(); foreach (PropertyInfo propertyInfo in properties) { if (!(propertyInfo.Name != "Item")) { ParameterInfo[] indexParameters = propertyInfo.GetIndexParameters(); if (indexParameters.Length == 1 && indexParameters[0].ParameterType == typeof(int)) { value.ElementType = propertyInfo.PropertyType; } } } lock (array_metadata_lock) { try { array_metadata.Add(type, value); } catch (ArgumentException) { } } } private static void AddObjectMetadata(Type type) { if (object_metadata.ContainsKey(type)) { return; } ObjectMetadata value = default(ObjectMetadata); if (type.GetInterface("System.Collections.IDictionary") != null) { value.IsDictionary = true; } value.Properties = new Dictionary(); PropertyInfo[] properties = type.GetProperties(); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.Name == "Item") { ParameterInfo[] indexParameters = propertyInfo.GetIndexParameters(); if (indexParameters.Length == 1 && indexParameters[0].ParameterType == typeof(string)) { value.ElementType = propertyInfo.PropertyType; } } else { PropertyMetadata value2 = new PropertyMetadata { Info = propertyInfo, Type = propertyInfo.PropertyType }; value.Properties.Add(propertyInfo.Name, value2); } } FieldInfo[] fields = type.GetFields(); foreach (FieldInfo fieldInfo in fields) { PropertyMetadata value3 = new PropertyMetadata { Info = fieldInfo, IsField = true, Type = fieldInfo.FieldType }; value.Properties.Add(fieldInfo.Name, value3); } lock (object_metadata_lock) { try { object_metadata.Add(type, value); } catch (ArgumentException) { } } } private static void AddTypeProperties(Type type) { if (type_properties.ContainsKey(type)) { return; } IList list = new List(); PropertyInfo[] properties = type.GetProperties(); foreach (PropertyInfo propertyInfo in properties) { if (!(propertyInfo.Name == "Item")) { list.Add(new PropertyMetadata { Info = propertyInfo, IsField = false }); } } FieldInfo[] fields = type.GetFields(); foreach (FieldInfo info in fields) { list.Add(new PropertyMetadata { Info = info, IsField = true }); } lock (type_properties_lock) { try { type_properties.Add(type, list); } catch (ArgumentException) { } } } private static MethodInfo GetConvOp(Type t1, Type t2) { lock (conv_ops_lock) { if (!conv_ops.ContainsKey(t1)) { conv_ops.Add(t1, new Dictionary()); } } if (conv_ops[t1].ContainsKey(t2)) { return conv_ops[t1][t2]; } MethodInfo method = t1.GetMethod("op_Implicit", new Type[1] { t2 }); lock (conv_ops_lock) { try { conv_ops[t1].Add(t2, method); return method; } catch (ArgumentException) { return conv_ops[t1][t2]; } } } private static object ReadValue(Type inst_type, JsonReader reader) { reader.Read(); if (reader.Token == JsonToken.ArrayEnd) { return null; } Type underlyingType = Nullable.GetUnderlyingType(inst_type); Type type = underlyingType ?? inst_type; if (reader.Token == JsonToken.Null) { if (inst_type.IsClass || underlyingType != null) { return null; } throw new JsonException($"Can't assign null to an instance of type {inst_type}"); } if (reader.Token == JsonToken.Double || reader.Token == JsonToken.Int || reader.Token == JsonToken.Long || reader.Token == JsonToken.String || reader.Token == JsonToken.Boolean) { Type type2 = reader.Value.GetType(); if (type.IsAssignableFrom(type2)) { return reader.Value; } if (custom_importers_table.ContainsKey(type2) && custom_importers_table[type2].ContainsKey(type)) { ImporterFunc importerFunc = custom_importers_table[type2][type]; return importerFunc(reader.Value); } if (base_importers_table.ContainsKey(type2) && base_importers_table[type2].ContainsKey(type)) { ImporterFunc importerFunc2 = base_importers_table[type2][type]; return importerFunc2(reader.Value); } if (type.IsEnum) { return Enum.ToObject(type, reader.Value); } MethodInfo convOp = GetConvOp(type, type2); if (convOp != null) { return convOp.Invoke(null, new object[1] { reader.Value }); } throw new JsonException($"Can't assign value '{reader.Value}' (type {type2}) to type {inst_type}"); } object obj = null; if (reader.Token == JsonToken.ArrayStart) { AddArrayMetadata(inst_type); ArrayMetadata arrayMetadata = array_metadata[inst_type]; if (!arrayMetadata.IsArray && !arrayMetadata.IsList) { throw new JsonException($"Type {inst_type} can't act as an array"); } IList list; Type elementType; if (!arrayMetadata.IsArray) { list = (IList)Activator.CreateInstance(inst_type); elementType = arrayMetadata.ElementType; } else { list = new ArrayList(); elementType = inst_type.GetElementType(); } while (true) { object obj2 = ReadValue(elementType, reader); if (obj2 == null && reader.Token == JsonToken.ArrayEnd) { break; } list.Add(obj2); } if (arrayMetadata.IsArray) { int count = list.Count; obj = Array.CreateInstance(elementType, count); for (int i = 0; i < count; i++) { ((Array)obj).SetValue(list[i], i); } } else { obj = list; } } else if (reader.Token == JsonToken.ObjectStart) { AddObjectMetadata(type); ObjectMetadata objectMetadata = object_metadata[type]; obj = Activator.CreateInstance(type); while (true) { reader.Read(); if (reader.Token == JsonToken.ObjectEnd) { break; } string text = (string)reader.Value; if (objectMetadata.Properties.ContainsKey(text)) { PropertyMetadata propertyMetadata = objectMetadata.Properties[text]; if (propertyMetadata.IsField) { ((FieldInfo)propertyMetadata.Info).SetValue(obj, ReadValue(propertyMetadata.Type, reader)); continue; } PropertyInfo propertyInfo = (PropertyInfo)propertyMetadata.Info; if (propertyInfo.CanWrite) { propertyInfo.SetValue(obj, ReadValue(propertyMetadata.Type, reader), null); } else { ReadValue(propertyMetadata.Type, reader); } } else if (!objectMetadata.IsDictionary) { if (!reader.SkipNonMembers) { throw new JsonException($"The type {inst_type} doesn't have the property '{text}'"); } ReadSkip(reader); } else { ((IDictionary)obj).Add(text, ReadValue(objectMetadata.ElementType, reader)); } } } return obj; } private static IJsonWrapper ReadValue(WrapperFactory factory, JsonReader reader) { reader.Read(); if (reader.Token == JsonToken.ArrayEnd || reader.Token == JsonToken.Null) { return null; } IJsonWrapper jsonWrapper = factory(); if (reader.Token == JsonToken.String) { jsonWrapper.SetString((string)reader.Value); return jsonWrapper; } if (reader.Token == JsonToken.Double) { jsonWrapper.SetDouble((double)reader.Value); return jsonWrapper; } if (reader.Token == JsonToken.Int) { jsonWrapper.SetInt((int)reader.Value); return jsonWrapper; } if (reader.Token == JsonToken.Long) { jsonWrapper.SetLong((long)reader.Value); return jsonWrapper; } if (reader.Token == JsonToken.Boolean) { jsonWrapper.SetBoolean((bool)reader.Value); return jsonWrapper; } if (reader.Token == JsonToken.ArrayStart) { jsonWrapper.SetJsonType(JsonType.Array); while (true) { IJsonWrapper jsonWrapper2 = ReadValue(factory, reader); if (jsonWrapper2 == null && reader.Token == JsonToken.ArrayEnd) { break; } jsonWrapper.Add(jsonWrapper2); } } else if (reader.Token == JsonToken.ObjectStart) { jsonWrapper.SetJsonType(JsonType.Object); while (true) { reader.Read(); if (reader.Token == JsonToken.ObjectEnd) { break; } string key = (string)reader.Value; jsonWrapper[key] = ReadValue(factory, reader); } } return jsonWrapper; } private static void ReadSkip(JsonReader reader) { ToWrapper(() => new JsonMockWrapper(), reader); } private static void RegisterBaseExporters() { base_exporters_table[typeof(byte)] = delegate(object obj, JsonWriter writer) { writer.Write(Convert.ToInt32((byte)obj)); }; base_exporters_table[typeof(char)] = delegate(object obj, JsonWriter writer) { writer.Write(Convert.ToString((char)obj)); }; base_exporters_table[typeof(DateTime)] = delegate(object obj, JsonWriter writer) { writer.Write(Convert.ToString((DateTime)obj, datetime_format)); }; base_exporters_table[typeof(decimal)] = delegate(object obj, JsonWriter writer) { writer.Write((decimal)obj); }; base_exporters_table[typeof(sbyte)] = delegate(object obj, JsonWriter writer) { writer.Write(Convert.ToInt32((sbyte)obj)); }; base_exporters_table[typeof(short)] = delegate(object obj, JsonWriter writer) { writer.Write(Convert.ToInt32((short)obj)); }; base_exporters_table[typeof(ushort)] = delegate(object obj, JsonWriter writer) { writer.Write(Convert.ToInt32((ushort)obj)); }; base_exporters_table[typeof(uint)] = delegate(object obj, JsonWriter writer) { writer.Write(Convert.ToUInt64((uint)obj)); }; base_exporters_table[typeof(ulong)] = delegate(object obj, JsonWriter writer) { writer.Write((ulong)obj); }; } private static void RegisterBaseImporters() { ImporterFunc importer = (object input) => Convert.ToByte((int)input); RegisterImporter(base_importers_table, typeof(int), typeof(byte), importer); importer = (object input) => Convert.ToUInt64((int)input); RegisterImporter(base_importers_table, typeof(int), typeof(ulong), importer); importer = (object input) => Convert.ToSByte((int)input); RegisterImporter(base_importers_table, typeof(int), typeof(sbyte), importer); importer = (object input) => Convert.ToInt16((int)input); RegisterImporter(base_importers_table, typeof(int), typeof(short), importer); importer = (object input) => Convert.ToUInt16((int)input); RegisterImporter(base_importers_table, typeof(int), typeof(ushort), importer); importer = (object input) => Convert.ToUInt32((int)input); RegisterImporter(base_importers_table, typeof(int), typeof(uint), importer); importer = (object input) => Convert.ToSingle((int)input); RegisterImporter(base_importers_table, typeof(int), typeof(float), importer); importer = (object input) => Convert.ToDouble((int)input); RegisterImporter(base_importers_table, typeof(int), typeof(double), importer); importer = (object input) => Convert.ToDecimal((double)input); RegisterImporter(base_importers_table, typeof(double), typeof(decimal), importer); importer = (object input) => Convert.ToUInt32((long)input); RegisterImporter(base_importers_table, typeof(long), typeof(uint), importer); importer = (object input) => Convert.ToChar((string)input); RegisterImporter(base_importers_table, typeof(string), typeof(char), importer); importer = (object input) => Convert.ToDateTime((string)input, datetime_format); RegisterImporter(base_importers_table, typeof(string), typeof(DateTime), importer); } private static void RegisterImporter(IDictionary> table, Type json_type, Type value_type, ImporterFunc importer) { if (!table.ContainsKey(json_type)) { table.Add(json_type, new Dictionary()); } table[json_type][value_type] = importer; } private static void WriteValue(object obj, JsonWriter writer, bool writer_is_private, int depth) { if (depth > max_nesting_depth) { throw new JsonException($"Max allowed object depth reached while trying to export from type {obj.GetType()}"); } if (obj == null) { writer.Write(null); return; } if (obj is IJsonWrapper) { if (writer_is_private) { writer.TextWriter.Write(((IJsonWrapper)obj).ToJson()); } else { ((IJsonWrapper)obj).ToJson(writer); } return; } if (obj is string) { writer.Write((string)obj); return; } if (obj is double) { writer.Write((double)obj); return; } if (obj is int) { writer.Write((int)obj); return; } if (obj is bool) { writer.Write((bool)obj); return; } if (obj is long) { writer.Write((long)obj); return; } if (obj is Array) { writer.WriteArrayStart(); foreach (object item in (Array)obj) { WriteValue(item, writer, writer_is_private, depth + 1); } writer.WriteArrayEnd(); return; } if (obj is IList) { writer.WriteArrayStart(); foreach (object item2 in (IList)obj) { WriteValue(item2, writer, writer_is_private, depth + 1); } writer.WriteArrayEnd(); return; } if (obj is IDictionary) { writer.WriteObjectStart(); foreach (DictionaryEntry item3 in (IDictionary)obj) { writer.WritePropertyName((string)item3.Key); WriteValue(item3.Value, writer, writer_is_private, depth + 1); } writer.WriteObjectEnd(); return; } Type type = obj.GetType(); if (custom_exporters_table.ContainsKey(type)) { ExporterFunc exporterFunc = custom_exporters_table[type]; exporterFunc(obj, writer); return; } if (base_exporters_table.ContainsKey(type)) { ExporterFunc exporterFunc2 = base_exporters_table[type]; exporterFunc2(obj, writer); return; } if (obj is Enum) { Type underlyingType = Enum.GetUnderlyingType(type); if (underlyingType == typeof(long) || underlyingType == typeof(uint) || underlyingType == typeof(ulong)) { writer.Write((ulong)obj); } else { writer.Write((int)obj); } return; } AddTypeProperties(type); IList list = type_properties[type]; writer.WriteObjectStart(); foreach (PropertyMetadata item4 in list) { if (item4.IsField) { writer.WritePropertyName(item4.Info.Name); WriteValue(((FieldInfo)item4.Info).GetValue(obj), writer, writer_is_private, depth + 1); continue; } PropertyInfo propertyInfo = (PropertyInfo)item4.Info; if (propertyInfo.CanRead) { writer.WritePropertyName(item4.Info.Name); WriteValue(propertyInfo.GetValue(obj, null), writer, writer_is_private, depth + 1); } } writer.WriteObjectEnd(); } public static string ToJson(object obj) { lock (static_writer_lock) { static_writer.Reset(); WriteValue(obj, static_writer, writer_is_private: true, 0); return static_writer.ToString(); } } public static void ToJson(object obj, JsonWriter writer) { WriteValue(obj, writer, writer_is_private: false, 0); } public static JsonData ToObject(JsonReader reader) { return (JsonData)ToWrapper(() => new JsonData(), reader); } public static JsonData ToObject(TextReader reader) { JsonReader reader2 = new JsonReader(reader); return (JsonData)ToWrapper(() => new JsonData(), reader2); } public static JsonData ToObject(string json) { return (JsonData)ToWrapper(() => new JsonData(), json); } public static T ToObject(JsonReader reader) { return (T)ReadValue(typeof(T), reader); } public static T ToObject(TextReader reader) { JsonReader reader2 = new JsonReader(reader); return (T)ReadValue(typeof(T), reader2); } public static T ToObject(string json) { JsonReader reader = new JsonReader(json); return (T)ReadValue(typeof(T), reader); } public static IJsonWrapper ToWrapper(WrapperFactory factory, JsonReader reader) { return ReadValue(factory, reader); } public static IJsonWrapper ToWrapper(WrapperFactory factory, string json) { JsonReader reader = new JsonReader(json); return ReadValue(factory, reader); } public static void RegisterExporter(ExporterFunc exporter) { ExporterFunc value = delegate(object obj, JsonWriter writer) { exporter((T)obj, writer); }; custom_exporters_table[typeof(T)] = value; } public static void RegisterImporter(ImporterFunc importer) { ImporterFunc importer2 = (object input) => importer((TJson)input); RegisterImporter(custom_importers_table, typeof(TJson), typeof(TValue), importer2); } public static void UnregisterExporters() { custom_exporters_table.Clear(); } public static void UnregisterImporters() { custom_importers_table.Clear(); } } internal class JsonMockWrapper : IJsonWrapper, IList, IOrderedDictionary, IDictionary, ICollection, IEnumerable { public bool IsArray => false; public bool IsBoolean => false; public bool IsDouble => false; public bool IsInt => false; public bool IsLong => false; public bool IsObject => false; public bool IsString => false; bool IList.IsFixedSize => true; bool IList.IsReadOnly => true; object IList.this[int index] { get { return null; } set { } } int ICollection.Count => 0; bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => null; bool IDictionary.IsFixedSize => true; bool IDictionary.IsReadOnly => true; ICollection IDictionary.Keys => null; ICollection IDictionary.Values => null; object IDictionary.this[object key] { get { return null; } set { } } object IOrderedDictionary.this[int idx] { get { return null; } set { } } public bool GetBoolean() { return false; } public double GetDouble() { return 0.0; } public int GetInt() { return 0; } public JsonType GetJsonType() { return JsonType.None; } public long GetLong() { return 0L; } public string GetString() { return ""; } public void SetBoolean(bool val) { } public void SetDouble(double val) { } public void SetInt(int val) { } public void SetJsonType(JsonType type) { } public void SetLong(long val) { } public void SetString(string val) { } public string ToJson() { return ""; } public void ToJson(JsonWriter writer) { } int IList.Add(object value) { return 0; } void IList.Clear() { } bool IList.Contains(object value) { return false; } int IList.IndexOf(object value) { return -1; } void IList.Insert(int i, object v) { } void IList.Remove(object value) { } void IList.RemoveAt(int index) { } void ICollection.CopyTo(Array array, int index) { } IEnumerator IEnumerable.GetEnumerator() { return null; } void IDictionary.Add(object k, object v) { } void IDictionary.Clear() { } bool IDictionary.Contains(object key) { return false; } void IDictionary.Remove(object key) { } IDictionaryEnumerator IDictionary.GetEnumerator() { return null; } IDictionaryEnumerator IOrderedDictionary.GetEnumerator() { return null; } void IOrderedDictionary.Insert(int i, object k, object v) { } void IOrderedDictionary.RemoveAt(int i) { } } internal enum JsonToken { None, ObjectStart, PropertyName, ObjectEnd, ArrayStart, ArrayEnd, Int, Long, Double, String, Boolean, Null } internal class JsonReader { private static IDictionary> parse_table; private Stack automaton_stack; private int current_input; private int current_symbol; private bool end_of_json; private bool end_of_input; private Lexer lexer; private bool parser_in_string; private bool parser_return; private bool read_started; private TextReader reader; private bool reader_is_owned; private bool skip_non_members; private object token_value; private JsonToken token; public bool AllowComments { get { return lexer.AllowComments; } set { lexer.AllowComments = value; } } public bool AllowSingleQuotedStrings { get { return lexer.AllowSingleQuotedStrings; } set { lexer.AllowSingleQuotedStrings = value; } } public bool SkipNonMembers { get { return skip_non_members; } set { skip_non_members = value; } } public bool EndOfInput => end_of_input; public bool EndOfJson => end_of_json; public JsonToken Token => token; public object Value => token_value; static JsonReader() { PopulateParseTable(); } public JsonReader(string json_text) : this(new StringReader(json_text), owned: true) { } public JsonReader(TextReader reader) : this(reader, owned: false) { } private JsonReader(TextReader reader, bool owned) { if (reader == null) { throw new ArgumentNullException("reader"); } parser_in_string = false; parser_return = false; read_started = false; automaton_stack = new Stack(); automaton_stack.Push(65553); automaton_stack.Push(65543); lexer = new Lexer(reader); end_of_input = false; end_of_json = false; skip_non_members = true; this.reader = reader; reader_is_owned = owned; } private static void PopulateParseTable() { parse_table = new Dictionary>(); TableAddRow(ParserToken.Array); TableAddCol(ParserToken.Array, 91, 91, 65549); TableAddRow(ParserToken.ArrayPrime); TableAddCol(ParserToken.ArrayPrime, 34, 65550, 65551, 93); TableAddCol(ParserToken.ArrayPrime, 91, 65550, 65551, 93); TableAddCol(ParserToken.ArrayPrime, 93, 93); TableAddCol(ParserToken.ArrayPrime, 123, 65550, 65551, 93); TableAddCol(ParserToken.ArrayPrime, 65537, 65550, 65551, 93); TableAddCol(ParserToken.ArrayPrime, 65538, 65550, 65551, 93); TableAddCol(ParserToken.ArrayPrime, 65539, 65550, 65551, 93); TableAddCol(ParserToken.ArrayPrime, 65540, 65550, 65551, 93); TableAddRow(ParserToken.Object); TableAddCol(ParserToken.Object, 123, 123, 65545); TableAddRow(ParserToken.ObjectPrime); TableAddCol(ParserToken.ObjectPrime, 34, 65546, 65547, 125); TableAddCol(ParserToken.ObjectPrime, 125, 125); TableAddRow(ParserToken.Pair); TableAddCol(ParserToken.Pair, 34, 65552, 58, 65550); TableAddRow(ParserToken.PairRest); TableAddCol(ParserToken.PairRest, 44, 44, 65546, 65547); TableAddCol(ParserToken.PairRest, 125, 65554); TableAddRow(ParserToken.String); TableAddCol(ParserToken.String, 34, 34, 65541, 34); TableAddRow(ParserToken.Text); TableAddCol(ParserToken.Text, 91, 65548); TableAddCol(ParserToken.Text, 123, 65544); TableAddRow(ParserToken.Value); TableAddCol(ParserToken.Value, 34, 65552); TableAddCol(ParserToken.Value, 91, 65548); TableAddCol(ParserToken.Value, 123, 65544); TableAddCol(ParserToken.Value, 65537, 65537); TableAddCol(ParserToken.Value, 65538, 65538); TableAddCol(ParserToken.Value, 65539, 65539); TableAddCol(ParserToken.Value, 65540, 65540); TableAddRow(ParserToken.ValueRest); TableAddCol(ParserToken.ValueRest, 44, 44, 65550, 65551); TableAddCol(ParserToken.ValueRest, 93, 65554); } private static void TableAddCol(ParserToken row, int col, params int[] symbols) { parse_table[(int)row].Add(col, symbols); } private static void TableAddRow(ParserToken rule) { parse_table.Add((int)rule, new Dictionary()); } private void ProcessNumber(string number) { int result2; long result3; ulong result4; if ((number.IndexOf('.') != -1 || number.IndexOf('e') != -1 || number.IndexOf('E') != -1) && double.TryParse(number, out var result)) { token = JsonToken.Double; token_value = result; } else if (int.TryParse(number, out result2)) { token = JsonToken.Int; token_value = result2; } else if (long.TryParse(number, out result3)) { token = JsonToken.Long; token_value = result3; } else if (ulong.TryParse(number, out result4)) { token = JsonToken.Long; token_value = result4; } else { token = JsonToken.Int; token_value = 0; } } private void ProcessSymbol() { if (current_symbol == 91) { token = JsonToken.ArrayStart; parser_return = true; } else if (current_symbol == 93) { token = JsonToken.ArrayEnd; parser_return = true; } else if (current_symbol == 123) { token = JsonToken.ObjectStart; parser_return = true; } else if (current_symbol == 125) { token = JsonToken.ObjectEnd; parser_return = true; } else if (current_symbol == 34) { if (parser_in_string) { parser_in_string = false; parser_return = true; return; } if (token == JsonToken.None) { token = JsonToken.String; } parser_in_string = true; } else if (current_symbol == 65541) { token_value = lexer.StringValue; } else if (current_symbol == 65539) { token = JsonToken.Boolean; token_value = false; parser_return = true; } else if (current_symbol == 65540) { token = JsonToken.Null; parser_return = true; } else if (current_symbol == 65537) { ProcessNumber(lexer.StringValue); parser_return = true; } else if (current_symbol == 65546) { token = JsonToken.PropertyName; } else if (current_symbol == 65538) { token = JsonToken.Boolean; token_value = true; parser_return = true; } } private bool ReadToken() { if (end_of_input) { return false; } lexer.NextToken(); if (lexer.EndOfInput) { Close(); return false; } current_input = lexer.Token; return true; } public void Close() { if (!end_of_input) { end_of_input = true; end_of_json = true; if (reader_is_owned) { reader.Close(); } reader = null; } } public bool Read() { if (end_of_input) { return false; } if (end_of_json) { end_of_json = false; automaton_stack.Clear(); automaton_stack.Push(65553); automaton_stack.Push(65543); } parser_in_string = false; parser_return = false; token = JsonToken.None; token_value = null; if (!read_started) { read_started = true; if (!ReadToken()) { return false; } } while (true) { if (parser_return) { if (automaton_stack.Peek() == 65553) { end_of_json = true; } return true; } current_symbol = automaton_stack.Pop(); ProcessSymbol(); if (current_symbol == current_input) { if (!ReadToken()) { break; } continue; } int[] array; try { array = parse_table[current_symbol][current_input]; } catch (KeyNotFoundException inner_exception) { throw new JsonException((ParserToken)current_input, inner_exception); } if (array[0] != 65554) { for (int num = array.Length - 1; num >= 0; num--) { automaton_stack.Push(array[num]); } } } if (automaton_stack.Peek() != 65553) { throw new JsonException("Input doesn't evaluate to proper JSON text"); } if (parser_return) { return true; } return false; } } internal enum Condition { InArray, InObject, NotAProperty, Property, Value } internal class WriterContext { public int Count; public bool InArray; public bool InObject; public bool ExpectingValue; public int Padding; } internal class JsonWriter { private static NumberFormatInfo number_format; private WriterContext context; private Stack ctx_stack; private bool has_reached_end; private char[] hex_seq; private int indentation; private int indent_value; private StringBuilder inst_string_builder; private bool pretty_print; private bool validate; private TextWriter writer; public int IndentValue { get { return indent_value; } set { indentation = indentation / indent_value * value; indent_value = value; } } public bool PrettyPrint { get { return pretty_print; } set { pretty_print = value; } } public TextWriter TextWriter => writer; public bool Validate { get { return validate; } set { validate = value; } } static JsonWriter() { number_format = NumberFormatInfo.InvariantInfo; } public JsonWriter() { inst_string_builder = new StringBuilder(); writer = new StringWriter(inst_string_builder); Init(); } public JsonWriter(StringBuilder sb) : this(new StringWriter(sb)) { } public JsonWriter(TextWriter writer) { if (writer == null) { throw new ArgumentNullException("writer"); } this.writer = writer; Init(); } private void DoValidation(Condition cond) { if (!context.ExpectingValue) { context.Count++; } if (!validate) { return; } if (has_reached_end) { throw new JsonException("A complete JSON symbol has already been written"); } switch (cond) { case Condition.InArray: if (!context.InArray) { throw new JsonException("Can't close an array here"); } break; case Condition.InObject: if (!context.InObject || context.ExpectingValue) { throw new JsonException("Can't close an object here"); } break; case Condition.NotAProperty: if (context.InObject && !context.ExpectingValue) { throw new JsonException("Expected a property"); } break; case Condition.Property: if (!context.InObject || context.ExpectingValue) { throw new JsonException("Can't add a property here"); } break; case Condition.Value: if (!context.InArray && (!context.InObject || !context.ExpectingValue)) { throw new JsonException("Can't add a value here"); } break; } } private void Init() { has_reached_end = false; hex_seq = new char[4]; indentation = 0; indent_value = 4; pretty_print = false; validate = true; ctx_stack = new Stack(); context = new WriterContext(); ctx_stack.Push(context); } private static void IntToHex(int n, char[] hex) { for (int i = 0; i < 4; i++) { int num = n % 16; if (num < 10) { hex[3 - i] = (char)(48 + num); } else { hex[3 - i] = (char)(65 + (num - 10)); } n >>= 4; } } private void Indent() { if (pretty_print) { indentation += indent_value; } } private void Put(string str) { if (pretty_print && !context.ExpectingValue) { for (int i = 0; i < indentation; i++) { writer.Write(' '); } } writer.Write(str); } private void PutNewline() { PutNewline(add_comma: true); } private void PutNewline(bool add_comma) { if (add_comma && !context.ExpectingValue && context.Count > 1) { writer.Write(','); } if (pretty_print && !context.ExpectingValue) { writer.Write('\n'); } } private void PutString(string str) { Put(string.Empty); writer.Write('"'); int length = str.Length; for (int i = 0; i < length; i++) { switch (str[i]) { case '\n': writer.Write("\\n"); continue; case '\r': writer.Write("\\r"); continue; case '\t': writer.Write("\\t"); continue; case '"': case '\\': writer.Write('\\'); writer.Write(str[i]); continue; case '\f': writer.Write("\\f"); continue; case '\b': writer.Write("\\b"); continue; } if (str[i] >= ' ' && str[i] <= '~') { writer.Write(str[i]); continue; } IntToHex(str[i], hex_seq); writer.Write("\\u"); writer.Write(hex_seq); } writer.Write('"'); } private void Unindent() { if (pretty_print) { indentation -= indent_value; } } public override string ToString() { if (inst_string_builder == null) { return string.Empty; } return inst_string_builder.ToString(); } public void Reset() { has_reached_end = false; ctx_stack.Clear(); context = new WriterContext(); ctx_stack.Push(context); if (inst_string_builder != null) { inst_string_builder.Remove(0, inst_string_builder.Length); } } public void Write(bool boolean) { DoValidation(Condition.Value); PutNewline(); Put(boolean ? "true" : "false"); context.ExpectingValue = false; } public void Write(decimal number) { DoValidation(Condition.Value); PutNewline(); Put(Convert.ToString(number, number_format)); context.ExpectingValue = false; } public void Write(double number) { DoValidation(Condition.Value); PutNewline(); string text = Convert.ToString(number, number_format); Put(text); if (text.IndexOf('.') == -1 && text.IndexOf('E') == -1) { writer.Write(".0"); } context.ExpectingValue = false; } public void Write(int number) { DoValidation(Condition.Value); PutNewline(); Put(Convert.ToString(number, number_format)); context.ExpectingValue = false; } public void Write(long number) { DoValidation(Condition.Value); PutNewline(); Put(Convert.ToString(number, number_format)); context.ExpectingValue = false; } public void Write(string str) { DoValidation(Condition.Value); PutNewline(); if (str == null) { Put("null"); } else { PutString(str); } context.ExpectingValue = false; } [CLSCompliant(false)] public void Write(ulong number) { DoValidation(Condition.Value); PutNewline(); Put(Convert.ToString(number, number_format)); context.ExpectingValue = false; } public void WriteArrayEnd() { DoValidation(Condition.InArray); PutNewline(add_comma: false); ctx_stack.Pop(); if (ctx_stack.Count == 1) { has_reached_end = true; } else { context = ctx_stack.Peek(); context.ExpectingValue = false; } Unindent(); Put("]"); } public void WriteArrayStart() { DoValidation(Condition.NotAProperty); PutNewline(); Put("["); context = new WriterContext(); context.InArray = true; ctx_stack.Push(context); Indent(); } public void WriteObjectEnd() { DoValidation(Condition.InObject); PutNewline(add_comma: false); ctx_stack.Pop(); if (ctx_stack.Count == 1) { has_reached_end = true; } else { context = ctx_stack.Peek(); context.ExpectingValue = false; } Unindent(); Put("}"); } public void WriteObjectStart() { DoValidation(Condition.NotAProperty); PutNewline(); Put("{"); context = new WriterContext(); context.InObject = true; ctx_stack.Push(context); Indent(); } public void WritePropertyName(string property_name) { DoValidation(Condition.Property); PutNewline(); PutString(property_name); if (pretty_print) { if (property_name.Length > context.Padding) { context.Padding = property_name.Length; } for (int num = context.Padding - property_name.Length; num >= 0; num--) { writer.Write(' '); } writer.Write(": "); } else { writer.Write(':'); } context.ExpectingValue = true; } } internal class FsmContext { public bool Return; public int NextState; public Lexer L; public int StateStack; } internal class Lexer { private delegate bool StateHandler(FsmContext ctx); private static int[] fsm_return_table; private static StateHandler[] fsm_handler_table; private bool allow_comments; private bool allow_single_quoted_strings; private bool end_of_input; private FsmContext fsm_context; private int input_buffer; private int input_char; private TextReader reader; private int state; private StringBuilder string_buffer; private string string_value; private int token; private int unichar; public bool AllowComments { get { return allow_comments; } set { allow_comments = value; } } public bool AllowSingleQuotedStrings { get { return allow_single_quoted_strings; } set { allow_single_quoted_strings = value; } } public bool EndOfInput => end_of_input; public int Token => token; public string StringValue => string_value; static Lexer() { PopulateFsmTables(); } public Lexer(TextReader reader) { allow_comments = true; allow_single_quoted_strings = true; input_buffer = 0; string_buffer = new StringBuilder(128); state = 1; end_of_input = false; this.reader = reader; fsm_context = new FsmContext(); fsm_context.L = this; } private static int HexValue(int digit) { switch (digit) { case 65: case 97: return 10; case 66: case 98: return 11; case 67: case 99: return 12; case 68: case 100: return 13; case 69: case 101: return 14; case 70: case 102: return 15; default: return digit - 48; } } private static void PopulateFsmTables() { fsm_handler_table = new StateHandler[28] { State1, State2, State3, State4, State5, State6, State7, State8, State9, State10, State11, State12, State13, State14, State15, State16, State17, State18, State19, State20, State21, State22, State23, State24, State25, State26, State27, State28 }; fsm_return_table = new int[28] { 65542, 0, 65537, 65537, 0, 65537, 0, 65537, 0, 0, 65538, 0, 0, 0, 65539, 0, 0, 65540, 65541, 65542, 0, 0, 65541, 65542, 0, 0, 0, 0 }; } private static char ProcessEscChar(int esc_char) { switch (esc_char) { case 34: case 39: case 47: case 92: return Convert.ToChar(esc_char); case 110: return '\n'; case 116: return '\t'; case 114: return '\r'; case 98: return '\b'; case 102: return '\f'; default: return '?'; } } private static bool State1(FsmContext ctx) { while (ctx.L.GetChar()) { if (ctx.L.input_char == 32 || (ctx.L.input_char >= 9 && ctx.L.input_char <= 13)) { continue; } if (ctx.L.input_char >= 49 && ctx.L.input_char <= 57) { ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 3; return true; } switch (ctx.L.input_char) { case 34: ctx.NextState = 19; ctx.Return = true; return true; case 44: case 58: case 91: case 93: case 123: case 125: ctx.NextState = 1; ctx.Return = true; return true; case 45: ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 2; return true; case 48: ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 4; return true; case 102: ctx.NextState = 12; return true; case 110: ctx.NextState = 16; return true; case 116: ctx.NextState = 9; return true; case 39: if (!ctx.L.allow_single_quoted_strings) { return false; } ctx.L.input_char = 34; ctx.NextState = 23; ctx.Return = true; return true; case 47: if (!ctx.L.allow_comments) { return false; } ctx.NextState = 25; return true; default: return false; } } return true; } private static bool State2(FsmContext ctx) { ctx.L.GetChar(); if (ctx.L.input_char >= 49 && ctx.L.input_char <= 57) { ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 3; return true; } int num = ctx.L.input_char; if (num == 48) { ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 4; return true; } return false; } private static bool State3(FsmContext ctx) { while (ctx.L.GetChar()) { if (ctx.L.input_char >= 48 && ctx.L.input_char <= 57) { ctx.L.string_buffer.Append((char)ctx.L.input_char); continue; } if (ctx.L.input_char == 32 || (ctx.L.input_char >= 9 && ctx.L.input_char <= 13)) { ctx.Return = true; ctx.NextState = 1; return true; } switch (ctx.L.input_char) { case 44: case 93: case 125: ctx.L.UngetChar(); ctx.Return = true; ctx.NextState = 1; return true; case 46: ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 5; return true; case 69: case 101: ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 7; return true; default: return false; } } return true; } private static bool State4(FsmContext ctx) { ctx.L.GetChar(); if (ctx.L.input_char == 32 || (ctx.L.input_char >= 9 && ctx.L.input_char <= 13)) { ctx.Return = true; ctx.NextState = 1; return true; } switch (ctx.L.input_char) { case 44: case 93: case 125: ctx.L.UngetChar(); ctx.Return = true; ctx.NextState = 1; return true; case 46: ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 5; return true; case 69: case 101: ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 7; return true; default: return false; } } private static bool State5(FsmContext ctx) { ctx.L.GetChar(); if (ctx.L.input_char >= 48 && ctx.L.input_char <= 57) { ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 6; return true; } return false; } private static bool State6(FsmContext ctx) { while (ctx.L.GetChar()) { if (ctx.L.input_char >= 48 && ctx.L.input_char <= 57) { ctx.L.string_buffer.Append((char)ctx.L.input_char); continue; } if (ctx.L.input_char == 32 || (ctx.L.input_char >= 9 && ctx.L.input_char <= 13)) { ctx.Return = true; ctx.NextState = 1; return true; } switch (ctx.L.input_char) { case 44: case 93: case 125: ctx.L.UngetChar(); ctx.Return = true; ctx.NextState = 1; return true; case 69: case 101: ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 7; return true; default: return false; } } return true; } private static bool State7(FsmContext ctx) { ctx.L.GetChar(); if (ctx.L.input_char >= 48 && ctx.L.input_char <= 57) { ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 8; return true; } switch (ctx.L.input_char) { case 43: case 45: ctx.L.string_buffer.Append((char)ctx.L.input_char); ctx.NextState = 8; return true; default: return false; } } private static bool State8(FsmContext ctx) { while (ctx.L.GetChar()) { if (ctx.L.input_char >= 48 && ctx.L.input_char <= 57) { ctx.L.string_buffer.Append((char)ctx.L.input_char); continue; } if (ctx.L.input_char == 32 || (ctx.L.input_char >= 9 && ctx.L.input_char <= 13)) { ctx.Return = true; ctx.NextState = 1; return true; } int num = ctx.L.input_char; if (num == 44 || num == 93 || num == 125) { ctx.L.UngetChar(); ctx.Return = true; ctx.NextState = 1; return true; } return false; } return true; } private static bool State9(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 114) { ctx.NextState = 10; return true; } return false; } private static bool State10(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 117) { ctx.NextState = 11; return true; } return false; } private static bool State11(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 101) { ctx.Return = true; ctx.NextState = 1; return true; } return false; } private static bool State12(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 97) { ctx.NextState = 13; return true; } return false; } private static bool State13(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 108) { ctx.NextState = 14; return true; } return false; } private static bool State14(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 115) { ctx.NextState = 15; return true; } return false; } private static bool State15(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 101) { ctx.Return = true; ctx.NextState = 1; return true; } return false; } private static bool State16(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 117) { ctx.NextState = 17; return true; } return false; } private static bool State17(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 108) { ctx.NextState = 18; return true; } return false; } private static bool State18(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 108) { ctx.Return = true; ctx.NextState = 1; return true; } return false; } private static bool State19(FsmContext ctx) { while (ctx.L.GetChar()) { switch (ctx.L.input_char) { case 34: ctx.L.UngetChar(); ctx.Return = true; ctx.NextState = 20; return true; case 92: ctx.StateStack = 19; ctx.NextState = 21; return true; } ctx.L.string_buffer.Append((char)ctx.L.input_char); } return true; } private static bool State20(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 34) { ctx.Return = true; ctx.NextState = 1; return true; } return false; } private static bool State21(FsmContext ctx) { ctx.L.GetChar(); switch (ctx.L.input_char) { case 117: ctx.NextState = 22; return true; case 34: case 39: case 47: case 92: case 98: case 102: case 110: case 114: case 116: ctx.L.string_buffer.Append(ProcessEscChar(ctx.L.input_char)); ctx.NextState = ctx.StateStack; return true; default: return false; } } private static bool State22(FsmContext ctx) { int num = 0; int num2 = 4096; ctx.L.unichar = 0; while (ctx.L.GetChar()) { if ((ctx.L.input_char >= 48 && ctx.L.input_char <= 57) || (ctx.L.input_char >= 65 && ctx.L.input_char <= 70) || (ctx.L.input_char >= 97 && ctx.L.input_char <= 102)) { ctx.L.unichar += HexValue(ctx.L.input_char) * num2; num++; num2 /= 16; if (num == 4) { ctx.L.string_buffer.Append(Convert.ToChar(ctx.L.unichar)); ctx.NextState = ctx.StateStack; return true; } continue; } return false; } return true; } private static bool State23(FsmContext ctx) { while (ctx.L.GetChar()) { switch (ctx.L.input_char) { case 39: ctx.L.UngetChar(); ctx.Return = true; ctx.NextState = 24; return true; case 92: ctx.StateStack = 23; ctx.NextState = 21; return true; } ctx.L.string_buffer.Append((char)ctx.L.input_char); } return true; } private static bool State24(FsmContext ctx) { ctx.L.GetChar(); int num = ctx.L.input_char; if (num == 39) { ctx.L.input_char = 34; ctx.Return = true; ctx.NextState = 1; return true; } return false; } private static bool State25(FsmContext ctx) { ctx.L.GetChar(); switch (ctx.L.input_char) { case 42: ctx.NextState = 27; return true; case 47: ctx.NextState = 26; return true; default: return false; } } private static bool State26(FsmContext ctx) { while (ctx.L.GetChar()) { if (ctx.L.input_char == 10) { ctx.NextState = 1; return true; } } return true; } private static bool State27(FsmContext ctx) { while (ctx.L.GetChar()) { if (ctx.L.input_char == 42) { ctx.NextState = 28; return true; } } return true; } private static bool State28(FsmContext ctx) { while (ctx.L.GetChar()) { if (ctx.L.input_char != 42) { if (ctx.L.input_char == 47) { ctx.NextState = 1; return true; } ctx.NextState = 27; return true; } } return true; } private bool GetChar() { if ((input_char = NextChar()) != -1) { return true; } end_of_input = true; return false; } private int NextChar() { if (input_buffer != 0) { int result = input_buffer; input_buffer = 0; return result; } return reader.Read(); } public bool NextToken() { fsm_context.Return = false; while (true) { StateHandler stateHandler = fsm_handler_table[state - 1]; if (!stateHandler(fsm_context)) { throw new JsonException(input_char); } if (end_of_input) { return false; } if (fsm_context.Return) { break; } state = fsm_context.NextState; } string_value = string_buffer.ToString(); string_buffer.Remove(0, string_buffer.Length); token = fsm_return_table[state - 1]; if (token == 65542) { token = input_char; } state = fsm_context.NextState; return true; } private void UngetChar() { input_buffer = input_char; } } internal enum ParserToken { None = 65536, Number, True, False, Null, CharSeq, Char, Text, Object, ObjectPrime, Pair, PairRest, Array, ArrayPrime, Value, ValueRest, String, End, Epsilon } } namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry(ConfigEntry sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry 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; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; 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; 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; }; } } [PublicAPI] internal sealed class CustomSyncedValue : 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; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register(configSync2.Name + " ConfigSync", (Action)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()); } static void SendAdmin(List 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)); } } 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 CurrentList = new List(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List(adminList.GetList()); List 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 nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register(configSync.Name + " ConfigSync", (Action)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary configValues = new Dictionary(); public readonly Dictionary customValues = new Dictionary(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] 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; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List Package = new List(); 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); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary? __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(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary __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 entries = new List(); 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((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] 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")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] 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; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet configSyncs; private readonly HashSet allConfigs = new HashSet(); private HashSet allCustomValues = new HashSet(); private static bool isServer; private static bool lockExempt; 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> configValueCache = new Dictionary>(); private readonly List> cacheExpirations = new List>(); 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; public event Action? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry AddConfigEntry(ConfigEntry configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry syncedEntry = ownConfigEntryBase as SyncedConfigEntry; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty()).Concat(new SyncedConfigEntry[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry AddLockingConfigEntry(ConfigEntry lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((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(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, 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(delegate(KeyValuePair 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 SortedDictionary value)) { value = new SortedDictionary(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair(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((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 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 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 dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (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((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(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((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((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator 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 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().GetEnumerator(); } List list = (List)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != ZRoutedRpc.Everybody) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List 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> writers = (from p in peers where p.IsReady() select distributeConfigToPeers(p, package)).ToList(); writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator 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)); } } } private static OwnConfigEntryBase? configData(ConfigEntryBase config) { return config.Description.Tags?.OfType().SingleOrDefault(); } public static SyncedConfigEntry? ConfigData(ConfigEntry config) { return ((ConfigEntryBase)config).Description.Tags?.OfType>().SingleOrDefault(); } private static T configAttribute(ConfigEntryBase config) { return config.Description.Tags.OfType().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(IEnumerable? configs = null, IEnumerable? customValues = null, IEnumerable? 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 list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List(); List list2 = customValues?.ToList() ?? new List(); 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()) { 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, 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) && 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 source = new List(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [PublicAPI] [HarmonyPatch] internal class VersionCheck { private static readonly HashSet versionChecks; private static readonly Dictionary notProcessedNames; public string Name; private string? displayName; private string? currentVersion; private string? minimumRequiredVersion; public bool ModRequired = true; private string? ReceivedCurrentVersion; private string? ReceivedMinimumRequiredVersion; private readonly List ValidatedClients = new List(); 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((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0) { return; } Harmony val = new Harmony("org.bepinex.helpers.ServerSync"); foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) }) where t.IsClass select t) { val.PatchAll(item); } } static VersionCheck() { versionChecks = new HashSet(); notProcessedNames = new Dictionary(); typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1] { new Action(PatchServerSync) }); } public VersionCheck(string name) { Name = name; ModRequired = true; versionChecks.Add(this); } public VersionCheck(ConfigSync configSync) { ConfigSync = configSync; Name = ConfigSync.Name; versionChecks.Add(this); } public void Initialize() { ReceivedCurrentVersion = null; ReceivedMinimumRequiredVersion = null; if (ConfigSync != null) { Name = ConfigSync.Name; DisplayName = ConfigSync.DisplayName; CurrentVersion = ConfigSync.CurrentVersion; MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion; ModRequired = ConfigSync.ModRequired; } } private bool IsVersionOk() { if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null) { return !ModRequired; } bool flag = new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion); bool flag2 = new Version(ReceivedCurrentVersion) >= new Version(MinimumRequiredVersion); return flag && flag2; } private string ErrorClient() { if (ReceivedMinimumRequiredVersion == null) { return DisplayName + " is not installed on the server."; } return (new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion)) ? (DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + ".") : (DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + "."); } private string ErrorServer(ZRpc rpc) { return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion; } private string Error(ZRpc? rpc = null) { return (rpc == null) ? ErrorClient() : ErrorServer(rpc); } private static VersionCheck[] GetFailedClient() { return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray(); } private static VersionCheck[] GetFailedServer(ZRpc rpc) { return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc)).ToArray(); } private static void Logout() { Game.instance.Logout(true, true); AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3); } private static void DisconnectClient(ZRpc rpc) { rpc.Invoke("Error", new object[1] { 3 }); } private static void CheckVersion(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, null); } private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action? original) { string text = pkg.ReadString(); string text2 = pkg.ReadString(); string text3 = pkg.ReadString(); bool flag = false; foreach (VersionCheck versionCheck in versionChecks) { if (!(text != versionCheck.Name)) { Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + ".")); versionCheck.ReceivedMinimumRequiredVersion = text2; versionCheck.ReceivedCurrentVersion = text3; if (ZNet.instance.IsServer() && versionCheck.IsVersionOk()) { versionCheck.ValidatedClients.Add(rpc); } flag = true; } } if (flag) { return; } pkg.SetPos(0); if (original != null) { original(rpc, pkg); if (pkg.GetPos() == 0) { notProcessedNames.Add(text, text3); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPrefix] private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance) { VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient()); if (array.Length == 0) { return true; } VersionCheck[] array2 = array; foreach (VersionCheck versionCheck in array2) { Debug.LogWarning((object)versionCheck.Error(rpc)); } if (__instance.IsServer()) { DisconnectClient(rpc); } else { Logout(); } return false; } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance) { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown notProcessedNames.Clear(); IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc); if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck"))) { object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")]; Action action = (Action)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj); peer.m_rpc.Register("ServerSync VersionCheck", (Action)delegate(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, action); }); } else { peer.m_rpc.Register("ServerSync VersionCheck", (Action)CheckVersion); } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.Initialize(); if (versionCheck.ModRequired || __instance.IsServer()) { Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + ".")); ZPackage val = new ZPackage(); val.Write(versionCheck.Name); val.Write(versionCheck.MinimumRequiredVersion); val.Write(versionCheck.CurrentVersion); peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val }); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance) { if (!__instance.IsServer()) { return; } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.ValidatedClients.Remove(peer.m_rpc); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] [HarmonyPostfix] private static void ShowConnectionError(FejdStartup __instance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3) { return; } bool flag = false; VersionCheck[] failedClient = GetFailedClient(); if (failedClient.Length != 0) { string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error())); TMP_Text connectionFailedError = __instance.m_connectionFailedError; connectionFailedError.text = connectionFailedError.text + "\n" + text; flag = true; } foreach (KeyValuePair item in notProcessedNames.OrderBy, string>((KeyValuePair kv) => kv.Key)) { if (!__instance.m_connectionFailedError.text.Contains(item.Key)) { TMP_Text connectionFailedError2 = __instance.m_connectionFailedError; connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed."; flag = true; } } if (flag) { RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent(); Vector2 sizeDelta = component.sizeDelta; sizeDelta.x = 675f; component.sizeDelta = sizeDelta; __instance.m_connectionFailedError.ForceMeshUpdate(false, false); float num = __instance.m_connectionFailedError.renderedHeight + 105f; RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent(); component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f); sizeDelta = component.sizeDelta; sizeDelta.y = num; component.sizeDelta = sizeDelta; } } } }