Decompiled source of DropinMultiplayer v1.0.3

DropInMultiplayer.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
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 DropInMultiplayerPlugin.Helpers;
using Microsoft.CodeAnalysis;
using On.RoR2;
using R2API.Utils;
using RiskOfOptions;
using RiskOfOptions.Options;
using RoR2;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("DropInMultiplayer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("DropInMultiplayer")]
[assembly: AssemblyTitle("DropInMultiplayer")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DropInMultiplayerPlugin
{
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("MengHu.DropInMultiplayerPlugin", "DropInMultiplayerPlugin", "1.0.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class DropInMultiplayerPlugin : BaseUnityPlugin
	{
		public const string PluginGUID = "MengHu.DropInMultiplayerPlugin";

		public const string PluginAuthor = "MengHu";

		public const string PluginName = "DropInMultiplayerPlugin";

		public const string PluginVersion = "1.0.3";

		public static List<IBasePlugin> basePlugins = new List<IBasePlugin>();

		public static byte[] logo;

		public static Sprite logo2;

		public static DropInMultiplayerPlugin Instance { get; set; }

		public static ConfigFile PluginConfig { get; set; }

		public void Awake()
		{
			Instance = this;
			Console.OutputEncoding = Encoding.UTF8;
			Log.Init(((BaseUnityPlugin)this).Logger);
			PluginConfig = ((BaseUnityPlugin)this).Config;
			ModSettingsManager.SetModDescription("Describe your mod in incredible detail over the course of the next 2 hours");
			SetLogo();
			DropInMultiplayer item = new DropInMultiplayer();
			basePlugins.Add(item);
			InitPlugins();
		}

		private void InitPlugins()
		{
			foreach (IBasePlugin basePlugin in basePlugins)
			{
				basePlugin.Init(PluginConfig);
				Log.Info(basePlugin.GetType().Name + " initialized");
			}
		}

		public void OnEnable()
		{
			foreach (IBasePlugin basePlugin in basePlugins)
			{
				basePlugin.OnEnable();
			}
		}

		public void OnDisable()
		{
			foreach (IBasePlugin basePlugin in basePlugins)
			{
				basePlugin.OnDisable();
			}
		}

		private void Start()
		{
			foreach (IBasePlugin basePlugin in basePlugins)
			{
				basePlugin.Start();
			}
		}

		private void Update()
		{
			foreach (IBasePlugin basePlugin in basePlugins)
			{
				basePlugin.Update();
			}
		}

		public void OnDestroy()
		{
			foreach (IBasePlugin basePlugin in basePlugins)
			{
				basePlugin.OnDestroy();
			}
		}

		private void SetLogo()
		{
			//IL_0090: 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_00bf: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string path = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "icon");
				int num = Random.Range(0, Directory.GetFiles(path).Length);
				int num2 = 0;
				string[] files = Directory.GetFiles(path);
				foreach (string path2 in files)
				{
					if (num2 == num)
					{
						logo = File.ReadAllBytes(path2);
					}
					num2++;
				}
				Texture2D val = LoadTexture(logo, 256, 256);
				logo2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0f, 0f), 100f, 1u, (SpriteMeshType)1, new Vector4(0f, 0f, 0f, 0f), true);
				ModSettingsManager.SetModIcon(logo2);
			}
			catch (Exception)
			{
			}
		}

		public static Texture2D LoadTexture(byte[] bytes, int width, int height)
		{
			//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_0011: Expected O, but got Unknown
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			Texture2D val = new Texture2D(width, height, (TextureFormat)5, false, false);
			ImageConversion.LoadImage(val, bytes);
			((Texture)val).filterMode = (FilterMode)0;
			return val;
		}

		private Texture2D LoadTexture(string name)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_002a: Expected O, but got Unknown
			Texture2D val = new Texture2D(2, 2);
			ImageConversion.LoadImage(val, File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), name)));
			return val;
		}

		private Sprite LoadTexture()
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Expected O, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			string path = "icon.png";
			Texture2D val = new Texture2D(2, 2);
			ImageConversion.LoadImage(val, File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), path)));
			return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0f, 0f));
		}
	}
	internal enum JoinAsResult
	{
		Success,
		DeadAndNotAllowRespawn
	}
	internal class ChatCommand
	{
		public string Name { get; set; }

		public string HelpText { get; set; }

		public Func<NetworkUser, string[], string> Handler { get; set; }

		internal ChatCommand(string name, string helpText, Func<NetworkUser, string[], string> handler)
		{
			Name = name;
			HelpText = helpText;
			Handler = handler;
		}
	}
	public class DropInMultiplayer : IBasePlugin
	{
		[CompilerGenerated]
		private sealed class <SendChatMessageInternal>d__43 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public string message;

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

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

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

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

			private bool MoveNext()
			{
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0027: Expected O, but got Unknown
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				//IL_003c: Unknown result type (might be due to invalid IL or missing references)
				//IL_004d: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(0.1f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage
					{
						baseToken = message
					});
					return false;
				}
			}

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

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

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

			private object <>2__current;

			public NetworkUser player;

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

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

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

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

			private bool MoveNext()
			{
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0027: Expected O, but got Unknown
				//IL_0052: Unknown result type (might be due to invalid IL or missing references)
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(0.1f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					SpawnPlayerWithBody(player, BodyCatalog.FindBodyIndex(GetAvailibleSurvivorForPlayerByName(player, "random").bodyPrefab));
					return false;
				}
			}

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

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

		private const string HelpHelpText = "Usage: help {command}\nDescription: Prints help text for command";

		private const string JoinAsHelpText = "Usage: join_as {survivor} {player (optional)}\nDescription: Join in-progress run as the given survivor";

		private const string ListSurvivorsHelpText = "Usage: list_survivors {player (optional)}\nDescription: Shows a list of all availible survivors for given player (or self)";

		private const string ListBodiesHelpText = "Usage: list_bodies\nDescription: Shows a list of all bodies to be used with join_as command (if AllowJoinAsAllBodies is true)";

		private const string CatchupHelpText = "Usage: catchup {player (optional)}\nDescription: Manually adds catch up items for the given player (or self)";

		private const string GiveRandomItemsHelpText = "Usage: give_random_items {count} {lunarEnabled} {voidEnabled} {player (optional)}\nDescription: Adds random items for the given player (or self)";

		private static readonly Random _rand = new Random();

		private static readonly Dictionary<string, ChatCommand> _chatCommands = new List<ChatCommand>
		{
			new ChatCommand("HELP", "Usage: help {command}\nDescription: Prints help text for command", Help),
			new ChatCommand("JOIN", "Usage: join_as {survivor} {player (optional)}\nDescription: Join in-progress run as the given survivor", JoinAs),
			new ChatCommand("JOIN_AS", "Usage: join_as {survivor} {player (optional)}\nDescription: Join in-progress run as the given survivor", JoinAs),
			new ChatCommand("LIST_SURVIVORS", "Usage: list_survivors {player (optional)}\nDescription: Shows a list of all availible survivors for given player (or self)", ListSurvivors),
			new ChatCommand("LIST_BODIES", "Usage: list_bodies\nDescription: Shows a list of all bodies to be used with join_as command (if AllowJoinAsAllBodies is true)", ListBodies)
		}.ToDictionary((ChatCommand rec) => rec.Name);

		private static HashSet<Inventory> captainBlacklistInventories;

		public static DropInMultiplayerConfig DropInConfig { get; set; }

		public void Init(ConfigFile config)
		{
			DropInConfig = new DropInMultiplayerConfig(config);
			SetupEventHandlers();
		}

		private void SetupEventHandlers()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			Run.onRunStartGlobal += Run_onRunStartGlobal;
			Console.RunCmd += new hook_RunCmd(Console_RunCmd);
			Run.SetupUserCharacterMaster += new hook_SetupUserCharacterMaster(Run_SetupUserCharacterMaster);
			NetworkUser.onPostNetworkUserStart += new NetworkUserGenericDelegate(NetworkUser_onPostNetworkUserStart);
		}

		private void Run_onRunStartGlobal(Run obj)
		{
			captainBlacklistInventories = new HashSet<Inventory>();
		}

		private void Run_SetupUserCharacterMaster(orig_SetupUserCharacterMaster orig, Run self, NetworkUser user)
		{
			try
			{
				orig.Invoke(self, user);
			}
			catch (Exception ex)
			{
				Debug.LogException(ex);
				Log.Error(ex);
				Log.Message("SetupUserCharacterMaster threw an exception and was caught");
			}
		}

		private void Console_RunCmd(orig_RunCmd orig, Console self, CmdSender sender, string concommandName, List<string> userArgs)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			orig.Invoke(self, sender, concommandName, userArgs);
			if (!NetworkServer.active || (Object)(object)Run.instance == (Object)null || !concommandName.Equals("say", StringComparison.InvariantCultureIgnoreCase))
			{
				return;
			}
			string text = userArgs.FirstOrDefault();
			if (!string.IsNullOrWhiteSpace(text) && text.StartsWith("/"))
			{
				string[] source = text.Split(new char[1] { ' ' });
				string text2 = source.FirstOrDefault().Substring(1);
				string[] arg = source.Skip(1).ToArray();
				if (!_chatCommands.TryGetValue(text2.ToUpperInvariant(), out var value))
				{
					SendChatMessage("Unable to find command, try /help");
				}
				string text3 = value.Handler(sender.networkUser, arg);
				if (!string.IsNullOrWhiteSpace(text3))
				{
					SendChatMessage(text3);
				}
			}
		}

		private void NetworkUser_onPostNetworkUserStart(NetworkUser networkUser)
		{
			if (NetworkServer.active && (Object)(object)Run.instance != (Object)null)
			{
				bool flag = (Object)(object)networkUser.master != (Object)null;
				Log.Message(networkUser.userName + " has joined " + (flag ? "as a previously connected player" : "as a new player"));
				GreetNewPlayer(networkUser);
				if (DropInConfig.JoinAsRandomByDefault.Value && !flag)
				{
					Log.Message("Spawning new player as random, since JoinAsRandomByDefault is true");
					((MonoBehaviour)DropInMultiplayerPlugin.Instance).StartCoroutine(SpawnPlayerAsRandomInternal(networkUser));
				}
				if (DropInConfig.GiveCatchUpItems.Value && DropInConfig.GiveRejoiningPlayersCatchUpItems.Value && flag)
				{
					GiveCatchUpItems(networkUser);
				}
			}
		}

		private static JoinAsResult SpawnPlayerWithBody(NetworkUser player, BodyIndex newBodyIndex)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player.master == (Object)null)
			{
				Log.Message($"Spawning new player {player.userName} with bodyIndex = {newBodyIndex}");
				return SpawnNewPlayerWithBody(player, newBodyIndex);
			}
			Log.Message($"Respawning existing player {player.userName} with bodyIndex = {newBodyIndex}");
			return RespawnExistingPlayerWithBody(player, newBodyIndex);
		}

		private static JoinAsResult SpawnNewPlayerWithBody(NetworkUser player, BodyIndex newBodyIndex)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: 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)
			player.CmdSetBodyPreference(newBodyIndex);
			Reflection.SetFieldValue<bool>((object)Run.instance, "allowNewParticipants", true);
			Run.instance.OnUserAdded(player);
			Reflection.SetFieldValue<bool>((object)Run.instance, "allowNewParticipants", false);
			Transform spawnTransformForPlayer = GetSpawnTransformForPlayer(player);
			player.master.SpawnBody(spawnTransformForPlayer.position, spawnTransformForPlayer.rotation);
			HandleBodyItems(player, null, BodyCatalog.GetBodyPrefab(newBodyIndex));
			if (DropInConfig.GiveCatchUpItems.Value)
			{
				GiveCatchUpItems(player);
			}
			return JoinAsResult.Success;
		}

		private static JoinAsResult RespawnExistingPlayerWithBody(NetworkUser player, BodyIndex newBodyIndex)
		{
			//IL_000d: 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_0074: 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_004c: Unknown result type (might be due to invalid IL or missing references)
			GameObject bodyPrefab = player.master.bodyPrefab;
			player.CmdSetBodyPreference(newBodyIndex);
			JoinAsResult result = JoinAsResult.Success;
			if ((Object)(object)player.GetCurrentBody() == (Object)null && player.master.lostBodyToDeath && !DropInConfig.AllowRespawn.Value)
			{
				Log.Message($"Unable immediately to spawn {player.userName} with bodyIndex = {newBodyIndex} due to being player being dead and AllowRespawn being set to false");
				result = JoinAsResult.DeadAndNotAllowRespawn;
			}
			else
			{
				Transform spawnTransformForPlayer = GetSpawnTransformForPlayer(player);
				player.master.Respawn(spawnTransformForPlayer.position, spawnTransformForPlayer.rotation, false);
			}
			HandleBodyItems(player, bodyPrefab, BodyCatalog.GetBodyPrefab(newBodyIndex));
			return result;
		}

		private static void HandleBodyItems(NetworkUser player, GameObject oldBodyPrefab, GameObject newBodyPrefab)
		{
			string text = null;
			string text2 = null;
			try
			{
				Inventory inventory = player.master.inventory;
				text = ((oldBodyPrefab != null) ? ((Object)oldBodyPrefab).name : null);
				text2 = ((Object)newBodyPrefab).name;
				if (!(text == "CaptainBody"))
				{
					if (text == "HereticBody" && DropInConfig.GiveHereticItems.Value)
					{
						inventory.RemoveItem(Items.LunarPrimaryReplacement, 1);
						inventory.RemoveItem(Items.LunarSecondaryReplacement, 1);
						inventory.RemoveItem(Items.LunarSpecialReplacement, 1);
						inventory.RemoveItem(Items.LunarUtilityReplacement, 1);
					}
				}
				else
				{
					if (inventory.GetItemCount(Items.CaptainDefenseMatrix) <= 0 && DropInConfig.PreventCaptainScrapAbuse.Value)
					{
						captainBlacklistInventories.Add(inventory);
					}
					if (!captainBlacklistInventories.Contains(inventory))
					{
						inventory.RemoveItem(Items.CaptainDefenseMatrix, 1);
					}
				}
				if (!(text2 == "CaptainBody"))
				{
					if (text2 == "HereticBody" && DropInConfig.GiveHereticItems.Value)
					{
						inventory.GiveItem(Items.LunarPrimaryReplacement, 1);
						inventory.GiveItem(Items.LunarSecondaryReplacement, 1);
						inventory.GiveItem(Items.LunarSpecialReplacement, 1);
						inventory.GiveItem(Items.LunarUtilityReplacement, 1);
					}
				}
				else if (!captainBlacklistInventories.Contains(inventory) || !DropInConfig.PreventCaptainScrapAbuse.Value)
				{
					inventory.GiveItem(Items.CaptainDefenseMatrix, 1);
				}
			}
			catch (Exception ex)
			{
				Debug.LogException(ex);
				Log.Error(ex);
				Log.Message("Handling body items for transition from " + (text ?? "none") + " to " + (text2 ?? "none") + " resulted in an exception");
			}
		}

		private static string Help(NetworkUser sender, string[] args)
		{
			if (args.Length > 1)
			{
				return "Help requires either 0 or 1 argument";
			}
			if (args.Length == 1)
			{
				if (!_chatCommands.TryGetValue(args[0].ToUpperInvariant(), out var value))
				{
					return "Unable to find command, try /help";
				}
				return value.HelpText;
			}
			return "Availible Commands: " + string.Join(",", _chatCommands.Values.Select((ChatCommand command) => command.Name.ToLower()));
		}

		private static string JoinAs(NetworkUser sender, string[] args)
		{
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Invalid comparison between Unknown and I4
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			if (args.Length != 1 && args.Length != 2)
			{
				return "join as requires either 1 or 2 arguments";
			}
			NetworkUser val = ((args.Length == 1) ? sender : GetNetUserFromString(args[1]));
			if ((Object)(object)val == (Object)null)
			{
				return "Unable to find player with given name";
			}
			Log.Error("Player " + val.userName + " is attempting to join as " + args[0]);
			Log.Error($"Player {val.userName} is attempting to join as GetCurrentBody: {(Object)(object)val.GetCurrentBody() != (Object)null}");
			Log.Error($"Player {val.userName} is attempting to join as lostBodyToDeath: {val.master.lostBodyToDeath}");
			Log.Error($"Player {val.userName} is attempting to join as hasBody: {val.master.hasBody}");
			if (!DropInConfig.RepetitionRespawn.Value)
			{
				if ((Object)(object)val.GetCurrentBody() != (Object)null)
				{
					return "Player is already spawned in, cannot use join as";
				}
				if ((Object)(object)val.master != (Object)null && !val.master.lostBodyToDeath)
				{
					return "Player is not dead, cannot use join as";
				}
				if ((Object)(object)val.master != (Object)null && val.master.hasBody)
				{
					return "Player is not dead, cannot use join as";
				}
			}
			if (DropInConfig.RejoinOnlyAfterDeath.Value && (Object)(object)val.master != (Object)null && !val.master.lostBodyToDeath)
			{
				return "Player is not dead, cannot use join as";
			}
			string text = args[0];
			SurvivorDef availibleSurvivorForPlayerByName = GetAvailibleSurvivorForPlayerByName(val, text);
			BodyIndex val2 = (BodyIndex)(-1);
			string text2;
			if ((Object)(object)availibleSurvivorForPlayerByName != (Object)null)
			{
				val2 = BodyCatalog.FindBodyIndex(availibleSurvivorForPlayerByName.bodyPrefab);
				text2 = Language.GetString(availibleSurvivorForPlayerByName.displayNameToken);
			}
			else
			{
				if (!DropInConfig.AllowJoinAsAllBodies.Value)
				{
					return "Unable to find survivor with the given name";
				}
				BodyIndex val3 = BodyCatalog.FindBodyIndexCaseInsensitive(text.EndsWith("Body", StringComparison.InvariantCultureIgnoreCase) ? text : (text + "Body"));
				if ((int)val3 == -1)
				{
					return "Unable to find survivor or body with given name";
				}
				val2 = val3;
				text2 = BodyCatalog.GetBodyName(val3);
			}
			JoinAsResult joinAsResult;
			try
			{
				joinAsResult = SpawnPlayerWithBody(val, val2);
			}
			catch (Exception ex)
			{
				Debug.LogException(ex);
				Log.Error(ex);
				return "An exception occured spawning " + val.userName + " as " + text2;
			}
			return joinAsResult switch
			{
				JoinAsResult.Success => "Spawning " + val.userName + " as " + text2, 
				JoinAsResult.DeadAndNotAllowRespawn => val.userName + " will be spawned as " + text2 + " next stage", 
				_ => "Unknown join as result", 
			};
		}

		private static string ListSurvivors(NetworkUser sender, string[] args)
		{
			if (args.Length > 1)
			{
				return "list survivors requires 0 or 1 argument";
			}
			NetworkUser val = ((args.Length == 0) ? sender : GetNetUserFromString(args[0]));
			if ((Object)(object)val == (Object)null)
			{
				return "Unable to find player with given name";
			}
			return GetSurvivorChatListForPlayer(val);
		}

		private static string ListBodies(NetworkUser sender, string[] args)
		{
			if (args.Length != 0)
			{
				return "list bodies requires no arguments";
			}
			return string.Join(", ", BodyCatalog.allBodyPrefabs.Select((GameObject prefab) => ((Object)prefab).name));
		}

		private string Transform(NetworkUser sender, string[] args)
		{
			if (args.Length > 2 || args.Length == 0)
			{
				return "transform requires 1 or 2 arguments";
			}
			NetworkUser val = ((args.Length == 1) ? sender : GetNetUserFromString(args[1]));
			if ((Object)(object)val == (Object)null)
			{
				return "Unable to find player with given name";
			}
			val.master.TransformBody(args[0]);
			return "Transformed " + sender.userName + " into " + args[0];
		}

		private static string Catchup(NetworkUser sender, string[] args)
		{
			if (args.Length != 0 && args.Length != 1)
			{
				return "Catchup requires 0 or 1 argument";
			}
			NetworkUser val = ((args.Length == 0) ? sender : GetNetUserFromString(args[0]));
			if ((Object)(object)val == (Object)null)
			{
				return "Unable to find player with given name";
			}
			GiveCatchUpItems(val);
			return "Gave " + val.userName + " catch up items";
		}

		private static string GiveRandomItems(NetworkUser sender, string[] args)
		{
			if (args.Length != 3 && args.Length != 4)
			{
				return "Give random items requires 3 or 4 arguments";
			}
			NetworkUser val = ((args.Length == 3) ? sender : GetNetUserFromString(args[3]));
			if ((Object)(object)val == (Object)null)
			{
				return "Unable to find player with given name";
			}
			object obj;
			if (val == null)
			{
				obj = null;
			}
			else
			{
				CharacterMaster master = val.master;
				obj = ((master != null) ? master.inventory : null);
			}
			if ((Object)obj == (Object)null)
			{
				return "Player has no inventory so cannot be given items";
			}
			int num;
			bool lunarEnabled;
			bool voidEnabled;
			try
			{
				num = int.Parse(args[0]);
				lunarEnabled = bool.Parse(args[1]);
				voidEnabled = bool.Parse(args[2]);
			}
			catch
			{
				return "Unable to parse arguments";
			}
			GiveRandomItems(val.master.inventory, num, lunarEnabled, voidEnabled);
			return $"Gave {val.userName} {num} random items";
		}

		[ConCommand(/*Could not decode attribute arguments.*/)]
		private void CommandJoinAs(ConCommandArgs args)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Debug.Log((object)JoinAs(args.sender, args.userArgs.ToArray()));
		}

		[ConCommand(/*Could not decode attribute arguments.*/)]
		private void CommandShowSurvivors(ConCommandArgs args)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Debug.Log((object)ListSurvivors(args.sender, args.userArgs.ToArray()));
		}

		[ConCommand(/*Could not decode attribute arguments.*/)]
		private void CommandListBodies(ConCommandArgs args)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Debug.Log((object)ListBodies(args.sender, args.userArgs.ToArray()));
		}

		private static NetworkUser GetNetUserFromString(string playerString)
		{
			if (!string.IsNullOrWhiteSpace(playerString))
			{
				if (int.TryParse(playerString, out var result))
				{
					if (result < NetworkUser.readOnlyInstancesList.Count && result >= 0)
					{
						return NetworkUser.readOnlyInstancesList[result];
					}
					return null;
				}
				foreach (NetworkUser readOnlyInstances in NetworkUser.readOnlyInstancesList)
				{
					if (readOnlyInstances.userName.Replace(" ", "").Equals(playerString.Replace(" ", ""), StringComparison.InvariantCultureIgnoreCase))
					{
						return readOnlyInstances;
					}
				}
				return null;
			}
			return null;
		}

		public static SurvivorDef[] GetAvailibleSurvivorsForPlayer(NetworkUser player)
		{
			return SurvivorCatalog.allSurvivorDefs.Where(SurvivorIsUnlockedAndAvailable).ToArray();
			static bool SurvivorIsUnlockedAndAvailable(SurvivorDef survivorDef)
			{
				if (!DropInConfig.AllowJoinAsHiddenSurvivors.Value && survivorDef.hidden)
				{
					Log.Message("Survivor " + survivorDef.cachedName + " is not availible because survivor is hidden and AllowJoinAsHiddenSurvivors is false");
					return false;
				}
				return true;
			}
		}

		public static SurvivorDef GetAvailibleSurvivorForPlayerByName(NetworkUser player, string name)
		{
			SurvivorDef[] availibleSurvivorsForPlayer = GetAvailibleSurvivorsForPlayer(player);
			if (string.Equals(name, "random", StringComparison.InvariantCultureIgnoreCase))
			{
				return availibleSurvivorsForPlayer[_rand.Next(availibleSurvivorsForPlayer.Length)];
			}
			return availibleSurvivorsForPlayer.Where(NameStringMatches).FirstOrDefault();
			bool NameStringMatches(SurvivorDef survivorDef)
			{
				if (!string.Equals(survivorDef.cachedName, name, StringComparison.InvariantCultureIgnoreCase))
				{
					return string.Equals(Language.GetString(survivorDef.displayNameToken).Replace(" ", ""), name.Replace(" ", ""), StringComparison.InvariantCultureIgnoreCase);
				}
				return true;
			}
		}

		private static void GiveCatchUpItems(NetworkUser player)
		{
			List<PickupIndex> list = new List<PickupIndex>();
			list.AddRange(Run.instance.availableTier1DropList);
			list.AddRange(Run.instance.availableTier2DropList);
			list.AddRange(Run.instance.availableTier3DropList);
			list.AddRange(Run.instance.availableBossDropList);
			list.AddRange(Run.instance.availableVoidTier1DropList);
			list.AddRange(Run.instance.availableVoidTier2DropList);
			list.AddRange(Run.instance.availableVoidTier3DropList);
			list.AddRange(Run.instance.availableVoidBossDropList);
			list.AddRange(Run.instance.availableLunarItemDropList);
			Inventory[] source = (from netUser in NetworkUser.readOnlyInstancesList.Where(delegate(NetworkUser netUser)
				{
					if ((Object)(object)netUser != (Object)(object)player)
					{
						object obj;
						if (netUser == null)
						{
							obj = null;
						}
						else
						{
							CharacterMaster master = netUser.master;
							obj = ((master != null) ? master.inventory : null);
						}
						return (Object)obj != (Object)null;
					}
					return false;
				})
				select netUser.master.inventory).ToArray();
			ItemIndex[] countItemIndexes = list.Select((PickupIndex pickupIndex) => PickupCatalog.GetPickupDef(pickupIndex).itemIndex).ToArray();
			int num = (int)((IEnumerable<Inventory>)source).Average((Func<Inventory, int>)CountInventoryItems);
			int num2 = num - CountInventoryItems(player.master.inventory);
			Log.Message($"On average other players have {num} items, giving {player.userName} {num2} items to match");
			GiveRandomItems(player.master.inventory, num2, DropInConfig.GiveCatchUpLunarItems.Value, DropInConfig.GiveCatchUpVoidItems.Value);
			int CountInventoryItems(Inventory inventory)
			{
				return countItemIndexes.Sum((ItemIndex itemIndex) => inventory.GetItemCount(itemIndex));
			}
		}

		private static void GiveRandomItems(Inventory inventory, int count, bool lunarEnabled, bool voidEnabled)
		{
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			if (count <= 0)
			{
				return;
			}
			WeightedSelection<List<PickupIndex>> val = new WeightedSelection<List<PickupIndex>>(8);
			val.AddChoice(Run.instance.availableTier1DropList, 100f);
			val.AddChoice(Run.instance.availableTier2DropList, 60f);
			val.AddChoice(Run.instance.availableTier3DropList, 4f);
			if (lunarEnabled)
			{
				val.AddChoice(Run.instance.availableLunarItemDropList, 4f);
			}
			if (voidEnabled)
			{
				val.AddChoice(Run.instance.availableVoidTier1DropList, 4f);
				val.AddChoice(Run.instance.availableVoidTier2DropList, 2.3999999f);
				val.AddChoice(Run.instance.availableVoidTier3DropList, 0.16f);
			}
			for (int i = 0; i < count; i++)
			{
				try
				{
					List<PickupIndex> list = val.Evaluate(Random.value);
					PickupDef pickupDef = PickupCatalog.GetPickupDef(list.ElementAtOrDefault(Random.Range(0, list.Count)));
					ItemIndex val2 = (ItemIndex)((pickupDef == null) ? (-1) : ((int)pickupDef.itemIndex));
					inventory.GiveItem(val2, 1);
				}
				catch (Exception ex)
				{
					Debug.LogException(ex);
					Log.Error(ex);
					Log.Message("Exception occured giving player an item");
				}
			}
		}

		private static Transform GetSpawnTransformForPlayer(NetworkUser player)
		{
			Transform val = player.GetCurrentBody()?.transform;
			if ((Object)(object)val == (Object)null)
			{
				Log.Message(player.userName + " does not have a current body, spawning on another player");
				NetworkUser? obj = NetworkUser.readOnlyInstancesList.Where((NetworkUser user) => (Object)(object)user.GetCurrentBody() != (Object)null).FirstOrDefault();
				val = ((obj != null) ? obj.GetCurrentBody().transform : null);
			}
			if ((Object)(object)val == (Object)null)
			{
				Log.Message("Unable to find alive player for " + player.userName + " to spawn on, defaulting to map spawn");
				val = Stage.instance.GetPlayerSpawnTransform();
			}
			return val;
		}

		[IteratorStateMachine(typeof(<SpawnPlayerAsRandomInternal>d__39))]
		private IEnumerator SpawnPlayerAsRandomInternal(NetworkUser player)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SpawnPlayerAsRandomInternal>d__39(0)
			{
				player = player
			};
		}

		private static string GetSurvivorChatListForPlayer(NetworkUser player)
		{
			string[] value = (from survivor in GetAvailibleSurvivorsForPlayer(player)
				select survivor.cachedName + " (" + Language.GetString(survivor.displayNameToken) + ")").ToArray();
			return string.Join(", ", value);
		}

		private void GreetNewPlayer(NetworkUser player)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			if (DropInConfig.SendWelcomeMessage.Value)
			{
				string value = DropInConfig.CustomWelcomeMessage.Value;
				if (value.Length > 1000)
				{
					Log.Message($"The custom welcome message has a length of {value.Length} which is longer than the limit of 1000 characters");
					return;
				}
				string baseToken = value.ReplaceOnce("{username}", player.userName).ReplaceOnce("{survivorlist}", GetSurvivorChatListForPlayer(player));
				Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage
				{
					baseToken = baseToken
				});
			}
		}

		private void SendChatMessage(string message)
		{
			((MonoBehaviour)DropInMultiplayerPlugin.Instance).StartCoroutine(SendChatMessageInternal(message));
		}

		[IteratorStateMachine(typeof(<SendChatMessageInternal>d__43))]
		private IEnumerator SendChatMessageInternal(string message)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SendChatMessageInternal>d__43(0)
			{
				message = message
			};
		}
	}
	public class DropInMultiplayerConfig
	{
		private const string DefaultWelcomeMessage = "你好{username}! 在聊天中输入“/join_as {幸存者姓名}”(或“/join_as random”)加入游戏。要获取可用幸存者的列表,请在聊天中输入“/list_survivors”";

		public ConfigEntry<bool> AllowRespawn { get; }

		public ConfigEntry<bool> RepetitionRespawn { get; }

		public ConfigEntry<bool> RejoinOnlyAfterDeath { get; }

		public ConfigEntry<bool> JoinAsRandomByDefault { get; }

		public ConfigEntry<bool> AllowJoinAsHiddenSurvivors { get; }

		public ConfigEntry<bool> AllowJoinAsAllBodies { get; }

		public ConfigEntry<bool> GiveHereticItems { get; }

		public ConfigEntry<bool> PreventCaptainScrapAbuse { get; }

		public ConfigEntry<bool> GiveCatchUpItems { get; }

		public ConfigEntry<bool> GiveRejoiningPlayersCatchUpItems { get; }

		public ConfigEntry<bool> GiveCatchUpVoidItems { get; }

		public ConfigEntry<bool> GiveCatchUpLunarItems { get; }

		public ConfigEntry<bool> SendWelcomeMessage { get; }

		public ConfigEntry<string> CustomWelcomeMessage { get; }

		public DropInMultiplayerConfig(ConfigFile config)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Expected O, but got Unknown
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Expected O, but got Unknown
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Expected O, but got Unknown
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Expected O, but got Unknown
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Expected O, but got Unknown
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Expected O, but got Unknown
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Expected O, but got Unknown
			//IL_0268: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Expected O, but got Unknown
			AllowRespawn = config.Bind<bool>("General", "AllowRespawn", true, "启用后,使用 /join_as 命令的死亡玩家将立即重生");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(AllowRespawn));
			RepetitionRespawn = config.Bind<bool>("General", "RepetitionRespawn", false, "启用后,玩家可以多次使用 /join_as 命令重新生成");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(RepetitionRespawn));
			RejoinOnlyAfterDeath = config.Bind<bool>("General", "RejoinOnlyAfterDeath", true, "启用后,玩家只能在死亡后使用 /join_as 命令重新生成");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(RejoinOnlyAfterDeath));
			JoinAsRandomByDefault = config.Bind<bool>("General", "JoinAsRandomByDefault", false, "启用后,新加入的玩家将默认生成为随机幸存者");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(JoinAsRandomByDefault));
			AllowJoinAsHiddenSurvivors = config.Bind<bool>("Survivors", "AllowJoinAsHiddenSurvivors", true, "启用后,允许玩家以隐藏角色的身份加入,例如异端");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(AllowJoinAsHiddenSurvivors));
			AllowJoinAsAllBodies = config.Bind<bool>("General", "AllowJoinAsAllBodies", false, "启用后,使用 /join_as 命令将尝试与任何身体匹配,例如甲虫,警告:非常未经测试join_as!");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(AllowJoinAsAllBodies));
			GiveHereticItems = config.Bind<bool>("Survivors", "GiveHereticItems", true, "启用后,以异端身份加入将自动获得所有 4 个异端物品。");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(GiveHereticItems));
			PreventCaptainScrapAbuse = config.Bind<bool>("Survivors", "PreventCaptainScrapAbuse", true, "启用后,如果 船长 被报废或移除,则不会收到替换的微型机器人。");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(PreventCaptainScrapAbuse));
			GiveCatchUpItems = config.Bind<bool>("Items", "GiveCatchUpItems", true, "启用后,玩家将在加入时获得追赶物品");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(GiveCatchUpItems));
			GiveRejoiningPlayersCatchUpItems = config.Bind<bool>("Items", "GiveRejoiningPlayersCatchUpItems", true, "启用后,离开并重新加入的玩家将获得追赶物品,警告:可以通过将所有物品交给一名玩家,然后离开并重新加入来利用");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(GiveRejoiningPlayersCatchUpItems));
			GiveCatchUpVoidItems = config.Bind<bool>("Items", "GiveCatchUpVoidItems", false, "启用后,月球物品将在玩家开始时掉落(需要启用 GiveCatchUpItems)");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(GiveCatchUpVoidItems));
			GiveCatchUpLunarItems = config.Bind<bool>("Items", "GiveCatchUpLunarItems", false, "启用后,月球物品将在玩家开始时掉落(需要启用 GiveCatchUpItems)");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(GiveCatchUpLunarItems));
			SendWelcomeMessage = config.Bind<bool>("Chat Messages", "SendWelcomeMessage", true, "当新玩家加入时发送欢迎消息。");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(SendWelcomeMessage));
			CustomWelcomeMessage = config.Bind<string>("Chat Messages", "CustomWelcomeMessage", "你好{username}! 在聊天中输入“/join_as {幸存者姓名}”(或“/join_as random”)加入游戏。要获取可用幸存者的列表,请在聊天中输入“/list_survivors”", "欢迎信息的格式。 {username} 将替换为加入用户名, {survivorlist} 将被可用幸存者名单取代。");
			ModSettingsManager.AddOption((BaseOption)new StringInputFieldOption(CustomWelcomeMessage));
		}
	}
	public interface IBasePlugin
	{
		void Init(ConfigFile config)
		{
		}

		void OnEnable()
		{
		}

		void OnDisable()
		{
		}

		void Start()
		{
		}

		void Update()
		{
		}

		void OnDestroy()
		{
		}
	}
	internal static class Log
	{
		private static ManualLogSource _logSource;

		internal static void Init(ManualLogSource logSource)
		{
			_logSource = logSource;
		}

		internal static void Debug(object data)
		{
			_logSource.LogDebug(data);
		}

		internal static void Error(object data)
		{
			_logSource.LogError(data);
		}

		internal static void Fatal(object data)
		{
			_logSource.LogFatal(data);
		}

		internal static void Info(object data)
		{
			_logSource.LogInfo(data);
		}

		internal static void Message(object data)
		{
			_logSource.LogMessage(data);
		}

		internal static void Warning(object data)
		{
			_logSource.LogWarning(data);
		}
	}
}
namespace DropInMultiplayerPlugin.Helpers
{
	internal class BodyManager
	{
		private readonly Run _run;

		private readonly DropInMultiplayerConfig _config;

		private static Random _rand = new Random();

		internal BodyManager(DropInMultiplayerConfig config, Run run)
		{
			_config = config;
			_run = run;
		}

		public SurvivorDef[] GetAvailibleSurvivorsForPlayer(NetworkUser player)
		{
			return SurvivorCatalog.allSurvivorDefs.Where(SurvivorIsUnlockedAndAvailable).ToArray();
			bool SurvivorIsUnlockedAndAvailable(SurvivorDef survivorDef)
			{
				if (_config.AllowJoinAsHiddenSurvivors.Value || !survivorDef.hidden)
				{
					if (!survivorDef.CheckRequiredExpansionEnabled((NetworkUser)null))
					{
						return false;
					}
					UnlockableDef unlockableDef = survivorDef.unlockableDef;
					if ((Object)(object)unlockableDef != (Object)null)
					{
						return _run.IsUnlockableUnlocked(unlockableDef);
					}
					return false;
				}
				return false;
			}
		}

		public SurvivorDef GetAvailibleSurvivorForPlayerByName(NetworkUser player, string name)
		{
			SurvivorDef[] availibleSurvivorsForPlayer = GetAvailibleSurvivorsForPlayer(player);
			if (string.Equals(name, "random", StringComparison.InvariantCultureIgnoreCase))
			{
				return availibleSurvivorsForPlayer[_rand.Next(availibleSurvivorsForPlayer.Length)];
			}
			return availibleSurvivorsForPlayer.Where(NameStringMatches).FirstOrDefault();
			bool NameStringMatches(SurvivorDef survivorDef)
			{
				return string.Equals(survivorDef.cachedName, name);
			}
		}
	}
	public static class HelperExtensions
	{
		public static void SetPrivateFieldValue(this object instance, string fieldName, object value)
		{
			instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(instance, value);
		}

		public static string ReplaceOnce(this string source, string replacetoken, string replacewith)
		{
			int num = source.IndexOf(replacetoken);
			if (num > -1)
			{
				return source.Substring(0, num) + replacewith + source.Substring(num + replacetoken.Length);
			}
			return source;
		}
	}
}