// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.Client.Library; using System; using System.Collections.Generic; using System.Diagnostics; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using MUnique.OpenMU.Network; using MUnique.OpenMU.Network.SimpleModulus; using MUnique.OpenMU.Network.Xor; using Pipelines.Sockets.Unofficial; /// /// Class which manages the connections which are created through the game client. /// To identify each connection, we use handles (a simple number). /// public unsafe partial class ConnectionManager { /// /// The currently active connections, with their handle as key. /// private static readonly Dictionary Connections = new(); /// /// The currently used maximum handle number. /// private static int _maxHandle; /// /// Connects the specified host and port. /// /// The pointer to a string which contains the host (ip or hostname). /// The port. /// /// A flag, if the connection is supposed to be encrypted. /// This is usually 1 for connections to the game server, but 0 for connections to the connect server. /// /// The pointer to an unmanaged method which is called when a new packet got received. /// Parameters: handle, size, pointer to the data. /// The pointer to an unmanaged method which is called when the connection got disconnected. /// Parameter: handle. /// /// The handle of the created connection. If negative, the connection couldn't be established. /// [UnmanagedCallersOnly(EntryPoint = "ConnectionManager_Connect")] public static int Connect(IntPtr hostPtr, int port, byte isEncrypted, delegate* unmanaged onPacketReceived, delegate* unmanaged onDisconnected) { try { var host = Marshal.PtrToStringAuto(hostPtr) ?? throw new ArgumentNullException(nameof(hostPtr)); return ConnectInner(host, port, isEncrypted == 1, onPacketReceived, onDisconnected); } catch (Exception ex) { Debug.WriteLine($"Error establishing connection: {ex}"); return -1; } } /// /// Sends a packet over the connection of the specified handle. /// /// The handle of the connection. /// The pointer to the packet data. /// The count of bytes which should be sent. [UnmanagedCallersOnly(EntryPoint = "ConnectionManager_Send")] public static void Send(int handle, byte* data, int count) { if (Connections.TryGetValue(handle, out var connection)) { try { var bytes = new Span(data, count); bytes.SetPacketSize(); connection.Send(bytes); Debug.WriteLine("Sent {0} bytes with handle {1}", count, handle); } catch (Exception ex) { Debug.WriteLine($"Error sending {0} bytes with handle {1}: {2}", count, handle, ex); } } else { Debug.WriteLine("Connection with handle {0} not found.", handle); } } /// /// Begins receiving data for the connection of the specified handle. /// /// The handle of the connection. [UnmanagedCallersOnly(EntryPoint = "ConnectionManager_BeginReceive")] public static void BeginReceive(int connectionHandle) { if (Connections.TryGetValue(connectionHandle, out var connection)) { connection.BeginReceive(); } } /// /// Disconnects the connection of the specified handle. /// /// The handle of the connection. [UnmanagedCallersOnly(EntryPoint = "ConnectionManager_Disconnect")] public static void Disconnect(int connectionHandle) { if (Connections.TryGetValue(connectionHandle, out var connection)) { connection.DisconnectAndDispose(); } } private static int ConnectInner(string host, int port, bool isEncrypted, delegate* unmanaged onPacketReceived, delegate* unmanaged onDisconnected) { var tcpClient = new TcpClient(host, port); var socketConnection = SocketConnection.Create(tcpClient.Client); var encryptor = isEncrypted ? new PipelinedXor32Encryptor(new PipelinedSimpleModulusEncryptor(socketConnection.Output, PipelinedSimpleModulusEncryptor.DefaultClientKey).Writer) : null; var decryptor = isEncrypted ? new PipelinedSimpleModulusDecryptor(socketConnection.Input, PipelinedSimpleModulusDecryptor.DefaultClientKey) : null; var connection = new Connection(socketConnection, decryptor, encryptor, new NullLogger()); var handle = Interlocked.Increment(ref _maxHandle); var wrapper = new ConnectionWrapper(handle, connection, onPacketReceived, onDisconnected); Connections.Add(handle, wrapper); connection.Disconnected += () => { Connections.Remove(handle); return ValueTask.CompletedTask; }; return handle; } }