Дизассемблер 6502 (nes/famicom/dendy) на php

от автора

Я продолжаю изучать ассемблер 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 а так же наш скрипт результат выполнения следующий.

ld65 дизасемблер

ld65 дизасемблер
наш мини-дизассемблер

наш мини-дизассемблер

Как вы видите код идентичный да da65 генерирует метки для перехода и ZeroPage. Но мы пока разбираемся в корне механизма дизассемблирования.

В плане написать скрипт ассемблера на php из кода который был дизассемблирован. Это даст чуть более широкие возможности в ром хакинге не просто поменять какой то ресурс а изменить логику. Не так давно Я изменил логику в игре Dick Tracy там при стрельбе если у врага нет оружия отнимается хит у игрока. И пришлось изменить только аргумент вместо того что бы убрать вызов сабрутины отнимающего жизнь у игрока.

В качестве заключения приведу ссылку на документацию которую я использовал при написание скрипта:


ссылка на оригинал статьи https://habr.com/ru/articles/741192/


Комментарии

Добавить комментарий

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