From 8b163f0685ff2abf13d281738d78f28367103dd2 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:47:33 -0500 Subject: [PATCH 01/78] [+] Copy code --- AquaMai.Mods/WorldsLink/FutariClient.cs | 150 ++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariExt.cs | 34 +++++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 195 ++++++++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariTypes.cs | 76 +++++++++ 4 files changed, 455 insertions(+) create mode 100644 AquaMai.Mods/WorldsLink/FutariClient.cs create mode 100644 AquaMai.Mods/WorldsLink/FutariExt.cs create mode 100644 AquaMai.Mods/WorldsLink/FutariSocket.cs create mode 100644 AquaMai.Mods/WorldsLink/FutariTypes.cs diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs new file mode 100644 index 0000000..7247ec5 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace AquaMai.Mods.WorldsLink; + +public class FutariClient +{ + public static FutariClient Instance { get; set; } + private static readonly JsonSerializerSettings settings = new() + { + NullValueHandling = NullValueHandling.Ignore + }; + + private readonly string _keychip; + private TcpClient _tcpClient; + private StreamWriter _writer; + private StreamReader _reader; + + public readonly ConcurrentQueue sendQ = new(); + // + public readonly ConcurrentDictionary> tcpRecvQ = new(); + // + public readonly ConcurrentDictionary> udpRecvQ = new(); + // + public readonly ConcurrentDictionary> acceptQ = new(); + + private Thread _sendThread; + private Thread _recvThread; + + public IPAddress StubIP => new IPAddress(FutariExt.KeychipToStubIp(_keychip)); + + public FutariClient(string keychip) + { + _keychip = keychip; + Instance = this; + } + + public void Connect(string host, int port) + { + _tcpClient = new TcpClient(); + _tcpClient.Connect(host, port); + + var networkStream = _tcpClient.GetStream(); + _writer = new StreamWriter(networkStream, Encoding.UTF8) { AutoFlush = true }; + _reader = new StreamReader(networkStream, Encoding.UTF8); + + // Register + Send(new Msg { cmd = Cmd.CTL_START, data = _keychip }); + Log.WriteLine($"Connected to server at {host}:{port}"); + + // Start communication and message receiving in separate threads + _sendThread = new Thread(SendThread) { IsBackground = true }; + _recvThread = new Thread(RecvThread) { IsBackground = true }; + + _sendThread.Start(); + _recvThread.Start(); + } + + public void Bind(int port, ProtocolType proto) + { + if (proto == ProtocolType.Tcp) + acceptQ.TryAdd(port, new ConcurrentQueue()); + else if (proto == ProtocolType.Udp) + udpRecvQ.TryAdd(port, new ConcurrentQueue()); + } + + private void SendThread() + { + try + { + long lastHeartbeat = 0; + while (true) + { + var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (time - lastHeartbeat > 1000) + { + Send(new Msg { cmd = Cmd.CTL_HEARTBEAT }); + lastHeartbeat = time; + } + + // Send any data in the send queue + while (sendQ.TryDequeue(out var msg)) + Send(msg); + + Thread.Sleep(10); + } + } + catch (Exception ex) + { + Log.WriteLine($"Error during communication: {ex.Message}"); + } + finally { _tcpClient.Close(); } + } + + private void RecvThread() + { + try + { + while (true) + { + var line = _reader.ReadLine(); + if (line == null) break; + + var message = JsonConvert.DeserializeObject(line); + if (message == null) continue; + HandleIncomingMessage(message); + } + } + catch (Exception ex) + { + Log.WriteLine($"Error receiving messages: {ex.Message}"); + } + finally { _tcpClient.Close(); } + } + + private void HandleIncomingMessage(Msg msg) + { + Log.WriteLine($"{_keychip} <<< {JsonConvert.SerializeObject(msg, settings)}"); + + switch (msg.cmd) + { + // UDP message + case Cmd.DATA_SEND or Cmd.DATA_BROADCAST when msg.proto == ProtocolType.Udp && msg.dPort != null: + udpRecvQ.Get(msg.dPort.Value)?.Enqueue(msg); + break; + + // TCP connection + case Cmd.DATA_SEND when msg.proto == ProtocolType.Tcp && msg.sid != null: + tcpRecvQ.Get(msg.sid.Value)?.Enqueue(msg); + break; + + // TCP connection accepted + case Cmd.CTL_TCP_CONNECT when msg.dPort != null: + acceptQ.Get(msg.dPort.Value)?.Enqueue(msg); + break; + } + } + + private void Send(Msg msg) + { + var json = JsonConvert.SerializeObject(msg, settings); + _writer.WriteLine(json); + Log.WriteLine($"{_keychip} >>> {json}"); + } +} diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs new file mode 100644 index 0000000..c91b1a4 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -0,0 +1,34 @@ +#nullable enable +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace AquaMai.Mods.WorldsLink; + +public static class FutariExt +{ + public static uint KeychipToStubIp(string keychip) + { + return uint.Parse("1" + keychip.Substring(2)); + } + + public static byte[] View(this byte[] buffer, int offset, int size) + { + var array = new byte[size]; + Array.Copy(buffer, offset, array, 0, size); + return array; + } + + public static V? Get(this ConcurrentDictionary dict, K key) where V : class + { + return dict.GetValueOrDefault(key); + } + + // Call a function using reflection + public static void Call(this object obj, string method, params object[] args) + { + obj.GetType().GetMethod(method)?.Invoke(obj, args); + } + + public static uint MyStubIP() => KeychipToStubIp(AMDaemon.System.KeychipId.ShortValue); +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs new file mode 100644 index 0000000..3392b61 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using PartyLink; + +namespace AquaMai.Mods.WorldsLink; + +public class NFSocket +{ + private int _bindPort = -1; + private readonly FutariClient _client; + private readonly ProtocolType _proto; + private int _streamId = -1; + + // ConnectSocket.Enter_Active (doesn't seem to be actually used) + public EndPoint LocalEndPoint { get; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); + + // ConnectSocket.Enter_Active, ListenSocket.acceptClient (TCP) + // Each client's remote endpoint must be different + public EndPoint RemoteEndPoint { get; private set; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); + + public NFSocket(FutariClient client, ProtocolType proto) + { + _client = client; + _proto = proto; + } + + // Compatibility constructor + public NFSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) : + this(FutariClient.Instance!, protocolType) + { + } + + // ListenSocket.open (TCP) + public void Listen(int backlog) + { + /* Do nothing */ + } + + // ListenSocket.open, UdpRecvSocket.open + public void Bind(EndPoint localEndP) + { + if (localEndP is not IPEndPoint ipEndP) return; + _bindPort = ipEndP.Port; + _client.Bind(_bindPort, _proto); + _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_BIND, proto = _proto, + src = ipEndP.Address.ToNetworkByteOrderU32(), sPort = ipEndP.Port }); + } + + // Only used in BroadcastSocket + public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) + { + /* Do nothing */ + } + + // SocketBase.checkRecvEnable, checkSendEnable + // This is the Select step called before blocking calls (e.g. Accept) + public static bool Poll(NFSocket socket, SelectMode mode) + { + Log.Debug("Poll called"); + if (mode == SelectMode.SelectRead) + { + return (socket._proto == ProtocolType.Udp) // Check is UDP + ? !socket._client.udpRecvQ.Get(socket._bindPort)?.IsEmpty ?? false + : (socket._streamId == -1) // Check is TCP stream or TCP server + ? !socket._client.acceptQ.Get(socket._bindPort)?.IsEmpty ?? false + : !socket._client.tcpRecvQ.Get(socket._streamId)?.IsEmpty ?? false; + } + // Write is always ready? + return mode == SelectMode.SelectWrite; + } + + // ConnectSocket.Enter_Connect (TCP) + // The destination address is obtained from a RecruitInfo packet sent by the host. + // The host should patch their PartyLink.Util.MyIpAddress() to return a mock address instead of the real one + // Returns true if IO is pending and false if the operation completed synchronously + // When it's false, e will contain the result of the operation + public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) + { + if (e.RemoteEndPoint is not IPEndPoint ipEndP) return false; + _streamId = new Random().Next(); + _client.tcpRecvQ[_streamId] = new ConcurrentQueue(); + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.CTL_TCP_CONNECT, + proto = _proto, + sid = _streamId, + dst = ipEndP.Address.ToNetworkByteOrderU32(), + dPort = ipEndP.Port + }); + // It is very annoying to call Complete event using reflection + // So we'll just pretend that the client has ACKed + return false; + } + + // Accept is blocking + public NFSocket Accept() + { + // Check if accept queue has any pending connections + if (!_client.acceptQ.TryGetValue(_bindPort, out var q) || + !q.TryDequeue(out var msg) || + msg.sid == null || + msg.src == null) + { + Log.Warn("Accept: No pending connections"); + return null; + } + + _client.tcpRecvQ[msg.sid.Value] = new ConcurrentQueue(); + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.CTL_TCP_ACCEPT, proto = _proto, sid = msg.sid, dst = msg.src + }); + + return new NFSocket(_client, _proto) + { + _streamId = msg.sid.Value, + RemoteEndPoint = new IPEndPoint(new IPAddress(new IpAddress(msg.src.Value).GetAddressBytes()), + _bindPort) + }; + } + + public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) + { + Log.Debug($"Send: {size} bytes"); + // Remote EP is not relevant here, because the stream is already established, + // there can only be one remote endpoint + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size), + sid = _streamId == -1 ? null : _streamId + }); + return size; + } + + // Only used in BroadcastSocket + public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) + { + Log.Debug($"SendTo: {size} bytes"); + if (remoteEP is not IPEndPoint ipEndP) return 0; + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size), dPort = ipEndP.Port + }); + return size; + } + + // Only used in TCP ConnectSocket + public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode) + { + Log.Debug("Receive called"); + if (!_client.tcpRecvQ.TryGetValue(_streamId, out var q) || + !q.TryDequeue(out var msg)) + { + Log.Warn("Receive: No data to receive"); + errorCode = SocketError.WouldBlock; + return 0; + } + var data = Convert.FromBase64String((string) msg.data!); + + Buffer.BlockCopy(data, 0, buffer, 0, data.Length); + errorCode = SocketError.Success; + return data.Length; + } + + // Only used in UdpRecvSocket to receive from 0 (broadcast) + public int ReceiveFrom(byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP) + { + Log.Debug("ReceiveFrom called"); + if (!_client.udpRecvQ.TryGetValue(_bindPort, out var q) || + !q.TryDequeue(out var msg)) + { + Log.Warn("ReceiveFrom: No data to receive"); + return 0; + } + var data = Convert.FromBase64String((string) msg.data!); + + Buffer.BlockCopy(data, 0, buffer, 0, data.Length); + return data.Length; + } + + // Called everywhere, but only relevant for TCP + public void Close() + { + // TCP FIN/RST + if (_proto == ProtocolType.Tcp) + _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_TCP_CLOSE, proto = _proto }); + } + + public void Shutdown(SocketShutdown how) + { + Close(); + } +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs new file mode 100644 index 0000000..db8120a --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -0,0 +1,76 @@ +using System.Net.Sockets; + +namespace AquaMai.Mods.WorldsLink; + +public enum Cmd +{ + // Control plane + CTL_START = 1, + CTL_BIND = 2, + CTL_HEARTBEAT = 3, + CTL_TCP_CONNECT = 4, // Accept a new multiplexed TCP stream + CTL_TCP_ACCEPT = 5, + CTL_TCP_ACCEPT_ACK = 6, + CTL_TCP_CLOSE = 7, + + // Data plane + DATA_SEND = 21, + DATA_BROADCAST = 22, +} + + +public class Msg +{ + public Cmd cmd { get; set; } + public ProtocolType? proto { get; set; } + public int? sid { get; set; } + public uint? src { get; set; } + public int? sPort { get; set; } + public uint? dst { get; set; } + public int? dPort { get; set; } + public object? data { get; set; } +} + +public abstract class Log +{ + // Text colors + private const string BLACK = "\u001b[30m"; + private const string RED = "\u001b[31m"; + private const string GREEN = "\u001b[32m"; + private const string YELLOW = "\u001b[33m"; + private const string BLUE = "\u001b[34m"; + private const string MAGENTA = "\u001b[35m"; + private const string CYAN = "\u001b[36m"; + private const string WHITE = "\u001b[37m"; + + // Bright text colors + private const string BRIGHT_BLACK = "\u001b[90m"; + private const string BRIGHT_RED = "\u001b[91m"; + private const string BRIGHT_GREEN = "\u001b[92m"; + private const string BRIGHT_YELLOW = "\u001b[93m"; + private const string BRIGHT_BLUE = "\u001b[94m"; + private const string BRIGHT_MAGENTA = "\u001b[95m"; + private const string BRIGHT_CYAN = "\u001b[96m"; + private const string BRIGHT_WHITE = "\u001b[97m"; + + // Reset + private const string RESET = "\u001b[0m"; + + public static void Warn(string msg) + { + System.Console.WriteLine(YELLOW + "WARN " + msg + RESET); + } + + public static void Debug(string msg) + { + System.Console.WriteLine(BLUE + "DEBUG " + msg + RESET); + } + + public static void WriteLine(string msg) + { + if (msg.StartsWith("A001")) msg = MAGENTA + msg; + if (msg.StartsWith("A002")) msg = CYAN + msg; + msg = msg.Replace("Error", RED + "Error"); + System.Console.WriteLine(msg + RESET); + } +} From 6cc5d4d999fbaa8601581f133bb74ccf86b4ca70 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:49:08 -0500 Subject: [PATCH 02/78] [-] Remove json and use manual serailization --- AquaMai.Mods/WorldsLink/FutariClient.cs | 14 ++----- AquaMai.Mods/WorldsLink/FutariExt.cs | 5 +++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 8 ++-- AquaMai.Mods/WorldsLink/FutariTypes.cs | 54 ++++++++++++++++++++----- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 7247ec5..cfcbff0 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -11,10 +11,6 @@ namespace AquaMai.Mods.WorldsLink; public class FutariClient { public static FutariClient Instance { get; set; } - private static readonly JsonSerializerSettings settings = new() - { - NullValueHandling = NullValueHandling.Ignore - }; private readonly string _keychip; private TcpClient _tcpClient; @@ -106,8 +102,7 @@ private void RecvThread() var line = _reader.ReadLine(); if (line == null) break; - var message = JsonConvert.DeserializeObject(line); - if (message == null) continue; + var message = Msg.FromString(line); HandleIncomingMessage(message); } } @@ -120,7 +115,7 @@ private void RecvThread() private void HandleIncomingMessage(Msg msg) { - Log.WriteLine($"{_keychip} <<< {JsonConvert.SerializeObject(msg, settings)}"); + Log.WriteLine($"{_keychip} <<< {msg}"); switch (msg.cmd) { @@ -143,8 +138,7 @@ private void HandleIncomingMessage(Msg msg) private void Send(Msg msg) { - var json = JsonConvert.SerializeObject(msg, settings); - _writer.WriteLine(json); - Log.WriteLine($"{_keychip} >>> {json}"); + _writer.WriteLine(msg); + Log.WriteLine($"{_keychip} >>> {msg}"); } } diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index c91b1a4..7bc2365 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -11,6 +11,8 @@ public static uint KeychipToStubIp(string keychip) { return uint.Parse("1" + keychip.Substring(2)); } + + public static R Let(this T x, Func f) => f(x); public static byte[] View(this byte[] buffer, int offset, int size) { @@ -19,6 +21,9 @@ public static byte[] View(this byte[] buffer, int offset, int size) return array; } + public static string B64(this byte[] buffer) => Convert.ToBase64String(buffer); + public static byte[] B64(this string str) => Convert.FromBase64String(str); + public static V? Get(this ConcurrentDictionary dict, K key) where V : class { return dict.GetValueOrDefault(key); diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index 3392b61..e21490e 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -128,7 +128,7 @@ public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) // there can only be one remote endpoint _client.sendQ.Enqueue(new Msg { - cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size), + cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size).B64(), sid = _streamId == -1 ? null : _streamId }); return size; @@ -141,7 +141,7 @@ public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, if (remoteEP is not IPEndPoint ipEndP) return 0; _client.sendQ.Enqueue(new Msg { - cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size), dPort = ipEndP.Port + cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size).B64(), dPort = ipEndP.Port }); return size; } @@ -157,7 +157,7 @@ public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, errorCode = SocketError.WouldBlock; return 0; } - var data = Convert.FromBase64String((string) msg.data!); + var data = msg.data!.B64(); Buffer.BlockCopy(data, 0, buffer, 0, data.Length); errorCode = SocketError.Success; @@ -174,7 +174,7 @@ public int ReceiveFrom(byte[] buffer, SocketFlags socketFlags, ref EndPoint remo Log.Warn("ReceiveFrom: No data to receive"); return 0; } - var data = Convert.FromBase64String((string) msg.data!); + var data = msg.data!.B64(); Buffer.BlockCopy(data, 0, buffer, 0, data.Length); return data.Length; diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index db8120a..9a2b083 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using System.Net.Sockets; namespace AquaMai.Mods.WorldsLink; @@ -19,16 +21,50 @@ public enum Cmd } -public class Msg +public struct Msg { - public Cmd cmd { get; set; } - public ProtocolType? proto { get; set; } - public int? sid { get; set; } - public uint? src { get; set; } - public int? sPort { get; set; } - public uint? dst { get; set; } - public int? dPort { get; set; } - public object? data { get; set; } + public Cmd cmd; + public ProtocolType? proto; + public int? sid; + public uint? src; + public int? sPort; + public uint? dst; + public int? dPort; + public string? data; + + public override string ToString() + { + int? proto_ = proto == null ? null : (int) proto; + var arr = new object[] { + 1, (int) cmd, proto_, sid, src, sPort, dst, dPort, + null, null, null, null, null, null, null, null, // reserved for future use + data + }; + + // Map nulls to empty strings + return string.Join(",", arr.Select(x => x ?? "")).TrimEnd(','); + } + + private static T? Parse(string[] fields, int i) where T : struct + => fields.Length <= i || fields[i] == "" ? null + : (T) Convert.ChangeType(fields[i], typeof(T)); + + + public static Msg FromString(string str) + { + var fields = str.Split(','); + return new Msg + { + cmd = (Cmd) (Parse(fields, 1) ?? throw new InvalidOperationException("cmd is required")), + proto = Parse(fields, 2)?.Let(it => (ProtocolType) it), + sid = Parse(fields, 3), + src = Parse(fields, 4), + sPort = Parse(fields, 5), + dst = Parse(fields, 6), + dPort = Parse(fields, 7), + data = string.Join(",", fields.Skip(16)) + }; + } } public abstract class Log From 6cd386be95bc4057beaff95801e2c0e286e233c7 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:17:50 -0500 Subject: [PATCH 03/78] [+] Patch? --- AquaMai.Mods/WorldsLink/FutariClient.cs | 10 +- AquaMai.Mods/WorldsLink/FutariPatch.cs | 164 ++++++++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 31 ++--- AquaMai.Mods/WorldsLink/FutariTypes.cs | 14 +- 4 files changed, 189 insertions(+), 30 deletions(-) create mode 100644 AquaMai.Mods/WorldsLink/FutariPatch.cs diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index cfcbff0..fda7e75 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -47,7 +47,7 @@ public void Connect(string host, int port) // Register Send(new Msg { cmd = Cmd.CTL_START, data = _keychip }); - Log.WriteLine($"Connected to server at {host}:{port}"); + Log.Info($"Connected to server at {host}:{port}"); // Start communication and message receiving in separate threads _sendThread = new Thread(SendThread) { IsBackground = true }; @@ -88,7 +88,7 @@ private void SendThread() } catch (Exception ex) { - Log.WriteLine($"Error during communication: {ex.Message}"); + Log.Info($"Error during communication: {ex.Message}"); } finally { _tcpClient.Close(); } } @@ -108,14 +108,14 @@ private void RecvThread() } catch (Exception ex) { - Log.WriteLine($"Error receiving messages: {ex.Message}"); + Log.Info($"Error receiving messages: {ex.Message}"); } finally { _tcpClient.Close(); } } private void HandleIncomingMessage(Msg msg) { - Log.WriteLine($"{_keychip} <<< {msg}"); + Log.Info($"{_keychip} <<< {msg}"); switch (msg.cmd) { @@ -139,6 +139,6 @@ private void HandleIncomingMessage(Msg msg) private void Send(Msg msg) { _writer.WriteLine(msg); - Log.WriteLine($"{_keychip} >>> {msg}"); + Log.Info($"{_keychip} >>> {msg}"); } } diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs new file mode 100644 index 0000000..9522dc9 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using HarmonyLib; +using MelonLoader; +using PartyLink; + +namespace AquaMai.Mods.WorldsLink; + +public static class FutariPatch +{ + private static readonly Dictionary redirect = new(); + + static FutariPatch() + { + new FutariClient(AMDaemon.System.KeychipId.ShortValue).Connect("violet", 20101); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(AddressFamily), typeof(SocketType), typeof(ProtocolType), typeof(int))] + private static void NFCreate(NFSocket __instance, AddressFamily a1, SocketType a2, ProtocolType a3, int a4) + { + Log.Debug("new NFSocket(AddressFamily, SocketType, ProtocolType, int)"); + var futari = new FutariSocket(a1, a2, a3, a4); + redirect.Add(__instance, futari); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(Socket))] + private static void NFCreate2(NFSocket __instance, Socket nfSocket) + { + Log.Warn("new NFSocket(Socket) -- We shouldn't get here."); + throw new NotImplementedException(); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Poll")] + private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) + { + Log.Debug("NFPoll"); + FutariSocket.Poll(redirect[socket], mode); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Send")] + private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags) + { + Log.Debug("NFSend"); + redirect[__instance].Send(buffer, offset, size, socketFlags); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "SendTo")] + private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) + { + Log.Debug("NFSendTo"); + redirect[__instance].SendTo(buffer, offset, size, socketFlags, remoteEP); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Receive")] + private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode) + { + Log.Debug("NFReceive"); + redirect[__instance].Receive(buffer, offset, size, socketFlags, out errorCode); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "ReceiveFrom")] + private static bool NFReceiveFrom(NFSocket __instance, byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP) + { + Log.Debug("NFReceiveFrom"); + redirect[__instance].ReceiveFrom(buffer, socketFlags, ref remoteEP); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Bind")] + private static bool NFBind(NFSocket __instance, EndPoint localEndP) + { + Log.Debug("NFBind"); + redirect[__instance].Bind(localEndP); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Listen")] + private static bool NFListen(NFSocket __instance, int backlog) + { + Log.Debug("NFListen"); + redirect[__instance].Listen(backlog); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Accept")] + private static bool NFAccept(NFSocket __instance, ref NFSocket __result) + { + Log.Debug("NFAccept"); + var futariSocket = redirect[__instance].Accept(); + var mockSocket = new NFSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp, 0); + redirect.Add(mockSocket, futariSocket); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "ConnectAsync")] + private static bool NFConnectAsync(NFSocket __instance, SocketAsyncEventArgs e, int mockID) + { + Log.Debug("NFConnectAsync"); + redirect[__instance].ConnectAsync(e, mockID); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "SetSocketOption")] + private static bool NFSetSocketOption(NFSocket __instance, SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue) + { + Log.Debug("NFSetSocketOption"); + redirect[__instance].SetSocketOption(optionLevel, optionName, optionValue); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Close")] + private static bool NFClose(NFSocket __instance) + { + Log.Debug("NFClose"); + redirect[__instance].Close(); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Shutdown")] + private static bool NFShutdown(NFSocket __instance, SocketShutdown how) + { + Log.Debug("NFShutdown"); + redirect[__instance].Shutdown(how); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "RemoteEndPoint", MethodType.Getter)] + private static bool NFGetRemoteEndPoint(NFSocket __instance, ref EndPoint __result) + { + Log.Debug("NFGetRemoteEndPoint"); + __result = redirect[__instance].RemoteEndPoint; + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "LocalEndPoint", MethodType.Getter)] + private static bool NFGetLocalEndPoint(NFSocket __instance, ref EndPoint __result) + { + Log.Debug("NFGetLocalEndPoint"); + __result = redirect[__instance].LocalEndPoint; + return false; + } +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index e21490e..0cfb009 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -6,7 +6,7 @@ namespace AquaMai.Mods.WorldsLink; -public class NFSocket +public class FutariSocket { private int _bindPort = -1; private readonly FutariClient _client; @@ -20,23 +20,18 @@ public class NFSocket // Each client's remote endpoint must be different public EndPoint RemoteEndPoint { get; private set; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); - public NFSocket(FutariClient client, ProtocolType proto) + public FutariSocket(FutariClient client, ProtocolType proto) { _client = client; _proto = proto; } // Compatibility constructor - public NFSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) : - this(FutariClient.Instance!, protocolType) - { - } + public FutariSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) : + this(FutariClient.Instance, protocolType) { } // ListenSocket.open (TCP) - public void Listen(int backlog) - { - /* Do nothing */ - } + public void Listen(int backlog) { } // ListenSocket.open, UdpRecvSocket.open public void Bind(EndPoint localEndP) @@ -49,14 +44,11 @@ public void Bind(EndPoint localEndP) } // Only used in BroadcastSocket - public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) - { - /* Do nothing */ - } + public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) { } // SocketBase.checkRecvEnable, checkSendEnable // This is the Select step called before blocking calls (e.g. Accept) - public static bool Poll(NFSocket socket, SelectMode mode) + public static bool Poll(FutariSocket socket, SelectMode mode) { Log.Debug("Poll called"); if (mode == SelectMode.SelectRead) @@ -95,7 +87,7 @@ public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) } // Accept is blocking - public NFSocket Accept() + public FutariSocket Accept() { // Check if accept queue has any pending connections if (!_client.acceptQ.TryGetValue(_bindPort, out var q) || @@ -113,7 +105,7 @@ public NFSocket Accept() cmd = Cmd.CTL_TCP_ACCEPT, proto = _proto, sid = msg.sid, dst = msg.src }); - return new NFSocket(_client, _proto) + return new FutariSocket(_client, _proto) { _streamId = msg.sid.Value, RemoteEndPoint = new IPEndPoint(new IPAddress(new IpAddress(msg.src.Value).GetAddressBytes()), @@ -188,8 +180,5 @@ public void Close() _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_TCP_CLOSE, proto = _proto }); } - public void Shutdown(SocketShutdown how) - { - Close(); - } + public void Shutdown(SocketShutdown how) => Close(); } \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index 9a2b083..aa5a383 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Net.Sockets; +using MelonLoader; namespace AquaMai.Mods.WorldsLink; @@ -92,21 +93,26 @@ public abstract class Log // Reset private const string RESET = "\u001b[0m"; + public static void Error(string msg) + { + MelonLogger.Error(RED + "[FUTARI] ERROR " + msg + RESET); + } + public static void Warn(string msg) { - System.Console.WriteLine(YELLOW + "WARN " + msg + RESET); + MelonLogger.Warning(YELLOW + "[FUTARI] WARN " + msg + RESET); } public static void Debug(string msg) { - System.Console.WriteLine(BLUE + "DEBUG " + msg + RESET); + MelonLogger.Msg(BLUE + "[FUTARI] DEBUG " + msg + RESET); } - public static void WriteLine(string msg) + public static void Info(string msg) { if (msg.StartsWith("A001")) msg = MAGENTA + msg; if (msg.StartsWith("A002")) msg = CYAN + msg; msg = msg.Replace("Error", RED + "Error"); - System.Console.WriteLine(msg + RESET); + MelonLogger.Msg("[FUTARI] INFO " + msg + RESET); } } From 595a293a93f52d02a4ec006fe439388bce40718e Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:30:10 -0500 Subject: [PATCH 04/78] [F] Fix patch? --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 9522dc9..8bdeff6 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -2,12 +2,17 @@ using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using AquaMai.Config.Attributes; using HarmonyLib; using MelonLoader; using PartyLink; namespace AquaMai.Mods.WorldsLink; +[ConfigSection( + en: "Enable WorldsLink Multiplayer", + zh: "启用 WorldsLink 多人游戏", + defaultOn: true)] public static class FutariPatch { private static readonly Dictionary redirect = new(); @@ -19,10 +24,10 @@ static FutariPatch() [HarmonyPostfix] [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(AddressFamily), typeof(SocketType), typeof(ProtocolType), typeof(int))] - private static void NFCreate(NFSocket __instance, AddressFamily a1, SocketType a2, ProtocolType a3, int a4) + private static void NFCreate(NFSocket __instance, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) { Log.Debug("new NFSocket(AddressFamily, SocketType, ProtocolType, int)"); - var futari = new FutariSocket(a1, a2, a3, a4); + var futari = new FutariSocket(addressFamily, socketType, protocolType, mockID); redirect.Add(__instance, futari); } From 3205d293e0237b87333c3623bf75e631bc4ac028 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:11:03 -0500 Subject: [PATCH 05/78] [O] Better logging --- AquaMai.Mods/WorldsLink/FutariTypes.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index aa5a383..696d00a 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -95,24 +95,23 @@ public abstract class Log public static void Error(string msg) { - MelonLogger.Error(RED + "[FUTARI] ERROR " + msg + RESET); + MelonLogger.Error($"[FUTARI] {RED}ERROR {RESET}{msg}{RESET}"); } public static void Warn(string msg) { - MelonLogger.Warning(YELLOW + "[FUTARI] WARN " + msg + RESET); + MelonLogger.Warning($"[FUTARI] {YELLOW}WARN {RESET}{msg}{RESET}"); } public static void Debug(string msg) { - MelonLogger.Msg(BLUE + "[FUTARI] DEBUG " + msg + RESET); + MelonLogger.Msg($"[FUTARI] {CYAN}DEBUG {RESET}{msg}{RESET}"); } public static void Info(string msg) { if (msg.StartsWith("A001")) msg = MAGENTA + msg; if (msg.StartsWith("A002")) msg = CYAN + msg; - msg = msg.Replace("Error", RED + "Error"); - MelonLogger.Msg("[FUTARI] INFO " + msg + RESET); + MelonLogger.Msg($"[FUTARI] {GREEN}INFO {RESET}{msg}{RESET}"); } } From 435f29d04bd823cbd57a37f04ce444ce83e3b420 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:11:24 -0500 Subject: [PATCH 06/78] [O] Reconnect --- AquaMai.Mods/WorldsLink/FutariClient.cs | 71 ++++++++++++++++++------- AquaMai.Mods/WorldsLink/FutariExt.cs | 1 + AquaMai.Mods/WorldsLink/FutariPatch.cs | 20 +++++-- AquaMai.Mods/WorldsLink/FutariSocket.cs | 1 - 4 files changed, 70 insertions(+), 23 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index fda7e75..973988c 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -8,11 +8,15 @@ namespace AquaMai.Mods.WorldsLink; -public class FutariClient +public class FutariClient(string keychip, string host, int port, int _) { public static FutariClient Instance { get; set; } - - private readonly string _keychip; + + public FutariClient(string keychip, string host, int port) : this(keychip, host, port, 0) + { + Instance = this; + } + private TcpClient _tcpClient; private StreamWriter _writer; private StreamReader _reader; @@ -27,16 +31,14 @@ public class FutariClient private Thread _sendThread; private Thread _recvThread; + + private bool _reconnecting = false; - public IPAddress StubIP => new IPAddress(FutariExt.KeychipToStubIp(_keychip)); - - public FutariClient(string keychip) - { - _keychip = keychip; - Instance = this; - } + public IPAddress StubIP => new IPAddress(FutariExt.KeychipToStubIp(keychip)); + + public void ConnectAsync() => new Thread(Connect) { IsBackground = true }.Start(); - public void Connect(string host, int port) + public void Connect() { _tcpClient = new TcpClient(); _tcpClient.Connect(host, port); @@ -44,9 +46,10 @@ public void Connect(string host, int port) var networkStream = _tcpClient.GetStream(); _writer = new StreamWriter(networkStream, Encoding.UTF8) { AutoFlush = true }; _reader = new StreamReader(networkStream, Encoding.UTF8); + _reconnecting = false; // Register - Send(new Msg { cmd = Cmd.CTL_START, data = _keychip }); + Send(new Msg { cmd = Cmd.CTL_START, data = keychip }); Log.Info($"Connected to server at {host}:{port}"); // Start communication and message receiving in separate threads @@ -65,6 +68,30 @@ public void Bind(int port, ProtocolType proto) udpRecvQ.TryAdd(port, new ConcurrentQueue()); } + private void Reconnect() + { + Log.Warn("Reconnect Entered"); + if (_reconnecting) return; + _reconnecting = true; + + try { _tcpClient.Close(); } + catch { /* ignored */ } + + try { _sendThread.Abort(); } + catch { /* ignored */ } + + try { _recvThread.Abort(); } + catch { /* ignored */ } + + _sendThread = null; + _recvThread = null; + _tcpClient = null; + + // Reconnect + Log.Warn("Reconnecting..."); + ConnectAsync(); + } + private void SendThread() { try @@ -88,9 +115,13 @@ private void SendThread() } catch (Exception ex) { - Log.Info($"Error during communication: {ex.Message}"); + Log.Error($"Error during communication: {ex.Message}"); + } + finally + { + Log.Error("SendThread finally reached"); + Reconnect(); } - finally { _tcpClient.Close(); } } private void RecvThread() @@ -108,14 +139,18 @@ private void RecvThread() } catch (Exception ex) { - Log.Info($"Error receiving messages: {ex.Message}"); + Log.Error($"Error receiving messages: {ex.Message}"); + } + finally + { + Log.Error("RecvThread finally reached"); + Reconnect(); } - finally { _tcpClient.Close(); } } private void HandleIncomingMessage(Msg msg) { - Log.Info($"{_keychip} <<< {msg}"); + Log.Info($"{keychip} <<< {msg}"); switch (msg.cmd) { @@ -139,6 +174,6 @@ private void HandleIncomingMessage(Msg msg) private void Send(Msg msg) { _writer.WriteLine(msg); - Log.Info($"{_keychip} >>> {msg}"); + Log.Info($"{keychip} >>> {msg}"); } } diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index 7bc2365..51f2474 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Net.Sockets; namespace AquaMai.Mods.WorldsLink; diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 8bdeff6..0c9f545 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -17,9 +17,20 @@ public static class FutariPatch { private static readonly Dictionary redirect = new(); - static FutariPatch() - { - new FutariClient(AMDaemon.System.KeychipId.ShortValue).Connect("violet", 20101); + public static void OnBeforePatch() + { + Log.Info("Starting WorldsLink patch..."); + var keychip = AMDaemon.System.KeychipId.ShortValue; + Log.Info($"Keychip ID: {keychip}"); + if (string.IsNullOrEmpty(keychip)) + { + Log.Error("Keychip ID is empty. WorldsLink will not work."); + // return; + + // For testing: Create a random keychip (10-digit number) + keychip = "A" + new Random().Next(1000000000, int.MaxValue); + } + new FutariClient(keychip, "violet", 20101).ConnectAsync(); } [HarmonyPostfix] @@ -43,7 +54,8 @@ private static void NFCreate2(NFSocket __instance, Socket nfSocket) [HarmonyPatch(typeof(NFSocket), "Poll")] private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) { - Log.Debug("NFPoll"); + // Let's not log this, there's too many of them + // Log.Debug("NFPoll"); FutariSocket.Poll(redirect[socket], mode); return false; } diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index 0cfb009..d2bf854 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -50,7 +50,6 @@ public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) { } // This is the Select step called before blocking calls (e.g. Accept) public static bool Poll(FutariSocket socket, SelectMode mode) { - Log.Debug("Poll called"); if (mode == SelectMode.SelectRead) { return (socket._proto == ProtocolType.Udp) // Check is UDP From 7db889ffb5dfa3e3cc572fa1ea1b2715bc0e0bf9 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:34:35 -0500 Subject: [PATCH 07/78] [F] Fix keychip --- AquaMai.Mods/WorldsLink/FutariClient.cs | 2 ++ AquaMai.Mods/WorldsLink/FutariPatch.cs | 38 +++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 973988c..f708ea6 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -17,6 +17,8 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, Instance = this; } + public string keychip { get; set; } = keychip; + private TcpClient _tcpClient; private StreamWriter _writer; private StreamReader _reader; diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 0c9f545..fdfad21 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -4,6 +4,7 @@ using System.Net.Sockets; using AquaMai.Config.Attributes; using HarmonyLib; +using Manager; using MelonLoader; using PartyLink; @@ -16,10 +17,40 @@ namespace AquaMai.Mods.WorldsLink; public static class FutariPatch { private static readonly Dictionary redirect = new(); + private static FutariClient client; public static void OnBeforePatch() { Log.Info("Starting WorldsLink patch..."); + client = new FutariClient("A000", "violet", 20101); + } + + // Other patches not in NFSocket + // public static IPAddress MyIpAddress(int mockID) + [HarmonyPrefix] + [HarmonyPatch(typeof(PartyLink.Util), "MyIpAddress", typeof(int))] + private static bool MyIpAddress(int mockID, ref IPAddress __result) + { + __result = new IPAddress(FutariExt.MyStubIP()); + return false; + } + + // public static uint ToNetworkByteOrderU32(this IPAddress ip) + [HarmonyPrefix] + [HarmonyPatch(typeof(PartyLink.Util), "ToNetworkByteOrderU32")] + private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) + { + __result = BitConverter.ToUInt32(ip.GetAddressBytes(), 0); + return false; + } + + // private void CheckAuth_Proc() + [HarmonyPrefix] + [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] + private static bool CheckAuth_Proc() + { + Log.Info("CheckAuth_Proc"); + var keychip = AMDaemon.System.KeychipId.ShortValue; Log.Info($"Keychip ID: {keychip}"); if (string.IsNullOrEmpty(keychip)) @@ -30,7 +61,10 @@ public static void OnBeforePatch() // For testing: Create a random keychip (10-digit number) keychip = "A" + new Random().Next(1000000000, int.MaxValue); } - new FutariClient(keychip, "violet", 20101).ConnectAsync(); + client.keychip = keychip; + client.ConnectAsync(); + + return true; // Allow the original method to run } [HarmonyPostfix] @@ -54,8 +88,6 @@ private static void NFCreate2(NFSocket __instance, Socket nfSocket) [HarmonyPatch(typeof(NFSocket), "Poll")] private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) { - // Let's not log this, there's too many of them - // Log.Debug("NFPoll"); FutariSocket.Poll(redirect[socket], mode); return false; } From cd1a87546ec1933141353cd852a99c5107e2d707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Wed, 15 Jan 2025 00:02:58 +0900 Subject: [PATCH 08/78] [F] Possible lifecycle iteration corrections --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index fdfad21..d9abde0 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -18,6 +18,7 @@ public static class FutariPatch { private static readonly Dictionary redirect = new(); private static FutariClient client; + private static bool isInit=false; public static void OnBeforePatch() { @@ -49,6 +50,7 @@ private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] private static bool CheckAuth_Proc() { + if (isInit) return true; Log.Info("CheckAuth_Proc"); var keychip = AMDaemon.System.KeychipId.ShortValue; @@ -62,8 +64,9 @@ private static bool CheckAuth_Proc() keychip = "A" + new Random().Next(1000000000, int.MaxValue); } client.keychip = keychip; - client.ConnectAsync(); - + client.ConnectAsync(); + + isInit = true; return true; // Allow the original method to run } From 77939776467101db3553d63338c5b20335314970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Wed, 15 Jan 2025 00:04:27 +0900 Subject: [PATCH 09/78] [+] Connect Failed Log --- AquaMai.Mods/WorldsLink/FutariClient.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index f708ea6..b93efd8 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -43,8 +43,16 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, public void Connect() { _tcpClient = new TcpClient(); - _tcpClient.Connect(host, port); + try + { + _tcpClient.Connect(host, port); + } + catch (Exception ex) + { + Log.Error($"Error connecting to server:\nHost:{host}:{port}\n{ex.Message}"); + return; + } var networkStream = _tcpClient.GetStream(); _writer = new StreamWriter(networkStream, Encoding.UTF8) { AutoFlush = true }; _reader = new StreamReader(networkStream, Encoding.UTF8); From 60978ad9250f710d126f05257379e6aad1f23a5f Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:13:57 -0500 Subject: [PATCH 10/78] [+] Patch send enable --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 35 ++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index d9abde0..a790a4b 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -26,6 +26,37 @@ public static void OnBeforePatch() client = new FutariClient("A000", "violet", 20101); } + // Patch for logging + // SocketBase:: public void sendClass(ICommandParam info) + [HarmonyPrefix] + [HarmonyPatch(typeof(SocketBase), "sendClass", typeof(ICommandParam))] + private static bool sendClass(SocketBase __instance, ICommandParam info) + { + // For logging only, log the actual type of info and the actual type of this class + Log.Debug($"SendClass: {info.GetType().Name} from {__instance.GetType().Name}"); + return true; + } + + // Patch for error logging + // SocketBase:: protected void error(string message, int no) + [HarmonyPrefix] + [HarmonyPatch(typeof(SocketBase), "error", typeof(string), typeof(int))] + private static bool error(string message, int no) + { + Log.Error($"Error: {message} ({no})"); + return true; + } + + // Patch to always enable send + // SocketBase:: public static bool checkSendEnable(NFSocket socket) + [HarmonyPrefix] + [HarmonyPatch(typeof(SocketBase), "checkSendEnable", typeof(NFSocket))] + private static bool checkSendEnable(NFSocket socket, ref bool __result) + { + __result = true; + return false; + } + // Other patches not in NFSocket // public static IPAddress MyIpAddress(int mockID) [HarmonyPrefix] @@ -64,8 +95,8 @@ private static bool CheckAuth_Proc() keychip = "A" + new Random().Next(1000000000, int.MaxValue); } client.keychip = keychip; - client.ConnectAsync(); - + client.ConnectAsync(); + isInit = true; return true; // Allow the original method to run } From 83b7e433efcf38716ce6667de21b7d319871996c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:52:59 +0900 Subject: [PATCH 11/78] [+]RM Packet Encrypt --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 57 +++++++++++++++++++------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index a790a4b..e57244a 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using System.Reflection; using AquaMai.Config.Attributes; using HarmonyLib; using Manager; @@ -20,10 +21,15 @@ public static class FutariPatch private static FutariClient client; private static bool isInit=false; + static MethodBase packet_writeunit; public static void OnBeforePatch() { Log.Info("Starting WorldsLink patch..."); - client = new FutariClient("A000", "violet", 20101); + + packet_writeunit = typeof(Packet).GetMethod("write_uint", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static,null, new System.Type[] { typeof(PacketType), typeof(int), typeof(uint)},null); + if(packet_writeunit==null)Log.Error("write_uint not found"); + + client = new FutariClient("A000", "70.49.234.104", 20101); } // Patch for logging @@ -74,9 +80,9 @@ private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) { __result = BitConverter.ToUInt32(ip.GetAddressBytes(), 0); return false; - } - - // private void CheckAuth_Proc() + } + + // private void CheckAuth_Proc() [HarmonyPrefix] [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] private static bool CheckAuth_Proc() @@ -99,8 +105,8 @@ private static bool CheckAuth_Proc() isInit = true; return true; // Allow the original method to run - } - + } + #region NFSocket [HarmonyPostfix] [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(AddressFamily), typeof(SocketType), typeof(ProtocolType), typeof(int))] private static void NFCreate(NFSocket __instance, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) @@ -108,24 +114,24 @@ private static void NFCreate(NFSocket __instance, AddressFamily addressFamily, S Log.Debug("new NFSocket(AddressFamily, SocketType, ProtocolType, int)"); var futari = new FutariSocket(addressFamily, socketType, protocolType, mockID); redirect.Add(__instance, futari); - } - + } + [HarmonyPostfix] [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(Socket))] private static void NFCreate2(NFSocket __instance, Socket nfSocket) { Log.Warn("new NFSocket(Socket) -- We shouldn't get here."); throw new NotImplementedException(); - } - + } + [HarmonyPrefix] [HarmonyPatch(typeof(NFSocket), "Poll")] private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) { FutariSocket.Poll(redirect[socket], mode); return false; - } - + } + [HarmonyPrefix] [HarmonyPatch(typeof(NFSocket), "Send")] private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags) @@ -133,8 +139,8 @@ private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int s Log.Debug("NFSend"); redirect[__instance].Send(buffer, offset, size, socketFlags); return false; - } - + } + [HarmonyPrefix] [HarmonyPatch(typeof(NFSocket), "SendTo")] private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) @@ -243,5 +249,28 @@ private static bool NFGetLocalEndPoint(NFSocket __instance, ref EndPoint __resul Log.Debug("NFGetLocalEndPoint"); __result = redirect[__instance].LocalEndPoint; return false; + } + #endregion + #region Packet + [HarmonyPrefix] + [HarmonyPatch(typeof(Packet), "encrypt")] + private static bool PacketEncrypt(Packet __instance, PacketType ____encrypt, PacketType ____plane) + { + ____encrypt.ClearAndResize(____plane.Count); + Array.Copy(____plane.GetBuffer(), 0, ____encrypt.GetBuffer(), 0, ____plane.Count); + ____encrypt.ChangeCount(____plane.Count); + packet_writeunit.Invoke(null, new object[] { ____plane, 0, (uint)____plane.Count }); + return true; + } + [HarmonyPrefix] + [HarmonyPatch(typeof(Packet), "decode")] + private static bool PacketDecode(Packet __instance, BufferType buffer, IpAddress address, PacketType ____encrypt, PacketType ____plane) + { + ____plane.ClearAndResize(____encrypt.Count); + Array.Copy(____encrypt.GetBuffer(), 0, ____plane.GetBuffer(), 0, ____encrypt.Count); + ____plane.ChangeCount(____encrypt.Count); + packet_writeunit.Invoke(null, new object[] { ____plane, 0, (uint)____plane.Count }); + return true; } + #endregion } \ No newline at end of file From 4d218d50d42faad52e1809bb670f750176c6feac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:53:13 +0900 Subject: [PATCH 12/78] [+]Skip DeliveryChecker --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index e57244a..e847601 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -82,6 +82,15 @@ private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) return false; } + //Skip DeliveryChecker + [HarmonyPostfix] + [HarmonyPatch("PartyLink.DeliveryChecker+Manager", "isOk")] + private static void DeliveryCheckerIsOk(ref bool __result) + { + MelonLogger.Msg("DeliveryCheckerIsOk:true"); + __result = true; + } + // private void CheckAuth_Proc() [HarmonyPrefix] [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] From 0cc618603bb6e463beaed1e58ccc9f99a2776381 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:58:27 -0500 Subject: [PATCH 13/78] [F] Fix return --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 115 +++++++++++++------------ 1 file changed, 61 insertions(+), 54 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index e847601..e18f723 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -19,16 +19,17 @@ public static class FutariPatch { private static readonly Dictionary redirect = new(); private static FutariClient client; - private static bool isInit=false; + private static bool isInit = false; static MethodBase packet_writeunit; public static void OnBeforePatch() { Log.Info("Starting WorldsLink patch..."); - packet_writeunit = typeof(Packet).GetMethod("write_uint", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static,null, new System.Type[] { typeof(PacketType), typeof(int), typeof(uint)},null); - if(packet_writeunit==null)Log.Error("write_uint not found"); - + packet_writeunit = typeof (Packet).GetMethod("write_uint", BindingFlags.NonPublic | BindingFlags.Static,null, + [typeof(PacketType), typeof(int), typeof(uint)],null); + if (packet_writeunit == null) Log.Error("write_uint not found"); + client = new FutariClient("A000", "70.49.234.104", 20101); } @@ -55,13 +56,13 @@ private static bool error(string message, int no) // Patch to always enable send // SocketBase:: public static bool checkSendEnable(NFSocket socket) - [HarmonyPrefix] - [HarmonyPatch(typeof(SocketBase), "checkSendEnable", typeof(NFSocket))] - private static bool checkSendEnable(NFSocket socket, ref bool __result) - { - __result = true; - return false; - } + // [HarmonyPrefix] + // [HarmonyPatch(typeof(SocketBase), "checkSendEnable", typeof(NFSocket))] + // private static bool checkSendEnable(NFSocket socket, ref bool __result) + // { + // __result = true; + // return false; + // } // Other patches not in NFSocket // public static IPAddress MyIpAddress(int mockID) @@ -80,18 +81,18 @@ private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) { __result = BitConverter.ToUInt32(ip.GetAddressBytes(), 0); return false; - } - - //Skip DeliveryChecker - [HarmonyPostfix] - [HarmonyPatch("PartyLink.DeliveryChecker+Manager", "isOk")] - private static void DeliveryCheckerIsOk(ref bool __result) - { - MelonLogger.Msg("DeliveryCheckerIsOk:true"); - __result = true; - } - - // private void CheckAuth_Proc() + } + + //Skip DeliveryChecker + [HarmonyPostfix] + [HarmonyPatch("PartyLink.DeliveryChecker+Manager", "isOk")] + private static void DeliveryCheckerIsOk(ref bool __result) + { + MelonLogger.Msg("DeliveryCheckerIsOk:true"); + __result = true; + } + + // private void CheckAuth_Proc() [HarmonyPrefix] [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] private static bool CheckAuth_Proc() @@ -114,8 +115,9 @@ private static bool CheckAuth_Proc() isInit = true; return true; // Allow the original method to run - } - #region NFSocket + } + + #region NFSocket [HarmonyPostfix] [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(AddressFamily), typeof(SocketType), typeof(ProtocolType), typeof(int))] private static void NFCreate(NFSocket __instance, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) @@ -123,57 +125,57 @@ private static void NFCreate(NFSocket __instance, AddressFamily addressFamily, S Log.Debug("new NFSocket(AddressFamily, SocketType, ProtocolType, int)"); var futari = new FutariSocket(addressFamily, socketType, protocolType, mockID); redirect.Add(__instance, futari); - } - + } + [HarmonyPostfix] [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(Socket))] private static void NFCreate2(NFSocket __instance, Socket nfSocket) { Log.Warn("new NFSocket(Socket) -- We shouldn't get here."); throw new NotImplementedException(); - } - + } + [HarmonyPrefix] [HarmonyPatch(typeof(NFSocket), "Poll")] private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) { - FutariSocket.Poll(redirect[socket], mode); + __result = FutariSocket.Poll(redirect[socket], mode); return false; - } - + } + [HarmonyPrefix] [HarmonyPatch(typeof(NFSocket), "Send")] - private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags) + private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, ref int __result) { Log.Debug("NFSend"); - redirect[__instance].Send(buffer, offset, size, socketFlags); + __result = redirect[__instance].Send(buffer, offset, size, socketFlags); return false; - } - + } + [HarmonyPrefix] [HarmonyPatch(typeof(NFSocket), "SendTo")] - private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) + private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP, ref int __result) { Log.Debug("NFSendTo"); - redirect[__instance].SendTo(buffer, offset, size, socketFlags, remoteEP); + __result = redirect[__instance].SendTo(buffer, offset, size, socketFlags, remoteEP); return false; } [HarmonyPrefix] [HarmonyPatch(typeof(NFSocket), "Receive")] - private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode) + private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode, ref int __result) { Log.Debug("NFReceive"); - redirect[__instance].Receive(buffer, offset, size, socketFlags, out errorCode); + __result = redirect[__instance].Receive(buffer, offset, size, socketFlags, out errorCode); return false; } [HarmonyPrefix] [HarmonyPatch(typeof(NFSocket), "ReceiveFrom")] - private static bool NFReceiveFrom(NFSocket __instance, byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP) + private static bool NFReceiveFrom(NFSocket __instance, byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP, ref int __result) { Log.Debug("NFReceiveFrom"); - redirect[__instance].ReceiveFrom(buffer, socketFlags, ref remoteEP); + __result = redirect[__instance].ReceiveFrom(buffer, socketFlags, ref remoteEP); return false; } @@ -203,15 +205,16 @@ private static bool NFAccept(NFSocket __instance, ref NFSocket __result) var futariSocket = redirect[__instance].Accept(); var mockSocket = new NFSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp, 0); redirect.Add(mockSocket, futariSocket); + __result = mockSocket; return false; } [HarmonyPrefix] [HarmonyPatch(typeof(NFSocket), "ConnectAsync")] - private static bool NFConnectAsync(NFSocket __instance, SocketAsyncEventArgs e, int mockID) + private static bool NFConnectAsync(NFSocket __instance, SocketAsyncEventArgs e, int mockID, ref bool __result) { Log.Debug("NFConnectAsync"); - redirect[__instance].ConnectAsync(e, mockID); + __result = redirect[__instance].ConnectAsync(e, mockID); return false; } @@ -258,27 +261,31 @@ private static bool NFGetLocalEndPoint(NFSocket __instance, ref EndPoint __resul Log.Debug("NFGetLocalEndPoint"); __result = redirect[__instance].LocalEndPoint; return false; - } + } #endregion + #region Packet + // Disable encryption [HarmonyPrefix] [HarmonyPatch(typeof(Packet), "encrypt")] private static bool PacketEncrypt(Packet __instance, PacketType ____encrypt, PacketType ____plane) { - ____encrypt.ClearAndResize(____plane.Count); - Array.Copy(____plane.GetBuffer(), 0, ____encrypt.GetBuffer(), 0, ____plane.Count); - ____encrypt.ChangeCount(____plane.Count); - packet_writeunit.Invoke(null, new object[] { ____plane, 0, (uint)____plane.Count }); + ____encrypt.ClearAndResize(____plane.Count); + Array.Copy(____plane.GetBuffer(), 0, ____encrypt.GetBuffer(), 0, ____plane.Count); + ____encrypt.ChangeCount(____plane.Count); + packet_writeunit.Invoke(null, [____plane, 0, (uint)____plane.Count]); return true; - } + } + + // Disable decryption [HarmonyPrefix] [HarmonyPatch(typeof(Packet), "decode")] private static bool PacketDecode(Packet __instance, BufferType buffer, IpAddress address, PacketType ____encrypt, PacketType ____plane) { - ____plane.ClearAndResize(____encrypt.Count); - Array.Copy(____encrypt.GetBuffer(), 0, ____plane.GetBuffer(), 0, ____encrypt.Count); - ____plane.ChangeCount(____encrypt.Count); - packet_writeunit.Invoke(null, new object[] { ____plane, 0, (uint)____plane.Count }); + ____plane.ClearAndResize(____encrypt.Count); + Array.Copy(____encrypt.GetBuffer(), 0, ____plane.GetBuffer(), 0, ____encrypt.Count); + ____plane.ChangeCount(____encrypt.Count); + packet_writeunit.Invoke(null, [____plane, 0, (uint)____plane.Count]); return true; } #endregion From 1d168e969ca0573fa1b0a22fe27afe44037a9691 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Tue, 14 Jan 2025 12:22:59 -0500 Subject: [PATCH 14/78] [O] Use consts --- AquaMai.Mods/WorldsLink/FutariClient.cs | 16 +++---- AquaMai.Mods/WorldsLink/FutariPatch.cs | 60 ++++++++++++------------- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index b93efd8..21b1bc4 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -44,14 +44,14 @@ public void Connect() { _tcpClient = new TcpClient(); - try - { - _tcpClient.Connect(host, port); + try + { + _tcpClient.Connect(host, port); } - catch (Exception ex) - { - Log.Error($"Error connecting to server:\nHost:{host}:{port}\n{ex.Message}"); - return; + catch (Exception ex) + { + Log.Error($"Error connecting to server:\nHost:{host}:{port}\n{ex.Message}"); + return; } var networkStream = _tcpClient.GetStream(); _writer = new StreamWriter(networkStream, Encoding.UTF8) { AutoFlush = true }; @@ -141,7 +141,7 @@ private void RecvThread() while (true) { var line = _reader.ReadLine(); - if (line == null) break; + if (line == null) continue; var message = Msg.FromString(line); HandleIncomingMessage(message); diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index e18f723..fdd4c5a 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -21,6 +21,9 @@ public static class FutariPatch private static FutariClient client; private static bool isInit = false; + private const bool BLOCK_ORIGINAL = false; + private const bool RUN_ORIGINAL = true; + static MethodBase packet_writeunit; public static void OnBeforePatch() { @@ -39,9 +42,12 @@ public static void OnBeforePatch() [HarmonyPatch(typeof(SocketBase), "sendClass", typeof(ICommandParam))] private static bool sendClass(SocketBase __instance, ICommandParam info) { + // Block AdvocateDelivery + if (info is AdvocateDelivery) return false; + // For logging only, log the actual type of info and the actual type of this class Log.Debug($"SendClass: {info.GetType().Name} from {__instance.GetType().Name}"); - return true; + return RUN_ORIGINAL; } // Patch for error logging @@ -51,19 +57,9 @@ private static bool sendClass(SocketBase __instance, ICommandParam info) private static bool error(string message, int no) { Log.Error($"Error: {message} ({no})"); - return true; + return RUN_ORIGINAL; } - // Patch to always enable send - // SocketBase:: public static bool checkSendEnable(NFSocket socket) - // [HarmonyPrefix] - // [HarmonyPatch(typeof(SocketBase), "checkSendEnable", typeof(NFSocket))] - // private static bool checkSendEnable(NFSocket socket, ref bool __result) - // { - // __result = true; - // return false; - // } - // Other patches not in NFSocket // public static IPAddress MyIpAddress(int mockID) [HarmonyPrefix] @@ -71,7 +67,7 @@ private static bool error(string message, int no) private static bool MyIpAddress(int mockID, ref IPAddress __result) { __result = new IPAddress(FutariExt.MyStubIP()); - return false; + return BLOCK_ORIGINAL; } // public static uint ToNetworkByteOrderU32(this IPAddress ip) @@ -80,7 +76,7 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) { __result = BitConverter.ToUInt32(ip.GetAddressBytes(), 0); - return false; + return BLOCK_ORIGINAL; } //Skip DeliveryChecker @@ -97,7 +93,7 @@ private static void DeliveryCheckerIsOk(ref bool __result) [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] private static bool CheckAuth_Proc() { - if (isInit) return true; + if (isInit) return RUN_ORIGINAL; Log.Info("CheckAuth_Proc"); var keychip = AMDaemon.System.KeychipId.ShortValue; @@ -114,7 +110,7 @@ private static bool CheckAuth_Proc() client.ConnectAsync(); isInit = true; - return true; // Allow the original method to run + return RUN_ORIGINAL; } #region NFSocket @@ -140,7 +136,7 @@ private static void NFCreate2(NFSocket __instance, Socket nfSocket) private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) { __result = FutariSocket.Poll(redirect[socket], mode); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -149,7 +145,7 @@ private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int s { Log.Debug("NFSend"); __result = redirect[__instance].Send(buffer, offset, size, socketFlags); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -158,7 +154,7 @@ private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int { Log.Debug("NFSendTo"); __result = redirect[__instance].SendTo(buffer, offset, size, socketFlags, remoteEP); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -167,7 +163,7 @@ private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, in { Log.Debug("NFReceive"); __result = redirect[__instance].Receive(buffer, offset, size, socketFlags, out errorCode); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -176,7 +172,7 @@ private static bool NFReceiveFrom(NFSocket __instance, byte[] buffer, SocketFlag { Log.Debug("NFReceiveFrom"); __result = redirect[__instance].ReceiveFrom(buffer, socketFlags, ref remoteEP); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -185,7 +181,7 @@ private static bool NFBind(NFSocket __instance, EndPoint localEndP) { Log.Debug("NFBind"); redirect[__instance].Bind(localEndP); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -194,7 +190,7 @@ private static bool NFListen(NFSocket __instance, int backlog) { Log.Debug("NFListen"); redirect[__instance].Listen(backlog); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -206,7 +202,7 @@ private static bool NFAccept(NFSocket __instance, ref NFSocket __result) var mockSocket = new NFSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp, 0); redirect.Add(mockSocket, futariSocket); __result = mockSocket; - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -215,7 +211,7 @@ private static bool NFConnectAsync(NFSocket __instance, SocketAsyncEventArgs e, { Log.Debug("NFConnectAsync"); __result = redirect[__instance].ConnectAsync(e, mockID); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -224,7 +220,7 @@ private static bool NFSetSocketOption(NFSocket __instance, SocketOptionLevel opt { Log.Debug("NFSetSocketOption"); redirect[__instance].SetSocketOption(optionLevel, optionName, optionValue); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -233,7 +229,7 @@ private static bool NFClose(NFSocket __instance) { Log.Debug("NFClose"); redirect[__instance].Close(); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -242,7 +238,7 @@ private static bool NFShutdown(NFSocket __instance, SocketShutdown how) { Log.Debug("NFShutdown"); redirect[__instance].Shutdown(how); - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -251,7 +247,7 @@ private static bool NFGetRemoteEndPoint(NFSocket __instance, ref EndPoint __resu { Log.Debug("NFGetRemoteEndPoint"); __result = redirect[__instance].RemoteEndPoint; - return false; + return BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -260,7 +256,7 @@ private static bool NFGetLocalEndPoint(NFSocket __instance, ref EndPoint __resul { Log.Debug("NFGetLocalEndPoint"); __result = redirect[__instance].LocalEndPoint; - return false; + return BLOCK_ORIGINAL; } #endregion @@ -274,7 +270,7 @@ private static bool PacketEncrypt(Packet __instance, PacketType ____encrypt, Pac Array.Copy(____plane.GetBuffer(), 0, ____encrypt.GetBuffer(), 0, ____plane.Count); ____encrypt.ChangeCount(____plane.Count); packet_writeunit.Invoke(null, [____plane, 0, (uint)____plane.Count]); - return true; + return BLOCK_ORIGINAL; } // Disable decryption @@ -286,7 +282,7 @@ private static bool PacketDecode(Packet __instance, BufferType buffer, IpAddress Array.Copy(____encrypt.GetBuffer(), 0, ____plane.GetBuffer(), 0, ____encrypt.Count); ____plane.ChangeCount(____encrypt.Count); packet_writeunit.Invoke(null, [____plane, 0, (uint)____plane.Count]); - return true; + return BLOCK_ORIGINAL; } #endregion } \ No newline at end of file From d561ca2231ef13ac67b3c1063116220651b263a3 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Tue, 14 Jan 2025 12:37:56 -0500 Subject: [PATCH 15/78] [F] Fix decrypt --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index fdd4c5a..6df688c 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -275,8 +275,8 @@ private static bool PacketEncrypt(Packet __instance, PacketType ____encrypt, Pac // Disable decryption [HarmonyPrefix] - [HarmonyPatch(typeof(Packet), "decode")] - private static bool PacketDecode(Packet __instance, BufferType buffer, IpAddress address, PacketType ____encrypt, PacketType ____plane) + [HarmonyPatch(typeof(Packet), "decrypt")] + private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, PacketType ____plane) { ____plane.ClearAndResize(____encrypt.Count); Array.Copy(____encrypt.GetBuffer(), 0, ____plane.GetBuffer(), 0, ____encrypt.Count); From 7ca4eac51ba644d21a2d909e11b5fc70a151830a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Wed, 15 Jan 2025 02:49:01 +0900 Subject: [PATCH 16/78] [+] StartupNetworkChecker --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 96 ++++++++++++++------------ 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index e18f723..0b1514d 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -6,8 +6,8 @@ using AquaMai.Config.Attributes; using HarmonyLib; using Manager; -using MelonLoader; using PartyLink; +using Process; namespace AquaMai.Mods.WorldsLink; @@ -22,19 +22,23 @@ public static class FutariPatch private static bool isInit = false; static MethodBase packet_writeunit; + static System.Type StartUpStateType; public static void OnBeforePatch() { Log.Info("Starting WorldsLink patch..."); - packet_writeunit = typeof (Packet).GetMethod("write_uint", BindingFlags.NonPublic | BindingFlags.Static,null, - [typeof(PacketType), typeof(int), typeof(uint)],null); - if (packet_writeunit == null) Log.Error("write_uint not found"); + packet_writeunit = typeof(Packet).GetMethod("write_uint", BindingFlags.NonPublic | BindingFlags.Static, null, + [typeof(PacketType), typeof(int), typeof(uint)], null); + if (packet_writeunit == null) Log.Error("write_uint not found"); + + StartUpStateType = typeof(StartupProcess).GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance).FieldType; + if (StartUpStateType == null) Log.Error("StartUpStateType not found"); client = new FutariClient("A000", "70.49.234.104", 20101); - } - - // Patch for logging - // SocketBase:: public void sendClass(ICommandParam info) + } + + // Patch for logging + // SocketBase:: public void sendClass(ICommandParam info) [HarmonyPrefix] [HarmonyPatch(typeof(SocketBase), "sendClass", typeof(ICommandParam))] private static bool sendClass(SocketBase __instance, ICommandParam info) @@ -42,39 +46,39 @@ private static bool sendClass(SocketBase __instance, ICommandParam info) // For logging only, log the actual type of info and the actual type of this class Log.Debug($"SendClass: {info.GetType().Name} from {__instance.GetType().Name}"); return true; - } - - // Patch for error logging - // SocketBase:: protected void error(string message, int no) + } + + // Patch for error logging + // SocketBase:: protected void error(string message, int no) [HarmonyPrefix] [HarmonyPatch(typeof(SocketBase), "error", typeof(string), typeof(int))] private static bool error(string message, int no) { Log.Error($"Error: {message} ({no})"); return true; - } - - // Patch to always enable send - // SocketBase:: public static bool checkSendEnable(NFSocket socket) - // [HarmonyPrefix] - // [HarmonyPatch(typeof(SocketBase), "checkSendEnable", typeof(NFSocket))] - // private static bool checkSendEnable(NFSocket socket, ref bool __result) - // { - // __result = true; - // return false; - // } - - // Other patches not in NFSocket - // public static IPAddress MyIpAddress(int mockID) + } + + // Patch to always enable send + // SocketBase:: public static bool checkSendEnable(NFSocket socket) + // [HarmonyPrefix] + // [HarmonyPatch(typeof(SocketBase), "checkSendEnable", typeof(NFSocket))] + // private static bool checkSendEnable(NFSocket socket, ref bool __result) + // { + // __result = true; + // return false; + // } + + // Other patches not in NFSocket + // public static IPAddress MyIpAddress(int mockID) [HarmonyPrefix] [HarmonyPatch(typeof(PartyLink.Util), "MyIpAddress", typeof(int))] private static bool MyIpAddress(int mockID, ref IPAddress __result) { __result = new IPAddress(FutariExt.MyStubIP()); return false; - } - - // public static uint ToNetworkByteOrderU32(this IPAddress ip) + } + + // public static uint ToNetworkByteOrderU32(this IPAddress ip) [HarmonyPrefix] [HarmonyPatch(typeof(PartyLink.Util), "ToNetworkByteOrderU32")] private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) @@ -83,13 +87,17 @@ private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) return false; } - //Skip DeliveryChecker + //Skip StartupNetworkChecker [HarmonyPostfix] - [HarmonyPatch("PartyLink.DeliveryChecker+Manager", "isOk")] - private static void DeliveryCheckerIsOk(ref bool __result) - { - MelonLogger.Msg("DeliveryCheckerIsOk:true"); - __result = true; + [HarmonyPatch("StartupProcess", nameof(StartupProcess.OnUpdate))] + private static void SkipStartupNetworkCheck(ref byte ____state) + { + //Log.Info("StartupProcess E:"+ Enum.GetName(StartUpStateType,____state)); + if (____state == 0x04/*StartupProcess.StartUpState.WaitLinkDelivery*/) + { + ____state = 0x08;//StartupProcess.StartUpState.Ready + Log.Info("Skip Startup Network Check"); + } } // private void CheckAuth_Proc() @@ -98,16 +106,16 @@ private static void DeliveryCheckerIsOk(ref bool __result) private static bool CheckAuth_Proc() { if (isInit) return true; - Log.Info("CheckAuth_Proc"); - + Log.Info("CheckAuth_Proc"); + var keychip = AMDaemon.System.KeychipId.ShortValue; Log.Info($"Keychip ID: {keychip}"); if (string.IsNullOrEmpty(keychip)) { - Log.Error("Keychip ID is empty. WorldsLink will not work."); - // return; - - // For testing: Create a random keychip (10-digit number) + Log.Error("Keychip ID is empty. WorldsLink will not work."); + // return; + + // For testing: Create a random keychip (10-digit number) keychip = "A" + new Random().Next(1000000000, int.MaxValue); } client.keychip = keychip; @@ -275,9 +283,9 @@ private static bool PacketEncrypt(Packet __instance, PacketType ____encrypt, Pac ____encrypt.ChangeCount(____plane.Count); packet_writeunit.Invoke(null, [____plane, 0, (uint)____plane.Count]); return true; - } - - // Disable decryption + } + + // Disable decryption [HarmonyPrefix] [HarmonyPatch(typeof(Packet), "decode")] private static bool PacketDecode(Packet __instance, BufferType buffer, IpAddress address, PacketType ____encrypt, PacketType ____plane) From 64519b676b8216adf289cbdaf37fc02ea3ea46d5 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 05:58:10 -0500 Subject: [PATCH 17/78] [+] Log to readable string --- AquaMai.Mods/WorldsLink/FutariTypes.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index 696d00a..a724e2a 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Sockets; +using System.Text; using MelonLoader; namespace AquaMai.Mods.WorldsLink; @@ -66,6 +68,23 @@ public static Msg FromString(string str) data = string.Join(",", fields.Skip(16)) }; } + + public string ToReadableString() + { + var parts = new List { cmd.ToString() }; + + if (proto.HasValue) parts.Add(proto.ToString()); + if (sid.HasValue) parts.Add($"Stream: {sid}"); + if (src.HasValue) parts.Add($"Src: {src?.ToIP()}:{sPort}"); + if (dst.HasValue) parts.Add($"Dst: {dst?.ToIP()}:{dPort}"); + if (!string.IsNullOrEmpty(data)) + { + try { parts.Add(Encoding.UTF8.GetString(data.B64())); } + catch { parts.Add(data); } + } + + return string.Join(" | ", parts); + } } public abstract class Log From 2feccb13caf7db47472255265426974614fdd5df Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 05:58:21 -0500 Subject: [PATCH 18/78] [O] Log with locking --- AquaMai.Mods/WorldsLink/FutariTypes.cs | 28 +++++++++++++++++++------- build.bat | 2 ++ 2 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 build.bat diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index a724e2a..904431f 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -87,8 +87,10 @@ public string ToReadableString() } } -public abstract class Log +public static class Log { + private static readonly object _lock = new object(); + // Text colors private const string BLACK = "\u001b[30m"; private const string RED = "\u001b[31m"; @@ -114,23 +116,35 @@ public abstract class Log public static void Error(string msg) { - MelonLogger.Error($"[FUTARI] {RED}ERROR {RESET}{msg}{RESET}"); + lock (_lock) + { + MelonLogger.Error($"[FUTARI] {RED}ERROR {RESET}{msg}{RESET}"); + } } public static void Warn(string msg) { - MelonLogger.Warning($"[FUTARI] {YELLOW}WARN {RESET}{msg}{RESET}"); + lock (_lock) + { + MelonLogger.Warning($"[FUTARI] {YELLOW}WARN {RESET}{msg}{RESET}"); + } } public static void Debug(string msg) { - MelonLogger.Msg($"[FUTARI] {CYAN}DEBUG {RESET}{msg}{RESET}"); + lock (_lock) + { + MelonLogger.Msg($"[FUTARI] {CYAN}DEBUG {RESET}{msg}{RESET}"); + } } public static void Info(string msg) { - if (msg.StartsWith("A001")) msg = MAGENTA + msg; - if (msg.StartsWith("A002")) msg = CYAN + msg; - MelonLogger.Msg($"[FUTARI] {GREEN}INFO {RESET}{msg}{RESET}"); + lock (_lock) + { + if (msg.StartsWith("A001")) msg = MAGENTA + msg; + if (msg.StartsWith("A002")) msg = CYAN + msg; + MelonLogger.Msg($"[FUTARI] {GREEN}INFO {RESET}{msg}{RESET}"); + } } } diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..816f3a2 --- /dev/null +++ b/build.bat @@ -0,0 +1,2 @@ +dotnet build +copy C:\ws\maimai\AquaMai\Output\AquaMai.dll C:\ws\maimai\145\Mods \ No newline at end of file From 8285f70236b01a3fb455832b5e828f646f3e19d9 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 05:58:39 -0500 Subject: [PATCH 19/78] [+] ToIP --- AquaMai.Mods/WorldsLink/FutariExt.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index 51f2474..4101f4e 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Net; using System.Net.Sockets; +using System.Text; namespace AquaMai.Mods.WorldsLink; @@ -12,8 +14,11 @@ public static uint KeychipToStubIp(string keychip) { return uint.Parse("1" + keychip.Substring(2)); } + + public static IPAddress ToIP(this uint ipAsUint) => new(BitConverter.GetBytes(ipAsUint)); public static R Let(this T x, Func f) => f(x); + public static T Also(this T x, Action f) { f(x); return x; } public static byte[] View(this byte[] buffer, int offset, int size) { From d339f8cbeacd4cc22a04a68c14277931b1cd8681 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 05:59:07 -0500 Subject: [PATCH 20/78] [-] Remove redundant logs --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 8d5af8c..c219dad 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -151,7 +151,6 @@ private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) [HarmonyPatch(typeof(NFSocket), "Send")] private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, ref int __result) { - Log.Debug("NFSend"); __result = redirect[__instance].Send(buffer, offset, size, socketFlags); return BLOCK_ORIGINAL; } @@ -160,7 +159,6 @@ private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int s [HarmonyPatch(typeof(NFSocket), "SendTo")] private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP, ref int __result) { - Log.Debug("NFSendTo"); __result = redirect[__instance].SendTo(buffer, offset, size, socketFlags, remoteEP); return BLOCK_ORIGINAL; } @@ -169,7 +167,6 @@ private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int [HarmonyPatch(typeof(NFSocket), "Receive")] private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode, ref int __result) { - Log.Debug("NFReceive"); __result = redirect[__instance].Receive(buffer, offset, size, socketFlags, out errorCode); return BLOCK_ORIGINAL; } @@ -178,7 +175,6 @@ private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, in [HarmonyPatch(typeof(NFSocket), "ReceiveFrom")] private static bool NFReceiveFrom(NFSocket __instance, byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP, ref int __result) { - Log.Debug("NFReceiveFrom"); __result = redirect[__instance].ReceiveFrom(buffer, socketFlags, ref remoteEP); return BLOCK_ORIGINAL; } @@ -226,7 +222,6 @@ private static bool NFConnectAsync(NFSocket __instance, SocketAsyncEventArgs e, [HarmonyPatch(typeof(NFSocket), "SetSocketOption")] private static bool NFSetSocketOption(NFSocket __instance, SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue) { - Log.Debug("NFSetSocketOption"); redirect[__instance].SetSocketOption(optionLevel, optionName, optionValue); return BLOCK_ORIGINAL; } @@ -280,7 +275,7 @@ private static bool PacketEncrypt(Packet __instance, PacketType ____encrypt, Pac packet_writeunit.Invoke(null, [____plane, 0, (uint)____plane.Count]); return BLOCK_ORIGINAL; } - + // Disable decryption [HarmonyPrefix] [HarmonyPatch(typeof(Packet), "decrypt")] From 71ec7394b244169945326879cb4c170765de2b92 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:00:36 -0500 Subject: [PATCH 21/78] [F] Fix accept --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 68 ++++++++++++++------------ 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index c219dad..81412db 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -26,6 +26,9 @@ public static class FutariPatch static MethodBase packet_writeunit; static System.Type StartUpStateType; + + #region Init + public static void OnBeforePatch() { Log.Info("Starting WorldsLink patch..."); @@ -34,12 +37,39 @@ public static void OnBeforePatch() [typeof(PacketType), typeof(int), typeof(uint)], null); if (packet_writeunit == null) Log.Error("write_uint not found"); - StartUpStateType = typeof(StartupProcess).GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance).FieldType; + StartUpStateType = typeof(StartupProcess).GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance)!.FieldType; if (StartUpStateType == null) Log.Error("StartUpStateType not found"); client = new FutariClient("A000", "70.49.234.104", 20101); } - + + // Entrypoint + // private void CheckAuth_Proc() + [HarmonyPrefix] + [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] + private static bool CheckAuth_Proc() + { + if (isInit) return RUN_ORIGINAL; + Log.Info("CheckAuth_Proc"); + + var keychip = AMDaemon.System.KeychipId.ShortValue; + Log.Info($"Keychip ID: {keychip}"); + if (string.IsNullOrEmpty(keychip)) + { + Log.Error("Keychip ID is empty. WorldsLink will not work."); + // return; + + // For testing: Create a random keychip (10-digit number) + keychip = "A" + new Random().Next(1000000000, int.MaxValue); + } + client.keychip = keychip; + client.ConnectAsync(); + + isInit = true; + return RUN_ORIGINAL; + } + + #endregion // Patch for logging // SocketBase:: public void sendClass(ICommandParam info) [HarmonyPrefix] @@ -73,7 +103,7 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) __result = new IPAddress(FutariExt.MyStubIP()); return BLOCK_ORIGINAL; } - + // public static uint ToNetworkByteOrderU32(this IPAddress ip) [HarmonyPrefix] [HarmonyPatch(typeof(PartyLink.Util), "ToNetworkByteOrderU32")] @@ -96,37 +126,13 @@ private static void SkipStartupNetworkCheck(ref byte ____state) } } - // private void CheckAuth_Proc() - [HarmonyPrefix] - [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] - private static bool CheckAuth_Proc() - { - if (isInit) return RUN_ORIGINAL; - Log.Info("CheckAuth_Proc"); - - var keychip = AMDaemon.System.KeychipId.ShortValue; - Log.Info($"Keychip ID: {keychip}"); - if (string.IsNullOrEmpty(keychip)) - { - Log.Error("Keychip ID is empty. WorldsLink will not work."); - // return; - - // For testing: Create a random keychip (10-digit number) - keychip = "A" + new Random().Next(1000000000, int.MaxValue); - } - client.keychip = keychip; - client.ConnectAsync(); - - isInit = true; - return RUN_ORIGINAL; - } - #region NFSocket [HarmonyPostfix] [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(AddressFamily), typeof(SocketType), typeof(ProtocolType), typeof(int))] private static void NFCreate(NFSocket __instance, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) { - Log.Debug("new NFSocket(AddressFamily, SocketType, ProtocolType, int)"); + Log.Debug($"new NFSocket({addressFamily}, {socketType}, {protocolType}, {mockID})"); + if (mockID == 3939) return; // Created in redirected NFAccept as a stub var futari = new FutariSocket(addressFamily, socketType, protocolType, mockID); redirect.Add(__instance, futari); } @@ -203,8 +209,8 @@ private static bool NFAccept(NFSocket __instance, ref NFSocket __result) { Log.Debug("NFAccept"); var futariSocket = redirect[__instance].Accept(); - var mockSocket = new NFSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp, 0); - redirect.Add(mockSocket, futariSocket); + var mockSocket = new NFSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp, 3939); + redirect[mockSocket] = futariSocket; __result = mockSocket; return BLOCK_ORIGINAL; } From 8d5cf0d4d7f7a97db0a3e8a10bdb0cf61de581fa Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:01:13 -0500 Subject: [PATCH 22/78] [+] Regions --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 81412db..97d335d 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -4,8 +4,11 @@ using System.Net.Sockets; using System.Reflection; using AquaMai.Config.Attributes; +using DB; using HarmonyLib; using Manager; +using Manager.Party.Party; +using MelonLoader; using PartyLink; using Process; @@ -70,6 +73,9 @@ private static bool CheckAuth_Proc() } #endregion + + #region Logging + // Patch for logging // SocketBase:: public void sendClass(ICommandParam info) [HarmonyPrefix] @@ -83,7 +89,7 @@ private static bool sendClass(SocketBase __instance, ICommandParam info) Log.Debug($"SendClass: {info.GetType().Name} from {__instance.GetType().Name}"); return RUN_ORIGINAL; } - + // Patch for error logging // SocketBase:: protected void error(string message, int no) [HarmonyPrefix] @@ -94,7 +100,6 @@ private static bool error(string message, int no) return RUN_ORIGINAL; } - // Other patches not in NFSocket // public static IPAddress MyIpAddress(int mockID) [HarmonyPrefix] [HarmonyPatch(typeof(PartyLink.Util), "MyIpAddress", typeof(int))] @@ -112,6 +117,8 @@ private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) __result = BitConverter.ToUInt32(ip.GetAddressBytes(), 0); return BLOCK_ORIGINAL; } + + #endregion //Skip StartupNetworkChecker [HarmonyPostfix] From 1935d03c1591c1a159f55bbf82e5205c3d032e76 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:01:25 -0500 Subject: [PATCH 23/78] [F] Fix my ip --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 97d335d..84ae0e5 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -105,7 +105,7 @@ private static bool error(string message, int no) [HarmonyPatch(typeof(PartyLink.Util), "MyIpAddress", typeof(int))] private static bool MyIpAddress(int mockID, ref IPAddress __result) { - __result = new IPAddress(FutariExt.MyStubIP()); + __result = FutariExt.MyStubIP().ToIP(); return BLOCK_ORIGINAL; } From 6b9468dfcfc53ee1bb7ed37663922f315904b256 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:02:05 -0500 Subject: [PATCH 24/78] [F] Fix loopback IP translation --- AquaMai.Mods/WorldsLink/FutariClient.cs | 3 ++- AquaMai.Mods/WorldsLink/FutariSocket.cs | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 21b1bc4..c4dc97d 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -5,6 +5,7 @@ using System.Net.Sockets; using System.Text; using System.Threading; +using PartyLink; namespace AquaMai.Mods.WorldsLink; @@ -36,7 +37,7 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, private bool _reconnecting = false; - public IPAddress StubIP => new IPAddress(FutariExt.KeychipToStubIp(keychip)); + public IPAddress StubIP => FutariExt.KeychipToStubIp(keychip).ToIP(); public void ConnectAsync() => new Thread(Connect) { IsBackground = true }.Start(); diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index d2bf854..7ba9216 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -14,7 +14,7 @@ public class FutariSocket private int _streamId = -1; // ConnectSocket.Enter_Active (doesn't seem to be actually used) - public EndPoint LocalEndPoint { get; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); + public EndPoint LocalEndPoint => new IPEndPoint(_client.StubIP, 0); // ConnectSocket.Enter_Active, ListenSocket.acceptClient (TCP) // Each client's remote endpoint must be different @@ -24,6 +24,7 @@ public FutariSocket(FutariClient client, ProtocolType proto) { _client = client; _proto = proto; + RemoteEndPoint = new IPEndPoint(_client.StubIP, 0); } // Compatibility constructor @@ -70,14 +71,18 @@ public static bool Poll(FutariSocket socket, SelectMode mode) public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) { if (e.RemoteEndPoint is not IPEndPoint ipEndP) return false; + var addr = ipEndP.Address.ToNetworkByteOrderU32(); + // Localhost + if (addr is 2130706433 or 16777343) addr = _client.StubIP.ToNetworkByteOrderU32(); _streamId = new Random().Next(); _client.tcpRecvQ[_streamId] = new ConcurrentQueue(); + Log.Error($"Addr: {addr}"); _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_TCP_CONNECT, proto = _proto, sid = _streamId, - dst = ipEndP.Address.ToNetworkByteOrderU32(), + dst = addr, dPort = ipEndP.Port }); // It is very annoying to call Complete event using reflection @@ -107,8 +112,7 @@ public FutariSocket Accept() return new FutariSocket(_client, _proto) { _streamId = msg.sid.Value, - RemoteEndPoint = new IPEndPoint(new IPAddress(new IpAddress(msg.src.Value).GetAddressBytes()), - _bindPort) + RemoteEndPoint = new IPEndPoint(msg.src.Value.ToIP(), _bindPort) }; } From ea964230a1b8f33f75630c013eba930ca4a11872 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:02:15 -0500 Subject: [PATCH 25/78] [+] More logging --- AquaMai.Mods/WorldsLink/FutariClient.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index c4dc97d..abce942 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -161,23 +161,33 @@ private void RecvThread() private void HandleIncomingMessage(Msg msg) { - Log.Info($"{keychip} <<< {msg}"); + if (msg.cmd != Cmd.CTL_HEARTBEAT) + Log.Info($"{FutariExt.KeychipToStubIp(keychip).ToIP()} <<< {msg.ToReadableString()}"); switch (msg.cmd) { // UDP message case Cmd.DATA_SEND or Cmd.DATA_BROADCAST when msg.proto == ProtocolType.Udp && msg.dPort != null: - udpRecvQ.Get(msg.dPort.Value)?.Enqueue(msg); + udpRecvQ.Get(msg.dPort.Value)?.Also(q => + { + Log.Info($"+ Added to UDP queue, there are {q.Count + 1} messages in queue"); + })?.Enqueue(msg); break; // TCP connection case Cmd.DATA_SEND when msg.proto == ProtocolType.Tcp && msg.sid != null: - tcpRecvQ.Get(msg.sid.Value)?.Enqueue(msg); + tcpRecvQ.Get(msg.sid.Value)?.Also(q => + { + Log.Info($"+ Added to TCP queue, there are {q.Count + 1} messages in queue"); + })?.Enqueue(msg); break; // TCP connection accepted case Cmd.CTL_TCP_CONNECT when msg.dPort != null: - acceptQ.Get(msg.dPort.Value)?.Enqueue(msg); + acceptQ.Get(msg.dPort.Value)?.Also(q => + { + Log.Info($"+ Added to Accept queue, there are {q.Count + 1} messages in queue"); + })?.Enqueue(msg); break; } } @@ -185,6 +195,7 @@ private void HandleIncomingMessage(Msg msg) private void Send(Msg msg) { _writer.WriteLine(msg); - Log.Info($"{keychip} >>> {msg}"); + if (msg.cmd != Cmd.CTL_HEARTBEAT) + Log.Info($"{FutariExt.KeychipToStubIp(keychip).ToIP()} >>> {msg.ToReadableString()}"); } } From b031fed40a585b1b8e11341defe0a5763933f697 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:35:08 -0500 Subject: [PATCH 26/78] [F] Fix SYN ACK callback --- AquaMai.Mods/WorldsLink/FutariClient.cs | 13 ++++++++++--- AquaMai.Mods/WorldsLink/FutariPatch.cs | 6 ++++++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 20 ++++++++++++++++---- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index abce942..5081afb 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -31,6 +31,8 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, public readonly ConcurrentDictionary> udpRecvQ = new(); // public readonly ConcurrentDictionary> acceptQ = new(); + // + public readonly ConcurrentDictionary> acceptCallbacks = new(); private Thread _sendThread; private Thread _recvThread; @@ -174,7 +176,7 @@ private void HandleIncomingMessage(Msg msg) })?.Enqueue(msg); break; - // TCP connection + // TCP message case Cmd.DATA_SEND when msg.proto == ProtocolType.Tcp && msg.sid != null: tcpRecvQ.Get(msg.sid.Value)?.Also(q => { @@ -182,13 +184,18 @@ private void HandleIncomingMessage(Msg msg) })?.Enqueue(msg); break; - // TCP connection accepted + // TCP connection request case Cmd.CTL_TCP_CONNECT when msg.dPort != null: acceptQ.Get(msg.dPort.Value)?.Also(q => { Log.Info($"+ Added to Accept queue, there are {q.Count + 1} messages in queue"); })?.Enqueue(msg); - break; + break; + + // TCP connection accept + case Cmd.CTL_TCP_ACCEPT when msg.sid != null: + acceptCallbacks.Get(msg.sid.Value)?.Invoke(msg); + break; } } diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 84ae0e5..405497c 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -100,6 +100,12 @@ private static bool error(string message, int no) return RUN_ORIGINAL; } + + #endregion + + #region IP + + // Patch my IP address to a stub // public static IPAddress MyIpAddress(int mockID) [HarmonyPrefix] [HarmonyPatch(typeof(PartyLink.Util), "MyIpAddress", typeof(int))] diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index 7ba9216..2d50697 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; +using System.Reflection; using PartyLink; namespace AquaMai.Mods.WorldsLink; @@ -62,6 +63,9 @@ public static bool Poll(FutariSocket socket, SelectMode mode) // Write is always ready? return mode == SelectMode.SelectWrite; } + + private static FieldInfo completedField = typeof(SocketAsyncEventArgs) + .GetField("Completed", BindingFlags.Instance | BindingFlags.NonPublic); // ConnectSocket.Enter_Connect (TCP) // The destination address is obtained from a RecruitInfo packet sent by the host. @@ -76,7 +80,17 @@ public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) if (addr is 2130706433 or 16777343) addr = _client.StubIP.ToNetworkByteOrderU32(); _streamId = new Random().Next(); _client.tcpRecvQ[_streamId] = new ConcurrentQueue(); - Log.Error($"Addr: {addr}"); + _client.acceptCallbacks[_streamId] = msg => + { + Log.Info("ConnectAsync: Accept callback, invoking Completed event"); + var eventDelegate = (MulticastDelegate) completedField.GetValue(e); + if (eventDelegate == null) return; + foreach (var handler in eventDelegate.GetInvocationList()) + { + Log.Info($"ConnectAsync: Invoking {handler.Method.Name}"); + handler.DynamicInvoke(e, new SocketAsyncEventArgs { SocketError = SocketError.Success }); + } + }; _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_TCP_CONNECT, @@ -85,9 +99,7 @@ public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) dst = addr, dPort = ipEndP.Port }); - // It is very annoying to call Complete event using reflection - // So we'll just pretend that the client has ACKed - return false; + return true; } // Accept is blocking From 7211200712bb87496566a1b9e8d4c27096e621d4 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 08:22:00 -0500 Subject: [PATCH 27/78] [F] Fix TCP Multiport --- AquaMai.Mods/WorldsLink/FutariClient.cs | 19 ++++----- AquaMai.Mods/WorldsLink/FutariPatch.cs | 22 +++++------ AquaMai.Mods/WorldsLink/FutariSocket.cs | 52 ++++++++++++++++--------- 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 5081afb..ddbecd0 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -25,13 +25,13 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, private StreamReader _reader; public readonly ConcurrentQueue sendQ = new(); - // + // public readonly ConcurrentDictionary> tcpRecvQ = new(); // public readonly ConcurrentDictionary> udpRecvQ = new(); // public readonly ConcurrentDictionary> acceptQ = new(); - // + // public readonly ConcurrentDictionary> acceptCallbacks = new(); private Thread _sendThread; @@ -39,7 +39,8 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, private bool _reconnecting = false; - public IPAddress StubIP => FutariExt.KeychipToStubIp(keychip).ToIP(); + public uint StubIPU32 => FutariExt.KeychipToStubIp(keychip); + public IPAddress StubIP => StubIPU32.ToIP(); public void ConnectAsync() => new Thread(Connect) { IsBackground = true }.Start(); @@ -169,7 +170,7 @@ private void HandleIncomingMessage(Msg msg) switch (msg.cmd) { // UDP message - case Cmd.DATA_SEND or Cmd.DATA_BROADCAST when msg.proto == ProtocolType.Udp && msg.dPort != null: + case Cmd.DATA_SEND or Cmd.DATA_BROADCAST when msg is { proto: ProtocolType.Udp, dPort: not null }: udpRecvQ.Get(msg.dPort.Value)?.Also(q => { Log.Info($"+ Added to UDP queue, there are {q.Count + 1} messages in queue"); @@ -177,10 +178,10 @@ private void HandleIncomingMessage(Msg msg) break; // TCP message - case Cmd.DATA_SEND when msg.proto == ProtocolType.Tcp && msg.sid != null: - tcpRecvQ.Get(msg.sid.Value)?.Also(q => + case Cmd.DATA_SEND when msg.proto == ProtocolType.Tcp && msg is { sid: not null, dPort: not null }: + tcpRecvQ.Get(msg.sid.Value + msg.dPort.Value)?.Also(q => { - Log.Info($"+ Added to TCP queue, there are {q.Count + 1} messages in queue"); + Log.Info($"+ Added to TCP queue, there are {q.Count + 1} messages in queue for port {msg.dPort}"); })?.Enqueue(msg); break; @@ -193,8 +194,8 @@ private void HandleIncomingMessage(Msg msg) break; // TCP connection accept - case Cmd.CTL_TCP_ACCEPT when msg.sid != null: - acceptCallbacks.Get(msg.sid.Value)?.Invoke(msg); + case Cmd.CTL_TCP_ACCEPT when msg is { sid: not null, dPort: not null }: + acceptCallbacks.Get(msg.sid.Value + msg.dPort.Value)?.Invoke(msg); break; } } diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 405497c..2af25ec 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -127,17 +127,17 @@ private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) #endregion //Skip StartupNetworkChecker - [HarmonyPostfix] - [HarmonyPatch("StartupProcess", nameof(StartupProcess.OnUpdate))] - private static void SkipStartupNetworkCheck(ref byte ____state) - { - //Log.Info("StartupProcess E:"+ Enum.GetName(StartUpStateType,____state)); - if (____state == 0x04/*StartupProcess.StartUpState.WaitLinkDelivery*/) - { - ____state = 0x08;//StartupProcess.StartUpState.Ready - Log.Info("Skip Startup Network Check"); - } - } + // [HarmonyPostfix] + // [HarmonyPatch("StartupProcess", nameof(StartupProcess.OnUpdate))] + // private static void SkipStartupNetworkCheck(ref byte ____state) + // { + // //Log.Info("StartupProcess E:"+ Enum.GetName(StartUpStateType,____state)); + // if (____state == 0x04/*StartupProcess.StartUpState.WaitLinkDelivery*/) + // { + // ____state = 0x08;//StartupProcess.StartUpState.Ready + // Log.Info("Skip Startup Network Check"); + // } + // } #region NFSocket [HarmonyPostfix] diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index 2d50697..c091e68 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -3,6 +3,7 @@ using System.Net; using System.Net.Sockets; using System.Reflection; +using HarmonyLib; using PartyLink; namespace AquaMai.Mods.WorldsLink; @@ -19,7 +20,7 @@ public class FutariSocket // ConnectSocket.Enter_Active, ListenSocket.acceptClient (TCP) // Each client's remote endpoint must be different - public EndPoint RemoteEndPoint { get; private set; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); + public EndPoint RemoteEndPoint { get; private set; } public FutariSocket(FutariClient client, ProtocolType proto) { @@ -58,7 +59,7 @@ public static bool Poll(FutariSocket socket, SelectMode mode) ? !socket._client.udpRecvQ.Get(socket._bindPort)?.IsEmpty ?? false : (socket._streamId == -1) // Check is TCP stream or TCP server ? !socket._client.acceptQ.Get(socket._bindPort)?.IsEmpty ?? false - : !socket._client.tcpRecvQ.Get(socket._streamId)?.IsEmpty ?? false; + : !socket._client.tcpRecvQ.Get(socket._streamId + socket._bindPort)?.IsEmpty ?? false; } // Write is always ready? return mode == SelectMode.SelectWrite; @@ -74,13 +75,18 @@ public static bool Poll(FutariSocket socket, SelectMode mode) // When it's false, e will contain the result of the operation public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) { - if (e.RemoteEndPoint is not IPEndPoint ipEndP) return false; - var addr = ipEndP.Address.ToNetworkByteOrderU32(); - // Localhost - if (addr is 2130706433 or 16777343) addr = _client.StubIP.ToNetworkByteOrderU32(); + if (e.RemoteEndPoint is not IPEndPoint remote) return false; + var addr = remote.Address.ToNetworkByteOrderU32(); + + // Change Localhost to the local keychip address + if (addr is 2130706433 or 16777343) addr = _client.StubIPU32; + + // Random stream ID and port _streamId = new Random().Next(); - _client.tcpRecvQ[_streamId] = new ConcurrentQueue(); - _client.acceptCallbacks[_streamId] = msg => + _bindPort = new Random().Next(49152, 65535); + + _client.tcpRecvQ[_streamId + _bindPort] = new ConcurrentQueue(); + _client.acceptCallbacks[_streamId + _bindPort] = msg => { Log.Info("ConnectAsync: Accept callback, invoking Completed event"); var eventDelegate = (MulticastDelegate) completedField.GetValue(e); @@ -96,9 +102,10 @@ public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) cmd = Cmd.CTL_TCP_CONNECT, proto = _proto, sid = _streamId, - dst = addr, - dPort = ipEndP.Port + src = _client.StubIPU32, sPort = _bindPort, + dst = addr, dPort = remote.Port }); + RemoteEndPoint = new IPEndPoint(addr, remote.Port); return true; } @@ -108,35 +115,40 @@ public FutariSocket Accept() // Check if accept queue has any pending connections if (!_client.acceptQ.TryGetValue(_bindPort, out var q) || !q.TryDequeue(out var msg) || - msg.sid == null || - msg.src == null) + msg.sid == null || msg.src == null || msg.sPort == null) { Log.Warn("Accept: No pending connections"); return null; } - _client.tcpRecvQ[msg.sid.Value] = new ConcurrentQueue(); + _client.tcpRecvQ[msg.sid.Value + _bindPort] = new ConcurrentQueue(); _client.sendQ.Enqueue(new Msg { - cmd = Cmd.CTL_TCP_ACCEPT, proto = _proto, sid = msg.sid, dst = msg.src + cmd = Cmd.CTL_TCP_ACCEPT, proto = _proto, sid = msg.sid, + src = _client.StubIPU32, sPort = _bindPort, + dst = msg.src, dPort = msg.sPort }); return new FutariSocket(_client, _proto) { _streamId = msg.sid.Value, - RemoteEndPoint = new IPEndPoint(msg.src.Value.ToIP(), _bindPort) + _bindPort = _bindPort, + RemoteEndPoint = new IPEndPoint(msg.src.Value.ToIP(), msg.sPort.Value) }; } public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) { + if (RemoteEndPoint is not IPEndPoint remote) throw new InvalidOperationException("RemoteEndPoint is not set"); Log.Debug($"Send: {size} bytes"); // Remote EP is not relevant here, because the stream is already established, // there can only be one remote endpoint _client.sendQ.Enqueue(new Msg { cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size).B64(), - sid = _streamId == -1 ? null : _streamId + sid = _streamId == -1 ? null : _streamId, + src = _client.StubIPU32, sPort = _bindPort, + dst = remote.Address.ToNetworkByteOrderU32(), dPort = remote.Port }); return size; } @@ -145,10 +157,12 @@ public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) { Log.Debug($"SendTo: {size} bytes"); - if (remoteEP is not IPEndPoint ipEndP) return 0; + if (remoteEP is not IPEndPoint remote) return 0; _client.sendQ.Enqueue(new Msg { - cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size).B64(), dPort = ipEndP.Port + cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size).B64(), + src = _client.StubIPU32, sPort = _bindPort, + dst = remote.Address.ToNetworkByteOrderU32(), dPort = remote.Port }); return size; } @@ -157,7 +171,7 @@ public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode) { Log.Debug("Receive called"); - if (!_client.tcpRecvQ.TryGetValue(_streamId, out var q) || + if (!_client.tcpRecvQ.TryGetValue(_streamId + _bindPort, out var q) || !q.TryDequeue(out var msg)) { Log.Warn("Receive: No data to receive"); From 3898fa68a328ecaaddf3a839a624a0fa0c91bef4 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 08:24:50 -0500 Subject: [PATCH 28/78] [O] Block SettingHostAddress --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 2af25ec..f33f9eb 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -82,8 +82,8 @@ private static bool CheckAuth_Proc() [HarmonyPatch(typeof(SocketBase), "sendClass", typeof(ICommandParam))] private static bool sendClass(SocketBase __instance, ICommandParam info) { - // Block AdvocateDelivery - if (info is AdvocateDelivery) return false; + // Block AdvocateDelivery, SettingHostAddress + if (info is AdvocateDelivery or Setting.SettingHostAddress) return BLOCK_ORIGINAL; // For logging only, log the actual type of info and the actual type of this class Log.Debug($"SendClass: {info.GetType().Name} from {__instance.GetType().Name}"); From 2cc8558a0120e5c66ee49f96dc16727977e2af91 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 08:28:49 -0500 Subject: [PATCH 29/78] [F] Fix receiveFrom remoteEP --- AquaMai.Mods/WorldsLink/FutariSocket.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index c091e68..caee108 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -197,6 +197,9 @@ public int ReceiveFrom(byte[] buffer, SocketFlags socketFlags, ref EndPoint remo } var data = msg.data!.B64(); + // Set remote endpoint to the sender + remoteEP = new IPEndPoint(msg.src?.ToIP()!, msg.sPort!.Value); + Buffer.BlockCopy(data, 0, buffer, 0, data.Length); return data.Length; } From 926c02596fc6e4d5a4354673c24fd572d34e053c Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 08:50:05 -0500 Subject: [PATCH 30/78] [F] Fix receivefrom --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 10 ++++++++++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 9 +++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index f33f9eb..682766a 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -99,6 +99,16 @@ private static bool error(string message, int no) Log.Error($"Error: {message} ({no})"); return RUN_ORIGINAL; } + + // Force isSameVersion to return true + // Packet:: public bool isSameVersion() + [HarmonyPostfix] + [HarmonyPatch(typeof(Packet), "isSameVersion")] + private static void isSameVersion(ref bool __result) + { + Log.Debug($"isSameVersion (original): {__result}, forcing true"); + __result = true; + } #endregion diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index caee108..7446213 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -188,18 +188,19 @@ public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, // Only used in UdpRecvSocket to receive from 0 (broadcast) public int ReceiveFrom(byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP) { - Log.Debug("ReceiveFrom called"); if (!_client.udpRecvQ.TryGetValue(_bindPort, out var q) || !q.TryDequeue(out var msg)) { Log.Warn("ReceiveFrom: No data to receive"); return 0; } - var data = msg.data!.B64(); + var data = msg.data?.B64() ?? []; + Log.Debug($"ReceiveFrom: {data.Length} bytes"); // Set remote endpoint to the sender - remoteEP = new IPEndPoint(msg.src?.ToIP()!, msg.sPort!.Value); - + if (msg.src.HasValue) + remoteEP = new IPEndPoint(msg.src.Value.ToIP(), msg.sPort ?? 0); + Buffer.BlockCopy(data, 0, buffer, 0, data.Length); return data.Length; } From f058224b95de98a8ad2a33626ba9d78c60dbe9c5 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:03:52 -0500 Subject: [PATCH 31/78] [F] Fix IP reverting --- AquaMai.Mods/WorldsLink/FutariClient.cs | 3 +-- AquaMai.Mods/WorldsLink/FutariExt.cs | 6 ++--- AquaMai.Mods/WorldsLink/FutariPatch.cs | 12 +-------- AquaMai.Mods/WorldsLink/FutariSocket.cs | 24 ++++++++--------- AquaMai.Mods/WorldsLink/FutariTypes.cs | 34 ++++++++++++------------- 5 files changed, 34 insertions(+), 45 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index ddbecd0..d1fb3b1 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -39,8 +39,7 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, private bool _reconnecting = false; - public uint StubIPU32 => FutariExt.KeychipToStubIp(keychip); - public IPAddress StubIP => StubIPU32.ToIP(); + public IPAddress StubIP => FutariExt.KeychipToStubIp(keychip).ToIP(); public void ConnectAsync() => new Thread(Connect) { IsBackground = true }.Start(); diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index 4101f4e..abdf912 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -3,8 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; -using System.Net.Sockets; -using System.Text; +using PartyLink; namespace AquaMai.Mods.WorldsLink; @@ -15,7 +14,8 @@ public static uint KeychipToStubIp(string keychip) return uint.Parse("1" + keychip.Substring(2)); } - public static IPAddress ToIP(this uint ipAsUint) => new(BitConverter.GetBytes(ipAsUint)); + public static IPAddress ToIP(this uint val) => new(new IpAddress(val).GetAddressBytes()); + public static uint ToU32(this IPAddress ip) => ip.ToNetworkByteOrderU32(); public static R Let(this T x, Func f) => f(x); public static T Also(this T x, Action f) { f(x); return x; } diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 682766a..7e4213e 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -60,7 +60,6 @@ private static bool CheckAuth_Proc() if (string.IsNullOrEmpty(keychip)) { Log.Error("Keychip ID is empty. WorldsLink will not work."); - // return; // For testing: Create a random keychip (10-digit number) keychip = "A" + new Random().Next(1000000000, int.MaxValue); @@ -86,7 +85,7 @@ private static bool sendClass(SocketBase __instance, ICommandParam info) if (info is AdvocateDelivery or Setting.SettingHostAddress) return BLOCK_ORIGINAL; // For logging only, log the actual type of info and the actual type of this class - Log.Debug($"SendClass: {info.GetType().Name} from {__instance.GetType().Name}"); + Log.Debug($"SendClass: {Log.BRIGHT_RED}{info.GetType().Name}{Log.RESET} from {__instance.GetType().Name}"); return RUN_ORIGINAL; } @@ -124,15 +123,6 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) __result = FutariExt.MyStubIP().ToIP(); return BLOCK_ORIGINAL; } - - // public static uint ToNetworkByteOrderU32(this IPAddress ip) - [HarmonyPrefix] - [HarmonyPatch(typeof(PartyLink.Util), "ToNetworkByteOrderU32")] - private static bool ToNetworkByteOrderU32(this IPAddress ip, ref uint __result) - { - __result = BitConverter.ToUInt32(ip.GetAddressBytes(), 0); - return BLOCK_ORIGINAL; - } #endregion diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index 7446213..df35d32 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -22,7 +22,7 @@ public class FutariSocket // Each client's remote endpoint must be different public EndPoint RemoteEndPoint { get; private set; } - public FutariSocket(FutariClient client, ProtocolType proto) + private FutariSocket(FutariClient client, ProtocolType proto) { _client = client; _proto = proto; @@ -43,7 +43,7 @@ public void Bind(EndPoint localEndP) _bindPort = ipEndP.Port; _client.Bind(_bindPort, _proto); _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_BIND, proto = _proto, - src = ipEndP.Address.ToNetworkByteOrderU32(), sPort = ipEndP.Port }); + src = ipEndP.Address.ToU32(), sPort = ipEndP.Port }); } // Only used in BroadcastSocket @@ -76,10 +76,10 @@ public static bool Poll(FutariSocket socket, SelectMode mode) public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) { if (e.RemoteEndPoint is not IPEndPoint remote) return false; - var addr = remote.Address.ToNetworkByteOrderU32(); + var addr = remote.Address.ToU32(); // Change Localhost to the local keychip address - if (addr is 2130706433 or 16777343) addr = _client.StubIPU32; + if (addr is 2130706433 or 16777343) addr = _client.StubIP.ToU32(); // Random stream ID and port _streamId = new Random().Next(); @@ -102,10 +102,10 @@ public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) cmd = Cmd.CTL_TCP_CONNECT, proto = _proto, sid = _streamId, - src = _client.StubIPU32, sPort = _bindPort, + src = _client.StubIP.ToU32(), sPort = _bindPort, dst = addr, dPort = remote.Port }); - RemoteEndPoint = new IPEndPoint(addr, remote.Port); + RemoteEndPoint = new IPEndPoint(addr.ToIP(), remote.Port); return true; } @@ -125,7 +125,7 @@ public FutariSocket Accept() _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_TCP_ACCEPT, proto = _proto, sid = msg.sid, - src = _client.StubIPU32, sPort = _bindPort, + src = _client.StubIP.ToU32(), sPort = _bindPort, dst = msg.src, dPort = msg.sPort }); @@ -147,8 +147,8 @@ public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) { cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size).B64(), sid = _streamId == -1 ? null : _streamId, - src = _client.StubIPU32, sPort = _bindPort, - dst = remote.Address.ToNetworkByteOrderU32(), dPort = remote.Port + src = _client.StubIP.ToU32(), sPort = _bindPort, + dst = remote.Address.ToU32(), dPort = remote.Port }); return size; } @@ -161,8 +161,8 @@ public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, _client.sendQ.Enqueue(new Msg { cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size).B64(), - src = _client.StubIPU32, sPort = _bindPort, - dst = remote.Address.ToNetworkByteOrderU32(), dPort = remote.Port + src = _client.StubIP.ToU32(), sPort = _bindPort, + dst = remote.Address.ToU32(), dPort = remote.Port }); return size; } @@ -170,7 +170,6 @@ public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, // Only used in TCP ConnectSocket public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode) { - Log.Debug("Receive called"); if (!_client.tcpRecvQ.TryGetValue(_streamId + _bindPort, out var q) || !q.TryDequeue(out var msg)) { @@ -179,6 +178,7 @@ public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, return 0; } var data = msg.data!.B64(); + Log.Debug($"Receive: {data.Length} bytes, {q.Count} left in queue"); Buffer.BlockCopy(data, 0, buffer, 0, data.Length); errorCode = SocketError.Success; diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index 904431f..3215cb1 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -92,27 +92,27 @@ public static class Log private static readonly object _lock = new object(); // Text colors - private const string BLACK = "\u001b[30m"; - private const string RED = "\u001b[31m"; - private const string GREEN = "\u001b[32m"; - private const string YELLOW = "\u001b[33m"; - private const string BLUE = "\u001b[34m"; - private const string MAGENTA = "\u001b[35m"; - private const string CYAN = "\u001b[36m"; - private const string WHITE = "\u001b[37m"; + public const string BLACK = "\u001b[30m"; + public const string RED = "\u001b[31m"; + public const string GREEN = "\u001b[32m"; + public const string YELLOW = "\u001b[33m"; + public const string BLUE = "\u001b[34m"; + public const string MAGENTA = "\u001b[35m"; + public const string CYAN = "\u001b[36m"; + public const string WHITE = "\u001b[37m"; // Bright text colors - private const string BRIGHT_BLACK = "\u001b[90m"; - private const string BRIGHT_RED = "\u001b[91m"; - private const string BRIGHT_GREEN = "\u001b[92m"; - private const string BRIGHT_YELLOW = "\u001b[93m"; - private const string BRIGHT_BLUE = "\u001b[94m"; - private const string BRIGHT_MAGENTA = "\u001b[95m"; - private const string BRIGHT_CYAN = "\u001b[96m"; - private const string BRIGHT_WHITE = "\u001b[97m"; + public const string BRIGHT_BLACK = "\u001b[90m"; + public const string BRIGHT_RED = "\u001b[91m"; + public const string BRIGHT_GREEN = "\u001b[92m"; + public const string BRIGHT_YELLOW = "\u001b[93m"; + public const string BRIGHT_BLUE = "\u001b[94m"; + public const string BRIGHT_MAGENTA = "\u001b[95m"; + public const string BRIGHT_CYAN = "\u001b[96m"; + public const string BRIGHT_WHITE = "\u001b[97m"; // Reset - private const string RESET = "\u001b[0m"; + public const string RESET = "\u001b[0m"; public static void Error(string msg) { From 1320afb13d3410635d8aa3bece3e59c3adba6f8a Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:34:37 -0500 Subject: [PATCH 32/78] [M] Reorder files --- AquaMai.Mods/WorldsLink/FutariDebug.cs | 100 +++++++++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariPatch.cs | 95 ++++++++++------------- AquaMai.Mods/WorldsLink/FutariTypes.cs | 6 ++ 3 files changed, 146 insertions(+), 55 deletions(-) create mode 100644 AquaMai.Mods/WorldsLink/FutariDebug.cs diff --git a/AquaMai.Mods/WorldsLink/FutariDebug.cs b/AquaMai.Mods/WorldsLink/FutariDebug.cs new file mode 100644 index 0000000..f7e9b3d --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariDebug.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using AquaMai.Config.Attributes; +using DB; +using HarmonyLib; +using Manager.Party.Party; +using PartyLink; + +namespace AquaMai.Mods.WorldsLink; + +[ConfigSection] +public class FutariDebug +{ + // Log ListenSocket creation + // ListenSocket:: public ListenSocket(string name, int mockID) + [HarmonyPostfix] + [HarmonyPatch(typeof(ListenSocket), MethodType.Constructor, typeof(string), typeof(int))] + private static void ListenSocket(ListenSocket __instance, string name, int mockID) + { + Log.Debug($"new ListenSocket({name}, {mockID})"); + } + + // Log ListenSocket open + // ListenSocket:: public bool open(ushort portNumber) + [HarmonyPrefix] + [HarmonyPatch(typeof(ListenSocket), "open", typeof(ushort))] + private static bool open(ListenSocket __instance, ushort portNumber) + { + Log.Debug($"ListenSocket.open({portNumber}) - {__instance}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log packet type + // Analyzer:: private void procPacketData(Packet packet) + [HarmonyPrefix] + [HarmonyPatch(typeof(Analyzer), "procPacketData", typeof(Packet))] + private static bool procPacketData(Packet packet, Dictionary ____commandMap) + { + var keys = string.Join(", ", ____commandMap.Keys); + Log.Debug($"procPacketData: {Log.BRIGHT_RED}{packet.getCommand()}{Log.RESET} in {keys}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log host creation + // Host:: public Host(string name) + [HarmonyPostfix] + [HarmonyPatch(typeof(Host), MethodType.Constructor, typeof(string))] + private static void Host(Host __instance, string name) + { + Log.Debug($"new Host({name})"); + } + + // Log host state change + // Host:: private void SetCurrentStateID(PartyPartyHostStateID nextState) + [HarmonyPrefix] + [HarmonyPatch(typeof(Host), "SetCurrentStateID", typeof(PartyPartyHostStateID))] + private static bool SetCurrentStateID(PartyPartyHostStateID nextState) + { + Log.Debug($"Host::SetCurrentStateID: {nextState}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log Member creation + // Member:: public Member(string name, Host host, NFSocket socket) + [HarmonyPostfix] + [HarmonyPatch(typeof(Member), MethodType.Constructor, typeof(string), typeof(Host), typeof(NFSocket))] + private static void Member(Member __instance, string name, Host host, NFSocket socket) + { + Log.Debug($"new Member({name}, {host}, {socket})"); + } + + // Log Member state change + // Member:: public void SetCurrentStateID(PartyPartyClientStateID state) + [HarmonyPrefix] + [HarmonyPatch(typeof(Member), "SetCurrentStateID", typeof(PartyPartyClientStateID))] + private static bool SetCurrentStateID(PartyPartyClientStateID state) + { + Log.Debug($"Member::SetCurrentStateID: {state}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log Member RecvRequestJoin + // Member:: private void RecvRequestJoin(Packet packet) + [HarmonyPrefix] + [HarmonyPatch(typeof(Member), "RecvRequestJoin", typeof(Packet))] + private static bool RecvRequestJoin(Packet packet) + { + Log.Debug($"Member::RecvRequestJoin: {packet.getParam()}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log Member RecvClientState + // Member:: private void RecvClientState(Packet packet) + [HarmonyPrefix] + [HarmonyPatch(typeof(Member), "RecvClientState", typeof(Packet))] + private static bool RecvClientState(Packet packet) + { + Log.Debug($"Member::RecvClientState: {packet.getParam()}"); + return PrefixRet.RUN_ORIGINAL; + } +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 7e4213e..9b92fdb 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -4,11 +4,8 @@ using System.Net.Sockets; using System.Reflection; using AquaMai.Config.Attributes; -using DB; using HarmonyLib; using Manager; -using Manager.Party.Party; -using MelonLoader; using PartyLink; using Process; @@ -24,11 +21,8 @@ public static class FutariPatch private static FutariClient client; private static bool isInit = false; - private const bool BLOCK_ORIGINAL = false; - private const bool RUN_ORIGINAL = true; - - static MethodBase packet_writeunit; - static System.Type StartUpStateType; + private static MethodBase packetWriteUInt; + private static System.Type StartUpStateType; #region Init @@ -36,14 +30,15 @@ public static void OnBeforePatch() { Log.Info("Starting WorldsLink patch..."); - packet_writeunit = typeof(Packet).GetMethod("write_uint", BindingFlags.NonPublic | BindingFlags.Static, null, + packetWriteUInt = typeof(Packet).GetMethod("write_uint", BindingFlags.NonPublic | BindingFlags.Static, null, [typeof(PacketType), typeof(int), typeof(uint)], null); - if (packet_writeunit == null) Log.Error("write_uint not found"); + if (packetWriteUInt == null) Log.Error("write_uint not found"); StartUpStateType = typeof(StartupProcess).GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance)!.FieldType; if (StartUpStateType == null) Log.Error("StartUpStateType not found"); - - client = new FutariClient("A000", "70.49.234.104", 20101); + + // TODO: Make IP configurable + client = new FutariClient("A1234567890", "futari.aquadx.net", 20101); } // Entrypoint @@ -52,43 +47,36 @@ public static void OnBeforePatch() [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] private static bool CheckAuth_Proc() { - if (isInit) return RUN_ORIGINAL; + if (isInit) return PrefixRet.RUN_ORIGINAL; Log.Info("CheckAuth_Proc"); var keychip = AMDaemon.System.KeychipId.ShortValue; Log.Info($"Keychip ID: {keychip}"); - if (string.IsNullOrEmpty(keychip)) - { - Log.Error("Keychip ID is empty. WorldsLink will not work."); - - // For testing: Create a random keychip (10-digit number) - keychip = "A" + new Random().Next(1000000000, int.MaxValue); - } + if (string.IsNullOrEmpty(keychip)) Log.Error("Keychip ID is empty. WorldsLink will not work."); client.keychip = keychip; client.ConnectAsync(); isInit = true; - return RUN_ORIGINAL; + return PrefixRet.RUN_ORIGINAL; } #endregion - - #region Logging - // Patch for logging - // SocketBase:: public void sendClass(ICommandParam info) + #region Misc + + // Block irrelevant packets [HarmonyPrefix] [HarmonyPatch(typeof(SocketBase), "sendClass", typeof(ICommandParam))] private static bool sendClass(SocketBase __instance, ICommandParam info) { // Block AdvocateDelivery, SettingHostAddress - if (info is AdvocateDelivery or Setting.SettingHostAddress) return BLOCK_ORIGINAL; + if (info is AdvocateDelivery or Setting.SettingHostAddress) return PrefixRet.BLOCK_ORIGINAL; // For logging only, log the actual type of info and the actual type of this class Log.Debug($"SendClass: {Log.BRIGHT_RED}{info.GetType().Name}{Log.RESET} from {__instance.GetType().Name}"); - return RUN_ORIGINAL; + return PrefixRet.RUN_ORIGINAL; } - + // Patch for error logging // SocketBase:: protected void error(string message, int no) [HarmonyPrefix] @@ -96,7 +84,7 @@ private static bool sendClass(SocketBase __instance, ICommandParam info) private static bool error(string message, int no) { Log.Error($"Error: {message} ({no})"); - return RUN_ORIGINAL; + return PrefixRet.RUN_ORIGINAL; } // Force isSameVersion to return true @@ -109,11 +97,6 @@ private static void isSameVersion(ref bool __result) __result = true; } - - #endregion - - #region IP - // Patch my IP address to a stub // public static IPAddress MyIpAddress(int mockID) [HarmonyPrefix] @@ -121,7 +104,7 @@ private static void isSameVersion(ref bool __result) private static bool MyIpAddress(int mockID, ref IPAddress __result) { __result = FutariExt.MyStubIP().ToIP(); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } #endregion @@ -154,7 +137,7 @@ private static void NFCreate(NFSocket __instance, AddressFamily addressFamily, S [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(Socket))] private static void NFCreate2(NFSocket __instance, Socket nfSocket) { - Log.Warn("new NFSocket(Socket) -- We shouldn't get here."); + Log.Error("new NFSocket(Socket) -- We shouldn't get here."); throw new NotImplementedException(); } @@ -163,7 +146,7 @@ private static void NFCreate2(NFSocket __instance, Socket nfSocket) private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) { __result = FutariSocket.Poll(redirect[socket], mode); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -171,7 +154,7 @@ private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, ref int __result) { __result = redirect[__instance].Send(buffer, offset, size, socketFlags); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -179,7 +162,7 @@ private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int s private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP, ref int __result) { __result = redirect[__instance].SendTo(buffer, offset, size, socketFlags, remoteEP); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -187,7 +170,7 @@ private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode, ref int __result) { __result = redirect[__instance].Receive(buffer, offset, size, socketFlags, out errorCode); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -195,7 +178,7 @@ private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, in private static bool NFReceiveFrom(NFSocket __instance, byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP, ref int __result) { __result = redirect[__instance].ReceiveFrom(buffer, socketFlags, ref remoteEP); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -204,7 +187,7 @@ private static bool NFBind(NFSocket __instance, EndPoint localEndP) { Log.Debug("NFBind"); redirect[__instance].Bind(localEndP); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -213,7 +196,7 @@ private static bool NFListen(NFSocket __instance, int backlog) { Log.Debug("NFListen"); redirect[__instance].Listen(backlog); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -225,7 +208,7 @@ private static bool NFAccept(NFSocket __instance, ref NFSocket __result) var mockSocket = new NFSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp, 3939); redirect[mockSocket] = futariSocket; __result = mockSocket; - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -234,7 +217,7 @@ private static bool NFConnectAsync(NFSocket __instance, SocketAsyncEventArgs e, { Log.Debug("NFConnectAsync"); __result = redirect[__instance].ConnectAsync(e, mockID); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -242,7 +225,7 @@ private static bool NFConnectAsync(NFSocket __instance, SocketAsyncEventArgs e, private static bool NFSetSocketOption(NFSocket __instance, SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue) { redirect[__instance].SetSocketOption(optionLevel, optionName, optionValue); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -251,7 +234,7 @@ private static bool NFClose(NFSocket __instance) { Log.Debug("NFClose"); redirect[__instance].Close(); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -260,7 +243,7 @@ private static bool NFShutdown(NFSocket __instance, SocketShutdown how) { Log.Debug("NFShutdown"); redirect[__instance].Shutdown(how); - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -269,7 +252,7 @@ private static bool NFGetRemoteEndPoint(NFSocket __instance, ref EndPoint __resu { Log.Debug("NFGetRemoteEndPoint"); __result = redirect[__instance].RemoteEndPoint; - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } [HarmonyPrefix] @@ -278,11 +261,12 @@ private static bool NFGetLocalEndPoint(NFSocket __instance, ref EndPoint __resul { Log.Debug("NFGetLocalEndPoint"); __result = redirect[__instance].LocalEndPoint; - return BLOCK_ORIGINAL; + return PrefixRet.BLOCK_ORIGINAL; } #endregion - #region Packet + #region Packet codec + // Disable encryption [HarmonyPrefix] [HarmonyPatch(typeof(Packet), "encrypt")] @@ -291,8 +275,8 @@ private static bool PacketEncrypt(Packet __instance, PacketType ____encrypt, Pac ____encrypt.ClearAndResize(____plane.Count); Array.Copy(____plane.GetBuffer(), 0, ____encrypt.GetBuffer(), 0, ____plane.Count); ____encrypt.ChangeCount(____plane.Count); - packet_writeunit.Invoke(null, [____plane, 0, (uint)____plane.Count]); - return BLOCK_ORIGINAL; + packetWriteUInt.Invoke(null, [____plane, 0, (uint)____plane.Count]); + return PrefixRet.BLOCK_ORIGINAL; } // Disable decryption @@ -303,8 +287,9 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac ____plane.ClearAndResize(____encrypt.Count); Array.Copy(____encrypt.GetBuffer(), 0, ____plane.GetBuffer(), 0, ____encrypt.Count); ____plane.ChangeCount(____encrypt.Count); - packet_writeunit.Invoke(null, [____plane, 0, (uint)____plane.Count]); - return BLOCK_ORIGINAL; + packetWriteUInt.Invoke(null, [____plane, 0, (uint)____plane.Count]); + return PrefixRet.BLOCK_ORIGINAL; } + #endregion } \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index 3215cb1..36fa7db 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -7,6 +7,12 @@ namespace AquaMai.Mods.WorldsLink; +public static class PrefixRet +{ + public const bool BLOCK_ORIGINAL = false; + public const bool RUN_ORIGINAL = true; +} + public enum Cmd { // Control plane From a31f0042070102cd7122b5b97a6b00d372da45c0 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:35:02 -0500 Subject: [PATCH 33/78] [-] Removeu nused --- build.bat | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 build.bat diff --git a/build.bat b/build.bat deleted file mode 100644 index 816f3a2..0000000 --- a/build.bat +++ /dev/null @@ -1,2 +0,0 @@ -dotnet build -copy C:\ws\maimai\AquaMai\Output\AquaMai.dll C:\ws\maimai\145\Mods \ No newline at end of file From 1c1e0dae977997722e716c30cb42dcfab1f6d9a4 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:51:47 -0500 Subject: [PATCH 34/78] [O] Config options --- AquaMai.Mods/WorldsLink/FutariDebug.cs | 100 ------------------------ AquaMai.Mods/WorldsLink/FutariPatch.cs | 104 ++++++++++++++++++++++++- AquaMai.Mods/WorldsLink/FutariTypes.cs | 1 + 3 files changed, 104 insertions(+), 101 deletions(-) delete mode 100644 AquaMai.Mods/WorldsLink/FutariDebug.cs diff --git a/AquaMai.Mods/WorldsLink/FutariDebug.cs b/AquaMai.Mods/WorldsLink/FutariDebug.cs deleted file mode 100644 index f7e9b3d..0000000 --- a/AquaMai.Mods/WorldsLink/FutariDebug.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using DB; -using HarmonyLib; -using Manager.Party.Party; -using PartyLink; - -namespace AquaMai.Mods.WorldsLink; - -[ConfigSection] -public class FutariDebug -{ - // Log ListenSocket creation - // ListenSocket:: public ListenSocket(string name, int mockID) - [HarmonyPostfix] - [HarmonyPatch(typeof(ListenSocket), MethodType.Constructor, typeof(string), typeof(int))] - private static void ListenSocket(ListenSocket __instance, string name, int mockID) - { - Log.Debug($"new ListenSocket({name}, {mockID})"); - } - - // Log ListenSocket open - // ListenSocket:: public bool open(ushort portNumber) - [HarmonyPrefix] - [HarmonyPatch(typeof(ListenSocket), "open", typeof(ushort))] - private static bool open(ListenSocket __instance, ushort portNumber) - { - Log.Debug($"ListenSocket.open({portNumber}) - {__instance}"); - return PrefixRet.RUN_ORIGINAL; - } - - // Log packet type - // Analyzer:: private void procPacketData(Packet packet) - [HarmonyPrefix] - [HarmonyPatch(typeof(Analyzer), "procPacketData", typeof(Packet))] - private static bool procPacketData(Packet packet, Dictionary ____commandMap) - { - var keys = string.Join(", ", ____commandMap.Keys); - Log.Debug($"procPacketData: {Log.BRIGHT_RED}{packet.getCommand()}{Log.RESET} in {keys}"); - return PrefixRet.RUN_ORIGINAL; - } - - // Log host creation - // Host:: public Host(string name) - [HarmonyPostfix] - [HarmonyPatch(typeof(Host), MethodType.Constructor, typeof(string))] - private static void Host(Host __instance, string name) - { - Log.Debug($"new Host({name})"); - } - - // Log host state change - // Host:: private void SetCurrentStateID(PartyPartyHostStateID nextState) - [HarmonyPrefix] - [HarmonyPatch(typeof(Host), "SetCurrentStateID", typeof(PartyPartyHostStateID))] - private static bool SetCurrentStateID(PartyPartyHostStateID nextState) - { - Log.Debug($"Host::SetCurrentStateID: {nextState}"); - return PrefixRet.RUN_ORIGINAL; - } - - // Log Member creation - // Member:: public Member(string name, Host host, NFSocket socket) - [HarmonyPostfix] - [HarmonyPatch(typeof(Member), MethodType.Constructor, typeof(string), typeof(Host), typeof(NFSocket))] - private static void Member(Member __instance, string name, Host host, NFSocket socket) - { - Log.Debug($"new Member({name}, {host}, {socket})"); - } - - // Log Member state change - // Member:: public void SetCurrentStateID(PartyPartyClientStateID state) - [HarmonyPrefix] - [HarmonyPatch(typeof(Member), "SetCurrentStateID", typeof(PartyPartyClientStateID))] - private static bool SetCurrentStateID(PartyPartyClientStateID state) - { - Log.Debug($"Member::SetCurrentStateID: {state}"); - return PrefixRet.RUN_ORIGINAL; - } - - // Log Member RecvRequestJoin - // Member:: private void RecvRequestJoin(Packet packet) - [HarmonyPrefix] - [HarmonyPatch(typeof(Member), "RecvRequestJoin", typeof(Packet))] - private static bool RecvRequestJoin(Packet packet) - { - Log.Debug($"Member::RecvRequestJoin: {packet.getParam()}"); - return PrefixRet.RUN_ORIGINAL; - } - - // Log Member RecvClientState - // Member:: private void RecvClientState(Packet packet) - [HarmonyPrefix] - [HarmonyPatch(typeof(Member), "RecvClientState", typeof(Packet))] - private static bool RecvClientState(Packet packet) - { - Log.Debug($"Member::RecvClientState: {packet.getParam()}"); - return PrefixRet.RUN_ORIGINAL; - } -} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 9b92fdb..f756e91 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -4,10 +4,13 @@ using System.Net.Sockets; using System.Reflection; using AquaMai.Config.Attributes; +using AquaMai.Core.Attributes; using HarmonyLib; using Manager; using PartyLink; using Process; +using DB; +using Manager.Party.Party; namespace AquaMai.Mods.WorldsLink; @@ -15,7 +18,7 @@ namespace AquaMai.Mods.WorldsLink; en: "Enable WorldsLink Multiplayer", zh: "启用 WorldsLink 多人游戏", defaultOn: true)] -public static class FutariPatch +public static class Futari { private static readonly Dictionary redirect = new(); private static FutariClient client; @@ -24,6 +27,9 @@ public static class FutariPatch private static MethodBase packetWriteUInt; private static System.Type StartUpStateType; + [ConfigEntry(hideWhenDefault: true)] + public static bool Debug = false; + #region Init public static void OnBeforePatch() @@ -292,4 +298,100 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac } #endregion + + #region Debug + + [EnableIf(nameof(Debug))] + public class FutariDebug + { + // Log ListenSocket creation + // ListenSocket:: public ListenSocket(string name, int mockID) + [HarmonyPostfix] + [HarmonyPatch(typeof(ListenSocket), MethodType.Constructor, typeof(string), typeof(int))] + private static void ListenSocket(ListenSocket __instance, string name, int mockID) + { + Log.Debug($"new ListenSocket({name}, {mockID})"); + } + + // Log ListenSocket open + // ListenSocket:: public bool open(ushort portNumber) + [HarmonyPrefix] + [HarmonyPatch(typeof(ListenSocket), "open", typeof(ushort))] + private static bool open(ListenSocket __instance, ushort portNumber) + { + Log.Debug($"ListenSocket.open({portNumber}) - {__instance}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log packet type + // Analyzer:: private void procPacketData(Packet packet) + [HarmonyPrefix] + [HarmonyPatch(typeof(Analyzer), "procPacketData", typeof(Packet))] + private static bool procPacketData(Packet packet, Dictionary ____commandMap) + { + var keys = string.Join(", ", ____commandMap.Keys); + Log.Debug($"procPacketData: {Log.BRIGHT_RED}{packet.getCommand()}{Log.RESET} in {keys}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log host creation + // Host:: public Host(string name) + [HarmonyPostfix] + [HarmonyPatch(typeof(Host), MethodType.Constructor, typeof(string))] + private static void Host(Host __instance, string name) + { + Log.Debug($"new Host({name})"); + } + + // Log host state change + // Host:: private void SetCurrentStateID(PartyPartyHostStateID nextState) + [HarmonyPrefix] + [HarmonyPatch(typeof(Host), "SetCurrentStateID", typeof(PartyPartyHostStateID))] + private static bool SetCurrentStateID(PartyPartyHostStateID nextState) + { + Log.Debug($"Host::SetCurrentStateID: {nextState}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log Member creation + // Member:: public Member(string name, Host host, NFSocket socket) + [HarmonyPostfix] + [HarmonyPatch(typeof(Member), MethodType.Constructor, typeof(string), typeof(Host), typeof(NFSocket))] + private static void Member(Member __instance, string name, Host host, NFSocket socket) + { + Log.Debug($"new Member({name}, {host}, {socket})"); + } + + // Log Member state change + // Member:: public void SetCurrentStateID(PartyPartyClientStateID state) + [HarmonyPrefix] + [HarmonyPatch(typeof(Member), "SetCurrentStateID", typeof(PartyPartyClientStateID))] + private static bool SetCurrentStateID(PartyPartyClientStateID state) + { + Log.Debug($"Member::SetCurrentStateID: {state}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log Member RecvRequestJoin + // Member:: private void RecvRequestJoin(Packet packet) + [HarmonyPrefix] + [HarmonyPatch(typeof(Member), "RecvRequestJoin", typeof(Packet))] + private static bool RecvRequestJoin(Packet packet) + { + Log.Debug($"Member::RecvRequestJoin: {packet.getParam()}"); + return PrefixRet.RUN_ORIGINAL; + } + + // Log Member RecvClientState + // Member:: private void RecvClientState(Packet packet) + [HarmonyPrefix] + [HarmonyPatch(typeof(Member), "RecvClientState", typeof(Packet))] + private static bool RecvClientState(Packet packet) + { + Log.Debug($"Member::RecvClientState: {packet.getParam()}"); + return PrefixRet.RUN_ORIGINAL; + } + } + + #endregion } \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index 36fa7db..8814648 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -138,6 +138,7 @@ public static void Warn(string msg) public static void Debug(string msg) { + if (!Futari.Debug) return; lock (_lock) { MelonLogger.Msg($"[FUTARI] {CYAN}DEBUG {RESET}{msg}{RESET}"); From b662e343f844b9434201c5bd80799d8b4e7e1b6f Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:04:29 -0500 Subject: [PATCH 35/78] [F] Fix debug flag --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index f756e91..4339c42 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -301,7 +301,7 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac #region Debug - [EnableIf(nameof(Debug))] + [EnableIf(typeof(Futari), nameof(Debug))] public class FutariDebug { // Log ListenSocket creation From 629a14ad5d4befcab6e5bdfb0bd568b141ea647d Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:33:47 -0500 Subject: [PATCH 36/78] [+] Delay --- AquaMai.Mods/WorldsLink/FutariClient.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index d1fb3b1..27118c4 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Concurrent; +using System.Diagnostics; using System.IO; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; @@ -38,6 +40,11 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, private Thread _recvThread; private bool _reconnecting = false; + + private readonly Stopwatch _heartbeat = new Stopwatch().Also(it => it.Start()); + private readonly long[] _delayWindow = new long[20]; + private int _delayIndex = 0; + public long _delayAvg = 0; public IPAddress StubIP => FutariExt.KeychipToStubIp(keychip).ToIP(); @@ -109,14 +116,12 @@ private void SendThread() { try { - long lastHeartbeat = 0; while (true) { - var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - if (time - lastHeartbeat > 1000) + if (_heartbeat.ElapsedMilliseconds > 1000) { + _heartbeat.Restart(); Send(new Msg { cmd = Cmd.CTL_HEARTBEAT }); - lastHeartbeat = time; } // Send any data in the send queue @@ -168,6 +173,15 @@ private void HandleIncomingMessage(Msg msg) switch (msg.cmd) { + // Heartbeat + case Cmd.CTL_HEARTBEAT: + var delay = _heartbeat.ElapsedMilliseconds; + _delayWindow[_delayIndex] = delay; + _delayIndex = (_delayIndex + 1) % _delayWindow.Length; + _delayAvg = (long) _delayWindow.Average(); + Log.Info($"Heartbeat: {delay}ms, Avg: {_delayAvg}ms"); + break; + // UDP message case Cmd.DATA_SEND or Cmd.DATA_BROADCAST when msg is { proto: ProtocolType.Udp, dPort: not null }: udpRecvQ.Get(msg.dPort.Value)?.Also(q => From 56c72aea11ded45c554e3d97d86fb0e7bae1ea70 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:43:31 -0500 Subject: [PATCH 37/78] [F] Fix delay calculation --- AquaMai.Mods/WorldsLink/FutariClient.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 27118c4..811f50a 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -7,7 +7,6 @@ using System.Net.Sockets; using System.Text; using System.Threading; -using PartyLink; namespace AquaMai.Mods.WorldsLink; @@ -42,7 +41,7 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, private bool _reconnecting = false; private readonly Stopwatch _heartbeat = new Stopwatch().Also(it => it.Start()); - private readonly long[] _delayWindow = new long[20]; + private readonly long[] _delayWindow = new int[20].Select(_ => -1L).ToArray(); private int _delayIndex = 0; public long _delayAvg = 0; @@ -178,7 +177,7 @@ private void HandleIncomingMessage(Msg msg) var delay = _heartbeat.ElapsedMilliseconds; _delayWindow[_delayIndex] = delay; _delayIndex = (_delayIndex + 1) % _delayWindow.Length; - _delayAvg = (long) _delayWindow.Average(); + _delayAvg = (long) _delayWindow.Where(x => x != -1).Average(); Log.Info($"Heartbeat: {delay}ms, Avg: {_delayAvg}ms"); break; From 4b7f6d0be11290f3ad87cb5996df3498867e3a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:47:25 +0900 Subject: [PATCH 38/78] [+] Start Process Info [F] Fix Skip Origin --- AquaMai.Mods/WorldsLink/FutariClient.cs | 24 +++++++++- AquaMai.Mods/WorldsLink/FutariPatch.cs | 62 +++++++++++++++++++------ 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 27118c4..9caf19e 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -7,7 +7,9 @@ using System.Net.Sockets; using System.Text; using System.Threading; +using AMDaemon; using PartyLink; +using static Manager.Accounting; namespace AquaMai.Mods.WorldsLink; @@ -46,20 +48,38 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, private int _delayIndex = 0; public long _delayAvg = 0; - public IPAddress StubIP => FutariExt.KeychipToStubIp(keychip).ToIP(); - + public IPAddress StubIP => FutariExt.KeychipToStubIp(keychip).ToIP(); + + public int StatusCode { get => statusCode; } + public string ErrorMsg { get => errorMsg; } + public string Host { get => host; } + public int Port { get => port; } + public void ConnectAsync() => new Thread(Connect) { IsBackground = true }.Start(); + /// + /// -1:Failed to connect + /// 0: Not connect + /// 1: Connecting + /// 2: Connected + /// + int statusCode = 0; + string errorMsg = ""; + public void Connect() { _tcpClient = new TcpClient(); try { + statusCode = 1; _tcpClient.Connect(host, port); + statusCode = 2; } catch (Exception ex) { + statusCode = -1; + errorMsg = ex.Message; Log.Error($"Error connecting to server:\nHost:{host}:{port}\n{ex.Message}"); return; } diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 4339c42..b205b8f 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -4,13 +4,13 @@ using System.Net.Sockets; using System.Reflection; using AquaMai.Config.Attributes; -using AquaMai.Core.Attributes; +using DB; using HarmonyLib; using Manager; using PartyLink; using Process; -using DB; using Manager.Party.Party; +using AquaMai.Core.Attributes; namespace AquaMai.Mods.WorldsLink; @@ -112,21 +112,55 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) __result = FutariExt.MyStubIP().ToIP(); return PrefixRet.BLOCK_ORIGINAL; } - + #endregion //Skip StartupNetworkChecker - // [HarmonyPostfix] - // [HarmonyPatch("StartupProcess", nameof(StartupProcess.OnUpdate))] - // private static void SkipStartupNetworkCheck(ref byte ____state) - // { - // //Log.Info("StartupProcess E:"+ Enum.GetName(StartUpStateType,____state)); - // if (____state == 0x04/*StartupProcess.StartUpState.WaitLinkDelivery*/) - // { - // ____state = 0x08;//StartupProcess.StartUpState.Ready - // Log.Info("Skip Startup Network Check"); - // } - // } + [HarmonyPostfix] + [HarmonyPatch("StartupProcess", nameof(StartupProcess.OnUpdate))] + private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____statusMsg , string[] ____statusSubMsg) + { + //Title + ____statusMsg[7] = "WORLD LINK"; + switch (client.StatusCode) + { + case -1: + ____statusSubMsg[7] = "BAD"; + break; + case 0: + ____statusSubMsg[7] = "Not Connect"; + break; + case 1: + ____statusSubMsg[7] = "Connecting"; + break; + case 2: + ____statusSubMsg[7] = "GOOD"; + break; + + default: + ____statusSubMsg[7] = "Waiting..."; + break; + } + //Ping + ____statusMsg[8] = "PING"; + ____statusSubMsg[8]= client._delayAvg==0?"N/A":client._delayAvg.ToString()+"ms"; + // + ____statusMsg[9] = ""; + ____statusSubMsg[9] = ""; + //Skip Oragin Init And Manual Init Party + if (____state == 0x04/*StartupProcess.StartUpState.WaitLinkDelivery*/) + { + ____state = 0x08;//StartupProcess.StartUpState.Ready + DeliveryChecker.get().start(true); + Setting.Data data = new Setting.Data(); + data.set(false,4); + Setting.get().setData(data); + Setting.get().setRetryEnable(true); + Advertise.get().initialize(MachineGroupID.ON); + Manager.Party.Party.Party.Get().Start(MachineGroupID.ON); + Log.Info("Skip Startup Network Check"); + } + } #region NFSocket [HarmonyPostfix] From edac6d4f7e9435cda49ce1dc2437822c18e7a09e Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:00:13 -0500 Subject: [PATCH 39/78] [O] Retry --- AquaMai.Mods/WorldsLink/FutariClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 6b85c56..0b862d8 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -58,7 +58,7 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, public void ConnectAsync() => new Thread(Connect) { IsBackground = true }.Start(); /// - /// -1:Failed to connect + /// -1: Failed to connect /// 0: Not connect /// 1: Connecting /// 2: Connected @@ -81,6 +81,7 @@ public void Connect() statusCode = -1; errorMsg = ex.Message; Log.Error($"Error connecting to server:\nHost:{host}:{port}\n{ex.Message}"); + ConnectAsync(); return; } var networkStream = _tcpClient.GetStream(); From 79998f9a8467531676b2514b1493c0daaf1535ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Thu, 16 Jan 2025 06:06:33 +0900 Subject: [PATCH 40/78] [+]Startup Check Text && Mulit Connect Room --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 241 ++++++++++++++++++++----- 1 file changed, 192 insertions(+), 49 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index b205b8f..bccf6d3 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -11,6 +11,10 @@ using Process; using Manager.Party.Party; using AquaMai.Core.Attributes; +using MAI2.Util; +using Manager.MaiStudio; +using Mai2.Mai2Cue; +using static Process.MusicSelectProcess; namespace AquaMai.Mods.WorldsLink; @@ -28,10 +32,10 @@ public static class Futari private static System.Type StartUpStateType; [ConfigEntry(hideWhenDefault: true)] - public static bool Debug = false; - + public static bool Debug = false; + #region Init - + public static void OnBeforePatch() { Log.Info("Starting WorldsLink patch..."); @@ -41,9 +45,9 @@ public static void OnBeforePatch() if (packetWriteUInt == null) Log.Error("write_uint not found"); StartUpStateType = typeof(StartupProcess).GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance)!.FieldType; - if (StartUpStateType == null) Log.Error("StartUpStateType not found"); - - // TODO: Make IP configurable + if (StartUpStateType == null) Log.Error("StartUpStateType not found"); + + // TODO: Make IP configurable client = new FutariClient("A1234567890", "futari.aquadx.net", 20101); } @@ -64,21 +68,21 @@ private static bool CheckAuth_Proc() isInit = true; return PrefixRet.RUN_ORIGINAL; - } - + } + #endregion - + #region Misc - - // Block irrelevant packets + + // Block irrelevant packets [HarmonyPrefix] [HarmonyPatch(typeof(SocketBase), "sendClass", typeof(ICommandParam))] private static bool sendClass(SocketBase __instance, ICommandParam info) { // Block AdvocateDelivery, SettingHostAddress - if (info is AdvocateDelivery or Setting.SettingHostAddress) return PrefixRet.BLOCK_ORIGINAL; - - // For logging only, log the actual type of info and the actual type of this class + if (info is AdvocateDelivery or Setting.SettingHostAddress) return PrefixRet.BLOCK_ORIGINAL; + + // For logging only, log the actual type of info and the actual type of this class Log.Debug($"SendClass: {Log.BRIGHT_RED}{info.GetType().Name}{Log.RESET} from {__instance.GetType().Name}"); return PrefixRet.RUN_ORIGINAL; } @@ -101,10 +105,10 @@ private static void isSameVersion(ref bool __result) { Log.Debug($"isSameVersion (original): {__result}, forcing true"); __result = true; - } - - // Patch my IP address to a stub - // public static IPAddress MyIpAddress(int mockID) + } + + // Patch my IP address to a stub + // public static IPAddress MyIpAddress(int mockID) [HarmonyPrefix] [HarmonyPatch(typeof(PartyLink.Util), "MyIpAddress", typeof(int))] private static bool MyIpAddress(int mockID, ref IPAddress __result) @@ -118,7 +122,7 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) //Skip StartupNetworkChecker [HarmonyPostfix] [HarmonyPatch("StartupProcess", nameof(StartupProcess.OnUpdate))] - private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____statusMsg , string[] ____statusSubMsg) + private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____statusMsg, string[] ____statusSubMsg) { //Title ____statusMsg[7] = "WORLD LINK"; @@ -143,7 +147,7 @@ private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____sta } //Ping ____statusMsg[8] = "PING"; - ____statusSubMsg[8]= client._delayAvg==0?"N/A":client._delayAvg.ToString()+"ms"; + ____statusSubMsg[8] = client._delayAvg == 0 ? "N/A" : client._delayAvg.ToString() + "ms"; // ____statusMsg[9] = ""; ____statusSubMsg[9] = ""; @@ -153,7 +157,7 @@ private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____sta ____state = 0x08;//StartupProcess.StartUpState.Ready DeliveryChecker.get().start(true); Setting.Data data = new Setting.Data(); - data.set(false,4); + data.set(false, 4); Setting.get().setData(data); Setting.get().setRetryEnable(true); Advertise.get().initialize(MachineGroupID.ON); @@ -329,12 +333,151 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac ____plane.ChangeCount(____encrypt.Count); packetWriteUInt.Invoke(null, [____plane, 0, (uint)____plane.Count]); return PrefixRet.BLOCK_ORIGINAL; + } + + #endregion + #region Recruit + static int musicIDSUM; + [HarmonyPrefix] + [HarmonyPatch(typeof(MusicSelectProcess), "OnStart")] + private static bool MusicSelectProcessOnStart(MusicSelectProcess __instance) + { + //初始化变量 + musicIDSUM = 0; + return PrefixRet.RUN_ORIGINAL; + } + [HarmonyPrefix] + [HarmonyPatch(typeof(MusicSelectProcess), "PartyExec")] + private static bool PartyExec(MusicSelectProcess __instance) + { + //检查联机房间是否有更新.如果更新的话设置IsConnectingMusic=false然后刷新列表 + int checkDiff = 0; + foreach (var item in Manager.Party.Party.Party.Get().GetRecruitListWithoutMe()) + { + checkDiff += item.MusicID; + } + if (musicIDSUM != checkDiff) + { + musicIDSUM = checkDiff; + __instance.IsConnectingMusic = false; + } + return PrefixRet.RUN_ORIGINAL; + } + [HarmonyPostfix] + [HarmonyPatch(typeof(MusicSelectProcess), "RecruitData", MethodType.Getter)] + private static void RecruitDataOverride(MusicSelectProcess __instance, ref RecruitInfo __result) + { + //开歌时设置当前选择的联机数据 + if (__result != null) + { + var list = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe(); + if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) + __result = list[__instance.CurrentMusicSelect]; + } + } + [HarmonyPrefix] + [HarmonyPatch(typeof(MusicSelectProcess), "IsConnectStart")] + private static bool RecruitDataOverride(MusicSelectProcess __instance, + List ____connectCombineMusicDataList, + MusicSelectProcess.SubSequence[] ____currentPlayerSubSequence, + ref bool __result) + { + //修正SetConnectData触发条件.阻止原有IP判断重新设置 + if (!__instance.IsConnectingMusic) + { + typeof(MusicSelectProcess).GetProperty("RecruitData").SetMethod.Invoke(__instance,new object[] { new RecruitInfo()}); + SetConnectData(__instance, ____connectCombineMusicDataList, ____currentPlayerSubSequence); + __result = true; + } + __result = false; + return PrefixRet.BLOCK_ORIGINAL; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(MusicSelectProcess), "SetConnectData")] + private static bool SetConnectData( + MusicSelectProcess __instance, + List ____connectCombineMusicDataList, + MusicSelectProcess.SubSequence[] ____currentPlayerSubSequence + ) + { + System.Type type = __instance.GetType(); + + ____connectCombineMusicDataList.Clear(); + type.GetProperty(nameof(__instance.IsConnectCategoryEnable)).SetMethod.Invoke(__instance, new object[] { false }); + //遍历所有房间并且显示 + foreach (var item in Manager.Party.Party.Party.Get().GetRecruitListWithoutMe()) + { + int musicID = item.MusicID; + MusicSelectProcess.CombineMusicSelectData combineMusicSelectData = new MusicSelectProcess.CombineMusicSelectData(); + MusicData music = Singleton.Instance.GetMusic(musicID); + List notesList = Singleton.Instance.GetNotesList()[musicID].NotesList; + + if (musicID < 10000) + { + combineMusicSelectData.existStandardScore = true; + } + else if (10000 < musicID && musicID < 20000) + { + combineMusicSelectData.existDeluxeScore = true; + } + for (int i = 0; i < 2; i++) + { + combineMusicSelectData.musicSelectData.Add(new MusicSelectProcess.MusicSelectData(music, notesList, 0)); + } + ____connectCombineMusicDataList.Add(combineMusicSelectData); + try + { + string thumbnailName = music.thumbnailName; + for (int j = 0; j < __instance.MonitorArray.Length; j++) + { + if (__instance.IsEntry(j)) + { + __instance.MonitorArray[j].SetRecruitInfo(thumbnailName); + SoundManager.PlaySE(Cue.SE_INFO_NORMAL, j); + } + } + } + catch (Exception e) + { + //防止有可能的空 + } + __instance.IsConnectingMusic = true; + } + if (Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Count == 0) + { + MusicSelectProcess.CombineMusicSelectData combineMusicSelectData2 = new MusicSelectProcess.CombineMusicSelectData(); + for (int k = 0; k < 2; k++) + { + combineMusicSelectData2.musicSelectData.Add(null); + } + combineMusicSelectData2.isWaitConnectScore = true; + ____connectCombineMusicDataList.Add(combineMusicSelectData2); + __instance.IsConnectingMusic = false; + } + if (__instance.MonitorArray != null) + { + for (int l = 0; l < __instance.MonitorArray.Length; l++) + { + if (____currentPlayerSubSequence[l] == MusicSelectProcess.SubSequence.Music) + { + __instance.MonitorArray[l].SetDeployList(false, false); + if (__instance.IsConnectionFolder(0)) + { + __instance.ChangeBGM(); + if (__instance.IsEntry(l)) + { + __instance.MonitorArray[l].SetVisibleButton(__instance.IsConnectingMusic, InputManager.ButtonSetting.Button04); + } + } + } + } + } + return PrefixRet.BLOCK_ORIGINAL; } - #endregion - #region Debug - + [EnableIf(typeof(Futari), nameof(Debug))] public class FutariDebug { @@ -345,10 +488,10 @@ public class FutariDebug private static void ListenSocket(ListenSocket __instance, string name, int mockID) { Log.Debug($"new ListenSocket({name}, {mockID})"); - } - - // Log ListenSocket open - // ListenSocket:: public bool open(ushort portNumber) + } + + // Log ListenSocket open + // ListenSocket:: public bool open(ushort portNumber) [HarmonyPrefix] [HarmonyPatch(typeof(ListenSocket), "open", typeof(ushort))] private static bool open(ListenSocket __instance, ushort portNumber) @@ -371,53 +514,53 @@ private static bool procPacketData(Packet packet, Dictionary __ // Log host creation // Host:: public Host(string name) [HarmonyPostfix] - [HarmonyPatch(typeof(Host), MethodType.Constructor, typeof(string))] + [HarmonyPatch(typeof(Comio.Host), MethodType.Constructor, typeof(string))] private static void Host(Host __instance, string name) { Log.Debug($"new Host({name})"); - } - - // Log host state change - // Host:: private void SetCurrentStateID(PartyPartyHostStateID nextState) + } + + // Log host state change + // Host:: private void SetCurrentStateID(PartyPartyHostStateID nextState) [HarmonyPrefix] [HarmonyPatch(typeof(Host), "SetCurrentStateID", typeof(PartyPartyHostStateID))] private static bool SetCurrentStateID(PartyPartyHostStateID nextState) { Log.Debug($"Host::SetCurrentStateID: {nextState}"); return PrefixRet.RUN_ORIGINAL; - } - - // Log Member creation - // Member:: public Member(string name, Host host, NFSocket socket) + } + + // Log Member creation + // Member:: public Member(string name, Host host, NFSocket socket) [HarmonyPostfix] [HarmonyPatch(typeof(Member), MethodType.Constructor, typeof(string), typeof(Host), typeof(NFSocket))] private static void Member(Member __instance, string name, Host host, NFSocket socket) { Log.Debug($"new Member({name}, {host}, {socket})"); - } - - // Log Member state change - // Member:: public void SetCurrentStateID(PartyPartyClientStateID state) + } + + // Log Member state change + // Member:: public void SetCurrentStateID(PartyPartyClientStateID state) [HarmonyPrefix] [HarmonyPatch(typeof(Member), "SetCurrentStateID", typeof(PartyPartyClientStateID))] private static bool SetCurrentStateID(PartyPartyClientStateID state) { Log.Debug($"Member::SetCurrentStateID: {state}"); return PrefixRet.RUN_ORIGINAL; - } - - // Log Member RecvRequestJoin - // Member:: private void RecvRequestJoin(Packet packet) + } + + // Log Member RecvRequestJoin + // Member:: private void RecvRequestJoin(Packet packet) [HarmonyPrefix] [HarmonyPatch(typeof(Member), "RecvRequestJoin", typeof(Packet))] private static bool RecvRequestJoin(Packet packet) { Log.Debug($"Member::RecvRequestJoin: {packet.getParam()}"); return PrefixRet.RUN_ORIGINAL; - } - - // Log Member RecvClientState - // Member:: private void RecvClientState(Packet packet) + } + + // Log Member RecvClientState + // Member:: private void RecvClientState(Packet packet) [HarmonyPrefix] [HarmonyPatch(typeof(Member), "RecvClientState", typeof(Packet))] private static bool RecvClientState(Packet packet) From 10d7bcb3bdc10e84e0b44fadb5afb0090a7b9130 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:50:12 -0500 Subject: [PATCH 41/78] [O] Meow :3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 392 ++++++++++++------------- 1 file changed, 188 insertions(+), 204 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index bccf6d3..5731e18 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; @@ -32,10 +33,10 @@ public static class Futari private static System.Type StartUpStateType; [ConfigEntry(hideWhenDefault: true)] - public static bool Debug = false; - + public static bool Debug = false; + #region Init - + public static void OnBeforePatch() { Log.Info("Starting WorldsLink patch..."); @@ -45,9 +46,9 @@ public static void OnBeforePatch() if (packetWriteUInt == null) Log.Error("write_uint not found"); StartUpStateType = typeof(StartupProcess).GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance)!.FieldType; - if (StartUpStateType == null) Log.Error("StartUpStateType not found"); - - // TODO: Make IP configurable + if (StartUpStateType == null) Log.Error("StartUpStateType not found"); + + // TODO: Make IP configurable client = new FutariClient("A1234567890", "futari.aquadx.net", 20101); } @@ -68,21 +69,21 @@ private static bool CheckAuth_Proc() isInit = true; return PrefixRet.RUN_ORIGINAL; - } - + } + #endregion - + #region Misc - - // Block irrelevant packets + + // Block irrelevant packets [HarmonyPrefix] [HarmonyPatch(typeof(SocketBase), "sendClass", typeof(ICommandParam))] private static bool sendClass(SocketBase __instance, ICommandParam info) { // Block AdvocateDelivery, SettingHostAddress - if (info is AdvocateDelivery or Setting.SettingHostAddress) return PrefixRet.BLOCK_ORIGINAL; - - // For logging only, log the actual type of info and the actual type of this class + if (info is AdvocateDelivery or Setting.SettingHostAddress) return PrefixRet.BLOCK_ORIGINAL; + + // For logging only, log the actual type of info and the actual type of this class Log.Debug($"SendClass: {Log.BRIGHT_RED}{info.GetType().Name}{Log.RESET} from {__instance.GetType().Name}"); return PrefixRet.RUN_ORIGINAL; } @@ -105,10 +106,10 @@ private static void isSameVersion(ref bool __result) { Log.Debug($"isSameVersion (original): {__result}, forcing true"); __result = true; - } - - // Patch my IP address to a stub - // public static IPAddress MyIpAddress(int mockID) + } + + // Patch my IP address to a stub + // public static IPAddress MyIpAddress(int mockID) [HarmonyPrefix] [HarmonyPatch(typeof(PartyLink.Util), "MyIpAddress", typeof(int))] private static bool MyIpAddress(int mockID, ref IPAddress __result) @@ -124,46 +125,34 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) [HarmonyPatch("StartupProcess", nameof(StartupProcess.OnUpdate))] private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____statusMsg, string[] ____statusSubMsg) { - //Title + // Status code ____statusMsg[7] = "WORLD LINK"; - switch (client.StatusCode) + ____statusSubMsg[7] = client.StatusCode switch { - case -1: - ____statusSubMsg[7] = "BAD"; - break; - case 0: - ____statusSubMsg[7] = "Not Connect"; - break; - case 1: - ____statusSubMsg[7] = "Connecting"; - break; - case 2: - ____statusSubMsg[7] = "GOOD"; - break; - - default: - ____statusSubMsg[7] = "Waiting..."; - break; - } - //Ping + -1 => "BAD", + 0 => "Not Connect", + 1 => "Connecting", + 2 => "GOOD", + _ => "Waiting..." + }; + + // Delay ____statusMsg[8] = "PING"; - ____statusSubMsg[8] = client._delayAvg == 0 ? "N/A" : client._delayAvg.ToString() + "ms"; - // - ____statusMsg[9] = ""; - ____statusSubMsg[9] = ""; - //Skip Oragin Init And Manual Init Party - if (____state == 0x04/*StartupProcess.StartUpState.WaitLinkDelivery*/) - { - ____state = 0x08;//StartupProcess.StartUpState.Ready - DeliveryChecker.get().start(true); - Setting.Data data = new Setting.Data(); - data.set(false, 4); - Setting.get().setData(data); - Setting.get().setRetryEnable(true); - Advertise.get().initialize(MachineGroupID.ON); - Manager.Party.Party.Party.Get().Start(MachineGroupID.ON); - Log.Info("Skip Startup Network Check"); - } + ____statusSubMsg[8] = client._delayAvg == 0 ? "N/A" : $"{client._delayAvg} ms"; + ____statusMsg[9] = "CAT :3"; + ____statusSubMsg[9] = "MEOW"; + + // If it is in the wait link delivery state, change to ready immediately + if (____state != 0x04) return; + ____state = 0x08; + + // Start the services that would have been started by the StartupNetworkChecker + DeliveryChecker.get().start(true); + Setting.get().setData(new Setting.Data().Also(x => x.set(false, 4))); + Setting.get().setRetryEnable(true); + Advertise.get().initialize(MachineGroupID.ON); + Manager.Party.Party.Party.Get().Start(MachineGroupID.ON); + Log.Info("Skip Startup Network Check"); } #region NFSocket @@ -333,151 +322,146 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac ____plane.ChangeCount(____encrypt.Count); packetWriteUInt.Invoke(null, [____plane, 0, (uint)____plane.Count]); return PrefixRet.BLOCK_ORIGINAL; - } - + } + #endregion #region Recruit - static int musicIDSUM; + + private static int musicIdSum; [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "OnStart")] - private static bool MusicSelectProcessOnStart(MusicSelectProcess __instance) - { - //初始化变量 - musicIDSUM = 0; - return PrefixRet.RUN_ORIGINAL; + private static bool MusicSelectProcessOnStart(MusicSelectProcess __instance) + { + //初始化变量 + musicIdSum = 0; + return PrefixRet.RUN_ORIGINAL; } + [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "PartyExec")] - private static bool PartyExec(MusicSelectProcess __instance) - { - //检查联机房间是否有更新.如果更新的话设置IsConnectingMusic=false然后刷新列表 - int checkDiff = 0; - foreach (var item in Manager.Party.Party.Party.Get().GetRecruitListWithoutMe()) - { - checkDiff += item.MusicID; - } - if (musicIDSUM != checkDiff) - { - musicIDSUM = checkDiff; - __instance.IsConnectingMusic = false; - } - return PrefixRet.RUN_ORIGINAL; - } + private static bool PartyExec(MusicSelectProcess __instance) + { + //检查联机房间是否有更新.如果更新的话设置IsConnectingMusic=false然后刷新列表 + var checkDiff = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Sum(item => item.MusicID); + if (musicIdSum != checkDiff) + { + musicIdSum = checkDiff; + __instance.IsConnectingMusic = false; + } + return PrefixRet.RUN_ORIGINAL; + } + [HarmonyPostfix] [HarmonyPatch(typeof(MusicSelectProcess), "RecruitData", MethodType.Getter)] - private static void RecruitDataOverride(MusicSelectProcess __instance, ref RecruitInfo __result) - { - //开歌时设置当前选择的联机数据 - if (__result != null) - { - var list = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe(); - if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) - __result = list[__instance.CurrentMusicSelect]; - } - } + private static void RecruitDataOverride(MusicSelectProcess __instance, ref RecruitInfo __result) + { + //开歌时设置当前选择的联机数据 + if (__result == null) return; + + var list = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe(); + if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) + __result = list[__instance.CurrentMusicSelect]; + } + + private static readonly MethodInfo SetRecruitData = typeof(MusicSelectProcess).GetProperty("RecruitData")!.SetMethod; + [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "IsConnectStart")] private static bool RecruitDataOverride(MusicSelectProcess __instance, - List ____connectCombineMusicDataList, - MusicSelectProcess.SubSequence[] ____currentPlayerSubSequence, - ref bool __result) - { - //修正SetConnectData触发条件.阻止原有IP判断重新设置 - if (!__instance.IsConnectingMusic) - { - typeof(MusicSelectProcess).GetProperty("RecruitData").SetMethod.Invoke(__instance,new object[] { new RecruitInfo()}); - SetConnectData(__instance, ____connectCombineMusicDataList, ____currentPlayerSubSequence); - __result = true; - } - __result = false; - return PrefixRet.BLOCK_ORIGINAL; - } - + List ____connectCombineMusicDataList, + SubSequence[] ____currentPlayerSubSequence, + ref bool __result) + { + //修正SetConnectData触发条件.阻止原有IP判断重新设置 + if (!__instance.IsConnectingMusic) + { + SetRecruitData.Invoke(__instance, [new RecruitInfo()]); + SetConnectData(__instance, ____connectCombineMusicDataList, ____currentPlayerSubSequence); + __result = true; + } + __result = false; + return PrefixRet.BLOCK_ORIGINAL; + } + + private static readonly MethodInfo SetConnectCategoryEnable = typeof(MusicSelectProcess).GetProperty("IsConnectCategoryEnable")!.SetMethod; + [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "SetConnectData")] - private static bool SetConnectData( - MusicSelectProcess __instance, - List ____connectCombineMusicDataList, - MusicSelectProcess.SubSequence[] ____currentPlayerSubSequence - ) + private static bool SetConnectData(MusicSelectProcess __instance, + List ____connectCombineMusicDataList, + SubSequence[] ____currentPlayerSubSequence) { - System.Type type = __instance.GetType(); - - ____connectCombineMusicDataList.Clear(); - type.GetProperty(nameof(__instance.IsConnectCategoryEnable)).SetMethod.Invoke(__instance, new object[] { false }); - //遍历所有房间并且显示 - foreach (var item in Manager.Party.Party.Party.Get().GetRecruitListWithoutMe()) - { - int musicID = item.MusicID; - MusicSelectProcess.CombineMusicSelectData combineMusicSelectData = new MusicSelectProcess.CombineMusicSelectData(); - MusicData music = Singleton.Instance.GetMusic(musicID); - List notesList = Singleton.Instance.GetNotesList()[musicID].NotesList; - - if (musicID < 10000) - { - combineMusicSelectData.existStandardScore = true; - } - else if (10000 < musicID && musicID < 20000) - { - combineMusicSelectData.existDeluxeScore = true; - } - for (int i = 0; i < 2; i++) - { - combineMusicSelectData.musicSelectData.Add(new MusicSelectProcess.MusicSelectData(music, notesList, 0)); - } - ____connectCombineMusicDataList.Add(combineMusicSelectData); - try - { - string thumbnailName = music.thumbnailName; - for (int j = 0; j < __instance.MonitorArray.Length; j++) - { - if (__instance.IsEntry(j)) - { - __instance.MonitorArray[j].SetRecruitInfo(thumbnailName); - SoundManager.PlaySE(Cue.SE_INFO_NORMAL, j); - } - } - } - catch (Exception e) - { - //防止有可能的空 - } - __instance.IsConnectingMusic = true; - } - if (Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Count == 0) - { - MusicSelectProcess.CombineMusicSelectData combineMusicSelectData2 = new MusicSelectProcess.CombineMusicSelectData(); - for (int k = 0; k < 2; k++) - { - combineMusicSelectData2.musicSelectData.Add(null); - } - combineMusicSelectData2.isWaitConnectScore = true; - ____connectCombineMusicDataList.Add(combineMusicSelectData2); - __instance.IsConnectingMusic = false; - } - if (__instance.MonitorArray != null) - { - for (int l = 0; l < __instance.MonitorArray.Length; l++) - { - if (____currentPlayerSubSequence[l] == MusicSelectProcess.SubSequence.Music) - { - __instance.MonitorArray[l].SetDeployList(false, false); - if (__instance.IsConnectionFolder(0)) - { - __instance.ChangeBGM(); - if (__instance.IsEntry(l)) - { - __instance.MonitorArray[l].SetVisibleButton(__instance.IsConnectingMusic, InputManager.ButtonSetting.Button04); - } - } - } - } + ____connectCombineMusicDataList.Clear(); + SetConnectCategoryEnable.Invoke(__instance, [false]); + + // 遍历所有房间并且显示 + foreach (var item in Manager.Party.Party.Party.Get().GetRecruitListWithoutMe()) + { + var musicID = item.MusicID; + var combineMusicSelectData = new CombineMusicSelectData(); + var music = Singleton.Instance.GetMusic(musicID); + var notesList = Singleton.Instance.GetNotesList()[musicID].NotesList; + + switch (musicID) + { + case < 10000: + combineMusicSelectData.existStandardScore = true; + break; + case > 10000 and < 20000: + combineMusicSelectData.existDeluxeScore = true; + break; + } + + for (var i = 0; i < 2; i++) + { + combineMusicSelectData.musicSelectData.Add(new MusicSelectData(music, notesList, 0)); + } + ____connectCombineMusicDataList.Add(combineMusicSelectData); + try + { + var thumbnailName = music.thumbnailName; + for (var j = 0; j < __instance.MonitorArray.Length; j++) + { + if (!__instance.IsEntry(j)) continue; + + __instance.MonitorArray[j].SetRecruitInfo(thumbnailName); + SoundManager.PlaySE(Cue.SE_INFO_NORMAL, j); + } + } + catch { /* 防止有可能的空 */ } + + __instance.IsConnectingMusic = true; + } + if (Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Count == 0) + { + ____connectCombineMusicDataList.Add(new CombineMusicSelectData + { + musicSelectData = [null, null], + isWaitConnectScore = true + }); + __instance.IsConnectingMusic = false; + } + + if (__instance.MonitorArray == null) return PrefixRet.BLOCK_ORIGINAL; + + for (var l = 0; l < __instance.MonitorArray.Length; l++) + { + if (____currentPlayerSubSequence[l] != SubSequence.Music) continue; + + __instance.MonitorArray[l].SetDeployList(false); + if (!__instance.IsConnectionFolder(0)) continue; + + __instance.ChangeBGM(); + if (!__instance.IsEntry(l)) continue; + + __instance.MonitorArray[l].SetVisibleButton(__instance.IsConnectingMusic, InputManager.ButtonSetting.Button04); } return PrefixRet.BLOCK_ORIGINAL; } #endregion + #region Debug - + [EnableIf(typeof(Futari), nameof(Debug))] public class FutariDebug { @@ -488,10 +472,10 @@ public class FutariDebug private static void ListenSocket(ListenSocket __instance, string name, int mockID) { Log.Debug($"new ListenSocket({name}, {mockID})"); - } - - // Log ListenSocket open - // ListenSocket:: public bool open(ushort portNumber) + } + + // Log ListenSocket open + // ListenSocket:: public bool open(ushort portNumber) [HarmonyPrefix] [HarmonyPatch(typeof(ListenSocket), "open", typeof(ushort))] private static bool open(ListenSocket __instance, ushort portNumber) @@ -518,49 +502,49 @@ private static bool procPacketData(Packet packet, Dictionary __ private static void Host(Host __instance, string name) { Log.Debug($"new Host({name})"); - } - - // Log host state change - // Host:: private void SetCurrentStateID(PartyPartyHostStateID nextState) + } + + // Log host state change + // Host:: private void SetCurrentStateID(PartyPartyHostStateID nextState) [HarmonyPrefix] [HarmonyPatch(typeof(Host), "SetCurrentStateID", typeof(PartyPartyHostStateID))] private static bool SetCurrentStateID(PartyPartyHostStateID nextState) { Log.Debug($"Host::SetCurrentStateID: {nextState}"); return PrefixRet.RUN_ORIGINAL; - } - - // Log Member creation - // Member:: public Member(string name, Host host, NFSocket socket) + } + + // Log Member creation + // Member:: public Member(string name, Host host, NFSocket socket) [HarmonyPostfix] [HarmonyPatch(typeof(Member), MethodType.Constructor, typeof(string), typeof(Host), typeof(NFSocket))] private static void Member(Member __instance, string name, Host host, NFSocket socket) { Log.Debug($"new Member({name}, {host}, {socket})"); - } - - // Log Member state change - // Member:: public void SetCurrentStateID(PartyPartyClientStateID state) + } + + // Log Member state change + // Member:: public void SetCurrentStateID(PartyPartyClientStateID state) [HarmonyPrefix] [HarmonyPatch(typeof(Member), "SetCurrentStateID", typeof(PartyPartyClientStateID))] private static bool SetCurrentStateID(PartyPartyClientStateID state) { Log.Debug($"Member::SetCurrentStateID: {state}"); return PrefixRet.RUN_ORIGINAL; - } - - // Log Member RecvRequestJoin - // Member:: private void RecvRequestJoin(Packet packet) + } + + // Log Member RecvRequestJoin + // Member:: private void RecvRequestJoin(Packet packet) [HarmonyPrefix] [HarmonyPatch(typeof(Member), "RecvRequestJoin", typeof(Packet))] private static bool RecvRequestJoin(Packet packet) { Log.Debug($"Member::RecvRequestJoin: {packet.getParam()}"); return PrefixRet.RUN_ORIGINAL; - } - - // Log Member RecvClientState - // Member:: private void RecvClientState(Packet packet) + } + + // Log Member RecvClientState + // Member:: private void RecvClientState(Packet packet) [HarmonyPrefix] [HarmonyPatch(typeof(Member), "RecvClientState", typeof(Packet))] private static bool RecvClientState(Packet packet) From ec9964661dc776630c503e76b607cd1168679a67 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:20:47 -0500 Subject: [PATCH 42/78] [F] Fix unknown fumen crash game MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 5731e18..d5f57bf 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -13,7 +13,6 @@ using Manager.Party.Party; using AquaMai.Core.Attributes; using MAI2.Util; -using Manager.MaiStudio; using Mai2.Mai2Cue; using static Process.MusicSelectProcess; @@ -118,6 +117,27 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) return PrefixRet.BLOCK_ORIGINAL; } + #endregion + + #region Bug Fix + + // Block start recruit if the song is not available + // Client:: private void RecvStartRecruit(Packet packet) + [HarmonyPrefix] + [HarmonyPatch(typeof(Client), "RecvStartRecruit", typeof(Packet))] + private static bool RecvStartRecruit(Packet packet) + { + var inf = packet.getParam().RecruitInfo; + if (Singleton.Instance.GetMusic(inf.MusicID) == null) + { + Log.Error($"Recruit received but music {inf.MusicID} is not available."); + Log.Error($"If you want to play with {string.Join(" and ", inf.MechaInfo.UserNames)},"); + Log.Error("make sure you have the same game version and option packs installed."); + return PrefixRet.BLOCK_ORIGINAL; + } + return PrefixRet.RUN_ORIGINAL; + } + #endregion //Skip StartupNetworkChecker @@ -325,6 +345,7 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac } #endregion + #region Recruit private static int musicIdSum; From 7aca93dcad5f1f32bd98a7ee67b092d7aba56de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Thu, 16 Jan 2025 07:28:59 +0900 Subject: [PATCH 43/78] [+] Room Info Displayer --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 57 +++++++++++++++++++++----- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 5731e18..c8211b4 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -13,7 +13,6 @@ using Manager.Party.Party; using AquaMai.Core.Attributes; using MAI2.Util; -using Manager.MaiStudio; using Mai2.Mai2Cue; using static Process.MusicSelectProcess; @@ -36,7 +35,7 @@ public static class Futari public static bool Debug = false; #region Init - + private static readonly MethodInfo SetRecruitData = typeof(MusicSelectProcess).GetProperty("RecruitData")!.SetMethod; public static void OnBeforePatch() { Log.Info("Starting WorldsLink patch..."); @@ -328,15 +327,16 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac #region Recruit private static int musicIdSum; + private static bool sideMessageFlag; [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "OnStart")] private static bool MusicSelectProcessOnStart(MusicSelectProcess __instance) { //初始化变量 musicIdSum = 0; + sideMessageFlag = false; return PrefixRet.RUN_ORIGINAL; } - [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "PartyExec")] private static bool PartyExec(MusicSelectProcess __instance) @@ -348,6 +348,37 @@ private static bool PartyExec(MusicSelectProcess __instance) musicIdSum = checkDiff; __instance.IsConnectingMusic = false; } + if (__instance.IsConnectingMusic && __instance.RecruitData != null && __instance.IsConnectionFolder()) + { + //设置房间信息显示 + string players = "WorldLink Room!"; + for (int i = 0; i < __instance.RecruitData.MechaInfo.UserNames.Length; i++) + { + if (__instance.RecruitData.MechaInfo.FumenDifs[i] != -1)//不是没加入的话 + { + players += " Player:" + __instance.RecruitData.MechaInfo.UserNames[i] + " "; + } + } + for (int i = 0; i < __instance.MonitorArray.Length; i++) + { + if (__instance.IsEntry(i)) + { + __instance.MonitorArray[i].SetSideMessage(players); + } + } + sideMessageFlag = true; + } + else if(!__instance.IsConnectionFolder()&&sideMessageFlag) + { + for (int i = 0; i < __instance.MonitorArray.Length; i++) + { + if (__instance.IsEntry(i)) + { + __instance.MonitorArray[i].SetSideMessage(CommonMessageID.Scroll_Music_Select.GetName()); + } + } + sideMessageFlag = false; + } return PrefixRet.RUN_ORIGINAL; } @@ -355,16 +386,20 @@ private static bool PartyExec(MusicSelectProcess __instance) [HarmonyPatch(typeof(MusicSelectProcess), "RecruitData", MethodType.Getter)] private static void RecruitDataOverride(MusicSelectProcess __instance, ref RecruitInfo __result) { + if (!__instance.IsConnectionFolder()) + { + return; + } //开歌时设置当前选择的联机数据 - if (__result == null) return; - - var list = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe(); - if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) - __result = list[__instance.CurrentMusicSelect]; + if (__result != null) + { + var list = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe(); + if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) + { + __result = list[__instance.CurrentMusicSelect]; + } + } } - - private static readonly MethodInfo SetRecruitData = typeof(MusicSelectProcess).GetProperty("RecruitData")!.SetMethod; - [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "IsConnectStart")] private static bool RecruitDataOverride(MusicSelectProcess __instance, From 3321de2c40572fb10911d9e708b2f39cb74378c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:07:49 +0900 Subject: [PATCH 44/78] [F] Fix RecruitInfo --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 985ab05..8110f75 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -432,7 +432,8 @@ private static bool RecruitDataOverride(MusicSelectProcess __instance, //修正SetConnectData触发条件.阻止原有IP判断重新设置 if (!__instance.IsConnectingMusic) { - SetRecruitData.Invoke(__instance, [new RecruitInfo()]); + + SetRecruitData.Invoke(__instance,[(Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Count==0?null:new RecruitInfo())]); SetConnectData(__instance, ____connectCombineMusicDataList, ____currentPlayerSubSequence); __result = true; } From 574efb1d1d236ed1d2b84fe4b87b2b5c6be0441c Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:17:54 -0500 Subject: [PATCH 45/78] [O] Style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 75 ++++++++++++-------------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 8110f75..b2b4dd5 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -354,72 +354,63 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac [HarmonyPatch(typeof(MusicSelectProcess), "OnStart")] private static bool MusicSelectProcessOnStart(MusicSelectProcess __instance) { - //初始化变量 + // 每次重新进入选区菜单之后重新初始化变量 musicIdSum = 0; sideMessageFlag = false; return PrefixRet.RUN_ORIGINAL; } + [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "PartyExec")] private static bool PartyExec(MusicSelectProcess __instance) { - //检查联机房间是否有更新.如果更新的话设置IsConnectingMusic=false然后刷新列表 + // 检查联机房间是否有更新,如果更新的话设置 IsConnectingMusic=false 然后刷新列表 var checkDiff = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Sum(item => item.MusicID); if (musicIdSum != checkDiff) { musicIdSum = checkDiff; __instance.IsConnectingMusic = false; } - if (__instance.IsConnectingMusic && __instance.RecruitData != null && __instance.IsConnectionFolder()) - { - //设置房间信息显示 - string players = "WorldLink Room!"; - for (int i = 0; i < __instance.RecruitData.MechaInfo.UserNames.Length; i++) - { - if (__instance.RecruitData.MechaInfo.FumenDifs[i] != -1)//不是没加入的话 - { - players += " Player:" + __instance.RecruitData.MechaInfo.UserNames[i] + " "; - } - } - for (int i = 0; i < __instance.MonitorArray.Length; i++) - { - if (__instance.IsEntry(i)) - { - __instance.MonitorArray[i].SetSideMessage(players); - } + if (__instance.IsConnectingMusic && __instance.RecruitData != null && __instance.IsConnectionFolder()) + { + // 设置房间信息显示 + var info = __instance.RecruitData.MechaInfo; + var players = "WorldLink Room! Players: " + + string.Join(" and ", info.UserNames.Where((_, i) => info.FumenDifs[i] != -1)); + for (var i = 0; i < __instance.MonitorArray.Length; i++) + { + if (__instance.IsEntry(i)) + { + __instance.MonitorArray[i].SetSideMessage(players); + } } sideMessageFlag = true; } - else if(!__instance.IsConnectionFolder()&&sideMessageFlag) - { - for (int i = 0; i < __instance.MonitorArray.Length; i++) - { - if (__instance.IsEntry(i)) - { - __instance.MonitorArray[i].SetSideMessage(CommonMessageID.Scroll_Music_Select.GetName()); - } + else if(!__instance.IsConnectionFolder() && sideMessageFlag) + { + for (var i = 0; i < __instance.MonitorArray.Length; i++) + { + if (__instance.IsEntry(i)) + { + __instance.MonitorArray[i].SetSideMessage(CommonMessageID.Scroll_Music_Select.GetName()); + } } - sideMessageFlag = false; + sideMessageFlag = false; } return PrefixRet.RUN_ORIGINAL; } - + [HarmonyPostfix] [HarmonyPatch(typeof(MusicSelectProcess), "RecruitData", MethodType.Getter)] private static void RecruitDataOverride(MusicSelectProcess __instance, ref RecruitInfo __result) { - if (!__instance.IsConnectionFolder()) - { - return; - } - //开歌时设置当前选择的联机数据 - if (__result != null) - { - var list = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe(); - if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) - { - __result = list[__instance.CurrentMusicSelect]; - } + // 开歌时设置当前选择的联机数据 + if (!__instance.IsConnectionFolder() || __result == null) return; + + var list = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe(); + if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) + { + __result = list[__instance.CurrentMusicSelect]; } } [HarmonyPrefix] @@ -490,6 +481,8 @@ private static bool SetConnectData(MusicSelectProcess __instance, __instance.IsConnectingMusic = true; } + + // No data available, add a dummy entry if (Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Count == 0) { ____connectCombineMusicDataList.Add(new CombineMusicSelectData From 6ed788898af5e81098fb63e6dcb075e8e1ddb2b4 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:35:59 -0500 Subject: [PATCH 46/78] [O] Flashing meow? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariClient.cs | 2 +- AquaMai.Mods/WorldsLink/FutariPatch.cs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 0b862d8..1be029f 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -45,7 +45,7 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, private readonly Stopwatch _heartbeat = new Stopwatch().Also(it => it.Start()); private readonly long[] _delayWindow = new int[20].Select(_ => -1L).ToArray(); - private int _delayIndex = 0; + public int _delayIndex = 0; public long _delayAvg = 0; public IPAddress StubIP => FutariExt.KeychipToStubIp(keychip).ToIP(); diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index b2b4dd5..beac766 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -140,6 +140,8 @@ private static bool RecvStartRecruit(Packet packet) #endregion + private static IManager PartyMan => Manager.Party.Party.Party.Get(); + //Skip StartupNetworkChecker [HarmonyPostfix] [HarmonyPatch("StartupProcess", nameof(StartupProcess.OnUpdate))] @@ -159,8 +161,8 @@ private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____sta // Delay ____statusMsg[8] = "PING"; ____statusSubMsg[8] = client._delayAvg == 0 ? "N/A" : $"{client._delayAvg} ms"; - ____statusMsg[9] = "CAT :3"; - ____statusSubMsg[9] = "MEOW"; + ____statusMsg[9] = "CAT"; + ____statusSubMsg[9] = client._delayIndex % 2 == 0 ? "MEOW" : ":3"; // If it is in the wait link delivery state, change to ready immediately if (____state != 0x04) return; @@ -171,7 +173,7 @@ private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____sta Setting.get().setData(new Setting.Data().Also(x => x.set(false, 4))); Setting.get().setRetryEnable(true); Advertise.get().initialize(MachineGroupID.ON); - Manager.Party.Party.Party.Get().Start(MachineGroupID.ON); + PartyMan.Start(MachineGroupID.ON); Log.Info("Skip Startup Network Check"); } @@ -365,7 +367,7 @@ private static bool MusicSelectProcessOnStart(MusicSelectProcess __instance) private static bool PartyExec(MusicSelectProcess __instance) { // 检查联机房间是否有更新,如果更新的话设置 IsConnectingMusic=false 然后刷新列表 - var checkDiff = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Sum(item => item.MusicID); + var checkDiff = PartyMan.GetRecruitListWithoutMe().Sum(item => item.MusicID); if (musicIdSum != checkDiff) { musicIdSum = checkDiff; @@ -407,7 +409,7 @@ private static void RecruitDataOverride(MusicSelectProcess __instance, ref Recru // 开歌时设置当前选择的联机数据 if (!__instance.IsConnectionFolder() || __result == null) return; - var list = Manager.Party.Party.Party.Get().GetRecruitListWithoutMe(); + var list = PartyMan.GetRecruitListWithoutMe(); if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) { __result = list[__instance.CurrentMusicSelect]; @@ -444,7 +446,7 @@ private static bool SetConnectData(MusicSelectProcess __instance, SetConnectCategoryEnable.Invoke(__instance, [false]); // 遍历所有房间并且显示 - foreach (var item in Manager.Party.Party.Party.Get().GetRecruitListWithoutMe()) + foreach (var item in PartyMan.GetRecruitListWithoutMe()) { var musicID = item.MusicID; var combineMusicSelectData = new CombineMusicSelectData(); @@ -483,7 +485,7 @@ private static bool SetConnectData(MusicSelectProcess __instance, } // No data available, add a dummy entry - if (Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Count == 0) + if (PartyMan.GetRecruitListWithoutMe().Count == 0) { ____connectCombineMusicDataList.Add(new CombineMusicSelectData { From e07a3d3d18aa92a90feef7014b986fbc6d00b685 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:37:00 -0500 Subject: [PATCH 47/78] [F] Fix utage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index beac766..15630e1 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -422,11 +422,10 @@ private static bool RecruitDataOverride(MusicSelectProcess __instance, SubSequence[] ____currentPlayerSubSequence, ref bool __result) { - //修正SetConnectData触发条件.阻止原有IP判断重新设置 - if (!__instance.IsConnectingMusic) + // 修正 SetConnectData 触发条件,阻止原有 IP 判断重新设置 + if (!__instance.IsConnectingMusic && PartyMan.GetRecruitListWithoutMe().Count > 0) { - - SetRecruitData.Invoke(__instance,[(Manager.Party.Party.Party.Get().GetRecruitListWithoutMe().Count==0?null:new RecruitInfo())]); + SetRecruitData.Invoke(__instance, [new RecruitInfo()]); SetConnectData(__instance, ____connectCombineMusicDataList, ____currentPlayerSubSequence); __result = true; } From d95210d0a9212face5e629cf53fc51fd6ba9ae0c Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:39:19 -0500 Subject: [PATCH 48/78] [F] Fix return logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 15630e1..03dea68 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -422,6 +422,8 @@ private static bool RecruitDataOverride(MusicSelectProcess __instance, SubSequence[] ____currentPlayerSubSequence, ref bool __result) { + __result = false; + // 修正 SetConnectData 触发条件,阻止原有 IP 判断重新设置 if (!__instance.IsConnectingMusic && PartyMan.GetRecruitListWithoutMe().Count > 0) { @@ -429,7 +431,6 @@ private static bool RecruitDataOverride(MusicSelectProcess __instance, SetConnectData(__instance, ____connectCombineMusicDataList, ____currentPlayerSubSequence); __result = true; } - __result = false; return PrefixRet.BLOCK_ORIGINAL; } From 1a02679f035f326185846d00b634e778eeec4172 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:49:28 -0500 Subject: [PATCH 49/78] [U] Update spacing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 03dea68..b401168 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -162,7 +162,7 @@ private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____sta ____statusMsg[8] = "PING"; ____statusSubMsg[8] = client._delayAvg == 0 ? "N/A" : $"{client._delayAvg} ms"; ____statusMsg[9] = "CAT"; - ____statusSubMsg[9] = client._delayIndex % 2 == 0 ? "MEOW" : ":3"; + ____statusSubMsg[9] = client._delayIndex % 2 == 0 ? "MEOW" : " :3 "; // If it is in the wait link delivery state, change to ready immediately if (____state != 0x04) return; From 40d2c5f651285c3c708d53192ead59ea75d9f258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:53:46 +0900 Subject: [PATCH 50/78] [+] Online Player Displayer --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 03dea68..433b01d 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -15,6 +15,10 @@ using MAI2.Util; using Mai2.Mai2Cue; using static Process.MusicSelectProcess; +using Monitor; +using TMPro; +using AquaMai.Mods.WorldsLink; +using UnityEngine; namespace AquaMai.Mods.WorldsLink; @@ -73,6 +77,36 @@ private static bool CheckAuth_Proc() #endregion #region Misc + //Online Display + [HarmonyPostfix] + [HarmonyPatch(typeof(CommonMonitor), "ViewUpdate")] + private static void CommonMonitorViewUpdate(CommonMonitor __instance,TextMeshProUGUI ____buildVersionText, GameObject ____developmentBuildText) + { + ____buildVersionText.transform.position = ____developmentBuildText.transform.position; + ____buildVersionText.gameObject.SetActive(true); + switch (client.StatusCode) + { + case -1: + ____buildVersionText.text = $"WorldLink Offline"; + ____buildVersionText.color = Color.red; + break; + case 0: + ____buildVersionText.text = $"WorldLink Disconnect"; + ____buildVersionText.color = Color.gray; + break; + case 1: + ____buildVersionText.text = $"WorldLink Connecting"; + ____buildVersionText.color = Color.yellow; + break; + case 2: + ____buildVersionText.color = Color.cyan; + if (Manager.Party.Party.Party.Get() == null) + ____buildVersionText.text = $"WorldLink Waiting Ready"; + else + ____buildVersionText.text = $"WorldLink Online:{Manager.Party.Party.Party.Get().GetRecruitList().Count}"; + break; + } + } // Block irrelevant packets [HarmonyPrefix] From b57e1567c2f9f23803f67768199b51a3007cf05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:08:47 +0900 Subject: [PATCH 51/78] [*]StartupProcess Text --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 0fca9fe..3393491 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -178,7 +178,7 @@ private static bool RecvStartRecruit(Packet packet) //Skip StartupNetworkChecker [HarmonyPostfix] - [HarmonyPatch("StartupProcess", nameof(StartupProcess.OnUpdate))] + [HarmonyPatch(nameof(StartupProcess), nameof(StartupProcess.OnUpdate))] private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____statusMsg, string[] ____statusSubMsg) { // Status code @@ -195,8 +195,8 @@ private static void SkipStartupNetworkCheck(ref byte ____state, string[] ____sta // Delay ____statusMsg[8] = "PING"; ____statusSubMsg[8] = client._delayAvg == 0 ? "N/A" : $"{client._delayAvg} ms"; - ____statusMsg[9] = "CAT"; - ____statusSubMsg[9] = client._delayIndex % 2 == 0 ? "MEOW" : " :3 "; + ____statusMsg[9] = "CAT :3"; + ____statusSubMsg[9] = client._delayIndex % 2 == 0 ? "" : "MEOW"; // If it is in the wait link delivery state, change to ready immediately if (____state != 0x04) return; From e1ae587ec49dcf32695293d48e9ade655e7c1d72 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 15 Jan 2025 19:27:59 -0500 Subject: [PATCH 52/78] [O] No leaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariExt.cs | 13 +++++++++++-- AquaMai.Mods/WorldsLink/FutariPatch.cs | 5 +---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index abdf912..529ebe4 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -3,17 +3,26 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; +using System.Security.Cryptography; +using System.Text; using PartyLink; namespace AquaMai.Mods.WorldsLink; public static class FutariExt { - public static uint KeychipToStubIp(string keychip) + private static uint HashStringToUInt(string input) { - return uint.Parse("1" + keychip.Substring(2)); + using var md5 = MD5.Create(); + var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(input)); + return ((uint)(hashBytes[0] & 0xFF) << 24) | + ((uint)(hashBytes[1] & 0xFF) << 16) | + ((uint)(hashBytes[2] & 0xFF) << 8) | + ((uint)(hashBytes[3] & 0xFF)); } + public static uint KeychipToStubIp(string keychip) => HashStringToUInt(keychip); + public static IPAddress ToIP(this uint val) => new(new IpAddress(val).GetAddressBytes()); public static uint ToU32(this IPAddress ip) => ip.ToNetworkByteOrderU32(); diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 3393491..8b0028c 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -100,10 +100,7 @@ private static void CommonMonitorViewUpdate(CommonMonitor __instance,TextMeshPro break; case 2: ____buildVersionText.color = Color.cyan; - if (Manager.Party.Party.Party.Get() == null) - ____buildVersionText.text = $"WorldLink Waiting Ready"; - else - ____buildVersionText.text = $"WorldLink Online:{Manager.Party.Party.Party.Get().GetRecruitList().Count}"; + ____buildVersionText.text = Manager.Party.Party.Party.Get() == null ? $"WorldLink Waiting Ready" : $"WorldLink Online:{Manager.Party.Party.Party.Get().GetRecruitList().Count}"; break; } } From c450d77ab8f432ec1ba94782b3fe28b906ab4386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=88?= <50571368+NekoShatoo@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:41:30 +0900 Subject: [PATCH 53/78] [+]Force LanAvailable --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 8b0028c..5e66017 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -72,11 +72,19 @@ private static bool CheckAuth_Proc() isInit = true; return PrefixRet.RUN_ORIGINAL; - } - + } + #endregion - + #region Misc + //Force Enable LanAvailable + [HarmonyPrefix] + [HarmonyPatch(typeof(Network), "IsLanAvailable", MethodType.Getter)] + private static bool PreIsLanAvailable(ref bool __result) + { + __result = true; + return false; + } //Online Display [HarmonyPostfix] [HarmonyPatch(typeof(CommonMonitor), "ViewUpdate")] From 127a8cba82cf75e0534c09216fa190b78b56947c Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:22:07 -0500 Subject: [PATCH 54/78] [F] Fix IsLanAvailable patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 5e66017..5415644 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -79,7 +79,7 @@ private static bool CheckAuth_Proc() #region Misc //Force Enable LanAvailable [HarmonyPrefix] - [HarmonyPatch(typeof(Network), "IsLanAvailable", MethodType.Getter)] + [HarmonyPatch(typeof(AMDaemon.Network), "IsLanAvailable", MethodType.Getter)] private static bool PreIsLanAvailable(ref bool __result) { __result = true; From 6fcab082fc50febeb2015df4bb7b41faeff0883a Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:23:08 -0500 Subject: [PATCH 55/78] [+] Fetch recruits from http MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariClient.cs | 110 ++++++++---------------- AquaMai.Mods/WorldsLink/FutariExt.cs | 48 +++++++++++ AquaMai.Mods/WorldsLink/FutariPatch.cs | 50 +++++++++-- AquaMai.Mods/WorldsLink/FutariSocket.cs | 6 ++ 4 files changed, 132 insertions(+), 82 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 1be029f..0e79d25 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -15,6 +15,8 @@ namespace AquaMai.Mods.WorldsLink; public class FutariClient(string keychip, string host, int port, int _) { + public static string LOBBY_BASE = "http://localhost/mai2-futari/recruit"; + // public static string LOBBY_BASE = "https://aquadx.net/aqua/mai2-futari/recruit"; public static FutariClient Instance { get; set; } public FutariClient(string keychip, string host, int port) : this(keychip, host, port, 0) @@ -50,36 +52,31 @@ public FutariClient(string keychip, string host, int port) : this(keychip, host, public IPAddress StubIP => FutariExt.KeychipToStubIp(keychip).ToIP(); - public int StatusCode { get => statusCode; } - public string ErrorMsg { get => errorMsg; } - public string Host { get => host; } - public int Port { get => port; } - - public void ConnectAsync() => new Thread(Connect) { IsBackground = true }.Start(); - /// /// -1: Failed to connect /// 0: Not connect /// 1: Connecting /// 2: Connected /// - int statusCode = 0; - string errorMsg = ""; + public int StatusCode { get; private set; } = 0; + public string ErrorMsg { get; private set; } = ""; - public void Connect() + public void ConnectAsync() => new Thread(Connect) { IsBackground = true }.Start(); + + private void Connect() { _tcpClient = new TcpClient(); try { - statusCode = 1; + StatusCode = 1; _tcpClient.Connect(host, port); - statusCode = 2; + StatusCode = 2; } catch (Exception ex) { - statusCode = -1; - errorMsg = ex.Message; + StatusCode = -1; + ErrorMsg = ex.Message; Log.Error($"Error connecting to server:\nHost:{host}:{port}\n{ex.Message}"); ConnectAsync(); return; @@ -94,19 +91,36 @@ public void Connect() Log.Info($"Connected to server at {host}:{port}"); // Start communication and message receiving in separate threads - _sendThread = new Thread(SendThread) { IsBackground = true }; - _recvThread = new Thread(RecvThread) { IsBackground = true }; + _sendThread = 10.Interval(() => + { + if (_heartbeat.ElapsedMilliseconds > 1000) + { + _heartbeat.Restart(); + Send(new Msg { cmd = Cmd.CTL_HEARTBEAT }); + } + + // Send any data in the send queue + while (sendQ.TryDequeue(out var msg)) Send(msg); + + }, final: Reconnect, name: "SendThread", stopOnError: true); + + _recvThread = 10.Interval(() => + { + var line = _reader.ReadLine(); + if (line == null) return; - _sendThread.Start(); - _recvThread.Start(); + var message = Msg.FromString(line); + HandleIncomingMessage(message); + + }, final: Reconnect, name: "RecvThread", stopOnError: true); } - public void Bind(int port, ProtocolType proto) + public void Bind(int bindPort, ProtocolType proto) { if (proto == ProtocolType.Tcp) - acceptQ.TryAdd(port, new ConcurrentQueue()); + acceptQ.TryAdd(bindPort, new ConcurrentQueue()); else if (proto == ProtocolType.Udp) - udpRecvQ.TryAdd(port, new ConcurrentQueue()); + udpRecvQ.TryAdd(bindPort, new ConcurrentQueue()); } private void Reconnect() @@ -133,60 +147,6 @@ private void Reconnect() ConnectAsync(); } - private void SendThread() - { - try - { - while (true) - { - if (_heartbeat.ElapsedMilliseconds > 1000) - { - _heartbeat.Restart(); - Send(new Msg { cmd = Cmd.CTL_HEARTBEAT }); - } - - // Send any data in the send queue - while (sendQ.TryDequeue(out var msg)) - Send(msg); - - Thread.Sleep(10); - } - } - catch (Exception ex) - { - Log.Error($"Error during communication: {ex.Message}"); - } - finally - { - Log.Error("SendThread finally reached"); - Reconnect(); - } - } - - private void RecvThread() - { - try - { - while (true) - { - var line = _reader.ReadLine(); - if (line == null) continue; - - var message = Msg.FromString(line); - HandleIncomingMessage(message); - } - } - catch (Exception ex) - { - Log.Error($"Error receiving messages: {ex.Message}"); - } - finally - { - Log.Error("RecvThread finally reached"); - Reconnect(); - } - } - private void HandleIncomingMessage(Msg msg) { if (msg.cmd != Cmd.CTL_HEARTBEAT) diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index 529ebe4..ae8b34c 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -5,6 +5,7 @@ using System.Net; using System.Security.Cryptography; using System.Text; +using System.Threading; using PartyLink; namespace AquaMai.Mods.WorldsLink; @@ -26,6 +27,7 @@ private static uint HashStringToUInt(string input) public static IPAddress ToIP(this uint val) => new(new IpAddress(val).GetAddressBytes()); public static uint ToU32(this IPAddress ip) => ip.ToNetworkByteOrderU32(); + public static void Do(this T x, Action f) => f(x); public static R Let(this T x, Func f) => f(x); public static T Also(this T x, Action f) { f(x); return x; } @@ -51,4 +53,50 @@ public static void Call(this object obj, string method, params object[] args) } public static uint MyStubIP() => KeychipToStubIp(AMDaemon.System.KeychipId.ShortValue); + + public static string Post(this string url, string body) => new WebClient().UploadString(url, body); + public static void PostAsync(this string url, string body, UploadStringCompletedEventHandler? callback = null) => + new WebClient().Also(web => + { + callback?.Do(it => web.UploadStringCompleted += it); + web.UploadStringAsync(new Uri(url), body); + }); + + public static Thread Interval( + this int delay, Action action, bool stopOnError = false, + Action? error = null, Action? final = null, string? name = null + ) => new Thread(() => + { + name ??= $"Interval {Thread.CurrentThread.ManagedThreadId} for {action}"; + try + { + while (true) + { + try + { + action(); + Thread.Sleep(delay); + } + catch (ThreadInterruptedException) + { + break; + } + catch (Exception e) + { + if (stopOnError) throw; + Log.Error($"Error in {name}: {e.Message}"); + } + } + } + catch (Exception e) + { + Log.Error($"Fatal error in {name}: {e.Message}"); + error?.Invoke(e); + } + finally + { + Log.Warn($"{name} stopped"); + final?.Invoke(); + } + }).Also(x => x.Start()); } \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 5415644..497a0b7 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Sockets; using System.Reflection; +using System.Threading; using AquaMai.Config.Attributes; using DB; using HarmonyLib; @@ -18,6 +19,7 @@ using Monitor; using TMPro; using AquaMai.Mods.WorldsLink; +using MelonLoader.TinyJSON; using UnityEngine; namespace AquaMai.Mods.WorldsLink; @@ -118,12 +120,25 @@ private static void CommonMonitorViewUpdate(CommonMonitor __instance,TextMeshPro [HarmonyPatch(typeof(SocketBase), "sendClass", typeof(ICommandParam))] private static bool sendClass(SocketBase __instance, ICommandParam info) { - // Block AdvocateDelivery, SettingHostAddress - if (info is AdvocateDelivery or Setting.SettingHostAddress) return PrefixRet.BLOCK_ORIGINAL; - - // For logging only, log the actual type of info and the actual type of this class - Log.Debug($"SendClass: {Log.BRIGHT_RED}{info.GetType().Name}{Log.RESET} from {__instance.GetType().Name}"); - return PrefixRet.RUN_ORIGINAL; + switch (info) + { + // Block AdvocateDelivery, SettingHostAddress + case AdvocateDelivery or Setting.SettingHostAddress: + return PrefixRet.BLOCK_ORIGINAL; + + // If it's a Start/FinishRecruit message, send it using http instead + case StartRecruit or FinishRecruit: + var start = info is StartRecruit ? "start" : "finish"; + var msg = JsonUtility.ToJson(info, false); + $"{FutariClient.LOBBY_BASE}/{start}".PostAsync(msg); + Log.Info($"Sent {start} recruit message: {msg}"); + return PrefixRet.BLOCK_ORIGINAL; + + // Log the actual type of info and the actual type of this class + default: + Log.Debug($"SendClass: {Log.BRIGHT_RED}{info.GetType().Name}{Log.RESET} from {__instance.GetType().Name}"); + return PrefixRet.RUN_ORIGINAL; + } } // Patch for error logging @@ -158,7 +173,27 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) #endregion - #region Bug Fix + #region Recruit Information + + private static readonly MethodInfo RStartRecruit = typeof(Client) + .GetMethod("RecvStartRecruit", BindingFlags.NonPublic | BindingFlags.Instance); + + // Client Constructor + // Client:: public Client(string name, PartyLink.Party.InitParam initParam) + [HarmonyPostfix] + [HarmonyPatch(typeof(Client), MethodType.Constructor, typeof(string), typeof(PartyLink.Party.InitParam))] + private static void Client(Client __instance, string name, PartyLink.Party.InitParam initParam) + { + Log.Debug($"new Client({name}, {initParam})"); + 10000.Interval(() => + $"{FutariClient.LOBBY_BASE}/list" + .Post("").Trim().Split('\n').Where(x => !string.IsNullOrEmpty(x)) + .Select(JsonUtility.FromJson).ToList() + .ForEach(x => RStartRecruit.Invoke(__instance, [ + new Packet(x.IpAddress).Also(y => y.encode(new StartRecruit(x))) + ]) + )); + } // Block start recruit if the song is not available // Client:: private void RecvStartRecruit(Packet packet) @@ -167,6 +202,7 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) private static bool RecvStartRecruit(Packet packet) { var inf = packet.getParam().RecruitInfo; + Log.Info($"RecvStartRecruit: {JsonUtility.ToJson(inf)}"); if (Singleton.Instance.GetMusic(inf.MusicID) == null) { Log.Error($"Recruit received but music {inf.MusicID} is not available."); diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index df35d32..7ad6382 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -156,6 +156,9 @@ public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) // Only used in BroadcastSocket public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) { + Log.Error("SendTo: Blocked"); + return 0; + Log.Debug($"SendTo: {size} bytes"); if (remoteEP is not IPEndPoint remote) return 0; _client.sendQ.Enqueue(new Msg @@ -188,6 +191,9 @@ public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, // Only used in UdpRecvSocket to receive from 0 (broadcast) public int ReceiveFrom(byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP) { + Log.Error("ReceiveFrom: Blocked"); + return 0; + if (!_client.udpRecvQ.TryGetValue(_bindPort, out var q) || !q.TryDequeue(out var msg)) { From c21c046bfe2f0174a736e86b2bf750cc2be09147 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:32:53 -0500 Subject: [PATCH 56/78] [O] Style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariExt.cs | 5 +++++ AquaMai.Mods/WorldsLink/FutariPatch.cs | 31 +++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index ae8b34c..77b4fa8 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -31,6 +31,11 @@ private static uint HashStringToUInt(string input) public static R Let(this T x, Func f) => f(x); public static T Also(this T x, Action f) { f(x); return x; } + public static void Each(this IEnumerable lst, Action f) + { + foreach (var v in lst) f(v); + } + public static byte[] View(this byte[] buffer, int offset, int size) { var array = new byte[size]; diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 497a0b7..2783f76 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -74,10 +74,10 @@ private static bool CheckAuth_Proc() isInit = true; return PrefixRet.RUN_ORIGINAL; - } - + } + #endregion - + #region Misc //Force Enable LanAvailable [HarmonyPrefix] @@ -110,7 +110,7 @@ private static void CommonMonitorViewUpdate(CommonMonitor __instance,TextMeshPro break; case 2: ____buildVersionText.color = Color.cyan; - ____buildVersionText.text = Manager.Party.Party.Party.Get() == null ? $"WorldLink Waiting Ready" : $"WorldLink Online:{Manager.Party.Party.Party.Get().GetRecruitList().Count}"; + ____buildVersionText.text = PartyMan == null ? $"WorldLink Waiting" : $"WorldLink Recruiting: {PartyMan.GetRecruitList().Count}"; break; } } @@ -427,6 +427,7 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac private static int musicIdSum; private static bool sideMessageFlag; + [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "OnStart")] private static bool MusicSelectProcessOnStart(MusicSelectProcess __instance) @@ -448,30 +449,24 @@ private static bool PartyExec(MusicSelectProcess __instance) musicIdSum = checkDiff; __instance.IsConnectingMusic = false; } + if (__instance.IsConnectingMusic && __instance.RecruitData != null && __instance.IsConnectionFolder()) { // 设置房间信息显示 var info = __instance.RecruitData.MechaInfo; var players = "WorldLink Room! Players: " + string.Join(" and ", info.UserNames.Where((_, i) => info.FumenDifs[i] != -1)); - for (var i = 0; i < __instance.MonitorArray.Length; i++) - { - if (__instance.IsEntry(i)) - { - __instance.MonitorArray[i].SetSideMessage(players); - } - } + + __instance.MonitorArray.Where((_, i) => __instance.IsEntry(i)) + .Each(x => x.SetSideMessage(players)); + sideMessageFlag = true; } else if(!__instance.IsConnectionFolder() && sideMessageFlag) { - for (var i = 0; i < __instance.MonitorArray.Length; i++) - { - if (__instance.IsEntry(i)) - { - __instance.MonitorArray[i].SetSideMessage(CommonMessageID.Scroll_Music_Select.GetName()); - } - } + __instance.MonitorArray.Where((_, i) => __instance.IsEntry(i)) + .Each(x => x.SetSideMessage(CommonMessageID.Scroll_Music_Select.GetName())); + sideMessageFlag = false; } return PrefixRet.RUN_ORIGINAL; From 213ee60743d32d2a1b6920ff5d8fc61de2fade5f Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:50:26 -0500 Subject: [PATCH 57/78] [U] Use official lobby MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 0e79d25..e076d68 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -15,8 +15,8 @@ namespace AquaMai.Mods.WorldsLink; public class FutariClient(string keychip, string host, int port, int _) { - public static string LOBBY_BASE = "http://localhost/mai2-futari/recruit"; - // public static string LOBBY_BASE = "https://aquadx.net/aqua/mai2-futari/recruit"; + // public static string LOBBY_BASE = "http://localhost/mai2-futari/recruit"; + public static string LOBBY_BASE = "https://aquadx.net/aqua/mai2-futari/recruit"; public static FutariClient Instance { get; set; } public FutariClient(string keychip, string host, int port) : this(keychip, host, port, 0) From e50219b84122d4c6fb5f6d12e737f4cd5d9a50e7 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:01:35 -0500 Subject: [PATCH 58/78] [F] Fix sleepy code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariClient.cs | 4 ++-- AquaMai.Mods/WorldsLink/FutariPatch.cs | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index e076d68..4af9662 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -16,8 +16,8 @@ namespace AquaMai.Mods.WorldsLink; public class FutariClient(string keychip, string host, int port, int _) { // public static string LOBBY_BASE = "http://localhost/mai2-futari/recruit"; - public static string LOBBY_BASE = "https://aquadx.net/aqua/mai2-futari/recruit"; - public static FutariClient Instance { get; set; } + public const string LOBBY_BASE = "https://aquadx.net/aqua/mai2-futari/recruit"; + public static FutariClient Instance { get; private set; } public FutariClient(string keychip, string host, int port) : this(keychip, host, port, 0) { diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 2783f76..7868474 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -482,9 +482,11 @@ private static void RecruitDataOverride(MusicSelectProcess __instance, ref Recru var list = PartyMan.GetRecruitListWithoutMe(); if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) { + Log.Debug($"MusicSelectProcess::RecruitData getter override: {__instance.CurrentMusicSelect}"); __result = list[__instance.CurrentMusicSelect]; } } + [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "IsConnectStart")] private static bool RecruitDataOverride(MusicSelectProcess __instance, @@ -495,9 +497,11 @@ private static bool RecruitDataOverride(MusicSelectProcess __instance, __result = false; // 修正 SetConnectData 触发条件,阻止原有 IP 判断重新设置 - if (!__instance.IsConnectingMusic && PartyMan.GetRecruitListWithoutMe().Count > 0) + var recruits = PartyMan.GetRecruitListWithoutMe(); + if (!__instance.IsConnectingMusic && recruits.Count > 0) { - SetRecruitData.Invoke(__instance, [new RecruitInfo()]); + Log.Debug("MusicSelectProcess::IsConnectStart recruit data has been set to empty"); + SetRecruitData.Invoke(__instance, [recruits[0]]); SetConnectData(__instance, ____connectCombineMusicDataList, ____currentPlayerSubSequence); __result = true; } From 400c1bf102e7cf6bd4d578293fd935fc5356f0a8 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:02:02 -0500 Subject: [PATCH 59/78] [+] Run and block blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariTypes.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index 8814648..ec70a94 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -11,6 +11,9 @@ public static class PrefixRet { public const bool BLOCK_ORIGINAL = false; public const bool RUN_ORIGINAL = true; + + public static bool Run(Action action) => RUN_ORIGINAL.Also(_ => action()); + public static bool Block(Action action) => BLOCK_ORIGINAL.Also(_ => action()); } public enum Cmd From 669be4459bcf28dfcf485e2b0227add5f7d526a4 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:27:26 -0500 Subject: [PATCH 60/78] [O] Loopback local traffic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariClient.cs | 12 ++++++++++-- AquaMai.Mods/WorldsLink/FutariPatch.cs | 2 +- AquaMai.Mods/WorldsLink/FutariSocket.cs | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 4af9662..653b5fa 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -150,7 +150,7 @@ private void Reconnect() private void HandleIncomingMessage(Msg msg) { if (msg.cmd != Cmd.CTL_HEARTBEAT) - Log.Info($"{FutariExt.KeychipToStubIp(keychip).ToIP()} <<< {msg.ToReadableString()}"); + Log.Info($"{StubIP} <<< {msg.ToReadableString()}"); switch (msg.cmd) { @@ -196,8 +196,16 @@ private void HandleIncomingMessage(Msg msg) private void Send(Msg msg) { + // Check if msg's destination ip is the same as my local ip. If so, handle it locally + if (msg.dst == StubIP.ToU32()) + { + Log.Debug($"Loopback @@@ {msg.ToReadableString()}"); + HandleIncomingMessage(msg); + return; + } + _writer.WriteLine(msg); if (msg.cmd != Cmd.CTL_HEARTBEAT) - Log.Info($"{FutariExt.KeychipToStubIp(keychip).ToIP()} >>> {msg.ToReadableString()}"); + Log.Info($"{StubIP} >>> {msg.ToReadableString()}"); } } diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 7868474..a212570 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -625,7 +625,7 @@ private static bool procPacketData(Packet packet, Dictionary __ // Log host creation // Host:: public Host(string name) [HarmonyPostfix] - [HarmonyPatch(typeof(Comio.Host), MethodType.Constructor, typeof(string))] + [HarmonyPatch(typeof(Host), MethodType.Constructor, typeof(string))] private static void Host(Host __instance, string name) { Log.Debug($"new Host({name})"); diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index 7ad6382..8c47dbd 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -83,7 +83,7 @@ public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) // Random stream ID and port _streamId = new Random().Next(); - _bindPort = new Random().Next(49152, 65535); + _bindPort = new Random().Next(55535, 65535); _client.tcpRecvQ[_streamId + _bindPort] = new ConcurrentQueue(); _client.acceptCallbacks[_streamId + _bindPort] = msg => From 4a959e1d4e045a66b1ded4b61048ec2e34186c91 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:39:35 -0500 Subject: [PATCH 61/78] [O] Bind can be local MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariSocket.cs | 15 +++++---------- AquaMai.Mods/WorldsLink/FutariTypes.cs | 2 -- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index 8c47dbd..a18cd91 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -39,11 +39,7 @@ public void Listen(int backlog) { } // ListenSocket.open, UdpRecvSocket.open public void Bind(EndPoint localEndP) { - if (localEndP is not IPEndPoint ipEndP) return; - _bindPort = ipEndP.Port; - _client.Bind(_bindPort, _proto); - _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_BIND, proto = _proto, - src = ipEndP.Address.ToU32(), sPort = ipEndP.Port }); + if (localEndP is IPEndPoint ep) _client.Bind(ep.Port, _proto); } // Only used in BroadcastSocket @@ -65,7 +61,7 @@ public static bool Poll(FutariSocket socket, SelectMode mode) return mode == SelectMode.SelectWrite; } - private static FieldInfo completedField = typeof(SocketAsyncEventArgs) + private static readonly FieldInfo CompletedField = typeof(SocketAsyncEventArgs) .GetField("Completed", BindingFlags.Instance | BindingFlags.NonPublic); // ConnectSocket.Enter_Connect (TCP) @@ -89,11 +85,10 @@ public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) _client.acceptCallbacks[_streamId + _bindPort] = msg => { Log.Info("ConnectAsync: Accept callback, invoking Completed event"); - var eventDelegate = (MulticastDelegate) completedField.GetValue(e); - if (eventDelegate == null) return; - foreach (var handler in eventDelegate.GetInvocationList()) + var events = (MulticastDelegate) CompletedField.GetValue(e); + foreach (var handler in events?.GetInvocationList() ?? []) { - Log.Info($"ConnectAsync: Invoking {handler.Method.Name}"); + Log.Debug($"ConnectAsync: Invoking {handler.Method.Name}"); handler.DynamicInvoke(e, new SocketAsyncEventArgs { SocketError = SocketError.Success }); } }; diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index ec70a94..e6cca48 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -20,11 +20,9 @@ public enum Cmd { // Control plane CTL_START = 1, - CTL_BIND = 2, CTL_HEARTBEAT = 3, CTL_TCP_CONNECT = 4, // Accept a new multiplexed TCP stream CTL_TCP_ACCEPT = 5, - CTL_TCP_ACCEPT_ACK = 6, CTL_TCP_CLOSE = 7, // Data plane From bbe260ffd7eeacc66921a630c76951d0db5723e4 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:17:24 -0500 Subject: [PATCH 62/78] [+] Call finish recruit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariExt.cs | 9 +++++---- AquaMai.Mods/WorldsLink/FutariPatch.cs | 24 +++++++++++++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index 77b4fa8..04a819b 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Security.Cryptography; using System.Text; using System.Threading; +using Manager.Party.Party; using PartyLink; namespace AquaMai.Mods.WorldsLink; @@ -31,10 +33,8 @@ private static uint HashStringToUInt(string input) public static R Let(this T x, Func f) => f(x); public static T Also(this T x, Action f) { f(x); return x; } - public static void Each(this IEnumerable lst, Action f) - { - foreach (var v in lst) f(v); - } + public static List Each(this IEnumerable enu, Action f) => + enu.ToList().Also(x => x.ForEach(f)); public static byte[] View(this byte[] buffer, int offset, int size) { @@ -104,4 +104,5 @@ public static Thread Interval( final?.Invoke(); } }).Also(x => x.Start()); + } \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index a212570..600e2fe 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -177,6 +177,12 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) private static readonly MethodInfo RStartRecruit = typeof(Client) .GetMethod("RecvStartRecruit", BindingFlags.NonPublic | BindingFlags.Instance); + private static readonly MethodInfo RFinishRecruit = typeof(Client) + .GetMethod("RecvFinishRecruit", BindingFlags.NonPublic | BindingFlags.Instance); + private static List recruits = []; + + private static string Identity(this RecruitInfo x) => $"{x.IpAddress} : {x.MusicID}"; + private static Packet ToPacket(this RecruitInfo info) => new Packet(info.IpAddress).Also(x => x.encode(new StartRecruit(info))); // Client Constructor // Client:: public Client(string name, PartyLink.Party.InitParam initParam) @@ -186,13 +192,17 @@ private static void Client(Client __instance, string name, PartyLink.Party.InitP { Log.Debug($"new Client({name}, {initParam})"); 10000.Interval(() => - $"{FutariClient.LOBBY_BASE}/list" - .Post("").Trim().Split('\n').Where(x => !string.IsNullOrEmpty(x)) - .Select(JsonUtility.FromJson).ToList() - .ForEach(x => RStartRecruit.Invoke(__instance, [ - new Packet(x.IpAddress).Also(y => y.encode(new StartRecruit(x))) - ]) - )); + recruits = $"{FutariClient.LOBBY_BASE}/list" + .Post("").Trim().Split('\n') + .Where(x => !string.IsNullOrEmpty(x)) + .Select(JsonUtility.FromJson) + .Each(x => RStartRecruit.Invoke(__instance, [x.ToPacket()])) + .Also(it => it + .Where(x => !recruits.Contains(x.Identity())) + .Each(x => RFinishRecruit.Invoke(__instance, [x.ToPacket()]))) + .Select(x => x.Identity()) + .ToList() + ); } // Block start recruit if the song is not available From 37778a6b9a08e3ce4f4d5daa0d7770b1f387e302 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:18:43 -0500 Subject: [PATCH 63/78] [F] Fix order: Finsih recruit before start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 600e2fe..f2c8fb6 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -195,11 +195,11 @@ private static void Client(Client __instance, string name, PartyLink.Party.InitP recruits = $"{FutariClient.LOBBY_BASE}/list" .Post("").Trim().Split('\n') .Where(x => !string.IsNullOrEmpty(x)) - .Select(JsonUtility.FromJson) - .Each(x => RStartRecruit.Invoke(__instance, [x.ToPacket()])) + .Select(JsonUtility.FromJson).ToList() .Also(it => it .Where(x => !recruits.Contains(x.Identity())) .Each(x => RFinishRecruit.Invoke(__instance, [x.ToPacket()]))) + .Each(x => RStartRecruit.Invoke(__instance, [x.ToPacket()])) .Select(x => x.Identity()) .ToList() ); From 816865d86db9cfe2b9a63969e9fd13031ab589d4 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:27:10 -0500 Subject: [PATCH 64/78] [F] Fix invocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariExt.cs | 6 +++--- AquaMai.Mods/WorldsLink/FutariPatch.cs | 15 +++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index 04a819b..17b28d1 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -79,8 +79,8 @@ public static Thread Interval( { try { - action(); Thread.Sleep(delay); + action(); } catch (ThreadInterruptedException) { @@ -89,13 +89,13 @@ public static Thread Interval( catch (Exception e) { if (stopOnError) throw; - Log.Error($"Error in {name}: {e.Message}"); + Log.Error($"Error in {name}: {e}"); } } } catch (Exception e) { - Log.Error($"Fatal error in {name}: {e.Message}"); + Log.Error($"Fatal error in {name}: {e}"); error?.Invoke(e); } finally diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index f2c8fb6..636b9f6 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -179,7 +179,7 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) .GetMethod("RecvStartRecruit", BindingFlags.NonPublic | BindingFlags.Instance); private static readonly MethodInfo RFinishRecruit = typeof(Client) .GetMethod("RecvFinishRecruit", BindingFlags.NonPublic | BindingFlags.Instance); - private static List recruits = []; + private static Dictionary recruits = []; private static string Identity(this RecruitInfo x) => $"{x.IpAddress} : {x.MusicID}"; private static Packet ToPacket(this RecruitInfo info) => new Packet(info.IpAddress).Also(x => x.encode(new StartRecruit(info))); @@ -196,12 +196,15 @@ private static void Client(Client __instance, string name, PartyLink.Party.InitP .Post("").Trim().Split('\n') .Where(x => !string.IsNullOrEmpty(x)) .Select(JsonUtility.FromJson).ToList() - .Also(it => it - .Where(x => !recruits.Contains(x.Identity())) - .Each(x => RFinishRecruit.Invoke(__instance, [x.ToPacket()]))) + .Also(lst => lst + .Select(x => x.Identity()) + .Do(ids => recruits.Keys + .Where(key => !ids.Contains(key)) + .Each(key => RFinishRecruit.Invoke(__instance, [recruits[key].ToPacket()])) + ) + ) .Each(x => RStartRecruit.Invoke(__instance, [x.ToPacket()])) - .Select(x => x.Identity()) - .ToList() + .ToDictionary(x => x.Identity()) ); } From 899942f2dc8d9356affd7de14b3b06ace61a4311 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:36:03 -0500 Subject: [PATCH 65/78] [F] Fix type mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 636b9f6..c71c3ee 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -182,7 +182,6 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) private static Dictionary recruits = []; private static string Identity(this RecruitInfo x) => $"{x.IpAddress} : {x.MusicID}"; - private static Packet ToPacket(this RecruitInfo info) => new Packet(info.IpAddress).Also(x => x.encode(new StartRecruit(info))); // Client Constructor // Client:: public Client(string name, PartyLink.Party.InitParam initParam) @@ -200,10 +199,14 @@ private static void Client(Client __instance, string name, PartyLink.Party.InitP .Select(x => x.Identity()) .Do(ids => recruits.Keys .Where(key => !ids.Contains(key)) - .Each(key => RFinishRecruit.Invoke(__instance, [recruits[key].ToPacket()])) + .Each(key => RFinishRecruit.Invoke(__instance, [new Packet(recruits[key].IpAddress) + .Also(p => p.encode(new FinishRecruit(recruits[key]))) + ])) ) ) - .Each(x => RStartRecruit.Invoke(__instance, [x.ToPacket()])) + .Each(x => RStartRecruit.Invoke(__instance, [new Packet(x.IpAddress) + .Also(p => p.encode(new StartRecruit(x))) + ])) .ToDictionary(x => x.Identity()) ); } @@ -494,8 +497,7 @@ private static void RecruitDataOverride(MusicSelectProcess __instance, ref Recru var list = PartyMan.GetRecruitListWithoutMe(); if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) - { - Log.Debug($"MusicSelectProcess::RecruitData getter override: {__instance.CurrentMusicSelect}"); + { __result = list[__instance.CurrentMusicSelect]; } } From 6ee5847c242821957925b9fcfdbfefefd7f167f5 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:53:13 -0500 Subject: [PATCH 66/78] [F] Fix bind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: シャト <50571368+NekoShatoo@users.noreply.github.com> --- AquaMai.Mods/WorldsLink/FutariSocket.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index a18cd91..be6b6ae 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -39,7 +39,7 @@ public void Listen(int backlog) { } // ListenSocket.open, UdpRecvSocket.open public void Bind(EndPoint localEndP) { - if (localEndP is IPEndPoint ep) _client.Bind(ep.Port, _proto); + if (localEndP is IPEndPoint ep) _client.Bind(_bindPort = ep.Port, _proto); } // Only used in BroadcastSocket From 4ad6e9971e14c3bf03060d65afdd9b6f39fa8abe Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Fri, 17 Jan 2025 08:56:41 -0500 Subject: [PATCH 67/78] [O] Final changes --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 45 ++++++++++++++------------ AquaMai.Mods/WorldsLink/FutariTypes.cs | 6 ++++ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index c71c3ee..5bda0b9 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -27,7 +27,7 @@ namespace AquaMai.Mods.WorldsLink; [ConfigSection( en: "Enable WorldsLink Multiplayer", zh: "启用 WorldsLink 多人游戏", - defaultOn: true)] + defaultOn: false)] public static class Futari { private static readonly Dictionary redirect = new(); @@ -128,8 +128,12 @@ private static bool sendClass(SocketBase __instance, ICommandParam info) // If it's a Start/FinishRecruit message, send it using http instead case StartRecruit or FinishRecruit: + var inf = info is StartRecruit o ? o.RecruitInfo : ((FinishRecruit) info).RecruitInfo; var start = info is StartRecruit ? "start" : "finish"; - var msg = JsonUtility.ToJson(info, false); + var msg = JsonUtility.ToJson(new RecruitRecord { + Keychip = client.keychip, + RecruitInfo = inf + }, false); $"{FutariClient.LOBBY_BASE}/{start}".PostAsync(msg); Log.Info($"Sent {start} recruit message: {msg}"); return PrefixRet.BLOCK_ORIGINAL; @@ -179,7 +183,7 @@ private static bool MyIpAddress(int mockID, ref IPAddress __result) .GetMethod("RecvStartRecruit", BindingFlags.NonPublic | BindingFlags.Instance); private static readonly MethodInfo RFinishRecruit = typeof(Client) .GetMethod("RecvFinishRecruit", BindingFlags.NonPublic | BindingFlags.Instance); - private static Dictionary recruits = []; + private static Dictionary lastRecruits = []; private static string Identity(this RecruitInfo x) => $"{x.IpAddress} : {x.MusicID}"; @@ -191,22 +195,23 @@ private static void Client(Client __instance, string name, PartyLink.Party.InitP { Log.Debug($"new Client({name}, {initParam})"); 10000.Interval(() => - recruits = $"{FutariClient.LOBBY_BASE}/list" + lastRecruits = $"{FutariClient.LOBBY_BASE}/list" .Post("").Trim().Split('\n') .Where(x => !string.IsNullOrEmpty(x)) - .Select(JsonUtility.FromJson).ToList() + .Select(JsonUtility.FromJson).ToList() .Also(lst => lst - .Select(x => x.Identity()) - .Do(ids => recruits.Keys + .Select(x => x.RecruitInfo.Identity()) + .Do(ids => lastRecruits.Keys .Where(key => !ids.Contains(key)) - .Each(key => RFinishRecruit.Invoke(__instance, [new Packet(recruits[key].IpAddress) - .Also(p => p.encode(new FinishRecruit(recruits[key]))) + .Each(key => RFinishRecruit.Invoke(__instance, [new Packet(lastRecruits[key].IpAddress) + .Also(p => p.encode(new FinishRecruit(lastRecruits[key]))) ])) ) ) - .Each(x => RStartRecruit.Invoke(__instance, [new Packet(x.IpAddress) - .Also(p => p.encode(new StartRecruit(x))) + .Each(x => RStartRecruit.Invoke(__instance, [new Packet(x.RecruitInfo.IpAddress) + .Also(p => p.encode(new StartRecruit(x.RecruitInfo))) ])) + .Select(x => x.RecruitInfo) .ToDictionary(x => x.Identity()) ); } @@ -439,7 +444,7 @@ private static bool PacketDecrypt(Packet __instance, PacketType ____encrypt, Pac #endregion - #region Recruit + #region Recruit UI private static int musicIdSum; private static bool sideMessageFlag; @@ -502,22 +507,22 @@ private static void RecruitDataOverride(MusicSelectProcess __instance, ref Recru } } + private static readonly MethodInfo SetConnData = typeof(MusicSelectProcess).GetMethod("SetConnectData")!; + [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "IsConnectStart")] - private static bool RecruitDataOverride(MusicSelectProcess __instance, - List ____connectCombineMusicDataList, - SubSequence[] ____currentPlayerSubSequence, - ref bool __result) + private static bool RecruitDataOverride(MusicSelectProcess __instance, ref bool __result) { __result = false; // 修正 SetConnectData 触发条件,阻止原有 IP 判断重新设置 var recruits = PartyMan.GetRecruitListWithoutMe(); - if (!__instance.IsConnectingMusic && recruits.Count > 0) + if (!__instance.IsConnectingMusic) { - Log.Debug("MusicSelectProcess::IsConnectStart recruit data has been set to empty"); - SetRecruitData.Invoke(__instance, [recruits[0]]); - SetConnectData(__instance, ____connectCombineMusicDataList, ____currentPlayerSubSequence); + var recruit = recruits.Count > 0 ? recruits[0] : null; + Log.Debug($"MusicSelectProcess::IsConnectStart recruit data has been set to {recruit}"); + SetRecruitData.Invoke(__instance, [recruit]); + SetConnData.Invoke(__instance, []); __result = true; } return PrefixRet.BLOCK_ORIGINAL; diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index e6cca48..690cc22 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net.Sockets; using System.Text; +using Manager.Party.Party; using MelonLoader; namespace AquaMai.Mods.WorldsLink; @@ -30,6 +31,11 @@ public enum Cmd DATA_BROADCAST = 22, } +public class RecruitRecord +{ + public RecruitInfo RecruitInfo; + public string Keychip; +} public struct Msg { From 6762e102fa7f2bda6a5ebec72ee2b48949a4402a Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Fri, 17 Jan 2025 09:04:27 -0500 Subject: [PATCH 68/78] [F] fix --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 5bda0b9..424bb02 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -517,9 +517,10 @@ private static bool RecruitDataOverride(MusicSelectProcess __instance, ref bool // 修正 SetConnectData 触发条件,阻止原有 IP 判断重新设置 var recruits = PartyMan.GetRecruitListWithoutMe(); - if (!__instance.IsConnectingMusic) + if (!__instance.IsConnectingMusic && recruits.Count > 0) { - var recruit = recruits.Count > 0 ? recruits[0] : null; + // var recruit = recruits.Count > 0 ? recruits[0] : null; + var recruit = recruits[0]; Log.Debug($"MusicSelectProcess::IsConnectStart recruit data has been set to {recruit}"); SetRecruitData.Invoke(__instance, [recruit]); SetConnData.Invoke(__instance, []); From e56788daef9107733522eabe407d533b56fd5ea9 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:47:33 -0500 Subject: [PATCH 69/78] [+] Copy code --- AquaMai.Mods/WorldsLink/FutariClient.cs | 150 ++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariExt.cs | 34 +++++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 195 ++++++++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariTypes.cs | 76 +++++++++ 4 files changed, 455 insertions(+) create mode 100644 AquaMai.Mods/WorldsLink/FutariClient.cs create mode 100644 AquaMai.Mods/WorldsLink/FutariExt.cs create mode 100644 AquaMai.Mods/WorldsLink/FutariSocket.cs create mode 100644 AquaMai.Mods/WorldsLink/FutariTypes.cs diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs new file mode 100644 index 0000000..7247ec5 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace AquaMai.Mods.WorldsLink; + +public class FutariClient +{ + public static FutariClient Instance { get; set; } + private static readonly JsonSerializerSettings settings = new() + { + NullValueHandling = NullValueHandling.Ignore + }; + + private readonly string _keychip; + private TcpClient _tcpClient; + private StreamWriter _writer; + private StreamReader _reader; + + public readonly ConcurrentQueue sendQ = new(); + // + public readonly ConcurrentDictionary> tcpRecvQ = new(); + // + public readonly ConcurrentDictionary> udpRecvQ = new(); + // + public readonly ConcurrentDictionary> acceptQ = new(); + + private Thread _sendThread; + private Thread _recvThread; + + public IPAddress StubIP => new IPAddress(FutariExt.KeychipToStubIp(_keychip)); + + public FutariClient(string keychip) + { + _keychip = keychip; + Instance = this; + } + + public void Connect(string host, int port) + { + _tcpClient = new TcpClient(); + _tcpClient.Connect(host, port); + + var networkStream = _tcpClient.GetStream(); + _writer = new StreamWriter(networkStream, Encoding.UTF8) { AutoFlush = true }; + _reader = new StreamReader(networkStream, Encoding.UTF8); + + // Register + Send(new Msg { cmd = Cmd.CTL_START, data = _keychip }); + Log.WriteLine($"Connected to server at {host}:{port}"); + + // Start communication and message receiving in separate threads + _sendThread = new Thread(SendThread) { IsBackground = true }; + _recvThread = new Thread(RecvThread) { IsBackground = true }; + + _sendThread.Start(); + _recvThread.Start(); + } + + public void Bind(int port, ProtocolType proto) + { + if (proto == ProtocolType.Tcp) + acceptQ.TryAdd(port, new ConcurrentQueue()); + else if (proto == ProtocolType.Udp) + udpRecvQ.TryAdd(port, new ConcurrentQueue()); + } + + private void SendThread() + { + try + { + long lastHeartbeat = 0; + while (true) + { + var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (time - lastHeartbeat > 1000) + { + Send(new Msg { cmd = Cmd.CTL_HEARTBEAT }); + lastHeartbeat = time; + } + + // Send any data in the send queue + while (sendQ.TryDequeue(out var msg)) + Send(msg); + + Thread.Sleep(10); + } + } + catch (Exception ex) + { + Log.WriteLine($"Error during communication: {ex.Message}"); + } + finally { _tcpClient.Close(); } + } + + private void RecvThread() + { + try + { + while (true) + { + var line = _reader.ReadLine(); + if (line == null) break; + + var message = JsonConvert.DeserializeObject(line); + if (message == null) continue; + HandleIncomingMessage(message); + } + } + catch (Exception ex) + { + Log.WriteLine($"Error receiving messages: {ex.Message}"); + } + finally { _tcpClient.Close(); } + } + + private void HandleIncomingMessage(Msg msg) + { + Log.WriteLine($"{_keychip} <<< {JsonConvert.SerializeObject(msg, settings)}"); + + switch (msg.cmd) + { + // UDP message + case Cmd.DATA_SEND or Cmd.DATA_BROADCAST when msg.proto == ProtocolType.Udp && msg.dPort != null: + udpRecvQ.Get(msg.dPort.Value)?.Enqueue(msg); + break; + + // TCP connection + case Cmd.DATA_SEND when msg.proto == ProtocolType.Tcp && msg.sid != null: + tcpRecvQ.Get(msg.sid.Value)?.Enqueue(msg); + break; + + // TCP connection accepted + case Cmd.CTL_TCP_CONNECT when msg.dPort != null: + acceptQ.Get(msg.dPort.Value)?.Enqueue(msg); + break; + } + } + + private void Send(Msg msg) + { + var json = JsonConvert.SerializeObject(msg, settings); + _writer.WriteLine(json); + Log.WriteLine($"{_keychip} >>> {json}"); + } +} diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs new file mode 100644 index 0000000..c91b1a4 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -0,0 +1,34 @@ +#nullable enable +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace AquaMai.Mods.WorldsLink; + +public static class FutariExt +{ + public static uint KeychipToStubIp(string keychip) + { + return uint.Parse("1" + keychip.Substring(2)); + } + + public static byte[] View(this byte[] buffer, int offset, int size) + { + var array = new byte[size]; + Array.Copy(buffer, offset, array, 0, size); + return array; + } + + public static V? Get(this ConcurrentDictionary dict, K key) where V : class + { + return dict.GetValueOrDefault(key); + } + + // Call a function using reflection + public static void Call(this object obj, string method, params object[] args) + { + obj.GetType().GetMethod(method)?.Invoke(obj, args); + } + + public static uint MyStubIP() => KeychipToStubIp(AMDaemon.System.KeychipId.ShortValue); +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs new file mode 100644 index 0000000..3392b61 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using PartyLink; + +namespace AquaMai.Mods.WorldsLink; + +public class NFSocket +{ + private int _bindPort = -1; + private readonly FutariClient _client; + private readonly ProtocolType _proto; + private int _streamId = -1; + + // ConnectSocket.Enter_Active (doesn't seem to be actually used) + public EndPoint LocalEndPoint { get; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); + + // ConnectSocket.Enter_Active, ListenSocket.acceptClient (TCP) + // Each client's remote endpoint must be different + public EndPoint RemoteEndPoint { get; private set; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); + + public NFSocket(FutariClient client, ProtocolType proto) + { + _client = client; + _proto = proto; + } + + // Compatibility constructor + public NFSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) : + this(FutariClient.Instance!, protocolType) + { + } + + // ListenSocket.open (TCP) + public void Listen(int backlog) + { + /* Do nothing */ + } + + // ListenSocket.open, UdpRecvSocket.open + public void Bind(EndPoint localEndP) + { + if (localEndP is not IPEndPoint ipEndP) return; + _bindPort = ipEndP.Port; + _client.Bind(_bindPort, _proto); + _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_BIND, proto = _proto, + src = ipEndP.Address.ToNetworkByteOrderU32(), sPort = ipEndP.Port }); + } + + // Only used in BroadcastSocket + public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) + { + /* Do nothing */ + } + + // SocketBase.checkRecvEnable, checkSendEnable + // This is the Select step called before blocking calls (e.g. Accept) + public static bool Poll(NFSocket socket, SelectMode mode) + { + Log.Debug("Poll called"); + if (mode == SelectMode.SelectRead) + { + return (socket._proto == ProtocolType.Udp) // Check is UDP + ? !socket._client.udpRecvQ.Get(socket._bindPort)?.IsEmpty ?? false + : (socket._streamId == -1) // Check is TCP stream or TCP server + ? !socket._client.acceptQ.Get(socket._bindPort)?.IsEmpty ?? false + : !socket._client.tcpRecvQ.Get(socket._streamId)?.IsEmpty ?? false; + } + // Write is always ready? + return mode == SelectMode.SelectWrite; + } + + // ConnectSocket.Enter_Connect (TCP) + // The destination address is obtained from a RecruitInfo packet sent by the host. + // The host should patch their PartyLink.Util.MyIpAddress() to return a mock address instead of the real one + // Returns true if IO is pending and false if the operation completed synchronously + // When it's false, e will contain the result of the operation + public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) + { + if (e.RemoteEndPoint is not IPEndPoint ipEndP) return false; + _streamId = new Random().Next(); + _client.tcpRecvQ[_streamId] = new ConcurrentQueue(); + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.CTL_TCP_CONNECT, + proto = _proto, + sid = _streamId, + dst = ipEndP.Address.ToNetworkByteOrderU32(), + dPort = ipEndP.Port + }); + // It is very annoying to call Complete event using reflection + // So we'll just pretend that the client has ACKed + return false; + } + + // Accept is blocking + public NFSocket Accept() + { + // Check if accept queue has any pending connections + if (!_client.acceptQ.TryGetValue(_bindPort, out var q) || + !q.TryDequeue(out var msg) || + msg.sid == null || + msg.src == null) + { + Log.Warn("Accept: No pending connections"); + return null; + } + + _client.tcpRecvQ[msg.sid.Value] = new ConcurrentQueue(); + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.CTL_TCP_ACCEPT, proto = _proto, sid = msg.sid, dst = msg.src + }); + + return new NFSocket(_client, _proto) + { + _streamId = msg.sid.Value, + RemoteEndPoint = new IPEndPoint(new IPAddress(new IpAddress(msg.src.Value).GetAddressBytes()), + _bindPort) + }; + } + + public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) + { + Log.Debug($"Send: {size} bytes"); + // Remote EP is not relevant here, because the stream is already established, + // there can only be one remote endpoint + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size), + sid = _streamId == -1 ? null : _streamId + }); + return size; + } + + // Only used in BroadcastSocket + public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) + { + Log.Debug($"SendTo: {size} bytes"); + if (remoteEP is not IPEndPoint ipEndP) return 0; + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size), dPort = ipEndP.Port + }); + return size; + } + + // Only used in TCP ConnectSocket + public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode) + { + Log.Debug("Receive called"); + if (!_client.tcpRecvQ.TryGetValue(_streamId, out var q) || + !q.TryDequeue(out var msg)) + { + Log.Warn("Receive: No data to receive"); + errorCode = SocketError.WouldBlock; + return 0; + } + var data = Convert.FromBase64String((string) msg.data!); + + Buffer.BlockCopy(data, 0, buffer, 0, data.Length); + errorCode = SocketError.Success; + return data.Length; + } + + // Only used in UdpRecvSocket to receive from 0 (broadcast) + public int ReceiveFrom(byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP) + { + Log.Debug("ReceiveFrom called"); + if (!_client.udpRecvQ.TryGetValue(_bindPort, out var q) || + !q.TryDequeue(out var msg)) + { + Log.Warn("ReceiveFrom: No data to receive"); + return 0; + } + var data = Convert.FromBase64String((string) msg.data!); + + Buffer.BlockCopy(data, 0, buffer, 0, data.Length); + return data.Length; + } + + // Called everywhere, but only relevant for TCP + public void Close() + { + // TCP FIN/RST + if (_proto == ProtocolType.Tcp) + _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_TCP_CLOSE, proto = _proto }); + } + + public void Shutdown(SocketShutdown how) + { + Close(); + } +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs new file mode 100644 index 0000000..db8120a --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -0,0 +1,76 @@ +using System.Net.Sockets; + +namespace AquaMai.Mods.WorldsLink; + +public enum Cmd +{ + // Control plane + CTL_START = 1, + CTL_BIND = 2, + CTL_HEARTBEAT = 3, + CTL_TCP_CONNECT = 4, // Accept a new multiplexed TCP stream + CTL_TCP_ACCEPT = 5, + CTL_TCP_ACCEPT_ACK = 6, + CTL_TCP_CLOSE = 7, + + // Data plane + DATA_SEND = 21, + DATA_BROADCAST = 22, +} + + +public class Msg +{ + public Cmd cmd { get; set; } + public ProtocolType? proto { get; set; } + public int? sid { get; set; } + public uint? src { get; set; } + public int? sPort { get; set; } + public uint? dst { get; set; } + public int? dPort { get; set; } + public object? data { get; set; } +} + +public abstract class Log +{ + // Text colors + private const string BLACK = "\u001b[30m"; + private const string RED = "\u001b[31m"; + private const string GREEN = "\u001b[32m"; + private const string YELLOW = "\u001b[33m"; + private const string BLUE = "\u001b[34m"; + private const string MAGENTA = "\u001b[35m"; + private const string CYAN = "\u001b[36m"; + private const string WHITE = "\u001b[37m"; + + // Bright text colors + private const string BRIGHT_BLACK = "\u001b[90m"; + private const string BRIGHT_RED = "\u001b[91m"; + private const string BRIGHT_GREEN = "\u001b[92m"; + private const string BRIGHT_YELLOW = "\u001b[93m"; + private const string BRIGHT_BLUE = "\u001b[94m"; + private const string BRIGHT_MAGENTA = "\u001b[95m"; + private const string BRIGHT_CYAN = "\u001b[96m"; + private const string BRIGHT_WHITE = "\u001b[97m"; + + // Reset + private const string RESET = "\u001b[0m"; + + public static void Warn(string msg) + { + System.Console.WriteLine(YELLOW + "WARN " + msg + RESET); + } + + public static void Debug(string msg) + { + System.Console.WriteLine(BLUE + "DEBUG " + msg + RESET); + } + + public static void WriteLine(string msg) + { + if (msg.StartsWith("A001")) msg = MAGENTA + msg; + if (msg.StartsWith("A002")) msg = CYAN + msg; + msg = msg.Replace("Error", RED + "Error"); + System.Console.WriteLine(msg + RESET); + } +} From c6cf6ff50c512a0f664a514ce11f4144b1681e88 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:49:08 -0500 Subject: [PATCH 70/78] [-] Remove json and use manual serailization --- AquaMai.Mods/WorldsLink/FutariClient.cs | 14 ++----- AquaMai.Mods/WorldsLink/FutariExt.cs | 5 +++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 8 ++-- AquaMai.Mods/WorldsLink/FutariTypes.cs | 54 ++++++++++++++++++++----- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 7247ec5..cfcbff0 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -11,10 +11,6 @@ namespace AquaMai.Mods.WorldsLink; public class FutariClient { public static FutariClient Instance { get; set; } - private static readonly JsonSerializerSettings settings = new() - { - NullValueHandling = NullValueHandling.Ignore - }; private readonly string _keychip; private TcpClient _tcpClient; @@ -106,8 +102,7 @@ private void RecvThread() var line = _reader.ReadLine(); if (line == null) break; - var message = JsonConvert.DeserializeObject(line); - if (message == null) continue; + var message = Msg.FromString(line); HandleIncomingMessage(message); } } @@ -120,7 +115,7 @@ private void RecvThread() private void HandleIncomingMessage(Msg msg) { - Log.WriteLine($"{_keychip} <<< {JsonConvert.SerializeObject(msg, settings)}"); + Log.WriteLine($"{_keychip} <<< {msg}"); switch (msg.cmd) { @@ -143,8 +138,7 @@ private void HandleIncomingMessage(Msg msg) private void Send(Msg msg) { - var json = JsonConvert.SerializeObject(msg, settings); - _writer.WriteLine(json); - Log.WriteLine($"{_keychip} >>> {json}"); + _writer.WriteLine(msg); + Log.WriteLine($"{_keychip} >>> {msg}"); } } diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index c91b1a4..7bc2365 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -11,6 +11,8 @@ public static uint KeychipToStubIp(string keychip) { return uint.Parse("1" + keychip.Substring(2)); } + + public static R Let(this T x, Func f) => f(x); public static byte[] View(this byte[] buffer, int offset, int size) { @@ -19,6 +21,9 @@ public static byte[] View(this byte[] buffer, int offset, int size) return array; } + public static string B64(this byte[] buffer) => Convert.ToBase64String(buffer); + public static byte[] B64(this string str) => Convert.FromBase64String(str); + public static V? Get(this ConcurrentDictionary dict, K key) where V : class { return dict.GetValueOrDefault(key); diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index 3392b61..e21490e 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -128,7 +128,7 @@ public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) // there can only be one remote endpoint _client.sendQ.Enqueue(new Msg { - cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size), + cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size).B64(), sid = _streamId == -1 ? null : _streamId }); return size; @@ -141,7 +141,7 @@ public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, if (remoteEP is not IPEndPoint ipEndP) return 0; _client.sendQ.Enqueue(new Msg { - cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size), dPort = ipEndP.Port + cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size).B64(), dPort = ipEndP.Port }); return size; } @@ -157,7 +157,7 @@ public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, errorCode = SocketError.WouldBlock; return 0; } - var data = Convert.FromBase64String((string) msg.data!); + var data = msg.data!.B64(); Buffer.BlockCopy(data, 0, buffer, 0, data.Length); errorCode = SocketError.Success; @@ -174,7 +174,7 @@ public int ReceiveFrom(byte[] buffer, SocketFlags socketFlags, ref EndPoint remo Log.Warn("ReceiveFrom: No data to receive"); return 0; } - var data = Convert.FromBase64String((string) msg.data!); + var data = msg.data!.B64(); Buffer.BlockCopy(data, 0, buffer, 0, data.Length); return data.Length; diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index db8120a..9a2b083 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using System.Net.Sockets; namespace AquaMai.Mods.WorldsLink; @@ -19,16 +21,50 @@ public enum Cmd } -public class Msg +public struct Msg { - public Cmd cmd { get; set; } - public ProtocolType? proto { get; set; } - public int? sid { get; set; } - public uint? src { get; set; } - public int? sPort { get; set; } - public uint? dst { get; set; } - public int? dPort { get; set; } - public object? data { get; set; } + public Cmd cmd; + public ProtocolType? proto; + public int? sid; + public uint? src; + public int? sPort; + public uint? dst; + public int? dPort; + public string? data; + + public override string ToString() + { + int? proto_ = proto == null ? null : (int) proto; + var arr = new object[] { + 1, (int) cmd, proto_, sid, src, sPort, dst, dPort, + null, null, null, null, null, null, null, null, // reserved for future use + data + }; + + // Map nulls to empty strings + return string.Join(",", arr.Select(x => x ?? "")).TrimEnd(','); + } + + private static T? Parse(string[] fields, int i) where T : struct + => fields.Length <= i || fields[i] == "" ? null + : (T) Convert.ChangeType(fields[i], typeof(T)); + + + public static Msg FromString(string str) + { + var fields = str.Split(','); + return new Msg + { + cmd = (Cmd) (Parse(fields, 1) ?? throw new InvalidOperationException("cmd is required")), + proto = Parse(fields, 2)?.Let(it => (ProtocolType) it), + sid = Parse(fields, 3), + src = Parse(fields, 4), + sPort = Parse(fields, 5), + dst = Parse(fields, 6), + dPort = Parse(fields, 7), + data = string.Join(",", fields.Skip(16)) + }; + } } public abstract class Log From 4799b3a61b14737ac1536a91ab4132a42c04affb Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:17:50 -0500 Subject: [PATCH 71/78] [+] Patch? --- AquaMai.Mods/WorldsLink/FutariClient.cs | 10 +- AquaMai.Mods/WorldsLink/FutariPatch.cs | 164 ++++++++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 31 ++--- AquaMai.Mods/WorldsLink/FutariTypes.cs | 14 +- 4 files changed, 189 insertions(+), 30 deletions(-) create mode 100644 AquaMai.Mods/WorldsLink/FutariPatch.cs diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index cfcbff0..fda7e75 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -47,7 +47,7 @@ public void Connect(string host, int port) // Register Send(new Msg { cmd = Cmd.CTL_START, data = _keychip }); - Log.WriteLine($"Connected to server at {host}:{port}"); + Log.Info($"Connected to server at {host}:{port}"); // Start communication and message receiving in separate threads _sendThread = new Thread(SendThread) { IsBackground = true }; @@ -88,7 +88,7 @@ private void SendThread() } catch (Exception ex) { - Log.WriteLine($"Error during communication: {ex.Message}"); + Log.Info($"Error during communication: {ex.Message}"); } finally { _tcpClient.Close(); } } @@ -108,14 +108,14 @@ private void RecvThread() } catch (Exception ex) { - Log.WriteLine($"Error receiving messages: {ex.Message}"); + Log.Info($"Error receiving messages: {ex.Message}"); } finally { _tcpClient.Close(); } } private void HandleIncomingMessage(Msg msg) { - Log.WriteLine($"{_keychip} <<< {msg}"); + Log.Info($"{_keychip} <<< {msg}"); switch (msg.cmd) { @@ -139,6 +139,6 @@ private void HandleIncomingMessage(Msg msg) private void Send(Msg msg) { _writer.WriteLine(msg); - Log.WriteLine($"{_keychip} >>> {msg}"); + Log.Info($"{_keychip} >>> {msg}"); } } diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs new file mode 100644 index 0000000..9522dc9 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using HarmonyLib; +using MelonLoader; +using PartyLink; + +namespace AquaMai.Mods.WorldsLink; + +public static class FutariPatch +{ + private static readonly Dictionary redirect = new(); + + static FutariPatch() + { + new FutariClient(AMDaemon.System.KeychipId.ShortValue).Connect("violet", 20101); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(AddressFamily), typeof(SocketType), typeof(ProtocolType), typeof(int))] + private static void NFCreate(NFSocket __instance, AddressFamily a1, SocketType a2, ProtocolType a3, int a4) + { + Log.Debug("new NFSocket(AddressFamily, SocketType, ProtocolType, int)"); + var futari = new FutariSocket(a1, a2, a3, a4); + redirect.Add(__instance, futari); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(Socket))] + private static void NFCreate2(NFSocket __instance, Socket nfSocket) + { + Log.Warn("new NFSocket(Socket) -- We shouldn't get here."); + throw new NotImplementedException(); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Poll")] + private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) + { + Log.Debug("NFPoll"); + FutariSocket.Poll(redirect[socket], mode); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Send")] + private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags) + { + Log.Debug("NFSend"); + redirect[__instance].Send(buffer, offset, size, socketFlags); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "SendTo")] + private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) + { + Log.Debug("NFSendTo"); + redirect[__instance].SendTo(buffer, offset, size, socketFlags, remoteEP); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Receive")] + private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode) + { + Log.Debug("NFReceive"); + redirect[__instance].Receive(buffer, offset, size, socketFlags, out errorCode); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "ReceiveFrom")] + private static bool NFReceiveFrom(NFSocket __instance, byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP) + { + Log.Debug("NFReceiveFrom"); + redirect[__instance].ReceiveFrom(buffer, socketFlags, ref remoteEP); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Bind")] + private static bool NFBind(NFSocket __instance, EndPoint localEndP) + { + Log.Debug("NFBind"); + redirect[__instance].Bind(localEndP); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Listen")] + private static bool NFListen(NFSocket __instance, int backlog) + { + Log.Debug("NFListen"); + redirect[__instance].Listen(backlog); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Accept")] + private static bool NFAccept(NFSocket __instance, ref NFSocket __result) + { + Log.Debug("NFAccept"); + var futariSocket = redirect[__instance].Accept(); + var mockSocket = new NFSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp, 0); + redirect.Add(mockSocket, futariSocket); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "ConnectAsync")] + private static bool NFConnectAsync(NFSocket __instance, SocketAsyncEventArgs e, int mockID) + { + Log.Debug("NFConnectAsync"); + redirect[__instance].ConnectAsync(e, mockID); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "SetSocketOption")] + private static bool NFSetSocketOption(NFSocket __instance, SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue) + { + Log.Debug("NFSetSocketOption"); + redirect[__instance].SetSocketOption(optionLevel, optionName, optionValue); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Close")] + private static bool NFClose(NFSocket __instance) + { + Log.Debug("NFClose"); + redirect[__instance].Close(); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Shutdown")] + private static bool NFShutdown(NFSocket __instance, SocketShutdown how) + { + Log.Debug("NFShutdown"); + redirect[__instance].Shutdown(how); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "RemoteEndPoint", MethodType.Getter)] + private static bool NFGetRemoteEndPoint(NFSocket __instance, ref EndPoint __result) + { + Log.Debug("NFGetRemoteEndPoint"); + __result = redirect[__instance].RemoteEndPoint; + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "LocalEndPoint", MethodType.Getter)] + private static bool NFGetLocalEndPoint(NFSocket __instance, ref EndPoint __result) + { + Log.Debug("NFGetLocalEndPoint"); + __result = redirect[__instance].LocalEndPoint; + return false; + } +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index e21490e..0cfb009 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -6,7 +6,7 @@ namespace AquaMai.Mods.WorldsLink; -public class NFSocket +public class FutariSocket { private int _bindPort = -1; private readonly FutariClient _client; @@ -20,23 +20,18 @@ public class NFSocket // Each client's remote endpoint must be different public EndPoint RemoteEndPoint { get; private set; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); - public NFSocket(FutariClient client, ProtocolType proto) + public FutariSocket(FutariClient client, ProtocolType proto) { _client = client; _proto = proto; } // Compatibility constructor - public NFSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) : - this(FutariClient.Instance!, protocolType) - { - } + public FutariSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) : + this(FutariClient.Instance, protocolType) { } // ListenSocket.open (TCP) - public void Listen(int backlog) - { - /* Do nothing */ - } + public void Listen(int backlog) { } // ListenSocket.open, UdpRecvSocket.open public void Bind(EndPoint localEndP) @@ -49,14 +44,11 @@ public void Bind(EndPoint localEndP) } // Only used in BroadcastSocket - public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) - { - /* Do nothing */ - } + public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) { } // SocketBase.checkRecvEnable, checkSendEnable // This is the Select step called before blocking calls (e.g. Accept) - public static bool Poll(NFSocket socket, SelectMode mode) + public static bool Poll(FutariSocket socket, SelectMode mode) { Log.Debug("Poll called"); if (mode == SelectMode.SelectRead) @@ -95,7 +87,7 @@ public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) } // Accept is blocking - public NFSocket Accept() + public FutariSocket Accept() { // Check if accept queue has any pending connections if (!_client.acceptQ.TryGetValue(_bindPort, out var q) || @@ -113,7 +105,7 @@ public NFSocket Accept() cmd = Cmd.CTL_TCP_ACCEPT, proto = _proto, sid = msg.sid, dst = msg.src }); - return new NFSocket(_client, _proto) + return new FutariSocket(_client, _proto) { _streamId = msg.sid.Value, RemoteEndPoint = new IPEndPoint(new IPAddress(new IpAddress(msg.src.Value).GetAddressBytes()), @@ -188,8 +180,5 @@ public void Close() _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_TCP_CLOSE, proto = _proto }); } - public void Shutdown(SocketShutdown how) - { - Close(); - } + public void Shutdown(SocketShutdown how) => Close(); } \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index 9a2b083..aa5a383 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Net.Sockets; +using MelonLoader; namespace AquaMai.Mods.WorldsLink; @@ -92,21 +93,26 @@ public abstract class Log // Reset private const string RESET = "\u001b[0m"; + public static void Error(string msg) + { + MelonLogger.Error(RED + "[FUTARI] ERROR " + msg + RESET); + } + public static void Warn(string msg) { - System.Console.WriteLine(YELLOW + "WARN " + msg + RESET); + MelonLogger.Warning(YELLOW + "[FUTARI] WARN " + msg + RESET); } public static void Debug(string msg) { - System.Console.WriteLine(BLUE + "DEBUG " + msg + RESET); + MelonLogger.Msg(BLUE + "[FUTARI] DEBUG " + msg + RESET); } - public static void WriteLine(string msg) + public static void Info(string msg) { if (msg.StartsWith("A001")) msg = MAGENTA + msg; if (msg.StartsWith("A002")) msg = CYAN + msg; msg = msg.Replace("Error", RED + "Error"); - System.Console.WriteLine(msg + RESET); + MelonLogger.Msg("[FUTARI] INFO " + msg + RESET); } } From 1160ec258d90c45828c76f0c77a0ae1ff4cf0de6 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:30:10 -0500 Subject: [PATCH 72/78] [F] Fix patch? --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 9522dc9..8bdeff6 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -2,12 +2,17 @@ using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using AquaMai.Config.Attributes; using HarmonyLib; using MelonLoader; using PartyLink; namespace AquaMai.Mods.WorldsLink; +[ConfigSection( + en: "Enable WorldsLink Multiplayer", + zh: "启用 WorldsLink 多人游戏", + defaultOn: true)] public static class FutariPatch { private static readonly Dictionary redirect = new(); @@ -19,10 +24,10 @@ static FutariPatch() [HarmonyPostfix] [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(AddressFamily), typeof(SocketType), typeof(ProtocolType), typeof(int))] - private static void NFCreate(NFSocket __instance, AddressFamily a1, SocketType a2, ProtocolType a3, int a4) + private static void NFCreate(NFSocket __instance, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) { Log.Debug("new NFSocket(AddressFamily, SocketType, ProtocolType, int)"); - var futari = new FutariSocket(a1, a2, a3, a4); + var futari = new FutariSocket(addressFamily, socketType, protocolType, mockID); redirect.Add(__instance, futari); } From fecb440318e7eab995f5b153e5038ed3f8b5b99d Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:07:29 -0500 Subject: [PATCH 73/78] [F] Fix null --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 424bb02..ac0a778 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -501,7 +501,8 @@ private static void RecruitDataOverride(MusicSelectProcess __instance, ref Recru if (!__instance.IsConnectionFolder() || __result == null) return; var list = PartyMan.GetRecruitListWithoutMe(); - if (!(__instance.CurrentMusicSelect < 0 || __instance.CurrentMusicSelect >= list.Count)) + if (list == null) return; + if (__instance.CurrentMusicSelect >= 0 && __instance.CurrentMusicSelect < list.Count) { __result = list[__instance.CurrentMusicSelect]; } @@ -511,7 +512,10 @@ private static void RecruitDataOverride(MusicSelectProcess __instance, ref Recru [HarmonyPrefix] [HarmonyPatch(typeof(MusicSelectProcess), "IsConnectStart")] - private static bool RecruitDataOverride(MusicSelectProcess __instance, ref bool __result) + private static bool RecruitDataOverride(MusicSelectProcess __instance, + List ____connectCombineMusicDataList, + SubSequence[] ____currentPlayerSubSequence, + ref bool __result) { __result = false; @@ -523,7 +527,7 @@ private static bool RecruitDataOverride(MusicSelectProcess __instance, ref bool var recruit = recruits[0]; Log.Debug($"MusicSelectProcess::IsConnectStart recruit data has been set to {recruit}"); SetRecruitData.Invoke(__instance, [recruit]); - SetConnData.Invoke(__instance, []); + SetConnectData(__instance, ____connectCombineMusicDataList, ____currentPlayerSubSequence); __result = true; } return PrefixRet.BLOCK_ORIGINAL; From 5b4709893686f1a426fe88bf3321d2bca00fbbe8 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:07:38 -0500 Subject: [PATCH 74/78] [F] Fix windows bell sounds --- AquaMai.Mods/WorldsLink/FutariTypes.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index 690cc22..552c5ff 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -127,11 +127,16 @@ public static class Log // Reset public const string RESET = "\u001b[0m"; + // Remove all non-printable characters + private static string Norm(this string msg) => + string.IsNullOrEmpty(msg) ? msg : + new string(msg.Where(ch => !char.IsControl(ch)).ToArray()); + public static void Error(string msg) { lock (_lock) { - MelonLogger.Error($"[FUTARI] {RED}ERROR {RESET}{msg}{RESET}"); + MelonLogger.Error($"[FUTARI] {RED}ERROR {RESET}{msg.Norm()}{RESET}"); } } @@ -139,7 +144,7 @@ public static void Warn(string msg) { lock (_lock) { - MelonLogger.Warning($"[FUTARI] {YELLOW}WARN {RESET}{msg}{RESET}"); + MelonLogger.Warning($"[FUTARI] {YELLOW}WARN {RESET}{msg.Norm()}{RESET}"); } } @@ -148,7 +153,7 @@ public static void Debug(string msg) if (!Futari.Debug) return; lock (_lock) { - MelonLogger.Msg($"[FUTARI] {CYAN}DEBUG {RESET}{msg}{RESET}"); + MelonLogger.Msg($"[FUTARI] {CYAN}DEBUG {RESET}{msg.Norm()}{RESET}"); } } @@ -158,7 +163,7 @@ public static void Info(string msg) { if (msg.StartsWith("A001")) msg = MAGENTA + msg; if (msg.StartsWith("A002")) msg = CYAN + msg; - MelonLogger.Msg($"[FUTARI] {GREEN}INFO {RESET}{msg}{RESET}"); + MelonLogger.Msg($"[FUTARI] {GREEN}INFO {RESET}{msg.Norm()}{RESET}"); } } } From 13e718ba65a5370e10e99cab8e6724931ec2a9de Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:47:33 -0500 Subject: [PATCH 75/78] [+] Copy code --- AquaMai.Mods/WorldsLink/FutariClient.cs | 150 ++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariExt.cs | 34 +++++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 195 ++++++++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariTypes.cs | 76 +++++++++ 4 files changed, 455 insertions(+) create mode 100644 AquaMai.Mods/WorldsLink/FutariClient.cs create mode 100644 AquaMai.Mods/WorldsLink/FutariExt.cs create mode 100644 AquaMai.Mods/WorldsLink/FutariSocket.cs create mode 100644 AquaMai.Mods/WorldsLink/FutariTypes.cs diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs new file mode 100644 index 0000000..7247ec5 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace AquaMai.Mods.WorldsLink; + +public class FutariClient +{ + public static FutariClient Instance { get; set; } + private static readonly JsonSerializerSettings settings = new() + { + NullValueHandling = NullValueHandling.Ignore + }; + + private readonly string _keychip; + private TcpClient _tcpClient; + private StreamWriter _writer; + private StreamReader _reader; + + public readonly ConcurrentQueue sendQ = new(); + // + public readonly ConcurrentDictionary> tcpRecvQ = new(); + // + public readonly ConcurrentDictionary> udpRecvQ = new(); + // + public readonly ConcurrentDictionary> acceptQ = new(); + + private Thread _sendThread; + private Thread _recvThread; + + public IPAddress StubIP => new IPAddress(FutariExt.KeychipToStubIp(_keychip)); + + public FutariClient(string keychip) + { + _keychip = keychip; + Instance = this; + } + + public void Connect(string host, int port) + { + _tcpClient = new TcpClient(); + _tcpClient.Connect(host, port); + + var networkStream = _tcpClient.GetStream(); + _writer = new StreamWriter(networkStream, Encoding.UTF8) { AutoFlush = true }; + _reader = new StreamReader(networkStream, Encoding.UTF8); + + // Register + Send(new Msg { cmd = Cmd.CTL_START, data = _keychip }); + Log.WriteLine($"Connected to server at {host}:{port}"); + + // Start communication and message receiving in separate threads + _sendThread = new Thread(SendThread) { IsBackground = true }; + _recvThread = new Thread(RecvThread) { IsBackground = true }; + + _sendThread.Start(); + _recvThread.Start(); + } + + public void Bind(int port, ProtocolType proto) + { + if (proto == ProtocolType.Tcp) + acceptQ.TryAdd(port, new ConcurrentQueue()); + else if (proto == ProtocolType.Udp) + udpRecvQ.TryAdd(port, new ConcurrentQueue()); + } + + private void SendThread() + { + try + { + long lastHeartbeat = 0; + while (true) + { + var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (time - lastHeartbeat > 1000) + { + Send(new Msg { cmd = Cmd.CTL_HEARTBEAT }); + lastHeartbeat = time; + } + + // Send any data in the send queue + while (sendQ.TryDequeue(out var msg)) + Send(msg); + + Thread.Sleep(10); + } + } + catch (Exception ex) + { + Log.WriteLine($"Error during communication: {ex.Message}"); + } + finally { _tcpClient.Close(); } + } + + private void RecvThread() + { + try + { + while (true) + { + var line = _reader.ReadLine(); + if (line == null) break; + + var message = JsonConvert.DeserializeObject(line); + if (message == null) continue; + HandleIncomingMessage(message); + } + } + catch (Exception ex) + { + Log.WriteLine($"Error receiving messages: {ex.Message}"); + } + finally { _tcpClient.Close(); } + } + + private void HandleIncomingMessage(Msg msg) + { + Log.WriteLine($"{_keychip} <<< {JsonConvert.SerializeObject(msg, settings)}"); + + switch (msg.cmd) + { + // UDP message + case Cmd.DATA_SEND or Cmd.DATA_BROADCAST when msg.proto == ProtocolType.Udp && msg.dPort != null: + udpRecvQ.Get(msg.dPort.Value)?.Enqueue(msg); + break; + + // TCP connection + case Cmd.DATA_SEND when msg.proto == ProtocolType.Tcp && msg.sid != null: + tcpRecvQ.Get(msg.sid.Value)?.Enqueue(msg); + break; + + // TCP connection accepted + case Cmd.CTL_TCP_CONNECT when msg.dPort != null: + acceptQ.Get(msg.dPort.Value)?.Enqueue(msg); + break; + } + } + + private void Send(Msg msg) + { + var json = JsonConvert.SerializeObject(msg, settings); + _writer.WriteLine(json); + Log.WriteLine($"{_keychip} >>> {json}"); + } +} diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs new file mode 100644 index 0000000..c91b1a4 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -0,0 +1,34 @@ +#nullable enable +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace AquaMai.Mods.WorldsLink; + +public static class FutariExt +{ + public static uint KeychipToStubIp(string keychip) + { + return uint.Parse("1" + keychip.Substring(2)); + } + + public static byte[] View(this byte[] buffer, int offset, int size) + { + var array = new byte[size]; + Array.Copy(buffer, offset, array, 0, size); + return array; + } + + public static V? Get(this ConcurrentDictionary dict, K key) where V : class + { + return dict.GetValueOrDefault(key); + } + + // Call a function using reflection + public static void Call(this object obj, string method, params object[] args) + { + obj.GetType().GetMethod(method)?.Invoke(obj, args); + } + + public static uint MyStubIP() => KeychipToStubIp(AMDaemon.System.KeychipId.ShortValue); +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs new file mode 100644 index 0000000..3392b61 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using PartyLink; + +namespace AquaMai.Mods.WorldsLink; + +public class NFSocket +{ + private int _bindPort = -1; + private readonly FutariClient _client; + private readonly ProtocolType _proto; + private int _streamId = -1; + + // ConnectSocket.Enter_Active (doesn't seem to be actually used) + public EndPoint LocalEndPoint { get; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); + + // ConnectSocket.Enter_Active, ListenSocket.acceptClient (TCP) + // Each client's remote endpoint must be different + public EndPoint RemoteEndPoint { get; private set; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); + + public NFSocket(FutariClient client, ProtocolType proto) + { + _client = client; + _proto = proto; + } + + // Compatibility constructor + public NFSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) : + this(FutariClient.Instance!, protocolType) + { + } + + // ListenSocket.open (TCP) + public void Listen(int backlog) + { + /* Do nothing */ + } + + // ListenSocket.open, UdpRecvSocket.open + public void Bind(EndPoint localEndP) + { + if (localEndP is not IPEndPoint ipEndP) return; + _bindPort = ipEndP.Port; + _client.Bind(_bindPort, _proto); + _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_BIND, proto = _proto, + src = ipEndP.Address.ToNetworkByteOrderU32(), sPort = ipEndP.Port }); + } + + // Only used in BroadcastSocket + public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) + { + /* Do nothing */ + } + + // SocketBase.checkRecvEnable, checkSendEnable + // This is the Select step called before blocking calls (e.g. Accept) + public static bool Poll(NFSocket socket, SelectMode mode) + { + Log.Debug("Poll called"); + if (mode == SelectMode.SelectRead) + { + return (socket._proto == ProtocolType.Udp) // Check is UDP + ? !socket._client.udpRecvQ.Get(socket._bindPort)?.IsEmpty ?? false + : (socket._streamId == -1) // Check is TCP stream or TCP server + ? !socket._client.acceptQ.Get(socket._bindPort)?.IsEmpty ?? false + : !socket._client.tcpRecvQ.Get(socket._streamId)?.IsEmpty ?? false; + } + // Write is always ready? + return mode == SelectMode.SelectWrite; + } + + // ConnectSocket.Enter_Connect (TCP) + // The destination address is obtained from a RecruitInfo packet sent by the host. + // The host should patch their PartyLink.Util.MyIpAddress() to return a mock address instead of the real one + // Returns true if IO is pending and false if the operation completed synchronously + // When it's false, e will contain the result of the operation + public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) + { + if (e.RemoteEndPoint is not IPEndPoint ipEndP) return false; + _streamId = new Random().Next(); + _client.tcpRecvQ[_streamId] = new ConcurrentQueue(); + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.CTL_TCP_CONNECT, + proto = _proto, + sid = _streamId, + dst = ipEndP.Address.ToNetworkByteOrderU32(), + dPort = ipEndP.Port + }); + // It is very annoying to call Complete event using reflection + // So we'll just pretend that the client has ACKed + return false; + } + + // Accept is blocking + public NFSocket Accept() + { + // Check if accept queue has any pending connections + if (!_client.acceptQ.TryGetValue(_bindPort, out var q) || + !q.TryDequeue(out var msg) || + msg.sid == null || + msg.src == null) + { + Log.Warn("Accept: No pending connections"); + return null; + } + + _client.tcpRecvQ[msg.sid.Value] = new ConcurrentQueue(); + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.CTL_TCP_ACCEPT, proto = _proto, sid = msg.sid, dst = msg.src + }); + + return new NFSocket(_client, _proto) + { + _streamId = msg.sid.Value, + RemoteEndPoint = new IPEndPoint(new IPAddress(new IpAddress(msg.src.Value).GetAddressBytes()), + _bindPort) + }; + } + + public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) + { + Log.Debug($"Send: {size} bytes"); + // Remote EP is not relevant here, because the stream is already established, + // there can only be one remote endpoint + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size), + sid = _streamId == -1 ? null : _streamId + }); + return size; + } + + // Only used in BroadcastSocket + public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) + { + Log.Debug($"SendTo: {size} bytes"); + if (remoteEP is not IPEndPoint ipEndP) return 0; + _client.sendQ.Enqueue(new Msg + { + cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size), dPort = ipEndP.Port + }); + return size; + } + + // Only used in TCP ConnectSocket + public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode) + { + Log.Debug("Receive called"); + if (!_client.tcpRecvQ.TryGetValue(_streamId, out var q) || + !q.TryDequeue(out var msg)) + { + Log.Warn("Receive: No data to receive"); + errorCode = SocketError.WouldBlock; + return 0; + } + var data = Convert.FromBase64String((string) msg.data!); + + Buffer.BlockCopy(data, 0, buffer, 0, data.Length); + errorCode = SocketError.Success; + return data.Length; + } + + // Only used in UdpRecvSocket to receive from 0 (broadcast) + public int ReceiveFrom(byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP) + { + Log.Debug("ReceiveFrom called"); + if (!_client.udpRecvQ.TryGetValue(_bindPort, out var q) || + !q.TryDequeue(out var msg)) + { + Log.Warn("ReceiveFrom: No data to receive"); + return 0; + } + var data = Convert.FromBase64String((string) msg.data!); + + Buffer.BlockCopy(data, 0, buffer, 0, data.Length); + return data.Length; + } + + // Called everywhere, but only relevant for TCP + public void Close() + { + // TCP FIN/RST + if (_proto == ProtocolType.Tcp) + _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_TCP_CLOSE, proto = _proto }); + } + + public void Shutdown(SocketShutdown how) + { + Close(); + } +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs new file mode 100644 index 0000000..db8120a --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -0,0 +1,76 @@ +using System.Net.Sockets; + +namespace AquaMai.Mods.WorldsLink; + +public enum Cmd +{ + // Control plane + CTL_START = 1, + CTL_BIND = 2, + CTL_HEARTBEAT = 3, + CTL_TCP_CONNECT = 4, // Accept a new multiplexed TCP stream + CTL_TCP_ACCEPT = 5, + CTL_TCP_ACCEPT_ACK = 6, + CTL_TCP_CLOSE = 7, + + // Data plane + DATA_SEND = 21, + DATA_BROADCAST = 22, +} + + +public class Msg +{ + public Cmd cmd { get; set; } + public ProtocolType? proto { get; set; } + public int? sid { get; set; } + public uint? src { get; set; } + public int? sPort { get; set; } + public uint? dst { get; set; } + public int? dPort { get; set; } + public object? data { get; set; } +} + +public abstract class Log +{ + // Text colors + private const string BLACK = "\u001b[30m"; + private const string RED = "\u001b[31m"; + private const string GREEN = "\u001b[32m"; + private const string YELLOW = "\u001b[33m"; + private const string BLUE = "\u001b[34m"; + private const string MAGENTA = "\u001b[35m"; + private const string CYAN = "\u001b[36m"; + private const string WHITE = "\u001b[37m"; + + // Bright text colors + private const string BRIGHT_BLACK = "\u001b[90m"; + private const string BRIGHT_RED = "\u001b[91m"; + private const string BRIGHT_GREEN = "\u001b[92m"; + private const string BRIGHT_YELLOW = "\u001b[93m"; + private const string BRIGHT_BLUE = "\u001b[94m"; + private const string BRIGHT_MAGENTA = "\u001b[95m"; + private const string BRIGHT_CYAN = "\u001b[96m"; + private const string BRIGHT_WHITE = "\u001b[97m"; + + // Reset + private const string RESET = "\u001b[0m"; + + public static void Warn(string msg) + { + System.Console.WriteLine(YELLOW + "WARN " + msg + RESET); + } + + public static void Debug(string msg) + { + System.Console.WriteLine(BLUE + "DEBUG " + msg + RESET); + } + + public static void WriteLine(string msg) + { + if (msg.StartsWith("A001")) msg = MAGENTA + msg; + if (msg.StartsWith("A002")) msg = CYAN + msg; + msg = msg.Replace("Error", RED + "Error"); + System.Console.WriteLine(msg + RESET); + } +} From 1b13fa04dd35d7414355e6c37a8ee1302cd3d8e5 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:49:08 -0500 Subject: [PATCH 76/78] [-] Remove json and use manual serailization --- AquaMai.Mods/WorldsLink/FutariClient.cs | 14 ++----- AquaMai.Mods/WorldsLink/FutariExt.cs | 5 +++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 8 ++-- AquaMai.Mods/WorldsLink/FutariTypes.cs | 54 ++++++++++++++++++++----- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index 7247ec5..cfcbff0 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -11,10 +11,6 @@ namespace AquaMai.Mods.WorldsLink; public class FutariClient { public static FutariClient Instance { get; set; } - private static readonly JsonSerializerSettings settings = new() - { - NullValueHandling = NullValueHandling.Ignore - }; private readonly string _keychip; private TcpClient _tcpClient; @@ -106,8 +102,7 @@ private void RecvThread() var line = _reader.ReadLine(); if (line == null) break; - var message = JsonConvert.DeserializeObject(line); - if (message == null) continue; + var message = Msg.FromString(line); HandleIncomingMessage(message); } } @@ -120,7 +115,7 @@ private void RecvThread() private void HandleIncomingMessage(Msg msg) { - Log.WriteLine($"{_keychip} <<< {JsonConvert.SerializeObject(msg, settings)}"); + Log.WriteLine($"{_keychip} <<< {msg}"); switch (msg.cmd) { @@ -143,8 +138,7 @@ private void HandleIncomingMessage(Msg msg) private void Send(Msg msg) { - var json = JsonConvert.SerializeObject(msg, settings); - _writer.WriteLine(json); - Log.WriteLine($"{_keychip} >>> {json}"); + _writer.WriteLine(msg); + Log.WriteLine($"{_keychip} >>> {msg}"); } } diff --git a/AquaMai.Mods/WorldsLink/FutariExt.cs b/AquaMai.Mods/WorldsLink/FutariExt.cs index c91b1a4..7bc2365 100644 --- a/AquaMai.Mods/WorldsLink/FutariExt.cs +++ b/AquaMai.Mods/WorldsLink/FutariExt.cs @@ -11,6 +11,8 @@ public static uint KeychipToStubIp(string keychip) { return uint.Parse("1" + keychip.Substring(2)); } + + public static R Let(this T x, Func f) => f(x); public static byte[] View(this byte[] buffer, int offset, int size) { @@ -19,6 +21,9 @@ public static byte[] View(this byte[] buffer, int offset, int size) return array; } + public static string B64(this byte[] buffer) => Convert.ToBase64String(buffer); + public static byte[] B64(this string str) => Convert.FromBase64String(str); + public static V? Get(this ConcurrentDictionary dict, K key) where V : class { return dict.GetValueOrDefault(key); diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index 3392b61..e21490e 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -128,7 +128,7 @@ public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) // there can only be one remote endpoint _client.sendQ.Enqueue(new Msg { - cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size), + cmd = Cmd.DATA_SEND, proto = _proto, data = buffer.View(offset, size).B64(), sid = _streamId == -1 ? null : _streamId }); return size; @@ -141,7 +141,7 @@ public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, if (remoteEP is not IPEndPoint ipEndP) return 0; _client.sendQ.Enqueue(new Msg { - cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size), dPort = ipEndP.Port + cmd = Cmd.DATA_BROADCAST, proto = _proto, data = buffer.View(offset, size).B64(), dPort = ipEndP.Port }); return size; } @@ -157,7 +157,7 @@ public int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags, errorCode = SocketError.WouldBlock; return 0; } - var data = Convert.FromBase64String((string) msg.data!); + var data = msg.data!.B64(); Buffer.BlockCopy(data, 0, buffer, 0, data.Length); errorCode = SocketError.Success; @@ -174,7 +174,7 @@ public int ReceiveFrom(byte[] buffer, SocketFlags socketFlags, ref EndPoint remo Log.Warn("ReceiveFrom: No data to receive"); return 0; } - var data = Convert.FromBase64String((string) msg.data!); + var data = msg.data!.B64(); Buffer.BlockCopy(data, 0, buffer, 0, data.Length); return data.Length; diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index db8120a..9a2b083 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using System.Net.Sockets; namespace AquaMai.Mods.WorldsLink; @@ -19,16 +21,50 @@ public enum Cmd } -public class Msg +public struct Msg { - public Cmd cmd { get; set; } - public ProtocolType? proto { get; set; } - public int? sid { get; set; } - public uint? src { get; set; } - public int? sPort { get; set; } - public uint? dst { get; set; } - public int? dPort { get; set; } - public object? data { get; set; } + public Cmd cmd; + public ProtocolType? proto; + public int? sid; + public uint? src; + public int? sPort; + public uint? dst; + public int? dPort; + public string? data; + + public override string ToString() + { + int? proto_ = proto == null ? null : (int) proto; + var arr = new object[] { + 1, (int) cmd, proto_, sid, src, sPort, dst, dPort, + null, null, null, null, null, null, null, null, // reserved for future use + data + }; + + // Map nulls to empty strings + return string.Join(",", arr.Select(x => x ?? "")).TrimEnd(','); + } + + private static T? Parse(string[] fields, int i) where T : struct + => fields.Length <= i || fields[i] == "" ? null + : (T) Convert.ChangeType(fields[i], typeof(T)); + + + public static Msg FromString(string str) + { + var fields = str.Split(','); + return new Msg + { + cmd = (Cmd) (Parse(fields, 1) ?? throw new InvalidOperationException("cmd is required")), + proto = Parse(fields, 2)?.Let(it => (ProtocolType) it), + sid = Parse(fields, 3), + src = Parse(fields, 4), + sPort = Parse(fields, 5), + dst = Parse(fields, 6), + dPort = Parse(fields, 7), + data = string.Join(",", fields.Skip(16)) + }; + } } public abstract class Log From cfab72c83e7eeae2c4e26b73033df5c8adcd2162 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:17:50 -0500 Subject: [PATCH 77/78] [+] Patch? --- AquaMai.Mods/WorldsLink/FutariClient.cs | 10 +- AquaMai.Mods/WorldsLink/FutariPatch.cs | 164 ++++++++++++++++++++++++ AquaMai.Mods/WorldsLink/FutariSocket.cs | 31 ++--- AquaMai.Mods/WorldsLink/FutariTypes.cs | 14 +- 4 files changed, 189 insertions(+), 30 deletions(-) create mode 100644 AquaMai.Mods/WorldsLink/FutariPatch.cs diff --git a/AquaMai.Mods/WorldsLink/FutariClient.cs b/AquaMai.Mods/WorldsLink/FutariClient.cs index cfcbff0..fda7e75 100644 --- a/AquaMai.Mods/WorldsLink/FutariClient.cs +++ b/AquaMai.Mods/WorldsLink/FutariClient.cs @@ -47,7 +47,7 @@ public void Connect(string host, int port) // Register Send(new Msg { cmd = Cmd.CTL_START, data = _keychip }); - Log.WriteLine($"Connected to server at {host}:{port}"); + Log.Info($"Connected to server at {host}:{port}"); // Start communication and message receiving in separate threads _sendThread = new Thread(SendThread) { IsBackground = true }; @@ -88,7 +88,7 @@ private void SendThread() } catch (Exception ex) { - Log.WriteLine($"Error during communication: {ex.Message}"); + Log.Info($"Error during communication: {ex.Message}"); } finally { _tcpClient.Close(); } } @@ -108,14 +108,14 @@ private void RecvThread() } catch (Exception ex) { - Log.WriteLine($"Error receiving messages: {ex.Message}"); + Log.Info($"Error receiving messages: {ex.Message}"); } finally { _tcpClient.Close(); } } private void HandleIncomingMessage(Msg msg) { - Log.WriteLine($"{_keychip} <<< {msg}"); + Log.Info($"{_keychip} <<< {msg}"); switch (msg.cmd) { @@ -139,6 +139,6 @@ private void HandleIncomingMessage(Msg msg) private void Send(Msg msg) { _writer.WriteLine(msg); - Log.WriteLine($"{_keychip} >>> {msg}"); + Log.Info($"{_keychip} >>> {msg}"); } } diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs new file mode 100644 index 0000000..9522dc9 --- /dev/null +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using HarmonyLib; +using MelonLoader; +using PartyLink; + +namespace AquaMai.Mods.WorldsLink; + +public static class FutariPatch +{ + private static readonly Dictionary redirect = new(); + + static FutariPatch() + { + new FutariClient(AMDaemon.System.KeychipId.ShortValue).Connect("violet", 20101); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(AddressFamily), typeof(SocketType), typeof(ProtocolType), typeof(int))] + private static void NFCreate(NFSocket __instance, AddressFamily a1, SocketType a2, ProtocolType a3, int a4) + { + Log.Debug("new NFSocket(AddressFamily, SocketType, ProtocolType, int)"); + var futari = new FutariSocket(a1, a2, a3, a4); + redirect.Add(__instance, futari); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(Socket))] + private static void NFCreate2(NFSocket __instance, Socket nfSocket) + { + Log.Warn("new NFSocket(Socket) -- We shouldn't get here."); + throw new NotImplementedException(); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Poll")] + private static bool NFPoll(NFSocket socket, SelectMode mode, ref bool __result) + { + Log.Debug("NFPoll"); + FutariSocket.Poll(redirect[socket], mode); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Send")] + private static bool NFSend(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags) + { + Log.Debug("NFSend"); + redirect[__instance].Send(buffer, offset, size, socketFlags); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "SendTo")] + private static bool NFSendTo(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP) + { + Log.Debug("NFSendTo"); + redirect[__instance].SendTo(buffer, offset, size, socketFlags, remoteEP); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Receive")] + private static bool NFReceive(NFSocket __instance, byte[] buffer, int offset, int size, SocketFlags socketFlags, out SocketError errorCode) + { + Log.Debug("NFReceive"); + redirect[__instance].Receive(buffer, offset, size, socketFlags, out errorCode); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "ReceiveFrom")] + private static bool NFReceiveFrom(NFSocket __instance, byte[] buffer, SocketFlags socketFlags, ref EndPoint remoteEP) + { + Log.Debug("NFReceiveFrom"); + redirect[__instance].ReceiveFrom(buffer, socketFlags, ref remoteEP); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Bind")] + private static bool NFBind(NFSocket __instance, EndPoint localEndP) + { + Log.Debug("NFBind"); + redirect[__instance].Bind(localEndP); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Listen")] + private static bool NFListen(NFSocket __instance, int backlog) + { + Log.Debug("NFListen"); + redirect[__instance].Listen(backlog); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Accept")] + private static bool NFAccept(NFSocket __instance, ref NFSocket __result) + { + Log.Debug("NFAccept"); + var futariSocket = redirect[__instance].Accept(); + var mockSocket = new NFSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp, 0); + redirect.Add(mockSocket, futariSocket); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "ConnectAsync")] + private static bool NFConnectAsync(NFSocket __instance, SocketAsyncEventArgs e, int mockID) + { + Log.Debug("NFConnectAsync"); + redirect[__instance].ConnectAsync(e, mockID); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "SetSocketOption")] + private static bool NFSetSocketOption(NFSocket __instance, SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue) + { + Log.Debug("NFSetSocketOption"); + redirect[__instance].SetSocketOption(optionLevel, optionName, optionValue); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Close")] + private static bool NFClose(NFSocket __instance) + { + Log.Debug("NFClose"); + redirect[__instance].Close(); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "Shutdown")] + private static bool NFShutdown(NFSocket __instance, SocketShutdown how) + { + Log.Debug("NFShutdown"); + redirect[__instance].Shutdown(how); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "RemoteEndPoint", MethodType.Getter)] + private static bool NFGetRemoteEndPoint(NFSocket __instance, ref EndPoint __result) + { + Log.Debug("NFGetRemoteEndPoint"); + __result = redirect[__instance].RemoteEndPoint; + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NFSocket), "LocalEndPoint", MethodType.Getter)] + private static bool NFGetLocalEndPoint(NFSocket __instance, ref EndPoint __result) + { + Log.Debug("NFGetLocalEndPoint"); + __result = redirect[__instance].LocalEndPoint; + return false; + } +} \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariSocket.cs b/AquaMai.Mods/WorldsLink/FutariSocket.cs index e21490e..0cfb009 100644 --- a/AquaMai.Mods/WorldsLink/FutariSocket.cs +++ b/AquaMai.Mods/WorldsLink/FutariSocket.cs @@ -6,7 +6,7 @@ namespace AquaMai.Mods.WorldsLink; -public class NFSocket +public class FutariSocket { private int _bindPort = -1; private readonly FutariClient _client; @@ -20,23 +20,18 @@ public class NFSocket // Each client's remote endpoint must be different public EndPoint RemoteEndPoint { get; private set; } = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0); - public NFSocket(FutariClient client, ProtocolType proto) + public FutariSocket(FutariClient client, ProtocolType proto) { _client = client; _proto = proto; } // Compatibility constructor - public NFSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) : - this(FutariClient.Instance!, protocolType) - { - } + public FutariSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) : + this(FutariClient.Instance, protocolType) { } // ListenSocket.open (TCP) - public void Listen(int backlog) - { - /* Do nothing */ - } + public void Listen(int backlog) { } // ListenSocket.open, UdpRecvSocket.open public void Bind(EndPoint localEndP) @@ -49,14 +44,11 @@ public void Bind(EndPoint localEndP) } // Only used in BroadcastSocket - public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) - { - /* Do nothing */ - } + public void SetSocketOption(SocketOptionLevel l, SocketOptionName n, bool o) { } // SocketBase.checkRecvEnable, checkSendEnable // This is the Select step called before blocking calls (e.g. Accept) - public static bool Poll(NFSocket socket, SelectMode mode) + public static bool Poll(FutariSocket socket, SelectMode mode) { Log.Debug("Poll called"); if (mode == SelectMode.SelectRead) @@ -95,7 +87,7 @@ public bool ConnectAsync(SocketAsyncEventArgs e, int mockID) } // Accept is blocking - public NFSocket Accept() + public FutariSocket Accept() { // Check if accept queue has any pending connections if (!_client.acceptQ.TryGetValue(_bindPort, out var q) || @@ -113,7 +105,7 @@ public NFSocket Accept() cmd = Cmd.CTL_TCP_ACCEPT, proto = _proto, sid = msg.sid, dst = msg.src }); - return new NFSocket(_client, _proto) + return new FutariSocket(_client, _proto) { _streamId = msg.sid.Value, RemoteEndPoint = new IPEndPoint(new IPAddress(new IpAddress(msg.src.Value).GetAddressBytes()), @@ -188,8 +180,5 @@ public void Close() _client.sendQ.Enqueue(new Msg { cmd = Cmd.CTL_TCP_CLOSE, proto = _proto }); } - public void Shutdown(SocketShutdown how) - { - Close(); - } + public void Shutdown(SocketShutdown how) => Close(); } \ No newline at end of file diff --git a/AquaMai.Mods/WorldsLink/FutariTypes.cs b/AquaMai.Mods/WorldsLink/FutariTypes.cs index 9a2b083..aa5a383 100644 --- a/AquaMai.Mods/WorldsLink/FutariTypes.cs +++ b/AquaMai.Mods/WorldsLink/FutariTypes.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Net.Sockets; +using MelonLoader; namespace AquaMai.Mods.WorldsLink; @@ -92,21 +93,26 @@ public abstract class Log // Reset private const string RESET = "\u001b[0m"; + public static void Error(string msg) + { + MelonLogger.Error(RED + "[FUTARI] ERROR " + msg + RESET); + } + public static void Warn(string msg) { - System.Console.WriteLine(YELLOW + "WARN " + msg + RESET); + MelonLogger.Warning(YELLOW + "[FUTARI] WARN " + msg + RESET); } public static void Debug(string msg) { - System.Console.WriteLine(BLUE + "DEBUG " + msg + RESET); + MelonLogger.Msg(BLUE + "[FUTARI] DEBUG " + msg + RESET); } - public static void WriteLine(string msg) + public static void Info(string msg) { if (msg.StartsWith("A001")) msg = MAGENTA + msg; if (msg.StartsWith("A002")) msg = CYAN + msg; msg = msg.Replace("Error", RED + "Error"); - System.Console.WriteLine(msg + RESET); + MelonLogger.Msg("[FUTARI] INFO " + msg + RESET); } } From 0b694a71b72a2b115e646738dae2ad223626ab70 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:30:10 -0500 Subject: [PATCH 78/78] [F] Fix patch? --- AquaMai.Mods/WorldsLink/FutariPatch.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/WorldsLink/FutariPatch.cs b/AquaMai.Mods/WorldsLink/FutariPatch.cs index 9522dc9..8bdeff6 100644 --- a/AquaMai.Mods/WorldsLink/FutariPatch.cs +++ b/AquaMai.Mods/WorldsLink/FutariPatch.cs @@ -2,12 +2,17 @@ using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using AquaMai.Config.Attributes; using HarmonyLib; using MelonLoader; using PartyLink; namespace AquaMai.Mods.WorldsLink; +[ConfigSection( + en: "Enable WorldsLink Multiplayer", + zh: "启用 WorldsLink 多人游戏", + defaultOn: true)] public static class FutariPatch { private static readonly Dictionary redirect = new(); @@ -19,10 +24,10 @@ static FutariPatch() [HarmonyPostfix] [HarmonyPatch(typeof(NFSocket), MethodType.Constructor, typeof(AddressFamily), typeof(SocketType), typeof(ProtocolType), typeof(int))] - private static void NFCreate(NFSocket __instance, AddressFamily a1, SocketType a2, ProtocolType a3, int a4) + private static void NFCreate(NFSocket __instance, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, int mockID) { Log.Debug("new NFSocket(AddressFamily, SocketType, ProtocolType, int)"); - var futari = new FutariSocket(a1, a2, a3, a4); + var futari = new FutariSocket(addressFamily, socketType, protocolType, mockID); redirect.Add(__instance, futari); }