using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading.Tasks; using Alpha; using Alpha.Core.Command; using Alpha.Core.Util; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Fleck; using Fomo; using Fomo.Core; using FomoWebSocket.Commands; using FomoWebSocket.Sinks; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("AndrewLin")] [assembly: AssemblyConfiguration("Publish")] [assembly: AssemblyDescription("FomoWebSocket: WebSocket relay add-on for the Fomo mod. Use /fomohelp and look for /fomowebsocket commands")] [assembly: AssemblyFileVersion("1.2.9.0")] [assembly: AssemblyInformationalVersion("1.2.9+93bf6c254192ca3bde9494f062dab68782362cda")] [assembly: AssemblyProduct("AndrewLin.FomoWebSocket")] [assembly: AssemblyTitle("AndrewLin.FomoWebSocket")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/andrewlimforfun/ot-mods")] [assembly: AssemblyVersion("1.2.9.0")] 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; } } } namespace FomoWebSocket { public static class BuildInfo { public const string Version = "1.2.9"; } [BepInPlugin("com.andrewlin.ontogether.fomo.websocket", "FomoWebSocket", "1.2.9")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class FomoWebSocketPlugin : BaseUnityPlugin { public const string ModGUID = "com.andrewlin.ontogether.fomo.websocket"; public const string ModName = "FomoWebSocket"; public const string ModVersion = "1.2.9"; public const int DefaultWebSocketChatPort = 8765; public static ConfigEntry? EnableFeature { get; private set; } public static ConfigEntry? WebSocketChatPort { get; private set; } public static ConfigEntry? MessageFormat { get; private set; } public static ConfigEntry? NotificationFormat { get; private set; } public static WebSocketManager? WsManager { get; private set; } private void Awake() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"FomoWebSocket v1.2.9 loaded!"); InitConfig(); WsManager = new WebSocketManager(); ChatSinkManager sinkManager = FomoPlugin.SinkManager; if (sinkManager != null) { sinkManager.Register((IChatSink)(object)new WebSocketChatSink()); } ChatCommandManager commandManager = AlphaPlugin.CommandManager; if (commandManager != null) { commandManager.Register((IChatCommand)(object)new FomoWebSocketCommand()); } ChatCommandManager commandManager2 = AlphaPlugin.CommandManager; if (commandManager2 != null) { commandManager2.Register((IChatCommand)(object)new FomoWebSocketPortCommand()); } ChatCommandManager commandManager3 = AlphaPlugin.CommandManager; if (commandManager3 != null) { commandManager3.Register((IChatCommand)(object)new FomoWebSocketMessageFormatCommand()); } ChatCommandManager commandManager4 = AlphaPlugin.CommandManager; if (commandManager4 != null) { commandManager4.Register((IChatCommand)(object)new FomoWebSocketNotificationFormatCommand()); } ConfigEntry? enableFeature = EnableFeature; if (enableFeature != null && enableFeature.Value) { WsManager.Start(); } } private void InitConfig() { EnableFeature = ((BaseUnityPlugin)this).Config.Bind("General", "EnableFeature", true, "Enable or disable chat file logging."); WebSocketChatPort = ((BaseUnityPlugin)this).Config.Bind("WebSocket", "WebSocketChatPort", 8765, "Port for the WebSocket chat server."); MessageFormat = ((BaseUnityPlugin)this).Config.Bind("Formatting", "MessageFormat", "[{timestamp:HH:mm} {channel:short}] {username}: {message}", "Format string for chat messages sent to Telegram.\nPlaceholders: {timestamp[:fmt]}, {channel[:short]}, {username}, {message}, {distance}, {source}, {playerid}"); NotificationFormat = ((BaseUnityPlugin)this).Config.Bind("Formatting", "NotificationFormat", "[{timestamp:HH:mm}] {message}", "Format string for system notifications sent to Telegram.\nPlaceholders: {timestamp[:fmt]}, {channel[:short]}, {username}, {message}, {distance}, {source}, {playerid}"); } private void OnDestroy() { WsManager?.Stop(); } } public class WebSocketManager : IDisposable { private readonly ManualLogSource _log = Logger.CreateLogSource("FomoWebSocket.WSM"); private WebSocketServer? _server; private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); public bool IsRunning => _server != null; public void Start() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown int num = FomoWebSocketPlugin.WebSocketChatPort?.Value ?? 8765; if (IsRunning) { ChatUtils.AddGlobalNotification("WebSocket is already running."); return; } _server = new WebSocketServer($"ws://0.0.0.0:{num}", true); _server.Start((Action)delegate(IWebSocketConnection socket) { socket.OnOpen = delegate { _log.LogInfo((object)$"Client connected: {socket.ConnectionInfo.Id} from {socket.ConnectionInfo.ClientIpAddress}"); _clients[socket.ConnectionInfo.Id] = socket; Broadcast("[WSM] Client connected: " + socket.ConnectionInfo.ClientIpAddress); }; socket.OnClose = delegate { _log.LogInfo((object)$"Client disconnected: {socket.ConnectionInfo.Id} from {socket.ConnectionInfo.ClientIpAddress}"); _clients.TryRemove(socket.ConnectionInfo.Id, out IWebSocketConnection _); Broadcast("[WSM] Client disconnected: " + socket.ConnectionInfo.ClientIpAddress); }; socket.OnMessage = delegate(string text) { FomoPlugin.DispatchIncomingText(text); }; socket.OnError = delegate(Exception ex) { _log.LogWarning((object)$"Client error {socket.ConnectionInfo.Id}: {ex.Message}"); _clients.TryRemove(socket.ConnectionInfo.Id, out IWebSocketConnection _); Broadcast("[WSM] Client disconnected due to error: " + socket.ConnectionInfo.ClientIpAddress); }; }); _log.LogInfo((object)$"WebSocketManager started on ws://0.0.0.0:{num}/"); ChatUtils.AddGlobalNotification($"WebSocketManager started on ws://0.0.0.0:{num}/"); } public void Stop() { ChatUtils.AddGlobalNotification("WebSocket stopping..."); foreach (KeyValuePair client in _clients) { try { client.Value.Close(); } catch { } } _clients.Clear(); try { WebSocketServer? server = _server; if (server != null) { server.Dispose(); } } catch { } _server = null; _log.LogInfo((object)"WebSocket server stopped."); } public void Broadcast(string message) { foreach (KeyValuePair client in _clients) { try { if (client.Value.IsAvailable) { client.Value.Send(message); } } catch (Exception ex) { _log.LogWarning((object)$"Failed to send to client {client.Key}: {ex.Message}"); _clients.TryRemove(client.Key, out IWebSocketConnection _); } } } public void Dispose() { Stop(); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "AndrewLin.FomoWebSocket"; public const string PLUGIN_NAME = "AndrewLin.FomoWebSocket"; public const string PLUGIN_VERSION = "1.2.9"; } } namespace FomoWebSocket.Sinks { public class WebSocketChatSink : IChatSink { private readonly ManualLogSource _log = Logger.CreateLogSource("FomoWebSocket.WSCS"); private const int WarnLimit = 20; private int _warnCount = 0; public Task SendAsync(ChatEntry entry) { try { WebSocketManager wsManager = FomoWebSocketPlugin.WsManager; if (wsManager != null && wsManager.IsRunning) { if (entry.IsNotification) { string message = ChatEntryFormatter.Format(entry, FomoWebSocketPlugin.NotificationFormat?.Value); wsManager.Broadcast(message); } else { string message2 = ChatEntryFormatter.Format(entry, FomoWebSocketPlugin.MessageFormat?.Value); wsManager.Broadcast(message2); } } } catch (Exception ex) { _warnCount++; if (_warnCount <= 20) { _log.LogWarning((object)("Failed to broadcast WebSocket message: " + ex.Message)); } else if (_warnCount == 21) { _log.LogWarning((object)"Further warnings about WebSocket message broadcasting will be suppressed."); } } return Task.CompletedTask; } } } namespace FomoWebSocket.Commands { public class FomoWebSocketMessageFormatCommand : IChatCommand, IComparable { public const string CMD = "fomowebsocketmessageformat"; public string Name => "fomowebsocketmessageformat"; public string ShortName => "fwsmf"; public string Description => "Set or get the WebSocket chat message format."; public string Namespace => "fomo"; public void Execute(string[] args) { ConfigEntry? enableFeature = FomoWebSocketPlugin.EnableFeature; if (enableFeature == null || !enableFeature.Value) { ChatUtils.AddGlobalNotification("WebSocket feature is disabled."); } else if (FomoWebSocketPlugin.MessageFormat == null || FomoWebSocketPlugin.WsManager == null) { ChatUtils.AddGlobalNotification("Fomo WebSocket Mod is not initialized yet."); } else if (args.Length == 0) { ChatUtils.AddGlobalNotification("WebSocket message format: " + FomoWebSocketPlugin.MessageFormat?.Value); } else if (args.Length != 0) { string text = string.Join(" ", args); if (string.IsNullOrWhiteSpace(text)) { ChatUtils.AddGlobalNotification("Please enter a valid WebSocket message format."); return; } FomoWebSocketPlugin.MessageFormat.Value = text; ChatUtils.AddGlobalNotification("WebSocket message format is now set to: " + text + "."); } } } public class FomoWebSocketNotificationFormatCommand : IChatCommand, IComparable { public const string CMD = "fomowebsocketnotificationformat"; public string Name => "fomowebsocketnotificationformat"; public string ShortName => "fwsnf"; public string Description => "Set or get the WebSocket notification message format."; public string Namespace => "fomo"; public void Execute(string[] args) { ConfigEntry? enableFeature = FomoWebSocketPlugin.EnableFeature; if (enableFeature == null || !enableFeature.Value) { ChatUtils.AddGlobalNotification("WebSocket feature is disabled."); } else if (FomoWebSocketPlugin.NotificationFormat == null || FomoWebSocketPlugin.WsManager == null) { ChatUtils.AddGlobalNotification("Fomo WebSocket Mod is not initialized yet."); } else if (args.Length == 0) { ChatUtils.AddGlobalNotification("WebSocket notification message format: " + FomoWebSocketPlugin.NotificationFormat?.Value); } else if (args.Length != 0) { string text = string.Join(" ", args); if (string.IsNullOrWhiteSpace(text)) { ChatUtils.AddGlobalNotification("Please enter a valid WebSocket notification message format."); return; } FomoWebSocketPlugin.NotificationFormat.Value = text; ChatUtils.AddGlobalNotification("WebSocket notification message format is now set to: " + text + "."); } } } public class FomoWebSocketPortCommand : IChatCommand, IComparable { public const string CMD = "fomowebsocketport"; public string Name => "fomowebsocketport"; public string ShortName => "fwsp"; public string Description => "Set or get the WebSocket chat port. Default (8765)."; public string Namespace => "fomo"; public void Execute(string[] args) { ConfigEntry? enableFeature = FomoWebSocketPlugin.EnableFeature; if (enableFeature == null || !enableFeature.Value) { ChatUtils.AddGlobalNotification("WebSocket feature is disabled."); } else if (FomoWebSocketPlugin.WebSocketChatPort == null) { ChatUtils.AddGlobalNotification("WebSocket config is not initialized yet."); } else if (FomoWebSocketPlugin.WsManager == null) { ChatUtils.AddGlobalNotification("WebSocketManager is not initialized yet."); } else if (args.Length == 0) { ChatUtils.AddGlobalNotification($"Current WebSocket chat port: {FomoWebSocketPlugin.WebSocketChatPort.Value}"); } else if (args.Length != 0) { string s = args[0]; if (!int.TryParse(s, out var result) || result <= 0) { ChatUtils.AddGlobalNotification("Please enter a valid WebSocket port value."); return; } FomoWebSocketPlugin.WebSocketChatPort.Value = result; ChatUtils.AddGlobalNotification($"WebSocket chat port is now set to {result}."); } } } public class FomoWebSocketCommand : IChatCommand, IComparable { public const string CMD = "fomowebsockettoggle"; public string Name => "fomowebsockettoggle"; public string ShortName => "fwst"; public string Description => "Toggle WebSocket chat feature on/off."; public string Namespace => "fomo"; public void Execute(string[] args) { ConfigEntry? enableFeature = FomoWebSocketPlugin.EnableFeature; if (enableFeature == null || !enableFeature.Value) { ChatUtils.AddGlobalNotification("WebSocket feature is disabled."); return; } if (FomoWebSocketPlugin.WebSocketChatPort == null) { ChatUtils.AddGlobalNotification("WebSocket config is not initialized yet."); return; } if (FomoWebSocketPlugin.WsManager == null) { ChatUtils.AddGlobalNotification("WebSocketManager is not initialized yet."); return; } WebSocketManager wsManager = FomoWebSocketPlugin.WsManager; FomoWebSocketPlugin.EnableFeature.Value = !FomoWebSocketPlugin.EnableFeature.Value; ChatUtils.AddGlobalNotification("Fomo WebSocket chat feature is now " + (FomoWebSocketPlugin.EnableFeature.Value ? "enabled" : "disabled") + "."); if (FomoWebSocketPlugin.EnableFeature.Value && !wsManager.IsRunning) { wsManager.Start(); } else if (!FomoWebSocketPlugin.EnableFeature.Value && wsManager.IsRunning) { wsManager.Stop(); } } } }