Decompiled source of OBS Control API v2.0.2

Mods/OBS_Control_API.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Il2CppRUMBLE.Audio;
using Il2CppRUMBLE.Managers;
using Il2CppRUMBLE.Players.Subsystems;
using Il2CppRUMBLE.Utilities;
using MelonLoader;
using MelonLoader.Preferences;
using Newtonsoft.Json;
using OBS_Control_API;
using RumbleModdingAPI.RMAPI;
using UIFramework;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: MelonInfo(typeof(OBS), "OBS_Control_API", "2.0.2", "Kalamart", null)]
[assembly: VerifyLoaderVersion(0, 6, 5, true)]
[assembly: MelonGame("Buckethead Entertainment", "RUMBLE")]
[assembly: MelonColor(255, 255, 31, 90)]
[assembly: MelonAuthorColor(255, 255, 31, 90)]
[assembly: MelonAdditionalDependencies(new string[] { "UIFramework", "RumbleModdingAPI" })]
[assembly: AssemblyDescription("Manages a websocket connection to OBS")]
[assembly: AssemblyCopyright("Copyright ©  2024")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("621d30a5-8fa1-4d87-9826-92c0149b033e")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("OBS_Control_API")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+f7d134b78116ca3678a6d993aa5931d265420ee4")]
[assembly: AssemblyProduct("OBS_Control_API")]
[assembly: AssemblyTitle("OBS_Control_API")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace OBS_Control_API;

public class OBS : MelonMod
{
	private class ConnectionManager
	{
		private RequestManager requestManager;

		private ClientWebSocket ws = new ClientWebSocket();

		private string PASSWORD = null;

		private string URL = null;

		private bool authentifying = false;

		private bool waitingOnServer = false;

		private bool shouldReconnect = true;

		public event Action<Event> onEvent;

		public ConnectionManager(RequestManager manager, string OBS_ip, int OBS_port, string OBS_password)
		{
			requestManager = manager;
			manager.SetConnectionManager(this);
			UpdateWebsocketConfig(OBS_ip, OBS_port, OBS_password);
		}

		public void UpdateWebsocketConfig(string OBS_ip, int OBS_port, string OBS_password)
		{
			URL = "ws://" + OBS_ip + ":" + OBS_port;
			PASSWORD = OBS_password;
		}

		public bool IsConnected()
		{
			return ws.State == WebSocketState.Open;
		}

		public void Start()
		{
			Task.Run(() => ConnectAsync());
		}

		private async Task ConnectAsync()
		{
			shouldReconnect = true;
			while (shouldReconnect)
			{
				bool shouldWait = true;
				try
				{
					ws = new ClientWebSocket();
					Uri uri = new Uri(URL);
					await ws.ConnectAsync(uri, CancellationToken.None);
					OnOpen();
					byte[] buffer = new byte[4096];
					while (IsConnected())
					{
						WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
						if (result.MessageType == WebSocketMessageType.Text)
						{
							OnMessage(Encoding.UTF8.GetString(buffer, 0, result.Count));
						}
					}
					OnClose();
					shouldWait = false;
				}
				catch (WebSocketException ex2) when (ex2.WebSocketErrorCode == WebSocketError.Faulted)
				{
					if (!waitingOnServer)
					{
						Log("OBS websocket server unavailable, will attempt reconnecting every 3 seconds.");
						waitingOnServer = true;
					}
				}
				catch (WebSocketException wse)
				{
					LogError($"WebSocket error: {wse.WebSocketErrorCode}");
					if (wse.WebSocketErrorCode != WebSocketError.ConnectionClosedPrematurely)
					{
						shouldReconnect = false;
					}
				}
				catch (Exception ex)
				{
					LogError("Error: " + ex.Message);
					shouldReconnect = false;
				}
				if (shouldReconnect && shouldWait)
				{
					await Task.Delay(3000);
				}
			}
		}

		private void OnOpen()
		{
			Log("OBS websocket server available");
			waitingOnServer = false;
		}

		private void OnClose()
		{
			if (authentifying)
			{
				LogError("Authentication failed");
				shouldReconnect = false;
			}
			Log("Connection to OBS closed");
			Task.Run(delegate
			{
				OBS.onDisconnect();
			});
		}

		public async void Stop(bool stopReconnect = true)
		{
			shouldReconnect = !stopReconnect;
			if (IsConnected())
			{
				Log("Closing websocket connection");
				await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
			}
		}

		private void OnMessage(string payload)
		{
			try
			{
				Msg msg = JsonConvert.DeserializeObject<Msg>(payload);
				if (msg == null)
				{
					return;
				}
				OpCode opCode = (OpCode)Convert.ToInt32(msg.op);
				string text = msg.d.ToString();
				switch (opCode)
				{
				case OpCode.Hello:
				{
					Hello hello = JsonConvert.DeserializeObject<Hello>(text);
					Hello.Authentication authentication = hello.authentication;
					string challenge = authentication.challenge;
					string salt = authentication.salt;
					string text2 = Sha256Base64(PASSWORD + salt);
					string value = Sha256Base64(text2 + challenge);
					Dictionary<string, object> msg2 = new Dictionary<string, object>
					{
						["op"] = OpCode.Identify,
						["d"] = new Dictionary<string, object>
						{
							["rpcVersion"] = 1,
							["authentication"] = value
						}
					};
					authentifying = true;
					SendMsg(msg2);
					break;
				}
				case OpCode.Identified:
					authentifying = false;
					Log("Connection to OBS successful");
					Task.Run(delegate
					{
						OBS.onConnect();
					});
					break;
				case OpCode.Event:
				{
					Event data = JsonConvert.DeserializeObject<Event>(text);
					Task.Run(delegate
					{
						this.onEvent(data);
					});
					break;
				}
				case OpCode.RequestResponse:
					requestManager.ReceiveResponse(text);
					break;
				default:
					LogError($"Received invalid OpCode {opCode}");
					break;
				}
			}
			catch (Exception ex)
			{
				LogError("Error in OnMessage: " + ex.Message);
			}
		}

		private string Sha256Base64(string input)
		{
			using SHA256 sHA = SHA256.Create();
			byte[] inArray = sHA.ComputeHash(Encoding.UTF8.GetBytes(input));
			return Convert.ToBase64String(inArray);
		}

		public async void SendMsg(object msg)
		{
			if (ws.State == WebSocketState.Open)
			{
				byte[] buffer = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(msg));
				ArraySegment<byte> segment = new ArraySegment<byte>(buffer);
				Func<Task> task = () => ws.SendAsync(segment, WebSocketMessageType.Text, endOfMessage: true, CancellationToken.None);
				await Task.Run(task);
			}
		}
	}

	private class RequestManager
	{
		private ConnectionManager connectionManager;

		private Dictionary<string, RequestResponse> currentRequests;

		public RequestManager()
		{
			currentRequests = new Dictionary<string, RequestResponse>();
		}

		public void SetConnectionManager(ConnectionManager manager)
		{
			connectionManager = manager;
		}

		public string SendRequest(string requestType, object parameters)
		{
			RequestResponse requestResponse = null;
			int num = 0;
			while (requestResponse == null && num < 100)
			{
				string text = NewRequest(requestType, parameters);
				if (text == null)
				{
					return null;
				}
				while (currentRequests[text] == null)
				{
					Thread.Sleep(10);
					num++;
				}
				if (currentRequests[text] == null)
				{
					continue;
				}
				requestResponse = currentRequests[text];
				if (requestResponse.requestStatus.code == 207)
				{
					requestResponse = null;
					Thread.Sleep(10);
					num++;
				}
				else if (!requestResponse.requestStatus.result)
				{
					if (requestResponse.requestStatus.comment == null)
					{
						LogError($"Request {requestType} returned error code {requestResponse.requestStatus.code}");
					}
					else
					{
						LogError($"Request {requestType} returned error code {requestResponse.requestStatus.code}: {requestResponse.requestStatus.comment}");
					}
					return null;
				}
			}
			if (requestResponse == null)
			{
				LogError("Request " + requestType + " timed out");
				return null;
			}
			return requestResponse.responseData;
		}

		private string NewRequest(string requestType, object parameters)
		{
			if (!connectionManager.IsConnected())
			{
				LogError("Request " + requestType + " cannot be executed because the client is not connected to OBS");
				return null;
			}
			string text = Guid.NewGuid().ToString();
			Dictionary<string, object> dictionary = new Dictionary<string, object>
			{
				["requestType"] = requestType,
				["requestId"] = text
			};
			if (parameters != null)
			{
				dictionary["requestData"] = parameters;
			}
			Dictionary<string, object> msg = new Dictionary<string, object>
			{
				["op"] = OpCode.Request,
				["d"] = dictionary
			};
			connectionManager.SendMsg(msg);
			currentRequests.Add(text, null);
			return text;
		}

		public void ReceiveResponse(string data_str)
		{
			RequestResponseRaw requestResponseRaw = JsonConvert.DeserializeObject<RequestResponseRaw>(data_str);
			string requestId = requestResponseRaw.requestId;
			if (currentRequests.ContainsKey(requestId))
			{
				RequestResponse requestResponse = new RequestResponse();
				requestResponse.requestStatus = requestResponseRaw.requestStatus;
				if (requestResponseRaw.responseData != null)
				{
					requestResponse.responseData = requestResponseRaw.responseData.ToString();
				}
				else
				{
					requestResponse.responseData = "{}";
				}
				currentRequests[requestId] = requestResponse;
			}
		}
	}

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

		private object <>2__current;

		public AudioCall audioPlayer;

		public Vector3 audioLocation;

		public OBS <>4__this;

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

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

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

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

		private bool MoveNext()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_0068: 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(<>4__this.replayBufferDelay);
				<>1__state = 1;
				return true;
			case 1:
				<>1__state = -1;
				if (SaveReplayBuffer())
				{
					Log("Saved replay buffer");
					<>4__this.Feedback(audioPlayer, audioLocation);
				}
				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 <StopHapticFeedback>d__101 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

		public float duration;

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

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

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

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

		private bool MoveNext()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<>2__current = (object)new WaitForSeconds(duration);
				<>1__state = 1;
				return true;
			case 1:
				<>1__state = -1;
				areHapticsPlaying = false;
				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 <UnlockKeyBinding>d__95 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

		public int index;

		public OBS <>4__this;

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

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

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

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

		private bool MoveNext()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<>2__current = (object)new WaitForSeconds(1f);
				<>1__state = 1;
				return true;
			case 1:
				<>1__state = -1;
				<>4__this.bindingLocked[index] = false;
				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();
		}
	}

	internal const string USER_DATA = "UserData/OBS_Control_API/";

	internal const string AUDIO_RESOURCES = "sfx_assets/";

	internal const string CONFIG_FILE = "config.cfg";

	private bool forceReplayBuffer = true;

	private float replayBufferDelay = 0f;

	private static string ip = "localhost";

	private static int port = 4455;

	private static string password = "your_password_here";

	private ControllerKeyActions[] keyBindings = new ControllerKeyActions[2];

	private bool[] bindingLocked = new bool[2];

	private float hapticsDuration = 1f;

	private bool enableSFX = true;

	private float SFXvolume = 1f;

	private static AudioCall Confirmation;

	private static AudioCall Screenshot;

	private static AudioCall StartRecording;

	private static AudioCall StopRecording;

	private static ConnectionManager connectionManager = null;

	private static RequestManager requestManager = null;

	private static PlayerHaptics playerHaptics = null;

	private static bool isReplayBufferActive = false;

	private static bool isRecordingActive = false;

	private static bool isStreamActive = false;

	private static bool isWindows = true;

	private static string recordingDirectory = "";

	private static string sceneUuid = "";

	private static bool stopReplayBufferAtShutdown = false;

	private static bool areHapticsPlaying = false;

	private static GameObject OBS_SFX_Players = null;

	private static GameObject screenshotSFXPlayer = null;

	private static GameObject confirmationSFXPlayer = null;

	private static GameObject startRecordingSFXPlayer = null;

	private static GameObject stopRecordingSFXPlayer = null;

	public static event Action onConnect;

	public static event Action onDisconnect;

	public static event Action<Event> onEvent;

	public static event Action<string> onReplayBufferSaved;

	public static event Action onReplayBufferStarted;

	public static event Action onReplayBufferStopped;

	public static event Action<string> onRecordingStarted;

	public static event Action<string> onRecordingStopping;

	public static event Action<string> onRecordingStopped;

	public static event Action onRecordingPaused;

	public static event Action onRecordingResumed;

	public static event Action onStreamStarted;

	public static event Action onStreamStopped;

	public static event Action<string> onRecordFileChanged;

	public static event Action<string> onScreenshotSaved;

	private void InitEvents()
	{
		connectionManager.onEvent += OnEvent;
	}

	private void OnEvent(Event data)
	{
		Task.Run(delegate
		{
			OBS.onEvent(data);
		});
		string text = data.eventData.ToString();
		if (data.eventType == "ReplayBufferSaved")
		{
			Event.ReplayBufferSaved eventData4 = JsonConvert.DeserializeObject<Event.ReplayBufferSaved>(text);
			Task.Run(delegate
			{
				OBS.onReplayBufferSaved(eventData4.savedReplayPath);
			});
		}
		if (data.eventType == "ReplayBufferStateChanged")
		{
			Event.ReplayBufferStateChanged replayBufferStateChanged = JsonConvert.DeserializeObject<Event.ReplayBufferStateChanged>(text);
			isReplayBufferActive = replayBufferStateChanged.outputActive;
			if (replayBufferStateChanged.outputState == "OBS_WEBSOCKET_OUTPUT_STARTED")
			{
				Task.Run(delegate
				{
					OBS.onReplayBufferStarted();
				});
			}
			else if (replayBufferStateChanged.outputState == "OBS_WEBSOCKET_OUTPUT_STOPPED")
			{
				Task.Run(delegate
				{
					OBS.onReplayBufferStopped();
				});
			}
		}
		else if (data.eventType == "RecordStateChanged")
		{
			Event.RecordStateChanged eventData3 = JsonConvert.DeserializeObject<Event.RecordStateChanged>(text);
			isRecordingActive = eventData3.outputActive;
			if (eventData3.outputState == "OBS_WEBSOCKET_OUTPUT_STARTED")
			{
				Task.Run(delegate
				{
					OBS.onRecordingStarted(eventData3.outputPath);
				});
			}
			else if (eventData3.outputState == "OBS_WEBSOCKET_OUTPUT_STOPPING")
			{
				Task.Run(delegate
				{
					OBS.onRecordingStopping(eventData3.outputPath);
				});
			}
			else if (eventData3.outputState == "OBS_WEBSOCKET_OUTPUT_STOPPED")
			{
				Task.Run(delegate
				{
					OBS.onRecordingStopped(eventData3.outputPath);
				});
			}
			else if (eventData3.outputState == "OBS_WEBSOCKET_OUTPUT_PAUSED")
			{
				Task.Run(delegate
				{
					OBS.onRecordingPaused();
				});
			}
			else if (eventData3.outputState == "OBS_WEBSOCKET_OUTPUT_RESUMED")
			{
				Task.Run(delegate
				{
					OBS.onRecordingResumed();
				});
			}
		}
		else if (data.eventType == "StreamStateChanged")
		{
			Event.ReplayBufferStateChanged replayBufferStateChanged2 = JsonConvert.DeserializeObject<Event.ReplayBufferStateChanged>(text);
			isStreamActive = replayBufferStateChanged2.outputActive;
			if (replayBufferStateChanged2.outputState == "OBS_WEBSOCKET_OUTPUT_STARTED")
			{
				Task.Run(delegate
				{
					OBS.onStreamStarted();
				});
			}
			else if (replayBufferStateChanged2.outputState == "OBS_WEBSOCKET_OUTPUT_STOPPED")
			{
				Task.Run(delegate
				{
					OBS.onStreamStopped();
				});
			}
		}
		else if (data.eventType == "RecordFileChanged")
		{
			Event.RecordFileChanged eventData2 = JsonConvert.DeserializeObject<Event.RecordFileChanged>(text);
			Task.Run(delegate
			{
				OBS.onRecordFileChanged(eventData2.newOutputPath);
			});
		}
		else if (data.eventType == "ScreenshotSaved")
		{
			Event.ScreenshotSaved eventData = JsonConvert.DeserializeObject<Event.ScreenshotSaved>(text);
			Task.Run(delegate
			{
				OBS.onScreenshotSaved(eventData.savedScreenshotPath);
			});
		}
	}

	private static void Log(string msg)
	{
		MelonLogger.Msg(msg);
	}

	private static void LogWarn(string msg)
	{
		MelonLogger.Warning(msg);
	}

	private static void LogError(string msg)
	{
		MelonLogger.Error(msg);
	}

	private void InitClient()
	{
		requestManager = new RequestManager();
		connectionManager = new ConnectionManager(requestManager, ip, port, password);
		InitEvents();
		onConnect += OnConnect;
		onDisconnect += OnDisconnect;
	}

	private void InitAudioCalls()
	{
		try
		{
			SFXvolume = Preferences.AudioVolume.Value;
			Confirmation = AudioManager.CreateAudioCall(Path.Combine("UserData/OBS_Control_API/", "sfx_assets/", "confirmation.wav"), SFXvolume);
			Screenshot = AudioManager.CreateAudioCall(Path.Combine("UserData/OBS_Control_API/", "sfx_assets/", "screenshot.wav"), SFXvolume);
			StartRecording = AudioManager.CreateAudioCall(Path.Combine("UserData/OBS_Control_API/", "sfx_assets/", "start_recording.wav"), SFXvolume);
			StopRecording = AudioManager.CreateAudioCall(Path.Combine("UserData/OBS_Control_API/", "sfx_assets/", "stop_recording.wav"), SFXvolume);
		}
		catch (Exception value)
		{
			LogError($"Error initializing audio calls: {value}");
			enableSFX = false;
		}
	}

	public override void OnInitializeMelon()
	{
		Preferences.PrefInit();
		if (Preferences.Password.Value == "")
		{
			((ModelModItem)UI.Register((MelonBase)(object)this, (MelonPreferences_Category[])(object)new MelonPreferences_Category[2]
			{
				Preferences.Connection,
				Preferences.GeneralCategory
			})).OnModSaved += OnUISaved;
		}
		else
		{
			((ModelModItem)UI.Register((MelonBase)(object)this, (MelonPreferences_Category[])(object)new MelonPreferences_Category[2]
			{
				Preferences.GeneralCategory,
				Preferences.Connection
			})).OnModSaved += OnUISaved;
		}
		PopulateUserDataIfNeeded();
		InitAudioCalls();
	}

	public override void OnSceneWasLoaded(int buildIndex, string sceneName)
	{
		playerHaptics = ((Component)Singleton<PlayerManager>.instance.playerControllerPrefab).gameObject.GetComponent<PlayerHaptics>();
		if (sceneName == "Loader")
		{
			InitClient();
			OnUISaved();
		}
	}

	public static void Connect()
	{
		connectionManager.UpdateWebsocketConfig(ip, port, password);
		if (IsConnected())
		{
			Disconnect(stopReconnect: false);
		}
		else
		{
			connectionManager.Start();
		}
	}

	public static void Disconnect(bool stopReconnect = true)
	{
		connectionManager.Stop(stopReconnect);
	}

	private void OnConnect()
	{
		SetMainStatus();
		if (!isReplayBufferActive && forceReplayBuffer)
		{
			Log("Replay Buffer was inactive, starting it...");
			stopReplayBufferAtShutdown = true;
			StartReplayBuffer();
		}
	}

	private void OnDisconnect()
	{
		isReplayBufferActive = false;
		isRecordingActive = false;
		isStreamActive = false;
	}

	private void OnUISaved()
	{
		try
		{
			forceReplayBuffer = Preferences.EnableReplayBuffer.Value;
			replayBufferDelay = Preferences.ReplayBufferDelay.Value;
			keyBindings[0] = Preferences.BindingLeft.Value;
			keyBindings[1] = Preferences.BindingRight.Value;
			hapticsDuration = Preferences.HapticDuration.Value;
			enableSFX = Preferences.AudioFeedback.Value;
			SFXvolume = Preferences.AudioVolume.Value;
			ip = Preferences.IpAddress.Value;
			port = Preferences.Port.Value;
			password = Preferences.Password.Value;
		}
		catch (Exception value)
		{
			LogError($"Error reading config values: {value}");
		}
		try
		{
			Connect();
		}
		catch (Exception value2)
		{
			LogError($"Error connecting to OBS: {value2}");
			Disconnect();
		}
		try
		{
			UpdateSFXvolume();
		}
		catch (Exception value3)
		{
			LogError($"Error updating SFX volume: {value3}");
		}
	}

	private void UpdateSFXvolume()
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Expected O, but got Unknown
		GeneralAudioSettings val = new GeneralAudioSettings();
		val.SetVolume(SFXvolume);
		val.Pitch = 1f;
		if ((Object)null != (Object)(object)Confirmation)
		{
			Confirmation.generalSettings = val;
		}
		if ((Object)null != (Object)(object)Screenshot)
		{
			Screenshot.generalSettings = val;
		}
		if ((Object)null != (Object)(object)StartRecording)
		{
			StartRecording.generalSettings = val;
		}
		if ((Object)null != (Object)(object)StopRecording)
		{
			StopRecording.generalSettings = val;
		}
	}

	private void ExecuteKeyBinding(int index, Vector3 audioLocation)
	{
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
		bindingLocked[index] = true;
		bool flag = false;
		AudioCall audioPlayer = null;
		switch (keyBindings[index])
		{
		case ControllerKeyActions.SaveReplayBuffer:
			if (hapticsDuration > 0f && replayBufferDelay >= 1f)
			{
				HapticFeedback(hapticsDuration);
			}
			MelonCoroutines.Start(DelayReplayBufferSaving(Confirmation, audioLocation));
			flag = false;
			break;
		case ControllerKeyActions.StartRecording:
			if (StartRecord())
			{
				Log("Started recording");
				audioPlayer = StartRecording;
				flag = true;
			}
			break;
		case ControllerKeyActions.StopRecording:
		{
			RequestResponse.StopRecord stopRecord = StopRecord();
			if (stopRecord != null)
			{
				Log("Stopped recording, saved to: " + stopRecord.outputPath);
				audioPlayer = StopRecording;
				flag = true;
			}
			break;
		}
		case ControllerKeyActions.ToggleRecording:
		{
			RequestResponse.ToggleRecord toggleRecord = ToggleRecord();
			if (toggleRecord != null)
			{
				bool outputActive = toggleRecord.outputActive;
				string text = (outputActive ? "Started" : "Stopped");
				Log(text + " recording");
				audioPlayer = (outputActive ? StartRecording : StopRecording);
				flag = true;
			}
			break;
		}
		case ControllerKeyActions.SaveScreenshot:
			if (SaveSourceScreenshot())
			{
				Log("Saved screenshot");
				audioPlayer = Screenshot;
				flag = true;
			}
			break;
		default:
			Log($"Invalid key binding \"{keyBindings[index]}\"");
			break;
		}
		if (flag)
		{
			Feedback(audioPlayer, audioLocation);
		}
		MelonCoroutines.Start(UnlockKeyBinding(index));
	}

	[IteratorStateMachine(typeof(<UnlockKeyBinding>d__95))]
	private IEnumerator UnlockKeyBinding(int index)
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <UnlockKeyBinding>d__95(0)
		{
			<>4__this = this,
			index = index
		};
	}

	[IteratorStateMachine(typeof(<DelayReplayBufferSaving>d__96))]
	private IEnumerator DelayReplayBufferSaving(AudioCall audioPlayer, Vector3 audioLocation)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <DelayReplayBufferSaving>d__96(0)
		{
			<>4__this = this,
			audioPlayer = audioPlayer,
			audioLocation = audioLocation
		};
	}

	private void Feedback(AudioCall audioPlayer, Vector3 AudioLocation)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		if (enableSFX && audioPlayer != null)
		{
			AudioManager.PlaySound(audioPlayer, AudioLocation, false);
		}
		if (hapticsDuration > 0f)
		{
			HapticFeedback(hapticsDuration);
		}
	}

	public static void playConfirmationSFX()
	{
		confirmationSFXPlayer.GetComponent<AudioSource>().Play();
	}

	private static void PopulateUserDataIfNeeded()
	{
		string folderName = "sfx_assets";
		string text = "UserData/OBS_Control_API//" + folderName + "/";
		if (!Directory.Exists(text))
		{
			Directory.CreateDirectory(text);
		}
		if (!Directory.GetFiles(text).Length.Equals(0))
		{
			return;
		}
		Assembly assembly = typeof(OBS).Assembly;
		IEnumerable<string> enumerable = from r in assembly.GetManifestResourceNames()
			where r.StartsWith("OBS_Control_API.Resources." + folderName + ".", StringComparison.OrdinalIgnoreCase)
			select r;
		foreach (string item in enumerable)
		{
			string path = item.Substring(("OBS_Control_API.Resources." + folderName + ".").Length);
			string path2 = Path.Combine(text, path);
			using Stream stream = assembly.GetManifestResourceStream(item);
			using FileStream destination = File.Create(path2);
			stream.CopyTo(destination);
		}
	}

	public static void HapticFeedback(float duration)
	{
		if ((Object)(object)playerHaptics != (Object)null)
		{
			areHapticsPlaying = true;
			MelonCoroutines.Start(StopHapticFeedback(duration));
		}
	}

	[IteratorStateMachine(typeof(<StopHapticFeedback>d__101))]
	public static IEnumerator StopHapticFeedback(float duration)
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <StopHapticFeedback>d__101(0)
		{
			duration = duration
		};
	}

	public override void OnUpdate()
	{
		if ((Object)(object)playerHaptics != (Object)null && areHapticsPlaying)
		{
			playerHaptics.PlayControllerHaptics(1f, 1f, 1f, 1f);
		}
	}

	public override void OnFixedUpdate()
	{
		//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_00cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		if (!bindingLocked[0] && keyBindings[0] != 0 && LeftController.GetPrimary() > 0f && LeftController.GetSecondary() > 0f)
		{
			Log("Left key binding activated");
			new Thread((ThreadStart)delegate
			{
				//IL_0004: Unknown result type (might be due to invalid IL or missing references)
				ExecuteKeyBinding(0, GetPlayerUILocation());
			}).Start();
		}
		if (!bindingLocked[1] && keyBindings[1] != 0 && RightController.GetPrimary() > 0f && RightController.GetSecondary() > 0f)
		{
			Vector3 zero = Vector3.zero;
			try
			{
				zero = ((Component)Singleton<PlayerManager>.Instance.LocalPlayer.Controller).gameObject.transform.GetChild(4).GetChild(0).position;
			}
			catch (Exception)
			{
			}
			Log("Right key binding activated");
			new Thread((ThreadStart)delegate
			{
				//IL_0004: Unknown result type (might be due to invalid IL or missing references)
				ExecuteKeyBinding(1, GetPlayerUILocation());
			}).Start();
		}
	}

	private Vector3 GetPlayerUILocation()
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_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_003b: 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_003f: Unknown result type (might be due to invalid IL or missing references)
		Vector3 result = Vector3.zero;
		try
		{
			result = ((Component)Singleton<PlayerManager>.Instance.LocalPlayer.Controller).gameObject.transform.GetChild(4).GetChild(0).position;
		}
		catch (Exception)
		{
		}
		return result;
	}

	public override void OnApplicationQuit()
	{
		if (connectionManager.IsConnected() && isReplayBufferActive && stopReplayBufferAtShutdown)
		{
			Log("Stopping replay buffer");
			StopReplayBuffer();
		}
		Disconnect(stopReconnect: false);
	}

	public static bool IsConnected()
	{
		return connectionManager.IsConnected();
	}

	public static bool IsReplayBufferActive()
	{
		return isReplayBufferActive;
	}

	public static bool IsRecordingActive()
	{
		return isRecordingActive;
	}

	public static bool IsStreamActive()
	{
		return isStreamActive;
	}

	public static string SendRequest(string requestType, object parameters)
	{
		return requestManager.SendRequest(requestType, parameters);
	}

	public static string SendRequest(string requestType)
	{
		return SendRequest(requestType, null);
	}

	public static RequestResponse.GetReplayBufferStatus GetReplayBufferStatus()
	{
		string text = SendRequest("GetReplayBufferStatus");
		if (text == null)
		{
			return null;
		}
		return JsonConvert.DeserializeObject<RequestResponse.GetReplayBufferStatus>(text);
	}

	public static RequestResponse.GetRecordStatus GetRecordStatus()
	{
		string text = SendRequest("GetRecordStatus");
		if (text == null)
		{
			return null;
		}
		return JsonConvert.DeserializeObject<RequestResponse.GetRecordStatus>(text);
	}

	public static RequestResponse.GetStreamStatus GetStreamStatus()
	{
		string text = SendRequest("GetStreamStatus");
		if (text == null)
		{
			return null;
		}
		return JsonConvert.DeserializeObject<RequestResponse.GetStreamStatus>(text);
	}

	public static RequestResponse.GetVersion GetVersion()
	{
		string text = SendRequest("GetVersion");
		if (text == null)
		{
			return null;
		}
		return JsonConvert.DeserializeObject<RequestResponse.GetVersion>(text);
	}

	public static bool StartStream()
	{
		string text = SendRequest("StartStream");
		return text != null;
	}

	public static bool StopStream()
	{
		string text = SendRequest("StopStream");
		return text != null;
	}

	public static bool StartReplayBuffer()
	{
		string text = SendRequest("StartReplayBuffer");
		return text != null;
	}

	public static bool StopReplayBuffer()
	{
		string text = SendRequest("StopReplayBuffer");
		return text != null;
	}

	public static bool SaveReplayBuffer()
	{
		string text = SendRequest("SaveReplayBuffer");
		return text != null;
	}

	public static RequestResponse.GetLastReplayBufferReplay GetLastReplayBufferReplay()
	{
		string text = SendRequest("GetLastReplayBufferReplay");
		if (text == null)
		{
			return null;
		}
		return JsonConvert.DeserializeObject<RequestResponse.GetLastReplayBufferReplay>(text);
	}

	public static bool StartRecord()
	{
		string text = SendRequest("StartRecord");
		return text != null;
	}

	public static RequestResponse.StopRecord StopRecord()
	{
		string text = SendRequest("StopRecord");
		if (text == null)
		{
			return null;
		}
		return JsonConvert.DeserializeObject<RequestResponse.StopRecord>(text);
	}

	public static RequestResponse.ToggleRecord ToggleRecord()
	{
		string text = SendRequest("ToggleRecord");
		if (text == null)
		{
			return null;
		}
		return JsonConvert.DeserializeObject<RequestResponse.ToggleRecord>(text);
	}

	public static bool PauseRecord()
	{
		string text = SendRequest("PauseRecord");
		return text != null;
	}

	public static bool ResumeRecord()
	{
		string text = SendRequest("ResumeRecord");
		return text != null;
	}

	public static bool SplitRecordFile()
	{
		string text = SendRequest("SplitRecordFile");
		return text != null;
	}

	public static RequestResponse.GetRecordDirectory GetRecordDirectory()
	{
		string text = SendRequest("GetRecordDirectory");
		if (text == null)
		{
			return null;
		}
		return JsonConvert.DeserializeObject<RequestResponse.GetRecordDirectory>(text);
	}

	public static bool SetRecordDirectory(string recordDirectory)
	{
		Dictionary<string, object> parameters = new Dictionary<string, object> { ["recordDirectory"] = recordDirectory };
		string text = SendRequest("SetRecordDirectory", parameters);
		return text != null;
	}

	public static RequestResponse.GetCurrentProgramScene GetCurrentProgramScene()
	{
		string text = SendRequest("GetCurrentProgramScene");
		if (text == null)
		{
			return null;
		}
		return JsonConvert.DeserializeObject<RequestResponse.GetCurrentProgramScene>(text);
	}

	public static bool SaveSourceScreenshot()
	{
		string text = (isWindows ? "\\" : "/");
		string text2 = "Screenshot " + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + ".png";
		return SaveSourceScreenshot(recordingDirectory + text + text2);
	}

	public static bool SaveSourceScreenshot(string imageFilePath)
	{
		return SaveSourceScreenshot(sceneUuid, imageFilePath);
	}

	public static bool SaveSourceScreenshot(string sourceUuid, string imageFilePath)
	{
		Dictionary<string, object> parameters = new Dictionary<string, object>
		{
			["sourceUuid"] = sourceUuid,
			["imageFormat"] = "png",
			["imageFilePath"] = imageFilePath
		};
		string text = SendRequest("SaveSourceScreenshot", parameters);
		return text != null;
	}

	private void SetMainStatus()
	{
		try
		{
			isReplayBufferActive = GetReplayBufferStatus().outputActive;
			RequestResponse.GetRecordStatus recordStatus = GetRecordStatus();
			isRecordingActive = recordStatus.outputActive && !recordStatus.outputPaused;
			isStreamActive = GetStreamStatus().outputActive;
			sceneUuid = GetCurrentProgramScene().sceneUuid;
			isWindows = GetVersion().platform == "windows";
			recordingDirectory = GetRecordDirectory().recordDirectory;
		}
		catch (Exception ex)
		{
			MelonLogger.Error(ex.Message);
		}
	}
}
public class Event
{
	public class ReplayBufferStateChanged
	{
		public bool outputActive { get; set; }

		public string outputState { get; set; }
	}

	public class ReplayBufferSaved
	{
		public string savedReplayPath { get; set; }
	}

	public class RecordStateChanged
	{
		public bool outputActive { get; set; }

		public string outputPath { get; set; }

		public string outputState { get; set; }
	}

	public class RecordFileChanged
	{
		public string newOutputPath { get; set; }
	}

	public class ScreenshotSaved
	{
		public string savedScreenshotPath { get; set; }
	}

	public object eventData { get; set; }

	public int eventIntent { get; set; }

	public string eventType { get; set; }
}
public static class BuildInfo
{
	public const string ModName = "OBS_Control_API";

	public const string ModVersion = "2.0.2";

	public const string Description = "Manages a websocket connection to OBS";

	public const string Author = "Kalamart";

	public const string Company = "";
}
public enum OpCode
{
	Hello = 0,
	Identify = 1,
	Identified = 2,
	Reidentify = 3,
	Event = 5,
	Request = 6,
	RequestResponse = 7,
	RequestBatch = 8,
	RequestBatchResponse = 9
}
public class Msg
{
	public object d { get; set; }

	public int op { get; set; }
}
public class Hello
{
	public class Authentication
	{
		public string challenge { get; set; }

		public string salt { get; set; }
	}

	public Authentication authentication { get; set; }

	public string obsWebSocketVersion { get; set; }

	public string rpcVersion { get; set; }
}
public class RequestResponseRaw
{
	public string requestId { get; set; }

	public RequestResponse.RequestStatus requestStatus { get; set; }

	public object responseData { get; set; }
}
public class RequestResponse
{
	public class RequestStatus
	{
		public bool result { get; set; }

		public int code { get; set; }

		public string comment { get; set; }
	}

	public enum Code
	{
		Success = 100,
		NotReady = 207
	}

	public class GetReplayBufferStatus
	{
		public bool outputActive { get; set; }
	}

	public class ToggleRecord
	{
		public bool outputActive { get; set; }
	}

	public class StopRecord
	{
		public string outputPath { get; set; }
	}

	public class GetRecordStatus
	{
		public bool outputActive { get; set; }

		public bool outputPaused { get; set; }

		public string outputTimecode { get; set; }

		public int outputDuration { get; set; }

		public long outputBytes { get; set; }
	}

	public class GetStreamStatus
	{
		public bool outputActive { get; set; }

		public bool outputReconnecting { get; set; }

		public string outputTimecode { get; set; }

		public int outputDuration { get; set; }

		public float outputCongestion { get; set; }

		public long outputBytes { get; set; }

		public int outputSkippedFrames { get; set; }

		public int outputTotalFrames { get; set; }
	}

	public class GetVersion
	{
		public string obsVersion { get; set; }

		public string obsWebSocketVersion { get; set; }

		public int rpcVersion { get; set; }

		public string[] availableRequests { get; set; }

		public string[] supportedImageFormats { get; set; }

		public string platform { get; set; }

		public string platformDescription { get; set; }
	}

	public class GetLastReplayBufferReplay
	{
		public string savedReplayPath { get; set; }
	}

	public class GetRecordDirectory
	{
		public string recordDirectory { get; set; }
	}

	public class GetCurrentProgramScene
	{
		public string sceneName { get; set; }

		public string sceneUuid { get; set; }

		public string currentProgramSceneName { get; set; }

		public string currentProgramSceneUuid { get; set; }
	}

	public RequestStatus requestStatus { get; set; }

	public string responseData { get; set; }
}
public class Preferences
{
	internal static MelonPreferences_Category GeneralCategory;

	internal static MelonPreferences_Entry<bool> EnableReplayBuffer;

	internal static MelonPreferences_Entry<float> ReplayBufferDelay;

	internal static MelonPreferences_Entry<ControllerKeyActions> BindingLeft;

	internal static MelonPreferences_Entry<ControllerKeyActions> BindingRight;

	internal static MelonPreferences_Entry<float> HapticDuration;

	internal static MelonPreferences_Entry<bool> AudioFeedback;

	internal static MelonPreferences_Entry<float> AudioVolume;

	internal static MelonPreferences_Category Connection;

	internal static MelonPreferences_Entry<string> IpAddress;

	internal static MelonPreferences_Entry<int> Port;

	internal static MelonPreferences_Entry<string> Password;

	internal static void PrefInit()
	{
		if (!Directory.Exists("UserData/OBS_Control_API/"))
		{
			Directory.CreateDirectory("UserData/OBS_Control_API/");
		}
		GeneralCategory = MelonPreferences.CreateCategory("OBS_Control_API", "General Settings");
		GeneralCategory.SetFilePath(Path.Combine("UserData/OBS_Control_API/", "config.cfg"));
		EnableReplayBuffer = GeneralCategory.CreateEntry<bool>("EnableReplayBuffer", true, "Force Enable Replay Buffer", "Never forget to start the replay buffer again!\nThe mod will start it for you on connection and stop it as you close the game.", false, false, (ValueValidator)null, (string)null);
		ReplayBufferDelay = GeneralCategory.CreateEntry<float>("ReplayBufferDelay", 0f, "Replay Save Delay", "Delay saving a replay buffer after pressing the keybind so your clips don't end suddenly", false, false, (ValueValidator)null, (string)null);
		BindingLeft = GeneralCategory.CreateEntry<ControllerKeyActions>("BindingLeft", ControllerKeyActions.SaveReplayBuffer, "Left Controller Binding", "Action to perform when both buttons on the left controller are being pressed.", false, false, (ValueValidator)null, (string)null);
		BindingRight = GeneralCategory.CreateEntry<ControllerKeyActions>("BindingRight", ControllerKeyActions.SaveScreenshot, "Right Controller Binding", "Action to perform when both buttons on the right controller are being pressed.", false, false, (ValueValidator)null, (string)null);
		HapticDuration = GeneralCategory.CreateEntry<float>("HapticDuration", 0.2f, "Haptic Feedback Duration", "Duration of the haptic impulse when an action is successful (set to 0 to disable).", false, false, (ValueValidator)null, (string)null);
		AudioFeedback = GeneralCategory.CreateEntry<bool>("AudioFeedback", true, "Audio Feedback", "Set to true to get a sound effect when an action is successful.", false, false, (ValueValidator)null, (string)null);
		AudioVolume = GeneralCategory.CreateEntry<float>("AudioVolume", 1f, "Audio Feedback Volume", "Volume of thesound effect when an action is successful.", false, false, (ValueValidator)null, (string)null);
		Connection = MelonPreferences.CreateCategory("WebsocketConnection", "OBS Connection");
		Connection.SetFilePath(Path.Combine("UserData/OBS_Control_API/", "config.cfg"));
		IpAddress = Connection.CreateEntry<string>("IpAddress", "localhost", "IP Address", "IP address of the OBS websocket server.", false, false, (ValueValidator)null, (string)null);
		Port = Connection.CreateEntry<int>("Port", 4455, "Port", "Port used by the OBS websocket server.", false, false, (ValueValidator)null, (string)null);
		Password = Connection.CreateEntry<string>("Password", "", "Websocket Password", "Password for the OBS websocket server.", false, false, (ValueValidator)null, (string)null);
	}
}
internal enum ControllerKeyActions
{
	Nothing,
	SaveReplayBuffer,
	StartRecording,
	StopRecording,
	ToggleRecording,
	SaveScreenshot
}