Главная » Хабрахабр » [Перевод] Устраняем баг в игре 2000 года на Shockwave

[Перевод] Устраняем баг в игре 2000 года на Shockwave

image

История замены единственного байта

Cartoon Cartoon Summer Resort

Это было лето 2000 года. Мне исполнилось шесть, я только что закончил первый класс, и начались каникулы. Это означало, что я мог долго играть на улице, смотреть мультфильмы и включать компьютер отца с Windows 98, чтобы искать игры в совершенно новом, неизведанном краю под названием «Интернет». Одним из моих любимых был веб-сайт Cartoon Network. На нём я нашёл множество увлекательных flash-игр на основе телевизионных мультфильмов. Тем летом они выпустили серию игр под названием «Cartoon Cartoon Summer Resort».

Геймплей первого эпизода Cartoon Cartoon Summer Resort

Игрок управлял мультяшным персонажем, находящимся в отпуске на курорте с другими персонажами мультфильмов. Эта игра была двухмерной RPG/адвенчурой с видом сверху сбоку, состоявшей из четырёх эпизодов. Игрок должен был решить её, взаимодействуя с персонажами и находя предметы или обмениваясь ими.
В каждом эпизоде на курорте появлялась проблема, которую нужно было решить.

Возвращаемся на 18 лет вперёд

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

Вероятно, я оказался первым человеком, нашедшим оправданную причину использовать Internet Explorer в 2018 году. Кроме того, ни один современный браузер не запустил бы древний и уязвимый плеер Shockwave… за исключением Internet Explorer.

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

Тот самый баг

Спустя какое-то время я обнаружил в игре баг:

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

При попытке арендовать другую лодку появляется сообщение «Сегодня больше нет арендуемых лодок!». Как видно из анимации, в игре можно арендовать лодку, чтобы передвигаться по воде. Если уплыть на север и пройтись по правому краю острова, но откроется тот же текст, который должен появляться у лодочного причала.

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

Мне казалось, что я раскопал древнюю гробницу и нашёл в ней нетронутую головоломку, которая могла пропасть, так никем и не решённая. Уже обладая всеми своими знаниями о программирования и глядя на игру, я воспринимал этот баг странно притягательным. Интересно, что именно такие возможности давал мне процесс самой игры в детстве. Для меня этот баг был возможностью поучиться и открыть что-то новое. Есть что-то почти поэтическое в том, как нечто совершенно ненамеренно может поставить новые задачи, если взглянуть на него под другим углом.

Деконструкция игры

Для исправления бага мне нужно было разобраться во внутренней работе игры. Изучив вопрос, я узнал, что игра была создана на Shockwave в приложении Director. При работе с Director проекты сохраняются как файл .dir (Director). Этот файл похож на файл PSD для Photoshop. Аналогично тому, как файл PSD содержит неразрушающую информацию о слоях и тексте, проект .dir сохраняет все ресурсы, сырой исходный код и другую информацию, помогающую в процессе разработки. Для анимирования сцен в Director использовался проприетарный скриптовый язык Lingo.

Однако игра опубликована как файл .dcr. Если бы игра была сохранена в файле .dir, я мог просто открыть его в Director и легко изучить, как работает игра. То есть весь исходный код скомпилирован в байт-код, выполняемый на платформе Shockwave. Файл .dcr — это скомпилированная версия проекта Director. Изображение PNG (в нашем случае файл DCR) меньше по размеру, но не содержит информации о слоях и редактирований, и предназначен только для распространения. Этот процесс схож с тем, как файл PSD упрощается и становится изображением PNG.

Даже если бы я разобрался, как найти низкоуровневый байт-код, похоже, никто не выполнял реверс-инжиниринг байт-кода Lingo и не документировал принцип работы платформы Shockwave. Это означало, что у меня на руках двоичный объект размером 500 КБ без документации о его структуре. Шансы разобраться в работе игры выглядели довольно мрачно. Вся эта информация проприетарна, ею владеет Adobe, которая не имеет никаких причин публиковать её.

Декомпрессия

Чувствуя себя побеждённым оттого, что скорее всего не смогу устранить этот баг, я решил выяснить можно ли каким-то образом извлечь из игры ресурсы. Я посчитал, что есть вероятность найти раздел сжатых данных или чего-то подобного. Поискав, я нашёл пару программ под названием offzip и packzip. Эти инструменты могут искать данные zlib в произвольных двоичных файлах, показывать смещения и извлекать их в отдельные файлы.

249 штук, если говорить точнее. Я запустил offzip с файлом DCR и к моему удивлению, он действительно нашёл архивы!

$ ./offzip.exe -a 1.dcr

| zip -> unzip size / offset | spaces before | info |
+------------+-----+----------------------------+----------------------+
0x00000026 . - open input file: 1.dcr
- zip data to check: 32 bytes
- zip windowBits: 15
- seek offset: 0x00000000 (0)
+------------+-----+----------------------------+----------------------+
| hex_offset | ... 3932 -> 9169 / 0x0000102f _ 9 8:7:26:0:1:c1079d84
...
0x00080490 . 164 -> 214 / 0x000000ca _ 38 8:7:26:0:1:7b6349f6
0x000000d3 .. 209 -> 366 / 0x0008066a _ 0 8:7:26:0:1:7da3ba08 265 -> 472 / 0x00080599 _ 0 8:7:26:0:1:04d6b43f
0x00080599 .

- 249 valid compressed streams found
- 0x0004040d -> 0x001565c8 bytes covering the 50% of the file

Там было 206 файлов .dat, 38 файлов .fff, 4 файла .atn и единственный файл .ini. Я извлёк все эти файлы в папку и начал изучать результаты.

Открытия

Я начал с файла INI, но от него не оказалось никакой пользы. Это была простая таблица преобразования шрифтов из Directory 7.0 между Windows и Mac. Затем я перешёл к файлам DAT. БОльшая их часть имела размер 1КБ, поэтому я начал с огромного, имевшего размер 144КБ. Я открыл его в hex-редакторе и изучил. В основном это были неразборчивые данные. Однако со временем я нашёл в них несколько слов, которые казались идентификаторами Lingo.

Анализ больших файлов DAT дал мне кое-какие подсказки, в них сохранилось несколько интересных сообщений. Я выяснил, что для графики скорее всего использовался Photoshop 3.0. Также я узнал, что в игре был инструмент редактирования внутренних карт под названием Map-O-Matic v1. Хотелось бы мне увидеть, как он выглядел и создавался.

Имя ведущего разработчика тоже было в файле, но его я называть не буду. Также я нашёл название компании, разрабатывавшей игру: Funny Garbage. Все эти крохи информации конечно были интересными, но особо ничем не помогли. Было здорово открыть для себя автора игры, которую я упорно пытался исправить спустя почти 20 лет, и наконец увидеть лицо человека, ставшего вероятной причиной этой агонии.

Прорыв

Затем я начал изучать в hex-редакторе файлы .fff. К моему большому удивлению, все данные в этих файлах были читаемыми и выглядели как данные карт:

Я вручную извлёк часть этих данных и подчистил их в текстовом редакторе. То, что у меня получилось, очень походило на массив JSON:

104",
#type: #FLOR,
#location: [16, 9],
#width: 64,
#WSHIFT: 0,
#height: 32,
#HSHIFT: 0,
#data: [
#item: [
#name: "",
#type: #WALL,
#visi: [
#visiObj: "",
#visiAct: "",
#inviObj: "",
#inviAct: ""
],
#COND: [[#hasObj: "", #hasAct: "", #giveObj: "sunscreen", #giveAct: "gotscreen"], #none, #none, #none]],
#move: [#U: 0, #d: 0, #L: 0, #R: 0, #COND: 1, #TIMEA: 0, #TIMEB: 0],
#message: [
[#text: "You bought the sun screen.", #plrObj: "", #plrAct: ""],
[#text: "No more sunscreen today!", #plrObj: "", #plrAct: "gotscreen"]]]] [
#member: "block.

Это было очень важно, ведь мне удалось многое узнать о том, как работала игра.

  1. Игра ожидает, что данные карт, текста и событий находятся в похожих на JSON объектах Lingo Objects
  2. Каждая запись #member — это отрисовываемый тайл, блок или персонаж.
  3. Смещения координат и размеров #member можно редактировать.

Зная, что диалоги игры сохранялись в эти файлы, я написал короткую строку для экспорта в файл только одного диалога:

grep -a -o '#text: "[^"]*' Uncompressed/*.fff | awk '' > Dialogue.txt

С помощью этого файла я быстро могу найти ошибочный текст и посмотреть, в каком файле он находился:

Ошибочный текст находится или в 0004eda0.fff, или в 0004f396.fff. В нашем случае текст бага оказался в первом файле. Мы знаем это, потому что сразу после него находится сообщение, которое мы получаем при взаимодействии с Огом, который является персонажем на той же карте, что и тайл с багом.

Исправление бага

Теперь я мог открыть 0004eda0.fff и найти строку про лодку в hex-редакторе. Найдя её, я смог обнаружить связанный с ней объект #member. После чего изменить его свойства и сохранить файл. Затем я снова сжал его и пропатчил в исходный файл игры DCR с помощью packzip.

$ ./packzip -o 0x0004EDA0 Uncompressed/0004eda0.fff test.dcr

11 на block. Когда я меняю тип блока с block. 13 и патчу игру, то могу чётко увидеть контур ошибочного тайла:

Изменив ID тайла, можно увидеть границы проблемной области

Всё, что мне нужно было сделать — изменить для этого ошибочного тайла идентификатор #message на #fessage: Само исправление бага до смешного просто.

Теперь, если мы пропатчим изменения и вернёмся в эту область, то сообщения больше не появятся!

Устранили баг в игре, в буквальном смысле изменив 1 байт

Почему так можно исправить ошибку?

Могу только предположить, что движок игры, вероятно, при движении игрока проверят тайл, на котором он стоит. Если выполняется какое-то условие, то он отображает соответствующее этому тайлу сообщение из массива #message. После изменения #message на #fessage ссылки на массив #message, которую ищет код, больше не находится. Он считает это пустым (или неопределённым?) объектом и ничего не отображает.

Рассмотрим пример на JS:

function foo(bar) { if (bar["message"] !== undefined) { // display the message }
}

Допустим, мы не можем изменить функцию foo(), но нам нужно изменить результат. У нас есть доступ к передаваемым ей данным. Можно переименовать свойство message передаваемого объекта и функция подумает, что его не было.

Как появился этот баг?

Предположу, что из-за лени. Разработчики использовали для создания этой области редактор карт. Скорее всего, они копипастили уже готовые карты в новые области, а потом изменяли их. Это проще, чем создавать всё с нуля. Но так как данные событий и текста перемешаны, они скорее всего недоглядели в некоторых местах и забыли удалить или заменить их. Это выглядит логичнее всего, потому что ошибочный диалог часто брался с соседней карты, где использовался правильно.

Зачем тратить столько труда на нечто столь незначительное?

Не знаю. Возможно, из-за ностальгии?


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

9 кругов автоматизации склада Lamoda

Наш склад размером с две Красные площади и высотой в 5 этажей работает круглый год и никогда не спит — 24/7 364 дня в году (единственный выходной — 1 января). У нас хранится и обслуживается более 8 000 000 товаров, ...

[Перевод] Каскадные SFU: улучшаем масштабируемость и качество медиа в WebRTC-приложениях

В развертывании медиасерверов для WebRTC есть две сложности: масштабирование, т.е. выход за рамки использования одного сервера и оптимизация задержек для всех пользователей конференции. В то время как простой шардинг в духе «отправить всех юзеров конференции X на сервер Y» легко ...