Decompiled source of ValhATLYSS v0.4.19

plugins/ValhATLYSS/ValhATLYSS.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using Mirror;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("ValhATLYSS")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+f7c5f081f7b5148cfa891a52698a5ac58702a1c1")]
[assembly: AssemblyProduct("ValhATLYSS")]
[assembly: AssemblyTitle("ValhATLYSS")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 ValhATLYSS
{
	internal static class HoddKista
	{
		private struct VaultModel
		{
			public int Level;

			public long Exp;
		}

		private static string _root;

		private static readonly Regex RxLevel = new Regex("(?im)^\\s*level\\s*=\\s*(?<n>-?\\d+)\\s*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);

		private static readonly Regex RxExp = new Regex("(?im)^\\s*exp\\s*=\\s*(?<n>-?\\d+)\\s*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);

		internal static void EnsureReady(ManualLogSource log)
		{
			try
			{
				string profilesRoot = Plugin.GetProfilesRoot();
				if (!string.IsNullOrEmpty(profilesRoot))
				{
					_root = Path.Combine(profilesRoot, "ValhATLYSS", "vault");
					Directory.CreateDirectory(_root);
					if (log != null)
					{
						log.LogInfo((object)("[ValhATLYSS] Vault ready at: " + _root));
					}
				}
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogWarning((object)("[ValhATLYSS] Vault setup failed: " + ex.Message));
				}
			}
		}

		private static string VaultPathForProfile(string profileFullPath)
		{
			string text = Path.GetFileName(profileFullPath) ?? "unknown_profile";
			return Path.Combine(_root ?? "", text + ".json");
		}

		private static bool LoadVault(string vp, out VaultModel model)
		{
			model = new VaultModel
			{
				Level = 0,
				Exp = -1L
			};
			try
			{
				if (!File.Exists(vp))
				{
					return false;
				}
				string input;
				using (FileStream stream = new FileStream(vp, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
				{
					using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
					input = streamReader.ReadToEnd();
				}
				Match match = RxLevel.Match(input);
				Match match2 = RxExp.Match(input);
				if (match.Success && int.TryParse(match.Groups["n"].Value, out var result))
				{
					model.Level = Math.Max(0, result);
				}
				if (match2.Success && long.TryParse(match2.Groups["n"].Value, out var result2))
				{
					model.Exp = ((result2 < 0) ? (-1) : result2);
				}
				return match.Success || match2.Success;
			}
			catch
			{
				return false;
			}
		}

		private static void SaveVault(string vp, VaultModel model)
		{
			try
			{
				StringBuilder stringBuilder = new StringBuilder(64);
				stringBuilder.Append("level=").Append(model.Level).Append('\n');
				stringBuilder.Append("exp=").Append(model.Exp).Append('\n');
				string text = vp + ".tmp";
				File.WriteAllText(text, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				File.Copy(text, vp, overwrite: true);
				File.Delete(text);
			}
			catch
			{
			}
		}

		internal static int Reconcile(string profilePath, KappaSlot.DiskStats disk, ManualLogSource log)
		{
			if (string.IsNullOrEmpty(_root))
			{
				return 0;
			}
			string vp = VaultPathForProfile(profilePath);
			LoadVault(vp, out var model);
			bool num = disk.HasLevel || disk.HasExp;
			bool flag = model.Level > 0 || model.Exp >= 0;
			if (!num && !flag)
			{
				if (log != null)
				{
					log.LogInfo((object)"[ValhATLYSS] Reconcile: nothing to do (no readable stats).");
				}
				return 0;
			}
			int num2 = (disk.HasLevel ? disk.Level : 0);
			long num3 = (disk.HasExp ? disk.Exp : (-1));
			int level = model.Level;
			long exp = model.Exp;
			if (level > num2 || (level == num2 && exp >= 0 && num3 >= 0 && exp > num3))
			{
				if (disk.HasLevel || disk.HasExp)
				{
					KappaSlot.DiskStats diskStats = default(KappaSlot.DiskStats);
					diskStats.Level = ((level > 0) ? level : num2);
					diskStats.Exp = ((exp >= 0) ? exp : num3);
					diskStats.LevelPatternUsed = disk.LevelPatternUsed;
					diskStats.ExpPatternUsed = disk.ExpPatternUsed;
					KappaSlot.DiskStats target = diskStats;
					if (KappaSlot.TryWriteProfileStats(profilePath, target, log))
					{
						if (log != null)
						{
							log.LogInfo((object)"[ValhATLYSS] Disk was restored from vault (anti-regression).");
						}
						SaveVault(vp, new VaultModel
						{
							Level = Math.Max(level, target.Level),
							Exp = Math.Max(exp, target.Exp)
						});
						return -1;
					}
					if (log != null)
					{
						log.LogInfo((object)"[ValhATLYSS] Vault better, but safe patch failed (pattern not found).");
					}
					return 0;
				}
				if (log != null)
				{
					log.LogInfo((object)"[ValhATLYSS] Vault better but disk tokens not recognized; skip write.");
				}
				return 0;
			}
			VaultModel vaultModel = default(VaultModel);
			vaultModel.Level = Math.Max(level, num2);
			vaultModel.Exp = Math.Max(exp, num3);
			VaultModel model2 = vaultModel;
			SaveVault(vp, model2);
			return 1;
		}
	}
	internal static class KappaSlot
	{
		internal struct DiskStats
		{
			public int Level;

			public long Exp;

			public string LevelPatternUsed;

			public string ExpPatternUsed;

			public bool HasLevel
			{
				get
				{
					if (Level > 0)
					{
						return !string.IsNullOrEmpty(LevelPatternUsed);
					}
					return false;
				}
			}

			public bool HasExp
			{
				get
				{
					if (Exp >= 0)
					{
						return !string.IsNullOrEmpty(ExpPatternUsed);
					}
					return false;
				}
			}
		}

		private static bool TryParseStatsFromText(string text, out DiskStats stats, ManualLogSource log)
		{
			stats = default(DiskStats);
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string[] array = new string[4] { "level", "mainLevel", "playerLevel", "lvl" };
			string[] array2 = new string[5] { "exp", "experience", "mainExp", "xp", "totalExp" };
			string[] array3 = array;
			for (int i = 0; i < array3.Length; i++)
			{
				string text2 = MakeNumberPattern(array3[i]);
				Match match = Regex.Match(text, text2, RegexOptions.CultureInvariant);
				if (match.Success && int.TryParse(match.Groups["num"].Value, out var result) && result > 0)
				{
					stats.Level = result;
					stats.LevelPatternUsed = text2;
					break;
				}
			}
			array3 = array2;
			for (int i = 0; i < array3.Length; i++)
			{
				string text3 = MakeNumberPattern(array3[i]);
				Match match2 = Regex.Match(text, text3, RegexOptions.CultureInvariant);
				if (match2.Success && long.TryParse(match2.Groups["num"].Value, out var result2) && result2 >= 0)
				{
					stats.Exp = result2;
					stats.ExpPatternUsed = text3;
					break;
				}
			}
			int num;
			if (!stats.HasLevel)
			{
				num = (stats.HasExp ? 1 : 0);
				if (num == 0 && log != null)
				{
					log.LogInfo((object)"[ValhATLYSS] Parser: no Level/Exp tokens matched. Profile format likely changed.");
				}
			}
			else
			{
				num = 1;
			}
			return (byte)num != 0;
			static string MakeNumberPattern(string key)
			{
				return "(?i)(?:[\"']?" + Regex.Escape(key) + "[\"']?\\s*[:=]\\s*)(?<num>-?\\d+)";
			}
		}

		internal static bool TryReadProfileStats(string path, out DiskStats stats, ManualLogSource log)
		{
			stats = default(DiskStats);
			if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
			{
				if (log != null)
				{
					log.LogWarning((object)("[ValhATLYSS] Parser: file not found: " + path));
				}
				return false;
			}
			string text;
			try
			{
				using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
				using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
				text = streamReader.ReadToEnd();
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogWarning((object)("[ValhATLYSS] Parser: read failed: " + ex.Message));
				}
				return false;
			}
			if (!TryParseStatsFromText(text, out stats, log))
			{
				return false;
			}
			if (stats.Level <= 0)
			{
				stats.Level = 0;
			}
			if (stats.Exp < 0)
			{
				stats.Exp = -1L;
			}
			if (!stats.HasLevel)
			{
				return stats.HasExp;
			}
			return true;
		}

		internal static bool TryWriteProfileStats(string path, DiskStats target, ManualLogSource log)
		{
			if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
			{
				return false;
			}
			string text;
			try
			{
				using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
				using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
				text = streamReader.ReadToEnd();
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogWarning((object)("[ValhATLYSS] Writer: read failed: " + ex.Message));
				}
				return false;
			}
			bool flag = false;
			if (target.HasLevel)
			{
				Regex regex = new Regex(target.LevelPatternUsed, RegexOptions.CultureInvariant);
				if (regex.Match(text).Success)
				{
					string text2 = regex.Replace(text, (Match match) => match.Value.Substring(0, match.Value.LastIndexOf(match.Groups["num"].Value, StringComparison.Ordinal)) + target.Level, 1);
					if ((object)text2 != text)
					{
						text = text2;
						flag = true;
					}
				}
				else if (log != null)
				{
					log.LogInfo((object)"[ValhATLYSS] Writer: Level pattern no longer matches; skip safe write.");
				}
			}
			if (target.HasExp)
			{
				Regex regex2 = new Regex(target.ExpPatternUsed, RegexOptions.CultureInvariant);
				if (regex2.Match(text).Success)
				{
					string text3 = regex2.Replace(text, (Match match) => match.Value.Substring(0, match.Value.LastIndexOf(match.Groups["num"].Value, StringComparison.Ordinal)) + target.Exp, 1);
					if ((object)text3 != text)
					{
						text = text3;
						flag = true;
					}
				}
				else if (log != null)
				{
					log.LogInfo((object)"[ValhATLYSS] Writer: Exp pattern no longer matches; skip safe write.");
				}
			}
			if (!flag)
			{
				return false;
			}
			try
			{
				string text4 = path + ".valh_tmp";
				File.WriteAllText(text4, text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				File.Copy(text4, path, overwrite: true);
				File.Delete(text4);
				return true;
			}
			catch (Exception ex2)
			{
				if (log != null)
				{
					log.LogWarning((object)("[ValhATLYSS] Writer: write failed: " + ex2.Message));
				}
				return false;
			}
		}
	}
	[BepInPlugin("cconx.ValhATLYSS", "ValhATLYSS", "0.4.19")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class Seen
		{
			public DateTime LastWriteUtc;

			public long LastSize;

			public int ContentHash;

			public int LastLevel;

			public long LastExp;

			public DateTime PendingUntilUtc;
		}

		[CompilerGenerated]
		private sealed class <Bootstrap>d__11 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Plugin <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <Bootstrap>d__11(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_002e: Expected O, but got Unknown
				int num = <>1__state;
				Plugin plugin = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(2f);
					<>1__state = 1;
					return true;
				case 1:
				{
					<>1__state = -1;
					plugin._bootstrapped = true;
					HoddKista.EnsureReady(Log);
					((MonoBehaviour)plugin).StartCoroutine(plugin.EnforceServerCapForever());
					((MonoBehaviour)plugin).StartCoroutine(plugin.ProfilePoller());
					if (TryGetMostRecentProfile(out var fullPath))
					{
						Log.LogInfo((object)("[ValhATLYSS] Initial reconcile of most recent profile: " + fullPath));
						plugin.TryReconcileProfile(fullPath);
					}
					else
					{
						Log.LogInfo((object)"[ValhATLYSS] No recent non-bak profile found to reconcile at bootstrap.");
					}
					return false;
				}
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <CoalesceAndProcess>d__18 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Plugin <>4__this;

			public string path;

			private int <i>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <CoalesceAndProcess>d__18(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_02a6: Unknown result type (might be due to invalid IL or missing references)
				//IL_02b0: Expected O, but got Unknown
				int num = <>1__state;
				Plugin plugin = <>4__this;
				Seen value;
				ManualLogSource log;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					goto IL_0029;
				case 1:
					<>1__state = -1;
					goto IL_0029;
				case 2:
					{
						<>1__state = -1;
						<i>5__2++;
						break;
					}
					IL_0029:
					if (plugin._seen.TryGetValue(path, out value) && DateTime.UtcNow < value.PendingUntilUtc)
					{
						<>2__current = null;
						<>1__state = 1;
						return true;
					}
					log = Log;
					if (log != null)
					{
						log.LogInfo((object)("[ValhATLYSS] Watcher: processing " + Path.GetFileName(path) + " after quiet window"));
					}
					<i>5__2 = 0;
					break;
				}
				if (<i>5__2 < 10)
				{
					if (TryReadAllText(path, out var text, out var info))
					{
						if (!plugin._seen.TryGetValue(path, out var value2))
						{
							value2 = (plugin._seen[path] = new Seen());
						}
						int num2 = StableHash(text);
						if (info.LastWriteTimeUtc == value2.LastWriteUtc && info.Length == value2.LastSize && num2 == value2.ContentHash)
						{
							ManualLogSource log2 = Log;
							if (log2 != null)
							{
								log2.LogInfo((object)("[ValhATLYSS] Watcher: no FS/hash change for " + Path.GetFileName(path)));
							}
							return false;
						}
						if (!KappaSlot.TryReadProfileStats(path, out var stats, Log))
						{
							ManualLogSource log3 = Log;
							if (log3 != null)
							{
								log3.LogInfo((object)("[ValhATLYSS] Watcher: could not read Level/Exp from " + Path.GetFileName(path)));
							}
							value2.LastWriteUtc = info.LastWriteTimeUtc;
							value2.LastSize = info.Length;
							value2.ContentHash = num2;
							return false;
						}
						ManualLogSource log4 = Log;
						if (log4 != null)
						{
							log4.LogInfo((object)$"[ValhATLYSS] Watcher: parsed Level={stats.Level} Exp={stats.Exp} (prev L={value2.LastLevel} E={value2.LastExp})");
						}
						switch (HoddKista.Reconcile(path, stats, Log))
						{
						case -1:
						{
							ManualLogSource log6 = Log;
							if (log6 != null)
							{
								log6.LogInfo((object)"[ValhATLYSS] Anti-regression: disk restored from vault.");
							}
							break;
						}
						case 1:
						{
							ManualLogSource log7 = Log;
							if (log7 != null)
							{
								log7.LogInfo((object)"[ValhATLYSS] Anti-regression: vault updated from disk.");
							}
							break;
						}
						default:
						{
							ManualLogSource log5 = Log;
							if (log5 != null)
							{
								log5.LogInfo((object)"[ValhATLYSS] Anti-regression: no change needed.");
							}
							break;
						}
						}
						value2.LastWriteUtc = info.LastWriteTimeUtc;
						value2.LastSize = info.Length;
						value2.ContentHash = num2;
						value2.LastLevel = stats.Level;
						value2.LastExp = stats.Exp;
						return false;
					}
					<>2__current = (object)new WaitForSeconds(0.1f);
					<>1__state = 2;
					return true;
				}
				ManualLogSource log8 = Log;
				if (log8 != null)
				{
					log8.LogDebug((object)("[ValhATLYSS] Skipped profile check (file busy): " + path));
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <EnforceServerCapForever>d__12 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Plugin <>4__this;

			private int <lastAnnounced>5__2;

			private int <desired>5__3;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <EnforceServerCapForever>d__12(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_003e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0048: Expected O, but got Unknown
				//IL_0184: Unknown result type (might be due to invalid IL or missing references)
				//IL_018e: Expected O, but got Unknown
				int num = <>1__state;
				Plugin plugin = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<lastAnnounced>5__2 = -1;
					<desired>5__3 = 64;
					<>2__current = (object)new WaitForSeconds(1f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					break;
				case 2:
					<>1__state = -1;
					break;
				}
				try
				{
					bool flag = false;
					try
					{
						flag = NetworkServer.active;
					}
					catch
					{
					}
					if (!flag && (Object)(object)Player._mainPlayer != (Object)null)
					{
						flag = ((NetworkBehaviour)Player._mainPlayer).isServer;
					}
					StatLogics val = (((Object)(object)GameManager._current != (Object)null) ? GameManager._current._statLogics : null);
					if (flag && (Object)(object)val != (Object)null)
					{
						if (val._maxMainLevel != <desired>5__3)
						{
							int maxMainLevel = val._maxMainLevel;
							val._maxMainLevel = <desired>5__3;
							ManualLogSource logger = ((BaseUnityPlugin)plugin).Logger;
							if (logger != null)
							{
								logger.LogInfo((object)$"[ValhATLYSS] Server level cap set: {maxMainLevel} → {val._maxMainLevel} (enforce)");
							}
							<lastAnnounced>5__2 = val._maxMainLevel;
						}
						else if (<lastAnnounced>5__2 != <desired>5__3)
						{
							ManualLogSource logger2 = ((BaseUnityPlugin)plugin).Logger;
							if (logger2 != null)
							{
								logger2.LogInfo((object)$"[ValhATLYSS] Server level cap OK: {val._maxMainLevel} (>= {<desired>5__3})");
							}
							<lastAnnounced>5__2 = <desired>5__3;
						}
					}
				}
				catch (Exception ex)
				{
					ManualLogSource logger3 = ((BaseUnityPlugin)plugin).Logger;
					if (logger3 != null)
					{
						logger3.LogDebug((object)("[ValhATLYSS] Cap enforcer tick failed: " + ex.Message));
					}
				}
				<>2__current = (object)new WaitForSeconds(1f);
				<>1__state = 2;
				return true;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <ProfilePoller>d__19 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Plugin <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <ProfilePoller>d__19(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0039: Expected O, but got Unknown
				//IL_0263: Unknown result type (might be due to invalid IL or missing references)
				//IL_026d: Expected O, but got Unknown
				int num = <>1__state;
				Plugin plugin = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(3f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					break;
				case 2:
					<>1__state = -1;
					break;
				}
				if (!string.IsNullOrEmpty(plugin._profilesRoot))
				{
					try
					{
						if (TryGetMostRecentProfile(out var fullPath) && File.Exists(fullPath) && TryReadAllText(fullPath, out var text, out var info))
						{
							if (!plugin._seen.TryGetValue(fullPath, out var value))
							{
								value = (plugin._seen[fullPath] = new Seen());
							}
							int num2 = StableHash(text);
							if (info.LastWriteTimeUtc != value.LastWriteUtc || info.Length != value.LastSize || num2 != value.ContentHash)
							{
								if (KappaSlot.TryReadProfileStats(fullPath, out var stats, Log))
								{
									ManualLogSource log = Log;
									if (log != null)
									{
										log.LogInfo((object)$"[ValhATLYSS] Poller: parsed Level={stats.Level} Exp={stats.Exp} (prev L={value.LastLevel} E={value.LastExp}) from {Path.GetFileName(fullPath)}");
									}
									switch (HoddKista.Reconcile(fullPath, stats, Log))
									{
									case -1:
									{
										ManualLogSource log3 = Log;
										if (log3 != null)
										{
											log3.LogInfo((object)"[ValhATLYSS] Anti-regression: disk restored from vault. (poller)");
										}
										break;
									}
									case 1:
									{
										ManualLogSource log4 = Log;
										if (log4 != null)
										{
											log4.LogInfo((object)"[ValhATLYSS] Anti-regression: vault updated from disk. (poller)");
										}
										break;
									}
									default:
									{
										ManualLogSource log2 = Log;
										if (log2 != null)
										{
											log2.LogInfo((object)"[ValhATLYSS] Anti-regression: no change needed. (poller)");
										}
										break;
									}
									}
									value.LastLevel = stats.Level;
									value.LastExp = stats.Exp;
								}
								else
								{
									ManualLogSource log5 = Log;
									if (log5 != null)
									{
										log5.LogInfo((object)("[ValhATLYSS] Poller: could not read Level/Exp in " + Path.GetFileName(fullPath)));
									}
								}
								value.LastWriteUtc = info.LastWriteTimeUtc;
								value.LastSize = info.Length;
								value.ContentHash = num2;
							}
							else
							{
								ManualLogSource log6 = Log;
								if (log6 != null)
								{
									log6.LogDebug((object)("[ValhATLYSS] Poller: no FS/hash change for " + Path.GetFileName(fullPath)));
								}
							}
						}
					}
					catch (Exception ex)
					{
						ManualLogSource log7 = Log;
						if (log7 != null)
						{
							log7.LogDebug((object)("[ValhATLYSS] Poller error: " + ex.Message));
						}
					}
				}
				<>2__current = (object)new WaitForSeconds(5f);
				<>1__state = 2;
				return true;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private const int DesiredCap = 64;

		private const int ReconcileDebounceMs = 2000;

		private const float PollIntervalSeconds = 5f;

		private const bool VerboseWatcherLogs = true;

		internal static ManualLogSource Log;

		private FileSystemWatcher _watcher;

		private string _profilesRoot;

		private bool _bootstrapped;

		private readonly Dictionary<string, Seen> _seen = new Dictionary<string, Seen>(StringComparer.OrdinalIgnoreCase);

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"[ValhATLYSS] Awake");
			_profilesRoot = GetProfilesRoot();
			if (string.IsNullOrEmpty(_profilesRoot))
			{
				Log.LogWarning((object)"[ValhATLYSS] Could not locate profileCollections path. Mod will idle.");
			}
			else
			{
				Log.LogInfo((object)("[ValhATLYSS] Profiles root: " + _profilesRoot));
				TryEnableWatcher(_profilesRoot);
				try
				{
					string[] files = Directory.GetFiles(_profilesRoot, "atl_characterProfile_*", SearchOption.TopDirectoryOnly);
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogInfo((object)$"[ValhATLYSS] Profiles discovered: {files.Length}");
					}
					string[] array = files;
					foreach (string text in array)
					{
						ManualLogSource log2 = Log;
						if (log2 != null)
						{
							log2.LogInfo((object)("[ValhATLYSS] Profile: " + text));
						}
					}
				}
				catch (Exception ex)
				{
					ManualLogSource log3 = Log;
					if (log3 != null)
					{
						log3.LogDebug((object)("[ValhATLYSS] Profile discovery failed: " + ex.Message));
					}
				}
			}
			((MonoBehaviour)this).StartCoroutine(Bootstrap());
		}

		[IteratorStateMachine(typeof(<Bootstrap>d__11))]
		private IEnumerator Bootstrap()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <Bootstrap>d__11(0)
			{
				<>4__this = this
			};
		}

		[IteratorStateMachine(typeof(<EnforceServerCapForever>d__12))]
		private IEnumerator EnforceServerCapForever()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <EnforceServerCapForever>d__12(0)
			{
				<>4__this = this
			};
		}

		private void TryEnableWatcher(string profilesRoot)
		{
			try
			{
				_watcher = new FileSystemWatcher(profilesRoot)
				{
					NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite),
					IncludeSubdirectories = false,
					Filter = "atl_characterProfile_*",
					EnableRaisingEvents = true
				};
				_watcher.Changed += OnProfileChanged;
				_watcher.Created += OnProfileChanged;
				_watcher.Renamed += OnProfileRenamed;
				Log.LogInfo((object)"[ValhATLYSS] File watcher enabled.");
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)("[ValhATLYSS] Failed to enable watcher: " + ex.Message));
				}
			}
		}

		private void OnProfileChanged(object sender, FileSystemEventArgs e)
		{
			QueueProfileCheck(e.FullPath);
		}

		private void OnProfileRenamed(object sender, RenamedEventArgs e)
		{
			QueueProfileCheck(e.FullPath);
		}

		private static bool IsBackupPath(string path)
		{
			if (!path.EndsWith("_bak", StringComparison.OrdinalIgnoreCase))
			{
				return Path.GetFileName(path).EndsWith("_bak", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private void QueueProfileCheck(string path)
		{
			if (!_bootstrapped || string.IsNullOrEmpty(path))
			{
				return;
			}
			if (IsBackupPath(path))
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)("[ValhATLYSS] Watcher: ignoring backup file " + Path.GetFileName(path)));
				}
				return;
			}
			if (!_seen.TryGetValue(path, out var value))
			{
				value = (_seen[path] = new Seen());
			}
			value.PendingUntilUtc = DateTime.UtcNow.AddMilliseconds(2000.0);
			ManualLogSource log2 = Log;
			if (log2 != null)
			{
				log2.LogInfo((object)$"[ValhATLYSS] Watcher: queued {Path.GetFileName(path)} until {value.PendingUntilUtc:HH:mm:ss.fff}Z");
			}
			((MonoBehaviour)this).StartCoroutine(CoalesceAndProcess(path));
		}

		[IteratorStateMachine(typeof(<CoalesceAndProcess>d__18))]
		private IEnumerator CoalesceAndProcess(string path)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <CoalesceAndProcess>d__18(0)
			{
				<>4__this = this,
				path = path
			};
		}

		[IteratorStateMachine(typeof(<ProfilePoller>d__19))]
		private IEnumerator ProfilePoller()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ProfilePoller>d__19(0)
			{
				<>4__this = this
			};
		}

		private static bool TryReadAllText(string path, out string text, out FileInfo info)
		{
			text = null;
			info = null;
			try
			{
				info = new FileInfo(path);
				using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
				using StreamReader streamReader = new StreamReader(stream);
				text = streamReader.ReadToEnd();
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static int StableHash(string s)
		{
			int num = 23;
			if (!string.IsNullOrEmpty(s))
			{
				for (int i = 0; i < s.Length; i++)
				{
					num = num * 31 + s[i];
				}
			}
			return num;
		}

		private void TryReconcileProfile(string profilePath)
		{
			try
			{
				if (string.IsNullOrEmpty(profilePath) || IsBackupPath(profilePath))
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogInfo((object)"[ValhATLYSS] Reconcile skipped: path null or backup file.");
					}
					return;
				}
				if (!File.Exists(profilePath))
				{
					ManualLogSource log2 = Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[ValhATLYSS] Reconcile skipped; path missing: " + profilePath));
					}
					return;
				}
				if (!KappaSlot.TryReadProfileStats(profilePath, out var stats, Log))
				{
					ManualLogSource log3 = Log;
					if (log3 != null)
					{
						log3.LogWarning((object)("[ValhATLYSS] Could not read disk stats for: " + profilePath));
					}
					return;
				}
				switch (HoddKista.Reconcile(profilePath, stats, Log))
				{
				case -1:
				{
					ManualLogSource log5 = Log;
					if (log5 != null)
					{
						log5.LogInfo((object)"[ValhATLYSS] Anti-regression: disk restored from vault.");
					}
					break;
				}
				case 1:
				{
					ManualLogSource log6 = Log;
					if (log6 != null)
					{
						log6.LogInfo((object)"[ValhATLYSS] Anti-regression: vault updated from disk.");
					}
					break;
				}
				default:
				{
					ManualLogSource log4 = Log;
					if (log4 != null)
					{
						log4.LogInfo((object)"[ValhATLYSS] No reconcile change necessary.");
					}
					break;
				}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log7 = Log;
				if (log7 != null)
				{
					log7.LogWarning((object)("[ValhATLYSS] Reconcile failed: " + ex.Message));
				}
			}
		}

		internal static string GetProfilesRoot()
		{
			try
			{
				string text = Path.Combine(Paths.GameRootPath, "ATLYSS_Data", "profileCollections");
				return Directory.Exists(text) ? text : null;
			}
			catch
			{
				return null;
			}
		}

		internal static bool TryGetMostRecentProfile(out string fullPath)
		{
			fullPath = null;
			try
			{
				string profilesRoot = GetProfilesRoot();
				if (profilesRoot == null)
				{
					return false;
				}
				string[] files = Directory.GetFiles(profilesRoot, "atl_characterProfile_*", SearchOption.TopDirectoryOnly);
				DateTime dateTime = DateTime.MinValue;
				string[] array = files;
				foreach (string text in array)
				{
					if (!Path.GetFileName(text).EndsWith("_bak", StringComparison.OrdinalIgnoreCase))
					{
						DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(text);
						if (lastWriteTimeUtc > dateTime)
						{
							dateTime = lastWriteTimeUtc;
							fullPath = text;
						}
					}
				}
				return fullPath != null;
			}
			catch
			{
				return false;
			}
		}
	}
	internal static class Ristir
	{
		internal static void Print(ManualLogSource log, string message)
		{
			try
			{
				if (log != null)
				{
					log.LogInfo((object)("[ValhATLYSS] " + message));
				}
			}
			catch
			{
			}
		}

		internal static int ClampLevel(int level, int min = 1, int max = 64)
		{
			if (level < min)
			{
				return min;
			}
			if (level > max)
			{
				return max;
			}
			return level;
		}

		internal static int CompareProgress(int level0, int exp0, int level1, int exp1)
		{
			if (level1 > level0)
			{
				return 1;
			}
			if (level1 < level0)
			{
				return -1;
			}
			if (exp1 > exp0)
			{
				return 1;
			}
			if (exp1 < exp0)
			{
				return -1;
			}
			return 0;
		}
	}
	internal static class SeidrVegr
	{
		internal static bool Handshake()
		{
			return false;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}