//MCCScript 1.0 //using System.Collections.Concurrent; //using System.Net.Sockets; //using System.Net.WebSockets; //using System.Text.Json; //using System.Text.Json.Serialization; //using System.Threading.Tasks; //using MinecraftClient.CommandHandler; // IMPORTANT: Change the password below before use! MCC.LoadBot(new WebSocketBot("127.0.0.1", 8043, "CHANGE_THIS_PASSWORD")); //MCCScript Extensions public class WebSocketSession { public string SessionId { get; set; } public WebSocket WebSocket { get; } public bool IsAuthenticated { get; set; } public WebSocketSession(string sessionId, WebSocket webSocket) { SessionId = sessionId; WebSocket = webSocket; IsAuthenticated = false; } } public class WebSocketServer { private HttpListener? _listener; private CancellationTokenSource? _cts; private readonly ConcurrentDictionary _sessions = new(); public event Action? NewSession; public event Action? SessionDropped; public event Action? MessageReceived; public IReadOnlyDictionary Sessions => _sessions; public async Task Start(string ip, int port) { _cts = new CancellationTokenSource(); _listener = new HttpListener(); _listener.Prefixes.Add($"http://{ip}:{port}/"); _listener.Start(); while (!_cts.Token.IsCancellationRequested) { try { var context = await _listener.GetContextAsync().ConfigureAwait(false); if (context.Request.IsWebSocketRequest) _ = Task.Run(() => ProcessWebSocketSession(context, _cts.Token)); else { context.Response.StatusCode = 400; context.Response.Close(); } } catch (ObjectDisposedException) { break; } catch (HttpListenerException) { break; } catch { /* ignore transient errors */ } } } private async Task ProcessWebSocketSession(HttpListenerContext context, CancellationToken ct) { WebSocketContext wsContext; try { wsContext = await context.AcceptWebSocketAsync(null).ConfigureAwait(false); } catch { return; } var ws = wsContext.WebSocket; var sessionId = Guid.NewGuid().ToString("D"); var session = new WebSocketSession(sessionId, ws); _sessions.TryAdd(sessionId, session); NewSession?.Invoke(sessionId, session); var buffer = new byte[4096]; var messageBuffer = new List(); try { while (ws.State == WebSocketState.Open && !ct.IsCancellationRequested) { var result = await ws.ReceiveAsync(new ArraySegment(buffer), ct).ConfigureAwait(false); if (result.MessageType == WebSocketMessageType.Close) break; messageBuffer.AddRange(new ArraySegment(buffer, 0, result.Count)); if (result.EndOfMessage) { var message = Encoding.UTF8.GetString(messageBuffer.ToArray()); messageBuffer.Clear(); MessageReceived?.Invoke(sessionId, message); } } } catch { /* connection dropped */ } finally { _sessions.TryRemove(sessionId, out _); SessionDropped?.Invoke(sessionId); if (ws.State == WebSocketState.Open || ws.State == WebSocketState.CloseReceived) { try { await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Session ended", CancellationToken.None) .ConfigureAwait(false); } catch { /* best effort */ } } ws.Dispose(); } } public bool RenameSession(string oldId, string newId) { if (!_sessions.TryRemove(oldId, out var session)) return false; if (_sessions.ContainsKey(newId)) { _sessions.TryAdd(oldId, session); return false; } session.SessionId = newId; _sessions.TryAdd(newId, session); return true; } public async Task SendToSession(string sessionId, string message) { if (!_sessions.TryGetValue(sessionId, out var session)) return; if (session.WebSocket.State != WebSocketState.Open) return; var bytes = Encoding.UTF8.GetBytes(message); try { await session.WebSocket.SendAsync( new ArraySegment(bytes), WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false); } catch { /* send failed, session will be cleaned up */ } } public void Stop() { _cts?.Cancel(); foreach (var kvp in _sessions) { try { var ws = kvp.Value.WebSocket; if (ws.State == WebSocketState.Open) ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Server stopping", CancellationToken.None) .GetAwaiter().GetResult(); ws.Dispose(); } catch { /* best effort */ } } _sessions.Clear(); try { _listener?.Stop(); } catch { } try { _listener?.Close(); } catch { } } } public class WebSocketBot : ChatBot { private readonly string _ip; private readonly int _port; private readonly string _password; private readonly bool _debugMode; private WebSocketServer? _server; private JsonSerializerOptions _jsonOptions = null!; private readonly List _waitingEvents = new(); private bool _gameJoined; private static readonly Regex Ipv4Regex = new( @"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.){3}(25[0-5]|(2[0-4]|1\d|[1-9]|)\d)$", RegexOptions.Compiled); public WebSocketBot(string ip, int port, string password, bool debugMode = false) { if (!Ipv4Regex.IsMatch(ip) && ip != "+" && ip != "*") throw new ArgumentException($"Invalid IP address: {ip}"); if (port is < 1 or > 65535) throw new ArgumentException($"Invalid port: {port}. Must be between 1 and 65535."); _ip = ip; _port = port; _password = password; _debugMode = debugMode; } public override void Initialize() { _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, IncludeFields = true, Converters = { new JsonStringEnumConverter() } }; _server = new WebSocketServer(); _server.NewSession += OnNewSession; _server.SessionDropped += OnSessionDropped; _server.MessageReceived += OnMessageReceived; _ = Task.Run(async () => { try { await _server.Start(_ip, _port); } catch (Exception ex) { LogToConsole($"[WebSocketBot] Server failed to start: {ex.Message}"); } }); LogToConsole($"[WebSocketBot] Starting on {_ip}:{_port}"); } public override void AfterGameJoined() { _gameJoined = true; _waitingEvents.Add("OnGameJoined"); } public override void Update() { if (_waitingEvents.Count > 0) { var events = _waitingEvents.ToList(); _waitingEvents.Clear(); foreach (var evt in events) BroadcastEvent(evt, "N/A"); } } public override void OnUnload() { BroadcastEvent("OnWsConnectionClose", "N/A"); _server?.Stop(); } // --- Event Overrides --- public override void GetText(string text) { text = GetVerbatim(text); string message = "", username = ""; if (IsPrivateMessage(text, ref message, ref username)) BroadcastEvent("OnChatPrivate", SerializeData(new { sender = username, message, rawText = text })); else if (IsChatMessage(text, ref message, ref username)) BroadcastEvent("OnChatPublic", SerializeData(new { sender = username, message, rawText = text })); string tpSender = ""; if (IsTeleportRequest(text, ref tpSender)) BroadcastEvent("OnTeleportRequest", SerializeData(new { sender = tpSender, rawText = text })); } public override void GetText(string text, string? json) { BroadcastEvent("OnChatRaw", SerializeData(new { text, json })); } public override bool OnDisconnect(DisconnectReason reason, string message) { BroadcastEvent("OnDisconnect", SerializeData(new { reason = reason.ToString(), message })); return false; } public override void OnBlockBreakAnimation(Entity entity, Location location, byte stage) { BroadcastEvent("OnBlockBreakAnimation", SerializeData(new { entity, location, stage })); } public override void OnEntityAnimation(Entity entity, byte animation) { BroadcastEvent("OnEntityAnimation", SerializeData(new { entity, animation })); } public override void OnPlayerProperty(Dictionary prop) { BroadcastEvent("OnPlayerProperty", SerializeData(prop)); } public override void OnServerTpsUpdate(double tps) { BroadcastEvent("OnServerTpsUpdate", SerializeData(new { tps })); } public override void OnTimeUpdate(long worldAge, long timeOfDay) { BroadcastEvent("OnTimeUpdate", SerializeData(new { worldAge, timeOfDay })); } public override void OnEntityMove(Entity entity) { BroadcastEvent("OnEntityMove", SerializeData(entity)); } public override void OnInternalCommand(string commandName, string commandParams, CmdResult result) { BroadcastEvent("OnInternalCommand", SerializeData(new { commandName, commandParams, result = new { status = result.status.ToString(), result = result.result } })); } public override void OnEntitySpawn(Entity entity) { BroadcastEvent("OnEntitySpawn", SerializeData(entity)); } public override void OnEntityDespawn(Entity entity) { BroadcastEvent("OnEntityDespawn", SerializeData(entity)); } public override void OnHeldItemChange(byte slot) { BroadcastEvent("OnHeldItemChange", SerializeData(new { slot })); } public override void OnHealthUpdate(float health, int food) { BroadcastEvent("OnHealthUpdate", SerializeData(new { health, food })); } public override void OnExplosion(Location explode, float strength, int recordcount) { BroadcastEvent("OnExplosion", SerializeData(new { location = explode, strength, recordcount })); } public override void OnSetExperience(float experienceBar, int level, int totalExperience) { BroadcastEvent("OnSetExperience", SerializeData(new { experienceBar, level, totalExperience })); } public override void OnGamemodeUpdate(string playerName, Guid uuid, int gamemode) { BroadcastEvent("OnGamemodeUpdate", SerializeData(new { playerName, uuid, gamemode })); } public override void OnLatencyUpdate(string playerName, Guid uuid, int latency) { BroadcastEvent("OnLatencyUpdate", SerializeData(new { playerName, uuid, latency })); } public override void OnMapData(int mapId, byte scale, bool trackingPosition, bool locked, List icons, byte columnsUpdated, byte rowsUpdated, byte mapColumnX, byte mapRowZ, byte[]? colors) { BroadcastEvent("OnMapData", SerializeData(new { mapId, scale, trackingPosition, locked, icons, columnsUpdated, rowsUpdated, mapColumnX, mapRowZ, colors = colors != null ? Convert.ToBase64String(colors) : null })); } public override void OnTradeList(int windowId, List trades, VillagerInfo villagerInfo) { BroadcastEvent("OnTradeList", SerializeData(new { windowId, trades, villagerInfo })); } public override void OnTitle(int action, string titleText, string subtitleText, string actionBarText, int fadeIn, int stay, int fadeOut, string json) { BroadcastEvent("OnTitle", SerializeData(new { action, titleText, subtitleText, actionBarText, fadeIn, stay, fadeOut, json })); } public override void OnEntityEquipment(Entity entity, int slot, Item? item) { BroadcastEvent("OnEntityEquipment", SerializeData(new { entity, slot, item })); } public override void OnEntityEffect(Entity entity, Effects effect, int amplifier, int duration, byte flags) { BroadcastEvent("OnEntityEffect", SerializeData(new { entity, effect = effect.ToString(), amplifier, duration, flags })); } public override void OnScoreboardObjective(string objectiveName, byte mode, string objectiveValue, int type, string json, int numberFormat) { BroadcastEvent("OnScoreboardObjective", SerializeData(new { objectiveName, mode, objectiveValue, type, json, numberFormat })); } public override void OnUpdateScore(string entityName, int action, string objectiveName, string objectiveDisplayName, int value, int numberFormat) { BroadcastEvent("OnUpdateScore", SerializeData(new { entityName, action, objectiveName, objectiveDisplayName, value, numberFormat })); } public override void OnInventoryUpdate(int inventoryId) { BroadcastEvent("OnInventoryUpdate", SerializeData(new { inventoryId })); } public override void OnInventoryOpen(int inventoryId) { BroadcastEvent("OnInventoryOpen", SerializeData(new { inventoryId })); } public override void OnInventoryClose(int inventoryId) { BroadcastEvent("OnInventoryClose", SerializeData(new { inventoryId })); } public override void OnPlayerJoin(Guid uuid, string name) { BroadcastEvent("OnPlayerJoin", SerializeData(new { uuid, name })); } public override void OnPlayerLeave(Guid uuid, string? name) { BroadcastEvent("OnPlayerLeave", SerializeData(new { uuid, name })); } public override void OnDeath() { BroadcastEvent("OnDeath", "N/A"); } public override void OnRespawn() { BroadcastEvent("OnRespawn", "N/A"); } public override void OnEntityHealth(Entity entity, float health) { BroadcastEvent("OnEntityHealth", SerializeData(new { entity, health })); } public override void OnEntityMetadata(Entity entity, Dictionary metadata) { BroadcastEvent("OnEntityMetadata", SerializeData(new { entity, metadata })); } public override void OnPlayerStatus(byte statusId) { BroadcastEvent("OnPlayerStatus", SerializeData(new { statusId })); } public override void OnNetworkPacket(int packetID, List packetData, bool isLogin, bool isInbound) { BroadcastEvent("OnNetworkPacket", SerializeData(new { packetID, data = Convert.ToBase64String(packetData.ToArray()), isLogin, isInbound })); } // --- Serialization helpers --- private string SerializeData(object data) { return JsonSerializer.Serialize(data, _jsonOptions); } private void BroadcastEvent(string eventName, string data) { if (_server == null) return; var envelope = new Dictionary { ["event"] = eventName, ["data"] = data }; var json = JsonSerializer.Serialize(envelope); foreach (var kvp in _server.Sessions) { if (!kvp.Value.IsAuthenticated) continue; _ = _server.SendToSession(kvp.Key, json); } } private void SendSessionEvent(string sessionId, string eventName, string data) { if (_server == null) return; var envelope = new Dictionary { ["event"] = eventName, ["data"] = data }; var json = JsonSerializer.Serialize(envelope); _ = _server.SendToSession(sessionId, json); } // --- Session event handlers --- private void OnNewSession(string sessionId, WebSocketSession session) { if (_debugMode) LogToConsole($"[WebSocketBot] New session: {sessionId}"); } private void OnSessionDropped(string sessionId) { if (_debugMode) LogToConsole($"[WebSocketBot] Session dropped: {sessionId}"); } private void OnMessageReceived(string sessionId, string message) { if (_debugMode) LogToConsole($"[WebSocketBot] [{sessionId}] Received: {message}"); if (_server == null || !_server.Sessions.TryGetValue(sessionId, out var session)) return; try { using var doc = JsonDocument.Parse(message); var root = doc.RootElement; if (root.TryGetProperty("command", out var commandElement)) { var command = commandElement.GetString() ?? ""; var requestId = root.TryGetProperty("requestId", out var rid) ? rid.GetString() ?? "" : ""; var parameters = new List(); if (root.TryGetProperty("parameters", out var paramsElement) && paramsElement.ValueKind == JsonValueKind.Array) { foreach (var p in paramsElement.EnumerateArray()) { parameters.Add(p.ValueKind switch { JsonValueKind.String => p.GetString(), JsonValueKind.Number => p.TryGetInt64(out var l) ? (object)l : p.GetDouble(), JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.Null => null, _ => p.GetRawText() }); } } HandleCommand(sessionId, session, command, requestId, parameters); return; } } catch { // Not valid JSON, treat as plain text } HandlePlainText(sessionId, session, message); } private void HandlePlainText(string sessionId, WebSocketSession session, string text) { if (!session.IsAuthenticated) { SendSessionEvent(sessionId, "OnWsCommandResponse", SerializeData(new { success = false, message = "Not authenticated", requestId = "" })); return; } if (text.StartsWith('/')) { var cmd = text[1..]; var result = new CmdResult(); PerformInternalCommand("send " + cmd, ref result); SendSessionEvent(sessionId, "OnMccCommandResponse", SerializeData(new { command = cmd, status = result.status.ToString(), result = result.result ?? "" })); } else { SendText(text); } } // --- Command processing --- private void HandleCommand(string sessionId, WebSocketSession session, string command, string requestId, List parameters) { // Protocol commands available without auth switch (command) { case "Authenticate": HandleAuthenticate(sessionId, session, requestId, parameters); return; case "ChangeSessionId": HandleChangeSessionId(sessionId, session, requestId, parameters); return; } if (!session.IsAuthenticated) { SendCommandResponse(sessionId, requestId, false, "Not authenticated"); return; } try { switch (command) { case "LogToConsole": LogToConsole(GetParam(parameters, 0)); SendCommandResponse(sessionId, requestId, true); break; case "LogDebugToConsole": LogDebugToConsole(GetParam(parameters, 0)); SendCommandResponse(sessionId, requestId, true); break; case "LogToConsoleTranslated": LogToConsoleTranslated(GetParam(parameters, 0)); SendCommandResponse(sessionId, requestId, true); break; case "LogDebugToConsoleTranslated": LogDebugToConsoleTranslated(GetParam(parameters, 0)); SendCommandResponse(sessionId, requestId, true); break; case "ReconnectToTheServer": { var extra = parameters.Count > 0 ? Convert.ToInt32(parameters[0]) : 3; var delay = parameters.Count > 1 ? Convert.ToInt32(parameters[1]) : 0; ReconnectToTheServer(extra, delay); SendCommandResponse(sessionId, requestId, true); break; } case "DisconnectAndExit": SendCommandResponse(sessionId, requestId, true); DisconnectAndExit(); break; case "SendPrivateMessage": SendPrivateMessage(GetParam(parameters, 0), GetParam(parameters, 1)); SendCommandResponse(sessionId, requestId, true); break; case "RunScript": RunScript(GetParam(parameters, 0)); SendCommandResponse(sessionId, requestId, true); break; case "GetTerrainEnabled": SendCommandResponse(sessionId, requestId, true, SerializeData(new { enabled = GetTerrainEnabled() })); break; case "SetTerrainEnabled": SetTerrainEnabled(GetParam(parameters, 0)); SendCommandResponse(sessionId, requestId, true); break; case "GetEntityHandlingEnabled": SendCommandResponse(sessionId, requestId, true, SerializeData(new { enabled = GetEntityHandlingEnabled() })); break; case "Sneak": { var on = GetParam(parameters, 0); var result = Sneak(on); SendCommandResponse(sessionId, requestId, result); break; } case "SendEntityAction": { var actionType = ParseEnum(parameters[0]); var result = SendEntityAction(actionType); SendCommandResponse(sessionId, requestId, result); break; } case "DigBlock": { var x = Convert.ToDouble(parameters[0]); var y = Convert.ToDouble(parameters[1]); var z = Convert.ToDouble(parameters[2]); var direction = parameters.Count > 3 ? ParseEnum(parameters[3]) : Direction.Down; var loc = new Location(x, y, z); if (!GetTerrainEnabled()) { SendCommandResponse(sessionId, requestId, false, "Terrain not enabled"); break; } var current = GetCurrentLocation(); if (current.Distance(loc) > 6.0) { SendCommandResponse(sessionId, requestId, false, "Block too far away (max 6 blocks)"); break; } var world = GetWorld(); var block = world.GetBlock(loc); if (block.Type == Material.Air) { SendCommandResponse(sessionId, requestId, false, "Block is air"); break; } var digResult = DigBlock(loc, direction); SendCommandResponse(sessionId, requestId, digResult); break; } case "SetSlot": { var slot = Convert.ToInt32(parameters[0]); SetSlot(slot); SendCommandResponse(sessionId, requestId, true); break; } case "GetWorld": { if (!GetTerrainEnabled()) { SendCommandResponse(sessionId, requestId, false, "Terrain not enabled"); break; } // Return basic world info rather than full world data SendCommandResponse(sessionId, requestId, true, SerializeData(new { available = true })); break; } case "GetEntities": { if (!GetEntityHandlingEnabled()) { SendCommandResponse(sessionId, requestId, false, "Entity handling not enabled"); break; } var entities = GetEntities(); SendCommandResponse(sessionId, requestId, true, SerializeData(entities)); break; } case "GetPlayersLatency": { var latency = GetPlayersLatency(); SendCommandResponse(sessionId, requestId, true, SerializeData(latency)); break; } case "GetCurrentLocation": SendCommandResponse(sessionId, requestId, true, SerializeData(GetCurrentLocation())); break; case "MoveToLocation": { var x = Convert.ToDouble(parameters[0]); var y = Convert.ToDouble(parameters[1]); var z = Convert.ToDouble(parameters[2]); var allowUnsafe = parameters.Count > 3 && GetParam(parameters, 3); var allowDirectTp = parameters.Count > 4 && GetParam(parameters, 4); var maxOffset = parameters.Count > 5 ? Convert.ToInt32(parameters[5]) : 0; var minOffset = parameters.Count > 6 ? Convert.ToInt32(parameters[6]) : 0; var result = MoveToLocation(new Location(x, y, z), allowUnsafe, allowDirectTp, maxOffset, minOffset); SendCommandResponse(sessionId, requestId, result); break; } case "ClientIsMoving": SendCommandResponse(sessionId, requestId, true, SerializeData(new { moving = ClientIsMoving() })); break; case "LookAtLocation": { var x = Convert.ToDouble(parameters[0]); var y = Convert.ToDouble(parameters[1]); var z = Convert.ToDouble(parameters[2]); LookAtLocation(new Location(x, y, z)); SendCommandResponse(sessionId, requestId, true); break; } case "GetTimestamp": SendCommandResponse(sessionId, requestId, true, SerializeData(new { timestamp = GetTimestamp() })); break; case "GetServerPort": SendCommandResponse(sessionId, requestId, true, SerializeData(new { port = GetServerPort() })); break; case "GetServerHost": SendCommandResponse(sessionId, requestId, true, SerializeData(new { host = GetServerHost() })); break; case "GetUsername": SendCommandResponse(sessionId, requestId, true, SerializeData(new { username = GetUsername() })); break; case "GetGamemode": SendCommandResponse(sessionId, requestId, true, SerializeData(new { gamemode = GetGamemode() })); break; case "GetYaw": SendCommandResponse(sessionId, requestId, true, SerializeData(new { yaw = GetYaw() })); break; case "GetPitch": SendCommandResponse(sessionId, requestId, true, SerializeData(new { pitch = GetPitch() })); break; case "GetUserUUID": SendCommandResponse(sessionId, requestId, true, SerializeData(new { uuid = GetUserUUID() })); break; case "GetOnlinePlayers": SendCommandResponse(sessionId, requestId, true, SerializeData(GetOnlinePlayers())); break; case "GetOnlinePlayersWithUUID": SendCommandResponse(sessionId, requestId, true, SerializeData(GetOnlinePlayersWithUUID())); break; case "GetServerTPS": SendCommandResponse(sessionId, requestId, true, SerializeData(new { tps = GetServerTPS() })); break; case "InteractEntity": { var entityId = Convert.ToInt32(parameters[0]); var interactType = ParseEnum(parameters[1]); var hand = parameters.Count > 2 ? ParseEnum(parameters[2]) : Hand.MainHand; var result = InteractEntity(entityId, interactType, hand); SendCommandResponse(sessionId, requestId, result); break; } case "CreativeGive": { var slot = Convert.ToInt32(parameters[0]); var itemType = ParseEnum(parameters[1]); var count = Convert.ToInt32(parameters[2]); var result = CreativeGive(slot, itemType, count); SendCommandResponse(sessionId, requestId, result); break; } case "CreativeDelete": { var slot = Convert.ToInt32(parameters[0]); var result = CreativeDelete(slot); SendCommandResponse(sessionId, requestId, result); break; } case "SendAnimation": { var hand = parameters.Count > 0 ? ParseEnum(parameters[0]) : Hand.MainHand; var result = SendAnimation(hand); SendCommandResponse(sessionId, requestId, result); break; } case "SendPlaceBlock": { var x = Convert.ToDouble(parameters[0]); var y = Convert.ToDouble(parameters[1]); var z = Convert.ToDouble(parameters[2]); var direction = ParseEnum(parameters[3]); var hand = parameters.Count > 4 ? ParseEnum(parameters[4]) : Hand.MainHand; var result = SendPlaceBlock(new Location(x, y, z), direction, hand); SendCommandResponse(sessionId, requestId, result); break; } case "UseItemInHand": { var result = UseItemInHand(); SendCommandResponse(sessionId, requestId, result); break; } case "GetInventoryEnabled": SendCommandResponse(sessionId, requestId, true, SerializeData(new { enabled = GetInventoryEnabled() })); break; case "GetPlayerInventory": { if (!GetInventoryEnabled()) { SendCommandResponse(sessionId, requestId, false, "Inventory not enabled"); break; } var inv = GetPlayerInventory(); SendCommandResponse(sessionId, requestId, true, SerializeData(inv)); break; } case "GetInventories": { if (!GetInventoryEnabled()) { SendCommandResponse(sessionId, requestId, false, "Inventory not enabled"); break; } var inventories = GetInventories(); SendCommandResponse(sessionId, requestId, true, SerializeData(inventories)); break; } case "WindowAction": { var inventoryId = Convert.ToInt32(parameters[0]); var slot = Convert.ToInt32(parameters[1]); var actionType = ParseEnum(parameters[2]); var result = WindowAction(inventoryId, slot, actionType); SendCommandResponse(sessionId, requestId, result); break; } case "ChangeSlot": { var slot = Convert.ToInt16(parameters[0]); var result = ChangeSlot(slot); SendCommandResponse(sessionId, requestId, result); break; } case "GetCurrentSlot": SendCommandResponse(sessionId, requestId, true, SerializeData(new { slot = GetCurrentSlot() })); break; case "ClearInventories": { var result = ClearInventories(); SendCommandResponse(sessionId, requestId, result); break; } case "UpdateSign": { var x = Convert.ToDouble(parameters[0]); var y = Convert.ToDouble(parameters[1]); var z = Convert.ToDouble(parameters[2]); var line1 = GetParam(parameters, 3); var line2 = GetParam(parameters, 4); var line3 = GetParam(parameters, 5); var line4 = GetParam(parameters, 6); var result = UpdateSign(new Location(x, y, z), line1, line2, line3, line4); SendCommandResponse(sessionId, requestId, result); break; } case "SelectTrade": { var selectedSlot = Convert.ToInt32(parameters[0]); var result = SelectTrade(selectedSlot); SendCommandResponse(sessionId, requestId, result); break; } case "UpdateCommandBlock": { var x = Convert.ToDouble(parameters[0]); var y = Convert.ToDouble(parameters[1]); var z = Convert.ToDouble(parameters[2]); var cmd = GetParam(parameters, 3); var mode = ParseEnum(parameters[4]); var flags = ParseEnum(parameters[5]); var result = UpdateCommandBlock(new Location(x, y, z), cmd, mode, flags); SendCommandResponse(sessionId, requestId, result); break; } case "CloseInventory": { var inventoryId = Convert.ToInt32(parameters[0]); var result = CloseInventory(inventoryId); SendCommandResponse(sessionId, requestId, result); break; } case "GetMaxChatMessageLength": SendCommandResponse(sessionId, requestId, true, SerializeData(new { length = GetMaxChatMessageLength() })); break; case "Respawn": { var result = Respawn(); SendCommandResponse(sessionId, requestId, result); break; } case "GetProtocolVersion": SendCommandResponse(sessionId, requestId, true, SerializeData(new { protocolVersion = GetProtocolVersion() })); break; case "GetItemTypeMappings": { var mappings = new Dictionary(); foreach (ItemType value in Enum.GetValues(typeof(ItemType))) mappings[value.ToString()] = (int)value; SendCommandResponse(sessionId, requestId, true, SerializeData(mappings)); break; } case "GetEntityTypeMappings": { var mappings = new Dictionary(); foreach (EntityType value in Enum.GetValues(typeof(EntityType))) mappings[value.ToString()] = (int)value; SendCommandResponse(sessionId, requestId, true, SerializeData(mappings)); break; } default: SendCommandResponse(sessionId, requestId, false, $"Unknown command: {command}"); break; } } catch (Exception ex) { SendCommandResponse(sessionId, requestId, false, $"Error: {ex.Message}"); } } private void HandleAuthenticate(string sessionId, WebSocketSession session, string requestId, List parameters) { if (parameters.Count == 0) { SendCommandResponse(sessionId, requestId, false, "Invalid password"); return; } var provided = GetParam(parameters, 0); var expected = _password; // Fixed-time comparison to prevent timing attacks var diff = provided.Length ^ expected.Length; for (int i = 0; i < expected.Length; i++) diff |= expected[i] ^ (i < provided.Length ? provided[i] : 0xFF); if (diff != 0) { SendCommandResponse(sessionId, requestId, false, "Invalid password"); return; } session.IsAuthenticated = true; SendCommandResponse(sessionId, requestId, true, "Authenticated"); } private void HandleChangeSessionId(string sessionId, WebSocketSession session, string requestId, List parameters) { if (parameters.Count == 0) { SendCommandResponse(sessionId, requestId, false, "New session ID required"); return; } var newId = GetParam(parameters, 0); if (string.IsNullOrWhiteSpace(newId)) { SendCommandResponse(sessionId, requestId, false, "New session ID cannot be empty"); return; } if (_server == null) { SendCommandResponse(sessionId, requestId, false, "Server not initialized"); return; } if (_server.RenameSession(sessionId, newId)) SendCommandResponse(newId, requestId, true, $"Session renamed to {newId}"); else SendCommandResponse(sessionId, requestId, false, $"Failed to rename session to {newId}"); } // --- Response helpers --- private void SendCommandResponse(string sessionId, string requestId, bool success, string? message = null) { var response = new Dictionary { ["success"] = success, ["requestId"] = requestId }; if (message != null) response["message"] = message; SendSessionEvent(sessionId, "OnWsCommandResponse", SerializeData(response)); } // --- Parsing helpers --- private static T ParseEnum(object? param) where T : struct, Enum { if (param is string s) { if (Enum.TryParse(s, ignoreCase: true, out var parsed)) return parsed; } try { var numeric = Convert.ToInt32(param); return (T)Enum.ToObject(typeof(T), numeric); } catch { throw new ArgumentException($"Cannot parse '{param}' as {typeof(T).Name}"); } } private static T GetParam(List parameters, int index) { if (index >= parameters.Count) throw new ArgumentException($"Missing parameter at index {index}"); var val = parameters[index]; if (val is T typed) return typed; if (typeof(T) == typeof(bool) && val != null) return (T)(object)Convert.ToBoolean(val); if (typeof(T) == typeof(string)) return (T)(object)(val?.ToString() ?? ""); return (T)Convert.ChangeType(val!, typeof(T)); } }