using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using OBSWebsocketDotNet; using OBSWebsocketDotNet.Communication; using OBSWebsocketDotNet.Types; using OBSWebsocketDotNet.Types.Events; using WeatherElectric.OBSBridge; using WeatherElectric.OBSBridge.Melon; using WeatherElectric.OBSBridge.OBS; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Bridge between Unity and OBS")] [assembly: AssemblyDescription("Bridge between Unity and OBS")] [assembly: AssemblyCompany("Weather Electric")] [assembly: AssemblyProduct("OBSBridge")] [assembly: AssemblyCopyright("Developed by Mabel Amber")] [assembly: AssemblyTrademark("Weather Electric")] [assembly: AssemblyFileVersion("2.0.0")] [assembly: MelonInfo(typeof(Main), "OBSBridge", "2.0.0", "Mabel Amber", null)] [assembly: MelonColor(255, 255, 255, 255)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyVersion("2.0.0.0")] [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 WeatherElectric.OBSBridge { public class Main : MelonMod { internal const string Name = "OBSBridge"; internal const string Description = "Bridge between Unity and OBS"; internal const string Author = "Mabel Amber"; internal const string Company = "Weather Electric"; internal const string Version = "2.0.0"; internal const string DownloadLink = null; public override void OnInitializeMelon() { ModConsole.Setup(((MelonBase)this).LoggerInstance); Preferences.Setup(); ObsBridge.InitHooks(); ObsBridge.Connect(); } public override void OnApplicationQuit() { ObsBridge.Disconnect(); } } } namespace WeatherElectric.OBSBridge.OBS { public static class ObsBridge { private static readonly OBSWebsocket Obs = new OBSWebsocket(); public static Action OnConnected; public static Action OnDisconnected; public static Action OnRecordStateChanged; public static Action OnReplayBufferSaved; public static Action OnReplayBufferStateChanged; public static Action OnStreamStateChanged; public static Action OnSceneChanged; public static Action OnSceneCreated; public static Action OnVirtualCamStateChanged; public static Action OnSceneRemoved; public static bool Connected { get; private set; } public static bool ReplayBufferActive { get; private set; } public static bool StreamActive { get; private set; } public static bool RecordActive { get; private set; } public static bool RecordPaused { get; private set; } public static bool VirtualCamActive { get; private set; } private static void RecordStateChanged(object sender, RecordStateChangedEventArgs e) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected I4, but got Unknown OnRecordStateChanged?.Invoke(sender, e); OutputState state = ((OutputStateChanged)e.OutputState).State; switch ((int)state) { case 0: case 1: RecordActive = true; break; case 2: case 3: RecordActive = false; break; case 4: RecordPaused = true; break; case 5: RecordPaused = false; break; default: throw new ArgumentOutOfRangeException(); } } private static void ReplaySaved(object sender, ReplayBufferSavedEventArgs e) { OnReplayBufferSaved?.Invoke(sender, e); } private static void ReplayStateChanged(object sender, ReplayBufferStateChangedEventArgs e) { ReplayBufferActive = e.OutputState.IsActive; OnReplayBufferStateChanged?.Invoke(sender, e); } private static void StreamStateChanged(object sender, StreamStateChangedEventArgs e) { StreamActive = e.OutputState.IsActive; OnStreamStateChanged?.Invoke(sender, e); } private static void SceneChanged(object sender, ProgramSceneChangedEventArgs e) { OnSceneChanged?.Invoke(sender, e); } private static void VirtualCamStateChanged(object sender, VirtualcamStateChangedEventArgs e) { VirtualCamActive = e.OutputState.IsActive; OnVirtualCamStateChanged?.Invoke(sender, e); } private static void SceneCreated(object sender, SceneCreatedEventArgs e) { OnSceneCreated?.Invoke(sender, e); } private static void SceneRemoved(object sender, SceneRemovedEventArgs e) { OnSceneRemoved?.Invoke(sender, e); } private static void ObsConnected(object sender, EventArgs e) { Connected = true; InitValues(); OnConnected?.Invoke(sender, e); ModConsole.Msg("Connected to OBS!", 1); } private static void ObsDisconnected(object sender, ObsDisconnectionInfo e) { Connected = false; OnDisconnected?.Invoke(sender, e); ModConsole.Msg("Disconnected from OBS!", 1); } private static void InitValues() { ReplayBufferActive = Obs.GetReplayBufferStatus(); StreamActive = Obs.GetStreamStatus().IsActive; RecordActive = Obs.GetRecordStatus().IsRecording; RecordPaused = Obs.GetRecordStatus().IsRecordingPaused; VirtualCamActive = Obs.GetVirtualCamStatus().IsActive; } internal static void Connect() { //IL_002c: Expected O, but got Unknown //IL_0045: Expected O, but got Unknown ModConsole.Msg("Attempting to connect to OBS...", 1); try { Obs.ConnectAsync(Preferences.WebsocketURL.Value, Preferences.WebsocketPassword.Value); } catch (AuthFailureException val) { AuthFailureException val2 = val; ModConsole.Error("Failed to authenticate with OBS. Error: " + ((Exception)(object)val2).Message); } catch (ErrorResponseException val3) { ErrorResponseException val4 = val3; ModConsole.Error("Failed to connect to OBS. Error: " + ((Exception)(object)val4).Message); } catch (Exception ex) { ModConsole.Error("Failed to connect to OBS. Error: " + ex.Message); } } internal static void Disconnect() { ModConsole.Msg("Disconnecting from OBS...", 1); Obs.Disconnect(); Connected = false; } internal static void InitHooks() { Obs.Connected += ObsConnected; Obs.Disconnected += ObsDisconnected; Obs.RecordStateChanged += RecordStateChanged; Obs.ReplayBufferSaved += ReplaySaved; Obs.ReplayBufferStateChanged += ReplayStateChanged; Obs.StreamStateChanged += StreamStateChanged; Obs.CurrentProgramSceneChanged += SceneChanged; Obs.VirtualcamStateChanged += VirtualCamStateChanged; Obs.SceneCreated += SceneCreated; Obs.SceneRemoved += SceneRemoved; } public static List GetScenes() { return Obs.GetSceneList().Scenes; } public static void SetScene(string sceneName) { Obs.SetCurrentProgramScene(sceneName); } public static string GetActiveScene() { return Obs.GetCurrentProgramScene(); } public static void StartRecording() { //IL_0020: Expected O, but got Unknown if (RecordActive) { return; } ModConsole.Msg("Starting recording...", 1); try { Obs.StartRecord(); } catch (ErrorResponseException val) { ErrorResponseException val2 = val; ModConsole.Error("Failed to start recording. Error: " + ((Exception)(object)val2).Message); } catch (Exception ex) { ModConsole.Error("Failed to start recording. Error: " + ex.Message); } } public static void PauseRecording() { //IL_0020: Expected O, but got Unknown if (RecordPaused) { return; } ModConsole.Msg("Pausing recording...", 1); try { Obs.PauseRecord(); } catch (ErrorResponseException val) { ErrorResponseException val2 = val; ModConsole.Error("Failed to pause recording. Error: " + ((Exception)(object)val2).Message); } catch (Exception ex) { ModConsole.Error("Failed to pause recording. Error: " + ex.Message); } } public static void ResumeRecording() { //IL_0020: Expected O, but got Unknown if (!RecordPaused) { return; } ModConsole.Msg("Resuming recording...", 1); try { Obs.ResumeRecord(); } catch (ErrorResponseException val) { ErrorResponseException val2 = val; ModConsole.Error("Failed to resume recording. Error: " + ((Exception)(object)val2).Message); } catch (Exception ex) { ModConsole.Error("Failed to resume recording. Error: " + ex.Message); } } public static void StopRecording() { //IL_0021: Expected O, but got Unknown if (!RecordActive) { return; } ModConsole.Msg("Stopping recording...", 1); try { Obs.StopRecord(); } catch (ErrorResponseException val) { ErrorResponseException val2 = val; ModConsole.Error("Failed to stop recording. Error: " + ((Exception)(object)val2).Message); } catch (Exception ex) { ModConsole.Error("Failed to stop recording. Error: " + ex.Message); } } public static string GetRecordDirectory() { return Obs.GetRecordDirectory(); } public static void StartStreaming() { //IL_0020: Expected O, but got Unknown if (StreamActive) { return; } ModConsole.Msg("Starting stream...", 1); try { Obs.StartStream(); } catch (ErrorResponseException val) { ErrorResponseException val2 = val; ModConsole.Error("Failed to start streaming. Error: " + ((Exception)(object)val2).Message); } catch (Exception ex) { ModConsole.Error("Failed to start streaming. Error: " + ex.Message); } } public static void StopStreaming() { //IL_0020: Expected O, but got Unknown if (!StreamActive) { return; } ModConsole.Msg("Stopping stream...", 1); try { Obs.StopStream(); } catch (ErrorResponseException val) { ErrorResponseException val2 = val; ModConsole.Error("Failed to stop streaming. Error: " + ((Exception)(object)val2).Message); } catch (Exception ex) { ModConsole.Error("Failed to stop streaming. Error: " + ex.Message); } } public static void StartReplayBuffer() { //IL_0020: Expected O, but got Unknown if (ReplayBufferActive) { return; } ModConsole.Msg("Starting replay buffer...", 1); try { Obs.StartReplayBuffer(); } catch (ErrorResponseException val) { ErrorResponseException val2 = val; ModConsole.Error("Failed to start replay buffer. Error: " + ((Exception)(object)val2).Message); } catch (Exception ex) { ModConsole.Error("Failed to start replay buffer. Error: " + ex.Message); } } public static void StopReplayBuffer() { //IL_0020: Expected O, but got Unknown if (!ReplayBufferActive) { return; } ModConsole.Msg("Stopping replay buffer...", 1); try { Obs.StopReplayBuffer(); } catch (ErrorResponseException val) { ErrorResponseException val2 = val; ModConsole.Error("Failed to stop replay buffer. Error: " + ((Exception)(object)val2).Message); } catch (Exception ex) { ModConsole.Error("Failed to stop replay buffer. Error: " + ex.Message); } } public static void SaveReplayBuffer() { //IL_0020: Expected O, but got Unknown if (!ReplayBufferActive) { return; } ModConsole.Msg("Saving replay buffer...", 1); try { Obs.SaveReplayBuffer(); } catch (ErrorResponseException val) { ErrorResponseException val2 = val; ModConsole.Error("Failed to save replay buffer. Error: " + ((Exception)(object)val2).Message); } catch (Exception ex) { ModConsole.Error("Failed to save replay buffer. Error: " + ex.Message); } } public static void TriggerHotkeyByName(string hotkeyName) { Obs.TriggerHotkeyByName(hotkeyName); } } } namespace WeatherElectric.OBSBridge.Melon { internal static class ModConsole { private static Instance _logger; public static void Setup(Instance loggerInstance) { _logger = loggerInstance; } public static void Msg(object obj, int loggingMode = 0) { string text = ((loggingMode == 1) ? $"[DEBUG] {obj}" : obj.ToString()); ConsoleColor consoleColor = ((loggingMode == 1) ? ConsoleColor.Yellow : ConsoleColor.Gray); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Msg(consoleColor, text); } } public static void Msg(string txt, int loggingMode = 0) { string text = ((loggingMode == 1) ? ("[DEBUG] " + txt) : txt); ConsoleColor consoleColor = ((loggingMode == 1) ? ConsoleColor.Yellow : ConsoleColor.Gray); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Msg(consoleColor, text); } } public static void Msg(ConsoleColor txtcolor, object obj, int loggingMode = 0) { string text = ((loggingMode == 1) ? $"[DEBUG] {obj}" : obj.ToString()); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Msg(txtcolor, text); } } public static void Msg(ConsoleColor txtcolor, string txt, int loggingMode = 0) { string text = ((loggingMode == 1) ? ("[DEBUG] " + txt) : txt); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Msg(txtcolor, text); } } public static void Msg(string txt, int loggingMode = 0, params object[] args) { string text = ((loggingMode == 1) ? ("[DEBUG] " + txt) : txt); ConsoleColor consoleColor = ((loggingMode == 1) ? ConsoleColor.Yellow : ConsoleColor.Gray); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Msg(consoleColor, text, args); } } public static void Msg(ConsoleColor txtcolor, string txt, int loggingMode = 0, params object[] args) { string text = ((loggingMode == 1) ? ("[DEBUG] " + txt) : txt); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Msg(txtcolor, text, args); } } public static void Error(object obj, int loggingMode = 0) { string text = ((loggingMode == 1) ? $"[DEBUG] {obj}" : obj.ToString()); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Error(text); } } public static void Error(string txt, int loggingMode = 0) { string text = ((loggingMode == 1) ? ("[DEBUG] " + txt) : txt); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Error(text); } } public static void Error(string txt, int loggingMode = 0, params object[] args) { string text = ((loggingMode == 1) ? ("[DEBUG] " + txt) : txt); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Error(text, args); } } public static void Warning(object obj, int loggingMode = 0) { string text = ((loggingMode == 1) ? $"[DEBUG] {obj}" : obj.ToString()); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Warning(text); } } public static void Warning(string txt, int loggingMode = 0) { string text = ((loggingMode == 1) ? ("[DEBUG] " + txt) : txt); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Warning(text); } } public static void Warning(string txt, int loggingMode = 0, params object[] args) { string text = ((loggingMode == 1) ? ("[DEBUG] " + txt) : txt); if (Preferences.LoggingMode.Value >= loggingMode) { _logger.Warning(text, args); } } } internal static class Preferences { public static readonly MelonPreferences_Category GlobalCategory = MelonPreferences.CreateCategory("Global"); public static readonly MelonPreferences_Category OwnCategory = MelonPreferences.CreateCategory("OBSBridge"); public static MelonPreferences_Entry LoggingMode { get; set; } public static MelonPreferences_Entry WebsocketURL { get; set; } public static MelonPreferences_Entry WebsocketPassword { get; set; } public static void Setup() { LoggingMode = GlobalCategory.GetEntry("LoggingMode") ?? GlobalCategory.CreateEntry("LoggingMode", 0, "Logging Mode", "The level of logging to use. 0 = Important Only, 1 = All", false, false, (ValueValidator)null, (string)null); GlobalCategory.SetFilePath(MelonEnvironment.UserDataDirectory + "/WeatherElectric.cfg"); GlobalCategory.SaveToFile(false); WebsocketURL = OwnCategory.CreateEntry("WebsocketURL", "ws://127.0.0.1:4455", "Websocket URL", "The URL to use for the websocket connection. Usually don't have to change this.", false, false, (ValueValidator)null, (string)null); WebsocketPassword = OwnCategory.CreateEntry("WebsocketPassword", "REPLACEME", "Websocket Password", "The password to use for the websocket connection. Change this to the password you set in OBS.", false, false, (ValueValidator)null, (string)null); OwnCategory.SetFilePath(MelonEnvironment.UserDataDirectory + "/WeatherElectric.cfg"); OwnCategory.SaveToFile(false); ModConsole.Msg("Finished preferences setup for OBSBridge", 1); } } }