Decompiled source of RaceLeaderboard v1.0.0

Niko666.RaceLeaderboard.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FistVR;
using H3MP;
using H3MP.Networking;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Sodalite;
using Sodalite.Api;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("Niko666")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Lap timing, checkpoints and a persistent leaderboard for the Mugello raceway map, with per-player H3MP sync.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Niko666.RaceLeaderboard")]
[assembly: AssemblyTitle("Mugello Race")]
[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 Niko666.MugelloRace
{
	public static class H3MPSync
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static CustomPacketHandlerReceivedDelegate <0>__OnRegistered;

			public static CustomPacketHandler <1>__OnPacket;
		}

		private const string PacketId = "mugello_lap";

		private static Leaderboard _board;

		private static Action _onChange;

		private static bool _subscribed;

		private static bool _registered;

		private static int _index = -1;

		public static bool Active
		{
			get
			{
				try
				{
					return (Object)(object)Mod.managerObject != (Object)null;
				}
				catch
				{
					return false;
				}
			}
		}

		public static void Attach(Leaderboard board, Action onChange)
		{
			_board = board;
			_onChange = onChange;
		}

		public static void Poll()
		{
			//IL_004a: 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)
			//IL_0055: Expected O, but got Unknown
			if (!Active)
			{
				if (_registered)
				{
					MugelloRacePlugin.Log.LogInfo((object)"H3MP session closed, lap sync idle.");
					_registered = false;
					_index = -1;
				}
			}
			else
			{
				if (_registered)
				{
					return;
				}
				try
				{
					if (!_subscribed)
					{
						object obj = <>O.<0>__OnRegistered;
						if (obj == null)
						{
							CustomPacketHandlerReceivedDelegate val = OnRegistered;
							<>O.<0>__OnRegistered = val;
							obj = (object)val;
						}
						Mod.CustomPacketHandlerReceived += (CustomPacketHandlerReceivedDelegate)obj;
						_subscribed = true;
					}
					if (Mod.registeredCustomPacketIDs != null && Mod.registeredCustomPacketIDs.TryGetValue("mugello_lap", out var value))
					{
						Bind(value);
					}
					else if (ThreadManager.host)
					{
						Bind(Server.RegisterCustomPacketType("mugello_lap", 0));
					}
					else
					{
						ClientSend.RegisterCustomPacketType("mugello_lap");
					}
				}
				catch (Exception ex)
				{
					MugelloRacePlugin.Log.LogWarning((object)("H3MP registration failed: " + ex.Message));
					_registered = true;
					_index = -1;
				}
			}
		}

		private static void OnRegistered(string id, int index)
		{
			if (id == "mugello_lap")
			{
				Bind(index);
			}
		}

		private static void Bind(int index)
		{
			//IL_002d: 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_0038: Expected O, but got Unknown
			if (index >= 0 && Mod.customPacketHandlers != null && index < Mod.customPacketHandlers.Length)
			{
				CustomPacketHandler[] customPacketHandlers = Mod.customPacketHandlers;
				int num = index;
				object obj = <>O.<1>__OnPacket;
				if (obj == null)
				{
					CustomPacketHandler val = OnPacket;
					<>O.<1>__OnPacket = val;
					obj = (object)val;
				}
				customPacketHandlers[num] = (CustomPacketHandler)obj;
				_index = index;
				_registered = true;
				MugelloRacePlugin.Log.LogInfo((object)("H3MP lap sync ready (packet index " + index + ", host=" + ThreadManager.host + ")"));
			}
		}

		private static void OnPacket(int clientId, Packet packet)
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			try
			{
				int num = packet.ReadInt(true);
				string text = packet.ReadString(true);
				float num2 = packet.ReadFloat(true);
				long num3 = packet.ReadLong(true);
				if (num == GameManager.ID || _board == null || !_board.Merge(text, num2, num3, local: false))
				{
					return;
				}
				_board.Save();
				if (_onChange != null)
				{
					_onChange();
				}
				if (!ThreadManager.host || clientId == 0)
				{
					return;
				}
				Packet val = new Packet(_index);
				try
				{
					val.Write(num);
					val.Write(text);
					val.Write(num2);
					val.Write(num3);
					ServerSend.SendTCPDataToAll(val, true);
				}
				finally
				{
					((IDisposable)val)?.Dispose();
				}
			}
			catch (Exception ex)
			{
				MugelloRacePlugin.Log.LogWarning((object)("lap packet failed: " + ex.Message));
			}
		}

		public static void SendLap(string name, float seconds, long ticks)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			if (!Active || _index < 0)
			{
				return;
			}
			try
			{
				Packet val = new Packet(_index);
				try
				{
					val.Write(GameManager.ID);
					val.Write(name);
					val.Write(seconds);
					val.Write(ticks);
					if (ThreadManager.host)
					{
						ServerSend.SendTCPDataToAll(val, true);
					}
					else
					{
						ClientSend.SendTCPData(val, true);
					}
				}
				finally
				{
					((IDisposable)val)?.Dispose();
				}
				MugelloRacePlugin.Log.LogInfo((object)("lap sent: " + name + " " + seconds.ToString("0.00") + "s"));
			}
			catch (Exception ex)
			{
				MugelloRacePlugin.Log.LogWarning((object)("lap send failed: " + ex.Message));
			}
		}
	}
	public class HeadBoard : MonoBehaviour
	{
		public static HeadBoard Instance;

		private TextMesh _text;

		private Font _asciiFont;

		private Material _depthMat;

		private Material _overlayMat;

		private Transform _bg;

		private Material _bgMatOverlay;

		private Material _bgMatDepth;

		private float _fitSx = -1f;

		private float _fitSy = -1f;

		private int _fitQueued;

		private float _nextCfgPoll;

		private float _fwd = 0.62f;

		private float _right = 0.19f;

		private float _down = 0.13f;

		private bool _visible;

		private bool _materialLogged;

		private string _pending = "LEADERBOARD";

		private static Texture2D _white;

		public bool Visible => _visible;

		private static bool OnTop
		{
			get
			{
				if (MugelloRacePlugin.CfgBoardOnTop != null)
				{
					return MugelloRacePlugin.CfgBoardOnTop.Value;
				}
				return true;
			}
		}

		public static HeadBoard Create()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			if ((Object)(object)Instance != (Object)null)
			{
				return Instance;
			}
			GameObject val = new GameObject("LB_HEAD_BOARD");
			Object.DontDestroyOnLoad((Object)val);
			HeadBoard headBoard = val.AddComponent<HeadBoard>();
			headBoard.Build();
			Instance = headBoard;
			return headBoard;
		}

		private void Build()
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Expected O, but got Unknown
			_text = ((Component)this).gameObject.AddComponent<TextMesh>();
			float characterSize = ((MugelloRacePlugin.CfgBoardScale != null) ? MugelloRacePlugin.CfgBoardScale.Value : 0.0026f);
			Font val = null;
			GameObject val2 = MugelloRacePlugin.FindByName("MR_BOARD_TEXT");
			if ((Object)(object)val2 != (Object)null)
			{
				TextMesh component = val2.GetComponent<TextMesh>();
				if ((Object)(object)component != (Object)null)
				{
					val = component.font;
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				val = Resources.GetBuiltinResource<Font>("Arial.ttf");
			}
			Debug.Log((object)("[MugelloRace] head board font: " + (((Object)(object)val != (Object)null) ? ((Object)val).name : "none")));
			_asciiFont = val;
			_text.font = val;
			_text.fontSize = 64;
			_text.characterSize = characterSize;
			_text.anchor = (TextAnchor)0;
			_text.alignment = (TextAlignment)0;
			_text.color = new Color(1f, 0.78f, 0.28f);
			_text.richText = false;
			_text.text = _pending;
			MeshRenderer component2 = ((Component)this).gameObject.GetComponent<MeshRenderer>();
			if ((Object)(object)component2 != (Object)null && (Object)(object)val != (Object)null)
			{
				Shader val3 = Shader.Find("Niko666/MugelloText");
				if ((Object)(object)val3 != (Object)null)
				{
					_depthMat = new Material(val.material);
					_depthMat.shader = val3;
					_depthMat.renderQueue = 2450;
				}
				_overlayMat = val.material;
				((Renderer)component2).sharedMaterial = (((Object)(object)_depthMat != (Object)null) ? _depthMat : _overlayMat);
				((Renderer)component2).shadowCastingMode = (ShadowCastingMode)0;
				((Renderer)component2).receiveShadows = false;
			}
			BuildBackground();
			((Component)this).gameObject.SetActive(false);
		}

		private void BuildBackground()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Expected O, but got Unknown
			GameObject val = new GameObject("LB_BG");
			val.transform.SetParent(((Component)this).transform, false);
			val.AddComponent<MeshFilter>().sharedMesh = QuadMesh();
			Shader val2 = (((Object)(object)_overlayMat != (Object)null) ? _overlayMat.shader : null);
			if ((Object)(object)val2 != (Object)null)
			{
				_bgMatOverlay = new Material(val2);
				_bgMatOverlay.SetTexture("_MainTex", (Texture)(object)WhiteTexture());
				_bgMatOverlay.renderQueue = 2440;
			}
			Shader val3 = Shader.Find("Sprites/Default");
			if ((Object)(object)val3 == (Object)null)
			{
				val3 = Shader.Find("UI/Default");
			}
			if ((Object)(object)val3 != (Object)null)
			{
				_bgMatDepth = new Material(val3);
				if (_bgMatDepth.HasProperty("_MainTex"))
				{
					_bgMatDepth.SetTexture("_MainTex", (Texture)(object)WhiteTexture());
				}
				_bgMatDepth.renderQueue = 2440;
			}
			if ((Object)(object)_bgMatOverlay == (Object)null && (Object)(object)_bgMatDepth == (Object)null)
			{
				Debug.LogError((object)"[MugelloRace] board backdrop: no usable shader, plate skipped");
			}
			MeshRenderer obj = val.AddComponent<MeshRenderer>();
			((Renderer)obj).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)obj).receiveShadows = false;
			_bg = val.transform;
			ApplyBackground();
		}

		private static Texture2D WhiteTexture()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_white != (Object)null)
			{
				return _white;
			}
			_white = new Texture2D(1, 1, (TextureFormat)5, false);
			_white.SetPixel(0, 0, Color.white);
			_white.Apply();
			((Object)_white).hideFlags = (HideFlags)61;
			return _white;
		}

		private static Mesh QuadMesh()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			Mesh val = new Mesh();
			((Object)val).name = "LB_BG_Quad";
			val.vertices = (Vector3[])(object)new Vector3[4]
			{
				new Vector3(-0.5f, -0.5f, 0f),
				new Vector3(0.5f, -0.5f, 0f),
				new Vector3(0.5f, 0.5f, 0f),
				new Vector3(-0.5f, 0.5f, 0f)
			};
			val.uv = (Vector2[])(object)new Vector2[4]
			{
				new Vector2(0f, 0f),
				new Vector2(1f, 0f),
				new Vector2(1f, 1f),
				new Vector2(0f, 1f)
			};
			val.colors = (Color[])(object)new Color[4]
			{
				Color.white,
				Color.white,
				Color.white,
				Color.white
			};
			val.triangles = new int[6] { 0, 1, 2, 0, 2, 3 };
			val.RecalculateNormals();
			val.RecalculateBounds();
			return val;
		}

		private void ApplyBackground()
		{
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_bg == (Object)null)
			{
				return;
			}
			float num = ((MugelloRacePlugin.CfgBoardBackground != null) ? MugelloRacePlugin.CfgBoardBackground.Value : 0.55f);
			Material val = (OnTop ? _bgMatOverlay : (((Object)(object)_bgMatDepth != (Object)null) ? _bgMatDepth : _bgMatOverlay));
			if (!((Object)(object)val == (Object)null))
			{
				MeshRenderer component = ((Component)_bg).GetComponent<MeshRenderer>();
				if ((Object)(object)component != (Object)null && (Object)(object)((Renderer)component).sharedMaterial != (Object)(object)val)
				{
					((Renderer)component).sharedMaterial = val;
				}
				((Component)_bg).gameObject.SetActive(num > 0.001f);
				Color val2 = default(Color);
				((Color)(ref val2))..ctor(0.03f, 0.03f, 0.03f, num);
				if (val.HasProperty("_Color"))
				{
					val.SetColor("_Color", val2);
				}
				val.color = val2;
			}
		}

		private void FitBackground()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_bg == (Object)null || (Object)(object)_text == (Object)null)
			{
				return;
			}
			MeshRenderer component = ((Component)this).GetComponent<MeshRenderer>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			Bounds bounds = ((Renderer)component).bounds;
			if (!(((Bounds)(ref bounds)).size.y < 0.0001f))
			{
				float num = Mathf.Sqrt(((Bounds)(ref bounds)).size.x * ((Bounds)(ref bounds)).size.x + ((Bounds)(ref bounds)).size.z * ((Bounds)(ref bounds)).size.z);
				if (!(num < 0.0001f))
				{
					float num2 = ((MugelloRacePlugin.CfgBoardBgScaleX != null) ? MugelloRacePlugin.CfgBoardBgScaleX.Value : 1f);
					float num3 = ((MugelloRacePlugin.CfgBoardBgScaleY != null) ? MugelloRacePlugin.CfgBoardBgScaleY.Value : 1f);
					_fitSx = num2;
					_fitSy = num3;
					Vector3 val = ((Component)this).transform.InverseTransformPoint(((Bounds)(ref bounds)).center);
					_bg.localScale = new Vector3(num * num2 + 0.012f, ((Bounds)(ref bounds)).size.y * num3 + 0.004f, 1f);
					_bg.localPosition = new Vector3(val.x, val.y, 0.003f);
				}
			}
		}

		private void RefreshAsciiFont()
		{
			GameObject val = MugelloRacePlugin.FindByName("MR_BOARD_TEXT");
			if (!((Object)(object)val == (Object)null))
			{
				TextMesh component = val.GetComponent<TextMesh>();
				if (!((Object)(object)component == (Object)null) && !((Object)(object)component.font == (Object)null) && !((Object)(object)component.font == (Object)(object)_asciiFont))
				{
					_asciiFont = component.font;
					ApplyFont(BoardFonts.For(_pending, _asciiFont));
				}
			}
		}

		public void SetText(string text)
		{
			_pending = text;
			RefreshAsciiFont();
			if (_visible && (Object)(object)_text != (Object)null)
			{
				ApplyFont(BoardFonts.For(text, _asciiFont));
				_text.text = text;
				_fitQueued = 2;
			}
		}

		private void ApplyFont(Font f)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)f == (Object)null) && !((Object)(object)_text == (Object)null) && !((Object)(object)_text.font == (Object)(object)f))
			{
				_text.font = f;
				Shader val = Shader.Find("Niko666/MugelloText");
				_depthMat = ((!((Object)(object)val != (Object)null) || !((Object)(object)f.material != (Object)null)) ? ((Material)null) : new Material(f.material));
				if ((Object)(object)_depthMat != (Object)null)
				{
					_depthMat.shader = val;
					_depthMat.renderQueue = 2450;
				}
				_overlayMat = f.material;
				MeshRenderer component = ((Component)this).GetComponent<MeshRenderer>();
				if ((Object)(object)component != (Object)null && (Object)(object)_overlayMat != (Object)null)
				{
					((Renderer)component).sharedMaterial = (OnTop ? _overlayMat : (((Object)(object)_depthMat != (Object)null) ? _depthMat : _overlayMat));
				}
				_fitQueued = 2;
			}
		}

		public void Toggle()
		{
			_visible = !_visible;
			((Component)this).gameObject.SetActive(_visible);
			RefreshAsciiFont();
			if (_visible && (Object)(object)_text != (Object)null)
			{
				ApplyFont(BoardFonts.For(_pending, _asciiFont));
				_text.text = _pending;
				PollConfig();
				_fitQueued = 2;
				Place();
			}
		}

		private void LateUpdate()
		{
			if (_visible)
			{
				Place();
				if (_fitQueued > 0 && --_fitQueued == 0)
				{
					FitBackground();
				}
				if (!(Time.unscaledTime < _nextCfgPoll))
				{
					_nextCfgPoll = Time.unscaledTime + 1f;
					PollConfig();
				}
			}
		}

		private void PollConfig()
		{
			ConfigEntry<float> cfgBoardScale = MugelloRacePlugin.CfgBoardScale;
			if (cfgBoardScale != null && (Object)(object)_text != (Object)null && Mathf.Abs(_text.characterSize - cfgBoardScale.Value) > 1E-05f)
			{
				_text.characterSize = cfgBoardScale.Value;
				_fitQueued = 2;
			}
			float num = ((MugelloRacePlugin.CfgBoardBgScaleX != null) ? MugelloRacePlugin.CfgBoardBgScaleX.Value : 1f);
			float num2 = ((MugelloRacePlugin.CfgBoardBgScaleY != null) ? MugelloRacePlugin.CfgBoardBgScaleY.Value : 1f);
			if (Mathf.Abs(num - _fitSx) > 0.001f || Mathf.Abs(num2 - _fitSy) > 0.001f)
			{
				_fitQueued = 2;
			}
			ConfigEntry<float> cfgBoardForward = MugelloRacePlugin.CfgBoardForward;
			ConfigEntry<float> cfgBoardRight = MugelloRacePlugin.CfgBoardRight;
			ConfigEntry<float> cfgBoardDown = MugelloRacePlugin.CfgBoardDown;
			if (cfgBoardForward != null)
			{
				_fwd = cfgBoardForward.Value;
			}
			if (cfgBoardRight != null)
			{
				_right = cfgBoardRight.Value;
			}
			if (cfgBoardDown != null)
			{
				_down = cfgBoardDown.Value;
			}
			ApplyBackground();
			ApplyDepthTest();
		}

		private void Place()
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: 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)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			FVRPlayerBody currentPlayerBody = GM.CurrentPlayerBody;
			Transform val;
			if ((Object)(object)currentPlayerBody != (Object)null && (Object)(object)currentPlayerBody.Head != (Object)null)
			{
				val = currentPlayerBody.Head;
			}
			else
			{
				Camera main = Camera.main;
				if ((Object)(object)main == (Object)null)
				{
					return;
				}
				val = ((Component)main).transform;
			}
			((Component)this).transform.position = val.position + val.forward * _fwd + val.right * _right - val.up * _down;
			((Component)this).transform.rotation = Quaternion.LookRotation(((Component)this).transform.position - val.position, val.up);
		}

		private void ApplyDepthTest()
		{
			MeshRenderer component = ((Component)this).GetComponent<MeshRenderer>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			Material val = ((OnTop || (MugelloRacePlugin.CfgBoardDepthTested != null && !MugelloRacePlugin.CfgBoardDepthTested.Value)) ? _overlayMat : (((Object)(object)_depthMat != (Object)null) ? _depthMat : _overlayMat));
			if ((Object)(object)val != (Object)null && (Object)(object)((Renderer)component).sharedMaterial != (Object)(object)val)
			{
				((Renderer)component).sharedMaterial = val;
				if (!_materialLogged)
				{
					_materialLogged = true;
					Debug.Log((object)("[MugelloRace] head board material: " + (((Object)(object)val.shader != (Object)null) ? ((Object)val.shader).name : "?") + " onTop=" + OnTop));
				}
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
	public class LapEntry
	{
		public string Name;

		public float Seconds;

		public long Ticks;

		public bool Local;
	}
	public class Leaderboard
	{
		private readonly string _path;

		private readonly List<LapEntry> _entries = new List<LapEntry>();

		public string Path_ => _path;

		public string FilePath => _path;

		public List<LapEntry> Entries => _entries;

		public Leaderboard(string dir, string trackId)
		{
			_path = Path.Combine(dir, (string.IsNullOrEmpty(trackId) || trackId == "mugello") ? "leaderboard.txt" : ("leaderboard-" + Safe(trackId) + ".txt"));
			Load();
		}

		private static string Safe(string s)
		{
			char[] array = s.ToCharArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (!char.IsLetterOrDigit(array[i]) && array[i] != '-' && array[i] != '_')
				{
					array[i] = '_';
				}
			}
			return new string(array);
		}

		private void Load()
		{
			try
			{
				if (!File.Exists(_path))
				{
					return;
				}
				string[] array = File.ReadAllLines(_path);
				foreach (string text in array)
				{
					if (!string.IsNullOrEmpty(text) && text[0] != '#')
					{
						string[] array2 = text.Split(new char[1] { '\t' });
						if (array2.Length >= 3 && float.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && long.TryParse(array2[2], out var result2))
						{
							_entries.Add(new LapEntry
							{
								Name = array2[0],
								Seconds = result,
								Ticks = result2
							});
						}
					}
				}
			}
			catch (Exception ex)
			{
				MugelloRacePlugin.Log.LogWarning((object)("leaderboard load failed: " + ex.Message));
			}
			Sort();
		}

		public void Save()
		{
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine("# name\tseconds\tutcTicks");
				foreach (LapEntry entry in _entries)
				{
					stringBuilder.AppendLine(entry.Name + "\t" + entry.Seconds.ToString("0.000", CultureInfo.InvariantCulture) + "\t" + entry.Ticks);
				}
				File.WriteAllText(_path, stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				MugelloRacePlugin.Log.LogWarning((object)("leaderboard save failed: " + ex.Message));
			}
		}

		public bool Merge(string name, float seconds, long ticks, bool local)
		{
			for (int i = 0; i < _entries.Count; i++)
			{
				if (_entries[i].Ticks == ticks && _entries[i].Name == name)
				{
					return false;
				}
			}
			_entries.Add(new LapEntry
			{
				Name = name,
				Seconds = seconds,
				Ticks = ticks,
				Local = local
			});
			Sort();
			int num = ((MugelloRacePlugin.CfgMaxEntries != null) ? MugelloRacePlugin.CfgMaxEntries.Value : 20);
			if (_entries.Count > num)
			{
				_entries.RemoveRange(num, _entries.Count - num);
			}
			return true;
		}

		private void Sort()
		{
			_entries.Sort((LapEntry a, LapEntry b) => a.Seconds.CompareTo(b.Seconds));
		}

		public float? PersonalBest(string name)
		{
			float? result = null;
			foreach (LapEntry entry in _entries)
			{
				if (entry.Name == name && (!result.HasValue || entry.Seconds < result.Value))
				{
					result = entry.Seconds;
				}
			}
			return result;
		}

		public static string Format(float seconds)
		{
			if (seconds < 0f)
			{
				return "--:--.--";
			}
			int num = (int)(seconds / 60f);
			float num2 = seconds - (float)num * 60f;
			return num.ToString("00") + ":" + num2.ToString("00.00", CultureInfo.InvariantCulture);
		}
	}
	[BepInPlugin("Niko666.RaceLeaderboard", "Race Leaderboard", "1.0.0")]
	[BepInProcess("h3vr.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class MugelloRacePlugin : BaseUnityPlugin
	{
		private static class WristMenuPatch
		{
			private const string ButtonText = "Leaderboard";

			[HarmonyPatch(typeof(FVRWristMenu2), "Awake")]
			[HarmonyPostfix]
			public static void AddButton(FVRWristMenu2 __instance)
			{
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_005d: Expected O, but got Unknown
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_0062: Expected O, but got Unknown
				try
				{
					ICollection<WristMenuButton> buttons = WristMenuAPI.CustomButtonsSection.Buttons;
					foreach (WristMenuButton item in buttons)
					{
						if (item != null && item.Text == "Leaderboard")
						{
							return;
						}
					}
					buttons.Add(new WristMenuButton("Leaderboard", new ButtonClickEvent(OnClick)));
				}
				catch (Exception ex)
				{
					Log.LogWarning((object)("wrist menu button failed: " + ex.Message));
				}
			}

			private static void OnClick(object sender, ButtonClickEventArgs args)
			{
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				RaceDirector.ToggleHeadBoard();
				try
				{
					FVRPlayerBody currentPlayerBody = GM.CurrentPlayerBody;
					if ((Object)(object)currentPlayerBody != (Object)null)
					{
						SM.PlayGlobalUISound((GlobalUISound)1, ((Component)currentPlayerBody).transform.position);
					}
				}
				catch
				{
				}
			}
		}

		public const string Guid = "Niko666.RaceLeaderboard";

		private const string LegacyGuid = "Niko666.MugelloRace";

		public static MugelloRacePlugin Instance;

		public static ManualLogSource Log;

		public static string PluginDir;

		public static string DataDir;

		public static ConfigEntry<bool> CfgEnabled;

		public static ConfigEntry<int> CfgMaxEntries;

		public static ConfigEntry<string> CfgName;

		public static ConfigEntry<bool> CfgDebugGates;

		public static ConfigEntry<float> CfgBoardRight;

		public static ConfigEntry<float> CfgBoardForward;

		public static ConfigEntry<float> CfgBoardDown;

		public static ConfigEntry<float> CfgBoardScale;

		public static ConfigEntry<bool> CfgBoardStartVisible;

		public static ConfigEntry<bool> CfgBoardDepthTested;

		public static ConfigEntry<float> CfgBoardBackground;

		public static ConfigEntry<float> CfgBoardBgScaleX;

		public static ConfigEntry<float> CfgBoardBgScaleY;

		public static ConfigEntry<bool> CfgBoardOnTop;

		public static ConfigEntry<bool> CfgCjkFallback;

		public static ConfigEntry<string> CfgCjkFont;

		public static ConfigEntry<bool> CfgAutoFitPanel;

		public static ConfigEntry<float> CfgPanelMargin;

		private static string _pendingScene;

		private static void SetupDataDir()
		{
			try
			{
				string text = Path.Combine(Paths.ConfigPath, "Niko666.RaceLeaderboard");
				Directory.CreateDirectory(text);
				DataDir = text;
				string text2 = Path.Combine(PluginDir, "leaderboard.txt");
				string text3 = Path.Combine(text, "leaderboard.txt");
				if (File.Exists(text2) && !File.Exists(text3))
				{
					File.Copy(text2, text3);
				}
				Log.LogInfo((object)("leaderboard data: " + text));
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("data dir failed, falling back to the plugin folder: " + ex.Message));
				DataDir = PluginDir;
			}
		}

		private static void MigrateLegacyConfig()
		{
			try
			{
				string configPath = Paths.ConfigPath;
				string text = Path.Combine(configPath, "Niko666.MugelloRace.cfg");
				string text2 = Path.Combine(configPath, "Niko666.RaceLeaderboard.cfg");
				if (File.Exists(text) && !File.Exists(text2))
				{
					File.Copy(text, text2);
					File.AppendAllText(text2, Environment.NewLine + "# migrated from Niko666.MugelloRace.cfg (plugin renamed, GUID unchanged in spirit)" + Environment.NewLine);
				}
			}
			catch
			{
			}
		}

		private void Awake()
		{
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0227: Expected O, but got Unknown
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Expected O, but got Unknown
			//IL_0299: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a3: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			MigrateLegacyConfig();
			try
			{
				PluginDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("plugin dir unresolved: " + ex.Message));
				PluginDir = ".";
			}
			SetupDataDir();
			CfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable lap timing on the Mugello map.");
			CfgMaxEntries = ((BaseUnityPlugin)this).Config.Bind<int>("Leaderboard", "MaxEntries", 20, "How many leaderboard rows to keep and show.");
			CfgName = ((BaseUnityPlugin)this).Config.Bind<string>("Race", "PlayerName", "Niko", "Name shown on the leaderboard.");
			CfgDebugGates = ((BaseUnityPlugin)this).Config.Bind<bool>("Race", "DebugGates", false, "Show the nearest gate and your offset from it on the board (for diagnosing checkpoints).");
			SceneManager.sceneLoaded += OnSceneLoaded;
			try
			{
				Harmony.CreateAndPatchAll(typeof(WristMenuPatch), (string)null);
			}
			catch (Exception ex2)
			{
				Log.LogWarning((object)("wrist menu patch failed (Sodalite missing?): " + ex2.Message));
			}
			CfgBoardRight = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "OffsetRight", 0.19f, "Head board offset to the player's right, in metres.");
			CfgBoardForward = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "OffsetForward", 0.62f, "Head board distance in front of the eyes, in metres.");
			CfgBoardDown = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "OffsetDown", 0.13f, "Head board offset below the eye line, in metres.");
			CfgBoardScale = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "CharacterSize", 0.0026f, "Text size of the head board (0.0026 = ~1.7 cm glyphs).");
			CfgBoardStartVisible = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "StartVisible", false, "Show the head board as soon as a race scene loads (otherwise use the wrist menu button).");
			CfgBoardDepthTested = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "DepthTested", true, "Only used when AlwaysOnTop = false: true = hidden by walls/terrain, false = drawn on top.");
			CfgBoardBackground = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "BackgroundOpacity", 0.55f, new ConfigDescription("Opacity of the dark plate behind the text (0 = no plate, 1 = solid black).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[0]));
			CfgBoardBgScaleX = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "BackgroundScaleX", 1f, new ConfigDescription("Backdrop width multiplier (1 = snug around the text).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 3f), new object[0]));
			CfgBoardBgScaleY = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "BackgroundScaleY", 1f, new ConfigDescription("Backdrop height multiplier (1 = snug around the text).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 3f), new object[0]));
			CfgAutoFitPanel = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "AutoFitBoardPanel", true, "Resize the in-map backing panel (MR_BOARD_BACK) to the rendered text at runtime. false = keep the panel exactly as authored in Unity.");
			CfgPanelMargin = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "BoardPanelMargin", 0.25f, "Margin in metres left around the text when AutoFitBoardPanel is on.");
			CfgCjkFallback = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "CJKFallback", true, "If the board text contains non-ASCII characters (Chinese etc.), render it with an OS font instead of the LED face - the LED font is ASCII only.");
			CfgCjkFont = ((BaseUnityPlugin)this).Config.Bind<string>("Display", "CJKFontName", "Microsoft YaHei UI", "OS font used for the CJK fallback (falls back to Microsoft YaHei / SimHei / SimSun / Segoe UI).");
			CfgBoardOnTop = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "AlwaysOnTop", true, "true = the board and its backdrop draw in front of walls and terrain, false = they get occluded.");
			Log.LogInfo((object)"Race leaderboard loaded, watching for race scenes.");
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			if (CfgEnabled.Value)
			{
				_pendingScene = ((Scene)(ref scene)).name;
				((MonoBehaviour)this).StartCoroutine(AttachWhenReady());
			}
		}

		private IEnumerator AttachWhenReady()
		{
			TrackProfile profile = TrackProfile.LoadForScene(_pendingScene);
			for (int i = 0; i < 120; i++)
			{
				if ((Object)(object)FindByName(profile.StartGate) != (Object)null)
				{
					if ((Object)(object)Object.FindObjectOfType<RaceDirector>() == (Object)null)
					{
						GameObject val = new GameObject("RaceDirector");
						Object.DontDestroyOnLoad((Object)val);
						val.AddComponent<RaceDirector>().Begin(profile);
					}
					break;
				}
				yield return (object)new WaitForSeconds(0.25f);
			}
		}

		public static GameObject FindByName(string name)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			Transform[] array = Resources.FindObjectsOfTypeAll<Transform>();
			foreach (Transform val in array)
			{
				if (!((Object)(object)val == (Object)null) && !(((Object)val).name != name))
				{
					Scene scene = ((Component)val).gameObject.scene;
					if (((Scene)(ref scene)).IsValid() && ((Component)val).gameObject.activeInHierarchy)
					{
						return ((Component)val).gameObject;
					}
				}
			}
			return null;
		}
	}
	public static class BoardFonts
	{
		private static Font _cjk;

		private static bool _tried;

		public static bool NeedsCjk(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] > '~')
				{
					return true;
				}
			}
			return false;
		}

		public static Font Cjk()
		{
			if (_tried)
			{
				return _cjk;
			}
			_tried = true;
			string text = ((MugelloRacePlugin.CfgCjkFont != null) ? MugelloRacePlugin.CfgCjkFont.Value : "Microsoft YaHei UI");
			string[] array = new string[6] { text, "Microsoft YaHei UI", "Microsoft YaHei", "SimHei", "SimSun", "Segoe UI" };
			try
			{
				_cjk = Font.CreateDynamicFontFromOSFont(array, 72);
				if (MugelloRacePlugin.Log != null)
				{
					MugelloRacePlugin.Log.LogInfo((object)("CJK fallback font: " + (((Object)(object)_cjk != (Object)null) ? ((Object)_cjk).name : "none found")));
				}
			}
			catch (Exception ex)
			{
				if (MugelloRacePlugin.Log != null)
				{
					MugelloRacePlugin.Log.LogWarning((object)("CJK font failed: " + ex.Message));
				}
			}
			return _cjk;
		}

		public static Font For(string text, Font asciiFont)
		{
			if ((MugelloRacePlugin.CfgCjkFallback == null || MugelloRacePlugin.CfgCjkFallback.Value) && NeedsCjk(text))
			{
				Font val = Cjk();
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return asciiFont;
		}
	}
	public class Gate
	{
		public Transform T;

		public string Label;

		public float HalfWidth = 8f;

		public bool WasInside;

		public float PrevFwd;

		public bool HasFwd;

		public bool Armed = true;
	}
	public class RaceDirector : MonoBehaviour
	{
		private readonly List<Gate> _cps = new List<Gate>();

		private Gate _start;

		private Gate _finish;

		private TextMesh _boardText;

		private Font _boardAsciiFont;

		private Shader _boardDepthShader;

		private Material _boardMat;

		private Transform _boardPanel;

		private int _panelFitAt;

		private Leaderboard _board;

		private TrackProfile _profile = new TrackProfile();

		private string _name;

		private int _nextCp;

		private bool _running;

		private bool _finished;

		private float _lapStart;

		private float _lastTime = -1f;

		private float _nextBoardRefresh;

		private string _status = "";

		public static RaceDirector Current;

		private const float GateCenterHeight = 1.5f;

		private const string ItemTextName = "LB_ITEM_TEXT";

		public TrackProfile Profile => _profile;

		public static void ToggleHeadBoard()
		{
			HeadBoard.Create().Toggle();
			if ((Object)(object)Current != (Object)null)
			{
				Current.RefreshBoards();
			}
		}

		public void Begin(TrackProfile profile)
		{
			Current = this;
			_profile = profile ?? new TrackProfile();
			GameObject val = MugelloRacePlugin.FindByName(_profile.StartGate);
			if ((Object)(object)val == (Object)null)
			{
				MugelloRacePlugin.Log.LogWarning((object)("start gate '" + _profile.StartGate + "' not found"));
				return;
			}
			_start = MakeGate(val, "START");
			GameObject val2 = MugelloRacePlugin.FindByName(_profile.EffectiveFinish);
			_finish = (((Object)(object)val2 == (Object)null || (Object)(object)val2 == (Object)(object)val) ? _start : MakeGate(val2, "FINISH"));
			for (int i = 0; i < _profile.MaxCheckpoints; i++)
			{
				GameObject val3 = MugelloRacePlugin.FindByName(_profile.CheckpointName(i));
				if ((Object)(object)val3 == (Object)null)
				{
					break;
				}
				_cps.Add(MakeGate(val3, "CP" + i));
			}
			_board = new Leaderboard(MugelloRacePlugin.DataDir ?? MugelloRacePlugin.PluginDir, _profile.Id);
			_name = MugelloRacePlugin.CfgName.Value;
			BindBoard();
			HeadBoard headBoard = HeadBoard.Create();
			if (MugelloRacePlugin.CfgBoardStartVisible.Value && !headBoard.Visible)
			{
				headBoard.Toggle();
			}
			MugelloRacePlugin.Log.LogInfo((object)("race ready: " + _profile.DisplayName + " " + _profile.Mode.ToString() + ", " + _cps.Count + " checkpoints, board " + _board.FilePath));
			H3MPSync.Attach(_board, RefreshBoards);
		}

		private Gate MakeGate(GameObject go, string label)
		{
			return new Gate
			{
				T = go.transform,
				Label = label,
				HalfWidth = HalfWidthOf(go.transform),
				Armed = true
			};
		}

		private void BindBoard()
		{
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Expected O, but got Unknown
			if (string.IsNullOrEmpty(_profile.BoardText))
			{
				return;
			}
			GameObject val = MugelloRacePlugin.FindByName(_profile.BoardText);
			if ((Object)(object)val == (Object)null)
			{
				MugelloRacePlugin.Log.LogInfo((object)("no in-map board '" + _profile.BoardText + "', portable only."));
				return;
			}
			_boardText = val.GetComponent<TextMesh>();
			if ((Object)(object)_boardText == (Object)null)
			{
				return;
			}
			GameObject val2 = MugelloRacePlugin.FindByName("MR_BOARD_BACK");
			_boardPanel = (((Object)(object)val2 != (Object)null) ? val2.transform : null);
			Font val3 = (((Object)(object)_boardText.font != (Object)null) ? _boardText.font : Resources.GetBuiltinResource<Font>("Arial.ttf"));
			if ((Object)(object)val3 == (Object)null)
			{
				return;
			}
			_boardText.font = val3;
			_boardAsciiFont = val3;
			MeshRenderer component = ((Component)_boardText).GetComponent<MeshRenderer>();
			Shader val4 = null;
			if ((Object)(object)component != (Object)null && (Object)(object)((Renderer)component).sharedMaterial != (Object)null)
			{
				val4 = ((Renderer)component).sharedMaterial.shader;
			}
			if ((Object)(object)val4 == (Object)null || ((Object)val4).name == "Hidden/InternalErrorShader")
			{
				val4 = Shader.Find("Niko666/MugelloText");
			}
			if ((Object)(object)val4 != (Object)null && ((Object)val4).name == "Hidden/InternalErrorShader")
			{
				val4 = null;
			}
			if ((Object)(object)val4 != (Object)null && (Object)(object)val3.material != (Object)null)
			{
				_boardDepthShader = val4;
				_boardMat = new Material(val3.material);
				((Object)_boardMat).name = "MugelloBoardFont_Runtime";
				_boardMat.shader = val4;
				if ((Object)(object)component != (Object)null)
				{
					((Renderer)component).sharedMaterial = _boardMat;
				}
				MugelloRacePlugin.Log.LogInfo((object)("board shader: " + ((Object)val4).name));
			}
			else
			{
				MugelloRacePlugin.Log.LogWarning((object)"depth-tested text shader missing - board may draw through walls.");
			}
			RefreshBoards();
		}

		private void FitPanel()
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_boardText == (Object)null || (Object)(object)_boardPanel == (Object)null)
			{
				return;
			}
			ConfigEntry<bool> cfgAutoFitPanel = MugelloRacePlugin.CfgAutoFitPanel;
			if (cfgAutoFitPanel != null && !cfgAutoFitPanel.Value)
			{
				return;
			}
			MeshRenderer component = ((Component)_boardText).GetComponent<MeshRenderer>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			Bounds bounds = ((Renderer)component).bounds;
			if (((Bounds)(ref bounds)).size.y < 0.01f)
			{
				return;
			}
			float num = Mathf.Sqrt(((Bounds)(ref bounds)).size.x * ((Bounds)(ref bounds)).size.x + ((Bounds)(ref bounds)).size.z * ((Bounds)(ref bounds)).size.z);
			if (!(num < 0.01f))
			{
				Transform val = (((Object)(object)_boardPanel.parent != (Object)null) ? _boardPanel.parent : _boardPanel);
				Vector3 lossyScale = val.lossyScale;
				if (!(Mathf.Abs(lossyScale.x) < 0.0001f) && !(Mathf.Abs(lossyScale.y) < 0.0001f))
				{
					float num2 = ((MugelloRacePlugin.CfgPanelMargin != null) ? MugelloRacePlugin.CfgPanelMargin.Value : 0.25f);
					_boardPanel.localScale = new Vector3((num + num2 * 2f) / Mathf.Abs(lossyScale.x), (((Bounds)(ref bounds)).size.y + num2 * 2f) / Mathf.Abs(lossyScale.y), _boardPanel.localScale.z);
					Vector3 val2 = val.InverseTransformPoint(((Bounds)(ref bounds)).center);
					_boardPanel.localPosition = new Vector3(val2.x, val2.y, _boardPanel.localPosition.z);
				}
			}
		}

		private void ApplyBoardFont(string text)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			if ((Object)(object)_boardText == (Object)null)
			{
				return;
			}
			Font val = BoardFonts.For(text, _boardAsciiFont);
			if ((Object)(object)val == (Object)null || (Object)(object)_boardText.font == (Object)(object)val)
			{
				return;
			}
			_boardText.font = val;
			MeshRenderer component = ((Component)_boardText).GetComponent<MeshRenderer>();
			if ((Object)(object)component != (Object)null && (Object)(object)val.material != (Object)null)
			{
				_boardMat = new Material(val.material);
				((Object)_boardMat).name = "MugelloBoardFont_Runtime";
				if ((Object)(object)_boardDepthShader != (Object)null)
				{
					_boardMat.shader = _boardDepthShader;
				}
				((Renderer)component).sharedMaterial = _boardMat;
			}
		}

		private void Update()
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				H3MPSync.Poll();
				if (_panelFitAt > 0 && Time.frameCount >= _panelFitAt)
				{
					_panelFitAt = 0;
					try
					{
						FitPanel();
					}
					catch (Exception ex)
					{
						MugelloRacePlugin.Log.LogWarning((object)("panel fit: " + ex.Message));
					}
				}
				if (_start == null || (Object)(object)_start.T == (Object)null)
				{
					Object.Destroy((Object)(object)((Component)this).gameObject);
					return;
				}
				FVRPlayerBody currentPlayerBody = GM.CurrentPlayerBody;
				if ((Object)(object)currentPlayerBody == (Object)null || (Object)(object)currentPlayerBody.Head == (Object)null)
				{
					return;
				}
				Vector3 position = currentPlayerBody.Head.position;
				if (Crossed(_start, position))
				{
					OnStartCrossed();
				}
				if (_finish != _start && Crossed(_finish, position))
				{
					OnFinishCrossed();
				}
				for (int i = 0; i < _cps.Count; i++)
				{
					if (Crossed(_cps[i], position))
					{
						OnCheckpoint(i);
					}
				}
				if (Time.unscaledTime >= _nextBoardRefresh)
				{
					_nextBoardRefresh = Time.unscaledTime + 0.2f;
					RefreshBoards();
				}
			}
			catch (Exception ex2)
			{
				MugelloRacePlugin.Log.LogError((object)("race update: " + ex2));
				((Behaviour)this).enabled = false;
			}
		}

		private static float HalfWidthOf(Transform t)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Abs(t.lossyScale.x) * 0.5f;
			if (!(num > 1f))
			{
				return 12f;
			}
			return num;
		}

		private bool Crossed(Gate g, Vector3 head)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: 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_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			if (g == null || (Object)(object)g.T == (Object)null)
			{
				return false;
			}
			Vector3 val = head - g.T.position;
			float num = Mathf.Abs(Vector3.Dot(val, g.T.right));
			float num2 = Mathf.Abs(val.y - 1.5f);
			float num3 = Vector3.Dot(val, g.T.forward);
			bool flag = num <= g.HalfWidth && num2 <= 3f && Mathf.Abs(num3) <= 1.6f;
			bool flag2 = g.Armed && g.HasFwd && g.PrevFwd < 0f && num3 >= 0f && num <= g.HalfWidth && num2 <= 3f;
			bool num4 = (flag && !g.WasInside) || flag2;
			if (num4)
			{
				g.Armed = false;
			}
			else if (num3 < -0.3f)
			{
				g.Armed = true;
			}
			g.WasInside = flag;
			g.PrevFwd = num3;
			g.HasFwd = true;
			return num4;
		}

		private void OnStartCrossed()
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!_running)
			{
				StartRun(realtimeSinceStartup, (_profile.IsSprint ? "Run started - " : "Lap started - ") + _cps.Count + " checkpoints");
				return;
			}
			if (_finish == _start)
			{
				if (_nextCp >= _cps.Count)
				{
					Record(realtimeSinceStartup);
				}
				else
				{
					StartRun(realtimeSinceStartup, "Restarted: " + _nextCp + "/" + _cps.Count);
				}
			}
			else
			{
				StartRun(realtimeSinceStartup, "Restarted: " + _nextCp + "/" + _cps.Count);
			}
			RefreshBoards();
		}

		private void OnFinishCrossed()
		{
			if (_running)
			{
				if (_nextCp < _cps.Count)
				{
					_status = "Finish ignored: " + _nextCp + "/" + _cps.Count + " checkpoints";
					RefreshBoards();
				}
				else
				{
					Record(Time.realtimeSinceStartup);
					RefreshBoards();
				}
			}
		}

		private void Record(float now)
		{
			float seconds = (_lastTime = now - _lapStart);
			long ticks = DateTime.UtcNow.Ticks;
			if (_board.Merge(_name, seconds, ticks, local: true))
			{
				_board.Save();
				H3MPSync.SendLap(_name, seconds, ticks);
				_status = _profile.DisplayName + " " + Leaderboard.Format(seconds) + " recorded";
			}
			else
			{
				_status = "duplicate ignored";
			}
			if (_profile.IsSprint)
			{
				_running = false;
				_finished = true;
			}
			else
			{
				StartRun(now, _status);
			}
		}

		private void StartRun(float now, string status)
		{
			_running = true;
			_finished = false;
			_lapStart = now;
			_nextCp = 0;
			_status = status;
		}

		private void OnCheckpoint(int index)
		{
			if (_running && index == _nextCp)
			{
				_nextCp++;
				MugelloRacePlugin.Log.LogInfo((object)("checkpoint " + index + " ok (" + _nextCp + "/" + _cps.Count + ")"));
				if (_nextCp == _cps.Count)
				{
					_status = "All checkpoints - cross the finish!";
				}
			}
		}

		private static string Fit(string line, int width)
		{
			if (line.Length > width)
			{
				return line.Substring(0, width - 3) + "...";
			}
			return line;
		}

		public void RefreshBoards()
		{
			if ((Object)(object)_boardText != (Object)null)
			{
				string text = ComposeBoard();
				ApplyBoardFont(text);
				_boardText.text = text;
				if ((Object)(object)_boardMat != (Object)null && (Object)(object)_boardText.font != (Object)null && (Object)(object)_boardText.font.material != (Object)null)
				{
					_boardMat.SetTexture("_MainTex", _boardText.font.material.mainTexture);
				}
				_panelFitAt = Time.frameCount + 2;
			}
			HeadBoard instance = HeadBoard.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.SetText(ComposePortable());
			}
			RefreshHandheldItems();
		}

		private void RefreshHandheldItems()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			string text = null;
			TextMesh[] array = Resources.FindObjectsOfTypeAll<TextMesh>();
			Scene scene;
			foreach (TextMesh val in array)
			{
				if ((Object)(object)val == (Object)null || ((Object)val).name != "LB_ITEM_TEXT")
				{
					continue;
				}
				scene = ((Component)val).gameObject.scene;
				if (((Scene)(ref scene)).IsValid())
				{
					if (text == null)
					{
						text = ComposePortable();
					}
					val.text = text;
				}
			}
			Text[] array2 = Resources.FindObjectsOfTypeAll<Text>();
			foreach (Text val2 in array2)
			{
				if ((Object)(object)val2 == (Object)null || ((Object)val2).name != "LB_ITEM_TEXT")
				{
					continue;
				}
				scene = ((Component)val2).gameObject.scene;
				if (((Scene)(ref scene)).IsValid())
				{
					if (text == null)
					{
						text = ComposePortable();
					}
					val2.text = text;
				}
			}
		}

		private string ModeTitle()
		{
			return _profile.DisplayName + (_profile.IsSprint ? "  -  SPRINT" : "  -  LAP BOARD");
		}

		private string ComposeBoard()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(ModeTitle());
			stringBuilder.AppendLine();
			int num = 0;
			List<LapEntry> list = ((_board != null) ? _board.Entries : new List<LapEntry>());
			int num2 = 0;
			while (num2 < list.Count && num < 10)
			{
				stringBuilder.AppendLine(Fit((num2 + 1).ToString().PadLeft(2) + "  " + Leaderboard.Format(list[num2].Seconds) + "  " + list[num2].Name, 34));
				num2++;
				num++;
			}
			if (num == 0)
			{
				stringBuilder.AppendLine("   no times recorded yet");
			}
			stringBuilder.AppendLine();
			float? num3 = ((_board != null) ? _board.PersonalBest(_name) : ((float?)null));
			stringBuilder.AppendLine(Fit("YOU  " + _name + "   BEST " + (num3.HasValue ? Leaderboard.Format(num3.Value) : "--:--.--"), 34));
			if (_running)
			{
				stringBuilder.AppendLine((_profile.IsSprint ? "RUN " : "LAP ") + Leaderboard.Format(Time.realtimeSinceStartup - _lapStart) + "   CP " + _nextCp + "/" + _cps.Count);
			}
			else if (_finished)
			{
				stringBuilder.AppendLine("finished " + Leaderboard.Format(_lastTime) + " - cross the start again");
			}
			else
			{
				stringBuilder.AppendLine("cross the start gate to begin");
			}
			if (!string.IsNullOrEmpty(_status))
			{
				stringBuilder.AppendLine(Fit(_status, 40));
			}
			if (H3MPSync.Active)
			{
				stringBuilder.AppendLine("H3MP: per-player timing on");
			}
			if (MugelloRacePlugin.CfgDebugGates.Value)
			{
				stringBuilder.AppendLine(DebugLine());
			}
			return stringBuilder.ToString();
		}

		private string ComposePortable()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(_profile.DisplayName + (_profile.IsSprint ? " SPRINT" : " LAP"));
			int num = 0;
			List<LapEntry> list = ((_board != null) ? _board.Entries : new List<LapEntry>());
			int num2 = 0;
			while (num2 < list.Count && num < 5)
			{
				stringBuilder.AppendLine(num2 + 1 + " " + Leaderboard.Format(list[num2].Seconds) + " " + Fit(list[num2].Name, 8));
				num2++;
				num++;
			}
			if (num == 0)
			{
				stringBuilder.AppendLine("no times yet");
			}
			if (_running)
			{
				stringBuilder.AppendLine("CP " + _nextCp + "/" + _cps.Count + "  " + Leaderboard.Format(Time.realtimeSinceStartup - _lapStart));
			}
			else if (_finished)
			{
				stringBuilder.AppendLine("done " + Leaderboard.Format(_lastTime));
			}
			else
			{
				stringBuilder.AppendLine("cross start");
			}
			return stringBuilder.ToString();
		}

		private void OnDestroy()
		{
			if ((Object)(object)Current == (Object)(object)this)
			{
				Current = null;
			}
		}

		private string DebugLine()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			if (_cps.Count == 0)
			{
				return "dbg no checkpoints";
			}
			FVRPlayerBody currentPlayerBody = GM.CurrentPlayerBody;
			if ((Object)(object)currentPlayerBody == (Object)null || (Object)(object)currentPlayerBody.Head == (Object)null)
			{
				return "dbg no player";
			}
			Vector3 position = currentPlayerBody.Head.position;
			int num = -1;
			float num2 = float.MaxValue;
			float num3 = 0f;
			float num4 = 0f;
			float num5 = 0f;
			for (int i = 0; i < _cps.Count; i++)
			{
				Transform t = _cps[i].T;
				if (!((Object)(object)t == (Object)null))
				{
					Vector3 val = position - t.position;
					float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
					if (!(sqrMagnitude >= num2))
					{
						num2 = sqrMagnitude;
						num = i;
						num3 = Vector3.Dot(val, t.right);
						num4 = val.y - 1.5f;
						num5 = Vector3.Dot(val, t.forward);
					}
				}
			}
			if (num < 0)
			{
				return "dbg no gate";
			}
			return "dbg CP" + num + " lat" + num3.ToString("0.0") + " up" + num4.ToString("0.0") + " fwd" + num5.ToString("0.0") + " hw" + _cps[num].HalfWidth.ToString("0.0");
		}
	}
	public enum RaceMode
	{
		Circuit,
		Sprint
	}
	public class TrackProfile
	{
		public string Id = "mugello";

		public string DisplayName = "MUGELLO";

		public RaceMode Mode;

		public string StartGate = "MR_START";

		public string FinishGate = "";

		public string CheckpointFormat = "MR_CP_{0:00}";

		public string BoardText = "MR_BOARD_TEXT";

		public string PortableAnchor = "";

		public int MaxCheckpoints = 200;

		public bool PortableBoard = true;

		public bool FromFile;

		public string EffectiveFinish
		{
			get
			{
				if (!string.IsNullOrEmpty(FinishGate))
				{
					return FinishGate;
				}
				return StartGate;
			}
		}

		public bool IsSprint => Mode == RaceMode.Sprint;

		public static string TracksDir => Path.Combine(MugelloRacePlugin.PluginDir, "tracks");

		public string CheckpointName(int index)
		{
			try
			{
				return string.Format(CultureInfo.InvariantCulture, CheckpointFormat, new object[1] { index });
			}
			catch
			{
				return "CP" + index;
			}
		}

		public static List<string> SearchDirs()
		{
			List<string> dirs = new List<string>();
			Add(TracksDir);
			try
			{
				string directoryName = Path.GetDirectoryName(MugelloRacePlugin.PluginDir);
				if (!string.IsNullOrEmpty(directoryName) && Directory.Exists(directoryName))
				{
					string[] directories = Directory.GetDirectories(directoryName);
					foreach (string obj in directories)
					{
						Add(Path.Combine(obj, "tracks"));
						Add(obj);
					}
				}
			}
			catch (Exception ex)
			{
				MugelloRacePlugin.Log.LogWarning((object)("track search failed: " + ex.Message));
			}
			return dirs;
			void Add(string d)
			{
				if (!string.IsNullOrEmpty(d) && Directory.Exists(d) && !dirs.Contains(d))
				{
					dirs.Add(d);
				}
			}
		}

		public static TrackProfile LoadForScene(string sceneName)
		{
			TrackProfile trackProfile = Auto();
			List<string> list = new List<string>();
			if (!string.IsNullOrEmpty(sceneName))
			{
				foreach (string item in SearchDirs())
				{
					list.Add(Path.Combine(item, sceneName + ".json"));
				}
			}
			list.Add(Path.Combine(TracksDir, "default.json"));
			foreach (string item2 in list)
			{
				if (File.Exists(item2))
				{
					try
					{
						Parse(File.ReadAllText(item2), trackProfile);
						trackProfile.FromFile = true;
						MugelloRacePlugin.Log.LogInfo((object)("track profile: " + item2));
						return trackProfile;
					}
					catch (Exception ex)
					{
						MugelloRacePlugin.Log.LogWarning((object)("track profile " + item2 + " failed: " + ex.Message));
					}
				}
			}
			MugelloRacePlugin.Log.LogInfo((object)("track profile: built-in defaults for '" + sceneName + "'"));
			return trackProfile;
		}

		private static TrackProfile Auto()
		{
			return new TrackProfile();
		}

		private static void Parse(string text, TrackProfile p)
		{
			int num = 0;
			while (num < text.Length)
			{
				int num2 = text.IndexOf('"', num);
				if (num2 < 0)
				{
					break;
				}
				int num3 = text.IndexOf('"', num2 + 1);
				if (num3 < 0)
				{
					break;
				}
				string key = text.Substring(num2 + 1, num3 - num2 - 1);
				int num4 = text.IndexOf(':', num3 + 1);
				if (num4 < 0)
				{
					break;
				}
				int i;
				for (i = num4 + 1; i < text.Length && char.IsWhiteSpace(text[i]); i++)
				{
				}
				string value;
				if (i < text.Length && text[i] == '"')
				{
					int num5 = text.IndexOf('"', i + 1);
					if (num5 < 0)
					{
						break;
					}
					value = text.Substring(i + 1, num5 - i - 1);
					num = num5 + 1;
				}
				else
				{
					int j;
					for (j = i; j < text.Length && text[j] != ',' && text[j] != '}' && text[j] != '\n'; j++)
					{
					}
					value = text.Substring(i, j - i).Trim();
					num = j;
				}
				Apply(key, value, p);
			}
		}

		private static void Apply(string key, string value, TrackProfile p)
		{
			if (key == null)
			{
				return;
			}
			switch (key.Length)
			{
			case 9:
				switch (key[0])
				{
				case 's':
					if (key == "startGate")
					{
						p.StartGate = value;
					}
					break;
				case 'b':
					if (key == "boardText")
					{
						p.BoardText = value;
					}
					break;
				}
				break;
			case 14:
				switch (key[0])
				{
				case 'p':
					if (key == "portableAnchor")
					{
						p.PortableAnchor = value;
					}
					break;
				case 'm':
					if (key == "maxCheckpoints")
					{
						p.MaxCheckpoints = ParseInt(value, p.MaxCheckpoints);
					}
					break;
				}
				break;
			case 2:
				if (key == "id")
				{
					p.Id = value;
				}
				break;
			case 11:
				if (key == "displayName")
				{
					p.DisplayName = value;
				}
				break;
			case 4:
				if (key == "mode")
				{
					p.Mode = (string.Equals(value, "sprint", StringComparison.OrdinalIgnoreCase) ? RaceMode.Sprint : RaceMode.Circuit);
				}
				break;
			case 10:
				if (key == "finishGate")
				{
					p.FinishGate = value;
				}
				break;
			case 16:
				if (key == "checkpointFormat")
				{
					p.CheckpointFormat = value;
				}
				break;
			case 13:
				if (key == "portableBoard")
				{
					p.PortableBoard = value == "true" || value == "1";
				}
				break;
			case 3:
			case 5:
			case 6:
			case 7:
			case 8:
			case 12:
			case 15:
				break;
			}
		}

		private static int ParseInt(string s, int fallback)
		{
			if (!int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				return fallback;
			}
			return result;
		}

		private static float ParseFloat(string s, float fallback)
		{
			if (!float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return fallback;
			}
			return result;
		}

		public void WriteTemplate(string path)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("{");
			stringBuilder.AppendLine("  \"id\": \"" + Id + "\",");
			stringBuilder.AppendLine("  \"displayName\": \"" + DisplayName + "\",");
			stringBuilder.AppendLine("  \"mode\": \"" + (IsSprint ? "sprint" : "circuit") + "\",");
			stringBuilder.AppendLine("  \"startGate\": \"" + StartGate + "\",");
			stringBuilder.AppendLine("  \"finishGate\": \"" + FinishGate + "\",");
			stringBuilder.AppendLine("  \"checkpointFormat\": \"" + CheckpointFormat + "\",");
			stringBuilder.AppendLine("  \"boardText\": \"" + BoardText + "\",");
			stringBuilder.AppendLine("  \"portableAnchor\": \"" + PortableAnchor + "\",");
			stringBuilder.AppendLine("  \"portableBoard\": " + (PortableBoard ? "true" : "false"));
			stringBuilder.AppendLine("}");
			File.WriteAllText(path, stringBuilder.ToString());
		}
	}
}