В новом релизе Pizza Legacy v0.1.0 появилась возможность выиграть игру, как в оригинале.
Когда я играл в Pizza Tycoon (1994 год) в детстве, то не особо задавался вопросом, как победить. Мне просто нравилось открывать рестораны и придумывать пиццы, а также приторговывать оружием для финансирования роста моей империи пиццы. Но выигрыш? Я никогда до него не доходил и, вероятно, даже не задумывался о нём.
Когда я принялся за проект воссоздания этой игры, то начал изучать файлы данных Pizza Tycoon, обнаружив графический файл ENDE.VGA и текстовый файл ENDE.E, сообщающий нам, что происходит в случае выигрыша:

Первая строка ENDE.E:
Вы достигли немыслимого! Вы — король всего западного рынка фастфуда! Вы — тот самый МАГНАТ ПИЦЦЫ.
Это даёт нам понять, что игру как-то можно выиграть, но не как именно.
Я просто забыл об этом вопросе, ведь мне предстояло реализовать столь многое до того, прежде чем задумываться о победе игрока, но однажды наткнулся на пост «Я "выиграл" в Pizza Tycoon? (Как это произошло?)». После нахождения этого поста мне стало любопытно, но не настолько любопытно, чтобы заниматься расследованием, поэтому я поискал условия конца игры в ассемблерном коде и попросил Claude проанализировать их. Он сказал следующее:
end_of_week_processing раз в неделю проверяет, есть ли у текущего игрока >= доля рынка 5% во ВСЕХ 10 городах.
Это соответствует моим представлениям: логично с точки зрения геймплея и текста, найденного в ENDE.E. Однако это не соответствовало написанному пользователем Reddit: он утверждал, что у него были рестораны только в Париже и Берлине; возможно, он опустил какие-то подробности или у него просто был повреждённый файл сохранения? Я написал ему, но не получил ответа.
Что-то не сходится
Позже, во время плейтестинга моего движка я смотрел видео Pizza Tycoon - Worldwide Pizza King - Episode Ten, в котором пользователь YouTube AppleSauce играл в Париже и внезапно началась заставка, сообщающая о победе.
Так как весь процесс, приведший его к победе, был записан, я мог чётко увидеть, что на самом деле у него не было ресторанов во всех городах, а значит, что-то явно было не так. Теперь я как раз приближался к необходимости реализации сценария победы в Pizza Legacy, поэтому вернулся к ассемблерному коду, чтобы глубже разобраться в происходящем.
Соответствующий код находится в функции, которую я назвал end_of_week_processing (последние 15 лет я постепенно документировал ассемблерный код исполняемого файла игры PT.EXE; имена выбраны мной или сгенерированы IDA). Эта функция вызывается один раз за игровую неделю для каждого живого игрока: она выполняет такие действия, как проверка на банкротство, еженедельные трансферы прибыли городов, в которых игрока ещё нет, и проверку условия победы.

Проверка условия победы выполняется так:
loc_546CD: xor bl, bl ; индекс города = 0 (Париж) jmp short loc_546F3 ; начало цикла loc_546D1: movzx eax, bh ; bh = индекс текущего игрока imul eax, 0x0A ; игрок * 10 (10 городов) movzx edx, bl ; индекс города cmp byte ptr marketshare_percentages[edx+eax], 5 jb short loc_546F8 ; доля рынка < 5%? выходим из цикла push 0x8C ; константа GAME_OVER_YOU_WON call schedule_fullscreen_status_update ; ВЫ ПОБЕДИЛИ add esp, 4 inc bl loc_546F3: cmp bl, 0x0A ; выполнение цикла, пока город < 10 jb short loc_546D1 ; переход к реализации цикла loc_546F8: ; код после цикла movzx edx, bh ...
На C это бы выглядело примерно так:
int number_of_cities = 10; for (int city_id = 0; city_id < number_of_cities; city_id++) { if (marketshare_percentages[player_id][city_id] < 5) { break; } schedule_fullscreen_status_update(GAME_OVER_YOU_WON); }
Проблема в том, что schedule_fullscreen_status_update(GAME_OVER_YOU_WON) исполняется внутри цикла, а не после успешного завершения. На самом деле условие не «доля рынка не менее 5% во всех городах». Достаточно получить долю рынка 5% в городе 0!
Тестируем самостоятельно
Эта новая информация объясняет и пост на Reddit, и видео на YouTube. Чтобы убедиться, я протестировал это сам. В моей собственной сохранёнке с долей рынка более 5% в шести городах ничего не происходило, пока я не открыл пару ресторанов в Париже, после чего я впервые в своей жизни победил в Pizza Tycoon.
Мне стало любопытно, есть ли такое же поведение в оригинальной Pizza Connection (на немецком языке).
Я бы мог попробовать проверить это и в немецком исполняемом файле, но я документировал ассемблерный код только англоязычного PT.EXE; немецкий PC.EXE собирали сильно другим компилятором и он имел совершенно другую структуру, так что поиск эквивалентного условия победы потребовал бы существенного труда по реверс-инжинирингу. Поэтому я решил просто добиться доли рынка 5% в Париже, играя в игру.
Чтобы быстро получить нужную долю рынка (я ведь занимался расследованием, а не хотел потратить вечер на игровой процесс), мне требовались деньги. Быстрее всего добиться этого торговлей оружием: покупаем по дешёвке, продаём задорого, надеемся не попасться.
Вместо того, чтобы трудиться самому, я немного сжульничал: купил бомбу, сохранил игру, потом купил ещё одну бомбу, снова сохранился, и повторил это ещё раз. Потом я написал на Python небольшой скрипт, который сравнивает три файла сохранений и выводит рядом все различающиеся значения. Потом я посмотрел все поля, которые изменились ровно на единицу в каждой последующей сохранёнке (я подумал, что искать один байт с количеством бомб будет проще, чем double с деньгами).
По смещению 0x2E0 в файле сохранения я нашёл значения 158, 159 и 160. Открыв файл сохранения в шестнадцатеричном редакторе, изменив значение и загрузив сохранение в игре, я убедился, что именно там хранится количество бомб со смещением 157: если байт равен 157, то у игрока есть 0 бомб, значение 158 соответствует 1 бомбе. Так как значение хранится в одном байте, значения ниже 157 приводят к циклическому переполнению, поэтому значение 156 даёт нам 255 бомб.
Я записал значение 141, чтобы добавить себе 240 бомб (потому что быстрее всего продавать их наборами по 20 штук), продал их все и без особой траты времени получил капитал, который нужен, чтобы начать расширяться.
Вскоре я получил больше 5% рынка в Париже, но игра не закончилась, а значит, баг был присущ только английской версии Pizza Tycoon.

Это сделано специально или случайно?
Я предполагаю, что исходная логика заключалась в том, чтобы игрок одновременно удерживал долю рынка не менее 5% во всех десяти городах. В этом случае структура цикла, выполняющая выход при неудачной проверке, имеет смысл; если бы разработчикам нужен был только первый город, они бы могли проверить только индекс 0, а не писать цикл, который никогда не доходит дальше первого элемента.
Так почему же игра говорит, что мы выиграли, когда добились нужного только в одном Париже? Англоязычная Pizza Tycoon не совсем аналогична немецкому оригиналу. Терри Грир, работавший в то время художником в MicroProse, вспоминает, что Pizza Connection была немецкой игрой, которую MicroProse хотела переименовать и продавать под своим брендом Tycoon. Где-то в процессе этого и изменилось условие победы: в немецком оригинале бага нет, он присутствует только в англоязычном релизе, которым занималась MicroProse.
Думаю, что кто-то хотел облегчить задачу и позволить выиграть, если пользователь добьётся 5% рынка в любом городе, но из-за этого в коде появился баг. В результате это стал конкретно Париж, просто потому что это город 0, первый, который проверяется в цикле. То, что это оказался Париж, кажется случайностью: MicroProse локализовала игру для англоязычного рынка, даже вставив в неё три города США (Балтимор, Нью-Йорк и Чикаго, заменившие Москву, Цюрих и Афины). Если бы разработчики хотели выбрать победный город вручную, то скорее всего это был бы один из американских городов, а не столица Франции.
Зачем вообще упрощать победу? Возможно, это было сделано из коммерческих соображений. Pizza Tycoon была не отдельным релизом MicroProse, а частью Tycoon Series, наряду с Transport Tycoon Криса Сойера и более ранней Railroad Tycoon Сида Мейера; все они продавались под логотипом MicroProse примерно в одно и то же время. В рекламе на всю страницу Computer Gaming World (выпуск 126 за январь 1995 года) все три коробки объединены надписью «Теперь твой ход» и позиционируются как игры для PC, позволяющие покупать, строить и управлять собственной империей со слоганом MicroProse «Anything is possible».

В рамках локализации MicroProse изменила и текст в конце; на немецком игра оканчивается саркастическими титрами, в которых команда разработчиков благодарит, в том числе, разработчика компилятора за встраивание нужных багов в новые версии компилятора ровно в нужный момент, чтобы привести разработчиков в отчаяние. В конце титры предлагают начать всё сначала, ведь игра хороша.
В англоязычной версии от всего этого избавились: после коронации единственного и неповторимого МАГНАТА ПИЦЦЫ игра спрашивает, что пользователь собирается делать дальше и сама отвечает на этот вопрос:

Наверно, причина упрощения — всё вышеперечисленное. Игру рекламировали для другой, более казуальной аудитории, поэтому её нужно было пройти проще, а локализация текста в конце позволила добавить рекламу похожего продукта. Как бы то ни было, это привело к противоположным результатам, и Париж случайно стал единственным способом пройти игру.
Как это отражено в моей Pizza Legacy
Спустя тридцать лет игроки в Pizza Tycoon наконец-то могут выиграть так, как это задумывали разработчики: став настоящим магнатом, а не просто доминируя на французском рынке.
При включении опции устранения багов в Pizza Legacy игроку придётся получить долю больше 5% рынка во всех городах, а при отключенной опции сохраняется исходное поведение: для победы нужно достичь 5% рынка только в одном Париже. Думаю, в следующем релизе я добавлю переключатель победы между всеми городами и любым городом.
В Pizza Legacy уже есть довольно много модернизаций и исправлений; полный список можно найти в файле MODERNIZATIONS.md репозитория.
Недавно выпущенная Pizza Legacy v0.1.0 — это первый релиз, который наконец-то позволяет вам выиграть. Подробности о том, что было добавлено в этом релизе, можно прочитать в посте с объявлением релиза.
Zifix
Вроде нет, Париж стал читом, позволяющим пройти её быстрее, но захватив все города, как планировали разработчики изначально, тоже можно было победить.