![](https://habrastorage.org/webt/iu/ta/1e/iuta1emdycivb5cpxohmslchwug.jpeg)
Это не тот баг, который вы ищете
Бретт Дувилл, LucasArts
В начале 2002 года мы подготавливали Star Wars: Jedi Starfighter к передаче Sony для проверки. У нас оставался один пустячный баг TCR: при загрузке катсцен после миссий отключалась работа аналогового стика контроллера, что приводило и к отключению красного индикатора в центре контроллера. Этот баг проявился, когда мы обновились до версии библиотеки, которую требовала Sony, а программист, который писал и код загрузки видеороликов, и логику процессора ввода-вывода для контроллера, уволился из LucasArts за несколько месяцев до этого.
Надеясь быстро сузить поиски причины проблемы в коде, который я не понимал, я вставил семь сбросов экрана разных цветов в те фрагменты кода, которые, по моему мнению, были вероятным источником проблем. Я думал, что это, по крайней мере, сузит поиски до одного-двух фрагментов кода: цвет экрана покажет, какой код выполняется при отключении аналогового управления. Но когда я попытался воспроизвести баг, он исчез.
Существует старое выражение: если ты не понимаешь причины, то нельзя утверждать, что ты устранил баг. В данном случае у нас оставалось два-три дня до даты передачи игры Sony, и опоздание оказалось бы серьёзной проблемой. Поэтому я изменил все цвета сброса экрана на чёрный, пометил баг как устранённый и умыл руки. Мы выпустили игру вовремя, и никакие баги TCR больше не появлялись.
![](https://habrastorage.org/webt/ee/r3/at/eer3atr2spdxxc47qcrcgioo_7o.jpeg)
Эксплуатация эльфов
Джонатан Гарретт, Insomniac Games
Ratchet and Clank: Up Your Arsenal - это онлайн-игра, которую выпустили без возможности патчинга кода и данных. И это было очень зря.
При каждом запуске игра скачивает и отображает пользовательское соглашение (End User License Agreement). Это ASCII-строка, хранящаяся в статичном буфере. Буфер заполняется с сервера без проверки того, превышает ли размер строки объём буфера.
Мы воспользовались этим, чтобы скачивание EULA переполняло статичный буфер настолько, что перезаписывало нужную нам глобальную переменную. Так получилось, что эта переменная была обработчиком обратного вызова функции определённого сетевого пакета. После установки этого обработчика мы могли отправить сетевой пакет, чтобы вызвать переход к адресу в перезаписанной глобальной переменной. Адрес был указателем на код полезной нагрузки, который мы ранее сохранили в данные EULA.
Важные нам данные хранились между настоящим концом буфера EULA и перезаписанной глобальной переменной, поэтому первой задачей кода полезной нагрузки было восстановление этих повреждённых данных. После этого всё возвращалось к нормальной работе и можно было выполнять сам патчинг.
Сложность заключалась в том, что текст EULA копировался с помощью strcpy. А strcpy завершает строку, когда находит байт 0 (который обычно является концом строки). Наша строка содержала код, в котором часто присутствовали байты 0. Поэтому мы подвергали мутации скомпилированный код так, чтобы он не содержал нулевых байтов, и имел в себе тщательно проработанный фрагмент загрузочного ассемблерного кода для того, чтобы обратить мутацию вспять.
В конечном итоге хак выглядел так:
Отправляем EULA слишком большого размера
Переполняем буфер EULA различными данными и указателем на обработчик обратного вызова
Отправляем пакет для запуска обработчика
Игра переходит к загрузочному коду, на который указывает обработчик
Загрузочный код декодирует данные полезной нагрузки
Полезная нагрузка загружается и восстанавливает модифицированные данные
Выполняется патч
Выводы: добавляйте код патчинга при выпуске игры и не используйте strcpy без ограничений.
Крадущийся RSX, затаившиеся текстурные ресурсы
Джо Валенсуэла, Insomniac Games
Этот трюк пришлось проделать на PS3: у команды разработки движка Insomniac были текстуры, которые мы хотели выпустить вместе с движком и инструментами. Среди них были такие вещи, как текстуры шума и источники данных для полноэкранных эффектов фильтрации. По каким-то маловажным причинам мы не хотели распространять их как отдельные файлы ресурсов, поэтому преобразовали их в двоичные массивы и скомпилировали их в исполняемый файл. Однако у такого решения был один недостаток - мы хотели, чтобы они находились в другом фрагменте памяти (видимом процессору RSX), поэтому нужно было скопировать их, а место, занятое исходными данными, оказалось бы потраченным впустую.
В тулчейне PS3 была функция компоновки, позволявшая разместить отдельные сегменты в памяти RSX, но она требовала использования 1-мегабайтных страниц, а в нашем случае это бы привело к пустой трате 700 КБ. Вместо этого мы добавили в исполняемый файл новый раздел данных, создававший псевдоним bss (раздел "bss alias" или "balias"). В готовых сборках у нас было что-то около 3 МБ bss, поэтому оставалось более чем достаточно места для сокрытия текстурных ресурсов. Мы запускали код на этапе инициализации crt, чтобы инициализировать целевую память, скопировать ресурсы, а затем заново инициализировать bss со значением 0.
Можете не верить, но это сработало! Пришлось немного подкрутить код, чтобы скрытно использовать bss, и изменить тулчейн, но в целом всё оказалось довольно просто.
![](https://habrastorage.org/webt/sr/lb/ed/srlbedjisyhmgb5fsvonsj48m9c.jpeg)
Утрамбовываем картридж
Майкл Кэрр-Робб-Джон, Monolith Productions
В 1993 году я заканчивал работу над переносом игры Desert Strike с 16-битной Mega Drive / Genesis на её скромного младшего брата, 8-битную Master System. Игра превосходила нужный размер картриджа на 12 КБ, а переходить на картридж следующего размера нам запретили. Сегодня кажется, что 12 КБ - это невероятно мало, но в те времена проблема была серьёзной. В процессе разработки я планировал размер всех звуковых и графических ресурсов, и они укладывались в свои ограничения. Единственный аспект, с которым я не был так же строг - это код. В те времена игры писались на языке ассемблера (конкретно в этом случае на ассемблере Z80), поэтому у меня оставался только один вариант. Я потратил неделю на поиск избыточного кода и переписывание, чтобы он занимал меньше памяти (обычно ценой повышения нагрузки на процессор).
К тому времени, как я закончил, игра помещалась на картридж, оставляя свободными всего 98 байт! Игру записали на ROM, а потом несколько дней тестировали ребята из отдела QA, после чего отправили на сертификацию компании Sega. К сожалению, она не прошла сертификацию в первый раз, и требуемые исправления быстро заполнили эти 98 байт. Если не ошибаюсь, когда мы её выпустили, свободными оставалось всего 6 байт!
Распределитель Далтона
Джонатан Адамчевски, Insomniac Games
Ближе к концу одного проекта мы обнаружили, что после нескольких часов игрового процесса видеоролики не запускались в нужный момент. Из-за фрагментации внутри одной из созданных нами куч распределения памяти стало невозможно надёжным образом выделять большие блоки памяти, необходимые для воспроизведения полноэкранных видеороликов.
Нам нужно было найти способ обеспечить наличие необходимой памяти. Однако проблема была замечена на поздних стадиях проекта и мы не могли рисковать, реализуя дефрагментацию кучи, у нас не было достаточно свободной памяти, которую можно было бы выделить исключительно под воспроизведение видео, а отбирать пространство у других систем на этом этапе было нецелесообразно.
Мы понимали, что при воспроизведении видео многие другие системы простраивали, поэтому рассмотрели вариант "заимствования" памяти у них. Однако такие потенциальные источники памяти были или слишком маленькими, или тоже страдали от подобных проблем фрагментации. Было много свободного места для GPU, но по разным причинам мы не могли использовать его под буферы воспроизведения видео. Поэтому нам требовался другой источник пространства.
В процессе изучения другой проблемы мы выявили определённый паттерн распределений памяти внутри основной кучи, где хранились игровые ресурсы: при запуске игры многие ресурсы грузились с диска, после чего в памяти они никогда не изменялись. Некоторые из этих ресурсов были очень большими. Ни один из них не требовался при воспроизведении видео.
Это дало нам идею: что если мы скопируем содержимое одного из крупных файлов ресурсов куда-нибудь ещё в памяти? Для временного хранения ресурса идеально подойдёт память GPU, тогда мы сможем временно использовать пространство в куче ресурсов для воспроизведения видео, а после его завершения скопировать данные ресурса обратно из памяти GPU.
Так мы и поступили. Мы выбрали самый большой набор клипов анимации, используемый для одного из героев игры (по имени Далтон). [Прим. пер.: вероятно имеется в виду игра 2013 года Fuse для PlayStation 3 и Xbox 360] Система анимаций отключалась, клипы анимации копировались в пространство памяти GPU, и память передавалась системе воспроизведения видео. В конце видео данные анимаций копировались обратно на своё место, система анимаций перезапускалась, и игра продолжалась, как будто не произошло ничего ужасного. В результате реализация оказалась довольно простой, однако процесс растянули на несколько кадров, чтобы гарантировать надёжную синхронизацию всех задействованных в этом трюке систем на каждом его этапе.
С тех пор память для системы воспроизведения видео стали называть получаемой от "распределителя Далтона" в честь персонажа, чью память клипов анимации она отбирала.
(С тех пор я узнал, что в студии это является своеобразной традицией - как запихивание разных вещей в память GPU, так и ошибки с выделением достаточного количества места на ранних этапах проекта...)
Головняк с сертификацией
Майкл Кэрр-Робб-Джон, Monolith Productions
Любой, кто когда-нибудь писал игру для консоли, знает о проблемах сертификации. Чаще всего для сертификации достаточно здравого смысла и следования рекомендациям, но всегда есть одно-два "требования", кажущиеся ни чем иным, как способом доставить проблемы разработчикам. Одно из таких требований, с которыми нам пришлось справляться, заключается в том, что когда игрок решает запустить вашу игру, она должна отобразиться на экране не позже, чем через четыре секунды. Если исполняемый файл игры большой, то только на его загрузку может потребоваться две-три секунды, а вам ещё нужно загрузить целую кучу графики и звуков для отображения главного меню.
В моей игре от момента запуска до отображения первого экрана проходило 26 секунд, поэтому я уже начинал ощущать головную боль. Моей первой задачей стало изолирование всего того, что требуется для отображения меню, и откладывание загрузки глобальных данных на тот момент, когда они понадобятся на самом деле. Неожиданно эта оптимизация повлияла на игру сильнее, чем я ожидал: время загрузки снизилось на девять секунд. Реализовав ещё множество оптимизаций, я смог снизить его ещё на 10 секунд, но просто не мог заставить игру грузиться ещё быстрее. Когда я обсуждал эту проблему с другим инженером (лучший, по моему мнению, способ решения проблем), нас озарило.
У той консоли было ещё одно требование: перед переходом к системе меню игра должна отобразить ещё два экрана. Любопытно то, что пользователь не должен был иметь возможности пропускать эти экраны в течение заданного времени. Посмотрим - если мы загрузим только эти два экрана, а большинство данных меню будем загружать, пока игрок смотрит на эти экраны... то вуаля - у нас появляется 5,5 секунды на загрузку и отображение меню.
К сожалению, этого всё равно было недостаточно для соответствия перечню требований, поэтому мы попросили немного отступить от правил, и нам дали на это разрешение (вероятно, потому, что мы уже и так приблизились к требуемому).
![](https://habrastorage.org/webt/gw/9n/sk/gw9nskhk-fpnr6bihvbmvqdx3i8.jpeg)
Рисуем звуки
Эдвард Дуглас, Flying Helmet Games
Я занимался кинематографическими вставками для серии гоночных игр с очень долгой историей. Наши сцены представляли собой смесь из простой демонстрации стартовых сеток и более сложных активных роликов. В процессе работы над сиквелами наши амбиции и требования к сценам постоянно росли, однако технологии за ними не поспевали.
Дело в том, что машины анимировались "каскадёром" из отдела QA, создававшим базовое движение. Затем сцены модифицировались в 3D-редакторе, чтобы настроить время и место действия, после чего снова экспортировались в наш внутриигровой инструмент, в котором при воспроизведении ролика симулировалась вся физика и поведение движка. В сцену записывался большой объём геймплейных данных, в том числе информация о нажатии на газ и тормоз контроллера. Всё это записывалось в метаданные в файле 3D-формата, а потом воспроизводилось в игре. Идея заключалась в том, что такая схема будет управлять и аудиосистемой автомобилей.
Проблема возникла несколько сиквелов спустя, когда наши сцены стали очень сложными и сочетали в себе созданную вручную анимацию автомобилей с записанными данными захвата. Старые трюки с использованием метаданных движка для управления воспроизведением простых звуковых сэмплов рёва автомобилей больше не работали - таких данных там просто не было! Звуковой отдел не мог выполнить постобработку звука как в фильме, потому что в любой сцене могла находиться любая машина, в зависимости от выбора и модификаций игрока, поэтому звук обязан был оставаться процедурным. К тому времени наши игры уже получили множество наград за звук, и в особенности за звуки двигателей машин, поэтому мы были полны решимости заставить систему работать.
Ближе к бета-версии шансы начали казаться призрачными, однако благодаря сочетанию гениальности и безумия наших отделов кинематографии, звука и ИИ решение было найдено. Метаданные газа и тормоза были представлены значением масштаба куба в 3D-сцене в формате float. При "отрисовке" кривых в редакторе ключевых кадров, например, в 3DS Max, художник мог просто нарисовать нужные звуки автомобильных двигателей. Несколько сотрудников звукового отдела начали усиленно изучать 3DS Max, и воспользовавшись своими предположениями о том, как должны выглядеть паттерны рёва двигателей, они нарисовали их в анимации, экспортировали все сцены и соединили всё воедино. Звук получился отличным, но после того, как пришлось в ускоренном темпе создавать этот хак, мы решили, что если хотим продолжать работу над следующим сиквелом на той же технологической базе, нам потребуется нечто понадёжнее.
... Или, по крайней мере, так думал я. После этой игры я уволился из студии и несколько лет спустя встретился с звуковиком, который пришёл в команду после моего ухода. Я почти сразу же понял, что они так и не усовершенствовали технологии, и он работал "художником рёва движков" для нового сиквела.
...И одну на удачу
Ричард Морвуд
У меня был список фоновых текстур и я написал код отображения каждой при скроллинге по экрану. Одно из фоновых изображений пропускалось, и я не мог понять, почему, даже потратив кучу времени на отладку. До дедлайна оставалось пять дней, поэтому я просто вставил в список фонов ещё одну ссылку на текстуру. Та-да! Больше никакого "пропускаемого" фона.
Хаки HR
Бен Бёрбэнк
Когда я работал на одну очень крупную компанию, один из сотрудников выяснил, что лучший способ продвижения по карьерной лестнице - это писать отрицательные оценки эффективности на как можно большее количество коллег. Это привело к тому, что он получил более высокую должность, что в свою очередь привело к повышению премий и получению доли акций компании. Он сказал мне, что со временем это становится труднее, потому что нужно писать отзывы только на людей с разными руководителями, чтобы никто не уличил твою хитрость. Мой трюк побега из этого цикла заключался в том, чтобы уволиться и пойти работать в менее крупную, но более крутую компанию.
![](https://habrastorage.org/webt/1b/sl/1z/1bsl1zelpl-gl49stjd9klvlfri.jpeg)
Отличный сэйв
Крис Пруэтт, Robot Invader
[Примечание редактора: строго говоря, это не грязный трюк в разработке видеоигр, но он показался нам отличным способом применения рабочих навыков для решения проблем реального мира. К тому же это очень милая история.]
Моя жена мало играет в видеоигры, но её с детства зацепила серия игр Dragon Quest. Несколько лет назад она начала играть в Dragon Quest VII на моей старенькой PlayStation. Потратив на неё примерно 80 часов (то есть, насколько я понимаю, это примерно три четверти от прохождения главного квеста), она, к своему ужасу, обнаружила, что файл сохранения оказался повреждённым. Он отображался в меню продолжения игры, но был неактивным и его нельзя было выбрать. Жена была расстроена. Она была в гневе. Она поклялась, что больше никогда не будет играть в игры.
Я нашёл на eBay подержанный DexDrive (устройство, позволяющее считывать и записывать карты памяти PS1 через PC) за 15 долларов. Не стал говорить жене, что попытаюсь исправить её сэйв - не хотел давать ей надежду, да и не думал, что это будет возможно. Вероятно, все данные невозвратимо повреждены и случай безнадёжный. С другой стороны, я подумал, что попробовать стоит.
Благодаря DexDrive я смог сдампить повреждённый сэйв на PC и изучить его в шестнадцатеричном редакторе. В результате я решил распечатать его и пометить шестнадцатеричные значения маркером; хотя файлы сохранения PS1 представляли собой блоки по 8 КБ, печать 8 КБ в виде 16 столбцов шестнадцатеричных данных заняла множество страниц. Работая с неофициальной спецификацией, написанной автором эмулятора PS1, я нашёл основные фрагменты данных: заголовок, изображение значка и, наконец, сами данные сохранения. К сожалению, декодирование сырых данных игрового состояния оказалось сложным процессом; спустя несколько дней я решил, что это займёт намного больше времени, чем я планировал.
![](https://habrastorage.org/webt/wv/ox/ir/wvoxirw3btjfbcw0kphjl-mvqig.jpeg)
Поэтому вместо этого я сосредоточился на заголовке данных. Благодаря расположению значка (который находится на постоянном смещении от начала файла и пиксельные данные которого легко обнаружить в шестнадцатеричном виде), я мог чётко понять, где начинается и где заканчивается заголовок. Если меню продолжения игры может определить, что сэйв повреждён, то, вероятно, дело только в поломанном заголовке. Я проверил эту теорию, скопировав данные заголовка из какого-то другого сэйва, который скачал из Интернета, и вставив его вместо раздела заголовка в сломанном файле сохранения жены. Затем я сохранил данные обратно на карту памяти и загрузил их.
Чудесным образом это сработало. В меню продолжения игры начала отображаться статистика другого сохранения, но после загрузки игра жены оказывалась полностью восстановленной. ОТ покупки DexDrive до успешного патчинга сохранёнки весь процесс занят примерно три недели. Однажды вечером я запустил игру и показал жене меню продолжения игры и сэйв со странным названием. Она загрузила его и поразилась: её прогресс, персонажи, статистика и предметы оказались в порядке. Жена была в восторге, но прежде чем мы успели это обсудить, она уже шла проходить следующее подземелье.