[Разработка игр, Реверс-инжиниринг, Игры и игровые приставки] Как я сократил время загрузки GTA Online на 70% (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
GTA Online печально известна своей медленной скоростью загрузки. Запустив недавно игру, чтобы выполнить новые миссии-налёты, я был шокирован тем, что она загружается так же медленно, как и в момент выпуска семь лет назад.
Время настало. Пока разобраться в причинах этого.
Разведка
Для начала я захотел проверить, не решил ли уже кто-нибудь эту проблему. Большинство найденных результатов состояло из анекдотичных данных о том, насколько сложна игра, что ей приходится грузиться так долго, историй об отстойности сетевой архитектуры p2p (и в этом есть правда), сложных способов загрузки в сюжетный режим, а после него в одиночную сессию и пары модов, позволявших пропустить начальное видео с логотипом компании R*. Некоторые источники сообщали, при совместном использовании всех этих способов можно сэкономить аж целых 10-30 секунд!
Тем временем на моём PC…
Бенчмарк
Время загрузки сюжетного режима: около 1 мин 10 с
Время загрузки онлайн-режима: около 6 мин
Меню запуска отключено, время от появления логотипа R* до начала самого игрового процесса (время логина в social club не считается).
Старый, но приличный ЦП: AMD FX-8350
Дешёвый SSD: KINGSTON SA400S37120G
Без оперативки не обойтись: 2 модуля Kingston 8192 МБ (DDR3-1337) 99U5471
Относительно неплохой GPU: NVIDIA GeForce GTX 1070
Понимаю, что моя машина устарела, но какого чёрта онлайн-режим загружается в шесть раз медленнее? Я не смог обнаружить никаких отличий при использовании техники загрузки «сначала сюжет, потом онлайн», как это удалось сделать другим до меня. Но даже бы если это сработало, то результаты находились бы в рамках погрешности.
Я не одинок
Если поверить этому опросу, то проблема настолько распространена, что слегка подбешивает более 80% базы игроков. Ребята из R*, вообще-то уже семь лет прошло!
У 18,8% игроков мощнейшие компьютеры или консоли, у 81,2% всё довольно грустно, у 35,1% — совсем печально.
Поискав 20% тех счастливчиков, загрузка у которых занимает меньше трёх минут, я нашёл некоторое количество бенчмарков с мощными игровыми PC и временем загрузки онлайн-режима примерно две минуты. Чтобы получить время загрузки в две минуты я бы убил хакнул что угодно! Похоже, время загрузки зависит от «железа», но числа как-то не сходятся…
Как получилось, что людей, делавших эти бенчмарки, загрузка сюжетного режима всё равно занимает примерно минуту? (Кстати, в бенчмарке с M.2 не учтено время показа логотипов в начале.) Кроме того, загрузка из сюжетного в онлайн-режим занимает у них всего минуту, а у меня — больше пяти. Я знаю, что у них техника намного лучше моей, но точно не в пять раз.
Очень точные измерения
Вооружённый такими мощными инструментами, как Диспетчер задач, я начал расследование, чтобы выяснить, какие ресурсы могут быть «узким местом».
В течение одной минуты загружаются стандартные ресурсы сюжетного режима, после чего игра в течение четырёх с лишним минут грузит процессор.
После минуты загрузки общих ресурсов, используемых и в сюжетном, и в онлайн-режимах (показатель, почти равный бенчмаркам мощных PC) GTA решает максимально нагружать одно ядро моей машины в течение четырёх минут и больше ничего не делать.
Обращение к диску? Его нет! Использование сети? Есть немного, но спустя всего несколько секунд трафик падает почти до нуля (кроме загрузки вращающихся баннеров с информацией). Использование GPU? По нулям. Использование памяти? Совершенно плоский график…
Что происходит, игра майнит крипту, или ещё чего? Начинает попахивать кодом. Очень плохим кодом.
Ограничение одним потоком
Хотя мой старый ЦП AMD имеет восемь ядер и всё ещё может себя показать, он был создан в старые времена. Тогда однопоточная производительность процессоров AMD намного отставала от показателей процессоров Intel. Возможно, это и не объясняет всю разницу во времени загрузки, но должно объяснить самое главное.
Странно то, что игра использует только ЦП. Я ожидал огромного объёма загружаемых с диска ресурсов или кучу сетевых запросов для создания сессии в сети p2p. Но это? Скорее всего, это баг.
Профилирование
Профилировщики — отличный способ поиска «узких мест» в работе ЦП. Есть только одна проблема — большинство из них для получения идеальной картины происходящего в процессе использует исходный код. А у меня его нет. Но мне не нужны и показания с точностью до микросекунд — «узкое место» длится целых четыре минуты.
На сцене появляется сэмплирование стека: это единственный вариант изучения приложений с закрытыми исходниками. Выполняем дамп стека запущенного процесса и местоположения указателя текущей команды, чтобы строить дерево вызовов с заданными интервалами. Затем складываем их, чтобы получить статистику о происходящем. Есть только один известный мне профилировщик (здесь я могу ошибаться), способный на такое в Windows. И он не обновлялся больше десяти лет. Это Luke Stackwalker! Пусть кто-нибудь подарит этому проекту свою любовь.
Виновники №1 и №2.
Обычно Luke группирует одинаковые функции, но поскольку у меня нет отладочных символов, мне нужно глазами просматривать ближайшие адреса, чтобы понять, что это одно и то же место. И что же мы видим? Не одно, а целых два «узких места»!
Вниз по кроличьей норе
Позаимствовав у друга совершенно законную копию популярного дизассемблера (нет, я не могу себе его позволить… придётся как-нибудь изучить ghidra), я приступил к разборке GTA.
Всё это кажется совсем неправильным. Многие высокобюджетные игры имеют встроенную защиту от реверс-инжиниринга, чтобы защититься от пиратов, читеров и моддеров (не сказать, чтобы это когда-то их останавливало).
Похоже, здесь используется некая обфускация/шифрование, из-за которого большинство команд заменено абракадаброй. Но не волнуйтесь, нам просто нужно сдампить память игры в момент выполнения части, которую мы хотим изучить. Перед своим выполнением команды тем или иным способом должны деобфусцироваться. У меня был под рукой Process Dump, но есть множество других инструментов, способных выполнять подобные функции.
Проблема №1: это… strlen?!
При дизассемблировании теперь уже менее обфусцированного дампа обнаруживается, что один из адресов имеет метку, взятую ниоткуда! Это strlen? Следующий вниз по стеку вызовов помечен как vscan_fn, после чего метки заканчиваются, однако я практически уверен, что это sscanf.
Они что-то парсят. Но что? Разбор дизассемблированного кода занял бы бесконечность, поэтому я решил сдампить некоторые сэмплы из запущенного процесса при помощи x64dbg. Проведя пошаговую отладку, я выяснил, что это… JSON! Они парсят JSON. Целых 10 мегабайт данных JSON с почти 63 тысячами элементов.
...,
{
"key": "WP_WCT_TINT_21_t2_v9_n2",
"price": 45000,
"statName": "CHAR_KIT_FM_PURCHASE20",
"storageType": "BITFIELD",
"bitShift": 7,
"bitSize": 1,
"category": ["CATEGORY_WEAPON_MOD"]
},
...
Что это? Согласно некоторым источникам, это похоже на данные «каталога сетевого магазина». Предположу, что они содержат список всех возможных предметов и апгрейдов, которые можно купить в GTA Online.
Уточнение: я считаю, что это предметы, покупаемые за внутриигровые деньги, а не связанные напрямую с микротранзакциями.
Но 10 мегабайт — это ведь мелочь! А использование sscanf пусть и не оптимально, но не может же оно быть настолько плохим? Ну-у-у…
10 мегабайт строк C в памяти. 1. Перемещаем указатель на несколько байт к следующему значению. 2. Вызываем sscanf(p, "%d", ...). 3. Считываем каждый символ в 10 мегабайтах при считывании каждого мелкого значения (!?). 4. Возвращаем отсканированное значение.
Да, это займёт много времени… Честно говоря, я понятия не имел, что большинство реализаций sscanf называются strlen, поэтому не могу винить написавшего это разработчика. Я бы предположил, что эти данные просто сканируются байт за байтом и обработка может остановиться на NULL.
Проблема №2: давайте используем хэш-… массив?
Оказалось, что второй виновник вызывается непосредственно рядом с первым. Они оба даже вызываются в одном операторе if, как можно понять в этой уродливой декомпиляции:
Обе проблемы находятся внутри одного большого цикла парсинга всех предметов. Проблема №1 — парсинг, проблема №2 — сохранение.
Все метки указаны мной, я понятия не имею, как по-настоящему называются функции и параметры.
В чём же заключается вторая проблема? Сразу после парсинга предмета он сохраняется в массив (или во встроенный список C++? не совсем понятно). Каждый предмет выглядит примерно так:
struct {
uint64_t *hash;
item_t *item;
} entry;
Но что происходит перед сохранением? Код проверяет весь массив, один элемент за другим, сравнивая хэш предмета, чтобы понять, находится ли он в списке. Если мои вычисления верны, то при примерно 63 тысячах элементов это даёт (n^2+n)/2 = (63000^2+63000)/2 = 1984531500 проверок. Большинство из них бесполезно. У нас есть уникальные хэши, так почему бы не использовать hash map?
Профилировщик показывает, что процессор нагружают первые две строки. Оператор if выполняется только в самом конце. Предпоследняя строка вставляет предмет.
В процессе обратной разработки я назвал эту структуру hashmap, однако очевидно, что это not_a_hashmap. И дальше всё становится только лучше. Этот хэш/массив/список перед загрузкой JSON пуст. И все предметы в JSON уникальны! Коду даже не нужно проверять, есть ли предмет в списке! Есть даже функция для непосредственной вставки предметов, достаточно просто использовать её! Серьёзно, чё за фигня!?
Proof of Concept
Всё это конечно здорово, но никто не воспримет меня всерьёз, пока я это не протестирую, чтобы можно было написать к посту кликбейтный заголовок.
Каким будет план? Написать .dll, инъецировать её GTA, перехватить несколько функций, ???, ПРОФИТ!
Проблема с JSON запутанна, и замена парсера окажется чрезвычайно трудоёмкой задачей. Гораздо реалистичнее будет попытаться заменить sscanf на функцию, не зависящую от strlen. Но есть ещё более простой способ.
- перехватить strlen
- дождаться длинной строки
- «кэшировать» её начало и длину
- если она снова вызывается в пределах строки, возвращать кэшированное значение
Что-то типа такого:
size_t strlen_cacher(char* str)
{
static char* start;
static char* end;
size_t len;
const size_t cap = 20000;
// if we have a "cached" string and current pointer is within it
if (start && str >= start && str <= end) {
// calculate the new strlen
len = end - str;
// if we're near the end, unload self
// we don't want to mess something else up
if (len < cap / 2)
MH_DisableHook((LPVOID)strlen_addr);
// super-fast return!
return len;
}
// count the actual length
// we need at least one measurement of the large JSON
// or normal strlen for other strings
len = builtin_strlen(str);
// if it was the really long string
// save it's start and end addresses
if (len > cap) {
start = str;
end = str + len;
}
// slow, boring return
return len;
}
Что касается проблемы хэш-массива, то с ней всё проще — можно просто полностью пропускать дублирующиеся проверки и вставлять предметы напрямую, потому что мы знаем, что значения уникальны.
char __fastcall netcat_insert_dedupe_hooked(uint64_t catalog, uint64_t* key, uint64_t* item)
{
// didn't bother reversing the structure
uint64_t not_a_hashmap = catalog + 88;
// no idea what this does, but repeat what the original did
if (!(*(uint8_t(__fastcall**)(uint64_t*))(*item + 48))(item))
return 0;
// insert directly
netcat_insert_direct(not_a_hashmap, key, &item);
// remove hooks when the last item's hash is hit
// and unload the .dll, we are done here :)
if (*key == 0x7FFFD6BE) {
MH_DisableHook((LPVOID)netcat_insert_dedupe_addr);
unload();
}
return 1;
}
Полные исходники proof of concept находятся здесь.
Результаты
Ну и как, сработало?
Исходное время загрузки онлайн-режима: примерно 6 мин
Время только с пропатченными дублируемыми проверками: 4 мин 30 с
Время только с патчем парсера JSON: 2 мин 50 с
Время с патчами обеих проблем: 1 мин 50 с
(6*60 — (1*60+50)) / (6*60) = время загрузки уменьшилось на 69.4% (отлично!)
О да, ещё как сработало!
Скорее всего, это не уменьшит время загрузки у всех игроков — на других системах могут быть другие «узкие места», но это настолько очевидная проблема, что я не понимаю, как R* не замечала её все эти годы.
tl;dr
- При запуске GTA Online есть узкое место ЦП из-за однопотокового выполнения
- Оказывается, в это время GTA сражается с парсингом 10-мегабайтного файла JSON
- Сам парсер JSON плохо написан/наивно реализован и
- После парсинга выполняется медленная процедура проверки отсутствия дубликатов предметов
R*, пожалуйста, решите проблему
Просьба, если эта статья каким-то образом доберётся до Rockstar: на решение этих проблем не уйдёт больше дня работы одного разработчика. Пожалуйста, сделайте с этим что-нибудь.
Можно перейти на hashmap для устранения дубликатов или полностью пропускать эту проверку, что будет реализовать быстрее. В парсере JSON замените библиотеку на более производительную. Не думаю, что здесь есть более простое решение.
Спасибо.
===========
Источник:
habr.com
===========
===========
Автор оригинала: T0ST
===========Похожие новости:
- [Программирование, Клиентская оптимизация, Разработка игр, Софт] Как я сократил время загрузки GTA Online на 70% (перевод)
- [Презентации, Игры и игровые приставки, Здоровье] СМИ сообщили, что очной выставки видеоигр E3 в этом году не будет
- [Игры и игровые приставки, IT-компании] Greetings, Terrarians! Terraria снова в Stadia?
- [Разработка игр, Конференции, Игры и игровые приставки] Бесплатная онлайн-конференция Нарратив в играх
- [Разработка мобильных приложений, Разработка игр, Unity] Не мешай ему взрослеть: как оптимизировать «растущее» приложение, чтобы оно оставалось удобным для пользователя
- [Игры и игровые приставки, IT-компании] Суд обязал Valve предоставить Apple статистику продаж в Steam
- [Информационная безопасность, Реверс-инжиниринг] Распаковка исполняемых файлов
- [Информационная безопасность, Разработка игр, Игры и игровые приставки, IT-компании] Инцидент с вирусом-криптовымогателем в CD Projekt Red подорвал работу над обновлениями Cyberpunk 2077
- [Гаджеты, История IT, Периферия, Игры и игровые приставки, IT-компании] HP купила производителя игровой периферии HyperX за $425 млн
- [Разработка игр, Игры и игровые приставки, Интервью] Подкаст «Хочу в геймдев» выпуск #20
Теги для поиска: #_razrabotka_igr (Разработка игр), #_reversinzhiniring (Реверс-инжиниринг), #_igry_i_igrovye_pristavki (Игры и игровые приставки), #_gta, #_rockstar, #_obratnaja_razrabotka (обратная разработка), #_onlajnigry (онлайн-игры), #_otladka (отладка), #_bagi (баги), #_gta_online, #_razrabotka_igr (
Разработка игр
), #_reversinzhiniring (
Реверс-инжиниринг
), #_igry_i_igrovye_pristavki (
Игры и игровые приставки
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:43
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
GTA Online печально известна своей медленной скоростью загрузки. Запустив недавно игру, чтобы выполнить новые миссии-налёты, я был шокирован тем, что она загружается так же медленно, как и в момент выпуска семь лет назад. Время настало. Пока разобраться в причинах этого. Разведка Для начала я захотел проверить, не решил ли уже кто-нибудь эту проблему. Большинство найденных результатов состояло из анекдотичных данных о том, насколько сложна игра, что ей приходится грузиться так долго, историй об отстойности сетевой архитектуры p2p (и в этом есть правда), сложных способов загрузки в сюжетный режим, а после него в одиночную сессию и пары модов, позволявших пропустить начальное видео с логотипом компании R*. Некоторые источники сообщали, при совместном использовании всех этих способов можно сэкономить аж целых 10-30 секунд! Тем временем на моём PC… Бенчмарк Время загрузки сюжетного режима: около 1 мин 10 с
Время загрузки онлайн-режима: около 6 мин Меню запуска отключено, время от появления логотипа R* до начала самого игрового процесса (время логина в social club не считается). Старый, но приличный ЦП: AMD FX-8350 Дешёвый SSD: KINGSTON SA400S37120G Без оперативки не обойтись: 2 модуля Kingston 8192 МБ (DDR3-1337) 99U5471 Относительно неплохой GPU: NVIDIA GeForce GTX 1070 Понимаю, что моя машина устарела, но какого чёрта онлайн-режим загружается в шесть раз медленнее? Я не смог обнаружить никаких отличий при использовании техники загрузки «сначала сюжет, потом онлайн», как это удалось сделать другим до меня. Но даже бы если это сработало, то результаты находились бы в рамках погрешности. Я не одинок Если поверить этому опросу, то проблема настолько распространена, что слегка подбешивает более 80% базы игроков. Ребята из R*, вообще-то уже семь лет прошло! У 18,8% игроков мощнейшие компьютеры или консоли, у 81,2% всё довольно грустно, у 35,1% — совсем печально. Поискав 20% тех счастливчиков, загрузка у которых занимает меньше трёх минут, я нашёл некоторое количество бенчмарков с мощными игровыми PC и временем загрузки онлайн-режима примерно две минуты. Чтобы получить время загрузки в две минуты я бы убил хакнул что угодно! Похоже, время загрузки зависит от «железа», но числа как-то не сходятся… Как получилось, что людей, делавших эти бенчмарки, загрузка сюжетного режима всё равно занимает примерно минуту? (Кстати, в бенчмарке с M.2 не учтено время показа логотипов в начале.) Кроме того, загрузка из сюжетного в онлайн-режим занимает у них всего минуту, а у меня — больше пяти. Я знаю, что у них техника намного лучше моей, но точно не в пять раз. Очень точные измерения Вооружённый такими мощными инструментами, как Диспетчер задач, я начал расследование, чтобы выяснить, какие ресурсы могут быть «узким местом». В течение одной минуты загружаются стандартные ресурсы сюжетного режима, после чего игра в течение четырёх с лишним минут грузит процессор. После минуты загрузки общих ресурсов, используемых и в сюжетном, и в онлайн-режимах (показатель, почти равный бенчмаркам мощных PC) GTA решает максимально нагружать одно ядро моей машины в течение четырёх минут и больше ничего не делать. Обращение к диску? Его нет! Использование сети? Есть немного, но спустя всего несколько секунд трафик падает почти до нуля (кроме загрузки вращающихся баннеров с информацией). Использование GPU? По нулям. Использование памяти? Совершенно плоский график… Что происходит, игра майнит крипту, или ещё чего? Начинает попахивать кодом. Очень плохим кодом. Ограничение одним потоком Хотя мой старый ЦП AMD имеет восемь ядер и всё ещё может себя показать, он был создан в старые времена. Тогда однопоточная производительность процессоров AMD намного отставала от показателей процессоров Intel. Возможно, это и не объясняет всю разницу во времени загрузки, но должно объяснить самое главное. Странно то, что игра использует только ЦП. Я ожидал огромного объёма загружаемых с диска ресурсов или кучу сетевых запросов для создания сессии в сети p2p. Но это? Скорее всего, это баг. Профилирование Профилировщики — отличный способ поиска «узких мест» в работе ЦП. Есть только одна проблема — большинство из них для получения идеальной картины происходящего в процессе использует исходный код. А у меня его нет. Но мне не нужны и показания с точностью до микросекунд — «узкое место» длится целых четыре минуты. На сцене появляется сэмплирование стека: это единственный вариант изучения приложений с закрытыми исходниками. Выполняем дамп стека запущенного процесса и местоположения указателя текущей команды, чтобы строить дерево вызовов с заданными интервалами. Затем складываем их, чтобы получить статистику о происходящем. Есть только один известный мне профилировщик (здесь я могу ошибаться), способный на такое в Windows. И он не обновлялся больше десяти лет. Это Luke Stackwalker! Пусть кто-нибудь подарит этому проекту свою любовь. Виновники №1 и №2. Обычно Luke группирует одинаковые функции, но поскольку у меня нет отладочных символов, мне нужно глазами просматривать ближайшие адреса, чтобы понять, что это одно и то же место. И что же мы видим? Не одно, а целых два «узких места»! Вниз по кроличьей норе Позаимствовав у друга совершенно законную копию популярного дизассемблера (нет, я не могу себе его позволить… придётся как-нибудь изучить ghidra), я приступил к разборке GTA. Всё это кажется совсем неправильным. Многие высокобюджетные игры имеют встроенную защиту от реверс-инжиниринга, чтобы защититься от пиратов, читеров и моддеров (не сказать, чтобы это когда-то их останавливало). Похоже, здесь используется некая обфускация/шифрование, из-за которого большинство команд заменено абракадаброй. Но не волнуйтесь, нам просто нужно сдампить память игры в момент выполнения части, которую мы хотим изучить. Перед своим выполнением команды тем или иным способом должны деобфусцироваться. У меня был под рукой Process Dump, но есть множество других инструментов, способных выполнять подобные функции. Проблема №1: это… strlen?! При дизассемблировании теперь уже менее обфусцированного дампа обнаруживается, что один из адресов имеет метку, взятую ниоткуда! Это strlen? Следующий вниз по стеку вызовов помечен как vscan_fn, после чего метки заканчиваются, однако я практически уверен, что это sscanf. Они что-то парсят. Но что? Разбор дизассемблированного кода занял бы бесконечность, поэтому я решил сдампить некоторые сэмплы из запущенного процесса при помощи x64dbg. Проведя пошаговую отладку, я выяснил, что это… JSON! Они парсят JSON. Целых 10 мегабайт данных JSON с почти 63 тысячами элементов. ...,
{ "key": "WP_WCT_TINT_21_t2_v9_n2", "price": 45000, "statName": "CHAR_KIT_FM_PURCHASE20", "storageType": "BITFIELD", "bitShift": 7, "bitSize": 1, "category": ["CATEGORY_WEAPON_MOD"] }, ... Что это? Согласно некоторым источникам, это похоже на данные «каталога сетевого магазина». Предположу, что они содержат список всех возможных предметов и апгрейдов, которые можно купить в GTA Online. Уточнение: я считаю, что это предметы, покупаемые за внутриигровые деньги, а не связанные напрямую с микротранзакциями. Но 10 мегабайт — это ведь мелочь! А использование sscanf пусть и не оптимально, но не может же оно быть настолько плохим? Ну-у-у… 10 мегабайт строк C в памяти. 1. Перемещаем указатель на несколько байт к следующему значению. 2. Вызываем sscanf(p, "%d", ...). 3. Считываем каждый символ в 10 мегабайтах при считывании каждого мелкого значения (!?). 4. Возвращаем отсканированное значение. Да, это займёт много времени… Честно говоря, я понятия не имел, что большинство реализаций sscanf называются strlen, поэтому не могу винить написавшего это разработчика. Я бы предположил, что эти данные просто сканируются байт за байтом и обработка может остановиться на NULL. Проблема №2: давайте используем хэш-… массив? Оказалось, что второй виновник вызывается непосредственно рядом с первым. Они оба даже вызываются в одном операторе if, как можно понять в этой уродливой декомпиляции: Обе проблемы находятся внутри одного большого цикла парсинга всех предметов. Проблема №1 — парсинг, проблема №2 — сохранение. Все метки указаны мной, я понятия не имею, как по-настоящему называются функции и параметры. В чём же заключается вторая проблема? Сразу после парсинга предмета он сохраняется в массив (или во встроенный список C++? не совсем понятно). Каждый предмет выглядит примерно так: struct {
uint64_t *hash; item_t *item; } entry; Но что происходит перед сохранением? Код проверяет весь массив, один элемент за другим, сравнивая хэш предмета, чтобы понять, находится ли он в списке. Если мои вычисления верны, то при примерно 63 тысячах элементов это даёт (n^2+n)/2 = (63000^2+63000)/2 = 1984531500 проверок. Большинство из них бесполезно. У нас есть уникальные хэши, так почему бы не использовать hash map? Профилировщик показывает, что процессор нагружают первые две строки. Оператор if выполняется только в самом конце. Предпоследняя строка вставляет предмет. В процессе обратной разработки я назвал эту структуру hashmap, однако очевидно, что это not_a_hashmap. И дальше всё становится только лучше. Этот хэш/массив/список перед загрузкой JSON пуст. И все предметы в JSON уникальны! Коду даже не нужно проверять, есть ли предмет в списке! Есть даже функция для непосредственной вставки предметов, достаточно просто использовать её! Серьёзно, чё за фигня!? Proof of Concept Всё это конечно здорово, но никто не воспримет меня всерьёз, пока я это не протестирую, чтобы можно было написать к посту кликбейтный заголовок. Каким будет план? Написать .dll, инъецировать её GTA, перехватить несколько функций, ???, ПРОФИТ! Проблема с JSON запутанна, и замена парсера окажется чрезвычайно трудоёмкой задачей. Гораздо реалистичнее будет попытаться заменить sscanf на функцию, не зависящую от strlen. Но есть ещё более простой способ.
Что-то типа такого: size_t strlen_cacher(char* str)
{ static char* start; static char* end; size_t len; const size_t cap = 20000; // if we have a "cached" string and current pointer is within it if (start && str >= start && str <= end) { // calculate the new strlen len = end - str; // if we're near the end, unload self // we don't want to mess something else up if (len < cap / 2) MH_DisableHook((LPVOID)strlen_addr); // super-fast return! return len; } // count the actual length // we need at least one measurement of the large JSON // or normal strlen for other strings len = builtin_strlen(str); // if it was the really long string // save it's start and end addresses if (len > cap) { start = str; end = str + len; } // slow, boring return return len; } Что касается проблемы хэш-массива, то с ней всё проще — можно просто полностью пропускать дублирующиеся проверки и вставлять предметы напрямую, потому что мы знаем, что значения уникальны. char __fastcall netcat_insert_dedupe_hooked(uint64_t catalog, uint64_t* key, uint64_t* item)
{ // didn't bother reversing the structure uint64_t not_a_hashmap = catalog + 88; // no idea what this does, but repeat what the original did if (!(*(uint8_t(__fastcall**)(uint64_t*))(*item + 48))(item)) return 0; // insert directly netcat_insert_direct(not_a_hashmap, key, &item); // remove hooks when the last item's hash is hit // and unload the .dll, we are done here :) if (*key == 0x7FFFD6BE) { MH_DisableHook((LPVOID)netcat_insert_dedupe_addr); unload(); } return 1; } Полные исходники proof of concept находятся здесь. Результаты Ну и как, сработало? Исходное время загрузки онлайн-режима: примерно 6 мин
Время только с пропатченными дублируемыми проверками: 4 мин 30 с Время только с патчем парсера JSON: 2 мин 50 с Время с патчами обеих проблем: 1 мин 50 с (6*60 — (1*60+50)) / (6*60) = время загрузки уменьшилось на 69.4% (отлично!) О да, ещё как сработало! Скорее всего, это не уменьшит время загрузки у всех игроков — на других системах могут быть другие «узкие места», но это настолько очевидная проблема, что я не понимаю, как R* не замечала её все эти годы. tl;dr
R*, пожалуйста, решите проблему Просьба, если эта статья каким-то образом доберётся до Rockstar: на решение этих проблем не уйдёт больше дня работы одного разработчика. Пожалуйста, сделайте с этим что-нибудь. Можно перейти на hashmap для устранения дубликатов или полностью пропускать эту проверку, что будет реализовать быстрее. В парсере JSON замените библиотеку на более производительную. Не думаю, что здесь есть более простое решение. Спасибо. =========== Источник: habr.com =========== =========== Автор оригинала: T0ST ===========Похожие новости:
Разработка игр ), #_reversinzhiniring ( Реверс-инжиниринг ), #_igry_i_igrovye_pristavki ( Игры и игровые приставки ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:43
Часовой пояс: UTC + 5