Decompiled source of OBS Sync v1.0.0

Nicole.OBSSync.dll

Decompiled 3 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.WebSockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OBSSync.Websocket;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;

[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("Nicole")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Automatically control your OBS and create timestamp logs of your games for easier video editing.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Nicole.OBSSync")]
[assembly: AssemblyTitle("OBS Sync")]
[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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string id = null, string name = null, string version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string id = null, string name = null, string version = null)
		{
		}
	}
}
namespace OBSSync
{
	[BepInPlugin("Nicole.OBSSync", "OBS Sync", "1.0.0")]
	public class ObsSyncPlugin : BaseUnityPlugin
	{
		private readonly ObsWebsocket _obs = new ObsWebsocket();

		private SelectableLevel? _lastPlayedLevel;

		private StreamWriter? _currentTimestampLog;

		private DateTime _currentLogStart;

		private ConfigEntry<string> _configObsWebsocketAddress;

		private ConfigEntry<string> _configObsWebsocketPassword;

		private ConfigEntry<bool> _configAutoStartStop;

		private ConfigEntry<bool> _configAutoSplit;

		private ConfigEntry<Key> _configManualEventKey;

		public const string Id = "Nicole.OBSSync";

		public static ObsSyncPlugin Instance { get; private set; }

		internal static ManualLogSource Logger { get; private set; }

		internal static Harmony? Harmony { get; set; }

		private string? LastPlanetName
		{
			get
			{
				if ((Object)(object)_lastPlayedLevel == (Object)null)
				{
					return null;
				}
				int num = _lastPlayedLevel.PlanetName.IndexOf(' ');
				return _lastPlayedLevel.PlanetName.Substring(num + 1);
			}
		}

		public static string Name => "OBS Sync";

		public static string Version => "1.0.0";

		private void Awake()
		{
			Logger = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			Harmony = Harmony.CreateAndPatchAll(typeof(Patches), "Nicole.OBSSync");
			BuildConfig();
			InitializeObsClient();
		}

		private void OnDestroy()
		{
			_currentTimestampLog?.Close();
		}

		private void Update()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (((ButtonControl)Keyboard.current[_configManualEventKey.Value]).wasPressedThisFrame)
			{
				WriteTimestamppedEvent("Manual event");
			}
		}

		private void InitializeObsClient()
		{
			_obs.Connected += ObsConnected;
			_obs.Disconnected += ObsDisconnected;
			_obs.RecordStateChanged += delegate(RecordStateChangedEventData data)
			{
				if (!(data.OutputState != "OBS_WEBSOCKET_OUTPUT_STARTED"))
				{
					string directoryName = Path.GetDirectoryName(data.OutputPath);
					string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(data.OutputPath);
					string path = Path.Combine(directoryName, fileNameWithoutExtension + ".txt");
					_currentTimestampLog = new StreamWriter(path);
					_currentTimestampLog.AutoFlush = true;
					_currentLogStart = DateTime.Now;
				}
			};
			DoConnect();
		}

		private void DoConnect()
		{
			try
			{
				_obs.Connect("ws://" + _configObsWebsocketAddress.Value, _configObsWebsocketPassword.Value);
			}
			catch (Exception ex)
			{
				Logger.LogError((object)ex);
			}
		}

		private void ObsConnected()
		{
			Logger.LogInfo((object)"Connected to OBS websocket!");
		}

		private void ObsDisconnected(string reason)
		{
			Logger.LogWarning((object)("Disconnected from OBS websocket: " + reason + ". Retrying in 5 seconds..."));
			Task.Run(async delegate
			{
				await Task.Delay(5000);
				DoConnect();
			});
		}

		private void RenameLogFile(string outputFilePath, string filenameSuffix)
		{
			string directoryName = Path.GetDirectoryName(outputFilePath);
			string extension = Path.GetExtension(outputFilePath);
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputFilePath);
			string destFileName = Path.Combine(directoryName, fileNameWithoutExtension + "_" + filenameSuffix + extension);
			File.Move(outputFilePath, destFileName);
			if (_currentTimestampLog != null)
			{
				_currentTimestampLog.Close();
				_currentTimestampLog = null;
				string sourceFileName = Path.Combine(directoryName, fileNameWithoutExtension + ".txt");
				string destFileName2 = Path.Combine(directoryName, fileNameWithoutExtension + "_" + filenameSuffix + ".txt");
				File.Move(sourceFileName, destFileName2);
			}
		}

		public async void StartRecording()
		{
			await _obs.MakeRequestAsync(new StartRecordRequest());
		}

		public async void StopRecording(string filenameSuffix)
		{
			string filenameSuffix2 = filenameSuffix;
			if ((await _obs.MakeRequestAsync(new StopRecordRequest())).Status.Success)
			{
				_obs.RecordStateChanged += WaitForRecordingStopped;
			}
			void WaitForRecordingStopped(RecordStateChangedEventData e)
			{
				if (!(e.OutputState != "OBS_WEBSOCKET_OUTPUT_STOPPED"))
				{
					_obs.RecordStateChanged -= WaitForRecordingStopped;
					RenameLogFile(e.OutputPath, filenameSuffix2);
				}
			}
		}

		public async Task SplitRecording(string filenameSuffix)
		{
			string filenameSuffix2 = filenameSuffix;
			SemaphoreSlim waitHandle;
			if ((await _obs.MakeRequestAsync(new StopRecordRequest())).Status.Success)
			{
				waitHandle = new SemaphoreSlim(0, 1);
				_obs.RecordStateChanged += WaitForRecordingStopped;
				await waitHandle.WaitAsync();
				await Task.Yield();
			}
			else
			{
				await _obs.MakeRequestAsync(new StartRecordRequest());
			}
			async void WaitForRecordingStopped(RecordStateChangedEventData e)
			{
				if (e.OutputState == "OBS_WEBSOCKET_OUTPUT_STOPPED")
				{
					RenameLogFile(e.OutputPath, filenameSuffix2);
					await Task.Delay(150);
					await _obs.MakeRequestAsync(new StartRecordRequest());
				}
				else if (e.OutputState == "OBS_WEBSOCKET_OUTPUT_STARTED")
				{
					_obs.RecordStateChanged -= WaitForRecordingStopped;
					waitHandle.Release();
				}
			}
		}

		internal void JoinedGame()
		{
			_lastPlayedLevel = null;
			if (_configAutoStartStop.Value)
			{
				StartRecording();
			}
		}

		internal void LeftGame()
		{
			if (_configAutoStartStop.Value)
			{
				string filenameSuffix = LastPlanetName ?? "end";
				StopRecording(filenameSuffix);
			}
		}

		internal async void RoundStarting()
		{
			if (_configAutoSplit.Value)
			{
				string filenameSuffix = LastPlanetName ?? "start";
				await SplitRecording(filenameSuffix);
			}
			else
			{
				WriteTimestamppedEvent("");
			}
			_lastPlayedLevel = StartOfRound.Instance.currentLevel;
			WriteTimestamppedEvent("Landing on " + LastPlanetName);
		}

		internal void RoundFinished()
		{
			WriteTimestamppedEvent("Left moon, now in orbit");
		}

		internal void WriteTimestamppedEvent(string what)
		{
			TimeSpan timeSpan = DateTime.Now - _currentLogStart;
			string text = $"[{timeSpan:hh':'mm':'ss}] {what}";
			Logger.LogDebug((object)text);
			if (_currentTimestampLog == null)
			{
				Logger.LogWarning((object)"Trying to write stuff but no logfile is opened... start a recording!");
			}
			else if (string.IsNullOrEmpty(what))
			{
				_currentTimestampLog.WriteLine();
			}
			else
			{
				_currentTimestampLog.WriteLine(text);
			}
		}

		private void BuildConfig()
		{
			_configObsWebsocketAddress = ((BaseUnityPlugin)this).Config.Bind<string>("Connection", "WebsocketAddress", "[::1]:4455", "IP address / port of the computer running OBS");
			_configObsWebsocketPassword = ((BaseUnityPlugin)this).Config.Bind<string>("Connection", "WebsocketPassword", "", "Password to connect to OBS's websocket");
			_configAutoStartStop = ((BaseUnityPlugin)this).Config.Bind<bool>("Recording", "AutoStartStop", true, "Automatically start recording when you start or join a game and automatically stop recording when you leave a game.");
			_configAutoSplit = ((BaseUnityPlugin)this).Config.Bind<bool>("Recording", "AutoSplit", true, "Automatically stop and start a new recording between moons");
			_configManualEventKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Recording", "ManualEventKey", (Key)67, "The key that will add a manual event into the timestamp log");
		}
	}
	internal static class Patches
	{
		[HarmonyPatch(typeof(StartOfRound), "ResetPlayersLoadedValueClientRpc")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> OnNewRoundStarted(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			return SkipRpcCrap(new CodeMatcher(instructions, (ILGenerator)null)).Insert((CodeInstruction[])(object)new CodeInstruction[1]
			{
				new CodeInstruction(OpCodes.Call, (object)new Action(ActualFunction).Method)
			}).InstructionEnumeration();
			static void ActualFunction()
			{
				ObsSyncPlugin.Instance.RoundStarting();
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "EndOfGame")]
		[HarmonyPrefix]
		public static void StartOfRound_EndOfGame_Prefix()
		{
			ObsSyncPlugin.Instance.RoundFinished();
		}

		[HarmonyPatch(typeof(StartOfRound), "Start")]
		[HarmonyPrefix]
		public static void OnGameJoined()
		{
			ObsSyncPlugin.Instance.JoinedGame();
		}

		[HarmonyPatch(typeof(GameNetworkManager), "StartDisconnect")]
		[HarmonyPrefix]
		public static void OnGameLeave()
		{
			ObsSyncPlugin.Instance.LeftGame();
		}

		[HarmonyPatch(typeof(PlayerControllerB), "KillPlayerClientRpc")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> OnPlayerKilled(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			return SkipRpcCrap(new CodeMatcher(instructions, (ILGenerator)null)).Insert((CodeInstruction[])(object)new CodeInstruction[3]
			{
				new CodeInstruction(OpCodes.Ldarg_1, (object)null),
				new CodeInstruction(OpCodes.Ldarg, (object)4),
				new CodeInstruction(OpCodes.Call, (object)new Action<int, int>(ActualFunction).Method)
			}).InstructionEnumeration();
			static void ActualFunction(int playerId, int causeOfDeath)
			{
				string playerUsername = StartOfRound.Instance.allPlayerScripts[playerId].playerUsername;
				ObsSyncPlugin.Instance.WriteTimestamppedEvent($"Player {playerUsername} died ({(object)(CauseOfDeath)causeOfDeath})");
			}
		}

		[HarmonyPatch(typeof(EnemyAI), "KillEnemy")]
		[HarmonyPostfix]
		public static void OnEnemyKilled(EnemyAI __instance)
		{
			if (!((Object)((Component)__instance).gameObject).name.Contains("Doublewinged"))
			{
				ObsSyncPlugin.Instance.WriteTimestamppedEvent("Enemy " + ((Object)((Component)__instance).gameObject).name + " died");
			}
		}

		[HarmonyPatch(typeof(FlowermanAI), "EnterAngerModeClientRpc")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> OnBrackenAngered(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return SkipRpcCrap(new CodeMatcher(instructions, (ILGenerator)null)).Insert((CodeInstruction[])(object)new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Call, (object)new Action<FlowermanAI>(ActualFunction).Method)
			}).InstructionEnumeration();
			static void ActualFunction(FlowermanAI self)
			{
				if ((Object)(object)self.lookAtPlayer != (Object)null)
				{
					ObsSyncPlugin.Instance.WriteTimestamppedEvent("Bracken angered towards " + self.lookAtPlayer.playerUsername);
				}
			}
		}

		[HarmonyPatch(typeof(JesterAI), "Update")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> OnJesterStartWinding(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			FieldInfo field = typeof(JesterAI).GetField("previousState", BindingFlags.Instance | BindingFlags.NonPublic);
			return new CodeMatcher(instructions, (ILGenerator)null).MatchForward(false, (CodeMatch[])(object)new CodeMatch[3]
			{
				new CodeMatch((OpCode?)OpCodes.Ldarg_0, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldc_I4_1, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Stfld, (object)field, (string)null)
			}).Insert((CodeInstruction[])(object)new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Call, (object)new Action<JesterAI>(ActualFunction).Method)
			}).InstructionEnumeration();
			static void ActualFunction(JesterAI self)
			{
				ObsSyncPlugin.Instance.WriteTimestamppedEvent("Jester started winding");
			}
		}

		[HarmonyPatch(typeof(HoarderBugAI), "Update")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> OnLootBugAngered(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			FieldInfo field = typeof(HoarderBugAI).GetField("inChase", BindingFlags.Instance | BindingFlags.NonPublic);
			return new CodeMatcher(instructions, (ILGenerator)null).MatchForward(false, (CodeMatch[])(object)new CodeMatch[3]
			{
				new CodeMatch((OpCode?)OpCodes.Ldarg_0, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldc_I4_1, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Stfld, (object)field, (string)null)
			}).Insert((CodeInstruction[])(object)new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Call, (object)new Action<HoarderBugAI>(ActualFunction).Method)
			}).InstructionEnumeration();
			static void ActualFunction(HoarderBugAI self)
			{
				ObsSyncPlugin.Instance.WriteTimestamppedEvent("Loot bug angered by " + ((EnemyAI)self).targetPlayer.playerUsername);
			}
		}

		[HarmonyPatch(typeof(StunGrenadeItem), "ExplodeStunGrenade")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> OnEggExploded(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			FieldInfo field = typeof(StunGrenadeItem).GetField("hasExploded", BindingFlags.Instance | BindingFlags.Public);
			return new CodeMatcher(instructions, (ILGenerator)null).MatchForward(true, (CodeMatch[])(object)new CodeMatch[3]
			{
				new CodeMatch((OpCode?)OpCodes.Ldarg_0, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldc_I4_1, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Stfld, (object)field, (string)null)
			}).Insert((CodeInstruction[])(object)new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Call, (object)new Action<StunGrenadeItem>(ActualFunction).Method)
			}).InstructionEnumeration();
			static void ActualFunction(StunGrenadeItem self)
			{
				if (((Object)self).name.Contains("Easter egg"))
				{
					ObsSyncPlugin.Instance.WriteTimestamppedEvent("Easter egg exploded");
				}
				else
				{
					ObsSyncPlugin.Instance.WriteTimestamppedEvent("Stun grenade exploded");
				}
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "DamagePlayerClientRpc")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> OnPlayerDamaged(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return SkipRpcCrap(new CodeMatcher(instructions, (ILGenerator)null)).Insert((CodeInstruction[])(object)new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Call, (object)new Action<PlayerControllerB>(ActualFunction).Method)
			}).InstructionEnumeration();
			static void ActualFunction(PlayerControllerB self)
			{
				ObsSyncPlugin.Instance.WriteTimestamppedEvent("Player " + self.playerUsername + " took damage");
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "JumpToFearLevel")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> OnJumpToFearLevel(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			return new CodeMatcher(instructions, (ILGenerator)null).End().MatchBack(false, (CodeMatch[])(object)new CodeMatch[1]
			{
				new CodeMatch((OpCode?)OpCodes.Ret, (object)null, (string)null)
			}).Insert((CodeInstruction[])(object)new CodeInstruction[1]
			{
				new CodeInstruction(OpCodes.Call, (object)new Action(ActualFunction).Method)
			})
				.InstructionEnumeration();
			static void ActualFunction()
			{
				float fearLevel = StartOfRound.Instance.fearLevel;
				if (!(fearLevel > 0.9f))
				{
					if (!(fearLevel > 0.75f))
					{
						if (fearLevel > 0.4f)
						{
							ObsSyncPlugin.Instance.WriteTimestamppedEvent("Moderate fear event");
						}
						else
						{
							ObsSyncPlugin.Instance.WriteTimestamppedEvent("Low fear event");
						}
					}
					else
					{
						ObsSyncPlugin.Instance.WriteTimestamppedEvent("High fear event");
					}
				}
				else
				{
					ObsSyncPlugin.Instance.WriteTimestamppedEvent("Extreme fear event");
				}
			}
		}

		[HarmonyPatch(typeof(HUDManager), "AddTextToChatOnServer")]
		[HarmonyPrefix]
		public static bool OnChatMessage(string chatMessage, int playerId)
		{
			if (playerId != (int)GameNetworkManager.Instance.localPlayerController.playerClientId)
			{
				return true;
			}
			if (!chatMessage.StartsWith("!mark "))
			{
				return true;
			}
			string text = chatMessage.Substring(6);
			ObsSyncPlugin.Instance.WriteTimestamppedEvent("Manual Event: " + text);
			return false;
		}

		private static CodeMatcher SkipRpcCrap(this CodeMatcher matcher)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			FieldInfo field = typeof(NetworkBehaviour).GetField("__rpc_exec_stage", BindingFlags.Instance | BindingFlags.NonPublic);
			for (int i = 0; i < 2; i++)
			{
				matcher.MatchForward(true, (CodeMatch[])(object)new CodeMatch[2]
				{
					new CodeMatch((OpCode?)OpCodes.Ldarg_0, (object)null, (string)null),
					new CodeMatch((OpCode?)OpCodes.Ldfld, (object)field, (string)null)
				});
			}
			matcher.MatchForward(true, (CodeMatch[])(object)new CodeMatch[2]
			{
				new CodeMatch((OpCode?)OpCodes.Ret, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Nop, (object)null, (string)null)
			});
			matcher.Advance(1);
			matcher.ThrowIfInvalid("Could not match for rpc stuff");
			return matcher;
		}
	}
}
namespace OBSSync.Websocket
{
	public class RecordStateChangedEventData
	{
		public const string StateStarting = "OBS_WEBSOCKET_OUTPUT_STARTING";

		public const string StateStarted = "OBS_WEBSOCKET_OUTPUT_STARTED";

		public const string StateStopping = "OBS_WEBSOCKET_OUTPUT_STOPPING";

		public const string StateStopped = "OBS_WEBSOCKET_OUTPUT_STOPPED";

		[JsonProperty(PropertyName = "outputActive")]
		public bool OutputActive { get; set; }

		[JsonProperty(PropertyName = "outputPath")]
		public string? OutputPath { get; set; }

		[JsonProperty(PropertyName = "outputState")]
		public string OutputState { get; set; }
	}
	public struct ObsRequestId
	{
		internal ulong Id { get; set; }
	}
	public abstract class ObsRequest
	{
		public string RequestType { get; }

		protected ObsRequest(string requestType)
		{
			RequestType = requestType;
		}

		public JObject GetRequestBody(string requestId)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			JObject val = new JObject
			{
				["requestType"] = JToken.op_Implicit(RequestType),
				["requestId"] = JToken.op_Implicit(requestId)
			};
			JObject requestData = GetRequestData();
			if (requestData != null)
			{
				val["requestData"] = (JToken)(object)requestData;
			}
			return val;
		}

		protected virtual JObject? GetRequestData()
		{
			return null;
		}

		public abstract ObsRequestResponse MakeResponseObject();
	}
	public abstract class ObsRequest<TResponse> : ObsRequest where TResponse : ObsRequestResponse, new()
	{
		public override ObsRequestResponse MakeResponseObject()
		{
			return new TResponse();
		}

		protected ObsRequest(string requestType)
			: base(requestType)
		{
		}
	}
	public class ObsRequestResponse
	{
		public class ResponseStatus
		{
			public bool Success { get; }

			public int Code { get; }

			public string Comment { get; }

			public ResponseStatus(bool success, int code, string comment)
			{
				Success = success;
				Code = code;
				Comment = comment;
				base..ctor();
			}
		}

		public ResponseStatus Status { get; private set; }

		public void SetRequestResponseBody(JObject body)
		{
			JObject val = Extensions.Value<JObject>((IEnumerable<JToken>)body["requestStatus"]);
			bool success = Extensions.Value<bool>((IEnumerable<JToken>)val["result"]);
			int code = Extensions.Value<int>((IEnumerable<JToken>)val["code"]);
			JToken obj = val["comment"];
			string comment = ((obj != null) ? Extensions.Value<string>((IEnumerable<JToken>)obj) : null) ?? string.Empty;
			Status = new ResponseStatus(success, code, comment);
			if (body.ContainsKey("responseData"))
			{
				SetResponseData(Extensions.Value<JObject>((IEnumerable<JToken>)body["responseData"]));
			}
		}

		protected virtual void SetResponseData(JObject data)
		{
		}
	}
	public class ObsWebsocket
	{
		private class ObsMessage
		{
			[JsonProperty(PropertyName = "op")]
			public ObsMessageOpcode Op { get; set; }

			[JsonProperty(PropertyName = "d")]
			public JObject Data { get; set; } = new JObject();

		}

		private enum ObsMessageOpcode
		{
			Hello = 0,
			Identify = 1,
			Identified = 2,
			ReIdentify = 3,
			Event = 5,
			Request = 6,
			RequestResponse = 7,
			RequestBatch = 8,
			RequestBatchResponse = 9
		}

		private ClientWebSocket WebSocket { get; set; } = new ClientWebSocket();


		private string? Password { get; set; }

		private ulong NextRequestId { get; set; }

		private Dictionary<ulong, (ObsRequest, TaskCompletionSource<object>)> SentRequests { get; } = new Dictionary<ulong, (ObsRequest, TaskCompletionSource<object>)>();


		public event Action? Connected;

		public event Action<string>? Disconnected;

		public event Action<RecordStateChangedEventData>? RecordStateChanged;

		public async void Connect(string websocketAddress, string password)
		{
			Password = password;
			if (WebSocket.State != 0)
			{
				WebSocket = new ClientWebSocket();
			}
			try
			{
				await WebSocket.ConnectAsync(new Uri(websocketAddress), default(CancellationToken));
			}
			catch (Exception ex)
			{
				this.Disconnected?.Invoke(ex.Message);
				return;
			}
			byte[] bytes = new byte[2048];
			WebSocketReceiveResult webSocketReceiveResult;
			while (true)
			{
				webSocketReceiveResult = await WebSocket.ReceiveAsync(bytes, default(CancellationToken));
				if (!webSocketReceiveResult.EndOfMessage)
				{
					ObsSyncPlugin.Logger.LogError((object)"Message was larger than 2048 bytes, bug the author about this.");
					return;
				}
				if (webSocketReceiveResult.CloseStatus.HasValue)
				{
					break;
				}
				string @string = Encoding.UTF8.GetString(bytes, 0, webSocketReceiveResult.Count);
				ObsSyncPlugin.Logger.LogDebug((object)("Recv: " + @string));
				ObsMessage obsMessage = JsonConvert.DeserializeObject<ObsMessage>(@string);
				if (obsMessage != null)
				{
					HandleMessage(obsMessage);
				}
			}
			DisconnectInternal();
			this.Disconnected?.Invoke(webSocketReceiveResult.CloseStatusDescription);
		}

		public void Disconnect()
		{
			DisconnectInternal();
		}

		public async Task<TResponse> MakeRequestAsync<TResponse>(ObsRequest<TResponse> request) where TResponse : ObsRequestResponse, new()
		{
			ObsRequestId obsRequestId = default(ObsRequestId);
			obsRequestId.Id = NextRequestId++;
			ObsRequestId obsRequestId2 = obsRequestId;
			SendMessage(new ObsMessage
			{
				Op = ObsMessageOpcode.Request,
				Data = request.GetRequestBody(obsRequestId2.Id.ToString())
			});
			TaskCompletionSource<object> taskCompletionSource = new TaskCompletionSource<object>();
			SentRequests[obsRequestId2.Id] = (request, taskCompletionSource);
			return (TResponse)(await taskCompletionSource.Task);
		}

		private void HandleMessage(ObsMessage msg)
		{
			switch (msg.Op)
			{
			case ObsMessageOpcode.Hello:
				HandleHello(msg.Data);
				break;
			case ObsMessageOpcode.Identified:
				HandleIdentified(msg.Data);
				break;
			case ObsMessageOpcode.Event:
				HandleEvent(msg.Data);
				break;
			case ObsMessageOpcode.RequestResponse:
				HandleRequestResponse(msg.Data);
				break;
			default:
				ObsSyncPlugin.Logger.LogWarning((object)("Received unknown or unsupported opcode from OBS websocket: " + msg.Op));
				break;
			}
		}

		private void HandleHello(JObject data)
		{
			ObsMessage obsMessage = new ObsMessage();
			obsMessage.Op = ObsMessageOpcode.Identify;
			obsMessage.Data["rpcVersion"] = JToken.op_Implicit(1);
			if (data.ContainsKey("authentication"))
			{
				if (string.IsNullOrEmpty(Password))
				{
					DisconnectInternal();
					this.Disconnected?.Invoke("A password is required and not provided");
					return;
				}
				JObject val = Extensions.Value<JObject>((IEnumerable<JToken>)data["authentication"]);
				string challenge = Extensions.Value<string>((IEnumerable<JToken>)val["challenge"]);
				string salt = Extensions.Value<string>((IEnumerable<JToken>)val["salt"]);
				obsMessage.Data["authentication"] = JToken.op_Implicit(CreateAuthenticationData(challenge, salt));
			}
			SendMessage(obsMessage);
		}

		private void HandleIdentified(JObject data)
		{
			this.Connected?.Invoke();
		}

		private void HandleEvent(JObject data)
		{
			string text = Extensions.Value<string>((IEnumerable<JToken>)data["eventType"]);
			if (text == "RecordStateChanged")
			{
				this.RecordStateChanged?.Invoke(data["eventData"].ToObject<RecordStateChangedEventData>());
			}
		}

		private void HandleRequestResponse(JObject data)
		{
			if (!ulong.TryParse(Extensions.Value<string>((IEnumerable<JToken>)data["requestId"]), out var result))
			{
				ObsSyncPlugin.Logger.LogWarning((object)"Received RequestResponse with non-integer request ID");
				return;
			}
			if (!SentRequests.TryGetValue(result, out (ObsRequest, TaskCompletionSource<object>) value))
			{
				ObsSyncPlugin.Logger.LogWarning((object)"Received RequestResponse with unknown request ID");
				return;
			}
			ObsRequest item = value.Item1;
			TaskCompletionSource<object> item2 = value.Item2;
			string text = Extensions.Value<string>((IEnumerable<JToken>)data["requestType"]);
			if (text != item.RequestType)
			{
				ObsSyncPlugin.Logger.LogError((object)("Mismatched response type for request (" + item.RequestType + " != " + text + ")"));
			}
			else
			{
				ObsRequestResponse obsRequestResponse = item.MakeResponseObject();
				obsRequestResponse.SetRequestResponseBody(data);
				item2.SetResult(obsRequestResponse);
			}
		}

		private string CreateAuthenticationData(string challenge, string salt)
		{
			if (string.IsNullOrEmpty(Password))
			{
				throw new InvalidOperationException();
			}
			SHA256 sHA = SHA256.Create();
			string s = Password + salt;
			byte[] inArray = sHA.ComputeHash(Encoding.UTF8.GetBytes(s));
			string text = Convert.ToBase64String(inArray);
			string s2 = text + challenge;
			byte[] inArray2 = sHA.ComputeHash(Encoding.UTF8.GetBytes(s2));
			return Convert.ToBase64String(inArray2);
		}

		private void SendMessage(ObsMessage msg)
		{
			if (WebSocket.State == WebSocketState.Open)
			{
				string text = JsonConvert.SerializeObject((object)msg);
				byte[] bytes = Encoding.UTF8.GetBytes(text);
				ObsSyncPlugin.Logger.LogDebug((object)("Send: " + text));
				WebSocket.SendAsync(bytes, WebSocketMessageType.Text, endOfMessage: true, default(CancellationToken));
			}
		}

		private async void DisconnectInternal()
		{
			if (WebSocket.State == WebSocketState.Open && WebSocket.State == WebSocketState.CloseReceived)
			{
				await WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client disconnecting", default(CancellationToken));
			}
		}
	}
	public class StartRecordRequest : ObsRequest<ObsRequestResponse>
	{
		public StartRecordRequest()
			: base("StartRecord")
		{
		}
	}
	public class StopRecordRequest : ObsRequest<StopRecordResponse>
	{
		public StopRecordRequest()
			: base("StopRecord")
		{
		}
	}
	public class StopRecordResponse : ObsRequestResponse
	{
		public string OutputPath { get; private set; } = "";


		protected override void SetResponseData(JObject data)
		{
			OutputPath = Extensions.Value<string>((IEnumerable<JToken>)data["outputPath"]);
		}
	}
}