/// <summary> /// Отправляем список публичных комнат в чате. /// Сейчас фейкает только один чат рум GPG1 с названием "Room 1" /// Игра сама подставит красивое имя из HTTP данных о списке комнат /// </summary> /// <param name="validate">Строка валидации запроса. Должна быть получена от игры</param> void SendChatRooms(TcpPortHandler handler, TcpClientNode node, string validate) { var bytes = new List <byte>(); //var remoteEndPoint = handler.RemoteEndPoint; //bytes.AddRange(remoteEndPoint.Address.GetAddressBytes()); bytes.AddRange(IPAddress.Loopback.GetAddressBytes()); byte[] value2 = BitConverter.GetBytes((ushort)6500); bytes.AddRange(BitConverter.IsLittleEndian ? value2.Reverse() : value2); bytes.Add(5); // fields count bytes.Add(0); // Забивает поля, которые нужны игре. В этом же порядке надо будет пихнуть значения дальше для каждой комнаты bytes.AddRange("hostname".ToAsciiBytes()); bytes.Add(0); bytes.Add(0); bytes.AddRange("numwaiting".ToAsciiBytes()); bytes.Add(0); bytes.Add(0); bytes.AddRange("maxwaiting".ToAsciiBytes()); bytes.Add(0); bytes.Add(0); bytes.AddRange("numservers".ToAsciiBytes()); bytes.Add(0); bytes.Add(0); bytes.AddRange("numplayersname".ToAsciiBytes()); bytes.Add(0); bytes.Add(0); // Изначально было 10 комнат в игре, но мы сделаем только одну и весь код написан для синхронизации чата в игре с чатом из лаунчера // for (int i = 1; i <= 10; i++) // { // Странный байт в начале инфы о комнате bytes.Add(81); // инфа об IP комнаты, но игре на нее пофиг var b2 = BitConverter.GetBytes((long)1); bytes.Add(b2[3]); bytes.Add(b2[2]); bytes.Add(b2[1]); bytes.Add(b2[0]); // инфа о порте комнаты, но игре на нее пофиг bytes.Add(0); bytes.Add(0); // Скрытое название комнаты. Только такой формат принимает с цифрой в конце bytes.Add(255); bytes.AddRange("Room 1".ToAsciiBytes()); bytes.Add(0); // Количество игроков в комнате bytes.Add(255); bytes.AddRange(_emulationAdapter.ActivePlayersCount.ToString().ToAsciiBytes()); bytes.Add(0); bytes.Add(255); bytes.AddRange("1000".ToAsciiBytes()); bytes.Add(0); bytes.Add(255); bytes.AddRange("1".ToAsciiBytes()); bytes.Add(0); bytes.Add(255); bytes.AddRange("20".ToAsciiBytes()); bytes.Add(0); // } // Непонятный набор байт в конце, но без него не сработает bytes.AddRange(new byte[] { 0, 255, 255, 255, 255 }); var array = bytes.ToArray(); // Шифруем алгоритмом спая. Участвует строка валидации и уникальный ключ игры byte[] enc = GSEncoding.Encode(_gameGSkeyBytes, validate.ToAsciiBytes(), array, array.LongLength); handler.Send(node, enc); handler.KillClient(node); }
void RestartServices(TcpPortHandler handler, TcpClientNode node) { // Получение 0 байт говорит о том, что клиент хочет прекратить взаимодействие. // Сбрасываем все соединения Restart(); }
/// <summary> /// Обратывает строку IRC чата и вызывает соответствующий метод обработчик команды /// </summary> void HandleChatLine(TcpPortHandler handler, TcpClientNode node, string line) { var values = GetIrcChatLineValues(line); if (line.StartsWith("LOGIN", StringComparison.OrdinalIgnoreCase)) { HandleLoginCommand(handler, node, values); return; } if (line.StartsWith("USRIP", StringComparison.OrdinalIgnoreCase)) { HandleUsripCommand(handler, node, values); return; } if (line.StartsWith("CRYPT", StringComparison.OrdinalIgnoreCase)) { HandleCryptCommand(handler, node, values); return; } if (line.StartsWith("USER", StringComparison.OrdinalIgnoreCase)) { HandleUserCommand(handler, node, values); return; } if (line.StartsWith("NICK", StringComparison.OrdinalIgnoreCase)) { HandleNickCommand(handler, node, values); return; } if (line.StartsWith("CDKEY", StringComparison.OrdinalIgnoreCase)) { HandleCdkeyCommand(handler, node, values); return; } if (line.StartsWith("JOIN", StringComparison.OrdinalIgnoreCase)) { HandleJoinCommand(handler, node, line, values); return; } if (line.StartsWith("MODE", StringComparison.OrdinalIgnoreCase)) { HandleModeCommand(handler, node, values); return; } if (line.StartsWith("QUIT", StringComparison.OrdinalIgnoreCase)) { HandleQuitCommand(handler, values); return; } if (line.StartsWith("PRIVMSG", StringComparison.OrdinalIgnoreCase)) { HandlePrivmsgCommand(handler, values); return; } if (line.StartsWith("SETCKEY", StringComparison.OrdinalIgnoreCase)) { HandleSetckeyCommand(handler, node, line, values); return; } if (line.StartsWith("GETCKEY", StringComparison.OrdinalIgnoreCase)) { HandleGetckeyCommand(handler, node, values); return; } if (line.StartsWith("TOPIC", StringComparison.OrdinalIgnoreCase)) { HandleTopicCommand(handler, node, values); return; } if (line.StartsWith("PART", StringComparison.OrdinalIgnoreCase)) { HandlePartCommand(handler, values); return; } if (line.StartsWith("UTM", StringComparison.OrdinalIgnoreCase)) { HandleUtmCommand(handler, line); return; } if (line.StartsWith("PING", StringComparison.OrdinalIgnoreCase)) { HandlePingCommand(handler, node, values); return; } Debugger.Break(); }
void OnServerRetrieveReceived(TcpPortHandler handler, TcpClientNode node, byte[] buffer, int count) { var str = buffer.ToASCII(count); LogTrace("RETRIEVE " + str); var endPoint = node.RemoteEndPoint; if (endPoint == null) { handler.KillClient(node); return; } string[] data = str.Split(new char[] { '\x00' }, StringSplitOptions.RemoveEmptyEntries); string validate = data[4]; string filter = null; bool isAutomatch = false; if (validate.Length > 8) { filter = validate.Substring(8); validate = validate.Substring(0, 8); } else { //Log(Category, "ROOMS REQUEST - "+ data[2]); isAutomatch = data[2].EndsWith("am"); if (!isAutomatch) { SendChatRooms(handler, node, validate); return; } } var lobbies = _emulationAdapter.GetOpenedLobbies(); try { // var currentRating = ServerContext.ChatServer.CurrentRating; /*for (int i = 0; i < lobbies.Length; i++) * { * var server = lobbies[i]; * * //server["score_"] = GetCurrentRating(server.MaxPlayers); * }*/ var fields = data[5].Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); var unencryptedBytes = ParseHelper.PackServerList(endPoint, lobbies, fields, isAutomatch); _lastLoadedLobbies.Clear(); for (int i = 0; i < lobbies.Length; i++) { var server = lobbies[i]; var address = server.HostIP ?? server.LocalIP; var port = ushort.Parse(server.HostPort ?? server.LocalPort); var channelHash = ChatCrypt.PiStagingRoomHash(address, address, port); Log($"HASHFOR {address}:{port} {channelHash}"); server.RoomHash = channelHash; _lastLoadedLobbies[channelHash] = server; } Log("SERVERS VALIDATE VALUE ~" + validate + "~"); var encryptedBytes = GSEncoding.Encode(_gameGSkeyBytes, validate.ToAsciiBytes(), unencryptedBytes, unencryptedBytes.LongLength); Log("SERVERS bytes " + encryptedBytes.Length); int autoGames = 0; int customGames = 0; // TODO вынести в отдельно for (int i = 0; i < lobbies.Length; i++) { var server = lobbies[i]; if (server.Ranked) { autoGames++; } else { customGames++; } } //CoreContext.ClientServer.SendAsServerMessage( // "Received game list: " + customGames + " - custom; " + autoGames + // " - auto; Mod: "+ CoreContext.ThunderHawkModManager.CurrentModName); handler.Send(node, encryptedBytes); } finally { handler.KillClient(node); } }
void KillNode(TcpPortHandler handler, TcpClientNode node) { // Получение 0 байт говорит о том, что клиент хочет прекратить взаимодействие. handler.KillClient(node); }
void OnReceive(TcpClientNode node, Task <int> task) { try { if (task.IsCanceled) { return; } if (task.IsFaulted) { throw task.Exception.GetInnerException(); } var count = task.Result; if (count == 0) { _zeroHandlerDelegate?.Invoke(this, node); } else { _handlerDelegate(this, node, node.Buffer, count); } } catch (OperationCanceledException) { KillClient(node); } catch (InvalidOperationException) { KillClient(node); } catch (SocketException) { KillClient(node); } catch (Exception ex) { KillClient(node); _exceptionHandlerDelegate?.Invoke(ex, false, _port); } finally { try { if (node.Client.Connected) { var source = _tokenSource; if (source != null) { node.Client.ReceiveAsync(new ArraySegment <byte>(node.Buffer, 0, node.Buffer.Length), SocketFlags.None).ContinueWith(t => OnReceive(node, t)); } } } catch (OperationCanceledException ex) { KillClient(node); } catch (InvalidOperationException ex) { KillClient(node); } catch (SocketException ex) { KillClient(node); } catch (Exception ex) { KillClient(node); _exceptionHandlerDelegate?.Invoke(ex, false, _port); } } }
/// <summary> /// Обработка HTTP запроса от игры на порт 80. /// Обрабатывает новостное сообщение, наличие патча, запрос страницы статистики, настройки автоматча и список имен комнат чата. /// </summary> void OnHttpReceived(TcpPortHandler handler, TcpClientNode node, byte[] buffer, int count) { try { var str = buffer.ToUtf8(count); LogTrace("HTTP CLIENT HASH " + node.GetHashCode()); LogTrace("HTTP " + str); HttpRequest request; using (var ms = new MemoryStream(buffer, 0, count, false, true)) request = HttpHelper.GetRequest(ms); using (var ms = new MemoryStream()) { // Запрос страницы статистики по кнопке из игры if (request.Url.StartsWith("/SS_StatsPage", StringComparison.OrdinalIgnoreCase)) { HttpHelper.WriteResponse(ms, HttpResponceBuilder.DowstatsRedirect()); goto END; } // Запрос текста новостей if (request.Url.EndsWith("news.txt", StringComparison.OrdinalIgnoreCase)) { LogForUser($"News requested"); // Фикс для рускоязычных if (request.Url.EndsWith("Russiandow_news.txt", StringComparison.OrdinalIgnoreCase)) { HttpHelper.WriteResponse(ms, HttpResponceBuilder.Text(GameSpyHttpDataConstants.RusNews, Encoding.Unicode)); } else { HttpHelper.WriteResponse(ms, HttpResponceBuilder.Text(GameSpyHttpDataConstants.EnNews, Encoding.Unicode)); } goto END; } // Отправка сообщения дня. Вроде нигде не отображается if (request.Url.StartsWith("/motd/motd", StringComparison.OrdinalIgnoreCase)) { HttpHelper.WriteResponse(ms, HttpResponceBuilder.Text(GameSpyHttpDataConstants.RusNews, Encoding.Unicode)); goto END; } // Проверка на существование патча. Можно прокидывать свои патчи для игры if (request.Url.StartsWith("/motd/vercheck", StringComparison.OrdinalIgnoreCase)) { LogForUser($"Vercheck requested"); // Пример отправки ссылки на скачивания патча //HttpHelper.WriteResponse(ms, HttpResponceBuilder.Text(@"\newver\1\newvername\1.4\dlurl\http://127.0.0.1/NewPatchHere.exe")); // Отправка инфы о том, что патча сейчас нет HttpHelper.WriteResponse(ms, HttpResponceBuilder.Text(@"\newver\0", Encoding.UTF8)); goto END; } // Запрос списка комнат с именами для отображения в интерфейсе if (request.Url.EndsWith("LobbyRooms.lua", StringComparison.OrdinalIgnoreCase)) { LogForUser($"LobbyRooms requested"); HttpHelper.WriteResponse(ms, HttpResponceBuilder.Text(GameSpyHttpDataConstants.RoomPairs, Encoding.ASCII)); goto END; } // Запрос дефолных настроек автоматча if (request.Url.EndsWith("AutomatchDefaultsSS.lua", StringComparison.OrdinalIgnoreCase) || request.Url.EndsWith("AutomatchDefaultsDXP2Fixed.lua", StringComparison.OrdinalIgnoreCase)) { LogForUser($"AutomatchDefaults requested"); //HttpHelper.WriteResponse(ms, HttpResponceBuilder.TextFileBytes(CoreContext.MasterServer.AutomatchDefaultsBytes)); HttpHelper.WriteResponse(ms, HttpResponceBuilder.Text(GameSpyHttpDataConstants.AutomatchDefaults, Encoding.ASCII)); goto END; } /*if (request.Url.EndsWith("homepage.php.htm", StringComparison.OrdinalIgnoreCase)) * { * if (StatsResponce == null || (DateTime.Now - _lastStatsUpdate).TotalMinutes > 5) * StatsResponce = BuildTop10StatsResponce(); * * HttpHelper.WriteResponse(ms, StatsResponce); * goto END; * }*/ // Если дошли сюда - отправляет NotFound HttpHelper.WriteResponse(ms, HttpResponceBuilder.NotFound()); END: LogTrace("HTTP WANT TO SEND " + node.GetHashCode() + " " + ms.Length); handler.Send(node, ms.ToArray()); handler.KillClient(node); } } catch (InvalidDataException ex) { //Log(ex); } }
public bool SendAskii(TcpClientNode node, string message) { return(Send(node, message.ToAsciiBytes())); }
public bool SendUtf8(TcpClientNode node, string message) { return(Send(node, message.ToUTF8Bytes())); }
/// <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; } }
/// <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\"); }
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(); }