Decompiled source of IShowSeed v1.8.0

IShowSeed.dll

Decompiled 4 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using IShowSeed;
using IShowSeed.Prediction;
using IShowSeed.Random;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("IShowSeed")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Seeded runs! Now with leaderboards!")]
[assembly: AssemblyFileVersion("1.8.0.0")]
[assembly: AssemblyInformationalVersion("1.8.0+73043635dd784c9ffc6d3ed804654270a4cbce63")]
[assembly: AssemblyProduct("IShowSeed")]
[assembly: AssemblyTitle("IShowSeed")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.8.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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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;
		}
	}
}
internal static class MasterServer
{
	public record LeaderboardEntry(string steamid, int rank, int score, int time, int seed, bool hardmode, bool iron, bool finished);

	internal static HttpClient HttpClient;

	internal static bool Initialized;

	public static async Task InitializeHttpClient()
	{
		HttpClient = new HttpClient();
		HttpClient.Timeout = TimeSpan.FromSeconds(Plugin.TimeoutSeconds.Value);
		string[] uris = new string[3]
		{
			Plugin.LeaderboardUri.Value,
			"http://128.199.54.23:80",
			"http://158.160.65.211:5252"
		};
		string[] array = uris;
		foreach (string uri in array)
		{
			try
			{
				Plugin.Beep.LogInfo((object)("Checking seeded runs leaderboard uri: " + uri));
				HttpClient.BaseAddress = new Uri(uri);
				(await HttpClient.GetAsync("/health")).EnsureSuccessStatusCode();
				Initialized = true;
			}
			catch (Exception)
			{
				Plugin.Beep.LogInfo((object)("Uri `" + uri + "` failed"));
				continue;
			}
			break;
		}
		if (Initialized)
		{
			Plugin.Beep.LogInfo((object)$"Connected to {HttpClient.BaseAddress}");
			Plugin.LeaderboardUri.Value = HttpClient.BaseAddress.ToString();
		}
	}

	public static async Task<List<LeaderboardEntry>> FetchLeaderboards(string gamemode, ScoreType scoreType, int limit = 10, string steamid = "")
	{
		//IL_0019: 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)
		if (!Initialized)
		{
			return new List<LeaderboardEntry>();
		}
		string endpoint = string.Format($"getstats?gamemode={gamemode}&limit={limit}&sortby={scoreType}&steamid={steamid}");
		Plugin.Beep.LogWarning((object)("Fetching Leaderboards: " + endpoint));
		using HttpResponseMessage response = await HttpClient.GetAsync(endpoint);
		if (!response.IsSuccessStatusCode)
		{
			Plugin.Beep.LogError((object)response);
			response.EnsureSuccessStatusCode();
		}
		return JsonConvert.DeserializeObject<List<LeaderboardEntry>>(await response.Content.ReadAsStringAsync());
	}

	public static async void UploadScore(string gamemode, int seed, float score, float time, bool iron, bool hardmode, bool finished)
	{
		if (!Initialized)
		{
			return;
		}
		Plugin.Beep.LogWarning((object)$"Uploading run info: gamemode={gamemode}, seed={seed}, score={score}, time={time}, iron={iron}, hardmode={hardmode}, finished={finished}");
		using StringContent jsonContent = new StringContent(JsonConvert.SerializeObject((object)new Dictionary<string, object>
		{
			{ "gamemode", gamemode },
			{
				"steamid",
				SteamClient.SteamId.Value.ToString()
			},
			{ "seed", seed },
			{
				"time",
				Mathf.FloorToInt(Mathf.Clamp(time, 0f, 2.1474836E+09f))
			},
			{
				"score",
				Mathf.FloorToInt(Mathf.Clamp(score, 0f, 2.1474836E+09f))
			},
			{ "iron", iron },
			{ "hardmode", hardmode },
			{ "finished", finished }
		}, (Formatting)0), Encoding.UTF8, "application/json");
		using HttpResponseMessage response = await HttpClient.PostAsync("uploadscore", jsonContent);
		if (!response.IsSuccessStatusCode)
		{
			Plugin.Beep.LogError((object)response);
			response.EnsureSuccessStatusCode();
		}
	}
}
namespace System.Runtime.CompilerServices
{
	internal static class IsExternalInit
	{
	}
}
namespace IShowSeed
{
	public static class Helpers
	{
		public static readonly FieldRef<CL_AssetManager, List<WKDatabaseHolder>> databasesRef = (FieldRef<CL_AssetManager, List<WKDatabaseHolder>>)(object)AccessTools.FieldRefAccess<List<WKDatabaseHolder>>(typeof(CL_AssetManager), "databases");

		internal static List<string> BaseGamemodes = new List<string>(8) { "Campaign", "Endless Superstructure", "Endless Substructure", "Endless Underworks", "Endless Silos", "Endless Pipeworks", "Endless Habitation", "Endless Abyss" };

		public static string LevelOf(Transform tr)
		{
			if ((Object)(object)tr == (Object)null)
			{
				return "could_not_get_level_of_" + ((Object)((Component)tr).gameObject).name;
			}
			return ((Component)tr).GetComponentInParent<M_Level>(true).levelName;
		}

		public static List<Perk> GetAllPerks()
		{
			List<Perk> list = new List<Perk>();
			foreach (WKDatabaseHolder item in databasesRef.Invoke(CL_AssetManager.instance))
			{
				list.AddRange(item.database.perkAssets);
			}
			foreach (Perk item2 in list)
			{
				item2.id = item2.id.ToLower();
			}
			return list;
		}

		internal static void PatchAllWithAttribute<T>(Harmony harmony) where T : Attribute
		{
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			IEnumerable<Type> enumerable = from t in executingAssembly.GetTypes()
				where t.GetCustomAttribute<T>() != null
				select t;
			foreach (Type item in enumerable)
			{
				harmony.PatchAll(item);
			}
		}

		internal static List<string> GetAllGamemodes(bool withOptions)
		{
			List<string> list = new List<string>();
			foreach (string baseGamemode in BaseGamemodes)
			{
				list.Add(baseGamemode);
				if (withOptions)
				{
					list.Add(baseGamemode + "-Hardmode");
					list.Add(baseGamemode + "-Iron");
					list.Add(baseGamemode + "-Hardmode-Iron");
				}
			}
			return list;
		}

		internal static string GetAllGamemodesStr(bool withOptions, string delimiter)
		{
			return string.Join(delimiter, GetAllGamemodes(withOptions));
		}
	}
	[AttributeUsage(AttributeTargets.Class)]
	public class OnlyForSeededRunsPatchAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Class)]
	public class PermanentPatchAttribute : Attribute
	{
	}
	[PermanentPatch]
	[HarmonyPatch(typeof(Leaderboard_Panel), "UpdateScore")]
	public static class SpawnSettings_UpdateScore_Patcher
	{
		public static bool FilterBySeed = false;

		private static readonly FieldRef<Leaderboard_Panel, List<Leaderboard_Score>> scoreObjectsRef = (FieldRef<Leaderboard_Panel, List<Leaderboard_Score>>)(object)AccessTools.FieldRefAccess<List<Leaderboard_Score>>(typeof(Leaderboard_Panel), "scoreObjects");

		public static bool Prefix(Leaderboard_Panel __instance)
		{
			if (Plugin.IsRandomRun() || !MasterServer.Initialized)
			{
				return true;
			}
			try
			{
				OverrideLeaderboardsAsync(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Beep.LogError((object)"An error occurred when trying to connect to leaderboards server! Make sure you have the latest version of the mod and default URI setting in mod's config");
				Plugin.Beep.LogError((object)ex);
			}
			return false;
		}

		public static async Task OverrideLeaderboardsAsync(Leaderboard_Panel __instance)
		{
			List<Leaderboard_Score> scoreObjects = scoreObjectsRef.Invoke(__instance);
			string leaderboardsName = CL_GameManager.GetGamemodeName(true, false);
			for (int j = 0; j < scoreObjects.Count; j++)
			{
				((Component)scoreObjects[j]).gameObject.SetActive(false);
			}
			List<MasterServer.LeaderboardEntry> lbScores = null;
			if ((int)__instance.defaultType == 0)
			{
				lbScores = await MasterServer.FetchLeaderboards(leaderboardsName, __instance.scoreType, __instance.scoresToPull);
			}
			else if ((int)__instance.defaultType == 1 || (int)__instance.defaultType == 2)
			{
				lbScores = await MasterServer.FetchLeaderboards(leaderboardsName, __instance.scoreType, __instance.scoresToPull, SteamClient.SteamId.Value.ToString());
				List<MasterServer.LeaderboardEntry> list = lbScores;
				List<MasterServer.LeaderboardEntry> list2 = list;
				if (list2 == null)
				{
					List<MasterServer.LeaderboardEntry> list3;
					lbScores = (list3 = await MasterServer.FetchLeaderboards(leaderboardsName, __instance.scoreType, __instance.scoresToPull));
					list2 = list3;
				}
				_ = list2;
			}
			for (int i = 0; i < scoreObjects.Count; i++)
			{
				if (i >= lbScores.Count)
				{
					((Component)scoreObjects[i]).gameObject.SetActive(false);
					continue;
				}
				SteamId steamId = new SteamId
				{
					Value = Convert.ToUInt64(lbScores[i].steamid)
				};
				Friend steamUser = new Friend(steamId);
				((Behaviour)scoreObjects[i].hmIcon).enabled = lbScores[i].hardmode;
				((Behaviour)scoreObjects[i].ikIcon).enabled = lbScores[i].iron;
				((Component)scoreObjects[i]).gameObject.SetActive(true);
				scoreObjects[i].positionText.text = $"<mspace=''>{lbScores[i].rank}</mspace>";
				scoreObjects[i].nameText.text = ((Friend)(ref steamUser)).Name ?? "";
				if ((int)__instance.scoreType == 0)
				{
					scoreObjects[i].scoreText.text = $"{lbScores[i].score}\n<color=grey>seed: {lbScores[i].seed}</color>";
				}
				else
				{
					if ((int)__instance.scoreType != 1)
					{
						Plugin.Beep.LogWarning((object)$"Unsupported scoreType {__instance.scoreType}");
						break;
					}
					TimeSpan timeSpan = TimeSpan.FromSeconds(lbScores[i].time);
					scoreObjects[i].scoreText.text = $"{timeSpan:hh\\:mm\\:ss} <color=grey>seed: {lbScores[i].seed}</color>";
				}
				Texture2D texture = SteamManager.ConvertSteamIcon((await ((Friend)(ref steamUser)).GetMediumAvatarAsync()).Value);
				scoreObjects[i].profile.texture = (Texture)(object)texture;
				if (SteamClient.SteamId.Value == steamId.Value)
				{
					((Graphic)scoreObjects[i].positionText).color = __instance.isMeColor;
					((Graphic)scoreObjects[i].nameText).color = __instance.isMeColor;
					((Graphic)scoreObjects[i].scoreText).color = __instance.isMeColor;
				}
				else
				{
					((Graphic)scoreObjects[i].positionText).color = Color.white;
					((Graphic)scoreObjects[i].nameText).color = Color.white;
					((Graphic)scoreObjects[i].scoreText).color = Color.white;
				}
			}
		}
	}
	[PermanentPatch]
	[HarmonyPatch(typeof(M_Gamemode), "Finish")]
	public static class M_Gamemode_Finish_Patcher
	{
		public static bool Prefix(M_Gamemode __instance, float time, bool hasFinished)
		{
			if (Plugin.IsRandomRun())
			{
				return true;
			}
			__instance.gamemodeModule.OnFinish(hasFinished);
			bool flag = (!__instance.allowCheatedScores && CommandConsole.hasCheated) || (!__instance.allowCheatedScores && !CL_GameManager.AreAchievementsAllowed());
			try
			{
				MasterServer.UploadScore(__instance.GetGamemodeName(true), Plugin.SeedForRandom, flag ? 0f : __instance.GetPlayerScore(hasFinished), time, __instance.IsIronKnuckle(), __instance.allowHardmode && CL_GameManager.IsHardmode(), hasFinished);
			}
			catch (Exception ex)
			{
				Plugin.Beep.LogError((object)"An error occurred when trying to connect to leaderboards server! Make sure you have the latest version of the mod and default URI setting in mod's config");
				Plugin.Beep.LogError((object)ex);
			}
			return false;
		}
	}
	[BepInPlugin("shishyando.WK.IShowSeed", "IShowSeed", "1.8.0")]
	public class Plugin : BaseUnityPlugin
	{
		internal static Plugin Instance;

		internal static ManualLogSource Beep;

		internal static Harmony TogglableHarmony = new Harmony("shishyando.WK.IShowSeed.togglable");

		internal static Harmony PermanentHarmony = new Harmony("shishyando.WK.IShowSeed.permanent");

		internal static int SeedForRandom = 0;

		private static bool OnStartupDone = false;

		internal static ConfigEntry<int> ConfigPresetSeed;

		internal static ConfigEntry<bool> PersistBetweenGameRestarts;

		internal static ConfigEntry<string> LeaderboardUri;

		internal static ConfigEntry<int> TimeoutSeconds;

		internal static ConfigEntry<string> EnabledGamemodesStr;

		internal static List<string> EnabledGamemodes;

		internal static ConfigEntry<string> DesiredRouteDescription;

		internal static ConfigEntry<int> SeedSearchMin;

		internal static ConfigEntry<int> SeedSearchMax;

		internal static ConfigEntry<bool> EnableRandomSeedReplayability;

		private void Awake()
		{
			Instance = this;
			Beep = ((BaseUnityPlugin)this).Logger;
			ConfigPresetSeed = ((BaseUnityPlugin)this).Config.Bind<int>("General", "PresetSeed", 0, "Preset seed, `0` to keep the default behaviour");
			PersistBetweenGameRestarts = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "PersistBetweenGameRestarts", true, "If true, the seed will stay the same even after game restart");
			EnableRandomSeedReplayability = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableRandomSeedReplayability", false, "If true, random-seeded runs can be replayed in a seeded run by entering the seed you had");
			EnabledGamemodesStr = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Gamemodes", Helpers.GetAllGamemodesStr(withOptions: true, "|") ?? "", "Gamemodes which should be affected by IShowSeed, separated by `|`. Available values (can append \"-Hardmode\", \"-Iron\" or \"-Hardmode-Iron\"):\n" + Helpers.GetAllGamemodesStr(withOptions: false, "\n"));
			EnabledGamemodes = EnabledGamemodesStr.Value.Split('|').ToList();
			LeaderboardUri = ((BaseUnityPlugin)this).Config.Bind<string>("Leaderboards", "Uri", "http://128.199.54.23:80", "If you encounter any network problems, make sure you have the latest version of the mod and reset this to default");
			TimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Leaderboards", "TimeoutSeconds", 10, "If this is not enough, either the server is down or you have network issues");
			DesiredRouteDescription = ((BaseUnityPlugin)this).Config.Bind<string>("SeedSearch", "DesiredRouteDescription", "shortcut_burner: perk_u_t2_adoptionday, perk_metabolicstasis, perk_rabbitdna", "Format: `{route name}: {unstable_perk1}, {perk2}, {perk3}`, available route names: `default`, `shortcut_sink`, `shortcut_burner`");
			SeedSearchMin = ((BaseUnityPlugin)this).Config.Bind<int>("SeedSearch", "MinSeed", 3665, "Min seed for desired route search");
			SeedSearchMax = ((BaseUnityPlugin)this).Config.Bind<int>("SeedSearch", "MaxSeed", 3665, "Max seed for desired route search, do not make this range too big or your game will load forever");
			SceneManager.sceneLoaded += OnGameStartup;
			Beep.LogInfo((object)"shishyando.WK.IShowSeed is loaded");
		}

		public void OnGameStartup(Scene scene, LoadSceneMode loadSceneMode)
		{
			if (!(((Scene)(ref scene)).name != "Main-Menu") && !OnStartupDone)
			{
				OnStartupDone = true;
				MasterServer.InitializeHttpClient();
				ClearSeedOnRestartIfNeeded();
				Vanga.DoSeedSearch();
				Helpers.PatchAllWithAttribute<PermanentPatchAttribute>(PermanentHarmony);
			}
		}

		public void ClearSeedOnRestartIfNeeded()
		{
			if (!PersistBetweenGameRestarts.Value)
			{
				ConfigPresetSeed.Value = 0;
				Beep.LogInfo((object)"PersistBetweenGameRestarts is false, clearing the preset seed");
			}
		}

		public static bool IsSeededRun()
		{
			return ConfigPresetSeed.Value != 0;
		}

		public static bool IsRandomRun()
		{
			return ConfigPresetSeed.Value == 0;
		}

		public static bool ShouldEnableRod()
		{
			return (IsSeededRun() || EnableRandomSeedReplayability.Value) && EnabledGamemodes.Contains(CL_GameManager.GetGamemodeName(true, false));
		}
	}
	[PermanentPatch]
	[HarmonyPatch(typeof(SceneManager), "LoadScene", new Type[] { typeof(string) })]
	public static class SceneManager_LoadScene_Patcher
	{
		public static void Prefix(string sceneName)
		{
			if (sceneName == "Main-Menu")
			{
				Plugin.TogglableHarmony.UnpatchSelf();
				Rod.SwitchToMode(Rod.ERandomMode.Disabled);
			}
			if (!(sceneName == "Game-Main"))
			{
				return;
			}
			if (Plugin.ShouldEnableRod())
			{
				if (Rod.GetMode() == Rod.ERandomMode.Enabled)
				{
					Plugin.Beep.LogInfo((object)"Only resetting random on restart, no double patching");
					Rod.Reset();
				}
				else
				{
					Plugin.Beep.LogInfo((object)"Applying patches for random for the first time");
					Rod.SwitchToMode(Rod.ERandomMode.Enabled);
					Helpers.PatchAllWithAttribute<OnlyForSeededRunsPatchAttribute>(Plugin.TogglableHarmony);
				}
			}
			else
			{
				Plugin.Beep.LogInfo((object)("Gamemode `" + CL_GameManager.GetGamemodeName(true, false) + "` is not enabled in IShowSeed's configs, not doing anything"));
			}
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "shishyando.WK.IShowSeed";

		public const string PLUGIN_NAME = "IShowSeed";

		public const string PLUGIN_VERSION = "1.8.0";
	}
}
namespace IShowSeed.UI
{
	[PermanentPatch]
	[HarmonyPatch(typeof(UT_SeededEnable), "OnEnable")]
	public static class UT_SeededEnable_OnEnable_Patcher
	{
		public static bool Prefix(UT_SeededEnable __instance)
		{
			TextMeshProUGUI component = ((Component)__instance).gameObject.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).text = $"SEED: {Plugin.SeedForRandom}";
			if (Plugin.IsSeededRun())
			{
				((TMP_Text)component).text = "PRESET " + ((TMP_Text)component).text;
			}
			return false;
		}
	}
}
namespace IShowSeed.Random
{
	public static class Rod
	{
		public enum ERandomMode
		{
			Disabled,
			Enabled,
			Prediction
		}

		public struct Context
		{
			public State PrevRandomState;

			public int BaseSeed;

			public bool Valid;

			public uint CallNumber;
		}

		public static ERandomMode Mode = ERandomMode.Disabled;

		private static readonly object _lock = new object();

		private static readonly Dictionary<int, State> _stateBySiteSeed = new Dictionary<int, State>();

		private static readonly Dictionary<int, uint> _cntBySiteSeed = new Dictionary<int, uint>();

		internal static void Enter(ref Context ctx, string customCallSite)
		{
			//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_00a6: 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)
			if (Mode != 0)
			{
				ctx = default(Context);
				ctx.BaseSeed = DeriveSeed(customCallSite ?? ComputeSiteKey());
				Monitor.Enter(_lock);
				ctx.PrevRandomState = Random.state;
				if (!_stateBySiteSeed.ContainsKey(ctx.BaseSeed))
				{
					Random.InitState(ctx.BaseSeed);
					_stateBySiteSeed[ctx.BaseSeed] = Random.state;
					_cntBySiteSeed[ctx.BaseSeed] = 1u;
					ctx.CallNumber = 1u;
				}
				else
				{
					Random.state = _stateBySiteSeed[ctx.BaseSeed];
					ctx.CallNumber = ++_cntBySiteSeed[ctx.BaseSeed];
				}
				ctx.Valid = true;
			}
		}

		internal static void Exit(in Context ctx)
		{
			//IL_0010: 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)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			if (ctx.Valid)
			{
				State state = Random.state;
				_stateBySiteSeed[ctx.BaseSeed] = state;
				Random.state = ctx.PrevRandomState;
				Monitor.Exit(_lock);
			}
		}

		private static string ComputeSiteKey()
		{
			return GetStackTraceStr(2);
		}

		private static int DeriveSeed(string siteKey)
		{
			return Animator.StringToHash(siteKey) ^ Plugin.SeedForRandom;
		}

		internal static ERandomMode GetMode()
		{
			return Mode;
		}

		internal static void SwitchToMode(ERandomMode mode)
		{
			if (Mode != ERandomMode.Prediction && mode != ERandomMode.Prediction)
			{
				Plugin.Beep.LogInfo((object)$"RandomGod switching to {mode}");
			}
			Monitor.Enter(_lock);
			Mode = mode;
			_stateBySiteSeed.Clear();
			Monitor.Exit(_lock);
		}

		internal static void Reset()
		{
			Plugin.Beep.LogWarning((object)"RandomGod reset");
			Monitor.Enter(_lock);
			_stateBySiteSeed.Clear();
			Monitor.Exit(_lock);
		}

		private static string GetStackTraceStr(int frames)
		{
			StackTrace stackTrace = new StackTrace(frames);
			return string.Join(" ==> ", stackTrace.GetFrames().Select(delegate(StackFrame f)
			{
				MethodBase method = f.GetMethod();
				Type declaringType = method.DeclaringType;
				return (declaringType == null) ? "" : (declaringType.FullName + "." + method.Name);
			}));
		}
	}
	[OnlyForSeededRunsPatch]
	[HarmonyPatch(typeof(App_PerkPage), "GenerateCards", new Type[] { typeof(bool) })]
	public static class App_PerkPage_GenerateCards_Patcher
	{
		private static readonly FieldRef<App_PerkPage, OS_Manager> osRef = (FieldRef<App_PerkPage, OS_Manager>)(object)AccessTools.FieldRefAccess<OS_Manager>(typeof(App_PerkPage), "os");

		private static readonly FieldRef<OS_Computer_Interface, int> seedRef = (FieldRef<OS_Computer_Interface, int>)(object)AccessTools.FieldRefAccess<int>(typeof(OS_Computer_Interface), "seed");

		public static void Prefix(ref Rod.Context __state, App_PerkPage __instance, bool refresh)
		{
			Rod.Enter(ref __state, GenerateCustomCallSite(__instance, refresh));
			seedRef.Invoke(osRef.Invoke(__instance).worldInterface) = -1;
		}

		public static void Postfix(ref Rod.Context __state, App_PerkPage __instance)
		{
			seedRef.Invoke(osRef.Invoke(__instance).worldInterface) = (int)(__state.BaseSeed + __state.CallNumber);
			Rod.Exit(in __state);
		}

		private static string GenerateCustomCallSite(App_PerkPage i, bool refresh)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			Transform transform = ((Component)osRef.Invoke(i).worldInterface).transform;
			return $"perkpage_{i.perkPageType}_{Helpers.LevelOf(transform)}_{i.minCards}_{i.maxCards}_{refresh}";
		}
	}
	[PermanentPatch]
	[HarmonyPatch(typeof(CL_EventManager), "GetPossibleEvents")]
	public static class CL_EventManager_GetPossibleEvents_Patcher
	{
		public static void Postfix(ref List<SessionEvent> __result)
		{
			if (Plugin.IsSeededRun())
			{
				__result?.RemoveAll((SessionEvent x) => (int)x.startCheck == 3);
			}
		}
	}
	[OnlyForSeededRunsPatch]
	[HarmonyPatch(typeof(ENV_ArtifactDevice), "Start")]
	public static class ENV_ArtifactDevice_Start_Patcher
	{
		public static void Prefix(ref Rod.Context __state)
		{
			Rod.Enter(ref __state, "artifact_device");
		}

		public static void Finalizer(ref Rod.Context __state)
		{
			Rod.Exit(in __state);
		}
	}
	[OnlyForSeededRunsPatch]
	[HarmonyPatch(typeof(ENV_VendingMachine), "GenerateOptions")]
	public static class ENV_VendingMachine_GenerateOptions_Patcher
	{
		private static readonly FieldRef<ENV_VendingMachine, int> localSeedRef = AccessTools.FieldRefAccess<ENV_VendingMachine, int>("localSeed");

		public static void Prefix(ref Rod.Context __state, ENV_VendingMachine __instance)
		{
			Rod.Enter(ref __state, GenerateCustomCallSite(__instance));
			localSeedRef.Invoke(__instance) = (int)(__state.BaseSeed + __state.CallNumber);
		}

		public static void Finalizer(ref Rod.Context __state)
		{
			Rod.Exit(in __state);
		}

		private static string GenerateCustomCallSite(ENV_VendingMachine i)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			Transform transform = ((Component)i).transform;
			return $"vendo_{Helpers.LevelOf(transform)}_{transform.position.x:F2}_{transform.position.y:F2}_{transform.position.z:F2}";
		}
	}
	[OnlyForSeededRunsPatch]
	[HarmonyPatch(typeof(ENV_VendingMachine), "SetSeed")]
	public static class ENV_VendingMachine_SetSeed_Patcher
	{
		public static bool Prefix()
		{
			return false;
		}
	}
	[PermanentPatch]
	[HarmonyPatch(typeof(M_Level), "Awake")]
	public static class M_Level_Awake_Patcher
	{
		public static void Prefix(M_Level __instance)
		{
			if (Plugin.IsSeededRun())
			{
				__instance.canFlip = false;
			}
		}
	}
	[OnlyForSeededRunsPatch]
	[HarmonyPatch(typeof(OS_Computer_Interface), "SetSeed")]
	public static class OS_Computer_Interface_SetSeed_Patcher
	{
		public static bool Prefix()
		{
			return false;
		}
	}
	[OnlyForSeededRunsPatch]
	[HarmonyPatch(typeof(SpawnSettings), "RandomCheck")]
	public static class SpawnSettings_RandomCheck_Patcher
	{
		public static bool Prefix(ref Rod.Context __state, ref bool __result, SpawnSettings __instance)
		{
			float effectiveSpawnChance = __instance.GetEffectiveSpawnChance();
			if (effectiveSpawnChance == 0f)
			{
				__result = false;
				return false;
			}
			if (effectiveSpawnChance == 1f)
			{
				__result = true;
				return false;
			}
			Rod.Enter(ref __state, null);
			return true;
		}

		public static void Finalizer(ref Rod.Context __state)
		{
			Rod.Exit(in __state);
		}
	}
	[OnlyForSeededRunsPatch]
	[HarmonyPatch(typeof(WorldLoader), "Awake")]
	public static class WorldLoader_Awake_Patcher
	{
		public static void Prefix()
		{
			WorldLoader.SetPresetSeed(Plugin.ConfigPresetSeed.Value.ToString());
			Plugin.Beep.LogInfo((object)$"custom preset seed: {Plugin.ConfigPresetSeed.Value}");
			Plugin.SeedForRandom = Plugin.ConfigPresetSeed.Value;
		}
	}
	[PermanentPatch]
	[HarmonyPatch(typeof(WorldLoader), "Initialize")]
	public static class WorldLoader_Initialize_Patcher
	{
		public static void Postfix()
		{
			if (Plugin.IsRandomRun())
			{
				Plugin.SeedForRandom = WorldLoader.instance.seed;
			}
		}
	}
}
namespace IShowSeed.Random.UI
{
	[PermanentPatch]
	[HarmonyPatch(typeof(CL_UIManager), "Update")]
	public static class CL_UIManager_Update_Patcher
	{
		public static void Postfix()
		{
			DebugMenu.UpdateDebugText("starting-seed", $"<color=blue>Starting seed: {Plugin.SeedForRandom}</color>");
		}
	}
	[PermanentPatch]
	[HarmonyPatch(typeof(MenuManager), "Start")]
	public static class MenuManager_Start_Patcher
	{
		public static void Postfix(MenuManager __instance)
		{
			PatchSeedWindow(__instance);
			AddSeedWindowButton(__instance);
		}

		private static void AddSeedWindowButton(MenuManager __instance)
		{
			//IL_0061: 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_0093: Expected O, but got Unknown
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			Transform val = ((Component)__instance.menu).transform.Find("Main Menu Buttons/Logbook");
			Transform val2 = Object.Instantiate<Transform>(val, ((Component)val).transform.parent);
			((Object)val2).name = "Seeded runs";
			TextMeshProUGUI component = ((Component)val2.GetChild(0)).GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).text = "seeded runs";
			((Graphic)component).color = Color.grey;
			((TMP_Text)component).fontSize = ((TMP_Text)component).fontSize / 2f;
			Button component2 = ((Component)val2).GetComponent<Button>();
			component2.onClick = new ButtonClickedEvent();
			((UnityEvent)component2.onClick).AddListener((UnityAction)delegate
			{
				__instance.seedWindow.SetActive(!__instance.seedWindow.activeSelf);
			});
		}

		private static void PatchSeedWindow(MenuManager __instance)
		{
			GameObject seedWindow = __instance.seedWindow;
			TextMeshProUGUI component = ((Component)seedWindow.transform.Find("Overview Titles/Title Text")).gameObject.GetComponent<TextMeshProUGUI>();
			TextMeshProUGUI buttonText = ((Component)seedWindow.transform.Find("Tab Selection Hor/Exit/Text (TMP)")).gameObject.GetComponent<TextMeshProUGUI>();
			Button component2 = ((Component)seedWindow.transform.Find("Tab Selection Hor/Exit")).gameObject.GetComponent<Button>();
			TMP_InputField seedPrompt = ((Component)seedWindow.transform.Find("Seed Input")).gameObject.GetComponent<TMP_InputField>();
			TextMeshProUGUI component3 = ((Component)seedWindow.transform.Find("Seed Input/Text Area/Placeholder")).gameObject.GetComponent<TextMeshProUGUI>();
			if ((Object)(object)buttonText == (Object)null || (Object)(object)seedPrompt == (Object)null || (Object)(object)component2 == (Object)null || (Object)(object)component == (Object)null || (Object)(object)component3 == (Object)null)
			{
				Plugin.Beep.LogWarning((object)$"button: {component2}\nbuttonText: {buttonText}\nseedPrompt: {seedPrompt}\ntitle: {component}\nplaceholder {component3}");
			}
			else
			{
				PatchTitle(component);
				PatchPrompt(seedPrompt, component3);
				PatchButton(component2);
			}
			void PatchButton(Button button)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_002e: Expected O, but got Unknown
				//IL_003c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: Expected O, but got Unknown
				((Graphic)buttonText).color = Color.grey;
				((TMP_Text)buttonText).text = "Save to Config";
				button.onClick = new ButtonClickedEvent();
				((UnityEvent)button.onClick).AddListener((UnityAction)delegate
				{
					//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
					if (int.TryParse(seedPrompt.text, out var result) || seedPrompt.text == "")
					{
						Plugin.ConfigPresetSeed.Value = result;
						((BaseUnityPlugin)Plugin.Instance).Config.Save();
						Plugin.Beep.LogInfo((object)$"Set new seed to {result}");
						Plugin.Beep.LogInfo((object)("Run preview: " + JsonConvert.SerializeObject((object)Vanga.GenerateRouteInfos(result), (Formatting)1)));
						seedWindow.SetActive(false);
					}
					else
					{
						((TMP_Text)buttonText).text = "Invalid seed";
						((Graphic)buttonText).color = Color.red;
					}
				});
			}
			void PatchPrompt(TMP_InputField prompt, TextMeshProUGUI placeholder)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Expected O, but got Unknown
				prompt.onValueChanged = new OnChangeEvent();
				((UnityEvent<string>)(object)prompt.onValueChanged).AddListener((UnityAction<string>)delegate
				{
					//IL_0007: Unknown result type (might be due to invalid IL or missing references)
					((Graphic)buttonText).color = Color.white;
					((TMP_Text)buttonText).text = "Save to Config";
				});
				prompt.text = Plugin.ConfigPresetSeed.Value.ToString();
				if (prompt.text == "0")
				{
					prompt.text = "";
				}
				((TMP_Text)placeholder).text = "KEEP RANDOM";
			}
			static void PatchTitle(TextMeshProUGUI title)
			{
				((TMP_Text)title).text = "ENTER SEED";
			}
		}
	}
}
namespace IShowSeed.Prediction
{
	public struct PredictedPerks
	{
		public List<string> PerkIds;

		public List<string> RefreshedPerkIds;
	}
	public static class PerkMock
	{
		public static PredictedPerks Generate(PerkPageType perkPageType, string levelName, int minCards, int maxCards)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			Rod.SwitchToMode(Rod.ERandomMode.Prediction);
			bool flag = false;
			PredictedPerks predictedPerks = default(PredictedPerks);
			predictedPerks.PerkIds = new List<string>();
			predictedPerks.RefreshedPerkIds = new List<string>();
			PredictedPerks result = predictedPerks;
			int num = 0;
			while (num <= 1)
			{
				Rod.Context ctx = default(Rod.Context);
				Rod.Enter(ref ctx, $"perkpage_{perkPageType}_{levelName}_{minCards}_{maxCards}_{flag}");
				List<string> source = GenerateInternal(perkPageType, minCards, maxCards, flag, result.PerkIds);
				if (!flag)
				{
					result.PerkIds = source.ToList();
				}
				else
				{
					result.RefreshedPerkIds = source.ToList();
				}
				Rod.Exit(in ctx);
				num++;
				flag = true;
			}
			Rod.SwitchToMode(Rod.ERandomMode.Disabled);
			return result;
		}

		private static List<string> GenerateInternal(PerkPageType perkPageType, int minCards, int maxCards, bool refresh, List<string> generatedPerks)
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Invalid comparison between Unknown and I4
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Invalid comparison between Unknown and I4
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Invalid comparison between Unknown and I4
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Invalid comparison between Unknown and I4
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Invalid comparison between Unknown and I4
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			if (generatedPerks.Count > 0)
			{
				for (int num = generatedPerks.Count - 1; num >= 0; num--)
				{
					if (refresh)
					{
						list2.Add(generatedPerks[num]);
					}
				}
			}
			int num2 = Random.Range(minCards, maxCards + 1);
			List<Perk> list3 = Helpers.GetAllPerks().ToList();
			for (int num3 = list3.Count - 1; num3 >= 0; num3--)
			{
				if ((int)perkPageType == 0)
				{
					if ((int)list3[num3].spawnPool != 0)
					{
						list3.RemoveAt(num3);
						continue;
					}
				}
				else if ((int)perkPageType == 1 && (int)list3[num3].spawnPool != 1)
				{
					list3.RemoveAt(num3);
					continue;
				}
				if (refresh && list3.Count > maxCards && list2 != null && list2.Contains(list3[num3].id))
				{
					list3.RemoveAt(num3);
				}
			}
			for (int i = 0; i < num2; i++)
			{
				bool flag = false;
				Perk val = null;
				int num4 = 0;
				while (!flag && num4 < 100)
				{
					num4++;
					val = list3[Random.Range(0, list3.Count)];
					if ((int)perkPageType == 1)
					{
						flag = true;
					}
					else if ((i < num2 - 1 && val.cost == 0) || (i == num2 - 1 && val.cost > 0))
					{
						flag = true;
					}
				}
				list3.Remove(val);
				list.Add(val.id);
			}
			return list;
		}
	}
	public static class Vanga
	{
		public struct PerkMachinePred
		{
			public PerkPageType PerkPageType;

			public string LevelName;

			public PredictedPerks PredictedPerks;

			public PerkMachinePred(PerkPageType type, string levelName, int minCards, int maxCards)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0007: 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)
				PerkPageType = type;
				LevelName = levelName;
				PredictedPerks = PerkMock.Generate(type, levelName, minCards, maxCards);
			}
		}

		public struct RouteInfo
		{
			public string RouteName;

			public List<PerkMachinePred> PerkMachines;
		}

		private static readonly List<string> _machines = new List<string>(6) { "perkpage_unstable_M1_Silos_SafeArea_01_2_2", "perkpage_regular_Campaign_Interlude_Silo_To_Pipeworks_01_3_3", "perkpage_regular_Campaign_Interlude_Sink_To_Pipeworks_01_2_2", "perkpage_unstable_Campaign_Interlude_Sink_To_Pipeworks_01_2_2", "perkpage_regular_M3_Habitation_Shaft_Intro_3_3", "perkpage_regular_Campaign_Interlude_Chute_To_Habitation_2_2" };

		public static List<RouteInfo> GenerateRouteInfos(int s)
		{
			Plugin.SeedForRandom = s;
			return new List<RouteInfo>(3)
			{
				new RouteInfo
				{
					RouteName = "default",
					PerkMachines = new List<PerkMachinePred>(3)
					{
						new PerkMachinePred((PerkPageType)1, "M1_Silos_SafeArea_01", 2, 2),
						new PerkMachinePred((PerkPageType)0, "Campaign_Interlude_Silo_To_Pipeworks_01", 3, 3),
						new PerkMachinePred((PerkPageType)0, "M3_Habitation_Shaft_Intro", 3, 3)
					}
				},
				new RouteInfo
				{
					RouteName = "shortcut_sink",
					PerkMachines = new List<PerkMachinePred>(3)
					{
						new PerkMachinePred((PerkPageType)1, "Campaign_Interlude_Sink_To_Pipeworks_01", 2, 2),
						new PerkMachinePred((PerkPageType)0, "Campaign_Interlude_Sink_To_Pipeworks_01", 2, 2),
						new PerkMachinePred((PerkPageType)0, "M3_Habitation_Shaft_Intro", 3, 3)
					}
				},
				new RouteInfo
				{
					RouteName = "shortcut_burner",
					PerkMachines = new List<PerkMachinePred>(3)
					{
						new PerkMachinePred((PerkPageType)1, "M1_Silos_SafeArea_01", 2, 2),
						new PerkMachinePred((PerkPageType)0, "Campaign_Interlude_Silo_To_Pipeworks_01", 3, 3),
						new PerkMachinePred((PerkPageType)0, "Campaign_Interlude_Chute_To_Habitation", 2, 2)
					}
				}
			};
		}

		public static void DoSeedSearch()
		{
			if (string.IsNullOrWhiteSpace(Plugin.DesiredRouteDescription.Value))
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string> { "default", "shortcut_sink", "shortcut_burner" };
			string[] array = Plugin.DesiredRouteDescription.Value.Split(':');
			if (array.Length == 0)
			{
				Plugin.Beep.LogWarning((object)("Invalid DesiredRouteDescription format: '" + Plugin.DesiredRouteDescription.Value + "'. Expected format: '{route}: {perk1}, {perk2}, {perk3}'"));
				return;
			}
			string routeName = array[0].Trim().ToLower();
			string[] array2 = ((array.Length >= 2) ? (from p in array[1].Split(',')
				select p.Trim().ToLower()).ToArray() : Array.Empty<string>());
			if (!hashSet.Contains(routeName))
			{
				Plugin.Beep.LogWarning((object)("Invalid desired route name: '" + routeName + "'. Valid routes are: " + string.Join(", ", hashSet)));
				return;
			}
			if (array2.Length != 3)
			{
				Plugin.Beep.LogWarning((object)string.Format("Invalid desired perk count: {0}. Expected exactly {1} perks but got: {2}", array2.Length, 3, string.Join(", ", array2)));
				return;
			}
			Plugin.Beep.LogInfo((object)("Searching desired route: " + routeName + ", perks: " + string.Join(", ", array2)));
			for (int i = Plugin.SeedSearchMin.Value; i <= Plugin.SeedSearchMax.Value; i++)
			{
				RouteInfo routeInfo = GenerateRouteInfos(i).First((RouteInfo x) => x.RouteName == routeName);
				if (routeInfo.PerkMachines[0].PredictedPerks.PerkIds.Contains(array2[0]) && routeInfo.PerkMachines[1].PredictedPerks.PerkIds.Contains(array2[1]) && routeInfo.PerkMachines[2].PredictedPerks.PerkIds.Contains(array2[2]))
				{
					Plugin.Beep.LogInfo((object)$"Found suitable seed: {i}");
				}
			}
		}
	}
}