/// <summary> /// Обрабатывает команду перехода в шифрованный чат /// </summary> unsafe void HandleCryptCommand(TcpPortHandler handler, TcpClientNode node, string[] values) { _chatEncoded = true; _gameGSkeyBytes = null; // В зависимости от игры. Байты шифрования отличаются. Таблица есть в инете // https://gamerecon.net/support/topic/gamespy-supported-games-list/ // https://github.com/luisj135/nintendo_dwc_emulator/blob/master/gslist.cfg // Dawn of War if (values.Contains("dow")) { _gameGSkey = "dow"; _gameGSNameBytes = "dow".ToAsciiBytes(); _gameGSkeyBytes = "VLxgwe".ToAsciiBytes(); } // Dawn of War (более похоже на правду) if (values.Contains("whammer40000")) { _gameGSkey = "whammer40000"; _gameGSNameBytes = "whammer40000".ToAsciiBytes(); _gameGSkeyBytes = "uJ8d3N".ToAsciiBytes(); } // Winter Assault // ключа почему-то нет в таблице. Возьмем ключ 1 дова. // TODO: Надо потестить WA if (values.Contains("dowwad")) { _gameGSkey = "dowwad"; _gameGSNameBytes = "dowwad".ToAsciiBytes(); _gameGSkeyBytes = "uJ8d3N".ToAsciiBytes(); } // Dark Crusade if (values.Contains("whammer40kdc")) { _gameGSkey = "whammer40kdc"; _gameGSNameBytes = "whammer40kdc".ToAsciiBytes(); _gameGSkeyBytes = "Ue9v3H".ToAsciiBytes(); } // Soulstorm if (values.Contains("whamdowfr")) { _gameGSkey = "whamdowfr"; _gameGSNameBytes = "whamdowfr".ToAsciiBytes(); _gameGSkeyBytes = "pXL838".ToAsciiBytes(); } if (_gameGSkeyBytes == null) { Restart(); return; } // Ключ шифрования. Для простоты просто нули. Не тестил, что будет, если не нули. var chall = "0000000000000000".ToAsciiBytes(); var clientKey = new ChatCrypt.GDCryptKey(); var serverKey = new ChatCrypt.GDCryptKey(); // Инициализируем структуры-ключи для выполнения алгоритма спая fixed(byte *challPtr = chall) { fixed(byte *gamekeyPtr = _gameGSkeyBytes) { ChatCrypt.GSCryptKeyInit(clientKey, challPtr, gamekeyPtr, _gameGSkeyBytes.Length); ChatCrypt.GSCryptKeyInit(serverKey, challPtr, gamekeyPtr, _gameGSkeyBytes.Length); } } // Сохраняем структуры _chatClientKey = clientKey; _chatServerKey = serverKey; // Отправляем идентичные ключи для сервера и клиента. Шифрование в итоге будет полностью совпадать. // С этого момента чат шифрованный handler.SendAskii(node, ":s 705 * 0000000000000000 0000000000000000\r\n"); }
/// <summary> /// Обрабатывает входящие TCP соединения для сервера LOGIN (Client) /// </summary> void OnClientAccept(TcpPortHandler handler, TcpClientNode node, CancellationToken token) { //Обновляем челендж для нового соединения _serverChallenge = RandomHelper.GetString(10); handler.SendAskii(node, $@"\lc\1\challenge\{_serverChallenge}\id\1\final\"); }
/// <summary> /// Обрабатывает одно сообщение сервера /// </summary> private void HandleClientManagerMessage(TcpPortHandler handler, TcpClientNode node, string mes) { LogTrace("CLIENT " + mes); var pairs = ParseHelper.ParseMessage(mes, out string query); if (pairs == null || string.IsNullOrWhiteSpace(query)) { return; } // Исправление бага, когда игра по какой-то причине соединяет логин и почту в одну строку. Разбиваем, иначе не будет работать алгоритм хэширования при логине if (pairs.ContainsKey("name") && !pairs.ContainsKey("email")) { var parts = pairs["name"].Split('@'); if (parts.Length > 2) { pairs["name"] = parts[0]; pairs["email"] = parts[1] + "@" + parts[2]; } } switch (query) { case "login": HandleLogin(node, pairs); RestartUserSessionTimer(node); break; case "logout": _emulationAdapter.LeaveFromCurrentLobby(); _emulationAdapter.OnLogout(); break; case "registernick": handler.SendAskii(node, string.Format(@"\rn\{0}\id\{1}\final\", pairs["uniquenick"], pairs["id"])); break; case "ka": handler.SendAskii(node, $@"\ka\\final\"); break; case "status": HandleStatus(node, pairs); break; case "newuser": { var nick = pairs["nick"]; var email = pairs["email"]; var password = GSUtils.DecryptPassword(pairs["passwordenc"]); var passHash = password.ToMD5(); _emulationAdapter.TryCreateProfile(nick, email, passHash); } break; case "getprofile": // TODO break; default: Debugger.Break(); break; } }
void OnSearchManagerReceived(TcpPortHandler handler, TcpClientNode node, byte[] buffer, int count) { var str = buffer.ToUtf8(count); var pairs = ParseHelper.ParseMessage(str, out string query); switch (query) { case "nicks": { // \\nicks\\\\email\\[email protected]\\passenc\\J4PGhRi[\\namespaceid\\7\\partnerid\\0\\gamename\\whamdowfr\\final\\ if (!pairs.ContainsKey("email") || (!pairs.ContainsKey("passenc") && !pairs.ContainsKey("pass"))) { handler.SendAskii(node, @"\error\\err\0\fatal\\errmsg\Invalid Query!\id\1\final\"); return; } // Чей-то тестовый код /* * string password = String.Empty; * if (pairs.ContainsKey("passenc")) * { * password = GSUtils.DecryptPassword(pairs["passenc"]); * } * else if (pairs.ContainsKey("pass")) * { * password = pairs["pass"]; * } * * password = password.ToMD5();*/ _emulationAdapter.RequestAllUserNicks(pairs["email"]); return; } case "check": { string name = String.Empty; if (String.IsNullOrWhiteSpace(name)) { if (pairs.ContainsKey("uniquenick")) { name = pairs["uniquenick"]; } } if (String.IsNullOrWhiteSpace(name)) { if (pairs.ContainsKey("nick")) { name = pairs["nick"]; } } if (String.IsNullOrWhiteSpace(name)) { handler.SendAskii(node, @"\error\\err\0\fatal\\errmsg\Invalid Query!\id\1\final\"); return; } _emulationAdapter.RequestNameCheck(name); return; } default: break; } Debugger.Break(); }