Раздел 1: Последовательные наборы кнопок. В этой статье я покажу как нужно взламывать и искать комбинации клавиш на Нинтендо. Начнем с того, что как происходит опрос клавиш джойстика. Первый джойстик сидит на адресе 4016, а второй на 4017. В большинстве случаев процесс опроса выглядит следующим обраом, пример из игры Ninja Turtles 3: f92a: ad 16 40 LDA $4016 //чтение с порта джойстика f92d: 85 0c STA $0C f92f: 4A LSR f930: 05 0C ORA $0C f932: 4A LSR f933: 36 08 ROL $08,X @ $0008 //помещение состояние джойстика в регистр 08 Состояние джойстика хранится в виде закодированных значений байт и их комбинаций. Чаще всего они кодируются двумя видами кодировок. Самая распространненая выглядит так: Вправо 01 Влево 02 Вниз 04 Вверх 08 А 80 В 40 START 10 SELECT 20 и их комбинации. Например START+SELECT выглядит как 20 И 10 = 30. То есть складываются логическим И. Такая схема самая распространенная и встречается в подавляющем большинстве игр. Таких как Ninja Turtles 1-3, SuperContra 1-2, Contra, LifeForce и т.д.. Вторая по распространененности схема кодировок джойстика выглядит так: Вправо 80 Влево 40 Вниз 20 Вверх 10 А 1 В 2 START 4 SELECT 8 Как видно кодировка отличается лишь тем, что в процесс опроса джойстика сканирование происходит с конца. То есть не с кнопки Вправо, а с кнопки А. Такую систему легко отличить от первой тем что вместо команды ROL сдвига применяется обратная команда сдвига в другую сторону команда ROR. Пример такого опроса из игры CrossFire: c35b: BD 16 40 LDA $4016,Х //чтение с порта джойстика c35e: 85 23 STA $23 c360: 4A LSR c361: 05 23 ORA $23 c363: 4A LSR c364: 76 31 ROR $31,X @ $0031 //помещение состояние джойстика в регистр 31 Это два самых распространенных способа кодировок клавиш кнопок. Существуют также и экзотические формы кодировок клавиш, но уже после считывания их в память путем подмены одной кодировки на другую и опрос уже этих кодировок с тем, что записано в памяти. Наиболее яркий пример это игра The Hunt For Red October, которая кодирует повторно клавиши по схеме: Вправо 52 Влево 4С Вниз 44 Вверх 55 А 41 В 42 SELECT 53 И так мы разобрали как игры кодируют клавиши кнопок и теперь можно приступить к описанию как отлавливать комбинации в играх. СПОСОБ 1:Самый простой - поиск в памяти. Во-первых, определимся где чаще всего игры проверяют комбинации. Правильно чаще всего игры опрашивают на титульном экране и в паузе. Все что нам нужно например на титульном экране войти в дебаггер эмулятора (Я пользуюсь FCEUltra).Сначало необходимо определить какая кодировка используется первая или вторая. Для этого нужно поставить бряк на 4016 и посмотреть какая команда используется ROR или ROL. Затем зайти в редактор памяти, снять дамп памяти RАМ, загрузить в редактор(Я применяю Translhextion), поставить скрипт комбинации которую определили и искать похожие на комбинации наборы. Для примера я взял Ninja Turtles 3. Загружаем ее в FCEUltra ждем когда игра дойдет до титульного экрана и идем в дебаггер. Ставим бряк на 4016 и смотрим. Используется команда ROL. Идем в редактор памяти Memory... и делаем дамп. В строке Memory File Dump вводим 0000 слева и FFFF справа. Тем самым мы полностью дампим память в файл. Теперь загружаем в Translhextion. Далее делаем скрип - текстовый файл с расширением tbl и заполняем его следующим содержимым: 08=u 04=d 01=r 02=l 80=A 40=B Загружаем его в Translhextion - идем в Script и открываем наш файл. Ставим галочку на Thingy View Active и ищем подозрительные комбинации. Как правило они распологаются ниже середины дампа. После не долгих поисков видим такую строку по адресу A68A: uuddlrlrAB dduurlrlAB uuddlrlrBA ABrlrldduu urrdddllllBA dddBAdddBA Это и есть все комбинации которые опрашиваются игрой на заставке. Хотя в разных версиях игры (японская, американская) опрашиваются и не все. Способ легог до автоматизма, но тем не менее далеко не все игры грузят сразу свои комбинации в память. Есть которые грузять только в процессе сравнения регистра джойстика. Плюсы: простота метода Минусы: Подходит далеко не ко всем играм. СПОСОБ 2:Дамп памяти во время опроса регистра джойстика. Для начала нам необходимо найти регистр куда считывается состояние джойстика. Затем найти то место где проверется на нажатие Start. И только после этого провести дамп выше описанным способом. Для примера возьмем игру Super C. Ставим бряк на 4016. f92a: ad 16 40 LDA $4016 //чтение с порта джойстика f92d: 85 0c STA $04 f92f: 4A LSR f930: 05 0C ORA $04 f932: 4A LSR f933: 36 08 ROL $00,X @ $0000 //помещение состояние джойстика в регистр 00 Наши это регистр 0. Заметим что здесь адрес ROL-ся. Значит схема кодировки 1-я. Теперь ставим бряк на 0 и убираем бряк на 4016. И смотрим в какие другие регистры заносится содержимое из этого регистра. Во-первых, отбрасываем все срабатывания на адрес f933 где содержимое регистра ROL-я. Для этого в FCEUltra добавляем бряк на f933 с галкой Exclude и нажимаем опять Run до тех пор пока не попадаем сюда: fd06: A5 00 LDA $00 fd08: C5 02 CMP $02 fd0a: D0 1C BNE $FD28 Этот кусок нам не интересен. Нажимаем дальше Run пока не попадем сюда: fd18: B5 00 LDA $00,X @$0000 = $00 //чтение из регистра где записано значение джойстика fd1a: A8 TAY //помещение его в регистр Y fd1b: 55 F7 EOR $F7,X @$00F7 = $00 fd1d: 35 00 AND $00,X @$0000 = $00 fd1f: 95 F1 STA $F1,X @$00F1 //заносится значение джойстика в регистр F1 fd21: 95 F5 STA $F1,X @$00F5 //заносится значение джойстика в регистр F5 fd23: 95 F3 STA $F3,X @$00F3 //заносится значение джойстика в регистр F3 fd25: 94 F7 STA $F7,X @$00F7 //заносится значение джойстика в регистр F7 Тем самым мы нашли где значение джойстика размножается по регистрам. В нашем в случае это F1, F5, F3 и F7. Теперь ставим брак на эти регистры и убираем бряк на 0. Теперь цель найти то место где один из этих бряков сравнивается с 10, то есть со START-ом. Не долго листая нажимая Run попадаем сюда: e42f: A5 F5 LDA $F5 //чтение из регистра где записано значение джойстика e431: 29 10 ADC #$10 //сравнение со START-ом Теперь делаем дамп как в прошлом методе и грузим таблицу скрипта первого типа комбинаций клавиш и ищем последовательности похожие на комбинации. И находим по адресу A2BD такую последовательность: rlduAB Хотя если искать первым способом в памяти по этому адресу ее нет. Плюсы: Можно выявить комбинаций у большего количества игр Минусы: Более сложный чем первый метод. СПОСОБ 3:Анализ регистра где записано значение джойстика Это самый сложный метод и он более точно выявить скрытые опросы, так как непоредственно прослеживается весь путь от регистра до сравнения с комбинациями в памяти. Способ прост по смыслу, но индивидуален для каждых игр. Нужно найти регистр куда сохраняется значение джойстика и что с ним происходит дальше. Но как не трудно догадаться каждая игра в большинтстве случаев делает это по разному. Начнем с самого простого случая встречающегося очень часто. Речь идет о играх где сравнение происходит на лету. Такие игры как Ninja Turtles 3, Super C, Adventure Island 2,3 и т.д.. Пример 1: Ninja Turtles 3(U) Доходим до заставки выбора числа игроков и ставим бряк на 4016 и смотрим: f92a: ad 16 40 LDA $4016 //чтение с порта джойстика f92d: 85 0c STA $0C f92f: 4A LSR f930: 05 0C ORA $0C f932: 4A LSR f933: 36 08 ROL $08,X @ $0008 //помещение состояние джойстика в регистр 08 Нашли регистр куда сохраняется значение джойстика это адрес 8. Так же обращаем внимание на то, что регистр ROL-ся. Значит схема кодировки первая. Ставим бряк на 8 и убираем бряк на 4016. Пропускаем срабатывания адреса 8 где он ROL-ся. И видим: fd06: A5 08 LDA $08 fd08: C5 02 CMP $0A fd0a: D0 1C BNE $F914 Не интересно. Нажимаем Run до следующего срабатывания бряка и попадаем сюда: f904: B5 08 LDA $08,X @$0008 = $00 //чтение из регистра где записано значение джойстика f906: A8 TAY //помещение его в регистр Y f907: 55 FA EOR $FA,X @$00FA = $00 f909: 35 08 AND $08,X @$0008 = $00 f90b: 95 F8 STA $F8,X @$00F8 //заносится значение джойстика в регистр F8 f90d: 95 54 STA $54,X @$0054 //заносится значение джойстика в регистр 54 f90f: 95 FA STA $FA,X @$00FA //заносится значение джойстика в регистр FA f911: 94 56 STA $56,X @$0056 //заносится значение джойстика в регистр 56 Нашли место куда заносится значение джойстика дальше. В данном случае размножается в регистры F8,54,FA,56. Так же замечу это очень распространенная структура используется для обоих джойстиков. Для первого Х=0, для второго Х=1. Ставим бряки на эти адреса и убираем бряк на 8. Срабатывает бряк и мы попадаем сюда: 806с: A5 F8 LDA $F8 806e: 05 F9 ORA $F9 8070: 29 20 AND #$20 //проверка на SELECT Собственно здесь проверка на SELECT и нам не интересна. Нажимаем на Run и ждем срабатывания бряка и попадаем сюда: 8090: A5 F8 LDA $F8 8092: 05 F9 ORA $F9 8094: 29 20 AND #$10 //проверка на START Тоже самое только проверка уже на START. Жмем Run дальше и попадаем сюда: a648: A5 F8 LDA $F8 //чтение из регистра где записано значение джойстика a64a: F0 28 BEQ $A674 //проверка на нулевое значение a64c: BC DA 04 LDY $04DA,X @$04DA = $00 //грузится какое то число в Y a64f: D1 00 CMP ($00),Y @$A68A = $08 //сравнивается значение джойстика с каким то числом Вот оно! Нашли где проверка значения джойстика с тем, что в памяти находящимся комбинациями. Нажимая несколько раз на Run убиждаемся, что проверка осуществляется с 4-мя адресами: A68A, A697, A6A4, A6B1. Посмотрев в редакторе памяти и убеждаемся, что именно по этим адресам находится 4-е пароля: uuddlrlrAB dduurlrlAB uuddlrlrBA ABrlrldduu Хотя в памяти есть еще 2-а пароля, но именно в этой версии игры проверка только 4-х. Пример 2: Alien 3(U) Нашел пароль который набирается в паузе. Ставим игру на паузу и ставим бряк на 4016 и видим: e7e5: ad 16 40 LDA $4016 //чтение с порта джойстика e7e8: 4A LSR e7e9: 66 28 ROR $28 @$0028 //помещение состояние джойстика в регистр 28 Видим, что значение джойстика заносится в регистр 28 причем оно ROR-ся, т.е. кодировка используется вторая. Ставим бряк на 28 и убираем на 4016. Попадаем сюда минуя попадания где адрес ROR-ся: b3e1: A5 28 LDA $28 //чтение из регистра где записано значение джойстика b3e3: A8 TAY //помещение его в регистр Y b3e4: F0 50 BEQ $B436 //проверка на нулевое значение B3e6: 29 08 AND #$08 //проверка на START b3e8: D0 4C BNE $B436 //проверка, что нажал на START b3ea: 98 TYA //Если не нажали на START возвращаем значение джойстика b3eb: DD 37 B4 CMP $B437,X @$B437=$40//проверяем его с тем, что в памяти Смотри, а в памяти по адресу B437 находится комбинация: LRUDLRABA Проверяем ее и убеждаемся, что это пароль на переход на следующий уровень Теперь разберем более сложный случай, когда сначало зачения джойстика записываются в регистры, а уже потом после нажатия на START проверяется. Хороший пример игра CrossFire.Ее и разберем. Пример3: Cross Fire (J) Доходим до заставки где горит надпись нажми на START и ставим бряк на 4016 и смотрим: c35b: BD 16 40 LDA $4016,Х //чтение с порта джойстика c35e: 85 23 STA $23 c360: 4A LSR c361: 05 23 ORA $23 c363: 4A LSR c364: 76 31 ROR $31,X @ $0031 //помещение состояние джойстика в регистр 31 Нашли регистр куда сохраняется значение джойстика это адрес 31. Так же обращаем внимание на то, что регистр ROR-ся. Значит схема кодировки вторая. Ставим бряк на 31 и убираем бряк на 4016. Пропускаем срабатывания адреса 31 где он ROR-ся. И видим: c36c: A5 31 LDA $31 //чтение из регистра где записано значение джойстика c36e: 48 PHA c36f: 05 3B ORA $3B c371: 45 3B EOR $3B c373: 85 3A STA $3A //запись из регистра где записано значение джойстика в регистр 3А c375: 68 PLA c376: 85 3B STA $3B //запись из регистра где записано значение джойстика в регистр 3B Видим, что значение джойстика записывается в регистр 3B и 3А. Ставим на них бряк. и попадаем сюда: 8d5b: A5 3A LDA $3A 8d5d: 29 08 AND #$08 //проверка на START Собственно здесь проверка на START и нам не интересна. Нажимаем на Run и ждем срабатывания бряка и попадаем сюда: 8d2e: A5 3A LDA $3A //чтение из регистра где записано значение джойстика 8d30: F0 0E BEQ $8D40 //проверка на нулевое значение 8d32: AC 22 06 LDY &0622 8d35: C0 10 CPY #$10 8d37: B0 07 BCS $8D40 8d39: 99 23 06 STA $0623,Y @$63B //загрузка значения джойстика в один из регистров 8d3c: C8 INY //инкремент адреса куда писать новое значение джойстика Мы нашли то место где не нулевые значения джойстика записываются в виде массива в память. Теперь убирираем все бряки и ставим бряк на адрес начальный 0623 и на адрес 0622. Нажимаем Run и переходим к игре и нажимаем Start. Срабатывает бряк на 0622. Попадаем туда же, т.к. Start тоже заносится в память. Нажимаем Run. Срабатывает бряк на 0623. Там же. Опять жмем Run и попадаем сюда: 8dec: AD 22 06 LDA $0622 //сравнение нашего адреса с каким то в памяти 8def: DD 42 8E CMP $8E42,X @$8E42 = $0A 8df2: F0 0B BEQ $8DFF //если это число совпадает, то идем по адресу 8DFF Что мы видим по адресу 8DFF: 8dff: 8A TXA 8e00: 48 PHA 8e01: A0 00 LDY #$10 8e03: E8 INX 8e04: B9 23 06 LDA $0623,Y //сравнивается нажатые кнопки с какими в памяти 8e07: DD 42 8E CMP $8E42,X @$8E43 = $04 //сравнение Таким образом видим, что по адресу 0622 расположен счетчик нажатых клавишь, а начиная с 0623 массив нажатых кнопок. Смотри, что есть в дампе по адресу 8E43: SSABBSBSA BBBABSSBAAS SBSBABBABBS где S - Select И того 3-и комбинации. Теперь перейдем к более сложному примеру Hunt for the Red October. В нем клавиши шифруются в другие значения. Пример4: Hunt for the Red October, The [p1] Найдем пароли которые набираются в паузе. Ставим игру на паузу и ставим бряк на 4016 и видим: 9f7d: AD 16 40 LDA $4016 9f80: 29 03 AND #$03 9f82: c9 01 CMP #$01 9f82: 26 47 ROL $47 ...... //повторения 9fbc: AD 16 40 LDA $4016 9fbf: 29 03 AND #$03 9fbf: 26 47 ROL $47 //помещение состояние джойстика в регистр 47 Нашли регистр куда сохраняется значение джойстика это адрес 47. Так же обращаем внимание на то, что регистр ROL-ся. Значит схема кодировки первая. Тут восемь вхождений в 4016 и восемь сдвигов регистра 47. То есть по сути вместо цикла поставили подрят восемь считываний. Ставим бряк на 47 и убираем бряк на 4016. Пропускаем срабатывания адреса 47 где он ROL-ся. И видим: ffde: A5 47 LDA $47 ffe0: 29 30 AND #$30 //проверка на Select и Start ffe2: C9 30 CMP #$30 ffe4: D0 08 BNE $FFEE //Проверка одновременное нажатие кнопок Select и Start В принципе не интересно. Стоит отметить, что когда ждут одновременное нажатие Select и Start ставять BNE, а если одну из кнопок, то ставят BEQ. Нажимаем на Run. И видим: bbe8: A5 47 LDA $47 bbea: 29 10 AND #$10 bbec: D0 1C BNE $BC0A Не интересно. Банальная проверка на Старт. Нажимаем на Run. И видим: bbee: A5 47 LDA $47 bbf0: A2 00 LDX #$00 bbf2: 4A LSR //сдвиг вправо на 1 bbf3: 90 09 BCC $BBFE //проверка если не сдвинули до 0, то уходим на BBFE bbf5: BD 0E BC LDA $BC0E,X //загружаем новый код клавиши bbf8: 20 64 BD JSR $BD64 //уходим отсюда bbfb: 90 07 BCC $BC04 bbfd: 60 RTS bbfe: E8 INX //инкрементируем указатель на код клавиши расположенные по BC0E+Х bbff: E0 08 CPX #$08 //проверка обошли ли все 8-м шифровок клавиш bc01: 90 EF BCC $BBF2 Вот нашли. Здесь выбирается новый код клавиши из массива начинающемуся по адресу BC0E. Загрузив новый код клавиши уходит по адресу BD64. Смотрим, что происходит по этому адресу: bd64: 85 0F STA $0F //новый код заносится в регистр F. И так попав в адрес bbee занесем в регистр 47 число например 0Х80 - код клавиши, ставим бряк на F на чтение . Нажимаем Run и видим: bd80: B1 09 LDA($09),Y @$BCEA =$41 //грузится какой то код и bd82: C5 0F CMP $0F @$000F =$42 //сравнивает с нашим Попали в область где проверка нашей клавиши с массивом в памяти начало которого по адресу BCEA. Кодировки клавиш как я писал раньше: Вправо 52 Влево 4С Вниз 44 Вверх 55 А 41 В 42 SELECT 53 Посмотрим, что находится по адресу BCEA, сняв дамп и загрузив его в Translhextion. Включим скрипт по этой кодировке: ABBARDULL ABSABSABSABSABSABBA ABSRLLRSBABBA S UDLRUDLRUDLRUDLRAAABBBSSSRRRUUUDDDLLLABBA UDLRS UDLRUDLRUDLRABBA UUDDLLRR где S - Select И того всего 8-м паролей. Из них два новых. РАЗДЕЛ 2: Удерживаемые комбинации клавиш. Пример1: Splatter_House_-_Wanpaku_Graffiti_(J). Комбинации при нажатии на RESET. Теперь хотелось бы поговорить о поиск мест где необходимо зажимать определенные клавиши для включения чита. Особенно поиски мест где необходимо нажимать RESET на приставке. Как правило процесс опроса выглядит следующим образом на примере игры Splatter_House_-_Wanpaku_Graffiti_(J): ac6b: A5 68 LDA $68 @ $0068 = $00 //чтение из регистра где записано значение джойстика причем со второго ac6d: C9 C4 CMP #$C4 //сравнение с А+В+Вниз ac6f: D0 09 BNE $AC7A //проверка на данную комбинацию Как же найти процедуру проверки на зажатие кнопкок сразу после сброса приставки. А очень просто. Поставте бряк на 4016 и 4017 и в дебаггере нажмите Reset. Сразу попадаем в процедуру опроса джойстика: Для бряка на 4016: e1c2: AD 16 40 LDA $4016 @ $4016 = $40 //чтение с порта джойстика 1 e1c5: 85 02 STA $02 e1c7: 4A LSR e1c8: 05 02 ORA $02 @ $0002 = $00 e1ca: 4A LSR e1cb: 26 00 ROL $00 @ $0000 = $0F //помещение состояние джойстика в регистр 00 Для 4017 e1cd: AD 17 40 LDA $4017 @ $4017 = $40 //чтение с порта джойстика 2 e1d0: 85 02 STA $02 e1d2: 4A LSR e1d3: 05 02 ORA $02 @ $0002 = $00 e1d5: 4A LSR e1d6: 26 01 ROL $01 @ $0001 = $FF //помещение состояние джойстика в регистр 01 e1d8: 88 DEY e1d9: D0 E7 BNE $E1C2 Как видно для первого джойстика состояние записывается в 0, второго в 1. Ставим бряк на 0 и 1 и снимаем с 4016 и 4017, а так же исключаем бряки на e1cb и e1d6 где адреса 0 и 1 ROL-ся. Нажимаем Run и попадаем сюда: e1e6: B5 00 LDA $00,X @ $0000 = $00 //считывание состояние 1-го джойстика e1e8: A8 TAY //копирование состояние джойстика из регистра А в Y e1e9: 55 67 EOR $67,X @ $0067 = $00 e1eb: 35 00 AND $00,X @ $0000 = $00 e1ed: 95 65 STA $65,X @ $0065 //помещение состояние джойстика в регистр 65 e1ef: 94 67 STY $67,X @ $0067 //помещение состояние джойстика в регистр 67 e1f1: 60 RTS Это стандартная структура где Х при 0 для 1-го джойстика, а 1 для 2-го. То есть для 1-го джойстика будут адреса 65,67 а для второго 66,68. Ставим бряки еще для 65-68 адресов. 0 и 1 не убираем, так как бывает что и с на тоже проверка организуется. Нажимаем Run и попадаем сюда: ac6b: A5 68 LDA $68 @ $0068 = $00 //чтение из регистра где записано значение джойстика причем со второго ac6d: C9 C4 CMP #$C4 //сравнение с А+В+Вниз ac6f: D0 09 BNE $AC7A //проверка на данную комбинацию ac71: A9 09 LDA #$09 ac73: 85 70 STA $70 ac75: A9 00 LDA #$00 ac77: 85 71 STA $71 Вот и нашли где сразу после RESET опрашивается 2-й джойстик на комбинацию А+В+Вниз. Нажме еще Run может еще опрос есть: ac7a: A5 67 LDA $67 @ $0067 = $00 //чтение из регистра где записано значение джойстика причем с первого ac7c: C9 A8 CMP #$A8 //сравнение с А+Select+Вверх ac7e: D0 09 BNE $AC89 //проверка на данную комбинацию ac80: A9 05 LDA #$05 ac82: 85 70 STA $70 ac84: A9 00 LDA #$00 ac86: 85 71 STA $71 И не ошиблись есть еще проверка на А+Select+Вверх 1-го джойстика. Так же стоит обратить внимание на то что при разных комбинация есть только одно место которое отличается строка ac71 и ac80. В первом случае грузится 9, во втором 5. Это место где выбирается что приставке грузить дальше. Путем подбора выяснил, что если заменить 9 на 8, то вместо TEST MODE загрузится SCENE SELECT MODE. Судя по дампу памяти в роме есть еще STAGE SELECT MODE, но добратся до него не удалось. Пример2: Battletoads (U). Проверка комбинаций после Start. Бывает так, что комбинация при одновременном нажатии Start и еще каких либо проверяются не сразу. Одну из таких игр и рассмотрим. Ставим бряк на 4016-4017: Для 4016 8d83: AD 16 40 LDA $4016 @ $4016 = $40 //чтение с порта джойстика 1 8d86: 6A ROR 8d87: 26 15 ROL $15 @ $0015 = $00 //помещение состояние джойстика в регистр 15 Для 4017 8d89: AD 17 40 LDA $4017 @ $4017 = $40 //чтение с порта джойстика 2 8d8c: 6A ROR 8d8d: 26 16 ROL $16 @ $0016 = $00 //помещение состояние джойстика в регистр 16 Ставим бряк на 15-16, убираем на 4016-4017, делаем исключение для 8d87 и 8d8d.Запускаем Run и попадаем сюда: 8d92: A5 15 LDA $15 @ $0015 = $00 //чтение из регистра где записано значение джойстика1 8d94: AA TAX //копирование состояние джойстика из регистра А в Х 8d95: 45 29 EOR $29 @ $0029 = $00 8d97: 86 29 STX $29 //помещение состояние джойстика1 в регистр 29 8d99: 25 15 AND $15 @ $0015 = $00 8d9b: 85 2B STA $2B //помещение состояние джойстика1 в регистр 2B 8d9d: A5 16 LDA $16 @ $0016 = $00 //чтение из регистра где записано значение джойстика2 8d9f: AA TAX //копирование состояние джойстика из регистра А в Х 8da0: 45 2A EOR $2A @ $002A = $00 8da2: 86 2A STX $2A //помещение состояние джойстика2 в регистр 2А 8da4: 25 16 AND $16 @ $0016 = $00 8da6: 85 2C STA $2C //помещение состояние джойстика2 в регистр 2С Размножение из адресов 15, 16 в 29,2В и 2А,2С соответственно. Ставим бряки на 29,2В и 2А,2С.Запускаем Run и попадаем сюда: e5ca: A5 2B LDA $2B @ $002B = $00 //чтение из регистра где записано значение джойстика1 e5cc: 05 2C ORA $2C @ $002C = $00 e5ce: 29 10 AND #$10 //сравнение со START-ом e5d0: D0 0C BNE $E5DE Нашли где проверка на START. Теперь запишем в регистр 2В число 10. Для этого заходим в Memory... и в строке MemoryPoke ставим число 10 и нажимаем Poke Me!. Это эмуляция нажатия START. Запускаем Run и попадаем сюда: e5eb: B5 2B LDA $2B,X @ $002C = $00 //чтение из регистра где записано значение джойстика2 e5ed: 29 10 AND #$10 //сравнение со START-ом Пропускаем так как первый игрок нас не интересует. Запускаем Run и попадаем сюда: e5eb: B5 2B LDA $2B,X @ $002B = $10 //чтение из регистра где записано значение джойстика1 e5ed: 29 10 AND #$10 //сравнение со START-ом Видимо проверка есть вместе 2-х джойстиков на START и по отдельности. Здесь отдельная проверка для первого джойстика. Значение в регистре сохранено и равно по прежнему 10. Пропускаем. Запускаем Run и попадаем сюда: e611: B5 29 LDA $29,X @ $0029 = $00 //чтение из регистра где записано значение джойстика1 e613: C9 D4 CMP #$D4 //проверка на А+В+Вниз+Start e615: D0 0B BNE $E622 e617: A9 05 LDA #$05 //грузится в случае правильного набора 5 жизней e619: 95 11 STA $11,X @ $0011 //здесь память жизней Вот и нашли где после нажатия START проверяется комбинация клавиш. РАЗДЕЛ 3: ДОБАВЛЯЕМ СВОИ КОМБИНАЦИИ КЛАВИШ Используя знания второго раздела можно самому добавлять комбинации клавиш. Скажем кто мешает добавить пополнение энергии комбинацией Вверх+START. Как это делается в этом разделе и рассмотрим. Но сначало рассмотрим несколько нюансов связанных с тем, что игры на NES в память полностью не грузятся, а что грузить определяет маппер. Всего мапперов очень много и разбираться с ними не иммет мысла и перспектив в этом вопросе. Поэтому мы пойдем другим путем. А именно будет вставлять свой код в текущий в данный момент опроса блок памяти. Но чтобы знать какой блок в данный момент грузится необходимо опять обратится к маперру? Нет, можно и это обойти. Для этого необходимо найти то место где ведется опрос на Start.И пределы области памяти 0000-FFFF где находится этот опрос и будет текущий. И знать как маппер блоками рулит нет надобности. Пример1: Little Mermaid, The (U) добавим комбинацию Вверх+START для пополнения жизни. Для начала необходимо найти адрес где текущая жизнь хранится с помощью VirtualNes. В данной игре это адрес В1. Теперь найдем опрос на Start. Ставим игру на паузу и ставим бряк на 4016. Запускаем Run и попадаем сюда: c123: AD 16 40 LDA $4016 @ $4016 = $40 //чтение из регистра где записано значение джойстика1 c126: 4A LSR c127: 26 14 ROL $14 @ $0014 = $00 //помещение состояние джойстика в регистр 14 c129: 4A LSR c12a: 26 00 ROL $00 @ $0000 = $F5 //помещение состояние джойстика в регистр 0 Выбираем регистр 14 и ставим бряк на него, убираем на 4016, исключаем c127. Запускаем Run и попадаем c13a: 05 14 ORA $14 @ $0014 = $00 c13c: 85 14 STA $14 //помещение состояние джойстика в регистр 14 c13e: A5 01 LDA $01 @ $0001 = $00 c140: 05 15 ORA $15 @ $0015 = $00 c142: 85 15 STA $15 c144: A2 01 LDX #$01 c146: B5 14 LDA $14,X @ $0014 = $00 //чтение из регистра где записано значение джойстика1 c148: A8 TAY //копирование состояние джойстика из регистра А в Y c149: 55 16 EOR $16,X @ $0016 = $00 c14b: 35 14 AND $14,X @ $0014 = $00 c14d: 95 14 STA $14,X @ $0014 //помещение состояние джойстика в регистр 14 c14f: 94 16 STY $16,X @ $0016 //помещение состояние джойстика в регистр 16 Видим, что есть еще регистр где хранится состояние джойстика1. Это регистр 16. Ставим бряк на 16.Нажимаем Run пока не попадем сюда: eb70: A5 14 LDA $14 @ $0014 = $00 //чтение из регистра где записано значение джойстика1 eb72: 29 10 AND #$10 //сравнение со START-ом eb74: D0 09 BNE $EB7F //если нажали START идем по адресу EB7F. eb76: 20 02 EC JSR $EC02 eb79: 20 AD FF JSR $FFAD eb7c: 4C 70 EB JMP $EB70 eb7f: A9 1C LDA #$1C eb81: 20 A0 FC JSR $FCA0 То что нужно. Здесь опрос и переход если нажали START. Теперь необходимо взять 3 байта под переход JMP. Команду BNE мы взять не можем, т.к. это по сути команда пропуска байт если выполнилось условие от того место откуда она была вызвана. Смотрим что идет по перехода EB7F: eb7f: A9 1C LDA #$1C eb81: 20 A0 FC JSR $FCA0 Вот это мы можем захватить под переход, так как здесь переход прямой по адресу FCA0. Теперь необходимо найти место в роме где можно воспроизвести то, что мы позаимствовали под переход JMP. Смотрим ниже адреса eb81 и ищим повторяющиеся байты FF не менее 20-30 раз. Для этого прокручиваем в дебаггере бегунок вниз и смотрим. Смотрим и находим по адресу FE8D. Там достаточно места для написания нашего кода. Вообще смотреть свободное место лучше начинать с конца. Таким образом нам необходимо заменить код : eb7f: A9 1C LDA #$1C eb81: 20 A0 FC JSR $FCA0 На следующий eb7f: 4C 8D FE JMP $FE8D //переход по нашему адресу FE8D там где будет расположен наш код eb82: EA NOP //пустая команда под заполение оставшегося места eb83: EA NOP //пустая команда под заполение оставшегося места Для того чтобы заменить старый на новый необходимо в роме в нЕХ редакторе найти последовательность байт: 4C 70 EB A9 1C 20 A0 FC и заменить ее на 4C 70 EB 4C 8D FE EA EA. Теперь необходимо вставить наш код по адресу FE8D. А код такой: FE8D: A5 16 LDA $16 //грузится значение джойстика1 FE8F: C9 18 CMP #$18 //сравнение с Вврех+Start FE91: D0 04 BNE $FE97 //если не нажата комбинация-идем к старому коду который убрали под переход JMP FE93: A9 05 LDA #$05 //если нажали комбинацию, то грузим жизнь 5 FE95: 85 B1 STA $B1 //помещаем ее в регистр где жизнь текущая хранится FE97: A9 1C LDA #$1C //далее выполняем старый код который выбрали по JMP FE99: 20 A0 FC JSR $FCA0 FE9C: 4C 84 EB JMP $EB84 //переход обратно откуда перешли суда минуя переход который вставили Заменяем старый код на новый найдем где же пустое место с FF расположено в роме. Для этого в эмуляторе посморим, что расположено выше адреса FE8D: 04 08 10 20 40 80 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF Эту последовательность и ищем в НЕХ редакторе и заменяем ее на: 04 08 10 20 40 80 A5 16 C9 18 D0 04 A9 05 85 B1 A9 1C 20 A0 FC 4c 84 EB Сохраняем изменения в НЕХ редакторе. Запускаем ром нажимаем паузу и затем Вверх+Start и у вас 5 текущих жизней. Работает. Пример2: Snake_Rattle_n_Roll(U) добавим комбинацию Вниз+START чтобы жизней стало 9 и таймер установить на 90. Первоначально находим где хранится жизнь и таймер - 3DF и десятые значения таймера хранятся в СЕ. Теперь нужно найти где происходит проверка на Start. Как обычно ставим бряк на 4016, затем на регист где хранится состояние джойстика(у меня адрес 4) и находим все адреса где хранится значение джойстика. У меня получилось 4, 16 и 18. Теперь ставим бряк еще и на 16,18 и ищем где проверка на START пока не находим такой кусок кода: 8dac: A5 18 LDA $18 @ $0018 = $00 //грузится значение джойстика1 8dae: 05 19 ORA $19 @ $0019 = $00 8db0: 29 10 AND #$10 //сравнение со START-ом Теперь нужно определиться что будет заимствовать под переход. Лучше все здесь подходит операция загрузки LDA и логическая операция ORA их и заменим на JMP. Необходимо так же знать куда будем переходить - найдем свободную область памяти куда можно вставить свой код. Чесно сказать в этой игде свободного места почти нет. Единственное место это область заполненная нулями по адресу ECC1. Ее и будем использовать. Таким образом известен адрес куда будем идти. Меняем выуказанный код на свой: 8dac: 4C C1 EC JMP $ECC1 //переход по адресу ECC1 8daf: EA NOP //свободное место заполняем NOP 8db0: 29 10 AND #$10 Для того чтобы заменить старый на новый необходимо в роме в нЕХ редакторе найти последовательность байт: A5 18 05 19 29 10 и заменить ее на 4C C1 EC EA 29 10 Далее необходимо 00 по адресу ECC1 заменить на наш код: ecc1: A5 16 LDA $16 @ $0016 = $00 //берем второй регистр 16, а 18 для загрузки значения джойстика1 ecc3: C9 14 CMP #$14 //сравнение с Вниз+Start ecc5: D0 09 BNE $ECD0 //если не нажата комбинвация, то выполняем старый код который заменили на JMP ecc7: A9 09 LDA #$09 //загружаем жизнь равную 9 ecc9: 8D DF 03 STA $03DF //делаем жизнь по адресу 3DF равную 9 eccc: A9 09 LDA #$09 //загружаем таймер равный по десяткам 9 ecce: 85 CE STA $CE //делаем таймер по адресу СЕ равный по десяткам 9 ecd0: A5 18 LDA $18 @ $0018 = $00 //старый код который заменили выше на JMP ecd2: 05 19 ORA $19 @ $0019 = $00 ecd4: 4C B0 8D JMP $8DB0 //уходим обратно откуда ушли минуя наш JMP Заменяем старый код на новый найдем где же пустое место с 00 расположено в роме. Для этого в эмуляторе посморим, что расположено выше адреса ECC1: 48 3F 04 04 04 01 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Эту последовательность и ищем в НЕХ редакторе и заменяем ее на: 48 3F 04 04 04 01 01 01 00 A5 16 C9 14 D0 09 A9 09 8D Df 03 A9 09 85 CE A5 18 05 19 4C B0 8D Сохраняем изменения в НЕХ редакторе. Запускаем ром нажимаем паузу и затем Вниз+Start и у вас 9 жизней и таймер снова становится больше 90. Работает. Пример3:Darkwing Duck (U) [o1] добавим комбинацию в паузе Верх,Вниз,Влево,Вправо - сделаем9 жизней при ее вводе Пример болле сложный и места потребуется гораздо больше. Не случайно я выбрал эту игру. Во-первых, в ней нет комбинаций вообще, а во-вторых, в ней есть достаточно места чтобы вставить достаточно большой кусок кода. Для начала найдем где хранится жизнь - у меня это адрес 5Е6. Жизнь хранится в виде числа жизней + 0х70. То есть жизней 3, а хранится 73. Теперь найдем где происходит опрос на нажатие START. У меня это вот это место: 8fc4: A5 14 LDA $14 @ $0014 = $00 8fc6: 29 10 AND #$10 8fc8: F0 ED BEQ $8FB7 Ищем место куда можно вставить 43 байта. Очень много места по адресу FC4B. Определились куда будем пихать наш код. Теперь заменим кусок кода по адресу 8fc4-8fc7 на переход: 8fc4: 4C 4B FC JMP $FC4B 8fc7: EA NOP 8fc8: F0 ED BEQ $8FB7 Для того чтобы заменить старый на новый необходимо в роме в нЕХ редакторе найти последовательность байт: A5 14 29 10 F0 ED и заменить ее на 4C 4B FC EA F0 ED Далее начиная с адреса FC4B вставляем свой код: fc4b: AD FF 1F LDA $1FFF @ $1FFF = $00 //грузим счетчик введеных правильно комбинаций fc4e: AA TAX //грузим его в регистр Х fc4f: A5 14 LDA $14 @ $0014 = $00 //грузим состояние джойстика fc51: DD 73 FC CMP $FC73,X @ $FC83 = $FF //сравниваем его с правильной комбинацией fc54: D0 13 BNE $FC69 //если не правильно выполняем старый код который заменили на JMP fc56: E8 INX //если правильно ввели увеличиваем счетчик правильно введеных комбинаций fc57: 8A TXA //помещаем его в регистр А fc58: 8D FF 1F STA $1FFF //заносим увеличенный счетчик по адресу 1FFF fc5b: C9 04 CMP #$04 //сравниваем правильное введеное количество с 4.Всего 4-е комбинации fc5d: D0 0A BNE $FC69 //если не все правильно ввели, то выполняем старый код fc5f: A9 00 LDA #$00 //если всен ввели, то обнуляем счетчик введеных комбинаций fc61: 8D FF 1F STA $1FFF //заносим обнуленный счетчик по адресу 1FFF fc64: A9 79 LDA #$79 //делаем жизнь раную 9 fc66: 8D E6 05 STA $05E6 //грузим новое число жизней fc69: A9 FF LDA #$FF //заносим число которое было в Х обратно, там было FF fc6b: AA TAX //и в Х его fc6c: A5 14 LDA $14 @ $0014 = $00 //это старый код который мы заменяли выше на JMP fc6e: 29 10 AND #$10 fc70: 4C C8 8F JMP $8FC8 //уходим отсуда обратно минуя наш JMP fc73: 08 PHP //код кнопки Вверх fc74: 04 .db $04 //код кнопки Вниз fc75: 02 .db $02 //код кнопки Влево fc76: 01 00 ORA ($00,X) @ $0000 = $76 //код кнопки Вправо Теперь по подробнее рассмотрим, что и откуда я взял для написания этого кода. Первое, я взял под хранение счетчика правильно нажатых кнопок адрес 1FFF. Как правило этот адрес редко используется. В этом легко убедиться поставив бряк на этот адрес и начать с самого начала с загрузки игры и до паузы - бряк не сработал. Можно и другой, но нужно проверять используется ли этот адрес во время игры. Далее для того, чтобы смещаться относительно базового адреса и сравнивать код клавиши с тем что в памяти можно использовать X или Y. Я взял Х. Предварительно я посмотрел чему равно значение до вхождения в наш кусок кода. А равно оно FF. Так же убедился, что значение не меняется поиграв немного и снова войдя в наш кусок кода. По прежнему FF. И перед тем как выполнить старый код который мы заменили на JMP я вернул то самое FF. Алгоритм прост. Сначало грузится счетчик правильно введеных комбинаций кнопок по адресу 1FFF в регистр Х. Затем грузится состояние джойстика. Оно сравнивается с тем, что записано в памяти + смещение на величину введеных уже комбинаций. Если они не равны, то возвращается старое значение Х и выполняется старый код. А если они равны увеличивается счетчик введеных клавиш и сохраняется по адресу 1FFF. Далее оно сравнивается с максимальным числом комбинаций которое нужно ввести. Если оно меньше, то возвращается старое значение Х и выполняется старый код. Если же они они одинаковы, то счетчик обнуляется и сохраняется по адресу 1FFF. Потом заносится новое значение жизни 79 по адресу 5E6, возвращается старое значение Х и выполняется старый код. И наконец выходим из программы обратно минуя наш переход JMP. Заменяем старый код на новый найдем где же пустое место с FF расположено в роме. Для этого в эмуляторе посморим, что расположено выше адреса fc4b: 40 03 60 01 02 04 08 10 20 40 80 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF Эту последовательность и ищем в НЕХ редакторе и заменяем ее и все что дальше на: 40 03 60 01 02 04 08 10 20 40 80 AD FF 1F AA A5 14 DD 73 FC D0 13 E8 8A 8D FF 1F C9 04 D0 0A A9 00 8D FF 1F A9 79 8D E6 05 A9 FF AA A5 14 29 10 4C C8 8F 08 04 02 01 Сохраняем. Проверяем. Идем в паузу, Нажимаем Верх,Вниз,Влево,Вправо и видим, что жизнь с 3-х сменилась на 9. Работает.