/// <summary>
        /// Обрабатывает полученное сообщение по TCP соединению для запросов статистики
        /// </summary>
        void OnStatsReceived(TcpPortHandler handler, TcpClientNode node, byte[] buffer, int count)
        {
            var str = Encoding.UTF8.GetString(GSUtils.XorBytes(buffer, 0, count - 7, "GameSpy3D"), 0, count);

            LogTrace("STATS " + str);

            // Авторизация на сервере. Выдаем случайный ключ сессии (не обязательно)
            if (str.StartsWith(@"\auth\\gamename\", StringComparison.OrdinalIgnoreCase))
            {
                var sesskey = Interlocked.Increment(ref _sessionCounter).ToString("0000000000");

                handler.Send(node, GSUtils.XorBytes($@"\lc\2\sesskey\{sesskey}\proof\0\id\1\final\", "GameSpy3D", 7));
                return;
            }

            // Игра присылает серверу ID авторизованного профиля. Но мы и так его знаем, поэтому просто фейкаем успех
            if (str.StartsWith(@"\authp\\pid\", StringComparison.OrdinalIgnoreCase))
            {
                var pid       = GetPidFromInput(str, 12);
                var profileId = long.Parse(pid);

                handler.Send(node, GSUtils.XorBytes($@"\pauthr\{pid}\lid\1\final\", "GameSpy3D", 7));
                return;
            }

            // Запрос данных профиля. Надо отправить данные по списку запрошенных ключей.
            // Всегда запрашивается статистика
            if (str.StartsWith(@"\getpd\", StringComparison.OrdinalIgnoreCase))
            {
                // \\getpd\\\\pid\\87654321\\ptype\\3\\dindex\\0\\keys\\\u0001points\u0001points2\u0001points3\u0001stars\u0001games\u0001wins\u0001disconn\u0001a_durat\u0001m_streak\u0001f_race\u0001SM_wins\u0001Chaos_wins\u0001Ork_wins\u0001Tau_wins\u0001SoB_wins\u0001DE_wins\u0001Eldar_wins\u0001IG_wins\u0001Necron_wins\u0001lsw\u0001rnkd_vics\u0001con_rnkd_vics\u0001team_vics\u0001mdls1\u0001mdls2\u0001rg\u0001pw\\lid\\1\\final\\
                // \getpd\\pid\87654321\ptype\3\dindex\0\keys\pointspoints2points3starsgameswinsdisconna_duratm_streakf_raceSM_winsChaos_winsOrk_winsTau_winsSoB_winsDE_winsEldar_winsIG_winsNecron_winslswrnkd_vicscon_rnkd_vicsteam_vicsmdls1mdls2rgpw\lid\1\final\
                var profileId = GetPidFromInput(str, 12);

                var keysIndex = str.IndexOf("keys") + 5;
                var keys      = str.Substring(keysIndex);
                var keysList  = keys.Split(new string[] { "\u0001", "\\lid\\1\\final\\", "final", "\\", "lid" }, StringSplitOptions.RemoveEmptyEntries);

                var keysResult = new StringBuilder();
                var stats      = _emulationAdapter.GetUserStatsInfo(profileId.ParseToLongOrDefault());

                for (int i = 0; i < keysList.Length; i++)
                {
                    var key = keysList[i];

                    keysResult.Append("\\" + key + "\\");

                    switch (key)
                    {
                    case "points": keysResult.Append(stats.Score1v1); break;

                    case "points2": keysResult.Append(stats.Score2v2); break;

                    case "points3": keysResult.Append(stats.Score3v3_4v4); break;

                    case "stars": keysResult.Append(stats.StarsCount); break;

                    case "games": keysResult.Append(stats.GamesCount); break;

                    case "wins": keysResult.Append(stats.WinsCount); break;

                    case "disconn": keysResult.Append(stats.Disconnects); break;

                    case "a_durat": keysResult.Append(stats.AverageDuration); break;

                    case "m_streak": keysResult.Append(stats.Best1v1Winstreak); break;

                    case "f_race": keysResult.Append(stats.FavouriteRace); break;

                    // Ключи, которые не используюся игрой, но запрашиваются. Может на что-то и влияет, но я ничего не обнаружил

                    /* case "SM_wins": keysResult.Append("0"); break;
                     * case "Chaos_wins": keysResult.Append("0"); break;
                     * case "Ork_wins": keysResult.Append("0"); break;
                     * case "Tau_wins": keysResult.Append("0"); break;
                     * case "SoB_wins": keysResult.Append("0"); break;
                     * case "DE_wins": keysResult.Append("0"); break;
                     * case "Eldar_wins": keysResult.Append("0"); break;
                     * case "IG_wins": keysResult.Append("0"); break;
                     * case "Necron_wins": keysResult.Append("0"); break;
                     * case "lsw": keysResult.Append("0"); break;
                     * case "rnkd_vics": keysResult.Append("0"); break;
                     * case "con_rnkd_vics": keysResult.Append("0"); break;
                     * case "team_vics": keysResult.Append("0"); break;
                     * case "mdls1": keysResult.Append("0"); break;
                     * case "mdls2": keysResult.Append("0"); break;
                     * case "rg": keysResult.Append("0"); break;
                     * case "pw": keysResult.Append("0"); break;*/
                    default:
                        keysResult.Append("0");
                        break;
                    }
                }

                handler.Send(node, GSUtils.XorBytes($@"\getpdr\1\lid\1\pid\{profileId}\mod\{stats.ModifiedTimeTick}\length\{keys.Length}\data\{keysResult}\final\", "GameSpy3D", 7));

                return;
            }

            // Игра присылает обновление данных профиля по списку ключей.
            // Игнорируем, потому что статистика обновляется другим способов. Просто фейкаем успех
            if (str.StartsWith(@"\setpd\", StringComparison.OrdinalIgnoreCase))
            {
                var pid = GetPidFromInput(str, 12);

                var lidIndex = str.IndexOf("\\lid\\", StringComparison.OrdinalIgnoreCase);
                var lid      = str.Substring(lidIndex + 5, 1);

                var timeInSeconds = (ulong)((DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds);
                // \setpd\\pid\3\ptype\1\dindex\0\kv\1\lid\1\length\413\data\\ckey\5604 - 7796 - 6425 - 0127 - DA96\system\Nr.Proc:8, Type: 586, GenuineIntel, unknown: f = 6,m = 12, Fam: 6, Mdl: 12, St: 3, Fe: 7, OS: 7, Ch: 15\speed\CPUSpeed: 3.5Mhz\os\OS NT 6.2\lang\Language: Русский(Россия), Country: Россия, User Language:Русский(Россия), User Country:Россия\vid\Card: Dx9: Hardware TnL, NVIDIA GeForce GTX 1080, \vidmod\Mode: 1920 x 1080 x 32\mem\2048Mb phys. memory
                handler.Send(node, GSUtils.XorBytes($@"\setpdr\1\lid\{lid}\pid\{pid}\mod\{timeInSeconds}\final\", "GameSpy3D", 7));

                return;
            }

            // Игры присылает информацию о завершенной игре (камстока или автоматч)
            // Отсюда можно обновлять статистику игроков. Может быть прислано несколько раз от разных игроков в конце одного и того же матча,
            // поэтому необходима защита на сервере от двойного засчитывания статистики
            if (str.StartsWith(@"\updgame\", StringComparison.OrdinalIgnoreCase))
            {
                var gamedataIndex = str.IndexOf("gamedata");

                // Обрезаем конец сообщения
                var finalIndex     = str.IndexOf("final");
                var gameDataString = str.Substring(gamedataIndex + 9, finalIndex - gamedataIndex - 10);

                var valuesList = gameDataString.Split(new string[] { "\u0001", "\\lid\\1\\final\\", "\\" }, StringSplitOptions.None);

                // Преобразываем все пары ключ-значение в словарь для удобства
                var dictionary = new Dictionary <string, string>();

                for (int i = 0; i < valuesList.Length - 1; i += 2)
                {
                    if (i == valuesList.Length - 1)
                    {
                        continue;
                    }

                    dictionary[valuesList[i]] = valuesList[i + 1];
                }

                if (!dictionary.TryGetValue("Mod", out string v))
                {
                    dictionary.Clear();

                    for (int i = 1; i < valuesList.Length - 1; i += 2)
                    {
                        if (i == valuesList.Length - 1)
                        {
                            continue;
                        }

                        dictionary[valuesList[i]] = valuesList[i + 1];
                    }
                }

                // Использованный мод и его версия
                var mod        = dictionary["Mod"];
                var modVersion = dictionary["ModVer"];

                var playersCount = int.Parse(dictionary["Players"]);

                for (int i = 0; i < playersCount; i++)
                {
                    // Dont process games with AI
                    if (dictionary["PHuman_" + i] != "1")
                    {
                        LogTrace($"Stats socket: GAME WITH NONHUMAN PLAYER");
                        return;
                    }
                }

                var gameInternalSession = dictionary["SessionID"];
                var teamsCount          = int.Parse(dictionary["Teams"]);
                var version             = dictionary["Version"];

                // Строим уникальные идентификатор сессии игры, чтобы в дальнейшем не зачислить дважды одну и ту же игру
                var uniqueGameSessionBuilder = new StringBuilder(gameInternalSession);

                for (int i = 0; i < playersCount; i++)
                {
                    uniqueGameSessionBuilder.Append('<');
                    uniqueGameSessionBuilder.Append(dictionary["player_" + i]);
                    uniqueGameSessionBuilder.Append('>');
                }

                var uniqueSession = uniqueGameSessionBuilder.ToString();

                // Строим объекты с данным игроков
                var players = new PlayerData[playersCount];

                for (int i = 0; i < players.Length; i++)
                {
                    var player = new PlayerData();

                    player.Name       = dictionary["player_" + i];
                    player.Race       = dictionary["PRace_" + i];
                    player.Team       = int.Parse(dictionary["PTeam_" + i]);
                    player.FinalState = (PlayerFinalState)Enum.Parse(typeof(PlayerFinalState), dictionary["PFnlState_" + i]);

                    players[i] = player;
                }

                // Собираем окончательный объект с данными
                var gameFinishedMessage = new GameFinishedData
                {
                    Map        = dictionary["Scenario"],
                    SessionId  = uniqueSession,
                    Duration   = long.Parse(dictionary["Duration"]),
                    ModName    = dictionary["Mod"],
                    ModVersion = dictionary["ModVer"],
                    Players    = players,
                    IsRateGame = dictionary["Ladder"] == "1"
                };

                _emulationAdapter.SendGameFinishedData(gameFinishedMessage);

                //DowstatsReplaySender.SendReplay(gameFinishedMessage);

                return;
            }

            // Создание новой игры до регистрации статистики. Никакой полезной информации нет, поэтому игнорируем.
            // Вся логика произойдет в момент отправки данных об игре.
            if (str.StartsWith(@"\newgame\", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            // На случай, если есть еще какие-то команды
            Debugger.Break();
        }
 /// <summary>
 /// Обрабатывает получение нового TPC соединения для получения статистики игроков
 /// </summary>
 void OnStatsAccept(TcpPortHandler handler, TcpClientNode node, CancellationToken token)
 {
     // Отправляем challenge сервера шифрованный через Xor. Надо отправить, но особо ни на что не влияет
     // Все сообщения также надо будет прогонять через Xor.
     handler.Send(GSUtils.XorBytes(@"\lc\1\challenge\KNDVKXFQWP\id\1\final\", "GameSpy3D", 7));
 }
Пример #3
0
        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);
            }
        }
Пример #4
0
        /// <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);
        }
        /// <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);
            }
        }