Pull to refresh

Онлайн-сервер для NFS Underground 2 своими руками. Часть 1

Reading time11 min
Views16K

Привет, Хабр! Спустя 13 лет я решил рассказать как однажды начал писать dedicated-сервер для игры NFS Underground 2, как решил возродить функционал оригинального онлайн-сервера, закрытого ещё в 2007 году, и как получилось, что это всё ещё WIP, несмотря на килотонны написанного кода и сотни часов, проведённых в реверс-инжиниринге.

Материал поделю на 3 части: в этой статье будет предыстория, причины и общая картина сетевой реализации сервера. Во второй части расскажу про проблемы и нюансы реверс-инжиниринга. А в завершающей части уделю внимание организации структуры проекта и тому, “что” и “почему” до сих пор не реализовано.

Началось всё в 2009 году, мы с друзьями любили поиграть в Need for Speed Underground 2 по сети, но официальный сервер к тому моменту уже не работал, поэтому было относительно большое международное сообщество, которое играло в режиме локальной сети через pLan TC (Tracker client), после отключения которого сообщество практически распалось, т.к. все остальные способы поиграть вместе (например, через популярный тогда Hamachi) были ограничены “частными” комнатами. Конечно, тогда осталась и версия pLan OpenVPN, и был Tunngle, но собрать в одном месте всех игроков из разных стран тогда уже не получалось.

“Локальный” сервер на 200+ игроков.
Визуальный моддинг игры в рыжих цветах — понять и простить.
“Локальный” сервер на 200+ игроков. Визуальный моддинг игры в рыжих цветах — понять и простить.

Так и пришла идея — сделать сервер, который будет работать постоянно, 24/7, а в идеале, работать не только в LAN-режиме, но и как официальный, с рейтингом и статистикой.

Забегая вперёд, скажу — если LAN-сервер реализован полностью, то для полной реализации à la официального сервера всё ещё остались нереализованные части, хоть и совсем чуть-чуть.

Но, обо всём по порядку.

От ingame к standalone

Первая сложность, которая была и во времена использования pLan TC — запустить LAN-сервер можно было только из игры. Поэтому находились определённые энтузиасты, которые оставляли работающей игру с запущенным сервером на домашнем компьютере, к которому все подключались. Денег на VPS на Windows тогда не было, видимо, ни у кого.

Тогда же, без опыта реверс-инжиниринга, вооружившись одним очень известным дизассемблером, я решил посмотреть на файл с говорящим названием server.dll, находящийся в корне директории игры. Первое, что я обнаружил (и для чего дизассемблер, на самом деле, особо не нужен) — это 3 export-функции, тоже с говорящими названиями:

StartServer
StopServer
IsServerRunning

История могла закончиться, даже не начавшись, потому что, подключив библиотеку и вызвав функцию, например, так:

StartServer("MyNFSU2Server", 0, null, null);

действительно запускался сервер с названием MyNFSU2Server, к нему можно было подключаться и играть.

Но, не всё так просто. Оказалось, что ребята в pLan TC изменяли сетевые пакеты патчили server.dll (upd: исправил неточность) так, чтобы фильтрация по комнатам не работала, и это было хорошо, потому что ты видишь сразу все созданные игровые комнаты и не нужно перебирать фильтр по разным типам гонок (Драг, Кольцо, Спринт, Дрифт и т.д.).

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

Как убрать фильтрацию в списке комнат

Чтобы решить вопрос с фильтрацией, надо было понять как эта фильтрация работает.

Немного изучив псевдокод, сгенерированный дизассемблером, стало понятно, что всё достаточно просто — там обычная битовая маска. При обновлении списка комнат фильтрация по типу гонки обязательна, поэтому указывается битовая маска и сервер возвращает комнаты, подходящие под неё. Но, на самом деле, в игре (клиенте) маска никогда не использовалась и выбиралось точное совпадение под тот или иной тип гонки.

Выходов было несколько:

  1. Изменить код server.dll так, чтобы он не учитывал входящую маску и всегда возвращал полный список комнат;

  2. Модифицировать клиентский пакет, подставляя туда маску, под которую будут подходить все типы комнат;

  3. Написать свой сервер.

Первый вариант я отбросил сразу, т.к., начитался тогда историй про всякие претензии от правообладателей за изменение игровых файлов. Особенно, касаемо игр “и эй геймс”.

Второй вариант мне тогда не понравился, потому что, как мне казалось, это требовало приложения, которое каждый игрок поставит себе (как у всех стоял pLan TC).

Сейчас я уже понимаю, что, наверное, можно было бы отделаться простейшим MITM Proxy и всё бы заработало как надо, но тогда решил действовать иначе.

У меня был спортивный интерес, поэтому написать свой сервер мне казалось классным, т.к. помимо необходимости погрузиться в реверс-инжиниринг, мне было необходимо изучить сниффинг как анализировать сетевой трафик и вообще — идея написать свой игровой сервер очень привлекала продумываем и реализацией всей архитектуры сервера.

Что нужно знать про разные версии игры

NFSU2 был выпущен для PC, XBOX, PS2 и GameCube (Nintendo DS, GameBoy и пр.). Нас интересуют версии для PC, XBOX и PS2 - они основаны на одной кодовой базе, хоть и немного отличаются. А версия для GameCube вообще имеет мало что общего с остальными сборками.

На сетевом уровне происходит идентификация типа, версии и региона для клиента и сервера игры. С типом игры всё понятно — разделяются разные игры, разные платформы, разные билды этих игр.

С разными регионами интереснее. Основных региона два:

  • EU (Europe) для европейского рынка.

  • NA (North America) для североамериканского рынка и стран Азии.

Хоть я и находил упоминания версии AU (для Австралии), но соответствующий бинарник мне не попадался. Для остальных регионов, насколько мне удалось выяснить, поставлялась либо NA, либо EU версия.

Эти сборки отличаются немного разными игровыми ресурсами. Например, есть отличия просто в названиях одних и тех же автомобилей (в соответствии с тем как они поставлялись на соответствующие авторынки), например Mazda Miata и Mazda MX-5.

Также эти версии отличались и эксклюзивными автомобилями:

  • для NA — Honda Civic и Acura RSX;

  • для EU — Peugeot 106 и Vauxhall Corsa.

Несмотря на то, что абсолютное большинство игроков в 2009 году использовали NA-версию, через EU-версию, в том же pLan TC, можно было увидеть сервер с 20-40 игроками, которые и не знали, что существует версия для другого региона и на сервере с другим регионом в 10 раз больше игроков.

Для меня это звучало как ещё одна задача, которую нужно решить и дать возможность игрокам разных региональных версий играть вместе.

Кратко о совмещении разных версий игр

В объединении игроков из разных версий игр было несколько проблем:

  • Первая — сервер должен игнорировать региональность версии и подключать всех игроков к одному серверу. Т.к. в данном случае речь идёт о собственном сервере, проблем это не вызывает.

  • Вторая — если игрок одной региональной версии зайдёт в комнату с игроком другой версии и у одного из них будет эксклюзивный автомобиль, то у другого игрока, в зависимости от сборки, игра либо крашнется, либо вместо автомобиля будет отображаться пустота. Это тоже решается относительно просто, нужно в каждую из версий игры добавить файлы отсутствующих автомобилей.
    При этом в коде есть общая коллекция всех автомобилей и фактически отличается, опять же, if-ом.

  • Третья — теперь игроки видят друг у друга эксклюзивные авто, но выбрать себе такой автомобиль не могут, и здесь, кроме как патчингом оригинальной исполняемого файла (или оперативной памяти) уже не обойтись.

Помощь других версий игры и даже других игр

В те годы можно было особенно часто видеть релизы игр (и не только игр), где исполняемые файлы или библиотеки были скомпилированы под debug-окружение, а значит там были сохранены названия функций и прочая полезная информация. При этом игры часто содержали немало общей кодовой базы, в моём случае дебаг-символы NFS Most Wanted, например, очень помогли в дизассемблировании NFS Underground 2.
Кстати, есть даже целый сборник когда-либо публиковавшихся и выпущенных игр, скомпилированных с дебаг-символами и всем, что может помочь их реверс-инжинирингу.

Также помогали и разные версии игры, в том числе Demo-версия NFS Underground 2, которая уже умела в онлайн-режим, но содержала гораздо меньше кода, чем релизная версия, этим самым упрощая реверс-инжиниринг.

Так что там с сетевой архитектурой?

Отмечу, что помимо основного запускного файла speed2.exe и библиотеки server.dll, всё остальное в директории игры — это игровые ресурсы. Т.е. по сути, вся игра скомпилирована в единственный исполняемый файл speed2.exe. А server.dll использовался исключительно для запуска того самого ingame LAN сервера, при этом игра может подключаться к другим серверам даже при отсутствии этой библиотеки, так как основной код сетевого взаимодействия также был скомпилирован и в основной исполняемый файл и server.dll выступал урезанной версией исключительно для запуска своего сервера локальной игры. Для чего его так отделили мне до сих пор не до конца понятно.

Итак, шаг за шагом, снифая пакеты, я узнал, что в игре используется одновременно UDP и TCP для разных нужд, какой-то кастомный программный протокол передачи данных, да и сетевой сервер используется не один, а целых четыре — перенаправления, авторизации, основной и EA Messenger (EAM). Последний нужен только для официального онлайна, для LAN-режима используются только первые три.

После запуска, LAN-сервер начинает себя обозначать и периодически броадкастить UDP-пакет с базовой информацией о сервере:

  • тип игры, в этом случае это PCNFS05 - это и есть NFS Underground 2 для ПК.

  • версия сборки игры.

  • регион игры, EU / NA.

  • количество игроков на сервере.

  • сетевой порт, к которому нужно подключаться, чтобы оказаться на этом сервере.

Далее взаимодействие с клиентом очень идентично официальному серверу, за исключением, что в LAN-режиме не используется шифрование, а в режиме официального сервера весь трафик шифруется и есть подключение к EAM.

Шифрование?

Там ProtoSSL. Можно было бы обходить шифрование или использовать EA-MITM, который появился чуть позже, чем это нужно было мне. Но, на самом деле весь сетевой код игры не просто идентичен, а он един как для режима официального сервера, так и для LAN, но с разными ветвлениями if-ов в зависимости от того что нужно. Поэтому и подключение к серверу происходит, фактически, вызовом функции с указанием — использовать шифрование или нет.

Это заставило меня полениться и таки пропатчить исполняемый файл так, чтобы в обоих случаях использовалось подключение без шифрования, хотя бы на время разработки сервера (как я думал тогда). С тех пор к этому вопросу так и не вернулся и всё ещё использую для подключения пропатченную версию, либо патчу прямо в оперативной памяти, ведь “патч” изменяет целый 1 байт.

Какой-то кастомный программный протокол

Dirtysock - так называется протокол, который использовался и до сих пор используется в играх, эволюционируя, но сохраняя вполне оригинальный вид.

Тогда я этого не знал, к сожалению, иначе бы гораздо раньше нашёл, что он появился в EAWebKit и даже был выложен на Github. Поэтому раскапывал всё сам шаг за шагом, дизассемблируя все игры, выпущенные в те года этой компанией и её разными студиями.

В NFSU2 данный протокол используется достаточно просто:

  • cmd, 4 байта - название команды (например, auth - авторизация по логину/паролю);

  • error, 4 байта - тип ошибки текущей команды (например, pass - некорректный пароль);

  • length, 4 байта - Big Endian, 32-битное число, длина основного контента;

  • payload, <length> байт - полезная нагрузка для команды, вида KEY=VALUE\n…

Как правило, на каждую такую команду должен прийти ответ с таким же названием команды, причём не важно, применяется этот протокол по TCP или по UDP.

Например, минимальная авторизация с некорректным паролем будет выглядеть так:

Клиент:

cmd=auth
err=null
payload={
    NAME=greevex
    PASS=123456
    PROD=nfs-pc-2005
    VERS="pc/1.2-Feb  9 2005"
}

Сервер:

cmd=auth
err=pass
payload=null

А ответ на успешную авторизацию выглядит следующим образом:

Сервер:

cmd=auth
err=null
payload={
    NAME=AccountName,
    GTAG=AccountName
    PERSONAS=MyPlayerName
    XUID=null
    TOS=1
    SHARE=1
    ADDR=192.168.152.21
    VERS="pc/1.2-Feb  9 2005"
}

Кстати, один из видов хеширования шифрования пароля выглядит вот так.

При этом есть некоторые стандарты формирования имени команды, например:

  • команда, начинающаяся со знака плюс “+” информирует о появлении чего-либо, что нужно добавить в соответствующий список. Аналогичная команда со знаком минус “-” — об удалении. Например: +gam — новая игровая комната, +pla — новый игрок, -gam — это закрытие игровой комнаты и т.д.

  • есть также команды начинающиеся с тильды “~”, собачки “@”, заканчивающиеся на звёздочку “*” и т.д., всё это указывает на то, как именно нужно обрабатывать команду.

Типы сетевых серверов

Сервер перенаправления

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

Сервер авторизации

Взаимодействует с клиентом для выбора способа шифрования пароля, непосредственно самой авторизации, выбора игрового профиля, а также этот сервер выдаёт информацию для подключения к EAM и ссылки на условия использования и новости.

Кстати, ссылки ведут не просто на какие-то файлы, а файлы в формате, который имплементируется игровым интерфейсом и можно указывать какие кнопки отображать. 
Например, вот такой шаблон:

В игре будет отображаться так:

CMD указывает на какой команде этот шаблон отображается.
TITLE позволяет задать заголовок окна в игре
BTN1 - Текст на кнопке
BTN1-GOTO - Действие кнопки

Можно даже создать новую кнопку, указав BTN2 и BTN2-GOTO, например, как это сделано на экране Terms of Service:

Такой шаблон превращается в игре в это:

Основной сервер

Название говорит само за себя, это сервер, который позволяет создавать игровые комнаты, взаимодействовать игрокам между собой, переписываться в чате игровой комнаты, менять статусы, просматривать автомобили друг у друга и т.д.

Фактически, всё, что происходит после авторизации и до непосредственного старта гонки, обрабатывается данным сервером. Да-да, непосредственно в самой гонке сервер не участвует, а гонку хостит один из игроков, обычно — создатель игровой комнаты.

EA Messenger

Так выглядит EA Messenger с одним пользователем в контактах
Так выглядит EA Messenger с одним пользователем в контактах

Это такой мессенджер, встроенный во все игры от этого издателя тех времён, который позволял смотреть кто во что играет, переписываться, будучи в разных играх, приглашать в игру и создавать приватные комнаты без пароля “только по приглашению”. Предвосхищая вопросы, напомню, что игра эта 2004 года. Тогда это было прям “вау!”.

Авторизация в нём эволюционировала и зависит от конкретной игры. Способов несколько:

  • по имени аккаунта и паролю в открытом виде;

  • по имени аккаунта и шифрованному ключу, который формируется тем же алгоритмом, что и шифрование пароля и состоит из строки <user>:<pass> или <user>:<token>;

  • по платформе (без имени аккаунта, например, /PC/NFS-CONSOLE-2005) и токену, который генерируется на серверной стороне при авторизации на основном сервере.

В данном случае я решил остановиться на втором способе, т.к. он мне показался наиболее оптимальным.

Где оно работает и как подключиться?

Работал этот сервер ранее дома, на Raspberry Pi, а теперь в Яндекс.Облаке, на самой минимальной виртуалке, потому что практически не требует ресурсов, несмотря на то, что использует базу данных MongoDB.

Есть также сайт, на котором отображается текущее состояние сервера, игроки на сервере и созданные комнаты. Сервер почти 6 лет нон-стоп работал в Tunngle и “перерождённом” проекте pLan, который называется pLan Revive, но после переезда в Яндекс.Облако, только сейчас дошли руки снова его туда подключить в режиме LAN, поэтому последние 5 месяцев он был доступен только в официальном режиме с патчем, который был лишь у пары людей.

Сейчас к серверу также можно подключиться в режиме официального сервера, но для этого, как уже описывал выше, пока что нужно применять патч для отключения шифрования и заменой ip-адреса в файле hosts, либо использовать уже пропатченный исполняемый файл, в котором прописан и нужный домен и отключено шифрование.

Также, несколько лет назад, когда EA Messenger только-только заработал, я опубликовал видео с интерфейсами официального онлайн-режима, которые, судя по всему, кто-либо видел последний раз только в 2007 году.

Окей, на чём всё это написано?

Первая версия сервера с использованием игровой библиотеки server.dll была написана на C++.

Вторая версия уже своего сервера первые 3 года писалась на C#, но из-за того, что архитектура сервера на тот момент часто менялась, потому что это был, фактически, R&D, я выбрал PHP для прототипирования сервера и использовал парадигму событийно-ориентированного программирования. Сервер получился однопоточный, но асинхронный.

В качестве основного хранилища для игроков, комнат и прочего выбрал MongoDB. А коммуникация между разными контекстами (например, чат в игровой комнате) реализована через MessageBus.

Исходный код на текущий момент закрыт, но лишь потому, что его нужно привести в порядок: убрать разные deprecated части и некоторый хардкод, они лишь вносят путаницу.

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

Но если кому-то ну прям очень интересно посмотреть на код сейчас — напишите, открою доступ к репозиторию на github пока что индивидуально.

Резюме

Оказалось, что игра 2004 года не совсем умерла и всё ещё вполне жива. Сейчас, спустя почти 18 лет, хоть сообщество и не такое большое, но ностальгия так и бьёт ключом из разных мест: кто-то делает ремейк игры на Unreal Engine, другие делают синематики, третьи снимают ролики в стиле игры. Также на протяжении всех этих лет мне периодически писали разные люди, в особенности очень помогал и помогает один немец, который активно занимается дизассемблированием игры (и у него поразительные успехи в её пересборке!). А проект хоть и медленно, но все эти годы постоянно развивался, и пришло время дать возможность принять участие в этом проекте всем желающим.

Спасибо за внимание! Продолжение следует…

P.S. и вишенка на торте: Кодовая база относительно легко адаптируется и меняется для нескольких соседних игр, таких как NFS Underground (1), NFS Most Wanted, NFS Carbon — это те игры, что я сам проверил на работоспособность, внеся некоторые изменения. Возможно, этот список можно ещё расширить.

Tags:
Hubs:
+54
Comments23

Articles