Как мы мультиплеер для NFS MW писали

в 14:27, , рубрики: C, multiplayer, nfsmw, гонки, мультиплеер, онлайн, реверс-инжиниринг, метки:

Привет! В своём первом посте я расскажу, как мы уже полгода как пишем неофициальный мультиплеер для NFS Most Wanted 2005 года выпуска. Сразу скажу — ссылок не будет, лишь скриншоты, дабы не сочли за банальную рекламу. Если будет интересно — спросите в комментариях. Поехали!

image

Началось все в конце 2016, то есть прошлого, года, а точнее в ноябре. Я общался с разработчиком трейнеров MWInside, который, если честно, не блистал особыми знаниями. Я рассказал, что хотел бы научиться управлять авто, отличными от игрока — соперниками, полицией и обычными машинами трафика. Он ничего на это не ответил, ибо тогда об этом никто не знал. Давайте пропустим до конца декабря, когда я познакомился с Jojez'ом, человеком, вдохновившим меня на все это, и командой ExOpts, которая мне помогла идеями. Итак, у меня на руках были адреса координат, скорости, наклона и поворота игрока. Путем некоторых исследований я вычислил, что они все — поля одной структуры, предположительно ISimable, размером в 176 байт, т.е. прибавление 176 к всем этим адресам давало те же значения у другого автомобиля. Это было открытием, которое, по сути, открыло десятки путей, однако сначала, до конца января, я писал не онлайн, а простенький трейнер на управление вертолетом полиции. Ещё раз пропустим до двадцать третьего января.

Сам, собственно,. мультиплеер

Итак, двадцать третье число, утро. Я предлагаю своему другу, YaNet'у, написать мультиплеер. Тот соглашается, берет код из своих старых проектов, и к двадцать четвертому у нас есть минимальные сервер и клиент на C#. Впоследствии его переписали на Си, но это уже другая история. Двадцать четвертого числа мы начали делать работу с игрой. К слову, мы использовали NFSScript — API для создания модов от DennisStanistan'а. Мы заставили клиент просить случайное число у сервера, а затем, после того, как оно приходило, выводили его на экран средствами игры. Это было только начало…

Синхронизация игроков — сущий ад

Вскоре пришло время делать реальный мультиплеер. Работал он довольно просто: клиенты отправляли свои координаты, сервер их отправлял остальным, те на своей стороне присваивали их к машинам. Ничего интересного, а ещё оно было ужасно нестабильно и багано: машины были буквально под приступом, их трясло и подкидывало. В это же время один из администраторов сайта, имя которого я разглашать не буду, чтобы не рекламировать, ArturoPlayerOne, создал закреплённый топик, сообщающий, что проводится открытый тест. Люди, конечно, полезли в чат, но из-за действительно ломающих все багов нормально поиграть никто не смог, хоть и остались заинтересованными. Впоследствии все эти баги, конечно, исправили, но лишь полностью переписав клиент и сервер на Си. Синхронизацию тоже исправили, но это уже другая история…

А это кусок кода :)

	mw_write_memory(MW_ADDR_AI_STR_RACE, "AIActionNone", 13); //Зачем нам боты в онлайне?
	float hud_scale = 0.97265; //Мини-мод, изменяет размер HUD и стартовый экран на более похожие на версию для Xbox 360
	mw_write_memory(MW_ADDR_HUD_WIDTH, &hud_scale, 4); //Это HUD...
	mw_write_memory(MW_ADDR_HUD_HEIGHT, &hud_scale, 4);
	uint8_t opcode = 0xC3; //...а тут экран. Это немного сложнее, чем HUD, но все же.
	mw_write_memory((void*)0x604DA0, &opcode, 1);
	uint8_t widescreen_splash_patch[] = {0x90, 0x90};
	mw_write_memory((void*)0x5A3080, widescreen_splash_patch, 1);
	void* ws_screen_pointer = 0x89F828;
	mw_write_memory((void*)0x8F3C68, &ws_screen_pointer, 4);
	mw_write_memory((void*)0x8F3C88, &ws_screen_pointer, 4);
	mw_write_memory((void*)0x8F3CA4, &ws_screen_pointer, 4);
	uint8_t force_7_ai_patch1[] = {0xB8, 0x06, 0x00, 0x00, 0x00}; //Название говорит само за себя: мы заставляем игру спавнить 7 оппонентов. Этот массив, а именно код в нем, собственно это делает...
	mw_write_memory((void*)0x5DA122, force_7_ai_patch1, 5);
	uint8_t force_7_ai_patch2[] = {0xB8, 0xC5, 0x88, 0x3B, 0x9F, 0x50, 0xE8, 0x22, 0x45, 0xFE, 0xFF, 0x83, 0xC4, 0x04, 0x5E, 0xC2, 0x04, 0x00}; //...а этот исправляет падение игры. Посложнее, не так ли? На самом деле все просто: мы заменили [eax] на хеш строки "character_smart" в одной из инструкций и сделали ещё парочку незначительных изменений. В EAX находился указатель на хеш соперника, которые вручную заданы в игровых файлах, что, собственно, и вызывало падение на четвёртом сопернике, ибо его просто нет, что оставляло в EAX нулевой указатель/мусор. Оба варианта, понятное дело, ничего хорошего не сулят.
	mw_write_memory((void*)0x5FD353, force_7_ai_patch2, 18);

Довольно длинный код, если честно. Я писал комментарии минут пятнадцать. Ладно, пойдем дальше!

Какие проблемы мы испытали

Их было очень много, некоторые есть и сейчас, но сегодня мы поговорим о тройке самых важных, при том с конца. Поехали!

Номер три: синхронизация

Почему аж на третьем месте, спросите вы? Потому что особо она и не мешалась, ИМХО. Да, она была ужасной, но не ломала геймплей. Как же мы ее исправили-то?
Если честно, то полностью не исправили и по сей день, но давайте я вам расскажу обо всем, что мы пытались сделать.

  • Суровый челябинский метод: мы пытались синхронизировать все физические параметры и как можно чаще. Ha-ha, nope! Никаких изменений, только все начало безбожно тормозить. В помойку.
  • Уменьшение размера пакетов. Действие неплохое, ибо клиент «ест» меньше трафика, только в качестве решения проблемы синхронизации бессмысленное до костей. На свалку.
  • И, наконец,.разделение координат и скоростей, реально рабочее решение. Ранее мы синхронизировали все и всегда, но теперь это изменилось: скорость и поворот синхронизируются всегда, а вот координаты всего раз в несколько секунд. Это сделало игроков плавными и красивыми.

Номер два: пожирание всего соединения

Да-да, когда-то​ наш клиент забивал канал, что тесно связано с номером один. На соединениях хуже соединения датацентров Google клиент просто не работал, либо не давал работать остальному. Исправлено это было только недавно, в версии 0.6. Как оказалось, каким-то неведомым образом я написал код, который мало того, что буквально производил DoS- (а если на сервере игроков так пять, то вообще DDoS-) атаку на сервер, так ещё и убивал соединение, отправляя байт так тысячу раз в миллисекунду! Не знаю, что ещё можно сказать по поводу этого бага, но он был исправлен, так что все о'кей. Переходим к первому месту!

Номер один: перезаходы

Даже, точнее сказать, отключения. Выход игроков из игры просто не был предусмотрен. Если человек заходил один раз на пустой сервер, его ID был равен одному. Если он перезапустит клиент, то его ID уже будет равен двум. Странно, не так ли? А что, если три игрока вместе гоняются, у одного падает соединение, и он заходит ещё раз? Надо ли говорить, что три игрока были максимумом? Это ужасно бесило меня и всех игроков. В итоге я собрался с силами и таки написал отключение игроков. Если от игрока нет пакетов в течении двух секунд — он отключается. Случайное​ отключение исключается — два раза в секунду все равно приходит «пинг». Думаю, что это все.

image

Ещё один скриншот

Послесловие

Его не будет. Единственное — @grimbox, отзовись! Ещё хочу сказать спасибо:

  • nlgzrgn за способ получения семи соперников в гонке;
  • SpeedyHeart/speedycat/SpeedyChan за помощь с синхронизацией;
  • YaNet (dz3n) за помощь в первое время;
  • Многочисленным игрокам за преданность нашему проекту. Бокал за вас!

На этом все! Всем пока. Если все же опубликуют — буду рад и выпущу продолжение, ибо тут я описал лишь самое важное. До встречи!

Автор: osdeverr

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js