internal MetaData(PacketReader rx) { byte x; while ((x = rx.ReadByte()) != 0x7f) { switch (x >> 5) { case 0: Data[x & 0x1f] = rx.ReadByte(); break; case 1: Data[x & 0x1f] = rx.ReadShort(); break; case 2: Data[x & 0x1f] = rx.ReadInt(); break; case 3: Data[x & 0x1f] = rx.ReadFloat(); break; case 4: Data[x & 0x1f] = rx.ReadString16(64); break; default: Data[x & 0x1f] = null; break; } } }
/// <summary> /// 有新資料到達時呼叫的程式 /// </summary> /// <returns>旗標 (0=斷線, 1=不用回覆, 2=有資料等候回覆)</returns> /// <remarks>詳細運作方法, 請參考 http://wiki.vg/Protocol_Encryption </remarks> public int HandleStep() { byte version; bool isDisconnect = false; string ServerHost; int ServerPort; try { PacketReader PR = new PacketReader(Buffer, Buffer.Length); PacketWriter PW = PacketWriter.CreateInstance(); Queue<byte[]> strings = new Queue<byte[]>(); AppSettingsSection config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).AppSettings; // 分析客戶端指令 switch(Buffer[0]) { case 0xFE: // 查詢伺服器資料: 0xFE ConsoleWriteTime(); Console.WriteLine("@{0} 接收伺服器資訊請求. ", UserName); // 傳回踢走: 0xFF + 0x00 + (通訊協定版本) + 0x00 + (伺服器版本) + 0x00 + (伺服器名稱) + 0x00 + (在線玩家數) + 0x00 + (在線玩家上限) PW = kick(String.Format("§1\0{0}\0{1}\0{2}\0{3}\0{4}", 51, config.Settings["serverVersion"].Value, config.Settings["motd"].Value, config.Settings["onlinePlayers"].Value, config.Settings["maxPlayers"].Value)); isDisconnect = true; break; case 0x02: // 接收握手: 0x02 + (客戶端版本) + (玩家 ID) + (伺服器 IP / 網址) + (伺服器連接埠) version = PR.ReadByte(); UserName = PR.ReadString16(64); ServerHost = PR.ReadString16(64); ServerPort = PR.ReadInt(); ConsoleWriteTime(); Console.WriteLine("@{1} 接收握手指令.\n 玩家 {1} 使用通訊協定版本 {0} 連接到 {2}:{3}.", (int)version, UserName, ServerHost, ServerPort); strings.Enqueue(ASCIIEncoding.BigEndianUnicode.GetBytes(ConnectionId)); // 傳回加密請求: 0xFD + (伺服器 ID) + (密鑰長度) + (密鑰) + (金幣長度) + (金幣) PW = PacketWriter.CreateInstance(7 + keyLength + tokenLength, strings); PW.WriteByte(0xFD); PW.Write(ConnectionId); PW.Write(keyLength); PW.Write(publicKey, 0, keyLength); PW.Write(tokenLength); PW.Write(token, 0, tokenLength); ConsoleWriteTime(); Console.WriteLine("@{3} 送出加密請求指令.\n 伺服器 ID: {0}, 密鑰長度: {1}, 金幣長度: {2}.", ConnectionId, keyLength, tokenLength, UserName); break; case 0xFC: // 接受加密: 0xFC + (分享密碼長度) + (分享密碼) + (已加密的金幣長度) + (已加密的金幣) short sharedSecretLength = PR.ReadShort(); byte[] sharedSecret = PR.ReadBytes(sharedSecretLength); short variefyTokenLength = PR.ReadShort(); byte[] variefyToken = PacketCryptography.Decrypt(PR.ReadBytes(variefyTokenLength)); ConsoleWriteTime(); Console.WriteLine("@{2} 接收接受加密指令.\n 分享密碼長度: {0}, 已加密的金幣長度: {1}. ", sharedSecretLength, variefyTokenLength, UserName); // 檢查解密後的金幣是否跟傳出去的一樣 if(!variefyToken.SequenceEqual(PacketCryptography.VerifyToken)) { // 不一樣, 密鑰無效 ConsoleWriteTime(); Console.Write("@{0} 金幣不正確.", UserName); PW = kick(config.Settings["tokenInvalidText"].Value); } else { // 是一樣, 密鑰有效 ConsoleWriteTime(); Console.WriteLine("@{0} 金幣吻合.", UserName); ConsoleWriteTime(); Console.WriteLine("@{0} 檢查玩家是否正版...", UserName); // 檢查是否正版 // 查詢地址: http://session.minecraft.net/game/checkserver.jsp?user=(玩家ID)&serverId=(伺服器混湊值) // 伺服器混湊值 = (伺服器 ID 的 ASCII 碼) + (客戶端傳來的分享密碼) + (伺服器密鑰) -> ... // ... -> 轉換成 SHA -> 除掉前面的 0 再以二進制補碼補上負號 switch(verifyMinecraft(UserName, PacketCryptography.JavaHexDigest( Encoding.UTF8.GetBytes(ConnectionId) .Concat(PacketCryptography.Decrypt(sharedSecret)) .Concat(PacketCryptography.PublicKeyToAsn1(ServerKey)).ToArray()))) { case -1: // 與官方伺服器連接失敗 ConsoleWriteTime(); Console.WriteLine("@{0} 連接失敗.", UserName); PW = kick(config.Settings["connectionFailText"].Value); break; case 0: // 官方伺服器沒有玩家的登入進程記錄 (不是傳回 YES) ConsoleWriteTime(); Console.WriteLine("@{0} 得出結果不是正版.", UserName); PW = kick(config.Settings["verifyFailText"].Value); break; case 1: // 官方伺服器有玩家的登入進程記錄 (傳回 YES) ConsoleWriteTime(); Console.WriteLine("@{0} 得出結果是正版.", UserName); ConsoleWriteTime(); Console.WriteLine("@{0} 傳送驗證碼...", UserName); // 傳送驗證碼到指定的網頁 string randomcode = getRandomCapcha(); if(sendCode(config.Settings["successWebPage"].Value, UserName, randomcode, remoteIP)) { // 傳送成功, 把傳送出去的驗證碼也傳給客戶端 ConsoleWriteTime(); Console.WriteLine("@{0} 驗證碼已傳送.", UserName); PW = kick(String.Format(config.Settings["successText"].Value, randomcode)); } else { // 傳送失敗 ConsoleWriteTime(); Console.WriteLine("@{0} 連接失敗.", UserName); PW = kick(config.Settings["connectionFailText"].Value); } break; } } isDisconnect = true; break; case 0xFF: // 取消: 0xFF ConsoleWriteTime(); Console.WriteLine("@{0} 客戶端取消連接.", UserName); isDisconnect = true; break; default: // 其他 ConsoleWriteTime(); Console.WriteLine("@{1} 接收到無法辨識的請求, 以下是其內容:\n{0}", Utils.HexDump(Buffer), UserName); break; } Stream US = PW.UnderlyingStream; // 如果有傳回的資料, 把它封裝輸出成二進制 if(US.Length > 0) { US.Position = 0; _output = new byte[US.Length]; US.Read(_output, 0, (int)US.Length); haveToDisconnect = isDisconnect; return 2; } else if(isDisconnect) return 0; else return 1; } catch(Exception ex) { Console.WriteLine("@{1} 發生錯誤!\n{0}", ex.ToString(), UserName); } return 0; }