From 734de2a9ac2b0ba487552fcebf45f47493b47259 Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Wed, 10 Jan 2024 11:00:04 +0800 Subject: [PATCH 1/9] Login with yggdrasil server 'hitmc.cc' will get ``` Login failed : Invalid server response. ``` Print the raw response which is `result` in [/MinecraftClient/Protocol/ProtocolHandler.cs#L554](https://github.com/oldkingOK/Minecraft-Console-Client/blob/f6797cb4b5f8989d16382d396dfc7ca69f1d6da2/MinecraftClient/Protocol/ProtocolHandler.cs#L554) (Every test shows like this) ``` HTTP/1.1 200 OK ... 1e1 {"accessToken":"...","clientToken":"...","availableProfiles":[{"id":"..","name":".."},{"id":"..","na f me":"ok_bot"}]} 0 ``` After splited by line: - 1e1 - {"accessToken": ... ,"na - f - me":"ok_bot"}]} - 0 The response when Login with 'littleskin.cn' which works fine is: ``` HTTP/1.1 200 OK ... 1e1 {"accessToken":"...","clientToken":"...","availableProfiles":[{"id":"..","name":".."},{"id":"..","name":"ok_bot"}]} 0 ``` After splited by line: - 1e1 - {"accessToken": ... ,"name":"ok_bot"}]} - 0 - - So adding [1] and [3] will make both 'hitmc.cc' and 'littleskin.cn' work fine. --- MinecraftClient/Protocol/ProtocolHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index 92e53ce25d..ee47347210 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -1100,7 +1100,8 @@ private static int DoHTTPSRequest(List headers, string host,int port, re statusCode = int.Parse(raw_result.Split(' ')[1], NumberStyles.Any, CultureInfo.CurrentCulture); if (statusCode != 204) { - postResult = raw_result[(raw_result.IndexOf("\r\n\r\n") + 4)..].Split("\r\n")[1]; + var splited = raw_result[(raw_result.IndexOf("\r\n\r\n") + 4)..].Split("\r\n"); + postResult = splited[1] + splited[3]; } else { From 644014e42fb51875007f3a788885063955977288 Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Wed, 10 Jan 2024 17:48:03 +0800 Subject: [PATCH 2/9] Add QQbot scripts and python script --- MinecraftClient/ChatBots/OkWsBot.cs | 152 +++++++++++++++++++++++++ MinecraftClient/McClient.cs | 2 + MinecraftClient/MinecraftClient.csproj | 1 + MinecraftClient/Settings.cs | 7 ++ py-server.py | 114 +++++++++++++++++++ 5 files changed, 276 insertions(+) create mode 100644 MinecraftClient/ChatBots/OkWsBot.cs create mode 100644 py-server.py diff --git a/MinecraftClient/ChatBots/OkWsBot.cs b/MinecraftClient/ChatBots/OkWsBot.cs new file mode 100644 index 0000000000..431c3fcd3f --- /dev/null +++ b/MinecraftClient/ChatBots/OkWsBot.cs @@ -0,0 +1,152 @@ +using System; +using System.Net; +using System.Text; +using MinecraftClient.Scripting; +using WebSocketSharp; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System.Linq; +using Tomlet.Attributes; +using MinecraftClient.Mapping; +using static MinecraftClient.Settings; +using System.Threading.Tasks; + +namespace MinecraftClient.OkBots; +public class OkWsBot : ChatBot +{ + WebSocket? pyWebSocketServer; + protected static OkWsBot? chatBot; + + public static Configs Config = new (); + + [TomlDoNotInlineObject] + public class Configs { + public bool Enabled = true; + + [TomlInlineComment("群号")] + public string id = "12345678"; + [TomlInlineComment("Python Websocket接口")] + public string pythonSendWsApi = "ws://127.0.0.1:12345"; + [TomlInlineComment("在群内显示的服务器名称")] + public string serverName = "1.20"; + } + + public override void Initialize() + { + chatBot = this; + InitDeathMsgs(); + + pyWebSocketServer = new WebSocket (Config.pythonSendWsApi); + pyWebSocketServer.OnMessage += (sender, e) => { + var msg = e.Data; + Console.WriteLine("We get msg:" + msg); + if (msg == "#!list") { + pyWebSocketServer.Send("#!list" + GetPlayerListMsg()); + return; + } + SendToConsole(msg); + }; + pyWebSocketServer.Connect (); + + LogToConsole("OkQQbot has been initialized!"); + } + + public override void GetText(string text) + { + string message = ""; + string username = ""; + string serverPrefix = "["+ Config.serverName +"] "; + text = GetVerbatim(text); + + if (IsChatMessage(text, ref message, ref username)) + { + if (username == GetUsername()) return; + string msg = serverPrefix + text; + SendToQQ(msg); + return; + } + + if (IsAchievementMsg(text)) { + if (text.StartsWith("bot_")) return; + string msg = serverPrefix + "<喜报> " + text; + SendToQQ(msg); + return; + } + + if (IsDeathMsg(text)) { + if (text.StartsWith("bot_")) return; + string msg = serverPrefix + "<悲报> " + text; + SendToQQ(msg); + return; + } + + if (IsLoginLogoutMsg(text)) { + if (text.StartsWith("bot_")) return; + SendToQQ(serverPrefix + text); + } + } + + public void SendToConsole(string msg) { + base.SendText(msg); + } + + public void SendToQQ(string msg) { + if (!pyWebSocketServer!.Ping()) { + Console.WriteLine("Onebot Connection down, reconnecting..."); + pyWebSocketServer = new WebSocket (Config.pythonSendWsApi); + pyWebSocketServer.Connect (); + } + pyWebSocketServer.Send (msg); + } + + public string GetPlayerListMsg() { + string result = "["+Config.serverName+"]"; + string[] playerStrs = GetOnlinePlayers(); + if (playerStrs.Length == 0) { + return result += " [鬼服]\n没有玩家在线"; + } + result += " 在线玩家:"; + foreach (var playerStr in playerStrs) + { + result += "\n" + playerStr; + } + return result; + } + + public bool IsLoginLogoutMsg(string msg) { + return msg.EndsWith(" joined the game") || + msg.EndsWith(" left the game"); + } + + public bool IsAchievementMsg(string msg) { + return msg.IndexOf(" has made the advancement ") != -1; + } + + private static List DeathMsgs = new(); + public void InitDeathMsgs() { + string translationsStr = Encoding.ASCII.GetString((byte[])MinecraftAssets.ResourceManager.GetObject("en_us.json")!); + JObject translations = JObject.Parse(translationsStr); + var enumerator = translations.GetEnumerator(); + while (enumerator.MoveNext()) { + if (!enumerator.Current.Key.StartsWith("death.")) continue; + string result = enumerator.Current.Value!.ToString(); + result = result.Replace("%1$s ", ""); + + var index2s = result.IndexOf("%2$s"); + if (index2s != -1) { + result = result.Substring(0,index2s); + } + DeathMsgs.Add(result); + } + } + public bool IsDeathMsg(string msg) { + foreach (var item in DeathMsgs) + { + if (msg.IndexOf(item) != -1) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index c7296d52a3..1096252415 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -7,6 +7,7 @@ using Brigadier.NET; using Brigadier.NET.Exceptions; using MinecraftClient.ChatBots; +using MinecraftClient.OkBots; using MinecraftClient.CommandHandler; using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Commands; @@ -298,6 +299,7 @@ private void RegisterBots(bool reload = false) if (Config.ChatBot.TelegramBridge.Enabled) { BotLoad(new TelegramBridge()); } if (Config.ChatBot.ItemsCollector.Enabled) { BotLoad(new ItemsCollector()); } if (Config.ChatBot.WebSocketBot.Enabled) { BotLoad(new WebSocketBot()); } + if (Config.ChatBot.OkWsBot.Enabled) { BotLoad(new OkWsBot()); } //Add your ChatBot here by uncommenting and adapting //BotLoad(new ChatBots.YourBot()); } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index cf999ca210..faaed5fa95 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -45,6 +45,7 @@ + diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 7982b057fe..3a9021c319 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -1440,6 +1440,13 @@ public ChatBots.WebSocketBot.Configs WebSocketBot get { return ChatBots.WebSocketBot.Config!; } set { ChatBots.WebSocketBot.Config = value; } } + + [TomlPrecedingComment("Ok的QQbot")] + public OkBots.OkWsBot.Configs OkWsBot + { + get { return OkBots.OkWsBot.Config!; } + set { OkBots.OkWsBot.Config = value; } + } } } diff --git a/py-server.py b/py-server.py new file mode 100644 index 0000000000..80c7dfee96 --- /dev/null +++ b/py-server.py @@ -0,0 +1,114 @@ +from websocket_server import WebsocketServer +import logging +import requests +import json + +# 群号 +GROUP_ID = 12345678 +# Onebot 发送群聊消息的节点 +QQ_BOT_API = f"http://127.0.0.1:5700/send_group_msg?group_id={GROUP_ID}&message=" +# 用来查询玩家列表的关键词 +LIST_WORD = "#!list" +# 第一个连接websocket的一定要是onebot +onebot_id = -1 +# 已关闭的连接列表 +closed_list = {-1} + +def new_client(client, server): + client_id = client["id"] + client_addr = client["address"] + global onebot_id + if (onebot_id == -1): + print("Onebot client join us, you can start mcc client now.") + onebot_id = client["id"] + else: + print("New mcc client join us, ") + + print(f"id:{client_id}, address:{client_addr}") + +def send_to_qq(msg): + requests.get(QQ_BOT_API + msg) + +def on_client_closed(client, server): + global closed_list + closed_list.add(client["id"]) + +def on_get_msg(client, server, message): + client_id = client["id"] + print(f"client id: {client_id}, message: {message}") + + if (message.startswith(LIST_WORD)): + send_to_qq(message[len(LIST_WORD):]) + return + + if (message.startswith("{")): + handle_qq_json(message) + return + + # 如果不是 #!list 消息和 qq消息,那就是服里发来的消息 + # 发到其他服 + send_to_mccs(message, client, True) + + # 发到qq + send_to_qq(message) + +def handle_qq_json(msg): + + qq_json = json.loads(msg) + if (qq_json["post_type"] != "message"):return + if (qq_json["message_type"] != "group"):return + if (qq_json["sub_type"] != "normal"):return + if (qq_json["group_id"] != GROUP_ID):return + + sender = qq_json["sender"]["nickname"] + result = "" + """ + Message example: + "message":[ + {"data":{"text":"首先是需要加个材质包"},"type":"text"}, + {"data":{"file":"1356f3c15301f85246b5e15dda88294e","url":"http://gchat.qpic.cn/gchatpic_new/0/0-0-1356F3C15301F85246B5E15DDA88294E/0?term=2"},"type":"image"} + ] + """ + for item in qq_json["message"]: + data_type = item["type"] + if (data_type == "text"): + result += item["data"]["text"] + continue + if (data_type == "face"): + result += "[表情]" + # TODO see https://docs-v1.zhamao.xin/face_list.html + # face to image + continue + if (data_type == "image"): + image_url = item["data"]["url"] + result += f"[[CICode,url={image_url},name=图片]]"; + continue + if (data_type == "mface"): + result += "[动画表情]" + + if (result == LIST_WORD): + # Let those MCCs return with "#!list [1.20] 在线玩家:..." which starts with #!list + python_server.send_message_to_all(LIST_WORD) + return + + # Else it is a normal message, send to all mcc + send_to_mccs(f"<{sender}> {result}", None, True) + +def send_to_mccs(msg, except_mcc_client=None, except_onebot=False): + for client_item in python_server.clients: + if client_item["id"] in closed_list: + continue + if except_mcc_client is not None and client_item["id"] == except_mcc_client["id"]: + continue + if except_onebot and client_item["id"] == onebot_id: + continue + python_server.send_message(client_item, msg) + +python_server = WebsocketServer(host='127.0.0.1', port=12345, loglevel=logging.INFO) +python_server.set_fn_new_client(new_client) +python_server.set_fn_client_left(on_client_closed) +python_server.set_fn_message_received(on_get_msg) +python_server.run_forever(True) +print("输入回车以停止") +input() +python_server.shutdown_gracefully() \ No newline at end of file From 9ae3bc4d0d0844be358b73f45d8ac43c2d36dc0e Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Thu, 11 Jan 2024 11:01:40 +0800 Subject: [PATCH 3/9] =?UTF-8?q?Fix:=20=E7=BE=A4=E5=8F=8B=E6=9C=AA=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E7=BE=A4=E6=98=B5=E7=A7=B0=E6=97=B6=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E5=9C=A8=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=98=BE=E7=A4=BA=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=20=E5=85=88=E5=88=A4=E6=96=AD=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E6=9C=89=E7=BE=A4=E6=98=B5=E7=A7=B0=EF=BC=8C=E7=84=B6=E5=90=8E?= =?UTF-8?q?=E5=86=8D=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- py-server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/py-server.py b/py-server.py index 80c7dfee96..806b52bd28 100644 --- a/py-server.py +++ b/py-server.py @@ -60,7 +60,8 @@ def handle_qq_json(msg): if (qq_json["sub_type"] != "normal"):return if (qq_json["group_id"] != GROUP_ID):return - sender = qq_json["sender"]["nickname"] + sender = qq_json["sender"]["card"] + if (sender == ""): sender = qq_json["sender"]["nickname"] result = "" """ Message example: From 725510d3ef084cc33492f72a5dce0a4b29f97c3a Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Fri, 12 Jan 2024 13:25:26 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20OkWsBot.cs=EF=BC=8C?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=8E=A9=E5=AE=B6=E5=88=97=E8=A1=A8=E6=97=B6?= =?UTF-8?q?=E6=8E=92=E9=99=A4=E8=87=AA=E5=B7=B1=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E7=BE=A4=E5=8F=B7=E6=8C=87=E5=AE=9A=EF=BC=8C=E5=9B=A0=E4=B8=BA?= =?UTF-8?q?=E5=9C=A8py-server.py=E9=87=8C=E5=B7=B2=E7=BB=8F=E6=8C=87?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MinecraftClient/ChatBots/OkWsBot.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/ChatBots/OkWsBot.cs b/MinecraftClient/ChatBots/OkWsBot.cs index 431c3fcd3f..1e65ab0ab6 100644 --- a/MinecraftClient/ChatBots/OkWsBot.cs +++ b/MinecraftClient/ChatBots/OkWsBot.cs @@ -24,12 +24,12 @@ public class OkWsBot : ChatBot public class Configs { public bool Enabled = true; - [TomlInlineComment("群号")] - public string id = "12345678"; [TomlInlineComment("Python Websocket接口")] public string pythonSendWsApi = "ws://127.0.0.1:12345"; [TomlInlineComment("在群内显示的服务器名称")] public string serverName = "1.20"; + [TomlInlineComment("在群内昵称")] + public string groupCard = "QQbot"; } public override void Initialize() @@ -102,8 +102,9 @@ public void SendToQQ(string msg) { public string GetPlayerListMsg() { string result = "["+Config.serverName+"]"; - string[] playerStrs = GetOnlinePlayers(); - if (playerStrs.Length == 0) { + List playerStrs = new List(GetOnlinePlayers()); + playerStrs.Remove(Config.groupCard); + if (playerStrs.Count == 0) { return result += " [鬼服]\n没有玩家在线"; } result += " 在线玩家:"; From 22cf7a046bbdb5453e7a23a88d906e83dfe525a7 Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Fri, 12 Jan 2024 13:28:03 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81=20Fml3?= =?UTF-8?q?=20=EF=BC=8C=E5=B9=B6=E6=9A=82=E6=97=B6=E8=AE=BE=E7=BD=AEforce?= =?UTF-8?q?=20forge=E6=97=B6=E4=B8=BAfml3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Protocol/Handlers/Forge/FMLVersion.cs | 3 +- .../Protocol/Handlers/Forge/ForgeInfo.cs | 131 ++++++++++++++++++ .../Protocol/Handlers/Protocol18Forge.cs | 24 +++- 3 files changed, 152 insertions(+), 6 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Forge/FMLVersion.cs b/MinecraftClient/Protocol/Handlers/Forge/FMLVersion.cs index 2590d473ec..d9980dc3d2 100644 --- a/MinecraftClient/Protocol/Handlers/Forge/FMLVersion.cs +++ b/MinecraftClient/Protocol/Handlers/Forge/FMLVersion.cs @@ -7,6 +7,7 @@ enum FMLVersion { FML, - FML2 + FML2, + FML3 } } diff --git a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs index 781cfd5a99..db46829bab 100755 --- a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs +++ b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs @@ -46,6 +46,13 @@ internal ForgeInfo(FMLVersion fmlVersion) }; Version = fmlVersion; break; + case FMLVersion.FML3: + Mods = new List + { + new ForgeMod("forge", "ANY") + }; + Version = fmlVersion; + break; default: throw new InvalidOperationException(Translations.error_forgeforce); } @@ -133,10 +140,134 @@ internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion) } break; + case FMLVersion.FML3: + // All buf data are write do forgeData["d"] + // src/main/java/net/minecraftforge/network/ServerStatusPing.java > serialize(ServerStatusPing forgeData) + // src/main/java/net/minecraftforge/network/ServerStatusPing.java > deserializeOptimized(JsonObject forgeData) + + // { + // "enforcesSecureChat": true, + // "forgeData": { + // "channels": [], + // "mods": [], + // "truncated": false, + // "fmlNetworkVersion": 3, + // "d": "ȳ\u0000\u0000ࠨ㐤獋㙖⹌ᦘ̺⸱恤䒸⡑⛧沮婙㨹牥ఈㄵচ₀沮婙㨹牥ఈㄵচ倠⹡岙㜲獥䋊㷍᭳ႇׇ஌᜘㘴娘▅筳ص䰭宛㘲、\u0000ᠸጋ囗湌夜㘲杩棐䐱ᅱ挃☥ోᤗ㌮ఀ׈䬣 坖ɍ䮌ᤘ\r\n旉䠳ዣ◆䲌㜃瑥廮ⷉࠋ–䁠奚Ҵ㔱摜䂸ᅱ獳ౠᡚ㜷汥戊䂸űဓĠ嵛㖱数嫤Ǎ塰䛶ⶎᮚ㞳晲擞ᖝ″ዣ䘆ఋʂ潦令ඕ爈䖔⺁ᥚ⾹潳棤㦥ᬻ挐؅䅀㠹楬ۨ㣄উ瀀渀嬛㘼扩搢䃀熁挂♥\r\n墋㒺摬牜ࣜ䁠嘗湌孛㜴浩惂䠙熙排٥孁㒰ͮ屢Ӏ䠐⚐䷮ᣛ㊴瑳戚䢸熁匒إ஍᜚ܴ䫜巑፻ᚷؠ䀀ㆃ牵䋨㦥ࠫ㋣䗆䂌㨈慲䫬ᖱᮓᘧ汬尚ㆰ٫屲㣄ᆉ恳ಭ川㤷፫擨妅挫♖乮塘 㖱慰\r\n囆䓩\t" + // }, + // "description": { + // "text": "A Minecraft Server" + // }, + // "players": { + // "max": 100, + // "online": 0 + // }, + // "version": { + // "name": "1.20.1", + // "protocol": 763 + // } + // } + string encodedData = data.Properties["d"].StringValue; + Queue dataPackage = decodeOptimized(encodedData); + DataTypes dataTypes = new DataTypes(Protocol18Handler.MC_1_18_2_Version); + + // + // [truncated][boolean] placeholder for whether we are truncating + // [Mod Size][unsigned short] short so that we can replace it later in case of truncation + // + // Console.WriteLine("decodedData="); + bool truncated = false; + //Map> channels; + + truncated = dataTypes.ReadNextBool(dataPackage); + var modsSize = dataTypes.ReadNextUShort(dataPackage); + + Dictionary channels = new(); + Dictionary mods = new(); + + for (var i = 0; i < modsSize; i++) { + var channelSizeAndVersionFlag = dataTypes.ReadNextVarInt(dataPackage); + var channelSize = channelSizeAndVersionFlag >> 1; + + int VERSION_FLAG_IGNORESERVERONLY = 0b1; + var isIgnoreServerOnly = (channelSizeAndVersionFlag & VERSION_FLAG_IGNORESERVERONLY) != 0; + + var modId = dataTypes.ReadNextString(dataPackage); + + string IGNORESERVERONLY = "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31"; + var modVersion = isIgnoreServerOnly ? IGNORESERVERONLY : dataTypes.ReadNextString(dataPackage); + + for (var i1 = 0; i1 < channelSize; i1++) { + var channelName = dataTypes.ReadNextString(dataPackage); + var channelVersion = dataTypes.ReadNextString(dataPackage); + var requiredOnClient = dataTypes.ReadNextBool(dataPackage); + channels.Add(modId + ":" + channelName, channelVersion + ":" + requiredOnClient); + //channels.Add(new ResourceLocation(modId, channelName), Pair.of(channelVersion, requiredOnClient)); + } + + mods.Add(modId, modVersion); + Mods.Add(new ForgeMod(modId, modVersion)); + } + + var nonModChannelCount = dataTypes.ReadNextVarInt(dataPackage); + for (var i = 0; i < nonModChannelCount; i++) { + var channelName = dataTypes.ReadNextString(dataPackage); + var channelVersion = dataTypes.ReadNextString(dataPackage); + var requiredOnClient = dataTypes.ReadNextBool(dataPackage); + channels.Add(channelName, channelVersion + ":" + requiredOnClient); + // channels.put(channelName, Pair.of(channelVersion, requiredOnClient)); + } + + foreach (var key in channels.Keys) { + //Console.WriteLine("We got channel: " + key + "-" + channels[key]); + } + foreach (var key in mods.Keys) { + //Console.WriteLine("We got mod: " + key + "-" + mods[key]); + } + + break; default: throw new NotImplementedException("FMLVersion '" + fmlVersion + "' not implemented!"); } } + + // decode ForgeData["d"] to Queue + // see src/main/java/net/minecraftforge/network/ServerStatusPing.java#l361 + public static Queue decodeOptimized(string encodedData) { + // Console.WriteLine("Got encoded data:" + encodedData + ", decoding..."); + int size0 = (int)encodedData[0]; + int size1 = (int)encodedData[1]; + int size = size0 | (size1 << 15); + + List packageData = new(); + + int stringIndex = 2; + int buffer = 0; + int bitsInBuf = 0; + + while (stringIndex < encodedData.Length) + { + while (bitsInBuf >= 8) + { + packageData.Add((byte)buffer); + buffer >>= 8; + bitsInBuf -= 8; + } + + char c = encodedData[stringIndex]; + buffer |= ((int)c & 0x7FFF) << bitsInBuf; + bitsInBuf += 15; + stringIndex++; + } + + while (packageData.Count < size) + { + packageData.Add((byte)buffer); + buffer >>= 8; + bitsInBuf -= 8; + } + + return new Queue(packageData.ToArray()); + } } } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs index 1f1c5f4d22..d6d58e87e2 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs @@ -241,7 +241,7 @@ public bool HandlePluginMessage(string channel, Queue packetData, ref int /// TRUE/FALSE depending on whether the packet was understood or not public bool HandleLoginPluginRequest(string channel, Queue packetData, ref List responseData) { - if (ForgeEnabled() && forgeInfo!.Version == FMLVersion.FML2 && channel == "fml:loginwrapper") + if (ForgeEnabled() && (forgeInfo!.Version == FMLVersion.FML2 || forgeInfo!.Version == FMLVersion.FML3) && channel == "fml:loginwrapper") { // Forge Handshake handler source code used to implement the FML2 packets: // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java @@ -320,6 +320,14 @@ public bool HandleLoginPluginRequest(string channel, Queue packetData, ref for (int i = 0; i < registryCount; i++) registries.Add(dataTypes.ReadNextString(packetData)); + List dataPackRegistries = new(); + if (forgeInfo!.Version == FMLVersion.FML3 && packetData.Count != 0) + { + int dataPackRegistryCount = dataTypes.ReadNextVarInt(packetData); + for (int i = 0; i < dataPackRegistryCount; i++) + dataPackRegistries.Add(dataTypes.ReadNextString(packetData)); + } + // Server Mod List Reply: FMLHandshakeMessages.java > C2SModListReply > encode() // // [ Mod Count ][ VarInt ] @@ -375,7 +383,7 @@ public bool HandleLoginPluginRequest(string channel, Queue packetData, ref string registryName = dataTypes.ReadNextString(packetData); ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_fml2_registry, registryName)); } - + fmlResponsePacket.AddRange(DataTypes.GetVarInt(99)); fmlResponseReady = true; break; @@ -442,7 +450,8 @@ private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, b public static bool ServerInfoCheckForge(Json.JSONData jsonData, ref ForgeInfo? forgeInfo) { return ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML) // MC 1.12 and lower - || ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML2); // MC 1.13 and greater + || ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML2) // MC 1.13 and greater + || ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML3); // MC 1.18 and greater } /// @@ -464,7 +473,7 @@ public static ForgeInfo ServerForceForge(int protocolVersion) { if (ServerMayForceForge(protocolVersion)) { - return new ForgeInfo(FMLVersion.FML2); + return new ForgeInfo(FMLVersion.FML3); } else throw new InvalidOperationException(Translations.error_forgeforce); } @@ -494,6 +503,11 @@ private static bool ServerInfoCheckForgeSub(Json.JSONData jsonData, ref ForgeInf versionField = "fmlNetworkVersion"; versionString = "2"; break; + case FMLVersion.FML3: + forgeDataTag = "forgeData"; + versionField = "fmlNetworkVersion"; + versionString = "3"; + break; default: throw new NotImplementedException("FMLVersion '" + fmlVersion + "' not implemented!"); } @@ -523,6 +537,6 @@ private static bool ServerInfoCheckForgeSub(Json.JSONData jsonData, ref ForgeInf } } return false; - } + } } } From e7d519e4aace92604677322a273d78bf65326184 Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Sat, 13 Jan 2024 03:00:02 +0800 Subject: [PATCH 6/9] Fix bug: Yggdrasil client can't send message upper 22w17a When try to send message in yggdrasil-auth server with `enforce-secure-profile=true` and `online-mode=true` enabled, will fail with message in red: Chat disabled due to missing profile public key. Please try reconnecting. So yggdrasil-auth-client also has to encrypt chat messages like Microsoft-auth-client to send out messages. --- .../Protocol/ProfileKey/KeyUtils.cs | 55 +++++++++++++++---- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs index 4a8d5c66e4..a4bc6f2b22 100644 --- a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs +++ b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs @@ -5,6 +5,7 @@ using MinecraftClient.Protocol.Handlers; using MinecraftClient.Protocol.Message; using static MinecraftClient.Protocol.Message.LastSeenMessageList; +using Newtonsoft.Json.Linq; namespace MinecraftClient.Protocol.ProfileKey { @@ -14,27 +15,34 @@ static class KeyUtils private static readonly string certificates = "https://api.minecraftservices.com/player/certificates"; - public static PlayerKeyPair? GetNewProfileKeys(string accessToken) + public static PlayerKeyPair? GetNewProfileKeys(string accessToken, bool isYggdrasil) { ProxiedWebRequest.Response? response = null; try { - var request = new ProxiedWebRequest(certificates) - { - Accept = "application/json" - }; - request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken)); + if (!isYggdrasil) { + var request = new ProxiedWebRequest(certificates) + { + Accept = "application/json" + }; + request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken)); - response = request.Post("application/json", ""); + response = request.Post("application/json", ""); - if (Settings.Config.Logging.DebugMessages) - { - ConsoleIO.WriteLine(response.Body.ToString()); + if (Settings.Config.Logging.DebugMessages) + { + ConsoleIO.WriteLine(response.Body.ToString()); + } } - string jsonString = response.Body; + // see https://github.com/yushijinhun/authlib-injector/blob/da910956eaa30d2f6c2c457222d188aeb53b0d1f/src/main/java/moe/yushi/authlibinjector/httpd/ProfileKeyFilter.java#L49 + // POST to "https://api.minecraftservices.com/player/certificates" with authlib-injector will get a dummy response + string jsonString = isYggdrasil ? MakeDummyResponse() : response!.Body; Json.JSONData json = Json.ParseJson(jsonString); + Console.WriteLine("Got public key:" + json.Properties["keyPair"].Properties["publicKey"].StringValue); + Console.WriteLine("Got private key:" + json.Properties["keyPair"].Properties["privateKey"].StringValue); + // Error here PublicKey publicKey = new(pemKey: json.Properties["keyPair"].Properties["publicKey"].StringValue, sig: json.Properties["publicKeySignature"].StringValue, sigV2: json.Properties["publicKeySignatureV2"].StringValue); @@ -230,5 +238,30 @@ public static string EscapeString(string src) sb.Append(src, start, src.Length - start); return sb.ToString(); } + + public static string MakeDummyResponse() + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048); + var mimePublicKey = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo()); + var mimePrivateKey = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey()); + string publicKeyPEM = $"-----BEGIN RSA PUBLIC KEY-----\n{mimePublicKey}\n-----END RSA PUBLIC KEY-----\n"; + string privateKeyPEM = $"-----BEGIN RSA PRIVATE KEY-----\n{mimePrivateKey}\n-----END RSA PRIVATE KEY-----\n"; + DateTime now = DateTime.UtcNow; + DateTime expiresAt = now.AddHours(48); + DateTime refreshedAfter = now.AddHours(36); + JObject response = new JObject(); + JObject keyPairObj = new JObject + { + { "privateKey", privateKeyPEM }, + { "publicKey", publicKeyPEM } + }; + response.Add("keyPair", keyPairObj); + response.Add("publicKeySignature", "AA=="); + response.Add("publicKeySignatureV2", "AA=="); + string format = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; + response.Add("expiresAt", expiresAt.ToString(format)); + response.Add("refreshedAfter", refreshedAfter.ToString(format)); + return response.ToString(); + } } } From 2c8b15b02e1603383c256bb06c1ff2c08a792040 Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Sat, 13 Jan 2024 03:00:02 +0800 Subject: [PATCH 7/9] Fix bug: Yggdrasil client can't send message upper 22w17a When try to send message in yggdrasil-auth server with `enforce-secure-profile=true` and `online-mode=true` enabled, will fail with message in red: Chat disabled due to missing profile public key. Please try reconnecting. So yggdrasil-auth-client also has to encrypt chat messages like Microsoft-auth-client to send out messages. --- MinecraftClient/Program.cs | 4 +- .../Protocol/ProfileKey/KeyUtils.cs | 55 +++++++++++++++---- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index f0d8f0b864..29ea3df5d5 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -557,7 +557,7 @@ private static void InitializeClient() } } - if (Config.Main.General.AccountType == LoginType.microsoft + if ((Config.Main.General.AccountType == LoginType.microsoft || Config.Main.General.AccountType == LoginType.yggdrasil) && (InternalConfig.Account.Password != "-" || Config.Main.General.Method == LoginMethod.browser) && Config.Signature.LoginWithSecureProfile && protocolversion >= 759 /* 1.19 and above */) @@ -582,7 +582,7 @@ private static void InitializeClient() if (playerKeyPair == null || playerKeyPair.NeedRefresh()) { ConsoleIO.WriteLineFormatted(Translations.mcc_fetching_key, acceptnewlines: true); - playerKeyPair = KeyUtils.GetNewProfileKeys(session.ID); + playerKeyPair = KeyUtils.GetNewProfileKeys(session.ID, Config.Main.General.AccountType == LoginType.yggdrasil); if (Config.Main.Advanced.ProfileKeyCache != CacheType.none && playerKeyPair != null) { KeysCache.Store(loginLower, playerKeyPair); diff --git a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs index 4a8d5c66e4..a4bc6f2b22 100644 --- a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs +++ b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs @@ -5,6 +5,7 @@ using MinecraftClient.Protocol.Handlers; using MinecraftClient.Protocol.Message; using static MinecraftClient.Protocol.Message.LastSeenMessageList; +using Newtonsoft.Json.Linq; namespace MinecraftClient.Protocol.ProfileKey { @@ -14,27 +15,34 @@ static class KeyUtils private static readonly string certificates = "https://api.minecraftservices.com/player/certificates"; - public static PlayerKeyPair? GetNewProfileKeys(string accessToken) + public static PlayerKeyPair? GetNewProfileKeys(string accessToken, bool isYggdrasil) { ProxiedWebRequest.Response? response = null; try { - var request = new ProxiedWebRequest(certificates) - { - Accept = "application/json" - }; - request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken)); + if (!isYggdrasil) { + var request = new ProxiedWebRequest(certificates) + { + Accept = "application/json" + }; + request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken)); - response = request.Post("application/json", ""); + response = request.Post("application/json", ""); - if (Settings.Config.Logging.DebugMessages) - { - ConsoleIO.WriteLine(response.Body.ToString()); + if (Settings.Config.Logging.DebugMessages) + { + ConsoleIO.WriteLine(response.Body.ToString()); + } } - string jsonString = response.Body; + // see https://github.com/yushijinhun/authlib-injector/blob/da910956eaa30d2f6c2c457222d188aeb53b0d1f/src/main/java/moe/yushi/authlibinjector/httpd/ProfileKeyFilter.java#L49 + // POST to "https://api.minecraftservices.com/player/certificates" with authlib-injector will get a dummy response + string jsonString = isYggdrasil ? MakeDummyResponse() : response!.Body; Json.JSONData json = Json.ParseJson(jsonString); + Console.WriteLine("Got public key:" + json.Properties["keyPair"].Properties["publicKey"].StringValue); + Console.WriteLine("Got private key:" + json.Properties["keyPair"].Properties["privateKey"].StringValue); + // Error here PublicKey publicKey = new(pemKey: json.Properties["keyPair"].Properties["publicKey"].StringValue, sig: json.Properties["publicKeySignature"].StringValue, sigV2: json.Properties["publicKeySignatureV2"].StringValue); @@ -230,5 +238,30 @@ public static string EscapeString(string src) sb.Append(src, start, src.Length - start); return sb.ToString(); } + + public static string MakeDummyResponse() + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048); + var mimePublicKey = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo()); + var mimePrivateKey = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey()); + string publicKeyPEM = $"-----BEGIN RSA PUBLIC KEY-----\n{mimePublicKey}\n-----END RSA PUBLIC KEY-----\n"; + string privateKeyPEM = $"-----BEGIN RSA PRIVATE KEY-----\n{mimePrivateKey}\n-----END RSA PRIVATE KEY-----\n"; + DateTime now = DateTime.UtcNow; + DateTime expiresAt = now.AddHours(48); + DateTime refreshedAfter = now.AddHours(36); + JObject response = new JObject(); + JObject keyPairObj = new JObject + { + { "privateKey", privateKeyPEM }, + { "publicKey", publicKeyPEM } + }; + response.Add("keyPair", keyPairObj); + response.Add("publicKeySignature", "AA=="); + response.Add("publicKeySignatureV2", "AA=="); + string format = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; + response.Add("expiresAt", expiresAt.ToString(format)); + response.Add("refreshedAfter", refreshedAfter.ToString(format)); + return response.ToString(); + } } } From 8eee50044fda650a2688e08f047ecc8c352e780c Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Sun, 14 Jan 2024 01:45:38 +0800 Subject: [PATCH 8/9] Edit outdated links in comments and add FML3 stuff Add FML3 extra packetID 5 and 6 recognition based on https://github.com/MinecraftForge/MinecraftForge/blob/1.18.x/src/main/java/net/minecraftforge/network/NetworkInitialization.java Changed the way of selecting FML version for "Force Forge" from forced FML3 to game version based. MC 1.12 and lower: FML, MC 1.13 to 1.17: FML2, MC 1.18 and greater: FML3 Edit outdated links in comments: Accessing the link below will result in a message that the file cannot be found https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java Presumably it's version 1.13 based on the original commit and issue, then change to https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java https://github.com/MCCTeam/Minecraft-Console-Client/issues/1184 --- .../Protocol/Handlers/Forge/ForgeInfo.cs | 45 ++++------ .../Protocol/Handlers/Protocol18Forge.cs | 82 ++++++++++++++++--- 2 files changed, 90 insertions(+), 37 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs index db46829bab..ffbd4233b7 100755 --- a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs +++ b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs @@ -141,16 +141,14 @@ internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion) break; case FMLVersion.FML3: - // All buf data are write do forgeData["d"] - // src/main/java/net/minecraftforge/network/ServerStatusPing.java > serialize(ServerStatusPing forgeData) - // src/main/java/net/minecraftforge/network/ServerStatusPing.java > deserializeOptimized(JsonObject forgeData) + // Example ModInfo for Minecraft 1.18 and greater (FML3) // { // "enforcesSecureChat": true, // "forgeData": { // "channels": [], // "mods": [], - // "truncated": false, + // "truncated": false, // legacy versions see truncated lists, modern versions ignore this truncated flag (binary data has its own) // "fmlNetworkVersion": 3, // "d": "ȳ\u0000\u0000ࠨ㐤獋㙖⹌ᦘ̺⸱恤䒸⡑⛧沮婙㨹牥ఈㄵচ₀沮婙㨹牥ఈㄵচ倠⹡岙㜲獥䋊㷍᭳ႇׇ஌᜘㘴娘▅筳ص䰭宛㘲、\u0000ᠸጋ囗湌夜㘲杩棐䐱ᅱ挃☥ోᤗ㌮ఀ׈䬣 坖ɍ䮌ᤘ\r\n旉䠳ዣ◆䲌㜃瑥廮ⷉࠋ–䁠奚Ҵ㔱摜䂸ᅱ獳ౠᡚ㜷汥戊䂸űဓĠ嵛㖱数嫤Ǎ塰䛶ⶎᮚ㞳晲擞ᖝ″ዣ䘆ఋʂ潦令ඕ爈䖔⺁ᥚ⾹潳棤㦥ᬻ挐؅䅀㠹楬ۨ㣄উ瀀渀嬛㘼扩搢䃀熁挂♥\r\n墋㒺摬牜ࣜ䁠嘗湌孛㜴浩惂䠙熙排٥孁㒰ͮ屢Ӏ䠐⚐䷮ᣛ㊴瑳戚䢸熁匒إ஍᜚ܴ䫜巑፻ᚷؠ䀀ㆃ牵䋨㦥ࠫ㋣䗆䂌㨈慲䫬ᖱᮓᘧ汬尚ㆰ٫屲㣄ᆉ恳ಭ川㤷፫擨妅挫♖乮塘 㖱慰\r\n囆䓩\t" // }, @@ -166,19 +164,22 @@ internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion) // "protocol": 763 // } // } + + // All buffer data are encoded and write to forgeData["d"] + // https://github.com/MinecraftForge/MinecraftForge/blob/cb12df41e13da576b781be695f80728b9594c25f/src/main/java/net/minecraftforge/network/ServerStatusPing.java#L264 + + // 1.18 and greater, the buffer is encoded for efficiency + // see https://github.com/MinecraftForge/MinecraftForge/pull/8169 + string encodedData = data.Properties["d"].StringValue; Queue dataPackage = decodeOptimized(encodedData); - DataTypes dataTypes = new DataTypes(Protocol18Handler.MC_1_18_2_Version); + DataTypes dataTypes = new DataTypes(Protocol18Handler.MC_1_18_1_Version); // // [truncated][boolean] placeholder for whether we are truncating // [Mod Size][unsigned short] short so that we can replace it later in case of truncation // - // Console.WriteLine("decodedData="); - bool truncated = false; - //Map> channels; - - truncated = dataTypes.ReadNextBool(dataPackage); + bool truncated = dataTypes.ReadNextBool(dataPackage); var modsSize = dataTypes.ReadNextUShort(dataPackage); Dictionary channels = new(); @@ -193,7 +194,7 @@ internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion) var modId = dataTypes.ReadNextString(dataPackage); - string IGNORESERVERONLY = "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31"; + string IGNORESERVERONLY = "";// it was "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31"; var modVersion = isIgnoreServerOnly ? IGNORESERVERONLY : dataTypes.ReadNextString(dataPackage); for (var i1 = 0; i1 < channelSize; i1++) { @@ -201,7 +202,6 @@ internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion) var channelVersion = dataTypes.ReadNextString(dataPackage); var requiredOnClient = dataTypes.ReadNextBool(dataPackage); channels.Add(modId + ":" + channelName, channelVersion + ":" + requiredOnClient); - //channels.Add(new ResourceLocation(modId, channelName), Pair.of(channelVersion, requiredOnClient)); } mods.Add(modId, modVersion); @@ -214,15 +214,6 @@ internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion) var channelVersion = dataTypes.ReadNextString(dataPackage); var requiredOnClient = dataTypes.ReadNextBool(dataPackage); channels.Add(channelName, channelVersion + ":" + requiredOnClient); - // channels.put(channelName, Pair.of(channelVersion, requiredOnClient)); - } - - foreach (var key in channels.Keys) { - //Console.WriteLine("We got channel: " + key + "-" + channels[key]); - } - - foreach (var key in mods.Keys) { - //Console.WriteLine("We got mod: " + key + "-" + mods[key]); } break; @@ -231,12 +222,12 @@ internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion) } } - // decode ForgeData["d"] to Queue - // see src/main/java/net/minecraftforge/network/ServerStatusPing.java#l361 - public static Queue decodeOptimized(string encodedData) { + // https://github.com/MinecraftForge/MinecraftForge/blob/cb12df41e13da576b781be695f80728b9594c25f/src/main/java/net/minecraftforge/network/ServerStatusPing.java#L361 + // Decode binary data ForgeData["d"] to Queue + private static Queue decodeOptimized(string encodedData) { // Console.WriteLine("Got encoded data:" + encodedData + ", decoding..."); - int size0 = (int)encodedData[0]; - int size1 = (int)encodedData[1]; + int size0 = encodedData[0]; + int size1 = encodedData[1]; int size = size0 | (size1 << 15); List packageData = new(); @@ -255,7 +246,7 @@ public static Queue decodeOptimized(string encodedData) { } char c = encodedData[stringIndex]; - buffer |= ((int)c & 0x7FFF) << bitsInBuf; + buffer |= (c & 0x7FFF) << bitsInBuf; bitsInBuf += 15; stringIndex++; } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs index d6d58e87e2..99fb4c728f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs @@ -233,7 +233,7 @@ public bool HandlePluginMessage(string channel, Queue packetData, ref int } /// - /// Handle Forge plugin messages during login phase (Forge Protocol version 2: FML2) + /// Handle Forge plugin messages during login phase (Forge Protocol version 2: FML2 or Forge Protocol version 3: FML3) /// /// Plugin message channel /// Plugin message data @@ -244,12 +244,16 @@ public bool HandleLoginPluginRequest(string channel, Queue packetData, ref if (ForgeEnabled() && (forgeInfo!.Version == FMLVersion.FML2 || forgeInfo!.Version == FMLVersion.FML3) && channel == "fml:loginwrapper") { // Forge Handshake handler source code used to implement the FML2 packets: - // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java - // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLHandshakeHandler.java - // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/NetworkInitialization.java - // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLLoginWrapper.java - // https://github.com/MinecraftForge/MinecraftForge/blob/master/src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessages.java + // https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java + // https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/FMLHandshakeHandler.java + // https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/NetworkInitialization.java + // https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/FMLLoginWrapper.java + // https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessages.java // + // FML3 packets: + // https://github.com/MinecraftForge/MinecraftForge/blob/1.18.x/src/main/java/net/minecraftforge/network/NetworkInitialization.java + // https://github.com/MinecraftForge/MinecraftForge/blob/1.18.x/src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessages.java + // // During Login, Forge will send a set of LoginPluginRequest packets and we need to respond accordingly. // Each login plugin message contains in its payload field an inner packet created by FMLLoginWrapper.java: // @@ -274,6 +278,8 @@ public bool HandleLoginPluginRequest(string channel, Queue packetData, ref // 2 = Client to Server - Mod List // 3 = Server to Client - Registry // 4 = Server to Client - Config + // 5 = Server to Client - Mod Data List (FML3) + // 6 = Server to Client - MismatchedMod List (FML3) // // The content of each message is mapped into a class inside FMLHandshakeMessages.java // FMLHandshakeHandler will then process the packet, e.g. handleServerModListOnClient() for Server Mod List. @@ -320,6 +326,7 @@ public bool HandleLoginPluginRequest(string channel, Queue packetData, ref for (int i = 0; i < registryCount; i++) registries.Add(dataTypes.ReadNextString(packetData)); + // FML3 specific, List dataPackRegistries = new(); if (forgeInfo!.Version == FMLVersion.FML3 && packetData.Count != 0) { @@ -407,6 +414,53 @@ public bool HandleLoginPluginRequest(string channel, Queue packetData, ref fmlResponseReady = true; break; + case 5: + // FML 3 + // Server Config: FMLHandshakeMessages.java > S2CModData > decode() + // + // We're ignoring this packet in MCC + + /* + // Uncomment this code block if needed + var size = dataTypes.ReadNextVarInt(packetData); + Dictionary modsData = new(); + for (int i = 0; i < size; i++) + { + var modId = dataTypes.ReadNextString(packetData); + var displayName = dataTypes.ReadNextString(packetData); + var version = dataTypes.ReadNextString(packetData); + modsData.Add(modId, displayName + ":" + version); + } + */ + if (Settings.Config.Logging.DebugMessages) + { + ConsoleIO.WriteLineFormatted("§8" + "Received FML3 Server Mod Data List"); + } + break; + + case 6: + // FML 3 + // Server Config: FMLHandshakeMessages.java > S2CChannelMismatchData > decode() + // + // We're ignoring this packet in MCC + + /* + // Uncomment this code block if needed + Dictionary mismatchedMods = new(); + var size0 = dataTypes.ReadNextVarInt(packetData); + for (int i = 0; i < size0; i++) + { + var modId = dataTypes.ReadNextString(packetData); + var version = dataTypes.ReadNextString(packetData); + mismatchedMods.Add(modId, version); + } + */ + if (Settings.Config.Logging.DebugMessages) + { + ConsoleIO.WriteLineFormatted("§8" + "Received FML3 Server Mismatched Mods List"); + } + break; + default: if (Settings.Config.Logging.DebugMessages) ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_fml2_unknown, packetID)); @@ -450,7 +504,7 @@ private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, b public static bool ServerInfoCheckForge(Json.JSONData jsonData, ref ForgeInfo? forgeInfo) { return ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML) // MC 1.12 and lower - || ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML2) // MC 1.13 and greater + || ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML2) // MC 1.13 to 1.17 || ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML3); // MC 1.18 and greater } @@ -472,14 +526,22 @@ public static bool ServerMayForceForge(int protocolVersion) public static ForgeInfo ServerForceForge(int protocolVersion) { if (ServerMayForceForge(protocolVersion)) - { - return new ForgeInfo(FMLVersion.FML3); + { + // 1.17 is still FML2 + // https://github.com/MinecraftForge/MinecraftForge/blob/50b5414033de82f46be23201db50484f36c37d4f/src/main/java/net/minecraftforge/fmllegacy/network/FMLNetworkConstants.java#L37C29-L37C42 + // 1.18 change the constant FMLNETVERSION to 3 + // https://github.com/MinecraftForge/MinecraftForge/blob/cb12df41e13da576b781be695f80728b9594c25f/src/main/java/net/minecraftforge/network/NetworkConstants.java#L28 + if (protocolVersion > ProtocolHandler.MCVer2ProtocolVersion("1.18")) + { + return new ForgeInfo(FMLVersion.FML3); + } + return new ForgeInfo(FMLVersion.FML2); } else throw new InvalidOperationException(Translations.error_forgeforce); } /// - /// Server Info: Check for For Forge on a Minecraft server Ping result (Handles FML and FML2 + /// Server Info: Check for For Forge on a Minecraft server Ping result (Handles FML and FML2 and FML3 /// /// JSON data returned by the server /// ForgeInfo to populate From a8643d85fc6631cb1ec93e717161e127c5ab2f29 Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Sun, 14 Jan 2024 10:48:45 +0800 Subject: [PATCH 9/9] Remove QQbot scripts for fml3 pull request --- MinecraftClient/ChatBots/OkWsBot.cs | 153 ------------------------- MinecraftClient/McClient.cs | 2 - MinecraftClient/MinecraftClient.csproj | 1 - MinecraftClient/Settings.cs | 7 -- py-server.py | 115 ------------------- 5 files changed, 278 deletions(-) delete mode 100644 MinecraftClient/ChatBots/OkWsBot.cs delete mode 100644 py-server.py diff --git a/MinecraftClient/ChatBots/OkWsBot.cs b/MinecraftClient/ChatBots/OkWsBot.cs deleted file mode 100644 index 1e65ab0ab6..0000000000 --- a/MinecraftClient/ChatBots/OkWsBot.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Net; -using System.Text; -using MinecraftClient.Scripting; -using WebSocketSharp; -using System.Collections.Generic; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json; -using System.Linq; -using Tomlet.Attributes; -using MinecraftClient.Mapping; -using static MinecraftClient.Settings; -using System.Threading.Tasks; - -namespace MinecraftClient.OkBots; -public class OkWsBot : ChatBot -{ - WebSocket? pyWebSocketServer; - protected static OkWsBot? chatBot; - - public static Configs Config = new (); - - [TomlDoNotInlineObject] - public class Configs { - public bool Enabled = true; - - [TomlInlineComment("Python Websocket接口")] - public string pythonSendWsApi = "ws://127.0.0.1:12345"; - [TomlInlineComment("在群内显示的服务器名称")] - public string serverName = "1.20"; - [TomlInlineComment("在群内昵称")] - public string groupCard = "QQbot"; - } - - public override void Initialize() - { - chatBot = this; - InitDeathMsgs(); - - pyWebSocketServer = new WebSocket (Config.pythonSendWsApi); - pyWebSocketServer.OnMessage += (sender, e) => { - var msg = e.Data; - Console.WriteLine("We get msg:" + msg); - if (msg == "#!list") { - pyWebSocketServer.Send("#!list" + GetPlayerListMsg()); - return; - } - SendToConsole(msg); - }; - pyWebSocketServer.Connect (); - - LogToConsole("OkQQbot has been initialized!"); - } - - public override void GetText(string text) - { - string message = ""; - string username = ""; - string serverPrefix = "["+ Config.serverName +"] "; - text = GetVerbatim(text); - - if (IsChatMessage(text, ref message, ref username)) - { - if (username == GetUsername()) return; - string msg = serverPrefix + text; - SendToQQ(msg); - return; - } - - if (IsAchievementMsg(text)) { - if (text.StartsWith("bot_")) return; - string msg = serverPrefix + "<喜报> " + text; - SendToQQ(msg); - return; - } - - if (IsDeathMsg(text)) { - if (text.StartsWith("bot_")) return; - string msg = serverPrefix + "<悲报> " + text; - SendToQQ(msg); - return; - } - - if (IsLoginLogoutMsg(text)) { - if (text.StartsWith("bot_")) return; - SendToQQ(serverPrefix + text); - } - } - - public void SendToConsole(string msg) { - base.SendText(msg); - } - - public void SendToQQ(string msg) { - if (!pyWebSocketServer!.Ping()) { - Console.WriteLine("Onebot Connection down, reconnecting..."); - pyWebSocketServer = new WebSocket (Config.pythonSendWsApi); - pyWebSocketServer.Connect (); - } - pyWebSocketServer.Send (msg); - } - - public string GetPlayerListMsg() { - string result = "["+Config.serverName+"]"; - List playerStrs = new List(GetOnlinePlayers()); - playerStrs.Remove(Config.groupCard); - if (playerStrs.Count == 0) { - return result += " [鬼服]\n没有玩家在线"; - } - result += " 在线玩家:"; - foreach (var playerStr in playerStrs) - { - result += "\n" + playerStr; - } - return result; - } - - public bool IsLoginLogoutMsg(string msg) { - return msg.EndsWith(" joined the game") || - msg.EndsWith(" left the game"); - } - - public bool IsAchievementMsg(string msg) { - return msg.IndexOf(" has made the advancement ") != -1; - } - - private static List DeathMsgs = new(); - public void InitDeathMsgs() { - string translationsStr = Encoding.ASCII.GetString((byte[])MinecraftAssets.ResourceManager.GetObject("en_us.json")!); - JObject translations = JObject.Parse(translationsStr); - var enumerator = translations.GetEnumerator(); - while (enumerator.MoveNext()) { - if (!enumerator.Current.Key.StartsWith("death.")) continue; - string result = enumerator.Current.Value!.ToString(); - result = result.Replace("%1$s ", ""); - - var index2s = result.IndexOf("%2$s"); - if (index2s != -1) { - result = result.Substring(0,index2s); - } - DeathMsgs.Add(result); - } - } - public bool IsDeathMsg(string msg) { - foreach (var item in DeathMsgs) - { - if (msg.IndexOf(item) != -1) { - return true; - } - } - return false; - } -} \ No newline at end of file diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 1096252415..c7296d52a3 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -7,7 +7,6 @@ using Brigadier.NET; using Brigadier.NET.Exceptions; using MinecraftClient.ChatBots; -using MinecraftClient.OkBots; using MinecraftClient.CommandHandler; using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Commands; @@ -299,7 +298,6 @@ private void RegisterBots(bool reload = false) if (Config.ChatBot.TelegramBridge.Enabled) { BotLoad(new TelegramBridge()); } if (Config.ChatBot.ItemsCollector.Enabled) { BotLoad(new ItemsCollector()); } if (Config.ChatBot.WebSocketBot.Enabled) { BotLoad(new WebSocketBot()); } - if (Config.ChatBot.OkWsBot.Enabled) { BotLoad(new OkWsBot()); } //Add your ChatBot here by uncommenting and adapting //BotLoad(new ChatBots.YourBot()); } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index faaed5fa95..cf999ca210 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -45,7 +45,6 @@ - diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 3a9021c319..7982b057fe 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -1440,13 +1440,6 @@ public ChatBots.WebSocketBot.Configs WebSocketBot get { return ChatBots.WebSocketBot.Config!; } set { ChatBots.WebSocketBot.Config = value; } } - - [TomlPrecedingComment("Ok的QQbot")] - public OkBots.OkWsBot.Configs OkWsBot - { - get { return OkBots.OkWsBot.Config!; } - set { OkBots.OkWsBot.Config = value; } - } } } diff --git a/py-server.py b/py-server.py deleted file mode 100644 index 806b52bd28..0000000000 --- a/py-server.py +++ /dev/null @@ -1,115 +0,0 @@ -from websocket_server import WebsocketServer -import logging -import requests -import json - -# 群号 -GROUP_ID = 12345678 -# Onebot 发送群聊消息的节点 -QQ_BOT_API = f"http://127.0.0.1:5700/send_group_msg?group_id={GROUP_ID}&message=" -# 用来查询玩家列表的关键词 -LIST_WORD = "#!list" -# 第一个连接websocket的一定要是onebot -onebot_id = -1 -# 已关闭的连接列表 -closed_list = {-1} - -def new_client(client, server): - client_id = client["id"] - client_addr = client["address"] - global onebot_id - if (onebot_id == -1): - print("Onebot client join us, you can start mcc client now.") - onebot_id = client["id"] - else: - print("New mcc client join us, ") - - print(f"id:{client_id}, address:{client_addr}") - -def send_to_qq(msg): - requests.get(QQ_BOT_API + msg) - -def on_client_closed(client, server): - global closed_list - closed_list.add(client["id"]) - -def on_get_msg(client, server, message): - client_id = client["id"] - print(f"client id: {client_id}, message: {message}") - - if (message.startswith(LIST_WORD)): - send_to_qq(message[len(LIST_WORD):]) - return - - if (message.startswith("{")): - handle_qq_json(message) - return - - # 如果不是 #!list 消息和 qq消息,那就是服里发来的消息 - # 发到其他服 - send_to_mccs(message, client, True) - - # 发到qq - send_to_qq(message) - -def handle_qq_json(msg): - - qq_json = json.loads(msg) - if (qq_json["post_type"] != "message"):return - if (qq_json["message_type"] != "group"):return - if (qq_json["sub_type"] != "normal"):return - if (qq_json["group_id"] != GROUP_ID):return - - sender = qq_json["sender"]["card"] - if (sender == ""): sender = qq_json["sender"]["nickname"] - result = "" - """ - Message example: - "message":[ - {"data":{"text":"首先是需要加个材质包"},"type":"text"}, - {"data":{"file":"1356f3c15301f85246b5e15dda88294e","url":"http://gchat.qpic.cn/gchatpic_new/0/0-0-1356F3C15301F85246B5E15DDA88294E/0?term=2"},"type":"image"} - ] - """ - for item in qq_json["message"]: - data_type = item["type"] - if (data_type == "text"): - result += item["data"]["text"] - continue - if (data_type == "face"): - result += "[表情]" - # TODO see https://docs-v1.zhamao.xin/face_list.html - # face to image - continue - if (data_type == "image"): - image_url = item["data"]["url"] - result += f"[[CICode,url={image_url},name=图片]]"; - continue - if (data_type == "mface"): - result += "[动画表情]" - - if (result == LIST_WORD): - # Let those MCCs return with "#!list [1.20] 在线玩家:..." which starts with #!list - python_server.send_message_to_all(LIST_WORD) - return - - # Else it is a normal message, send to all mcc - send_to_mccs(f"<{sender}> {result}", None, True) - -def send_to_mccs(msg, except_mcc_client=None, except_onebot=False): - for client_item in python_server.clients: - if client_item["id"] in closed_list: - continue - if except_mcc_client is not None and client_item["id"] == except_mcc_client["id"]: - continue - if except_onebot and client_item["id"] == onebot_id: - continue - python_server.send_message(client_item, msg) - -python_server = WebsocketServer(host='127.0.0.1', port=12345, loglevel=logging.INFO) -python_server.set_fn_new_client(new_client) -python_server.set_fn_client_left(on_client_closed) -python_server.set_fn_message_received(on_get_msg) -python_server.run_forever(True) -print("输入回车以停止") -input() -python_server.shutdown_gracefully() \ No newline at end of file