Я продолжаю изучать ассемблер 6502, но для экспериментов мне понадобился дизассемблер, Я пробовал использовать da65 собственно тот что идет вместе с ассемблером и линкером ca65 и ld65. Но заметив в документации коды команд в hex представление. И вдруг понял что если прочитать файл nes то можно просто взять код инструкции, взять ее длину и спарсить аргумент. И мы получим дизассемблированный код в его простом представление.
Первым делом надо сформировать список всех команд и их опкодов. Список я взял из таблиц в документации http://emuverse.ru/wiki/MOS_Technology_6502/Система_команд#ADC немного ручной работы и небольшой скрипт и как результат был сформирован массив команд с которым можно работать.
Массив с hex кодом команды, шаблоном и длиной
<?php return [ '69' => [ 'ADC #$argument', 2 ], '65' => [ 'ADC $argument', 2 ], '75' => [ 'ADC $argument,X', 2 ], '6D' => [ 'ADC $argument', 3 ], '7D' => [ 'ADC $argument,X', 3 ], '79' => [ 'ADC $argument,Y', 3 ], '61' => [ 'ADC ($argument,X)', 2 ], '71' => [ 'ADC ($argument),Y', 2 ], '29' => [ 'AND #$argument', 2 ], '25' => [ 'AND $argument', 2 ], '35' => [ 'AND $argument,X', 2 ], '2D' => [ 'AND $argument', 3 ], '3D' => [ 'AND $argument,X', 3 ], '39' => [ 'AND $argument,Y', 3 ], '21' => [ 'AND ($argument,X)', 2 ], '31' => [ 'AND ($argument),Y', 2 ], '0A' => [ 'ASLA', 1 ], '06' => [ 'ASL $argument', 2 ], '16' => [ 'ASL $argument,X', 2 ], '0E' => [ 'ASL $argument', 3 ], '1E' => [ 'ASL $argument,X', 3 ], '90' => [ 'BCC $argument', 2 ], 'B0' => [ 'BCS $argument', 2 ], 'F0' => [ 'BEQ $argument', 2 ], '24' => [ 'BIT $argument', 2 ], '2C' => [ 'BIT $argument', 3 ], '30' => [ 'BMI $argument', 2 ], 'D0' => [ 'BNE $argument', 2 ], '10' => [ 'BPL $argument', 2 ], '00' => [ 'BRK', 1 ], '50' => [ 'BVC $argument', 2 ], '70' => [ 'BVS $argument', 2 ], '18' => [ 'CLC', 1 ], 'D8' => [ 'CLD', 1 ], '58' => [ 'CLI', 1 ], 'B8' => [ 'CLV', 1 ], 'C9' => [ 'CMP #$argument', 2 ], 'C5' => [ 'CMP $argument', 2 ], 'D5' => [ 'CMP $argument,X', 2 ], 'CD' => [ 'CMP $argument', 3 ], 'DD' => [ 'CMP $argument,X', 3 ], 'D9' => [ 'CMP $argument,Y', 3 ], 'C1' => [ 'CMP ($argument,X)', 2 ], 'D1' => [ 'CMP ($argument),Y', 2 ], 'E0' => [ 'CPX $argument', 2 ], 'E4' => [ 'CPX $argument', 2 ], 'EC' => [ 'CPX $argument', 3 ], 'C0' => [ 'CPY $argument', 2 ], 'C4' => [ 'CPY $argument', 2 ], 'CC' => [ 'CPY $argument', 3 ], 'C6' => [ 'DEC $argument', 2 ], 'D6' => [ 'DEC $argument,X', 2 ], 'CE' => [ 'DEC $argument', 3 ], 'DE' => [ 'DEC $argument,X', 3 ], 'CA' => [ 'DEX', 1 ], '88' => [ 'DEY', 1 ], '49' => [ 'EOR #$argument', 2 ], '45' => [ 'EOR $argument', 2 ], '55' => [ 'EOR $argument,X', 2 ], '4D' => [ 'EOR $argument', 3 ], '5D' => [ 'EOR $argument,X', 3 ], '59' => [ 'EOR $argument,Y', 3 ], '41' => [ 'EOR ($argument,X)', 2 ], '51' => [ 'EOR ($argument),Y', 2 ], 'E6' => [ 'INC $argument', 2 ], 'F6' => [ 'INC $argument,X', 2 ], 'EE' => [ 'INC $argument', 3 ], 'FE' => [ 'INC $argument,X', 3 ], 'E8' => [ 'INX', 1 ], 'C8' => [ 'INY', 1 ], '4C' => [ 'JMP $argument', 3 ], '6C' => [ 'JMP ($argument)', 3 ], '20' => [ 'JSR $argument', 3 ], 'A9' => [ 'LDA #$argument', 2 ], 'A5' => [ 'LDA $argument', 2 ], 'B5' => [ 'LDA $argument,X', 2 ], 'AD' => [ 'LDA $argument', 3 ], 'BD' => [ 'LDA $argument,X', 3 ], 'B9' => [ 'LDA $argument,Y', 3 ], 'A1' => [ 'LDA ($argument,X)', 2 ], 'B1' => [ 'LDA ($argument),Y', 2 ], 'A2' => [ 'LDX #$argument', 2 ], 'A6' => [ 'LDX $argument', 2 ], 'B6' => [ 'LDX $argument,Y', 2 ], 'AE' => [ 'LDX $argument', 3 ], 'BE' => [ 'LDX $argument,Y', 3 ], 'A0' => [ 'LDY #$argument', 2 ], 'A4' => [ 'LDY $argument', 2 ], 'B4' => [ 'LDY $argument,X', 2 ], 'AC' => [ 'LDY $argument', 3 ], 'BC' => [ 'LDY $argument,X', 3 ], '4A' => [ 'LSRA', 1 ], '46' => [ 'LSR $argument', 2 ], '56' => [ 'LSR $argument,X', 2 ], '4E' => [ 'LSR $argument', 3 ], '5E' => [ 'LSR $argument,X', 3 ], 'EA' => [ 'NOP', 1 ], '09' => [ 'ORA #$argument', 2 ], '05' => [ 'ORA $argument', 2 ], '15' => [ 'ORA $argument,X', 2 ], '0D' => [ 'ORA $argument', 3 ], '1D' => [ 'ORA $argument,X', 3 ], '19' => [ 'ORA $argument,Y', 3 ], '01' => [ 'ORA ($argument,X)', 2 ], '11' => [ 'ORA ($argument),Y', 2 ], '48' => [ 'PHA', 1 ], '08' => [ 'PHP', 1 ], '68' => [ 'PLA', 1 ], '28' => [ 'PLP', 1 ], '2A' => [ 'ROLA', 1 ], '26' => [ 'ROL $argument', 2 ], '36' => [ 'ROL $argument,X', 2 ], '2E' => [ 'ROL $argument', 3 ], '3E' => [ 'ROL $argument,X', 3 ], '6A' => [ 'RORA', 1 ], '66' => [ 'ROR $argument', 2 ], '76' => [ 'ROR $argument,X', 2 ], '6E' => [ 'ROR $argument', 3 ], '7E' => [ 'ROR $argument,X', 3 ], '40' => [ 'RTI', 1 ], '60' => [ 'RTS', 1 ], 'E9' => [ 'SBC #$argument', 2 ], 'E5' => [ 'SBC $argument', 2 ], 'F5' => [ 'SBC $argument,X', 2 ], 'ED' => [ 'SBC $argument', 3 ], 'FD' => [ 'SBC $argument,X', 3 ], 'F9' => [ 'SBC $argument,Y', 3 ], 'E1' => [ 'SBC ($argument,X)', 2 ], 'F1' => [ 'SBC ($argument),Y', 2 ], '38' => [ 'SEC', 1 ], 'F8' => [ 'SED', 1 ], '78' => [ 'SEI', 1 ], '85' => [ 'STA $argument', 2 ], '95' => [ 'STA $argument,X', 2 ], '8D' => [ 'STA $argument', 3 ], '9D' => [ 'STA $argument,X', 3 ], '99' => [ 'STA $argument,Y', 3 ], '81' => [ 'STA ($argument,X)', 2 ], '91' => [ 'STA ($argument),Y', 2 ], '86' => [ 'STX $argument', 2 ], '96' => [ 'STX $argument,Y', 2 ], '8E' => [ 'STX $argument', 3 ], '84' => [ 'STY $argument', 2 ], '94' => [ 'STY $argument,X', 2 ], '8C' => [ 'STY $argument', 3 ], 'AA' => [ 'TAX', 1 ], 'A8' => [ 'TAY', 1 ], 'BA' => [ 'TSX', 1 ], '8A' => [ 'TXA', 1 ], '9A' => [ 'TXS', 1 ], '98' => [ 'TYA', 1 ], ];
Тут я должен сказать пару слов о длине команды, она зависит от адресации допустим если абсолютная адресация то длина будет 3, если команда без аргументов 1, в остальных случаях длина будет 2 байта. К примеру
LDA $1234 ; AD 34 12 - hex последовательность 3 байта ; команда и аргумент (младший и старший байт) LDA #$0A ; A9 0A - hex последовательность 2 байт сама команда и аргумент TAX ; AA - комманда без аргумента и занимает 1 байт
И так с этим разобрались. Далее нам необходимо прочитать файл nes, это делается просто через file_get_contents в php, далее приводим бинарные данные в 16-ричную строку bin2hex. И разбиваем строку на 2 символа в массив инструкцией str_split. Далее последовательно идем перебирая каждый байт. И сравниваем с инструкциями которые загруженны в переменную. И относительно длины формируем значение аргумента инструкции.
И здесь Я понял довольно важную вещь, файл iNES содержит не только код но и заголовки, информацию о мапере (512кб если есть мапер), код и графику. Это основные секции кода в файле. И теперь для того что бы корректно прочитать код нам не обходимо игнорировать 16 первых байт, 512 байт пока не учитываем (подопытный будет supper mario bros 2 который не имеет маппера), далее берем только код в размере 16384 байта и уже проходя скриптом заменяем байткоды соответствиями команд.
Скрипт мини-дизассемблер под спойлером
Код дизассемблера 6502
<?php /** * Created by PhpStorm. * User: roman * Date: 11.06.2023 * Time: 22:02 */ class Disassembler { private $instructions = []; public function __construct() { $this->instructions = include 'opcodes.php'; } public function disassembly($code) { $stringCode = bin2hex($code); $arrayInstructions = str_split($stringCode, 2); $skipKeys = 0; $resultString = '.headers '; foreach ($arrayInstructions as $_key => $_value) { // 16 byte nes header if ($_key < 16) { $resultString .= ' ' . $arrayInstructions[$_key]; continue; } if ($_key == 16) { $resultString .= PHP_EOL . '.code ' . PHP_EOL; } if ($_key == 16384 + 16) { break; } $value = null; $capitalKey = strtoupper($_value); if ($skipKeys) { $skipKeys--; continue; } if (isset($this->instructions[$capitalKey])) { if ( $this->instructions[$capitalKey][1] == 3 ) { $value = $arrayInstructions[$_key+2] . $arrayInstructions[$_key+1]; $resultString .= str_replace('$argument', '$' . $value, $this->instructions[$capitalKey][0]) . PHP_EOL; $skipKeys = 2; } elseif ( $this->instructions[$capitalKey][1] = 2 ) { $value = $arrayInstructions[$_key + 1]; $skipKeys = 1; $resultString .= str_replace('$argument', '$' . $value, $this->instructions[$capitalKey][0]) . PHP_EOL; } else { $resultString .= $this->instructions[$capitalKey][0] . PHP_EOL; } } } file_put_contents('result.asm', $resultString); } } $binary = file_get_contents('../smb.nes'); $disasm = new Disassembler(); $disasm->disassembly($binary);
Теперь остается проверить лишь его работоспособность, для этого запускаем ld65 по файлу smb.nes а так же наш скрипт результат выполнения следующий.


Как вы видите код идентичный да da65 генерирует метки для перехода и ZeroPage. Но мы пока разбираемся в корне механизма дизассемблирования.
В плане написать скрипт ассемблера на php из кода который был дизассемблирован. Это даст чуть более широкие возможности в ром хакинге не просто поменять какой то ресурс а изменить логику. Не так давно Я изменил логику в игре Dick Tracy там при стрельбе если у врага нет оружия отнимается хит у игрока. И пришлось изменить только аргумент вместо того что бы убрать вызов сабрутины отнимающего жизнь у игрока.
В качестве заключения приведу ссылку на документацию которую я использовал при написание скрипта:
http://emuverse.ru/wiki/MOS_Technology_6502/Система_команд#ADC - документация по командам 6502
