public GameSpyServer(IEmulationAdapter handler) { _emulationAdapter = handler; handler.GameSpyServer = this; _serverReport = new UdpPortHandler(27900, OnServerReportReceived, OnError); _serverRetrieve = new TcpPortHandler(28910, new RetrieveTcpSetting(), OnServerRetrieveReceived, OnServerRetrieveError); _clientManager = new TcpPortHandler(29900, new LoginTcpSetting(), OnClientManagerReceived, OnError, OnClientAccept, KillNode); _searchManager = new TcpPortHandler(29901, new LoginTcpSetting(), OnSearchManagerReceived, OnError, null, KillNode); _chat = new TcpPortHandler(6667, new ChatTcpSetting(), OnChatReceived, OnError, OnChatAccept, RestartServices); _stats = new TcpPortHandler(29920, new StatsTcpSetting(), OnStatsReceived, OnError, OnStatsAccept, RestartServices); _http = new TcpPortHandler(80, new HttpTcpSetting(), OnHttpReceived, OnError, null, KillNode); LogTrace("Services inited"); }
/// <summary> /// Обрабатывает состояния создания хоста (отдельно от логики автомачт-комнат чата) /// Актуально и для авто и для кастомок. Для камтомок нет только входа в приватный-чат лобби. /// </summary> void OnServerReportReceived(UdpPortHandler handler, byte[] receivedBytes, IPEndPoint remote) { var str = receivedBytes.ToUtf8(receivedBytes.Length); Log("REPORT " + str); // Проверка доступности сервиса if (receivedBytes[0] == (byte)MessageType.AVAILABLE) { LogTrace("REPORT: Send AVAILABLE"); // Фейкаем доступность handler.Send(new byte[] { 0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00 }, remote); } else if (receivedBytes.Length > 5 && receivedBytes[0] == (byte)MessageType.HEARTBEAT) { // Сердебиение соединения. Если не ответим, хост закроется и игра выкенет в главный чат. // this is where server details come in, it starts with 0x03, it happens every 60 seconds or so var receivedData = Encoding.UTF8.GetString(receivedBytes.Skip(5).ToArray()); var sections = receivedData.Split(new string[] { "\x00\x00\x00", "\x00\x00\x02" }, StringSplitOptions.None); if (sections.Length != 3 && !receivedData.EndsWith("\x00\x00")) { return; // true means we don't send back a response } // Первый раз просто отвечаем. Игра еще не готова открыть хост для других, но прокидывает уникальный ID if (!_challengeResponded) { byte[] uniqueId = new byte[4]; Array.Copy(receivedBytes, 1, uniqueId, 0, 4); // Фейкаем успех byte[] response = new byte[] { 0xfe, 0xfd, (byte)MessageType.CHALLENGE_RESPONSE, uniqueId[0], uniqueId[1], uniqueId[2], uniqueId[3], 0x41, 0x43, 0x4E, 0x2B, 0x78, 0x38, 0x44, 0x6D, 0x57, 0x49, 0x76, 0x6D, 0x64, 0x5A, 0x41, 0x51, 0x45, 0x37, 0x68, 0x41, 0x00 }; LogTrace("REPORT: Send challenge responce"); handler.Send(response, remote); _challengeResponded = true; } else { // Игра прислала нам полные данные и хост почти готов string serverVars = sections[0]; //string playerVars = sections[1]; //string teamVars = sections[2]; var details = ParseHelper.ParseDetails(serverVars); if (details.StateChanged == "2") { // Данные изменились и хост пора закрывать LogForUser($"Clear local server"); LogTrace("REPORT: ClearServerDetails"); _emulationAdapter.OnLocalLobbyClearedByGame(); _challengeResponded = false; } else { // Хост активен LogTrace("REPORT: UpdateCurrentLobby"); details["IPAddress"] = remote.Address.ToString(); details["QueryPort"] = remote.Port.ToString(); details["LastRefreshed"] = DateTime.UtcNow.ToString(); details["LastPing"] = DateTime.UtcNow.ToString(); details["country"] = "??"; details["hostport"] = remote.Port.ToString(); details["localport"] = remote.Port.ToString(); // Фикс бага с рейтингом хоста (актуально только для SS). При этом при прямом подключении P2P это значение тоже надо пофиксить иначе багнется var serverScore = GetLocalUserRating(details.MaxPlayers); details["score_"] = serverScore; // Наш флаг, игре не нужен details.LobbyLimited = _emulationAdapter.ShouldLimitLocalLobbyByRating(); details.HostId = _emulationAdapter.GetLocalCreatedLobbyId(); _emulationAdapter.UpdateLocalLobbyDetails(details); LogForUser($"Update local server [{details.IsValid}]"); // Когда готовы принимать гостей в автоматче if (details.IsValid && details.Ranked) { _emulationAdapter.OnAutomatchLobbyValidated(); } } } } else if (receivedBytes.Length > 5 && receivedBytes[0] == (byte)MessageType.CHALLENGE_RESPONSE) { // Игре нужен уникальный набор байт CHALLENGE. Несколько раз прокидывается игрой LogTrace("REPORT: Validate challenge responce"); byte[] uniqueId = new byte[4]; Array.Copy(receivedBytes, 1, uniqueId, 0, 4); byte[] validate = Encoding.UTF8.GetBytes("Iare43/78WkOVaU1Aanv8vrXbSwA\0"); byte[] validateDC = Encoding.UTF8.GetBytes("Egn4q1jDYyOIVczkXvlGbBxavC4A\0"); byte[] clientResponse = new byte[validate.Length]; Array.Copy(receivedBytes, 5, clientResponse, 0, clientResponse.Length); var resStr = Encoding.UTF8.GetString(clientResponse); // if we validate, reply back a good response if (clientResponse.SequenceEqual(validate) || clientResponse.SequenceEqual(validateDC)) { // Фейкаем успех byte[] response = new byte[] { 0xfe, 0xfd, 0x0a, uniqueId[0], uniqueId[1], uniqueId[2], uniqueId[3] }; handler.Send(response, remote); if (!_emulationAdapter.HasHostedLobby) { _emulationAdapter.CreateLobby(_name); } } else { LogTrace("REPORT: Validation failed"); } } else if (receivedBytes.Length == 5 && receivedBytes[0] == (byte)MessageType.KEEPALIVE) { // Еще одно поддержание соединения, типа обновления пинга, чтобы убивать мертвые хосты, когда они перестают пинговать // this is a server ping, it starts with 0x08, it happens every 20 seconds or so LogTrace("REPORT: KEEPALIVE"); byte[] uniqueId = new byte[4]; Array.Copy(receivedBytes, 1, uniqueId, 0, 4); RefreshServerPing(remote); } }