Помните, как пираты испортили игрушку «Snake Rattle’ n Roll»? Главной проблемой модифицированного кода тогда была неочистка регистра при выходе из функции, которая должна была неявно это делать. Оказывается, пираты в свое время так и не извлекли урок из этого. Сегодня у нас на рассмотрении чуть более поздняя переделка одной известной игры, а именно:
Это всем известная версия игры «Ninja Gaiden» / «Ninja Ryukenden» для европейского рынка. В пиратском варианте в очередной раз была сделана попытка сконвертировать игру для маппера памяти ММС1 на новый маппер — ММС3. Это не всегда достаточно просто, но в целом не должно вызывать сильных затруднений. Хотя… Пиратская версия игры после подобной модификации потеряла… БОНУСЫ! А именно, висящие по стенам лампы, в которых можно найти в том числе новое оружие. В данной игре их просто нет. То есть нельзя не только пополнить жизни или число зарядов специального оружия, нельзя даже взять это специальное оружие!
Естественно, первое подозрение падает на ошибки при модификации кода под новый маппер. Процедуры смены банка разбросаны по разным частям игры, также дополнительно смена банков происходит непосредственно в отдельных частях кода, без вызова подпрограмм — так быстрее и проще. Помня о прошлых ошибках, сразу пойдем посмотрим одну из функций смены банка в пиратской версии:
$C02A 0A ASL A $C02B 48 PHA $C02C A9 06 LDA #6 $C02E 8D 00 80 STA $8000 $C031 68 PLA $C032 8D 01 80 STA $8001 $C035 09 01 ORA #1 $C037 48 PHA $C038 A9 07 LDA #7 $C03A 8D 00 80 STA $8000 $C03D 68 PLA $C03E 8D 01 80 STA $8001 $C041 A9 00 LDA #0 $C043 60 RTS
Невероятно, она чистит явно регистр А при выходе! Значит извлекают-таки урок пираты из прошлых ошибок! Значит проблема где-то еще. Смотрим, откуда вызывается данная функция:
$D8D5 A2 00 LDX #0 $D8D7 8A TXA $D8D8 20 2A C0 JSR $C02A $D8DB EA NOP $D8DC EA NOP $D8DD EA NOP $D8DE EA NOP $D8DF EA NOP $D8E0 EA NOP $D8E1 EA NOP $D8E2 EA NOP $D8E3 EA NOP $D8E4 EA NOP $D8E5 EA NOP $D8E6 EA NOP $D8E7 EA NOP
Данный код как раз выбирает банк с информацией о том, где и когда должны отображаться лампы с бонусами на уровне. Вроде бы ничего необычного, если сравнить с оригинальным:
$D8D5 A2 00 LDX #0 $D8D7 8E FF FF STX $FFFF $D8DA E8 INX $D8DB 8E FF FF STX $FFFF $D8DE CA DEX $D8DF 8E FF FF STX $FFFF $D8E2 8E FF FF STX $FFFF $D8E5 8E FF FF STX $FFFF
Но ПОСТОЙТЕ! Маппер ММС1 имеет 5-битный регистр для программного ПЗУ, а записывается этот регистр последовательно по одному биту, так что надо записать в регистр пять раз подряд биты требуемого числа. В коде выше это как раз и происходит. Что мы видим в этом коде. Первый записанный бит — это 0. Потом опкод INX увеличивает содержимое регистра Х на 1 и таким образом второй записанный в регистр бит — это 1! Далее идет DEX, таким образом три следующих бита снова будут 0. Число, записанное в регистр и номер банка в двоичной форме: 00010, то есть в десятичном виде — 2! А почему тогда пиратский код передает в функцию смены банка 0? Потому что кто-то не увидел разницы между универсальной функцией смены банка, в которую передается одно число и биты его сами сдвигаются при записи, и записью напрямую требуемых битов, без сдвигов и прочего!
Чтобы исправить игру, достаточно заменить «LDX #0» на «LDX #2»! И все. Но останется ли что-то интересное тогда в игре для собирателей пиратки? 😉
4 Comments
познавательно, че))
Давно же у вас не было новых записей в блоге. Пишите чаще 🙂
Очень интересно читать о внутренностях старых игр. Спасибо.
Да, согласен, это очень познавательно! Единственное, хотелось бы где-нить скачать енти «битые» пиратские поделки…
Люди а где взять ром этой похаченой пиратки? кто знает пожалуйста киньте ссылку заранее благодарю