Decompiled source of ModifiersFramework v1.0.2

dannanboi_ModifiersFramework.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using ModifiersFramework.Compatibility;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ModifiersFramework")]
[assembly: AssemblyDescription("Shared Valheim modifiers, source lifetimes and buff display")]
[assembly: AssemblyCompany("dannanboi")]
[assembly: AssemblyProduct("ModifiersFramework")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2")]
[assembly: AssemblyVersion("1.0.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ModifiersFramework
{
	public struct SkillBonus
	{
		public SkillType Skill;

		public float Value;
	}
	public sealed class ModifierSet
	{
		internal readonly SkillBonus[] SkillValues;

		internal readonly EffectBonus[] EffectValues;

		internal readonly AddedDamageBonus[] AddedDamageValues;

		internal readonly float HealthInstant;

		internal readonly float StaminaInstant;

		internal readonly float EitrInstant;

		internal readonly bool HasNamedFlags;

		public IReadOnlyList<SkillBonus> Skills { get; }

		public IReadOnlyList<EffectBonus> Effects { get; }

		public IReadOnlyList<AddedDamageBonus> AddedDamage { get; }

		public string Canonical { get; }

		public bool HasAny { get; }

		public string CustomBuffName { get; }

		public static ModifierSet Empty { get; } = new ModifierSet(null, null);

		public ModifierSet(IEnumerable<SkillBonus> skills, IEnumerable<EffectBonus> effects)
			: this(skills, effects, null)
		{
		}

		public ModifierSet(IEnumerable<SkillBonus> skills, IEnumerable<EffectBonus> effects, IEnumerable<AddedDamageBonus> addedDamage)
			: this(skills, effects, addedDamage, null)
		{
		}

		internal unsafe ModifierSet(IEnumerable<SkillBonus> skills, IEnumerable<EffectBonus> effects, IEnumerable<AddedDamageBonus> addedDamage, string customBuffName)
		{
			//IL_00af: 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_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Invalid comparison between Unknown and I4
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			CustomBuffName = customBuffName;
			SkillValues = (skills ?? Array.Empty<SkillBonus>()).ToArray();
			EffectValues = (effects ?? Array.Empty<EffectBonus>()).ToArray();
			AddedDamageValues = (addedDamage ?? Array.Empty<AddedDamageBonus>()).ToArray();
			if (SkillValues.Length > 64 || EffectValues.Length > 43 || AddedDamageValues.Length > 5)
			{
				throw new FormatException("Too many modifiers.");
			}
			HashSet<SkillType> hashSet = new HashSet<SkillType>();
			HashSet<EffectKind> hashSet2 = new HashSet<EffectKind>();
			HashSet<AddedDamageKind> hashSet3 = new HashSet<AddedDamageKind>();
			SkillBonus[] skillValues = SkillValues;
			for (int i = 0; i < skillValues.Length; i++)
			{
				SkillBonus skillBonus = skillValues[i];
				if (!Enum.IsDefined(typeof(SkillType), skillBonus.Skill) || (int)skillBonus.Skill == 0 || (int)skillBonus.Skill == 999 || !Numbers.Finite(skillBonus.Value, 0f, 100f) || !hashSet.Add(skillBonus.Skill))
				{
					throw new FormatException("Invalid or duplicate skill bonus.");
				}
			}
			EffectBonus[] effectValues = EffectValues;
			for (int i = 0; i < effectValues.Length; i++)
			{
				EffectBonus effectBonus = effectValues[i];
				if (!Modifiers.Valid(effectBonus.Kind, effectBonus.Value) || !hashSet2.Add(effectBonus.Kind))
				{
					throw new FormatException("Invalid or duplicate effect bonus.");
				}
				if (effectBonus.Value != 0f && Modifiers.Flag(effectBonus.Kind))
				{
					HasNamedFlags = true;
				}
				if (effectBonus.Kind == EffectKind.HealthInstant)
				{
					HealthInstant = effectBonus.Value;
				}
				else if (effectBonus.Kind == EffectKind.StaminaInstant)
				{
					StaminaInstant = effectBonus.Value;
				}
				else if (effectBonus.Kind == EffectKind.EitrInstant)
				{
					EitrInstant = effectBonus.Value;
				}
			}
			AddedDamageBonus[] addedDamageValues = AddedDamageValues;
			for (int i = 0; i < addedDamageValues.Length; i++)
			{
				AddedDamageBonus range = addedDamageValues[i];
				if (!DamageRanges.Valid(range) || !hashSet3.Add(range.Kind))
				{
					throw new FormatException("Invalid or duplicate added damage range. Use a single value or min-max damage units, with finite values from 0 to 100000 and min <= max; % is not allowed.");
				}
			}
			Array.Sort(SkillValues, (SkillBonus a, SkillBonus b) => ((int)a.Skill).CompareTo((int)b.Skill));
			Array.Sort(EffectValues, delegate(EffectBonus a, EffectBonus b)
			{
				int kind = (int)a.Kind;
				return kind.CompareTo((int)b.Kind);
			});
			Array.Sort(AddedDamageValues, delegate(AddedDamageBonus a, AddedDamageBonus b)
			{
				int kind = (int)a.Kind;
				return kind.CompareTo((int)b.Kind);
			});
			Skills = Array.AsReadOnly(SkillValues);
			Effects = Array.AsReadOnly(EffectValues);
			AddedDamage = Array.AsReadOnly(AddedDamageValues);
			HasAny = SkillValues.Any((SkillBonus x) => x.Value != 0f) || EffectValues.Any((EffectBonus x) => x.Value != 0f) || AddedDamageValues.Any((AddedDamageBonus x) => x.Max != 0f);
			Canonical = string.Join(",", SkillValues.Select((SkillBonus x) => ((object)(*(SkillType*)(&x.Skill))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant() + "[" + x.Value.ToString("R", CultureInfo.InvariantCulture) + "]").Concat(EffectValues.Select(Modifiers.Config)).Concat(AddedDamageValues.Select(DamageRanges.Config)));
		}
	}
	public sealed class BuffOptions
	{
		public const int MaxTextBytes = 4096;

		public string DisplayName { get; }

		public Sprite Icon { get; }

		public string BuffText { get; }

		public bool ShowBuff { get; }

		public bool ShowBuffIcons => ShowBuff;

		public bool ShowBuffTimers { get; }

		public bool ShowModifierDescriptions { get; }

		public string CustomBuffName { get; }

		public BuffOptions(string displayName, Sprite icon, string buffText, bool showBuff)
			: this(displayName, icon, buffText, showBuff, showBuffTimers: true, showModifierDescriptions: true)
		{
		}

		public BuffOptions(string displayName = null, Sprite icon = null, string buffText = null, bool showBuff = true, bool showBuffTimers = true, bool showModifierDescriptions = true)
			: this(displayName, icon, buffText, showBuff, showBuffTimers, showModifierDescriptions, null)
		{
		}

		internal BuffOptions(string displayName, Sprite icon, string buffText, bool showBuff, bool showBuffTimers, bool showModifierDescriptions, string customBuffName)
		{
			if (customBuffName != null)
			{
				CustomBuffYaml.ValidateCallingName(customBuffName);
			}
			CustomBuffName = customBuffName;
			if (displayName != null && displayName.Length > 512)
			{
				throw new FormatException("Buff display name exceeds 512 characters.");
			}
			if (Encoding.UTF8.GetByteCount(buffText ?? "") > 4096)
			{
				throw new FormatException("Buff text exceeds 4096 UTF-8 bytes.");
			}
			DisplayName = displayName;
			Icon = icon;
			BuffText = (string.IsNullOrWhiteSpace(buffText) ? null : buffText);
			ShowBuff = showBuff;
			ShowBuffTimers = showBuffTimers;
			ShowModifierDescriptions = showModifierDescriptions;
		}
	}
	public sealed class SourceSnapshot
	{
		public string OwnerGuid { get; }

		public string SourceId { get; }

		public ModifierSet Modifiers { get; }

		public string BuffText { get; }

		public float? RemainingSeconds { get; }

		internal SourceSnapshot(Source source)
		{
			OwnerGuid = source.Owner;
			SourceId = source.Id;
			Modifiers = source.Set;
			BuffText = source.Presentation.BuffText;
			RemainingSeconds = source.Remaining;
		}
	}
	public static class FrameworkApi
	{
		private static bool notifyingCustomBuffs;

		private static bool notifyingModifierNames;

		public const string FullNameCatalogMinimumVersion = "1.0.2";

		public const string PluginGuid = "dannanboi.modifiersframework";

		public const string MinimumVersion = "1.0.2";

		public const int ApiVersion = 1;

		private static readonly Regex bracket = new Regex("^([A-Za-z]+)\\s*\\[\\s*([^\\[\\]]+)\\s*\\]$", RegexOptions.CultureInvariant);

		private static readonly Dictionary<string, SkillType> skills = BuildSkills();

		public static IReadOnlyDictionary<string, string> ModifierFullNames => ModifierCatalog.Names;

		public static string Version => "1.0.2";

		public static bool IsReady => Plugin.Ready;

		public static IEnumerable<string> ModifierNames => Modifiers.Names;

		public unsafe static IEnumerable<string> SkillNames => from x in (from SkillType x in Enum.GetValues(typeof(SkillType))
				where (int)x != 0 && (int)x != 999
				select x).Distinct()
			orderby (int)x
			select ((object)(*(SkillType*)(&x))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant();

		internal static IEnumerable<string> SkillLookupNames => skills.Keys;

		public static event Action CustomBuffsChanged;

		public static event Action ModifierNamesChanged;

		internal static void NotifyCustomBuffsChanged()
		{
			Action customBuffsChanged = FrameworkApi.CustomBuffsChanged;
			if (!Plugin.Ready || customBuffsChanged == null || notifyingCustomBuffs)
			{
				return;
			}
			notifyingCustomBuffs = true;
			try
			{
				Delegate[] invocationList = customBuffsChanged.GetInvocationList();
				for (int i = 0; i < invocationList.Length; i++)
				{
					Action action = (Action)invocationList[i];
					try
					{
						action();
					}
					catch (Exception ex)
					{
						Plugin.Report("custom buff update callback", ex);
					}
				}
			}
			finally
			{
				notifyingCustomBuffs = false;
			}
		}

		internal static void ClearCustomBuffListeners()
		{
			FrameworkApi.CustomBuffsChanged = null;
		}

		internal static void NotifyModifierNamesChanged()
		{
			Action modifierNamesChanged = FrameworkApi.ModifierNamesChanged;
			if (!Plugin.Ready || modifierNamesChanged == null || notifyingModifierNames)
			{
				return;
			}
			notifyingModifierNames = true;
			try
			{
				Delegate[] invocationList = modifierNamesChanged.GetInvocationList();
				for (int i = 0; i < invocationList.Length; i++)
				{
					Action action = (Action)invocationList[i];
					try
					{
						action();
					}
					catch (Exception ex)
					{
						Plugin.Report("modifier name update callback", ex);
					}
				}
			}
			finally
			{
				notifyingModifierNames = false;
			}
		}

		internal static void ClearModifierNameListeners()
		{
			FrameworkApi.ModifierNamesChanged = null;
		}

		public static string GetModifierFullName(string name)
		{
			if (!ModifierCatalog.TryGet(name, out var fullName))
			{
				throw new FormatException("Unknown modifier name '" + name + "'.");
			}
			return fullName;
		}

		public static bool TryGetModifierFullName(string name, out string fullName)
		{
			return ModifierCatalog.TryGet(name, out fullName);
		}

		public static bool ReloadModifierNames()
		{
			Plugin.RequireReady();
			return ModifierCatalog.Reload();
		}

		private unsafe static Dictionary<string, SkillType> BuildSkills()
		{
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, SkillType> dictionary = (from SkillType x in Enum.GetValues(typeof(SkillType))
				where (int)x != 0 && (int)x != 999
				select x).Distinct().ToDictionary<SkillType, string, SkillType>((SkillType x) => ((object)(*(SkillType*)(&x))/*cast due to .constrained prefix*/).ToString(), (SkillType x) => x, StringComparer.OrdinalIgnoreCase);
			string[] array = new string[14]
			{
				"swimming:Swim", "running:Run", "jumping:Jump", "sneaking:Sneak", "riding:Ride", "sword:Swords", "knife:Knives", "club:Clubs", "polearm:Polearms", "spear:Spears",
				"axe:Axes", "bow:Bows", "crossbow:Crossbows", "pickaxe:Pickaxes"
			};
			for (int num = 0; num < array.Length; num++)
			{
				string[] array2 = array[num].Split(':');
				if (dictionary.TryGetValue(array2[1], out var value))
				{
					dictionary[array2[0]] = value;
				}
			}
			return dictionary;
		}

		public static bool TrySkill(string name, out SkillType skill)
		{
			name = (name ?? "").Trim();
			if (name.EndsWith("skill", StringComparison.OrdinalIgnoreCase))
			{
				name = name.Substring(0, name.Length - 5);
			}
			return skills.TryGetValue(name, out skill);
		}

		public static ModifierSet Parse(string text)
		{
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrWhiteSpace(text))
			{
				return ModifierSet.Empty;
			}
			if (Encoding.UTF8.GetByteCount(text) > 16384)
			{
				throw new FormatException("Modifier list exceeds 16384 UTF-8 bytes.");
			}
			List<SkillBonus> list = new List<SkillBonus>();
			List<EffectBonus> list2 = new List<EffectBonus>();
			List<AddedDamageBonus> list3 = new List<AddedDamageBonus>();
			string text2 = null;
			string[] array = text.Split(',');
			foreach (string text3 in array)
			{
				Match match = bracket.Match(text3.Trim());
				if (!match.Success)
				{
					throw new FormatException("Invalid modifier '" + text3 + "'. Use name[value], for example swim[10],jumpCost[-60%].");
				}
				string value = match.Groups[1].Value;
				string value2 = match.Groups[2].Value;
				EffectKind kind;
				float result;
				AddedDamageKind kind2;
				if (value.Equals("buff", StringComparison.OrdinalIgnoreCase))
				{
					Plugin.RequireReady();
					if (text2 != null)
					{
						throw new FormatException("Use one buff[name] per source; activate separate sources for multiple buff rows.");
					}
					text2 = value2.Trim();
					CustomBuffDefinition customBuffDefinition = CustomBuffs.Find(text2);
					list.AddRange(customBuffDefinition.Set.SkillValues);
					list2.AddRange(customBuffDefinition.Set.EffectValues);
					list3.AddRange(customBuffDefinition.Set.AddedDamageValues);
				}
				else if (Modifiers.TryKind(value, out kind))
				{
					if (!Modifiers.TryValue(kind, value2, out result))
					{
						throw new FormatException("Invalid value for '" + value + "'. " + Modifiers.ValueRule(kind));
					}
					list2.Add(new EffectBonus
					{
						Kind = kind,
						Value = result
					});
				}
				else if (DamageRanges.TryKind(value, out kind2))
				{
					if (!DamageRanges.TryValue(kind2, value2, out var range))
					{
						throw new FormatException("Invalid value for '" + value + "'. Use a single value or min-max damage units, with finite values from 0 to 100000 and min <= max; % is not allowed.");
					}
					list3.Add(range);
				}
				else
				{
					if (!TrySkill(value, out var skill))
					{
						throw new FormatException("Unknown modifier '" + value + "'.");
					}
					if (!float.TryParse(value2, NumberStyles.Float, CultureInfo.InvariantCulture, out result) || !Numbers.Finite(result, 0f, 100f))
					{
						throw new FormatException("Skill values must be between 0 and 100, without %.");
					}
					list.Add(new SkillBonus
					{
						Skill = skill,
						Value = result
					});
				}
			}
			return new ModifierSet(list, list2, list3, text2);
		}

		public static bool TryParse(string text, out ModifierSet modifiers, out string error)
		{
			try
			{
				modifiers = Parse(text);
				error = null;
				return true;
			}
			catch (FormatException ex)
			{
				Plugin.Debug("Modifier parsing rejected: " + ex.Message);
				modifiers = null;
				error = ex.Message;
				return false;
			}
		}

		public static string FormatLines(ModifierSet modifiers)
		{
			return ModifierText.Lines(modifiers ?? ModifierSet.Empty);
		}

		public static string FormatGains(ModifierSet modifiers)
		{
			return FormatLines(modifiers).Replace("\n", " and ");
		}

		public static BuffOptions GetCustomBuff(string callingName, bool showBuff = true, bool showBuffTimers = true, bool showModifierDescriptions = true)
		{
			Plugin.RequireReady();
			return CustomBuffs.Options(CustomBuffs.Find(callingName), showBuff, showBuffTimers, showModifierDescriptions);
		}

		public static bool ReloadCustomBuffs()
		{
			Plugin.RequireReady();
			return CustomBuffs.Reload();
		}

		private static BuffOptions DisplayFor(ModifierSet modifiers, BuffOptions display)
		{
			if (modifiers == null || modifiers.CustomBuffName == null || (display != null && display.CustomBuffName != null))
			{
				return display ?? new BuffOptions();
			}
			return CustomBuffs.Options(CustomBuffs.Find(modifiers.CustomBuffName), display?.ShowBuff ?? true, display?.ShowBuffTimers ?? true, display?.ShowModifierDescriptions ?? true);
		}

		public static void ValidateFoodRegistration(string ownerGuid, GameObject prefab, ModifierSet modifiers, BuffOptions display = null)
		{
			Plugin.RequireReady();
			FoodBindings.Validate(ownerGuid, prefab, modifiers, DisplayFor(modifiers, display));
		}

		public static void RegisterFood(string ownerGuid, GameObject prefab, ModifierSet modifiers, BuffOptions display = null)
		{
			Plugin.RequireReady();
			FoodBindings.Register(ownerGuid, prefab, modifiers, DisplayFor(modifiers, display));
		}

		public static bool UnregisterFood(string ownerGuid, string itemName)
		{
			Plugin.RequireReady();
			return FoodBindings.Remove(ownerGuid, itemName);
		}

		public static void UnregisterFoods(string ownerGuid)
		{
			Plugin.RequireReady();
			FoodBindings.RemoveOwner(ownerGuid);
		}

		public static SourceSnapshot GetFoodSnapshot(Player player, ItemData item)
		{
			return ActiveSources.FindFood(player, item);
		}

		public static SourceSnapshot GetSource(Player player, string ownerGuid, string sourceId)
		{
			Identity.Check(ownerGuid, sourceId);
			return ActiveSources.FindSource(player, ownerGuid, sourceId);
		}

		public static void RegisterSource(string ownerGuid, string sourceId, BuffOptions display = null)
		{
			Plugin.RequireReady();
			SourceBindings.Register(ownerGuid, sourceId, display ?? new BuffOptions());
		}

		public static bool UnregisterSource(string ownerGuid, string sourceId)
		{
			Plugin.RequireReady();
			return SourceBindings.Remove(ownerGuid, sourceId);
		}

		public static void UnregisterSources(string ownerGuid)
		{
			Plugin.RequireReady();
			SourceBindings.RemoveOwner(ownerGuid);
		}

		public static void Activate(Player player, string ownerGuid, string sourceId, ModifierSet modifiers, BuffOptions display = null, float? durationSeconds = null, bool persistOnSave = false)
		{
			Plugin.RequireReady();
			Identity.Check(ownerGuid, sourceId);
			if (!Object.op_Implicit((Object)(object)player))
			{
				throw new ArgumentNullException("player");
			}
			if (modifiers == null)
			{
				throw new ArgumentNullException("modifiers");
			}
			if (durationSeconds.HasValue && !Numbers.Finite(durationSeconds.Value, 0.001f, 6000000f))
			{
				throw new FormatException("Duration must be 0.001 through 6000000 seconds.");
			}
			BuffOptions buffOptions = SourceBindings.Find(ownerGuid, sourceId);
			if (persistOnSave && buffOptions == null)
			{
				throw new InvalidOperationException("RegisterSource must be called before activating a restorable source.");
			}
			ActiveSources.Activate(player, ownerGuid, sourceId, modifiers, (modifiers.CustomBuffName != null) ? DisplayFor(modifiers, display ?? buffOptions) : (display ?? buffOptions ?? new BuffOptions()), durationSeconds, persistOnSave);
		}

		public static bool Remove(Player player, string ownerGuid, string sourceId)
		{
			Plugin.RequireReady();
			Identity.Check(ownerGuid, sourceId);
			return ActiveSources.Remove(player, ownerGuid, sourceId);
		}

		public static void RemoveOwner(Player player, string ownerGuid)
		{
			Plugin.RequireReady();
			Identity.CheckOwner(ownerGuid);
			ActiveSources.RemoveOwner(player, ownerGuid);
		}

		public static float GetSkillBonus(Player player, SkillType skill)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return ActiveSources.Skill(player, skill);
		}

		public static AddedDamageBonus GetAddedDamageRange(Player player, AddedDamageKind kind)
		{
			if (kind < AddedDamageKind.Fire || kind >= (AddedDamageKind)5)
			{
				throw new ArgumentOutOfRangeException("kind");
			}
			return ActiveSources.AddedDamageRange(player, kind);
		}

		public static float GetEffectTotal(Player player, EffectKind effect)
		{
			if (effect < EffectKind.RunCost || effect >= (EffectKind)43)
			{
				throw new ArgumentOutOfRangeException("effect");
			}
			return ActiveSources.Effect(player, effect);
		}
	}
	internal static class Numbers
	{
		internal static bool Finite(float value, float min, float max)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value) && value >= min)
			{
				return value <= max;
			}
			return false;
		}

		internal static float ApplyLevel(float original, float bonus)
		{
			return Math.Max(0f, Math.Min(100f, original + bonus));
		}

		internal static float ApplyFactor(float original, float bonus)
		{
			return Math.Max(0f, Math.Min(1f, original + bonus / 100f));
		}
	}
	internal static class Identity
	{
		internal static void CheckOwner(string owner)
		{
			if (string.IsNullOrWhiteSpace(owner) || owner.Length > 128 || owner.IndexOf('\0') >= 0 || owner != owner.Trim())
			{
				throw new FormatException("Supply a stable owner GUID, up to 128 characters.");
			}
		}

		internal static void Check(string owner, string id)
		{
			CheckOwner(owner);
			if (string.IsNullOrWhiteSpace(id) || id.Length > 128 || id.IndexOf('\0') >= 0 || id != id.Trim())
			{
				throw new FormatException("Supply a stable source identifier, up to 128 characters.");
			}
			if (id.StartsWith("food:", StringComparison.Ordinal))
			{
				throw new FormatException("The food: source prefix is reserved for food bindings.");
			}
		}

		internal static string Key(string owner, string id)
		{
			return owner + "\0" + id;
		}
	}
	internal sealed class CustomBuffDefinition
	{
		internal string CallingName;

		internal string Name;

		internal string Text;

		internal ModifierSet Set;

		internal Sprite Icon;
	}
	internal static class CustomBuffYaml
	{
		internal const int MaxBytes = 262144;

		private static readonly Regex callingName = new Regex("\\A[A-Za-z0-9_.-]{1,128}\\z", RegexOptions.CultureInvariant);

		internal static void ValidateCallingName(string value)
		{
			if (value == null || !callingName.IsMatch(value))
			{
				throw new FormatException("callingName must contain 1-128 letters, digits, underscores, dots or hyphens, without spaces.");
			}
		}

		internal static Dictionary<string, CustomBuffDefinition> Parse(string yaml)
		{
			if (Encoding.UTF8.GetByteCount(yaml) > 262144)
			{
				throw new FormatException("Custom buff YAML exceeds 256 KiB.");
			}
			Dictionary<string, CustomBuffDefinition> dictionary = new Dictionary<string, CustomBuffDefinition>(StringComparer.Ordinal);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			string[] array = yaml.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
			Dictionary<string, string> dictionary2 = null;
			string rowName = null;
			List<string> list = new List<string>();
			bool flag = false;
			bool flag2 = false;
			for (int i = 0; i < array.Length; i++)
			{
				string obj = array[i];
				if (obj.IndexOf('\t') >= 0)
				{
					throw Error(i, "Use spaces for indentation.");
				}
				string text = StripComment(obj).TrimEnd();
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				int num = text.Length - text.TrimStart(' ').Length;
				text = text.TrimStart(' ');
				if (num == 0)
				{
					if (text == "---" && dictionary2 == null && dictionary.Count == 0 && !flag2)
					{
						continue;
					}
					if (text == "{}" && dictionary2 == null && dictionary.Count == 0 && !flag2)
					{
						flag2 = true;
						continue;
					}
					if (flag2)
					{
						throw Error(i, "Unexpected content after empty mapping.");
					}
					if (!text.EndsWith(":", StringComparison.Ordinal))
					{
						throw Error(i, "Expected a buff row followed by ':'.");
					}
					Finish(rowName, dictionary2, list, dictionary);
					string text2 = Scalar(text.Substring(0, text.Length - 1).Trim());
					ValidateCallingName(text2);
					if (!hashSet.Add(text2) || hashSet.Count > 256)
					{
						throw Error(i, "Duplicate row or more than 256 buffs.");
					}
					rowName = text2;
					dictionary2 = new Dictionary<string, string>(StringComparer.Ordinal);
					list.Clear();
					flag = false;
					continue;
				}
				if (dictionary2 == null)
				{
					throw Error(i, "Add a top-level buff row first.");
				}
				if (flag && (num == 4 || num == 2) && text.StartsWith("- ", StringComparison.Ordinal))
				{
					string text3 = Scalar(text.Substring(2).Trim());
					if (text3.IndexOf(',') >= 0 || list.Count >= 64 || text3.TrimStart().StartsWith("buff", StringComparison.OrdinalIgnoreCase))
					{
						throw Error(i, "Use one built-in modifier per effectsList item; nested buffs are unsupported.");
					}
					list.Add(text3);
					continue;
				}
				if (num != 2)
				{
					throw Error(i, "Fields use two spaces; list items use four spaces.");
				}
				int num2 = text.IndexOf(':');
				if (num2 < 1)
				{
					throw Error(i, "Expected field: value.");
				}
				string text4 = text.Substring(0, num2);
				string text5 = text.Substring(num2 + 1).Trim();
				if (text4 != "callingName" && text4 != "iconName" && text4 != "buffName" && text4 != "buffText" && text4 != "effectsList")
				{
					throw Error(i, "Unknown field '" + text4 + "'.");
				}
				if (dictionary2.ContainsKey(text4))
				{
					throw Error(i, "Duplicate field '" + text4 + "'.");
				}
				flag = text4 == "effectsList";
				if (flag)
				{
					if (text5 != "" && text5 != "[]")
					{
						throw Error(i, "effectsList must be an indented list or [].");
					}
					dictionary2.Add(text4, "");
					flag = text5 == "";
					continue;
				}
				if (text4 == "buffText")
				{
					switch (text5)
					{
					case "|":
					case "|-":
					case ">":
					case ">-":
					{
						StringBuilder stringBuilder = new StringBuilder();
						bool flag3 = text5[0] == '>';
						while (i + 1 < array.Length && (array[i + 1].StartsWith("    ", StringComparison.Ordinal) || string.IsNullOrWhiteSpace(array[i + 1])))
						{
							string text6 = array[++i];
							if (text6.IndexOf('\t') >= 0)
							{
								throw Error(i, "Use spaces for indentation.");
							}
							stringBuilder.Append((text6.Length >= 4) ? text6.Substring(4) : "");
							stringBuilder.Append(flag3 ? ' ' : '\n');
						}
						dictionary2.Add(text4, stringBuilder.ToString().TrimEnd());
						continue;
					}
					}
				}
				dictionary2.Add(text4, Scalar(text5));
			}
			Finish(rowName, dictionary2, list, dictionary);
			return dictionary;
		}

		private static void Finish(string rowName, Dictionary<string, string> fields, List<string> effects, Dictionary<string, CustomBuffDefinition> result)
		{
			if (fields == null)
			{
				return;
			}
			string[] array = new string[3] { "buffName", "buffText", "effectsList" };
			foreach (string text in array)
			{
				if (!fields.ContainsKey(text))
				{
					throw new FormatException("Each custom buff requires '" + text + "'.");
				}
			}
			if (!fields.TryGetValue("callingName", out var value))
			{
				value = rowName;
			}
			ValidateCallingName(value);
			if (fields.ContainsKey("iconName"))
			{
				Plugin.Warn("Custom buff '" + value + "': obsolete iconName ignored; icon must be icons/" + value + ".png.");
			}
			if (string.IsNullOrWhiteSpace(fields["buffName"]))
			{
				throw new FormatException("buffName cannot be empty.");
			}
			new BuffOptions(fields["buffName"], null, fields["buffText"]);
			ModifierSet set = FrameworkApi.Parse(string.Join(",", effects));
			if (result.ContainsKey(value))
			{
				throw new FormatException("Duplicate callingName '" + value + "'.");
			}
			result.Add(value, new CustomBuffDefinition
			{
				CallingName = value,
				Name = fields["buffName"],
				Text = fields["buffText"],
				Set = set
			});
		}

		internal static string StripComment(string value)
		{
			char c = '\0';
			for (int i = 0; i < value.Length; i++)
			{
				char c2 = value[i];
				if (c == '"' && c2 == '\\')
				{
					i++;
				}
				else if (c != 0)
				{
					if (c2 == c)
					{
						if (c == '\'' && i + 1 < value.Length && value[i + 1] == '\'')
						{
							i++;
						}
						else
						{
							c = '\0';
						}
					}
				}
				else if ((c2 == '"' || c2 == '\'') && (i == 0 || char.IsWhiteSpace(value[i - 1]) || value[i - 1] == ':'))
				{
					c = c2;
				}
				else if (c2 == '#' && (i == 0 || char.IsWhiteSpace(value[i - 1])))
				{
					return value.Substring(0, i);
				}
			}
			return value;
		}

		internal static string Scalar(string raw)
		{
			if (raw.Length == 0)
			{
				return "";
			}
			if (raw[0] == '\'' && raw.Length >= 2 && raw[raw.Length - 1] == '\'')
			{
				StringBuilder stringBuilder = new StringBuilder();
				for (int i = 1; i < raw.Length - 1; i++)
				{
					char c = raw[i];
					if (c == '\'' && (++i >= raw.Length - 1 || raw[i] != '\''))
					{
						throw new FormatException("Double single quotes inside single-quoted values.");
					}
					stringBuilder.Append(c);
				}
				return stringBuilder.ToString();
			}
			if (raw[0] == '"' && raw.Length >= 2 && raw[raw.Length - 1] == '"')
			{
				StringBuilder stringBuilder2 = new StringBuilder();
				for (int j = 1; j < raw.Length - 1; j++)
				{
					char c2 = raw[j];
					switch (c2)
					{
					case '"':
						throw new FormatException("Escape double quotes inside quoted values.");
					case '\\':
						if (++j >= raw.Length - 1)
						{
							throw new FormatException("Incomplete quoted escape.");
						}
						c2 = raw[j] switch
						{
							'n' => '\n', 
							'r' => '\r', 
							't' => '\t', 
							'"' => '"', 
							'\\' => '\\', 
							_ => throw new FormatException("Supported escapes are \\n, \\r, \\t, \\\" and \\\\. Use literal UTF-8 for other characters."), 
						};
						break;
					}
					stringBuilder2.Append(c2);
				}
				return stringBuilder2.ToString();
			}
			if ("!&*[{\"'".IndexOf(raw[0]) >= 0 || raw.IndexOf(": ", StringComparison.Ordinal) >= 0)
			{
				throw new FormatException("Quote text containing ': '. YAML tags, anchors and flow values are unsupported.");
			}
			return raw;
		}

		private static FormatException Error(int line, string message)
		{
			return new FormatException("buffs.yaml line " + (line + 1) + ": " + message);
		}
	}
	internal sealed class BoundedLog
	{
		internal const int MaxBytes = 1048576;

		private const int MaxMessageCharacters = 8192;

		private readonly string path;

		private readonly object sync = new object();

		private static readonly Encoding utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

		internal BoundedLog(string path)
		{
			this.path = path ?? throw new ArgumentNullException("path");
		}

		internal static byte[] Entry(string level, string message)
		{
			message = message ?? "";
			if (message.Length > 8192)
			{
				message = message.Substring(0, 8192) + " [truncated]";
			}
			return utf8.GetBytes(DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture) + " [" + level + "] " + message + "\n");
		}

		internal void Write(string level, string message)
		{
			WriteEntries(new byte[1][] { Entry(level, message) });
		}

		internal void WriteEntries(IReadOnlyList<byte[]> entries)
		{
			lock (sync)
			{
				Directory.CreateDirectory(Path.GetDirectoryName(path));
				using FileStream fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
				foreach (byte[] entry in entries)
				{
					if (fileStream.Length + entry.Length > 1048576)
					{
						fileStream.SetLength(0L);
						byte[] array = Entry("INFO", "Previous output overwritten at the 1 MiB limit.");
						fileStream.Write(array, 0, array.Length);
					}
					else
					{
						fileStream.Position = fileStream.Length;
					}
					fileStream.Write(entry, 0, entry.Length);
				}
			}
		}
	}
	internal static class SourceBindings
	{
		private static readonly Dictionary<string, BuffOptions> entries = new Dictionary<string, BuffOptions>(StringComparer.Ordinal);

		internal static BuffOptions Find(string owner, string id)
		{
			if (!entries.TryGetValue(Identity.Key(owner, id), out var value))
			{
				return null;
			}
			return value;
		}

		internal static void Register(string owner, string id, BuffOptions display)
		{
			Identity.Check(owner, id);
			string key = Identity.Key(owner, id);
			if (!entries.ContainsKey(key) && entries.Count >= 1024)
			{
				throw new FormatException("At most 1024 restorable source registrations are supported.");
			}
			entries[key] = display;
			Plugin.Debug("Restoration source registered: " + owner + "/" + id);
		}

		internal static bool Remove(string owner, string id)
		{
			Identity.Check(owner, id);
			bool num = entries.Remove(Identity.Key(owner, id));
			if (num)
			{
				Plugin.Debug("Restoration source unregistered: " + owner + "/" + id);
			}
			return num;
		}

		internal static void RemoveOwner(string owner)
		{
			Identity.CheckOwner(owner);
			string prefix = owner + "\0";
			string[] array = entries.Keys.Where((string x) => x.StartsWith(prefix, StringComparison.Ordinal)).ToArray();
			foreach (string text in array)
			{
				Remove(owner, text.Substring(prefix.Length));
			}
		}

		internal static void Clear()
		{
			entries.Clear();
		}
	}
	internal static class CustomBuffs
	{
		private static Dictionary<string, CustomBuffDefinition> definitions = new Dictionary<string, CustomBuffDefinition>(StringComparer.Ordinal);

		private static List<Object> assets = new List<Object>();

		private static Sprite fallback;

		private static string directory;

		private static string loadedYaml;

		private static FileSystemWatcher watcher;

		private static readonly object watchGate = new object();

		private static int pendingReload;

		private static int changedAt;

		private static string watchError;

		internal const int ReloadDebounceMilliseconds = 500;

		private const int MaxIconBytes = 1048576;

		private const int MaxPixels = 8388608;

		internal const string StarterYaml = "# Custom buff definitions. Each top-level key is its case-sensitive callingName.\n# PNGs go in icons/ and must be named <callingName>.png.\n# Calling mods select buff[VikingExample]. Requires framework 1.0.0 or newer.\n\nVikingExample:\n  buffName: \"This is an example\"\n  buffText: \"Swim better, strike harder, and move faster.\"\n  effectsList:\n    - swim[10]\n    - slash[10]\n    - runSpeed[10%]\n    - swimCost[-100%]\n    - swimSpeed[50%]\n";

		internal static bool ReloadPending => Volatile.Read(in pendingReload) != 0;

		internal static int Generation { get; private set; }

		internal static void Initialize(string configPath)
		{
			directory = Path.Combine(configPath, "ModifiersFramework");
			Directory.CreateDirectory(directory);
			MigrateLegacyCatalog();
			ModifierCatalog.Initialize(directory);
			Reload();
			StartWatching();
		}

		private static void MigrateLegacyCatalog()
		{
			string text = Path.Combine(directory, "customModifiers.yaml");
			string text2 = Path.Combine(directory, "buffs.yaml");
			if (!File.Exists(text))
			{
				return;
			}
			try
			{
				if (File.Exists(text2))
				{
					Plugin.Warn("Both buffs.yaml and legacy customModifiers.yaml exist; buffs.yaml is used and the legacy file is preserved.");
					return;
				}
				File.Move(text, text2);
				Plugin.Debug("Migrated customModifiers.yaml to buffs.yaml without changing its contents.");
			}
			catch (Exception innerException)
			{
				throw new IOException("Could not migrate customModifiers.yaml to buffs.yaml; existing definitions preserved.", innerException);
			}
		}

		internal static CustomBuffDefinition Find(string name)
		{
			CustomBuffYaml.ValidateCallingName(name);
			if (!definitions.TryGetValue(name, out var value))
			{
				throw new FormatException("Unknown custom buff callingName '" + name + "'.");
			}
			return value;
		}

		internal static BuffOptions Options(CustomBuffDefinition definition, bool show, bool timers, bool descriptions)
		{
			return new BuffOptions(definition.Name, definition.Icon, definition.Text, show, timers, descriptions, definition.CallingName);
		}

		internal static BuffOptions Resolve(BuffOptions original)
		{
			if (original.CustomBuffName == null)
			{
				return original;
			}
			if (!definitions.TryGetValue(original.CustomBuffName, out var value))
			{
				return new BuffOptions(original.DisplayName, fallback, original.BuffText, original.ShowBuff, original.ShowBuffTimers, original.ShowModifierDescriptions, original.CustomBuffName);
			}
			return Options(value, original.ShowBuff, original.ShowBuffTimers, original.ShowModifierDescriptions);
		}

		internal static bool Reload(bool automatic = false)
		{
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			List<Object> list = new List<Object>();
			try
			{
				if (directory == null)
				{
					throw new InvalidOperationException("Custom buff catalog has not initialized.");
				}
				Directory.CreateDirectory(Path.Combine(directory, "icons"));
				string path = Path.Combine(directory, "buffs.yaml");
				if (!File.Exists(path) && !automatic)
				{
					using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write);
					byte[] bytes = Encoding.UTF8.GetBytes("# Custom buff definitions. Each top-level key is its case-sensitive callingName.\n# PNGs go in icons/ and must be named <callingName>.png.\n# Calling mods select buff[VikingExample]. Requires framework 1.0.0 or newer.\n\nVikingExample:\n  buffName: \"This is an example\"\n  buffText: \"Swim better, strike harder, and move faster.\"\n  effectsList:\n    - swim[10]\n    - slash[10]\n    - runSpeed[10%]\n    - swimCost[-100%]\n    - swimSpeed[50%]\n");
					fileStream.Write(bytes, 0, bytes.Length);
				}
				string text = ReadText(path);
				if (automatic && text == loadedYaml)
				{
					return true;
				}
				Dictionary<string, CustomBuffDefinition> dictionary = CustomBuffYaml.Parse(text);
				Sprite val = null;
				if (!Application.isBatchMode && (dictionary.Count > 0 || definitions.Count > 0 || Object.op_Implicit((Object)(object)fallback)))
				{
					Texture2D val2 = new Texture2D(2, 2, (TextureFormat)4, false);
					list.Add((Object)(object)val2);
					Color val3 = default(Color);
					((Color)(ref val3))..ctor(0.7f, 0.7f, 0.7f, 1f);
					for (int i = 0; i < 2; i++)
					{
						for (int j = 0; j < 2; j++)
						{
							val2.SetPixel(j, i, val3);
						}
					}
					val2.Apply(false, true);
					val = Sprite.Create(val2, new Rect(0f, 0f, 2f, 2f), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0);
					list.Add((Object)(object)val);
					int pixels = 0;
					foreach (CustomBuffDefinition value in dictionary.Values)
					{
						value.Icon = LoadIcon(value.CallingName, list, ref pixels) ?? val;
					}
				}
				List<Object> values = assets;
				definitions = dictionary;
				assets = list;
				fallback = val;
				Generation++;
				loadedYaml = text;
				Plugin.Guard("custom buff display reload", delegate
				{
					ActiveSources.RebindCustomBuffs(Player.m_localPlayer);
				});
				Destroy(values);
				Plugin.Debug("Custom buff catalog loaded: " + dictionary.Count + " definitions; icons cached locally.");
				FrameworkApi.NotifyCustomBuffsChanged();
				return true;
			}
			catch (Exception ex)
			{
				Destroy(list);
				Plugin.Warn("Custom buff reload rejected; previous definitions retained. " + ex.Message);
				return false;
			}
		}

		private static void StartWatching()
		{
			StopWatching();
			try
			{
				FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(directory, "*.yaml")
				{
					NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite),
					IncludeSubdirectories = false
				};
				fileSystemWatcher.Changed += QueueReload;
				fileSystemWatcher.Created += QueueReload;
				fileSystemWatcher.Deleted += QueueReload;
				fileSystemWatcher.Renamed += QueueReload;
				fileSystemWatcher.Error += QueueWatchError;
				lock (watchGate)
				{
					watcher = fileSystemWatcher;
				}
				fileSystemWatcher.EnableRaisingEvents = true;
			}
			catch (Exception ex)
			{
				StopWatching();
				Plugin.Warn("Custom buff automatic reload unavailable; explicit reload remains available. " + ex.Message);
			}
		}

		private static int CatalogFlag(string name)
		{
			if (!string.Equals(name, "buffs.yaml", StringComparison.OrdinalIgnoreCase))
			{
				if (!string.Equals(name, "modifiers.yaml", StringComparison.OrdinalIgnoreCase))
				{
					return 0;
				}
				return 2;
			}
			return 1;
		}

		private static void QueueReload(object sender, FileSystemEventArgs args)
		{
			int num = CatalogFlag(args.Name);
			if (args is RenamedEventArgs e)
			{
				num |= CatalogFlag(e.OldName);
			}
			if (num == 0)
			{
				return;
			}
			lock (watchGate)
			{
				if (sender == watcher)
				{
					changedAt = Environment.TickCount;
					Volatile.Write(ref pendingReload, pendingReload | num);
				}
			}
		}

		private static void QueueWatchError(object sender, ErrorEventArgs args)
		{
			lock (watchGate)
			{
				if (sender == watcher)
				{
					watchError = args.GetException()?.Message ?? "Unknown file watcher error.";
					changedAt = Environment.TickCount;
					Volatile.Write(ref pendingReload, 3);
				}
			}
		}

		internal static void ProcessPendingReload()
		{
			if (ReloadPending)
			{
				ProcessPendingReload(Environment.TickCount);
			}
		}

		internal static void ProcessPendingReload(int now)
		{
			if (!ReloadPending)
			{
				return;
			}
			int num;
			string text;
			lock (watchGate)
			{
				if (pendingReload == 0 || (uint)(now - changedAt) < 500u)
				{
					return;
				}
				num = pendingReload;
				Volatile.Write(ref pendingReload, 0);
				text = watchError;
				watchError = null;
			}
			if (text != null)
			{
				Plugin.Warn("Custom buff file watcher reported an error; checking the catalog. " + text);
			}
			if ((num & 1) != 0)
			{
				Reload(automatic: true);
			}
			if ((num & 2) != 0)
			{
				ModifierCatalog.Reload(automatic: true);
			}
		}

		private static void StopWatching()
		{
			FileSystemWatcher fileSystemWatcher;
			lock (watchGate)
			{
				fileSystemWatcher = watcher;
				watcher = null;
				Volatile.Write(ref pendingReload, 0);
				watchError = null;
			}
			fileSystemWatcher?.Dispose();
		}

		private static string ReadText(string path)
		{
			using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
			if (fileStream.Length > 262144)
			{
				throw new FormatException("Custom buff YAML exceeds 256 KiB.");
			}
			using StreamReader streamReader = new StreamReader(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: true);
			return streamReader.ReadToEnd();
		}

		private static Sprite LoadIcon(string name, List<Object> pending, ref int pixels)
		{
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Expected O, but got Unknown
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = null;
			try
			{
				byte[] array;
				using (FileStream fileStream = new FileStream(Path.Combine(directory, "icons", name + ".png"), FileMode.Open, FileAccess.Read, FileShare.Read))
				{
					if (fileStream.Length < 33 || fileStream.Length > 1048576)
					{
						throw new FormatException("PNG must be at most 1 MiB.");
					}
					array = new byte[(int)fileStream.Length];
					using BinaryReader binaryReader = new BinaryReader(fileStream);
					if (binaryReader.Read(array, 0, array.Length) != array.Length)
					{
						throw new EndOfStreamException();
					}
				}
				byte[] array2 = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 };
				for (int i = 0; i < 8; i++)
				{
					if (array[i] != array2[i])
					{
						throw new FormatException("Invalid PNGsignature.");
					}
				}
				if (ReadInt(array, 8) != 13 || array[12] != 73 || array[13] != 72 || array[14] != 68 || array[15] != 82)
				{
					throw new FormatException("Invalid PNG header.");
				}
				int num = ReadInt(array, 16);
				int num2 = ReadInt(array, 20);
				if (num < 1 || num2 < 1 || num > 1024 || num2 > 1024 || pixels + num * num2 > 8388608)
				{
					throw new FormatException("PNG dimensions must be 1-1024; total icons are limited to 8 million pixels.");
				}
				val = new Texture2D(2, 2, (TextureFormat)4, false)
				{
					wrapMode = (TextureWrapMode)1,
					filterMode = (FilterMode)1
				};
				if (!ImageConversion.LoadImage(val, array, true) || ((Texture)val).width != num || ((Texture)val).height != num2)
				{
					throw new FormatException("PNG decoding failed.");
				}
				Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)num, (float)num2), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0);
				pending.Add((Object)(object)val);
				pending.Add((Object)(object)val2);
				pixels += num * num2;
				return val2;
			}
			catch (Exception ex)
			{
				if (Object.op_Implicit((Object)(object)val))
				{
					Object.Destroy((Object)(object)val);
				}
				Plugin.Warn("Custom buff icon '" + name + ".png' unavailable; using fallback. " + ex.Message);
				return null;
			}
		}

		private static int ReadInt(byte[] bytes, int at)
		{
			return (bytes[at] << 24) | (bytes[at + 1] << 16) | (bytes[at + 2] << 8) | bytes[at + 3];
		}

		private static void Destroy(List<Object> values)
		{
			for (int num = values.Count - 1; num >= 0; num--)
			{
				if (Object.op_Implicit(values[num]))
				{
					Object.Destroy(values[num]);
				}
			}
			values.Clear();
		}

		internal static void Clear()
		{
			StopWatching();
			ModifierCatalog.Clear();
			loadedYaml = null;
			definitions.Clear();
			Destroy(assets);
			fallback = null;
			directory = null;
			Generation++;
		}
	}
	public sealed class SourceBuff : StatusEffect
	{
		private Source source;

		private float lastRemaining = -1f;

		private float lastDuration = -1f;

		private ModifierSet tooltipSet;

		private IReadOnlyDictionary<string, string> tooltipNames;

		private string tooltipDescription;

		private string namedTooltip;

		private float tooltipDuration = -1f;

		internal void Bind(Source value)
		{
			if (source != value)
			{
				((Object)this).name = value.Owner + ".modifier." + value.Id;
			}
			source = value;
			Food food = value.Food;
			base.m_name = value.Presentation.DisplayName ?? food?.m_item?.m_shared?.m_name ?? value.Id;
			base.m_icon = value.Icon;
			base.m_ttl = ((!value.Presentation.ShowBuffTimers) ? 0f : (food?.m_item.m_shared.m_foodBurnTime ?? value.Duration.GetValueOrDefault()));
			float valueOrDefault = value.Remaining.GetValueOrDefault();
			if (valueOrDefault != lastRemaining || base.m_ttl != lastDuration)
			{
				GameApi.StatusTime.SetValue(this, Math.Max(0f, base.m_ttl - valueOrDefault));
				lastRemaining = valueOrDefault;
				lastDuration = base.m_ttl;
			}
		}

		public override string GetIconText()
		{
			Source obj = source;
			if (obj == null || !obj.Presentation.ShowBuffTimers || !source.Remaining.HasValue)
			{
				return "";
			}
			return GameApi.StatusTimeText(Math.Max(0f, source.Remaining.Value), arg2: false, arg3: false);
		}

		public override bool IsDone()
		{
			if (source != null)
			{
				if (source.Remaining.HasValue)
				{
					return source.Remaining <= 0f;
				}
				return false;
			}
			return true;
		}

		public override void UpdateStatusEffect(float dt)
		{
		}

		public override string GetTooltipString()
		{
			if (source == null || !source.Presentation.ShowModifierDescriptions)
			{
				return "";
			}
			BuffOptions presentation = source.Presentation;
			if (presentation.CustomBuffName == null)
			{
				return presentation.BuffText ?? FrameworkApi.FormatLines(source.Set);
			}
			float num = (presentation.ShowBuffTimers ? base.m_ttl : 0f);
			IReadOnlyDictionary<string, string> readOnlyDictionary = (source.Set.HasNamedFlags ? ModifierCatalog.Names : null);
			if (namedTooltip == null || tooltipSet != source.Set || tooltipNames != readOnlyDictionary || tooltipDescription != presentation.BuffText || tooltipDuration != num)
			{
				namedTooltip = ModifierText.NamedBuffTooltip(source.Set, presentation.BuffText, num);
				tooltipSet = source.Set;
				tooltipNames = readOnlyDictionary;
				tooltipDescription = presentation.BuffText;
				tooltipDuration = num;
			}
			return namedTooltip;
		}
	}
	internal static class BuffDisplay
	{
		[HarmonyPatch(typeof(SEMan), "GetHUDStatusEffects")]
		private static class HudHook
		{
			private static bool Prepare()
			{
				return !Application.isBatchMode;
			}

			private static void Postfix(SEMan __instance, List<StatusEffect> __0)
			{
				Player val = Owner(__instance);
				if (!Object.op_Implicit((Object)(object)val) || __0 == null)
				{
					return;
				}
				try
				{
					ActiveSources.AppendBuffs(val, __0);
				}
				catch (Exception ex)
				{
					Plugin.Report("modifier buff icons", ex);
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "GetStatusEffects")]
		private static class EffectsHook
		{
			private static bool Prepare()
			{
				return !Application.isBatchMode;
			}

			private static void Postfix(SEMan __instance, ref List<StatusEffect> __result)
			{
				Player val = Owner(__instance);
				if (Object.op_Implicit((Object)(object)val) && ActiveSources.HasBuffs(val))
				{
					List<StatusEffect> list = ((__result == null) ? new List<StatusEffect>() : new List<StatusEffect>(__result));
					try
					{
						ActiveSources.AppendBuffs(val, list);
					}
					catch (Exception ex)
					{
						Plugin.Report("active modifiers", ex);
					}
					__result = list;
				}
			}
		}

		private static Player Owner(SEMan manager)
		{
			Player localPlayer = Player.m_localPlayer;
			if (Application.isBatchMode || !Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).GetSEMan() != manager)
			{
				return null;
			}
			return localPlayer;
		}
	}
	internal sealed class DiagnosticLog : IDisposable
	{
		internal const int MaxPendingBytes = 65536;

		private readonly object sync = new object();

		private readonly object drainSync = new object();

		private readonly Queue<byte[]> pending = new Queue<byte[]>();

		private readonly BoundedLog output;

		private readonly Action<Exception> failed;

		private readonly Timer timer;

		private int pendingBytes;

		private int dropped;

		private bool scheduled;

		private bool disposed;

		private bool failureReported;

		internal int PendingBytes
		{
			get
			{
				lock (sync)
				{
					return pendingBytes;
				}
			}
		}

		internal DiagnosticLog(string path, Action<Exception> failed)
		{
			output = new BoundedLog(path);
			this.failed = failed;
			timer = new Timer(delegate
			{
				Flush();
			}, null, -1, -1);
		}

		internal void Write(string level, string message)
		{
			byte[] array = BoundedLog.Entry(level, message);
			lock (sync)
			{
				if (!disposed)
				{
					while (pendingBytes + array.Length > 65536 && pending.Count > 0)
					{
						pendingBytes -= pending.Dequeue().Length;
						dropped++;
					}
					pending.Enqueue(array);
					pendingBytes += array.Length;
					if (!scheduled)
					{
						scheduled = true;
						timer.Change(100, -1);
					}
				}
			}
		}

		internal void Flush()
		{
			lock (drainSync)
			{
				List<byte[]> list;
				lock (sync)
				{
					scheduled = false;
					if (pending.Count == 0)
					{
						return;
					}
					list = new List<byte[]>(pending.Count + 1);
					if (dropped > 0)
					{
						list.Add(BoundedLog.Entry("WARNING", dropped + " earlier diagnostic entries overwritten in the 64 KiB queue during a burst."));
					}
					while (pending.Count > 0)
					{
						list.Add(pending.Dequeue());
					}
					pendingBytes = 0;
					dropped = 0;
				}
				try
				{
					output.WriteEntries(list);
					failureReported = false;
				}
				catch (Exception obj)
				{
					if (!failureReported)
					{
						failureReported = true;
						try
						{
							failed?.Invoke(obj);
							return;
						}
						catch
						{
							return;
						}
					}
				}
			}
		}

		public void Dispose()
		{
			lock (sync)
			{
				if (disposed)
				{
					return;
				}
				disposed = true;
			}
			timer.Dispose();
			Flush();
		}
	}
	internal sealed class FoodBinding
	{
		internal string Owner;

		internal string Item;

		internal string SharedName;

		internal ModifierSet Set;

		internal BuffOptions Display;
	}
	internal static class FoodBindings
	{
		private static readonly Dictionary<string, FoodBinding> foods = new Dictionary<string, FoodBinding>(StringComparer.Ordinal);

		private static readonly Dictionary<string, FoodBinding> names = new Dictionary<string, FoodBinding>(StringComparer.Ordinal);

		internal static FoodBinding Validate(string owner, GameObject prefab, ModifierSet set, BuffOptions display)
		{
			Identity.CheckOwner(owner);
			if (!Object.op_Implicit((Object)(object)prefab))
			{
				throw new ArgumentNullException("prefab");
			}
			if (set == null)
			{
				throw new ArgumentNullException("set");
			}
			string name = ((Object)prefab).name;
			if (string.IsNullOrWhiteSpace(name) || name.Length > 128)
			{
				throw new FormatException("Invalid food prefab identity.");
			}
			ItemData obj = prefab.GetComponent<ItemDrop>()?.m_itemData;
			if (obj?.m_shared == null)
			{
				throw new FormatException("A food binding needs a registered ItemDrop prefab.");
			}
			string name2 = obj.m_shared.m_name;
			if (string.IsNullOrEmpty(name2) || name2.Length > 512)
			{
				throw new FormatException("A food binding needs a shared item name.");
			}
			if ((foods.TryGetValue(name, out var value) && value.Owner != owner) || (names.TryGetValue(name2, out value) && (value.Owner != owner || value.Item != name)))
			{
				throw new FormatException("A different source already owns food binding '" + name + "'.");
			}
			return new FoodBinding
			{
				Owner = owner,
				Item = name,
				SharedName = name2,
				Set = set,
				Display = display
			};
		}

		internal static void Register(string owner, GameObject prefab, ModifierSet set, BuffOptions display)
		{
			FoodBinding foodBinding = Validate(owner, prefab, set, display);
			if (foods.TryGetValue(foodBinding.Item, out var value) && value.SharedName != foodBinding.SharedName)
			{
				names.Remove(value.SharedName);
			}
			foods[foodBinding.Item] = foodBinding;
			names[foodBinding.SharedName] = foodBinding;
			Plugin.Debug("Food registered: " + owner + "/" + foodBinding.Item + "; " + set.Canonical);
		}

		internal static FoodBinding Find(ItemData data)
		{
			if (data?.m_shared == null)
			{
				return null;
			}
			if (!Object.op_Implicit((Object)(object)data.m_dropPrefab) || !foods.TryGetValue(((Object)data.m_dropPrefab).name, out var value))
			{
				if (!names.TryGetValue(data.m_shared.m_name ?? "", out value))
				{
					return null;
				}
				return value;
			}
			return value;
		}

		internal static bool Remove(string owner, string item)
		{
			Identity.CheckOwner(owner);
			if (item == null)
			{
				throw new ArgumentNullException("item");
			}
			if (!foods.TryGetValue(item, out var value) || value.Owner != owner)
			{
				return false;
			}
			foods.Remove(item);
			names.Remove(value.SharedName);
			Plugin.Debug("Food unregistered: " + owner + "/" + item);
			return true;
		}

		internal static void RemoveOwner(string owner)
		{
			Identity.CheckOwner(owner);
			string[] array = (from x in foods.Values
				where x.Owner == owner
				select x.Item).ToArray();
			foreach (string item in array)
			{
				Remove(owner, item);
			}
		}

		internal static void Clear()
		{
			foods.Clear();
			names.Clear();
		}
	}
	internal static class SkillDisplay
	{
		[HarmonyPatch(typeof(SkillsDialog), "Setup")]
		private static class DialogHook
		{
			[HarmonyPrepare]
			private static bool Prepare()
			{
				return !Application.isBatchMode;
			}

			private static void Prefix()
			{
				Reset();
			}

			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				return Rewrite(instructions);
			}

			private static Exception Finalizer(Exception __exception)
			{
				Reset();
				return __exception;
			}
		}

		private static readonly MethodInfo nativeLevel = AccessTools.Method(typeof(Skills), "GetSkillLevel", new Type[1] { typeof(SkillType) }, (Type[])null);

		private static readonly MethodInfo read = AccessTools.Method(typeof(SkillDisplay), "ReadLevel", (Type[])null, (Type[])null);

		private static readonly MethodInfo baseline = AccessTools.Method(typeof(SkillDisplay), "LearnedBase", (Type[])null, (Type[])null);

		private static readonly FieldInfo learned = AccessTools.Field(typeof(Skill), "m_level");

		[ThreadStatic]
		private static bool modifierAffected;

		[ThreadStatic]
		private static SkillType displayedSkill;

		internal static float ReadLevel(Skills skills, SkillType skill)
		{
			//IL_0001: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			float skillLevel = skills.GetSkillLevel(skill);
			modifierAffected = ActiveSources.Bonus(skills, skill) > 0f;
			displayedSkill = skill;
			return skillLevel;
		}

		internal static float LearnedBase(Skill skill)
		{
			//IL_001c: 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)
			float level = skill.m_level;
			if (!modifierAffected || skill.m_info == null || skill.m_info.m_skill != displayedSkill)
			{
				return level;
			}
			return (float)Math.Floor(level);
		}

		internal static void Reset()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			modifierAffected = false;
			displayedSkill = (SkillType)0;
		}

		internal static IEnumerable<CodeInstruction> Rewrite(IEnumerable<CodeInstruction> instructions)
		{
			CodeInstruction[] array = instructions.ToArray();
			List<int> list = new List<int>();
			List<int> list2 = new List<int>();
			bool flag = false;
			for (int i = 0; i < array.Length; i++)
			{
				if ((array[i].opcode == OpCodes.Call || array[i].opcode == OpCodes.Callvirt) && object.Equals(array[i].operand, nativeLevel))
				{
					list.Add(i);
					flag = true;
				}
				if (flag && array[i].opcode == OpCodes.Ldfld && object.Equals(array[i].operand, learned) && i + 1 < array.Length && array[i + 1].opcode == OpCodes.Sub)
				{
					list2.Add(i);
				}
			}
			if (list.Count == 0 || list2.Count == 0)
			{
				Plugin.Warn("Could not locate the Skills dialog level/bonus calculation; native skill display is retained.");
				return array;
			}
			foreach (int item in list)
			{
				array[item].opcode = OpCodes.Call;
				array[item].operand = read;
			}
			foreach (int item2 in list2)
			{
				array[item2].opcode = OpCodes.Call;
				array[item2].operand = baseline;
			}
			return array;
		}
	}
	internal static class ModifierGameplay
	{
		private sealed class Owner
		{
			internal Player Player;
		}

		[HarmonyPatch(typeof(SEMan), "ModifyRunStaminaDrain")]
		private static class RunHook
		{
			private static void Postfix(SEMan __instance, float __0, ref float __1, bool __3)
			{
				Cost(__instance, EffectKind.RunCost, __0, ref __1, __3);
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyJumpStaminaUsage")]
		private static class JumpHook
		{
			private static void Postfix(SEMan __instance, float __0, ref float __1, bool __2)
			{
				Cost(__instance, EffectKind.JumpCost, __0, ref __1, __2);
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifySwimStaminaUsage")]
		private static class SwimHook
		{
			private static void Postfix(SEMan __instance, float __0, ref float __1, bool __2)
			{
				Cost(__instance, EffectKind.SwimCost, __0, ref __1, __2);
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifySneakStaminaUsage")]
		private static class SneakHook
		{
			private static void Postfix(SEMan __instance, float __0, ref float __1, bool __2)
			{
				Cost(__instance, EffectKind.SneakCost, __0, ref __1, __2);
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyBlockStaminaUsage")]
		private static class BlockHook
		{
			private static void Postfix(SEMan __instance, float __0, ref float __1, bool __2)
			{
				Cost(__instance, EffectKind.BlockCost, __0, ref __1, __2);
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyAttackStaminaUsage")]
		private static class AttackCostHook
		{
			private static void Postfix(SEMan __instance, float __0, ref float __1, bool __2)
			{
				Cost(__instance, EffectKind.AttackCost, __0, ref __1, __2);
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyTimedBlockBonus")]
		private static class ParryHook
		{
			private static void Postfix(SEMan __instance, ref float __0)
			{
				float num = Value(__instance, EffectKind.ParryBonus);
				if (num != 0f)
				{
					__0 = Math.Max(0f, __0 * (1f + num / 100f));
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ApplyArmorMods")]
		private static class ArmorHook
		{
			private static void Postfix(SEMan __instance, ref float __0)
			{
				float num = Value(__instance, EffectKind.Armor);
				if (num != 0f)
				{
					__0 = Math.Max(0f, __0 + num);
				}
			}
		}

		[HarmonyPatch(typeof(Humanoid), "BlockAttack")]
		private static class BlockReturnHook
		{
			private static void Postfix(Humanoid __instance, bool __result)
			{
				if (!__result || !ActiveSources.HasEffect(EffectKind.BlockReturn))
				{
					return;
				}
				Player val = (Player)(object)((__instance is Player) ? __instance : null);
				if (Object.op_Implicit((Object)(object)val))
				{
					float num = ActiveSources.Effect(val, EffectKind.BlockReturn);
					if (num > 0f)
					{
						((Character)val).AddStamina(num);
					}
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyHealthRegen")]
		private static class HealthHook
		{
			private static void Postfix(SEMan __instance, ref float __0)
			{
				float num = Value(__instance, EffectKind.HpRegen);
				if (num != 0f)
				{
					__0 = AddPercent(__0, 1f, num);
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyStaminaRegen")]
		private static class StaminaHook
		{
			private static void Postfix(SEMan __instance, ref float __0)
			{
				float num = Value(__instance, EffectKind.StaminaRegen);
				if (num != 0f)
				{
					__0 = AddPercent(__0, 1f, num);
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyEitrRegen")]
		private static class EitrHook
		{
			private static void Postfix(SEMan __instance, ref float __0)
			{
				float num = Value(__instance, EffectKind.EitrRegen);
				if (num != 0f)
				{
					__0 = AddPercent(__0, 1f, num);
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyMaxCarryWeight")]
		private static class CarryHook
		{
			private static void Postfix(SEMan __instance, ref float __1)
			{
				float num = Value(__instance, EffectKind.CarryWeight);
				if (num != 0f)
				{
					__1 = Math.Max(0f, __1 + num);
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyAdrenaline")]
		private static class AdrenalineHook
		{
			private static void Postfix(SEMan __instance, float __0, ref float __1)
			{
				float num = Value(__instance, EffectKind.Adrenaline);
				if (num != 0f)
				{
					__1 = AddPercent(__1, __0, num);
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyStagger")]
		private static class StaggerHook
		{
			private static void Postfix(SEMan __instance, float __0, ref float __1)
			{
				float num = Value(__instance, EffectKind.Stagger);
				if (num != 0f)
				{
					__1 = AddPercent(__1, __0, num);
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ApplyStatusEffectSpeedMods")]
		private static class SpeedHook
		{
			private static void Prefix(float __0, out float __state)
			{
				__state = __0;
			}

			private static void Postfix(SEMan __instance, ref float __0, float __state)
			{
				if (!ActiveSources.HasEffect(EffectKind.MoveSpeed) && !ActiveSources.HasEffect(EffectKind.SwimSpeed))
				{
					return;
				}
				Player val = PlayerFor(__instance);
				float[] array = ActiveSources.Effects(val);
				if (array == null)
				{
					return;
				}
				float num = array[9];
				float num2 = array[37];
				if (num != 0f || num2 != 0f)
				{
					float num3 = (((Character)val).IsSwimming() ? (num * 0.5f + num2) : num);
					if (num3 != 0f)
					{
						__0 = AddPercent(__0, __state, num3);
					}
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ApplyDamageMods")]
		private static class ResistanceHook
		{
			private static void Postfix(SEMan __instance, ref DamageModifiers __0)
			{
				//IL_0009: 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_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_001d: 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_002d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				//IL_003f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				//IL_004b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0051: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_005b: 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_0062: Unknown result type (might be due to invalid IL or missing references)
				//IL_0068: 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_0072: Unknown result type (might be due to invalid IL or missing references)
				//IL_0078: Unknown result type (might be due to invalid IL or missing references)
				//IL_0079: Unknown result type (might be due to invalid IL or missing references)
				//IL_007f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0084: Unknown result type (might be due to invalid IL or missing references)
				//IL_0089: Unknown result type (might be due to invalid IL or missing references)
				//IL_008f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0090: Unknown result type (might be due to invalid IL or missing references)
				//IL_0096: Unknown result type (might be due to invalid IL or missing references)
				//IL_009b: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a6: 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)
				//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bd: 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_00c4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00db: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f2: 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)
				//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
				if (ActiveSources.HasResistanceEffects)
				{
					DamageModifiers val = __0;
					ActiveSources.Resistances(PlayerFor(__instance), ref __0);
					__0.m_blunt = KeepImmunity(val.m_blunt, __0.m_blunt);
					__0.m_slash = KeepImmunity(val.m_slash, __0.m_slash);
					__0.m_pierce = KeepImmunity(val.m_pierce, __0.m_pierce);
					__0.m_chop = KeepImmunity(val.m_chop, __0.m_chop);
					__0.m_pickaxe = KeepImmunity(val.m_pickaxe, __0.m_pickaxe);
					__0.m_fire = KeepImmunity(val.m_fire, __0.m_fire);
					__0.m_frost = KeepImmunity(val.m_frost, __0.m_frost);
					__0.m_lightning = KeepImmunity(val.m_lightning, __0.m_lightning);
					__0.m_poison = KeepImmunity(val.m_poison, __0.m_poison);
					__0.m_spirit = KeepImmunity(val.m_spirit, __0.m_spirit);
				}
			}
		}

		[HarmonyPatch(typeof(SEMan), "ModifyAttack")]
		private static class AttackHook
		{
			private static void Prefix(HitData __1, out DamageTypes __state)
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				//IL_0005: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				__state = (DamageTypes)(((??)__1?.m_damage) ?? default(DamageTypes));
			}

			private static void Postfix(SEMan __instance, HitData __1, DamageTypes __state)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				AttackHit(__instance, __1, __state);
			}
		}

		[HarmonyPatch(typeof(SEMan), "Update")]
		private static class TailwindHook
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				return Tailwind(instructions);
			}
		}

		private static readonly FieldInfo character = AccessTools.Field(typeof(SEMan), "m_character");

		private static readonly ConditionalWeakTable<SEMan, Owner> owners = new ConditionalWeakTable<SEMan, Owner>();

		private static ObjectDB torchDatabase;

		private static short torchVariant;

		internal static Player PlayerFor(SEMan manager)
		{
			return owners.GetValue(manager, (SEMan m) => new Owner
			{
				Player = (Player)/*isinst with value type is only supported in some contexts*/
			}).Player;
		}

		private static float Value(SEMan manager, EffectKind kind)
		{
			if (!ActiveSources.HasEffect(kind))
			{
				return 0f;
			}
			return ActiveSources.Effect(PlayerFor(manager), kind);
		}

		internal static float AddPercent(float current, float basis, float percent)
		{
			if (percent != 0f)
			{
				return Math.Max(0f, current + basis * (percent / 100f));
			}
			return current;
		}

		internal static void Cost(SEMan manager, EffectKind kind, float basis, ref float value, bool minZero)
		{
			float num = Value(manager, kind);
			if (num != 0f)
			{
				value = ((kind == EffectKind.BlockCost && !minZero) ? Math.Min(0f, value) : 0f) + Math.Max(0f, Math.Max(0f, value) + basis * (num / 100f));
				if (minZero)
				{
					value = Math.Max(0f, value);
				}
			}
		}

		internal static void ActivationGains(Player player, ModifierSet set)
		{
			if (set.HealthInstant > 0f)
			{
				((Character)player).Heal(set.HealthInstant, true);
			}
			if (set.StaminaInstant > 0f)
			{
				((Character)player).AddStamina(set.StaminaInstant);
			}
			if (set.EitrInstant > 0f)
			{
				((Character)player).AddEitr(set.EitrInstant);
			}
		}

		internal static DamageModifier KeepImmunity(DamageModifier original, DamageModifier changed)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if ((int)original != 3 && (int)original != 4)
			{
				return changed;
			}
			return original;
		}

		internal static bool Attack(SEMan manager, DamageTypes basis, ref DamageTypes value)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: 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_0090: 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_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: 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)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			if (!ActiveSources.HasAttackEffects || !ActiveSources.AttackValues(PlayerFor(manager), out var effects, out var addedDamage))
			{
				return false;
			}
			float num = effects[10];
			value.m_damage = AddPercent(value.m_damage, basis.m_damage, num);
			value.m_nonPlayer = AddPercent(value.m_nonPlayer, basis.m_nonPlayer, num);
			value.m_blunt = AddPercent(value.m_blunt, basis.m_blunt, num + effects[24]);
			value.m_slash = AddPercent(value.m_slash, basis.m_slash, num + effects[25]);
			value.m_pierce = AddPercent(value.m_pierce, basis.m_pierce, num + effects[26]);
			value.m_fire = AddPercent(value.m_fire, basis.m_fire, num + effects[27]);
			value.m_frost = AddPercent(value.m_frost, basis.m_frost, num + effects[28]);
			value.m_lightning = AddPercent(value.m_lightning, basis.m_lightning, num + effects[29]);
			value.m_poison = AddPercent(value.m_poison, basis.m_poison, num + effects[30]);
			value.m_spirit = AddPercent(value.m_spirit, basis.m_spirit, num + effects[31]);
			value.m_chop = AddPercent(value.m_chop, basis.m_chop, num + effects[32]);
			value.m_pickaxe = AddPercent(value.m_pickaxe, basis.m_pickaxe, num + effects[33]);
			if (addedDamage != null)
			{
				float num2 = DamageRanges.Roll(addedDamage, AddedDamageKind.Fire);
				value.m_fire += num2;
				value.m_frost += DamageRanges.Roll(addedDamage, AddedDamageKind.Frost);
				value.m_lightning += DamageRanges.Roll(addedDamage, AddedDamageKind.Lightning);
				value.m_spirit += DamageRanges.Roll(addedDamage, AddedDamageKind.Spirit);
				value.m_poison += DamageRanges.Roll(addedDamage, AddedDamageKind.Poison);
				return num2 > 0f;
			}
			return false;
		}

		internal static short TorchVariant()
		{
			ObjectDB instance = ObjectDB.instance;
			if (instance != torchDatabase)
			{
				torchVariant = 0;
				GameObject val = (Object.op_Implicit((Object)(object)instance) ? instance.GetItemPrefab("Torch") : null);
				ItemDrop val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<ItemDrop>() : null);
				if (Object.op_Implicit((Object)(object)val2) && val2.m_itemData?.m_shared != null && val2.m_itemData.m_shared.m_hitVariant >= 0)
				{
					torchVariant = val2.m_itemData.m_shared.m_hitVariant;
				}
				torchDatabase = instance;
			}
			return torchVariant;
		}

		internal static void AttackHit(SEMan manager, HitData hit, DamageTypes basis)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			if (hit != null && Attack(manager, basis, ref hit.m_damage))
			{
				hit.m_variant = TorchVariant();
			}
		}

		internal static int InitialAttributes(SEMan manager)
		{
			if (!(Value(manager, EffectKind.Tailwind) > 0f))
			{
				return 0;
			}
			return 4;
		}

		internal static IEnumerable<CodeInstruction> Tailwind(IEnumerable<CodeInstruction> instructions)
		{
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Expected O, but got Unknown
			List<CodeInstruction> list = instructions.ToList();
			FieldInfo objB = AccessTools.Field(typeof(SEMan), "m_statusEffectAttributes");
			int i;
			for (i = 0; i < list.Count && list[i].opcode == OpCodes.Nop; i++)
			{
			}
			if (list.Count < i + 3 || list[i].opcode != OpCodes.Ldarg_0 || list[i + 1].opcode != OpCodes.Ldc_I4_0 || list[i + 2].opcode != OpCodes.Stfld || !object.Equals(list[i + 2].operand, objB))
			{
				throw new NotSupportedException("Unrecognized native status-attribute initialization.");
			}
			list[i + 1].opcode = OpCodes.Ldarg_0;
			list.Insert(i + 2, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(ModifierGameplay), "InitialAttributes", (Type[])null, (Type[])null)));
			return list;
		}
	}
	internal static class ModifierText
	{
		private static readonly string[] damageTokens = new string[10] { "$inventory_blunt", "$inventory_slash", "$inventory_pierce", "$inventory_fire", "$inventory_frost", "$inventory_lightning", "$inventory_poison", "$inventory_spirit", "$inventory_chop", "$inventory_pickaxe" };

		private static readonly string[] tierTokens = new string[7] { "$inventory_veryweak", "$inventory_weak", "$inventory_slightlyweak", "$inventory_normal", "$inventory_slightlyresistant", "$inventory_resistant", "$inventory_veryresistant" };

		private static string Localize(string token, string fallback)
		{
			string text = ((Localization.instance == null) ? token : Localization.instance.Localize(token));
			if (!(text == token) && !text.StartsWith("[", StringComparison.Ordinal))
			{
				return text;
			}
			return fallback;
		}

		internal unsafe static string SkillName(SkillType skill)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Invalid comparison between Unknown and I4
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Invalid comparison between Unknown and I4
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Invalid comparison between Unknown and I4
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Invalid comparison between Unknown and I4
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Invalid comparison between Unknown and I4
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Invalid comparison between Unknown and I4
			string fallback = ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString();
			if ((int)skill == 102)
			{
				fallback = "Running";
			}
			else if ((int)skill == 103)
			{
				fallback = "Swimming";
			}
			else if ((int)skill == 100)
			{
				fallback = "Jumping";
			}
			else if ((int)skill == 13)
			{
				fallback = "Wood cutting";
			}
			else if ((int)skill == 9)
			{
				fallback = "Elemental magic";
			}
			else if ((int)skill == 10)
			{
				fallback = "Blood magic";
			}
			return Localize("$skill_" + ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant(), fallback);
		}

		private static string Gain(float amount, string label)
		{
			return "+" + amount.ToString("R", CultureInfo.InvariantCulture) + " " + label;
		}

		internal static string SkillLines(IEnumerable<SkillBonus> bonuses)
		{
			return string.Join("\n", from x in bonuses ?? Array.Empty<SkillBonus>()
				where x.Value > 0f
				select Gain(x.Value, SkillName(x.Skill)));
		}

		internal static string Lines(ModifierSet set)
		{
			return string.Join("\n", new string[3]
			{
				SkillLines(set.SkillValues),
				string.Join("\n", set.EffectValues.Where((EffectBonus x) => x.Value != 0f).Select(Modifiers.Describe)),
				string.Join("\n", set.AddedDamageValues.Where((AddedDamageBonus x) => x.Max > 0f).Select(DamageRanges.Describe))
			}.Where((string x) => x.Length > 0));
		}

		private static string NativeLabel(EffectKind kind)
		{
			if (kind >= EffectKind.BluntDamage && kind <= EffectKind.PickaxeDamage)
			{
				return damageTokens[(int)(kind - 24)];
			}
			return kind switch
			{
				EffectKind.RunCost => "$se_runstamina", 
				EffectKind.JumpCost => "$se_jumpstamina", 
				EffectKind.SwimCost => "$se_swimstamina", 
				EffectKind.SneakCost => "$se_sneakstamina", 
				EffectKind.BlockCost => "$se_blockstamina", 
				EffectKind.HpRegen => "$se_healthregen", 
				EffectKind.StaminaRegen => "$se_staminaregen", 
				EffectKind.EitrRegen => "$se_eitrregen", 
				EffectKind.CarryWeight => "$se_max_carryweight", 
				EffectKind.MoveSpeed => "$item_movement_modifier", 
				EffectKind.SwimSpeed => "$item_swimspeed_modifier", 
				EffectKind.Damage => "$base_item_modifier", 
				EffectKind.Adrenaline => "$se_adrenaline", 
				EffectKind.Stagger => "$se_stagger", 
				EffectKind.ParryBonus => "$item_parrybonus", 
				EffectKind.Armor => "$item_armor", 
				EffectKind.HealthInstant => "$se_health_upfront", 
				EffectKind.StaminaInstant => "$se_stamina_upfront", 
				EffectKind.EitrInstant => "$se_eitr_upfront", 
				EffectKind.AttackCost => "$se_attackstamina", 
				EffectKind.BlockReturn => "Stamina per block", 
				_ => throw new ArgumentOutOfRangeException("kind"), 
			};
		}

		private static string Number(float value)
		{
			return value.ToString("R", CultureInfo.InvariantCulture);
		}

		private static string Signed(float value)
		{
			return ((value > 0f) ? "+" : "") + Number(value);
		}

		private static void Row(StringBuilder text, string label, string value)
		{
			if (text.Length > 0 && text[text.Length - 1] != '\n')
			{
				text.Append('\n');
			}
			text.Append(label);
			if (value != null)
			{
				text.Append(": <color=orange>").Append(value).Append("</color>");
			}
		}

		internal unsafe static string NamedBuffTooltip(ModifierSet set, string description, float duration)
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder(256);
			if (!string.IsNullOrEmpty(description))
			{
				stringBuilder.Append(description.TrimEnd('\r', '\n'));
			}
			bool flag = set.HasAny || duration > 1f;
			if (stringBuilder.Length > 0 && flag)
			{
				stringBuilder.Append("\n\n");
			}
			SkillBonus[] skillValues = set.SkillValues;
			for (int i = 0; i < skillValues.Length; i++)
			{
				SkillBonus skillBonus = skillValues[i];
				if (skillBonus.Value > 0f)
				{
					SkillType skill = skillBonus.Skill;
					Row(stringBuilder, "$skill_" + ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant(), Signed(skillBonus.Value));
				}
			}
			EffectBonus[] effectValues = set.EffectValues;
			for (int i = 0; i < effectValues.Length; i++)
			{
				EffectBonus effect = effectValues[i];
				if (effect.Value == 0f)
				{
					continue;
				}
				if (Modifiers.Resistance(effect.Kind))
				{
					if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '\n')
					{
						stringBuilder.Append('\n');
					}
					stringBuilder.Append("$inventory_dmgmod: <color=orange>").Append(tierTokens[(int)effect.Value + 3]).Append("</color> VS <color=orange>")
						.Append(damageTokens[(int)(effect.Kind - 14)])
						.Append("</color>");
				}
				else if (Modifiers.Flag(effect.Kind))
				{
					Row(stringBuilder, Modifiers.Describe(effect), null);
				}
				else
				{
					Row(stringBuilder, NativeLabel(effect.Kind), Signed(effect.Value) + (Modifiers.Percentage(effect.Kind) ? "%" : ""));
				}
			}
			AddedDamageBonus[] addedDamageValues = set.AddedDamageValues;
			for (int i = 0; i < addedDamageValues.Length; i++)
			{
				AddedDamageBonus addedDamageBonus = addedDamageValues[i];
				if (addedDamageBonus.Max != 0f)
				{
					string text = ((addedDamageBonus.Kind == AddedDamageKind.Fire) ? damageTokens[3] : ((addedDamageBonus.Kind == AddedDamageKind.Frost) ? damageTokens[4] : ((addedDamageBonus.Kind == AddedDamageKind.Lightning) ? damageTokens[5] : ((addedDamageBonus.Kind == AddedDamageKind.Spirit) ? damageTokens[7] : damageTokens[6]))));
					Row(stringBuilder, "Added " + text + " damage", "+" + Number(addedDamageBonus.Min) + ((addedDamageBonus.Min == addedDamageBonus.Max) ? "" : ("-" + Number(addedDamageBonus.Max))));
				}
			}
			if (duration > 1f)
			{
				Row(stringBuilder, "$se_ttl", Number(duration));
			}
			return stringBuilder.ToString();
		}
	}
	internal static class ModifierCatalog
	{
		internal const int MaxBytes = 65536;

		private static readonly Dictionary<string, string> defaults = BuildDefaults();

		private static IReadOnlyDictionary<string, string> names = new ReadOnlyDictionary<string, string>(defaults);

		private static string path;

		private static string loadedYaml;

		internal static IReadOnlyDictionary<string, string> Names => Volatile.Read(in names);

		private static Dictionary<string, string> BuildDefaults()
		{
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			string[] array = new string[43]
			{
				"Stamina drain while running", "Stamina drain while jumping", "Stamina drain while swimming", "Stamina drain while sneaking", "Stamina drain while blocking", "Stamina restored per block", "Health regeneration", "Eitr regeneration", "Maximum carry weight", "Movement speed",
				"Weapon damage", "Adrenaline gain", "Stagger buildup", "Sailing tailwind", "Blunt resistance", "Slash resistance", "Pierce resistance", "Fire resistance", "Frost resistance", "Lightning resistance",
				"Poison resistance", "Spirit resistance", "Chop resistance", "Pickaxe resistance", "Blunt damage", "Slash damage", "Pierce damage", "Fire damage", "Frost damage", "Lightning damage",
				"Poison damage", "Spirit damage", "Chop damage", "Pickaxe damage", "Parry bonus", "Stamina regeneration", "Armor", "Swimming speed", "Health restored on activation", "Stamina restored on activation",
				"Eitr restored on activation", "Stamina drain while attacking", "Immunity to Wet"
			};
			if (array.Length != 43)
			{
				throw new InvalidOperationException("Missing modifier full names.");
			}
			foreach (EffectKind value in Enum.GetValues(typeof(EffectKind)))
			{
				dictionary.Add(Modifiers.Name(value), array[(int)value]);
			}
			foreach (AddedDamageKind value2 in Enum.GetValues(typeof(AddedDamageKind)))
			{
				dictionary.Add(DamageRanges.Name(value2), "Added " + value2.ToString().ToLowerInvariant() + " damage");
			}
			foreach (string skillName in FrameworkApi.SkillNames)
			{
				FrameworkApi.TrySkill(skillName, out var skill);
				dictionary.Add(skillName, SkillLabel(skill) + " skill");
			}
			foreach (string item in new string[3] { "runStamina", "runSpeed", "slash" }.Concat(FrameworkApi.SkillLookupNames.Select((string x) => x.ToLowerInvariant())))
			{
				if (!dictionary.ContainsKey(item))
				{
					string key = Canonical(item);
					dictionary.Add(item, dictionary[key]);
				}
			}
			dictionary["swords"] = "Sword skill";
			dictionary["knives"] = "Dagger skill";
			dictionary["clubs"] = "Club skill";
			dictionary["polearms"] = "Polearm skill";
			dictionary["spears"] = "Spear skill";
			dictionary["axes"] = "Axe skill";
			dictionary["bows"] = "Bow skill";
			dictionary["pickaxes"] = "Pickaxe skill";
			dictionary["crossbows"] = "Crossbow skill";
			dictionary["jump"] = "Jump skill";
			dictionary["sneak"] = "Sneak skill";
			dictionary["run"] = "Run skill";
			dictionary["swim"] = "Swim skill";
			return dictionary;
		}

		private unsafe static string SkillLabel(SkillType skill)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected I4, but got Unknown
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			switch (skill - 100)
			{
			default:
				if ((int)skill == 110)
				{
					return "Riding";
				}
				return Regex.Replace(((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString(), "([a-z])([A-Z])", "$1 $2");
			case 2:
				return "Running";
			case 0:
				return "Jumping";
			case 3:
				return "Swimming";
			case 1:
				return "Sneaking";
			}
		}

		private unsafe static string Canonical(string name)
		{
			if (Modifiers.TryKind(name, out var kind))
			{
				return Modifiers.Name(kind);
			}
			if (DamageRanges.TryKind(name, out var kind2))
			{
				return DamageRanges.Name(kind2);
			}
			if (FrameworkApi.TrySkill(name, out var skill))
			{
				return ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant();
			}
			return null;
		}

		internal static bool TryGet(string name, out string fullName)
		{
			fullName = null;
			if (string.IsNullOrWhiteSpace(name) || name.Length > 128)
			{
				return false;
			}
			name = name.Trim();
			IReadOnlyDictionary<string, string> readOnlyDictionary = Volatile.Read(in names);
			if (readOnlyDictionary.TryGetValue(name, out fullName))
			{
				return true;
			}
			string text = Canonical(name);
			if (text != null)
			{
				return readOnlyDictionary.TryGetValue(text, out fullName);
			}
			return false;
		}

		internal static string DefaultYaml()
		{
			StringBuilder stringBuilder = new StringBuilder("# Readable modifier names for calling mods; display metadata only.\n# Full name is a single-line string. Missing entries use built-in names.\n# Entries are alphabetical. Comments describe modifier inputs, not YAML settings.\n# Use examples in a caller's modifier list or a buff's effectsList.\n\n");
			foreach (KeyValuePair<string, string> item in defaults.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> x) => x.Key, StringComparer.OrdinalIgnoreCase))
			{
				Explain(item.Key, out var effect, out var unit, out var example);
				string text = Canonical(item.Key);
				if (!item.Key.Equals(text, StringComparison.OrdinalIgnoreCase))
				{
					effect = "Alias of " + text + ". " + effect;
				}
				stringBuilder.Append("# Effect: ").Append(effect).Append("\n# Unit: ")
					.Append(unit)
					.Append("\n# Example: ")
					.Append(example)
					.Append('\n');
				stringBuilder.Append(item.Key).Append(":\n  Full name: ").Append('"')
					.Append(item.Value.Replace("\\", "\\\\").Replace("\"", "\\\""))
					.Append("\"\n\n");
			}
			return stringBuilder.ToString();
		}

		private unsafe static void Explain(string name, out string effect, out string unit, out string example)
		{
			AddedDamageKind kind2;
			if (Modifiers.TryKind(name, out var kind))
			{
				if (Modifiers.Resistance(kind))
				{
					string text = kind.ToString().Substring(0, kind.ToString().Length - "Resist".Length).ToLowerInvariant();
					effect = "Changes incoming " + text + " resistance; positive tiers resist, negative tiers weaken. Native immunity remains intact.";
					unit = "Whole-number resistance tier -3..3, without %; 0 contributes no tier. Totals clamp to -3..3.";
					example = name + "[2]";
					return;
				}
				if (kind >= EffectKind.BluntDamage && kind <= EffectKind.PickaxeDamage)
				{
					string text2 = kind.ToString().Substring(0, kind.ToString().Length - "Damage".Length).ToLowerInvariant();
					effect = "Changes existing outgoing " + text2 + " weapon damage, before flat added damage; does not add a missing damage type.";
					unit = "Percentage (%); positive increases damage, negative reduces it. % is optional.";
					example = name + "[30%]";
					return;
				}
				unit = "Percentage (%); positive increases the named quantity, negative reduces it. % is optional.";
				example = name + "[30%]";
				switch (kind)
				{
				case EffectKind.RunCost:
					effect = "Changes stamina drain while running; negative values reduce the drain.";
					example = name + "[-30%]";
					break;
				case EffectKind.JumpCost:
					effect = "Changes stamina used when jumping; negative values reduce the cost.";
					example = name + "[-30%]";
					break;
				case EffectKind.SwimCost:
					effect = "Changes stamina drain while swimming; negative values reduce the drain.";
					example = name + "[-30%]";
					break;
				case EffectKind.SneakCost:
					effect = "Changes stamina drain while sneaking; negative values reduce the drain.";
					example = name + "[-30%]";
					break;
				case EffectKind.BlockCost:
					effect = "Changes stamina used when blocking; negative values reduce the cost without creating a stamina refund.";
					example = name + "[-30%]";
					break;
				case EffectKind.AttackCost:
					effect = "Changes stamina used by attacks; negative values reduce the cost.";
					example = name + "[-30%]";
					break;
				case EffectKind.BlockReturn:
					effect = "Restores stamina once after each successful block, separately from blocking cost.";
					unit = "Flat stamina points per successful block, without %.";
					example = name + "[5]";
					break;
				case EffectKind.HpRegen:
					effect = "Changes the health regeneration factor; 30% adds 0.3 to the factor, not 30 health per tick.";
					break;
				case EffectKind.StaminaRegen:
					effect = "Changes the stamina regeneration factor; 30% adds 0.3 to the factor, not 30 stamina per tick.";
					break;
				case EffectKind.EitrRegen:
					effect = "Changes the eitr regeneration factor; 30% adds 0.3 to the factor, not 30 eitr per tick.";
					break;
				case EffectKind.CarryWeight:
					effect = "Adds to maximum carry capacity; negative values reduce it. Does not change item weight.";
					unit = "Flat carry-weight units, without %.";
					example = name + "[100]";
					break;
				case EffectKind.MoveSpeed:
					effect = "Changes general movement speed, including running; swimming receives half this percentage (30% gives 15% while swimming).";
					break;
				case EffectKind.SwimSpeed:
					effect = "Changes swimming speed only; the full percentage adds alongside half any moveSpeed/runSpeed percentage.";
					break;
				case EffectKind.Damage:
					effect = "Changes existing outgoing weapon damage across damage channels, before flat added damage.";
					break;
				case EffectKind.Adrenaline:
					effect = "Changes adrenaline gained from the original native gain; negative values reduce it.";
					break;
				case EffectKind.Stagger:
					effect = "Changes incoming stagger buildup; negative values reduce it.";
					example = name + "[-30%]";
					break;
				case EffectKind.ParryBonus:
					effect = "Changes the native timed-parry block armor multiplier; 30% multiplies the current parry value by 1.3. Does not widen the parry window.";
					break;
				case EffectKind.Armor:
					effect = "Adds flat armor; negative values reduce armor, with a minimum total of zero.";
					unit = "Flat armor points, without %.";
					example = name + "[20]";
					break;
				case EffectKind.HealthInstant:
					effect = "Restores current health once on successful source activation or food consumption, within the existing maximum. Does not raise maximum health.";
					unit = "Flat health points per activation, without %.";
					example = name + "[50]";
					break;
				case EffectKind.StaminaInstant:
					effect = "Restores current stamina once on successful source activation or food consumption, within the existing maximum. Does not raise maximum stamina.";
					unit = "Flat stamina points per activation, without %.";
					example = name + "[50]";
					break;
				case EffectKind.EitrInstant:
					effect = "Restores current eitr once on successful source activation or food consumption, within the existing maximum. Does not raise maximum eitr.";
					unit = "Flat eitr points per activation, without %.";
					example = name + "[50]";
					break;
				case EffectKind.Tailwind:
					effect = "Enables the native sailing tailwind while active; normal ship rules apply.";
					unit = "Flag 1 = contribute tailwind, 0 = contribute no tailwind, without %.";
					example = name + "[1]";
					break;
				case EffectKind.WetImmune:
					effect = "Removes existing Wet when immunity starts and prevents the native Wet condition while at least one enabled source is active. Normal Wet application resumes when the final source ends.";
					unit = "Boolean true or false, without %. False contributes no immunity and does not cancel another source.";
					example = name + "[true]";
					break;
				default:
					throw new ArgumentOutOfRangeException("kind");
				}
			}
			else if (DamageRanges.TryKind(name, out kind2))
			{
				effect = "Adds " + kind2.ToString().ToLowerInvariant() + " damage to every weapon hit after percentage bonuses, even when the weapon has none. Targets apply native damage rules.";
				if (kind2 == AddedDamageKind.Fire)
				{
					effect += " Uses native burning ticks and torch fire visuals.";
				}
				unit = "Flat damage points; fixed value or min-max range (0..100000, min <= max), without %.";
				example = name + "[4-8] or " + name + "[50]";
			}
			else
			{
				if (!FrameworkApi.TrySkill(name, out var skill))
				{
					throw new FormatException("Unknown default modifier '" + name + "'.");
				}
				effect = "Adds temporary levels to the " + defaults[((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant()] + "; learned levels and XP remain unchanged.";
				unit = "Skill levels (0..100), without %; effective skill is capped at 100.";
				example = name + "[10]";
			}
		}

		internal static Dictionary<string, string> Parse(string yaml)
		{
			if (Encoding.UTF8.GetByteCount(yaml) > 65536)
			{
				throw new FormatException("modifiers.yaml exceeds 64 KiB.");
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>(defaults, StringComparer.OrdinalIgnoreCase);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string text = null;
			bool flag = true;
			bool flag2 = false;
			bool flag3 = false;
			string[] array = yaml.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string obj = array[i];
				if (obj.IndexOf('\t') >= 0)
				{
					throw Error(i, "Use spaces for indentation.");
				}
				string text2 = CustomBuffYaml.StripComment(obj).TrimEnd();
				if (string.IsNullOrWhiteSpace(text2))
				{
					continue;
				}
				int num = text2.Length - text2.TrimStart(' ').Length;
				text2 = text2.TrimStart(' ');
				if (num == 0)
				{
					if (text2 == "---" && !flag3)
					{
						flag3 = true;
						continue;
					}
					if (text2 == "{}" && text == null && !flag2)
					{
						flag2 = true;
						flag3 = true;
						continue;
					}
					if (flag2 || !flag || !text2.EndsWith(":", StringComparison.Ordinal))
					{
						throw Error(i, "Expected a modifier key after a complete Full name row.");
					}
					text = CustomBuffYaml.Scalar(text2.Substring(0, text2.Length - 1).Trim());
					if (!defaults.ContainsKey(text))
					{
						throw Error(i, "Unknown modifier key '" + text + "'.");
					}
					if (!hashSet.Add(text))
					{
						throw Error(i, "Duplicate modifier key '" + text + "'.");
					}
					flag = false;
					flag3 = true;
				}
				else
				{
					if (text == null || num != 2 || flag || !text2.StartsWith("Full name:", StringComparison.Ordinal))
					{
						throw E