/// <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));
 }
        /// <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();
        }
예제 #3
0
        /// <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;
            }
        }