Idewavecore. Ретроспектива

    Очень круто - запрограммировать механизм или программный модуль, заставив его выполнять твою волю. С похожими мыслями в конце 2018 я размышлял о том, что хочу сделать собственный WoW-сервер, который будет полностью мной управляем. Поизучав С++ исходники MANGOS, я пришел к выводу, что не смогу вот так взять и реализовать все свои идеи, не понимая, как же работает MMO RPG сервер от начала и до конца. И для этой цели я решил реализовать свой движок. С нуля.


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

    Если вкратце, то Login Server (как и аутентификация в клиенте WoW) построены на использовании алгоритма SRP. Описание алгоритма выходит за пределы статьи, но если вкратце, то он позволяет идентифицировать пользователя без передачи пароля на сервер, благодаря чему пароль (даже в закэшированном виде) можно не хранить на сервере. Даже желательно.

    Алгоритм шифрования же World сервера скорее всего отличается от клиента к клиенту (я сделал такой вывод при беглом изучении исходников сервера WoW 3.3.5a). Я разрабатывал сервер для WoW версии 2.4.3. Там используется что-то вроде шифра Цезаря. Хотя чаще (в исходниках) можно встретить название HeaderCrypt или Wowcrypt.

    В версии 2.4.3 шифруются первые 6 байт: size (2) и opcode (4) каждого пакета на World сервере (кроме первого пакета). Соответственно, если перехватить пакет (sniff), то не получится определить, на какой opcode он будет отправлен. А если пакет большой, то он может быть разбит на несколько и чтобы их правильно достать из буфера - нужны эти самые первые 2 байта (size).

    Процесс входа на сервер вкратце можно описать так:

    Клиент последовательно обменивается данными с Login сервером и в случае успеха получает SRP токен (session key), который затем будет использован для создания токена шифрования/расшифровки пакетов (crypto key). Токен создается на этапе "send auth request" (см. схему выше). После чего на клиент отправляется auth response, суть которого - показать, что операция прошла успешно. Auth response - единственный незашифрованный пакет, последующие пакеты будут шифроваться с помощью crypto key. По поводу шифра - у меня он реализован так.

    Процесс взаимодействия с World сервером простой - клиент отправляет зашифрованный пакет, на сервере он кладется в буфер, расшифровываются первые несколько байт (о чем я писал выше), получаем размер (size) и опкод (opcode) и если количество байт в буфере >= size, то мы получаем список необходимых хэндлеров (их может быть несколько) по заданному опкоду и передаем им size байт из буфера. Либо ожидаем получения остальных байт. Отдельного внимания заслуживает Update Packet. Он отличается от остальных пакетов более сложной структурой.

    Расшифровывать нужно каждый пакет. Если какой-то пакет пропущен, то следующий (согласно алгоритму) будет расшифрован неправильно. На эти грабли я наступал с особой старательностью.

    В чем суть моего сервера

    Во-первых, я его писал на Python 3 (asyncio + SQLAlchemy). Точнее, на момент создания я еще не имел опыта с SQLAlchemy - я его обрел после, когда решил, что существующая у меня реализация работы с БД - ужасна во всех ее проявлениях (а еще попутно решил изучить новую технологию). И очередной раз переписал проект с нуля.

    Во-вторых, подход к обработке данных тоже несколько отличался. Я предпочел отказаться от идеи глобального хранилища всех объектов (где каждый объект содержит также методы работы с ним) и создать специальные мэнеджеры (manager), для того, чтобы работать с каждым отдельным типом объектов: Item, Player, Unit и т.д. Т.е. если я хочу выполнить действие над Player, я использую PlayerManager, который выполняет нужное действие и удаляется из памяти. В первую очередь это сделано в угоду читабельности. К примеру, аналогичный класс в MANGOS лично для меня кажется громоздким (как и C++, несмотря на множество его преимуществ). Каждый отдельный класс объекта - это SQLAlchemy модель и он же - структура данных, хранящая текущее состояние. Таким образом, я использую такие объекты не только для взаимодействия с БД, но и для обмена структурированными данными между частями приложения.

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

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

    В-пятых, я неоднократно переписывал код, чтобы сделать его читабельнее и, в принципе, у меня получилось сделать более-менее однородную архитектуру. Думаю, это - однозначный плюс для тех, кто будет читать исходный код. Для этих же целей при разработке упор был на ООП.

    О самом сервере

    Наверное, есть смысл сказать несколько слов о том, что собственно реализовано.

    1. Собственно, вход на сервер

    2. Создание/удаление персонажа

    3. Вход в мир

    4. Отображение надетых вещей и оружия

    5. Отображение окружающих игроков/мобов

    6. Броадкаст движения/чата (сказать/крикнуть)

    7. Погода

    8. Отображение сообщения дня

    Что дальше

    В процессе разработки сервера у меня возникла идея создания MMO RPG шаблонизатора, который предоставит формат описания игровых серверов (например, связка Login + World сервер, или шард-ориентированная архитектура, где каждая локация - отдельный сервер и т.д.), а так же серверов, имеющих отношение к игровым косвенно (например, web сервер, где будет сайт и форум). Именно для этого я создал фреймворк (но не совсем внятно описал его в своей предыдущей статье). И сейчас сервер переписываю на базе этого фреймворка.

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

    P.S. продолжать развитие своего изначального проекта не планирую. Но, учитывая, что некоторые люди его используют в образовательных целях, то я рад, если кому-то еще он будет полезен.

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

    Есть ли у вас опыт написания игрового сервера с нуля?

    • 25,0%Да (есть полностью рабочий игровой сервер, сделанный с нуля)3
    • 8,3%Да (но не довел до конца)1
    • 25,0%Нету (и нет желания)3
    • 41,7%Хочу попробовать сделать5

    Комментарии 1

      0
      Забыл одну важную деталь добавить — что собственно было реализовано. Обновил статью.

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

      Самое читаемое