Доступ к архиву

Переход в группу "Пользователь"

300.00
Одноразовый платёж
Быстрый переход в группу "Пользователи", без надобности написания постов и ожидания.

Покупка дает возможность:
Быть полноправным участником форума
Нормальное копирование кода
Создавать темы
Скачивать файлы
Доступ к архиву Pawno-Info

Как работает мультиплеер, о читах\античитах и даже о блок чейне

Статус
В этой теме нельзя размещать новые ответы.

Nestyreff

Начинающий
Пользователь
Регистрация
18 Окт 2017
Сообщения
19
Лучшие ответы
0
Репутация
9
Добрый день! Данный урок составлен с целью прояснить Вам о том, как работает мультиплеер, что такое RPC пакеты, ping и так далее для разработчиков и не только!


Будет полезно как начинающим, так и опытным людям в этом вопросе, так как здесь будет представлена информация из моих личных экспериментов и анализов.




1. Мультиплеер.

Для начала нужно понять, что такое мультиплеер, а вернее как он работает. Основная идея в том, чтобы синхронизировать игру нескольких игроков. Каждый игрок принимает и отправляет информацию серверу, который хранит в себе те или иные данные. Когда вы скачиваете игру с мультиплеером, будь то SAMP или Gta V online, cs go, вы скачиваете некий лаунчер, который имеет свою информацию/файлы/скрипты, которые активно будут использоваться в будущем. Все это вместе называется клиент (клиентская часть или client-side).

Также существует централизованный сервер, в котором расположены скрипты, работающие с информацией, тут не буду говорить много, но в общем это папка, в которой расположены все коды и не только. Некоторые игры уже переходят на новый план и обходятся без сервера, передавая информацию игроков напрямую между ними. Когда мы заходим в игру, наш клиент начинает отправлять пакеты данных на сервер. В этих пакетах содержится некоторая информация, хранящаяся на клиенте, в случае сампа это данные о версии клиента, никнейм, настройки подключения к серверу и ещё кое какая информация. Когда этот пакет дойдет до сервера, сервер начнет его обрабатывать, через время (почти моментально) срабатывает коллбэк OnPlayerConnect. В общем-то само понятие "присоединиться к игре" это отправить пакет на сервер и начать получать обратные.

Как только наш клиент получает ответный от сервера пакет, нам отправляется сообщение(в случае сампа): "Connected. Joining the Game...". После этого сообщения, сервер начинает штурмовать наш клиент постоянными пакетами, которые передают информацию не только о других игроках, но и о своей личной информации. Когда Вы будете заспавнены, то сервер в соответствии с вашими данными о местоположении (синхронизируются в ONFOOT_SYNC, если Вы бегаете пешочком) будет рассказывать о маппинге, пикапах, авто, игроках и так далее… Радиус в котором сервер рассказывает Вам инфу называется зоной стриминга. Этот страшный термин говорит о том, что если Вы будете находиться очень далеко от игроков, то никогда не сможете их увидеть на карте (например), также это работает с маппингом, пикапами и другой информацией. Сделано это в первую очередь для оптимизации, ибо если сервер передавал бы Вам всю информацию, которая у него есть, то это бы очень сильно нагружало ваше интернет - соединение.
Помимо прочего, на подобном примере работает плагин streamer. Если Вы вдруг не знали, то максимальное количество объектов, которые могут быть созданы в SAMP ограничено, стример же позволяет не то чтобы обойти, а скорее оптимизировать использование объектов. Он создает и удаляет объекты, когда игрок их видит. То есть, если какой-то объект создан через CreateDynamicObject в другом мире и его никто не видит, то он не будет там находиться до тех пор, пока кто-нибудь не появится рядом. Как только игрок снова уйдет, то объект снова будет удален. Таким образом мы можем создавать больше объектов, ведь не все они будут созданы по факту.

Некоторые из SYNC информации реализованы в плагине :

Код:
enum PR_OnFootSync
        {
            PR_lrKey,
            PR_udKey,
            PR_keys,
            Float:PR_position[3],
            Float:PR_quaternion[4],
            PR_health,
            PR_armour,
            PR_weaponId,
            PR_additionalKey,
            PR_specialAction,
            Float:PR_velocity[3],
            Float:PR_surfingOffsets[3],
            PR_surfingVehicleId,
            PR_animationId,
            PR_animationFlags
        };

        enum PR_InCarSync
        {
            PR_vehicleId,
            PR_lrKey,
            PR_udKey,
            PR_keys,
            Float:PR_quaternion[4],
            Float:PR_position[3],
            Float:PR_velocity[3],
            Float:PR_vehicleHealth,
            PR_playerHealth,
            PR_armour,
            PR_weaponId,
            PR_additionalKey,
            PR_sirenState,
            PR_landingGearState,
            PR_trailerId,
            Float:PR_trainSpeed
        };
2. TickRate и Ping.


Сервер постоянно синхронизирует с игроком данные, они передают друг другу очень много всякой информации.

Происходит это вот все очень быстро, в случае с CS go это происходит в среднем 64 раза за секунду, в случае сампа 60. Но бывает такое, что наш клиент, ввиду проблем с интернетом, просто не успевает получать пакеты с информацией, а отправленные пакеты приходят на сервер достаточно долго. Так вот задержка в получении и отправлении пакетов называется ping. Когда нам пишет, что наш пинг 1000, это означает, что задержка в получении информации (и ее отправлении) от сервера составляет 1 секунду (ping измеряется в 1/1000 секунды). Конечно, бывает такое, что наши пакеты и вовсе не доходят до сервера, в таком случае они теряются и наш клиент, получая ошибку, начинает откатывать действия или их тормозить (распространенные фризы в игре, при плохом интернете, или как говорят в народе: большой пинг).




Так же работает Raknet, вернее это просто сетевой движок для передачи пакетов. У пакетов есть несколько видов и каждый из них подходит под определенные ситуации. Например, Remote Procedure Call (RPC) пакеты вызывают определенные функции на нашем клиенте с сервера. Также это работает и в обратную сторону, когда мы садимся в машину, мы отправляем RPC пакет на вызов функции OnPlayerEnterVehicle с передаваемыми параметрами (playerid, carid ....). Также это работает и в обратную сторону от сервера, например сервер сам сажает нас в машину, тогда у нас в клиенте вызывается функция EnterTheVehicle с параметрами, которые были переданы от сервера в RPC пакете.

Код:
void CNetGame::AddedVehiclePacket(RakNet::BitStream* bStream, _VehicleID vehicleID, _PlayerID remotePlayer)
{
    if(!this->playerPool || !this->vehiclePool) return;

    CPlayer* player = NULL;
    CVehicle* vehicle = NULL;

    vehicle = this->vehiclePool->GetVehicle(vehicleID);
    if(!this->vehiclePool->GetSlotState(vehicleID) || !vehicle) return;

    for(_PlayerID i = 0; i < MAX_PLAYERS; i++)
    {
        if(this->playerPool->GetSlotState(i) && (i != remotePlayer))
        {
            player = this->playerPool->GetPlayer(i);
            if(player && player->isVehicleStreamedIn(vehicleID)) 
            {
                this->rakServerInterface->Send(bStream, HIGH_PRIORITY, UNRELIABLE_SEQUENCED, PACKET_STREAM_SYNC, 
                    this->rakServerInterface->GetPlayerIDFromIndex(i), false);
            }
        }
    }
}

void CNetGame::UpdateNetwork() // Обновление данных
{
    Packet* p;
    unsigned char packetId;

    while(p = this->rakServerInterface->Receive()) 
    {
        packetId = p->data[0];
        
        switch(packetId) 
        {

        case ID_NEW_INCOMING_CONNECTION:
            Packet_NewIncomingConnection(p); // Получение информации о том, что появляется новое входящее соединение
            break;
        case ID_DISCONNECTION_NOTIFICATION:
            Packet_DisconnectionNotification(p); // Получение информации о том, что игрок вышел
            break;
        case ID_CONNECTION_LOST: // В этом случае игрок не вышел через /q или не просто закрыл игру, а вылетел
            Packet_ConnectionLost(p);
            break;
        case ID_MODIFIED_PACKET: // Если пакет редактируется
            Packet_ModifiedPacket(p);
            break;
        case ID_PLAYER_SYNC: // Синхронизация данных об игроке
            Packet_PlayerSync(p);
            break;
        case ID_VEHICLE_SYNC: // о машине
            Packet_VehicleSync(p);
            break;
        case ID_PASSENGER_SYNC: // о пассажире (или водителе)
            Packet_PassengerSync(p);
            break;
        case ID_SPECTATOR_SYNC: // о наблюдателе
            Packet_SpectatorSync(p);
            break;
        case ID_AIM_SYNC: // о прицеливании
            Packet_AimSync(p);
            break;
        case ID_RCON_COMMAND: // о том, что была отправлена RCON команда
            Packet_InGameRcon(p);
            break;
        case ID_STATS_UPDATE: // Обновление статистики сервера
            Packet_StatsUpdate(p);
            break;
        case ID_WEAPONS_UPDATE: // оружия
            Packet_WeaponsUpdate(p);
            break;
        case ID_TRAILER_SYNC: // ну и так далее :)
            Packet_TrailerSync(p);
            break;
        case ID_UNOCCUPIED_SYNC:
            Packet_UnoccupiedSync(p);
            break;
        }

        this->rakServerInterface->DeallocatePacket(p);        
    }

}

Но иногда пакеты могут не доходить до сервера или наоборот, не доходят до игрока и происходит это не случайно. NOP или No OPeration это некая блокировка на получаемые или отправляемые пакеты. Таким образом с помощью дополнительного софта игрок может игнорировать inComing (входящий в клиент) RPC пакет SetPlayerHealth. Тогда сервер просто не сможет изменить количество здоровья у игрока. Также можно поставить NOP на SetPlayerPos и тогда сервер не сможет изменить наше местоположение. Важно!: RPC пакеты доходят до клиента, но просто не пропускаются в игру. Такие NOP'ы могут быть абсолютно на любые пакеты, причем не только входящие, но и исходящие (Outcoming RPC), таким образом мы можем просто скрыть от сервера информацию о нашем передвижении. NOP'ы являются большой проблемой для Samp серверов, так как их очень сложно выявить, однако сделать это можно. Так или иначе, NOP не может контролировать такие функции как Kick и Ban, ибо эти функции просто блокируют соединение сервера с игроком со стороны сервера.

Код:
void CNetGame::KickPlayer(_PlayerID playerId)
{
    PlayerID player = this->rakServerInterface->GetPlayerIDFromIndex(playerId);

    if(playerId < MAX_PLAYERS) // странная проверка
    {
        if(this->playerPool->GetSlotState(playerId)) 
        {
            this->playerPool->Delete(playerId, 2); // Как мы видим из исходников, сервер не спрашивает у игрока хочет он выйти или нет
            this->rakServerInterface->Kick(player); // он сразу же его удаляет
        }
    }
}
3. Чит программы.


Дело в том, что если информация обо всех игроках отправляется в клиент, но лишь не показывается игрой, то почему бы не показывать ее самим? Именно так работает WallHack. Он читает пакеты обо всех игроках, о которых сервером было сообщено, и показывает через специальный интерфейс.

Читы есть в любой игре и сделать их можно абсолютно без проблем. Кстати, в основном читы действуют лишь на 2 параметра, это пакеты входящие и исходящие, однако читы могут изменять только то, что обрабатывается клиентом, что обрабатывается сервером - им не подвластно.

Возьмем пример из сампа: По умолчанию в игре есть полоска с отображением денег, читы с лёгкостью изменяют количество именно игровых денег, на что разработчики модов решили сохранять количество денег в памяти на сервере, а не на клиенте. Так и началась(образно) борьба разработчиков читерских софтов и игровых серверов. Самым известным исходом данной войны можно назвать античит. Античит это некий алгоритм, который находя нарушение от игрока, производит с ним те или иные манипуляции. Как только выходит обновление на античит, разработчики софтов тут же подравниваются под него и делают противоборствующие методы обхода этого алгоритма.

Алгоритмы препятствия использования читов есть нескольких видов:

Алгоритмы, которые просто анализируют данные об игроке и понимая, что игрок изменил данные (например свое местоположение) не очень естественно - производит манипуляции (например кикает). Данный вид достаточно активно используется в большинстве мультиплееров, НО стоит отметить, что есть некоторые проблемы. Конечно, тут главная проблема в исходном материале, так как функционал SA:MP не очень точный, и не больно хорошо оптимизирован.

Аналитика игровых составляющих. Данный вид алгоритмов просто проверяет наличие посторонних программ, которые воздействуют на игру. Не обойтись без клиентской части. И как правило такой вот подход помогает лишь временно, ибо разработчики софтов умеют и декомпилировать файлы игры и вносить в них какие-то изменения и вообще все что угодно, поэтому это не очень действенный способ, однако в сампе я думаю он не сильно то и используется.

4. Теперь стоит рассказать как проверить игрока на наличие NOP.


Для этого нужно провести обычный листинг callback'ов. За сколько тиков срабатывает callback OnPlayerSpawn после функции SpawnPlayer. Потом будем просто проверять, а вызвался ли callback? Если нет, то скорее всего у нашего экземпляра имеется NOP. Конечно, есть ещё много других функций, но главное, что у всех примерно такой способ работы и поэтому данный метод подойдёт для большинства NOP'ов. Приведу как ещё один пример: OnPlayerEnterTheVehicle. Ноп на данный каллбэк можно выявить обычной проверкой на доступность этого транспорта для игрока, на нажатую клавишу Enter и на "Вызвался ли callback?".

Код:
// Пример реализации AntiNOP OnPlayerSpawn()

new const NEED_TO_CALL_TIME = 1000; // Создадим сток, который будет хранить постоянное время, 
//которое мы даем игроком на спавн(в случае проблем с соединением и вообще оптимизацией сервера)

new AntiNOP_SpawnTime[MAX_PLAYERS]; // Будет хранить таймер для спавна игрока

stock SpawnPlayerEx(playerid){  // К примеру создадим сток, который будет просто спавнить игрока
    // Создадим таймер, который в случае, если OnPlayerSpawn его не остановит (то есть у игрока есть на него NOP) - вызовет функцию AntiNOP_NOP_Detected
    AntiNOP_SpawnTime[playerid] = SetTimerEx("AntiNOP_NOP_Detected", (GetPlayerPing(playerid) + NEED_TO_CALL_TIME), false, "d", playerid);
    // GetPlayerPing в строчке выше нужен на тот случай, если у игрока, например, пинг был около 500 и тут резко поднялся до 2000,
    // то есть таким образом мы исключаем факт просто плохого интернета, а не антинопа
    return SpawnPlayer(playerid);
}

public OnPlayerSpawn(playerid){ // Коллбек, который срабатывает, когда игрок заспавнился.
    KillTimer(AntiNOP_SpawnTime[playerid]); // Если у игрока нет антинопа, то OnPlayerSpawn при вызове остановит таймер
    return 1;
}




Автор: Nestyreff (MassonNN)
Копирование и доп.публикация без разрешения запрещена.
Любая обоснованная критика приветствуется.
 
Последнее редактирование модератором:
Статус
В этой теме нельзя размещать новые ответы.
Сверху Снизу