Decompiled source of MatchSeed v1.0.0

MatchSeed.dll

Decompiled 17 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("GameAssembly")]
[assembly: IgnoresAccessChecksTo("SharedAssembly")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("MatchSeed")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+1b90ff945a285959a9ec2b98a235974fe797ddc3")]
[assembly: AssemblyProduct("Match Seed")]
[assembly: AssemblyTitle("MatchSeed")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 MatchSeed
{
	internal static class SeedCodec
	{
		private const int HEX_SEED_LENGTH = 8;

		private const string HEX_FORMAT = "X8";

		private const uint FNV_OFFSET_BASIS = 2166136261u;

		private const uint FNV_PRIME = 16777619u;

		internal static string Format(uint seed)
		{
			return seed.ToString("X8", CultureInfo.InvariantCulture);
		}

		internal static bool TryParse(string input, out uint seed)
		{
			seed = 0u;
			if (string.IsNullOrWhiteSpace(input))
			{
				return false;
			}
			string text = input.Trim();
			if (text.Length == 8 && uint.TryParse(text, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out seed))
			{
				return true;
			}
			seed = HashWord(text);
			return true;
		}

		internal static uint HashWord(string word)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(word.ToLowerInvariant());
			uint num = 2166136261u;
			byte[] array = bytes;
			foreach (byte b in array)
			{
				num ^= b;
				num *= 16777619;
			}
			return num;
		}

		internal static uint CreateRandom()
		{
			return BitConverter.ToUInt32(Guid.NewGuid().ToByteArray(), 0);
		}
	}
	internal enum SeedCommandKind
	{
		Show,
		SetRandom,
		SetValue
	}
	internal static class SeedCommandParser
	{
		private const string RANDOM_KEYWORD = "random";

		internal static SeedCommandKind ParseArgument(string argument)
		{
			if (string.IsNullOrWhiteSpace(argument))
			{
				return SeedCommandKind.Show;
			}
			if (!argument.Trim().Equals("random", StringComparison.OrdinalIgnoreCase))
			{
				return SeedCommandKind.SetValue;
			}
			return SeedCommandKind.SetRandom;
		}
	}
	internal enum StreamId
	{
		MatchHoles = 1,
		Wind,
		Layout,
		ItemRolls,
		ItemContest,
		Tees,
		Teams,
		Respawn,
		Hazards,
		Traffic
	}
	internal static class StreamKey
	{
		private const ulong GOLDEN_GAMMA = 11400714819323198485uL;

		private const ulong MIX_MULTIPLIER_A = 13787848793156543929uL;

		private const ulong MIX_MULTIPLIER_B = 10723151780598845931uL;

		private const int MIX_SHIFT_A = 30;

		private const int MIX_SHIFT_B = 27;

		private const int MIX_SHIFT_C = 31;

		private const int HIGH_WORD_SHIFT = 32;

		private const int HASH_PRIME_X = 73856093;

		private const int HASH_PRIME_Y = 19349663;

		private const int HASH_PRIME_Z = 83492791;

		internal static int Derive(uint seed, StreamId stream, int holeIndex, int subKey)
		{
			return (int)(Mix(Mix(Mix(Mix(seed) ^ (uint)stream) ^ (uint)holeIndex) ^ (uint)subKey) >> 32);
		}

		internal static uint DeriveNonZero(uint seed, StreamId stream, int holeIndex, int subKey)
		{
			uint num = (uint)Derive(seed, stream, holeIndex, subKey);
			if (num != 0)
			{
				return num;
			}
			return 1u;
		}

		internal static int CombineHash(int x, int y, int z)
		{
			return (x * 73856093) ^ (y * 19349663) ^ (z * 83492791);
		}

		private static ulong Mix(ulong value)
		{
			long num = (long)value + -7046029254386353131L;
			long num2 = (num ^ (num >>> 30)) * -4658895280553007687L;
			long num3 = (num2 ^ (num2 >>> 27)) * -7723592293110705685L;
			return (ulong)(num3 ^ (num3 >>> 31));
		}
	}
	internal sealed class MatchSeedConfig
	{
		private const string SECTION_SEED = "Seed";

		private const string SECTION_DISPLAY = "Display";

		private const string SECTION_DEBUG = "Debug";

		internal ConfigEntry<string> Seed { get; }

		internal ConfigEntry<bool> ShowSeedOnHud { get; }

		internal ConfigEntry<bool> LogRolls { get; }

		internal MatchSeedConfig(ConfigFile config)
		{
			Seed = config.Bind<string>("Seed", "Seed", string.Empty, "Seed for the next match. Blank picks a new random seed every match. Accepts 8 hex characters (as shown on the HUD) or any word (hashed). The 'matchSeed' console command overrides this until you run 'matchSeed random'.");
			ShowSeedOnHud = config.Bind<bool>("Display", "ShowSeedOnHud", true, "Show the active seed in a corner of the host's screen. Nothing is sent to other players.");
			LogRolls = config.Bind<bool>("Debug", "LogRolls", false, "Log every seeded scope, wind roll and item roll. Diff two runs' logs to verify they reproduce.");
		}
	}
	[BepInPlugin("com.sbg.matchseed", "MatchSeed", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PLUGIN_GUID = "com.sbg.matchseed";

		public const string PLUGIN_NAME = "MatchSeed";

		public const string PLUGIN_VERSION = "1.0.0";

		internal static ManualLogSource Log { get; private set; }

		internal static MatchSeedConfig Settings { get; private set; }

		private void Awake()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings = new MatchSeedConfig(((BaseUnityPlugin)this).Config);
			Harmony val = new Harmony("com.sbg.matchseed");
			val.PatchAll();
			ScopePatcher.Apply(val, ScopeTable.Specs);
			CreatePersistentComponents();
			Log.LogInfo((object)"MatchSeed 1.0.0 loaded.");
		}

		private void CreatePersistentComponents()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			GameObject val = new GameObject("MatchSeed");
			Object.DontDestroyOnLoad((Object)val);
			val.AddComponent<SeedHud>();
		}
	}
	internal enum SubKeyKind
	{
		None,
		InstancePosition,
		PlayerArgument
	}
	internal sealed class ScopeSpec
	{
		internal Type TargetType { get; }

		internal string MethodName { get; }

		internal StreamId Stream { get; }

		internal SubKeyKind SubKey { get; }

		internal bool AllowInLobby { get; }

		internal bool SkipIfNested { get; }

		internal ScopeSpec(Type targetType, string methodName, StreamId stream, SubKeyKind subKey = SubKeyKind.None, bool allowInLobby = false, bool skipIfNested = false)
		{
			TargetType = targetType;
			MethodName = methodName;
			Stream = stream;
			SubKey = subKey;
			AllowInLobby = allowInLobby;
			SkipIfNested = skipIfNested;
		}
	}
	internal static class ScopePatcher
	{
		private const float POSITION_QUANTISATION_PER_UNIT = 10f;

		private const int NO_PLAYER_SUB_KEY = -1;

		private const string LOCAL_FUNCTION_MARKER = "g__";

		private const string LOCAL_FUNCTION_TERMINATOR = "|";

		private static readonly Dictionary<string, ScopeSpec> SpecsByMethod = new Dictionary<string, ScopeSpec>();

		internal static void Apply(Harmony harmony, IReadOnlyList<ScopeSpec> specs)
		{
			//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: Expected O, but got Unknown
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			HarmonyMethod prefix = new HarmonyMethod(typeof(ScopePatcher), "ScopePrefix", (Type[])null)
			{
				priority = 700
			};
			HarmonyMethod finalizer = new HarmonyMethod(typeof(ScopePatcher), "ScopeFinalizer", (Type[])null)
			{
				priority = 0
			};
			int num = 0;
			foreach (ScopeSpec spec in specs)
			{
				if (TryApplyOne(harmony, spec, prefix, finalizer))
				{
					num++;
				}
			}
			Plugin.Log.LogInfo((object)$"Seed scopes applied: {num} of {specs.Count()}.");
		}

		private static bool TryApplyOne(Harmony harmony, ScopeSpec spec, HarmonyMethod prefix, HarmonyMethod finalizer)
		{
			try
			{
				MethodBase methodBase = ResolveMethod(spec.TargetType, spec.MethodName);
				if (methodBase == null)
				{
					Plugin.Log.LogWarning((object)("Seed scope skipped: " + spec.TargetType.Name + "." + spec.MethodName + " not found. " + $"Cause: the game changed. Resolution: {spec.Stream} may not be seeded until the mod is updated."));
					return false;
				}
				SpecsByMethod[KeyOf(methodBase)] = spec;
				harmony.Patch(methodBase, prefix, (HarmonyMethod)null, (HarmonyMethod)null, finalizer, (HarmonyMethod)null);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Seed scope skipped: " + spec.TargetType.Name + "." + spec.MethodName + " failed to patch (" + ex.GetType().Name + ": " + ex.Message + "). " + $"Resolution: {spec.Stream} may not be seeded until the mod is updated."));
				return false;
			}
		}

		private static void ScopePrefix(MethodBase __originalMethod, object __instance, object[] __args, out ScopeToken __state)
		{
			__state = ScopeToken.Inactive;
			if (SpecsByMethod.TryGetValue(KeyOf(__originalMethod), out var value))
			{
				int subKey = ResolveSubKey(value.SubKey, __instance, __args);
				__state = SeedScope.Enter(value.Stream, subKey, value.AllowInLobby, value.SkipIfNested);
			}
		}

		private static void ScopeFinalizer(ScopeToken __state)
		{
			SeedScope.Exit(in __state);
		}

		private static int ResolveSubKey(SubKeyKind kind, object instance, object[] args)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			switch (kind)
			{
			case SubKeyKind.InstancePosition:
			{
				Component val2 = (Component)((instance is Component) ? instance : null);
				if (val2 == null)
				{
					return 0;
				}
				return HashPosition(val2.transform.position);
			}
			case SubKeyKind.PlayerArgument:
				if (args.Length != 0)
				{
					object obj = args[0];
					PlayerInfo val = (PlayerInfo)((obj is PlayerInfo) ? obj : null);
					if (val != null)
					{
						return GetPlayerSlot(val);
					}
				}
				return -1;
			default:
				return 0;
			}
		}

		private static int HashPosition(Vector3 position)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			return StreamKey.CombineHash(Mathf.RoundToInt(position.x * 10f), Mathf.RoundToInt(position.y * 10f), Mathf.RoundToInt(position.z * 10f));
		}

		internal static int GetPlayerSlot(PlayerInfo player)
		{
			if (!((Object)(object)player != (Object)null) || ((NetworkBehaviour)player).connectionToClient == null)
			{
				return -1;
			}
			return ((NetworkBehaviour)player).connectionToClient.connectionId;
		}

		private static string KeyOf(MethodBase method)
		{
			return method.DeclaringType.FullName + "::" + method.Name;
		}

		private static MethodBase ResolveMethod(Type type, string name)
		{
			MethodBase methodBase = AccessTools.DeclaredMethod(type, name, (Type[])null, (Type[])null);
			if (methodBase != null)
			{
				return methodBase;
			}
			string value = "g__" + name + "|";
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
			{
				if (declaredMethod.Name.Contains(value))
				{
					return declaredMethod;
				}
			}
			return null;
		}
	}
	internal static class ScopeTable
	{
		internal static IReadOnlyList<ScopeSpec> Specs { get; } = new List<ScopeSpec>
		{
			new ScopeSpec(typeof(MatchSetupMenu), "StartOrCancelMatch", StreamId.MatchHoles, SubKeyKind.None, allowInLobby: true),
			new ScopeSpec(typeof(WindManager), "RandomizeWind", StreamId.Wind),
			new ScopeSpec(typeof(ItemSpawnerSettings), "GetRandomItemFor", StreamId.ItemRolls, SubKeyKind.PlayerArgument),
			new ScopeSpec(typeof(ItemSpawner), "ServerTryGiveItemToRandomPlayer", StreamId.ItemContest, SubKeyKind.InstancePosition),
			new ScopeSpec(typeof(ItemSpawner), "ServerTryGiveItemToRandomGolfCart", StreamId.ItemContest, SubKeyKind.InstancePosition),
			new ScopeSpec(typeof(CourseManager), "ReserveTeeingSpotsForAllAwaitingPlayers", StreamId.Tees),
			new ScopeSpec(typeof(GolfTeeManager), "GetAvailableTeeingSpotInternal", StreamId.Tees),
			new ScopeSpec(typeof(CourseManager), "RedistributeTeamsInternal", StreamId.Teams),
			new ScopeSpec(typeof(CourseManager), "GetDefaultTeam", StreamId.Teams),
			new ScopeSpec(typeof(Checkpoint), "GetRespawnPosition", StreamId.Respawn, SubKeyKind.InstancePosition),
			new ScopeSpec(typeof(BreakableIce), "ServerRegisterHit", StreamId.Hazards, SubKeyKind.InstancePosition),
			new ScopeSpec(typeof(FreezeBomb), "ServerExplode", StreamId.Hazards),
			new ScopeSpec(typeof(FreezeBombPlatform), "OnStartServer", StreamId.Hazards, SubKeyKind.InstancePosition),
			new ScopeSpec(typeof(Thunderstorm), "RandomizeTimeUntilNextLightningStrike", StreamId.Hazards),
			new ScopeSpec(typeof(Thunderstorm), "LightningStrike", StreamId.Hazards),
			new ScopeSpec(typeof(ThunderstormLightningStrike), "Strike", StreamId.Hazards),
			new ScopeSpec(typeof(TrafficSpawner), "OnStartServer", StreamId.Traffic, SubKeyKind.InstancePosition),
			new ScopeSpec(typeof(TrafficSpawner), "OnBUpdate", StreamId.Traffic, SubKeyKind.InstancePosition),
			new ScopeSpec(typeof(TrafficSettings), "GetWeightedRandomVehicleType", StreamId.Traffic, SubKeyKind.None, allowInLobby: false, skipIfNested: true),
			new ScopeSpec(typeof(TrafficVehicle), "ServerInitialize", StreamId.Traffic, SubKeyKind.None, allowInLobby: false, skipIfNested: true)
		};
	}
	internal static class SeedCommandService
	{
		private const string REPLY_PREFIX = "[MatchSeed] ";

		private const string RANDOM_LABEL = "random";

		internal static string Execute(SeedCommandKind kind, string argument)
		{
			return kind switch
			{
				SeedCommandKind.SetRandom => SetRandom(), 
				SeedCommandKind.SetValue => SetValue(argument), 
				_ => Describe(), 
			};
		}

		private static string Describe()
		{
			string text = (SeedSession.ActiveSeed.HasValue ? SeedCodec.Format(SeedSession.ActiveSeed.Value) : "none");
			uint? nextSeed = SeedSession.GetNextSeed();
			string text2 = (nextSeed.HasValue ? SeedCodec.Format(nextSeed.Value) : "random");
			return "[MatchSeed] Active: " + text + " | Next match: " + text2;
		}

		private static string SetRandom()
		{
			SeedSession.ClearPending();
			if (SeedCodec.TryParse(Plugin.Settings.Seed.Value, out var seed))
			{
				return "[MatchSeed] Command seed cleared, but the config Seed (" + SeedCodec.Format(seed) + ") still applies. Clear it in the config for random seeds.";
			}
			return "[MatchSeed] Next match will use a random seed.";
		}

		private static string SetValue(string argument)
		{
			if (!SeedCodec.TryParse(argument, out var seed))
			{
				return "[MatchSeed] Seed was empty. Use 'matchSeed <value>' or 'matchSeed random'.";
			}
			SeedSession.SetPending(seed);
			return "[MatchSeed] Next match seed set to " + SeedCodec.Format(seed) + ". It applies when the next match starts.";
		}
	}
	internal static class SeedConsoleCommand
	{
		[CCommand("matchSeed", "Show or set the next match's seed: matchSeed [value|random]", false, false)]
		private static void MatchSeed(string value = "")
		{
			Debug.Log((object)SeedCommandService.Execute(SeedCommandParser.ParseArgument(value), value));
		}
	}
	internal sealed class SeedHud : MonoBehaviour
	{
		private const int FONT_SIZE = 16;

		private const float MARGIN = 10f;

		private const float WIDTH = 320f;

		private const float HEIGHT = 26f;

		private const float SHADOW_OFFSET = 1f;

		private const float TEXT_ALPHA = 0.9f;

		private const float SHADOW_ALPHA = 0.8f;

		private GUIStyle textStyle;

		private GUIStyle shadowStyle;

		private void OnGUI()
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Settings.ShowSeedOnHud.Value)
			{
				string hudText = SeedSession.GetHudText();
				if (hudText != null)
				{
					EnsureStyles();
					Rect val = new Rect(10f, 10f, 320f, 26f);
					GUI.Label(new Rect(11f, 11f, 320f, 26f), hudText, shadowStyle);
					GUI.Label(val, hudText, textStyle);
				}
			}
		}

		private void EnsureStyles()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			if (textStyle == null)
			{
				textStyle = new GUIStyle(GUI.skin.label)
				{
					fontSize = 16,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)0
				};
				textStyle.normal.textColor = new Color(1f, 1f, 1f, 0.9f);
				shadowStyle = new GUIStyle(textStyle);
				shadowStyle.normal.textColor = new Color(0f, 0f, 0f, 0.8f);
			}
		}
	}
	internal readonly struct ScopeToken
	{
		internal static readonly ScopeToken Inactive;

		internal bool IsActive { get; }

		internal State SavedGlobalState { get; }

		internal State EnteredStreamState { get; }

		internal StreamId Stream { get; }

		internal int HoleIndex { get; }

		internal int SubKey { get; }

		internal ScopeToken(State savedGlobalState, State enteredStreamState, StreamId stream, int holeIndex, int subKey)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			IsActive = true;
			SavedGlobalState = savedGlobalState;
			EnteredStreamState = enteredStreamState;
			Stream = stream;
			HoleIndex = holeIndex;
			SubKey = subKey;
		}
	}
	internal static class SeedScope
	{
		private static readonly Dictionary<(StreamId, int, int), State> StreamStates = new Dictionary<(StreamId, int, int), State>();

		private static int nestingDepth;

		internal static void ResetStreams()
		{
			StreamStates.Clear();
			nestingDepth = 0;
		}

		internal static ScopeToken Enter(StreamId stream, int subKey, bool allowInLobby, bool skipIfNested)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: 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)
			if (!SeedSession.IsSeeding || (!allowInLobby && SeedSession.IsInLobby))
			{
				return ScopeToken.Inactive;
			}
			if (skipIfNested && nestingDepth > 0)
			{
				return ScopeToken.Inactive;
			}
			int currentHoleCourseIndex = CourseManager.CurrentHoleCourseIndex;
			State state = Random.state;
			if (!StreamStates.TryGetValue((stream, currentHoleCourseIndex, subKey), out var value))
			{
				Random.InitState(StreamKey.Derive(SeedSession.ActiveSeed.Value, stream, currentHoleCourseIndex, subKey));
				value = Random.state;
			}
			Random.state = value;
			nestingDepth++;
			return new ScopeToken(state, value, stream, currentHoleCourseIndex, subKey);
		}

		internal static void Exit(in ScopeToken token)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: 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_0060: Unknown result type (might be due to invalid IL or missing references)
			if (token.IsActive)
			{
				nestingDepth = Mathf.Max(0, nestingDepth - 1);
				State state = Random.state;
				StreamStates[(token.Stream, token.HoleIndex, token.SubKey)] = state;
				Random.state = token.SavedGlobalState;
				if (Plugin.Settings.LogRolls.Value)
				{
					LogIfDrew(in token, state);
				}
			}
		}

		private static void LogIfDrew(in ScopeToken token, State advancedState)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			string text = JsonUtility.ToJson((object)token.EnteredStreamState);
			string text2 = JsonUtility.ToJson((object)advancedState);
			if (!(text == text2))
			{
				Plugin.Log.LogInfo((object)$"scope {token.Stream} hole={token.HoleIndex} sub={token.SubKey} -> {text2}");
			}
		}
	}
	internal static class SeedSession
	{
		private const int SAME_START_FRAME_WINDOW = 3;

		private const string HUD_NEXT_PREFIX = "Next seed: ";

		private const string HUD_ACTIVE_PREFIX = "Seed: ";

		private const string HUD_RANDOM_LABEL = "random";

		private static int lockedAtFrame = int.MinValue;

		internal static uint? PendingSeed { get; private set; }

		internal static uint? ActiveSeed { get; private set; }

		internal static bool IsInLobby => SingletonBehaviour<DrivingRangeManager>.HasInstance;

		internal static bool IsSeeding
		{
			get
			{
				if (ActiveSeed.HasValue)
				{
					return NetworkServer.active;
				}
				return false;
			}
		}

		internal static void SetPending(uint seed)
		{
			PendingSeed = seed;
		}

		internal static void ClearPending()
		{
			PendingSeed = null;
		}

		internal static uint? GetNextSeed()
		{
			if (PendingSeed.HasValue)
			{
				return PendingSeed;
			}
			if (!SeedCodec.TryParse(Plugin.Settings.Seed.Value, out var seed))
			{
				return null;
			}
			return seed;
		}

		internal static void BeginMatch()
		{
			if (NetworkServer.active && IsInLobby && (!ActiveSeed.HasValue || Time.frameCount - lockedAtFrame > 3))
			{
				uint num = GetNextSeed() ?? SeedCodec.CreateRandom();
				ActiveSeed = num;
				lockedAtFrame = Time.frameCount;
				SeedScope.ResetStreams();
				Plugin.Log.LogInfo((object)("Match seed locked: " + SeedCodec.Format(num)));
			}
		}

		internal static void EndMatch()
		{
			if (ActiveSeed.HasValue)
			{
				Plugin.Log.LogInfo((object)("Match seed released: " + SeedCodec.Format(ActiveSeed.Value)));
				ActiveSeed = null;
				SeedScope.ResetStreams();
			}
		}

		internal static string GetHudText()
		{
			if (!NetworkServer.active)
			{
				return null;
			}
			if (IsInLobby)
			{
				return GetLobbyHudText();
			}
			if (!ActiveSeed.HasValue)
			{
				return null;
			}
			return "Seed: " + SeedCodec.Format(ActiveSeed.Value);
		}

		private static string GetLobbyHudText()
		{
			uint? nextSeed = GetNextSeed();
			return "Next seed: " + (nextSeed.HasValue ? SeedCodec.Format(nextSeed.Value) : "random");
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "MatchSeed";

		public const string PLUGIN_NAME = "Match Seed";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace MatchSeed.Patches
{
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class LayoutSeedPatch
	{
		[HarmonyPrepare]
		private static bool Prepare(MethodBase original)
		{
			if (original != null)
			{
				return true;
			}
			bool num = AccessTools.PropertySetter(typeof(ObjectRandomizationManager), "Networkseed") != null;
			if (!num)
			{
				Plugin.Log.LogWarning((object)"Layout seeding skipped: ObjectRandomizationManager.Networkseed not found. Cause: the game changed. Resolution: item box and pond layout will not be seeded until the mod is updated.");
			}
			return num;
		}

		[HarmonyPrefix]
		private static void Prefix(ref uint value)
		{
			if (value != 0 && SeedSession.IsSeeding && !SeedSession.IsInLobby)
			{
				int currentHoleCourseIndex = CourseManager.CurrentHoleCourseIndex;
				value = StreamKey.DeriveNonZero(SeedSession.ActiveSeed.Value, StreamId.Layout, currentHoleCourseIndex, 0);
				if (Plugin.Settings.LogRolls.Value)
				{
					Plugin.Log.LogInfo((object)$"layout seed hole={currentHoleCourseIndex} -> {value}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(MatchSetupMenu), "StartOrCancelMatch")]
	internal static class BeginMatchFromMenuPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void Prefix()
		{
			SeedSession.BeginMatch();
		}
	}
	[HarmonyPatch(typeof(CourseManager), "StartCourse")]
	internal static class BeginMatchFromCoursePatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void Prefix()
		{
			SeedSession.BeginMatch();
		}
	}
	[HarmonyPatch(typeof(CourseManager), "EndCourse")]
	internal static class EndMatchPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			SeedSession.EndMatch();
		}
	}
	[HarmonyPatch(typeof(WindManager), "RandomizeWind")]
	internal static class WindRollLogPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(WindManager __instance)
		{
			if (Plugin.Settings.LogRolls.Value && NetworkServer.active)
			{
				Plugin.Log.LogInfo((object)($"wind hole={CourseManager.CurrentHoleCourseIndex} " + $"speed={__instance.currentWindSpeed} angle={__instance.currentWindAngle}"));
			}
		}
	}
	[HarmonyPatch(typeof(ItemSpawnerSettings), "GetRandomItemFor")]
	internal static class ItemRollLogPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerInfo player, ItemType __result)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Settings.LogRolls.Value)
			{
				Plugin.Log.LogInfo((object)($"item hole={CourseManager.CurrentHoleCourseIndex} " + $"player={ScopePatcher.GetPlayerSlot(player)} item={__result}"));
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}