- BrainTools - https://www.braintools.ru -

В этой статье мы проанализируем код одной из самых известных в мире игр: Grand Theft Auto Vice City. Это игра, на которой мы выросли. Хотя с момента её выпуска прошло более двадцати лет, код игры по-прежнему нас восхищает. Он не только хорошо работает на CPU с частотой 300 МГц (поскольку изначально его писали для PlayStation 2), но и обеспечивает игровой процесс без экранов загрузки сегментов карты, несмотря на ограничение в 32 МБ ОЗУ.
Здесь стоит упомянуть, что мы будем рассматривать не оригинальный исходный код, написанный Rockstar, а код на C++, полученный реверс-инжинирингом двоичных файлов игры. Мы поговорим о специфике эпохи PS2, почему игра почти никогда не распределяет память [1], о том, что весь город состоит из 25 пешеходов и 12 машин, обсудим работу физики и распознавание коллизий. В игре даже есть используемая для трюков система реплеев, которой для записи всех движущихся вокруг игрока объектов хватает одного мегабайта памяти.
Кроме того, Rockstar реализовала точно ту же механику работы с данными, что и в современных ECS-движках: целое число, в котором упакованы индекс слота и счётчик поколений, благодаря чему устаревшая ссылка ни на что не указывает.
В конце мы поговорим о знаменитых читах игры и о том, как она боролась с пиратством, ломая снайперскую винтовку, создавая ураган или даже повреждая данные сохранений.
Насколько я знаю, Vice City разрабатывали специально для консоли PlayStation 2, а значит, авторам были доступны всего 32 МБ основной ОЗУ CPU на 300 МГц под названием Emotion Engine, GPU Graphics Synthesizer с 4 МБ довольно быстрой для того времени ОЗУ и DVD-привод для потоковой передачи ресурсов игры со скоростью примерно 5 МБ/с. Любопытно, что, несмотря на низкую тактовую частоту CPU Emotion Engine, он был довольно производительным и обеспечивал до 6,2 гигафлопс, хотя его мощь в основном обеспечивалась двумя векторными блоками — VU0 и VU1.

Файл collision.cpp [2]:
GetVUresult(void)
{
#ifdef GTA_PS2
int ret;
__asm__ volatile (
"cfc2.i %0,vi01n" // .i important! wait for VU0 to finish
: "=r" (ret)
);
return ret;
#else
return vi01;
#endif
}
Здесь мы видим, что VU0 используется в коде, огороженный условием проверки GTA_PS2. Он применяется для системы коллизий, содержащей написанный вручную ассемблерный код векторного блока.
Что касается скорости чтения, то 5 МБ/с кажется не очень высоким показателем, но на деле это никогда не вызывало проблем. Однако трудности могут возникнуть из-за перемещения лазерной головки: каждая произвольная операция поиска занимала примерно 0,1 с, то есть примерно полмегабайта терялись на перемещения по диску. Поэтому код чтения файлов считывает файлы не в порядке, в котором их запрашивает игра, а сортирует их по физическому расположению на диске и соответствующим образом перемещает лазерную головку, считывая их при её прохождении. Кстати, это хороший пример алгоритма планирования доступа к диску circular elevator, также известного как C-Scan, который использовался в коде.
Наверно, первое, что я заметил — это то, что Vice City почти никогда не выполняет распределение памяти под игровые объекты. И буквально у каждого объекта в игре есть бюджет времени компиляции. 140 пешеходов, 110 транспортных средств, 7 тысяч зданий, 460 объектов и так далее. Каждый тип находится в отдельном пуле — сплошном блоке памяти, распределяемом при запуске программы, что не требует обхода кучи и обозначает отсутствие фрагментации.
// Pool sizes
NUMPTRNODES = 50000,
NUMENTRYINFOS = 3200,
NUMPEDS = 140,
NUMVEHICLES = 110,
NUMBUILDINGS = 7000,
NUMTREADABLES = 1,
NUMOBJECTS = 460,
NUMDUMMIES = 2340,
NUMAUDIOSCRIPTOBJECTS = 192,
NUMCOLMODELS = 4400,
NUMCUTSCENEOBJECTS = 50, // not a pool in VC
А когда пул заполнен, система создания объектов просто перестаёт работать. В игре становится меньше пешеходов, из-за чего город кажется намного более пустым. В коде также есть хитрый трюк с валидацией указателя. Код получает произвольный указатель и спрашивает у пула пешеходов, находится ли этот адрес в его диапазоне памяти. А поскольку все пешеходы находятся в одном сплошном блоке, арифметика указателей просто вычитает базовый адрес и проверяет, попал ли индекс внутрь пула. Это валидация указателя по диапазону адресов, дешёвое, но эффективное решение… Точнее, почти. Дело в том, что в оригинальном коде игры был баг, из-за которого код принимал на один слот дальше от конца пула как валидный, а реверс-инженеры устранили его. Поэтому теперь код [3] без ошибок находится за условием FIX_BUGS
bool
IsPedPointerValid_NotInWorld(CPed* pPed)
{
if (!pPed)
return false;
int index = CPools::GetPedPool()->GetJustIndex_NoFreeAssert(pPed);
#ifdef FIX_BUGS
if (index < 0 || index >= NUMPEDS)
#else
if (index < 0 || index > NUMPEDS)
#endif
return false;
return true;
}
Когда я играл в Vice City в детстве, мне всегда было интересно, как разработчики реализовали физику. Она ощущается аркадной, но ни в одной другой игре она не казалась столь хорошей, особенно когда прыгаешь, бегаешь или просто перемещаешься. Должен сказать, что и сегодня от неё получаешь удовольствие. Гравитация в мире Vice City равна 0,008 размерным единицам на время в квадрате (время измеряется в кадрах, а игра работает с частотой 50 FPS), что примерно равно 20 м/с2 , то есть вдвое больше земной гравитации.

Очевидно, кто-то в Rockstar решил, что реалистичная гравитация кажется слишком плавной и скучной, а с удвоенной она гораздо интереснее. Но они поменяли только саму константу, все остальные расчёты остались честными. Ровно одна строка кода связывает линейную массу и момент инерции для ответа на один простой вопрос: насколько тяжёлым ощущается объект, когда его толкают в этой точке в этом направлении? Если ударить по краю машины, её закрутит, а если по центру, то она будет ощущаться тяжёлой. Благодаря всему этому и создаётся знаменитая прыгучая физика GTA.

Кстати, у автомобилей в игре нет симуляции колёс, в них используется только четыре луча с пружинами амортизаторов, приложенных к точке касания. Это в буквальном смысле подвеска, состоящая из четырёх лазерных лучей, испускаемых из куба, который мы называем автомобилем.
Если вы когда-нибудь играли в Vice City на PC, то, вероятно, замечали, как всё ломается при высоком FPS. Причина этого заключается в переменной ms_fTimeStep. При 50 FPS её значение равно 1,0. При 100 FPS значение равно 0,5. При 200 FPS оно будет равно 0,25. Основная часть кода предполагает, что это значение всегда примерно равно 1,0, поэтому когда разница существенна, игра ломается.
static float GetTimeStepInSeconds() { return ms_fTimeStep / 50.0f; }
Ещё мне нравится, как Rockstar подошла к защите от пиратства. Впрочем, сам код обнаружения пиратства находится не здесь, потому что порт для PC выпускался с SecuROM — специальным слоем защиты от копирования, который, насколько я понимаю, применялся на этапе компиляции игры. Даже в случае обнаружения пиратской копии сама игра не отказывалась запускаться. Спираченная версия Vice City работала нормально… за исключением, так скажем, несущественных отличий. В коде присутствует восемь механик саботажа, и каждая из них реализуется не сразу, случайным образом или при определённых условиях. Например, когда игрок пытался воспользоваться снайперской винтовкой, она ничем не стреляла.

Также отключался весь граф дорог и навигации пешеходов. А спустя примерно 35 минут после начала игры все гаражи переставали работать, поэтому связанные с ними миссии невозможно было пройти. Также при каждом прохождении или провале миссии с вероятностью 1 к 4 погода могла превратиться в постоянный ураган. А когда где-нибудь создавалась пожарная машина, с вероятностью 1 к 8 со всей карты могли пропасть все поднимаемые предметы. Больше же всего мне нравится такая механика: при создании полицейских баррикад в случае достижения определённого уровня розыска полностью пропадали все метки на радаре из интерфейса игрока. Кроме того, если файл сохранения был создан после 72 тысяч кадров прохождения игры, то есть примерно через 20–40 минут, при попытке загрузить его данные повреждались.
#ifdef SECUROM
if (CTimer::m_FrameCounter > 72000){
buf += align4bytes(4);
}
#endif
Всё это забавно, потому что выглядит как обычные игровые баги; наверно, именно так разработчики и задумывали. Кстати, насколько я знаю, избавление от слоя защиты не восстанавливало игру полностью. Код здесь чуть сложнее, чем можно было бы предположить. Например, переменной sniperPirateCheck присваивалось какое-то мусорное значение, а если оно не равнялось нулю, то снайперская винтовка не работала. То есть саботаж здесь являлся исходным состоянием игры. Следовательно, только когда защита присутствует и подтверждает официальность копии, она присваивает нулевое значение, которое «чинит» винтовку.
#ifdef SECUROM
int32 sniperPirateCheck = 0x00797743; // 'Cwy' ???
#endif
Я всегда задавался ещё одним вопросом: как же конкретно прописан в коде открытый мир Grand Theft Auto: Vice City? В игре отсутствуют загрузки секторов, при этом она идеально работала на железе PS2. Поначалу это кажется настоящей магией, но прочитав код, я теперь понимаю, как всё это устроено.

Во-первых, мир Vice City представляет собой сетку 80 × 80, где каждая ячейка — это квадрат со стороной 50 метров. Это статически распределённый двухмерный массив. Кроме того, каждый сектор карты хранит 10 интрузивных связанных списков: здания, транспорт, пешеходы, объекты и так далее. Благодаря этому простому подходу распознавание коллизий работает очень быстро: когда игра задаёт вопросы, например «куда может попасть эта пуля?», «какие машины находятся рядом с этим взрывом?» или «что находится под колёсами?», всё в конечном итоге сводится к одной формуле. Достаточно преобразовать координаты в диапазон ячеек и пройтись по нескольким коротким спискам. Вот и всё. Как мы помним, память под игровые объекты распределена заранее, то есть при добавлении нового объекта в сектор ничего не распределяется. Также стоит упомянуть хитрый трюк, который использовали разработчики для объектов, пересекающих несколько границ. Допустим, большой грузовик может одновременно находиться в четырёх секторах. Очевидно, что для этого объекта нужен какой-то флаг, отмечающий, что его уже проверили. Но как его правильно реализовать? Ленивые разработчики сегодня, вероятно, перебирали бы в цикле десять с лишним тысяч элементов, присваивая флагу visited значение false, а при запросе меняя его на true. Проблема в том, что цикл сброса будет выполняться в каждом запросе, то есть в секунду будут выполняться тысячи запросов. Это огромная пустая трата ресурсов, которую не могли себе позволить разработчики GTA.
Вместо этого они придумали хитрый трюк со сканированием. Не нужно хранить булево значение, достаточно хранить число и один глобальный счётчик. При начале запроса код выполняет инкремент глобального счётчика, а при касании сущности помечает её этим числом. То есть при касании другой сущности достаточно проверить значение отметки, и если оно равно текущему глобальному счётчику, просто пропустить её. Благодаря этому флаг visited совершенно не влияет на производительность. Один простой инкремент стирает состояние visited всего мира. Разумеется, рано или поздно необходимо будет сбросить значение, потому что глобальный счётчик — это 16-битное значение, способное хранить числа до 65535. То есть время от времени это замедляет итерации по всем сущностям, но практически не влияет на производительность.

При движении игрока по миру Vice City различные ассеты загружаются с диска в реальном времени. Всё это происходит в одном файле streaming.cpp размером в три тысячи строк. Это вторая часть системы, позволяющей GTA работать в открытом мире без постоянных загрузок.
Ещё раз напомню, что PS2 имела всего 32 МБ основной ОЗУ, поэтому модели, текстуры, коллизии и анимации загружались с диска в процессе игры. И вся эта система была сильно оптимизирована. Мы уже говорили о том, как она считывала данные с диска при помощи алгоритма C-Scan, но в ней имелось и ещё более интересное правило: в одной группе не могло быть двух пешеходов и двух больших файлов.
// Can't load certain combinations of files together
if(streamId < STREAM_OFFSET_TXD){
if (havePed && CModelInfo::GetModelInfo(streamId)->GetModelType() == MITYPE_PED ||
haveBigFile && CModelInfo::GetModelInfo(streamId)->GetModelType() == MITYPE_VEHICLE ||
TxdNotLoaded(CModelInfo::GetModelInfo(streamId)->GetTxdSlot()) ||
AnimNotLoaded(CModelInfo::GetModelInfo(streamId)->GetAnimFileIndex()))
break;
}else{
if(haveBigFile && size > 200)
break;
}
Это было необходимо, чтобы уместиться в бюджет декодирования, потому что оно выполнялось в основном потоке. Кстати, у кода ввода-вывода присутствовал свой отдельный поток CDStream, единственная задача которого заключалась в считывании запрошенных байтов с DVD. Забавно, что при сбое чтения отладочное сообщение об ошибке [4] было довольно эмоциональным:
if(CdStreamRead(ch, ms_pStreamingBuffer[ch], imgOffset+posn, totalSize) == STREAM_NONE)
debug("FUCKFUCKFUCKn");
При исчерпании памяти код выгружал неиспользуемые данные при помощи двух функций, RemoveLeastUsedModel и DeleteRwObjectsBehindCamera. Первая выгружает объект с нулевым количеством ссылок, а вторая просто выгружает ассеты за пределами видимости камеры, пока не освободит достаточно памяти. Очевидно, это был очень малозатратный трюк, ведь игрок не видит город, выгружаемый за его спиной.

Кроме того, в Vice City есть система реплеев. При выполнении каскадёрского трюка включается его реплей или можно воспроизвести его вручную. Вопрос в том, как это реализовано. Игра и так едва укладывалась в ограничения оборудования, поэтому разработчики могли просто избавиться от системы реплеев, ведь она не так важна. Думаю, они сохранили её тоже благодаря хитрой оптимизации. Эта фича требует менее 1 МБ памяти для хранения примерно 30 секунд истории мира. И она умещается в эти рамки, потому что ничто не хранится в сыром виде. Состояние мира дискретизируется в типированные пакеты: обновления транспорта, обновления пешеходов, погода, часы и даже целые анимации пешеходов ужимаются в поля размером в байт. То же относится к позициям и направлениям, они сэмплируются в каждом кадре, а при воспроизведении реплея интерполируются. Когда реплей запускается, на самом деле он ничего не симулирует. Каждая машина и пешеход превращаются в марионеток, управляемых непосредственно из записанного потока. Кроме того, это довольно малозатратно, ведь воспроизведение реплея не требует расчётов физики. И, разумеется, при реплеях симуляция вокруг игрока останавливается.
enum {
REPLAYPACKET_END = 0,
REPLAYPACKET_VEHICLE,
REPLAYPACKET_BIKE,
REPLAYPACKET_PED_HEADER,
REPLAYPACKET_PED_UPDATE,
REPLAYPACKET_GENERAL,
REPLAYPACKET_CLOCK,
REPLAYPACKET_WEATHER,
REPLAYPACKET_ENDOFFRAME,
REPLAYPACKET_TIMER,
REPLAYPACKET_BULLET_TRACES,
REPLAYPACKET_PARTICLE,
REPLAYPACKET_MISC
};
Мне очень любопытно было изучить и код искусственного интеллекта [5] пешеходов. Разумеется, ИИ Vice City не представляет собой ничего особенного, но для того времени он был превосходным. Мир вокруг казался живым. И люди в нём — это не просто манекены. Мозг [6] ИИ на самом деле — это двухслойный конечный автомат. PedState отвечает за низкоуровневые действия, например, ходьбу, убегание, прицеливание, прыжки и так далее. eObjective обрабатывает цели более высокого уровня: залезть в машину в качестве пассажира, купить мороженое, напасть на игрока, охранять точку и так далее.
enum PedState
{
PED_NONE,
PED_IDLE,
PED_LOOK_ENTITY,
PED_LOOK_HEADING,
PED_WANDER_RANGE,
PED_WANDER_PATH,
PED_SEEK_POS,
PED_SEEK_ENTITY,
PED_FLEE_POS,
PED_FLEE_ENTITY,
PED_PURSUE,
PED_FOLLOW_PATH,
PED_SNIPER_MODE,
PED_ROCKET_MODE,
PED_DUMMY,
PED_PAUSE,
PED_ATTACK,
PED_FIGHT,
PED_FACE_PHONE,
PED_MAKE_CALL,
PED_CHAT,
PED_MUG,
PED_AIM_GUN,
PED_AI_CONTROL,
PED_SEEK_CAR,
PED_SEEK_IN_BOAT,
PED_FOLLOW_ROUTE,
PED_CPR,
PED_SOLICIT,
PED_BUY_ICECREAM,
PED_INVESTIGATE,
PED_STEP_AWAY,
PED_ON_FIRE,
PED_SUN_BATHE,
PED_FLASH,
PED_JOG,
PED_ANSWER_MOBILE,
PED_UNKNOWN, // Same with IDLE, but also infects up to 5 peds with same pedType and WANDER_PATH, so they become stone too. HANG_OUT in Fire_Head's idb
PED_STATES_NO_AI,
PED_ABSEIL,
PED_SIT,
PED_JUMP,
PED_FALL,
PED_GETUP,
PED_STAGGER,
PED_DIVE_AWAY,
PED_STATES_NO_ST,
PED_ENTER_TRAIN,
PED_EXIT_TRAIN,
PED_ARREST_PLAYER,
PED_DRIVING,
PED_PASSENGER,
PED_TAXI_PASSENGER,
PED_OPEN_DOOR,
PED_DIE,
PED_DEAD,
PED_CARJACK,
PED_DRAG_FROM_CAR,
PED_ENTER_CAR,
PED_STEAL_CAR,
PED_EXIT_CAR,
PED_HANDS_UP,
PED_ARRESTED,
PED_DEPLOY_STINGER
};
enum eObjective {
OBJECTIVE_NONE,
OBJECTIVE_WAIT_ON_FOOT,
OBJECTIVE_WAIT_ON_FOOT_FOR_COP,
OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE,
OBJECTIVE_GUARD_SPOT,
OBJECTIVE_GUARD_AREA,
OBJECTIVE_WAIT_IN_CAR,
OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT,
OBJECTIVE_KILL_CHAR_ON_FOOT,
OBJECTIVE_KILL_CHAR_ANY_MEANS,
OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE,
OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS,
OBJECTIVE_GOTO_CHAR_ON_FOOT,
OBJECTIVE_GOTO_CHAR_ON_FOOT_WALKING,
OBJECTIVE_HASSLE_CHAR,
OBJECTIVE_FOLLOW_CHAR_IN_FORMATION,
OBJECTIVE_LEAVE_CAR,
OBJECTIVE_ENTER_CAR_AS_PASSENGER,
OBJECTIVE_ENTER_CAR_AS_DRIVER,
OBJECTIVE_FOLLOW_CAR_IN_CAR,
OBJECTIVE_FIRE_AT_OBJECT_FROM_VEHICLE,
OBJECTIVE_DESTROY_OBJECT,
OBJECTIVE_DESTROY_CAR,
OBJECTIVE_GOTO_AREA_ANY_MEANS,
OBJECTIVE_GOTO_AREA_ON_FOOT,
OBJECTIVE_RUN_TO_AREA,
OBJECTIVE_GOTO_AREA_IN_CAR,
OBJECTIVE_FOLLOW_CAR_ON_FOOT_WITH_OFFSET,
OBJECTIVE_GUARD_ATTACK,
OBJECTIVE_SET_LEADER,
OBJECTIVE_FOLLOW_ROUTE,
OBJECTIVE_SOLICIT_VEHICLE,
OBJECTIVE_HAIL_TAXI,
OBJECTIVE_CATCH_TRAIN,
OBJECTIVE_BUY_ICE_CREAM,
OBJECTIVE_STEAL_ANY_CAR,
OBJECTIVE_STEAL_ANY_MISSION_CAR,
OBJECTIVE_MUG_CHAR,
OBJECTIVE_LEAVE_CAR_AND_DIE,
OBJECTIVE_GOTO_SEAT_ON_FOOT,
OBJECTIVE_GOTO_ATM_ON_FOOT,
OBJECTIVE_FLEE_CAR,
OBJECTIVE_SUN_BATHE,
OBJECTIVE_GOTO_BUS_STOP_ON_FOOT,
OBJECTIVE_GOTO_PIZZA_ON_FOOT,
OBJECTIVE_GOTO_SHELTER_ON_FOOT,
OBJECTIVE_AIM_GUN_AT,
OBJECTIVE_WANDER,
OBJECTIVE_WAIT_ON_FOOT_AT_SHELTER,
OBJECTIVE_SPRINT_TO_AREA,
OBJECTIVE_KILL_CHAR_ON_BOAT,
OBJECTIVE_SOLICIT_FOOT,
OBJECTIVE_WAIT_ON_FOOT_AT_BUS_STOP,
OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT,
OBJECTIVE_WAIT_ON_FOOT_AT_ICE_CREAM_VAN,
OBJ_55,
OBJ_56,
OBJ_57,
OBJ_58,
OBJ_59
};
Таким образом, слой целей решает, что делать, а слой состояний решает, как это выполнить. Кстати, весь живой город состоит всего из 25 человек. В коде есть переменные MaxNumberOfPedsInUse и MaxNumberOfCarsInUse, определяющие, что в мире может быть не больше 25 активных пешеходов и 12 активных автомобилей. Во внутренних помещениях максимальное количество людей увеличивается до 40. Конкретные генерируемые модели зависят от зоны карты и времени суток. Например, днём на улицах можно встретить игроков в гольф, а ночью их количество снижается до нуля. То же самое и с пляжами: спасатели, роллеры и другие люди генерируются днём и пропадают с наступлением ночи. Члены банд чаще появляются по ночам, а в некоторых секторах будет больше полиции. Всё это находится в файле petgrp.dat, который можно открыть и изменить в любом текстовом редакторе. И это одна из причин простоты добавления модов в игру.
В коде даже есть нечто напоминающее индивидуальность пешеходов. У каждой сущности имеется небольшая переменная m_randomSeed, используемая для изменения скорости анимаций, поэтому каждый пешеход движется в собственном темпе.
Забавно, что в системе преступлений есть переменная m_nChaos, накапливающая очки правонарушений. Чем больше преступлений совершит игрок, тем выше становится уровень его розыска. Уровень хаоса определяется таблицей из семи вручную заданных констант, начинающихся с нуля. Кроме того, разные виды совершаемых игроком преступлений увеличивают этот уровень по-разному. Нападение на пешехода добавляет 5 очков хаоса. Нападение на полицейского — 45. И по какой-то причине уничтожение самолёта Cessna поднимает это значение на 500. Разумеется, чем больше очков хаоса, тем больше полицейских автомобилей и сотрудников полиции будет генерироваться.
ClearQdCrimes();
switch (level) {
case 0:
m_nChaos = 0;
break;
case 1:
m_nChaos = 70;
break;
case 2:
m_nChaos = 200;
break;
case 3:
m_nChaos = 570;
break;
case 4:
m_nChaos = 1220;
break;
case 5:
m_nChaos = 2420;
break;
case 6:
m_nChaos = 4820;
break;
default:
break;
}
Мне бы хотелось рассмотреть более технический аспект: обработку слабых ссылок без интеллектуальных указателей. Эта тема интересна, потому что Rockstar в 2002 году реализовала практически тот же паттерн работы с данными, что используют современные игровые ECS-движки. Как вы, возможно, знаете, одна из самых серьёзных проблем игр в открытом мире, где объекты постоянно уничтожаются, — это висячие указатели. Допустим, скрипт какой-то миссии использует указатель на грузовик; грузовик затем уничтожается и свободный слот занимает, допустим, такси. Как сделать так, чтобы этот скрипт случайно не начал управлять не тем транспортом? Rockstar придумала такое решение: счётчик поколения. У каждого слота есть один байт флага. Биты 0-6 хранят ID, а бит 7 сообщает, свободен ли слот. Инкремент этого счётчика происходит каждый раз, когда слот используется повторно; сам дескриптор — это значение int32. Ресолвинг дескриптора заново проверяет весь байт, и если слот использовался повторно или освобождался после создания дескриптора, он возвращает nil. Вот код, отвечающий за всю эту проверку.
return m_flags[handle>>8] == (handle & 0xFF) ?
(T*)&m_entries[handle >> 8] : nil;
Также стоит отметить, что поколение — это всего семь бит, то есть оно обнуляется. Когда слот повторно используют 128 раз, поколение возвращается к исходному значению. Это значит, дескриптор ресолвится в то, что занимает слот в данный момент. Однако, насколько я знаю, современные ECS-движки наподобие Unity используют для поколений 32 бита, чтобы подобное было невозможно. А в generational_arena Rust используется где-то 64 бита. Как бы то ни было, Rockstar задействовала семь, реализовав слабую ссылку в одном целом числе. Не потребовался подсчёт ссылок и атомарные операции, только одно простое сравнение, отлично подошедшее для этой игры.

Последнее, о чём я хочу поговорить — это система читов. Насколько помню, в предыдущей игре, GTA 3, все читы хранились в исполняемом файле в виде обычного текста в обратном направлении, поэтому они стали известны сразу после выпуска игры. В Vice City компания Rockstar для сокрытия читов в игре решила использовать простой шифр Цезаря. Если применить его и обратить строку VQVPanJ\I_, то она превратится в THUGSTOOLS.

Кроме того, разработчики оставили в коде сообщение для тех, кому удастся расшифровать это. Это чит-код CHEATSHAVEBEENCRACKED. Кстати, это реальный рабочий чит, если ввести его, то персонаж игрока превратится в Рикардо Диаса. В игре есть 58 жёстко прописанных чита, один из них — это PROGRAMMER. Если ввести его, Томми станет тощим и с тонкими руками.

На этом я и хочу закончить свою статью. Надеюсь, вы получили от неё такое же удовольствие, как и я.
Изучить полученный реверс-инжинирингом код можно в репозитории [7] GitHub.
Автор: interpres
Источник [8]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/33398
URLs in this post:
[1] память: http://www.braintools.ru/article/4140
[2] collision.cpp: https://github.com/mrxenginner/reVC/blob/b9eeb33efcd04a5b7a423921609baef11bf4719a/src/collision/Collision.cpp#L31-L43
[3] код: https://github.com/mrxenginner/reVC/blob/b9eeb33efcd04a5b7a423921609baef11bf4719a/src/peds/Ped.cpp#L7698-L7711
[4] ошибке: http://www.braintools.ru/article/4192
[5] интеллекта: http://www.braintools.ru/article/7605
[6] Мозг: http://www.braintools.ru/parts-of-the-brain
[7] репозитории: https://github.com/mrxenginner/reVC/tree/miami
[8] Источник: https://habr.com/ru/companies/ruvds/articles/1061048/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1061048
Нажмите здесь для печати.