1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 #define TITLE "hax65816 - Simple 65816 disassembler"
4 #define COPYR "Copyright (C) 1998,2010 Neill Corlett"
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 //
19 ////////////////////////////////////////////////////////////////////////////////
20 
21 #include "common.h"
22 #include "banner.h"
23 
24 ////////////////////////////////////////////////////////////////////////////////
25 
26 static char aluop[8][4] =
27 {"ora", "and", "eor", "adc", "sta", "lda", "cmp", "sbc" };
28 static char rmwop[8][4] =
29 {"asl", "rol", "lsr", "ror", "???", "???", "dec", "inc" };
30 
31 static int8_t mflag_default = 1;
32 static int8_t xflag_default = 0;
33 static int8_t flag_return = 1;
34 static int8_t flag_guess  = 1;
35 static int8_t flag_follow = 1;
36 
37 static uint32_t dasm_address, ins_address;
38 static int8_t mflag, xflag;
39 
40 static int getbyte(void);
41 
42 ////////////////////////////////////////////////////////////////////////////////
43 
44 static int fetchlist[10];
45 static int unfetchstack[10];
46 static int fetchn, unfetchn;
47 
fetchbyte(void)48 static int fetchbyte(void) {
49     int b;
50     dasm_address++;
51     if(unfetchn) b = unfetchstack[--unfetchn]; else b = getbyte();
52     fetchlist[fetchn++] = b;
53     return b;
54 }
55 
unfetchbyte(int b)56 static void unfetchbyte(int b) {
57     dasm_address--;
58     fetchn--;
59     if(fetchn < 0) fetchn = 0;
60     unfetchstack[unfetchn++] = b;
61 }
62 
63 enum { INS_COLUMN = 23 };
64 
ins(const char * fmt,...)65 static void ins(const char* fmt, ...) {
66     static char outputstr[100];
67     int outputn = INS_COLUMN;
68     va_list ap;
69     va_start(ap, fmt);
70     for(;;) {
71         char c = *fmt++;
72         if(!c) break;
73         if(c == '%') {
74             c = *fmt++;
75             switch(c) {
76             case '%': {
77                 outputstr[outputn++] = '%';
78                 break; }
79             case 'A': {
80                 int n = (va_arg(ap, int) >> 5) & 7;
81                 outputstr[outputn++] = aluop[n][0];
82                 outputstr[outputn++] = aluop[n][1];
83                 outputstr[outputn++] = aluop[n][2];
84                 break; }
85             case 'M': {
86                 int n = (va_arg(ap, int) >> 5) & 7;
87                 outputstr[outputn++] = rmwop[n][0];
88                 outputstr[outputn++] = rmwop[n][1];
89                 outputstr[outputn++] = rmwop[n][2];
90                 break; }
91             case 'B': {
92                 int n = fetchbyte();
93                 outputstr[outputn++] = '$';
94                 if(n < 0) {
95                     outputstr[outputn++] = '-';
96                     outputstr[outputn++] = '-';
97                 } else {
98                     sprintf(
99                         outputstr + outputn,
100                         "%02lX", (unsigned long)n
101                     );
102                     outputn += 2;
103                 }
104                 break; }
105             case 'R': {
106                 int n = fetchbyte();
107                 outputstr[outputn++] = '$';
108                 if(n < 0) {
109                     outputstr[outputn++] = '-';
110                     outputstr[outputn++] = '-';
111                     outputstr[outputn++] = '-';
112                     outputstr[outputn++] = '-';
113                 } else {
114                     n = ((int8_t)(n)) +
115                         dasm_address;
116                     sprintf(
117                         outputstr + outputn,
118                         "%04lX", (unsigned long)(n & 0xFFFF)
119                     );
120                     outputn += 4;
121                 }
122                 break; }
123             case 'Z': {
124                 int n1 = fetchbyte();
125                 int n2 = fetchbyte();
126                 outputstr[outputn++] = '$';
127                 if((n1 < 0) || (n2 < 0)) {
128                     for(n1 = 0; n1 < 6; n1++) {
129                         outputstr[outputn++] = '-';
130                     }
131                 } else {
132                     int n = (n2 << 8) | (n1 & 0xFF);
133                     n = ((int16_t)(n)) +
134                         dasm_address;
135                     sprintf(
136                         outputstr + outputn,
137                         "%04lX", (unsigned long)(n & 0xFFFF)
138                     );
139                     outputn += 6;
140                 }
141                 break; }
142             case 'G': {
143                 int n = va_arg(ap, int);
144                 outputstr[outputn++] = '$';
145                 if(n < 0) {
146                     outputstr[outputn++] = '-';
147                     outputstr[outputn++] = '-';
148                 } else {
149                     sprintf(
150                         outputstr + outputn,
151                         "%02lX", (unsigned long)(n)
152                     );
153                     outputn += 2;
154                 }
155                 break; }
156             case 'W': {
157                 int q, n[2];
158                 n[1] = fetchbyte();
159                 n[0] = fetchbyte();
160                 outputstr[outputn++] = '$';
161                 for(q = 0; q < 2; q++) {
162                     if(n[q] < 0) {
163                         outputstr[outputn++] = '-';
164                         outputstr[outputn++] = '-';
165                     } else {
166                         sprintf(
167                             outputstr + outputn,
168                             "%02lX", (unsigned long)(n[q])
169                         );
170                         outputn += 2;
171                     }
172                 }
173                 break; }
174             case 'L': {
175                 int q, n[3];
176                 n[2] = fetchbyte();
177                 n[1] = fetchbyte();
178                 n[0] = fetchbyte();
179                 outputstr[outputn++] = '$';
180                 for(q = 0; q < 3; q++) {
181                     if(n[q] < 0) {
182                         outputstr[outputn++] = '-';
183                         outputstr[outputn++] = '-';
184                     } else {
185                         sprintf(
186                             outputstr + outputn,
187                             "%02lX", (unsigned long)(n[q])
188                         );
189                         outputn += 2;
190                     }
191                 }
192                 break; }
193             case 'I': case 'X': {
194                 int q, n[3];
195                 int8_t* sflag = (c == 'I') ? (&mflag) : (&xflag);
196                 outputstr[outputn++] = '$';
197                 if(*sflag) {
198                     n[0] = fetchbyte();
199                     n[1] = fetchbyte();
200                     //
201                     // BRK/COP/WDM/STP after this?
202                     //
203                     switch(n[1]) {
204                     case 0x00: case 0x02:
205                     case 0x42: case 0xDB:
206                         if(flag_follow && flag_guess
207                         ) {
208                             (*sflag) = 0;
209                             q = 2;
210                             break;
211                         }
212                     default:
213                         unfetchbyte(n[1]);
214                         q = 1;
215                         break;
216                     }
217                 } else {
218                     n[0] = fetchbyte();
219                     n[1] = fetchbyte();
220                     n[2] = fetchbyte();
221                     //
222                     // BRK/COP/WDM/STP after this?
223                     //
224                     switch(n[2]) {
225                     case 0x00: case 0x02:
226                     case 0x42: case 0xDB:
227                         if(flag_follow && flag_guess
228                         ) {
229                             (*sflag) = 1;
230                             unfetchbyte(n[2]);
231                             unfetchbyte(n[1]);
232                             q = 1;
233                             break;
234                         }
235                     default:
236                         unfetchbyte(n[2]);
237                         q = 2;
238                         break;
239                     }
240                 }
241                 while(q--) {
242                     if(n[q] < 0) {
243                         outputstr[outputn++] = '-';
244                         outputstr[outputn++] = '-';
245                     } else {
246                         sprintf(
247                             outputstr + outputn,
248                             "%02lX", (unsigned long)(n[q])
249                         );
250                         outputn += 2;
251                     }
252                 }
253                 break; }
254             }
255         } else {
256             outputstr[outputn++] = c;
257         }
258     }
259     outputstr[outputn] = 0;
260     sprintf(outputstr, "%02lX/%04lX:",
261         (unsigned long)(ins_address >> 16),
262         (unsigned long)(ins_address & 0xFFFF)
263     );
264     memset(outputstr + 8, ' ', INS_COLUMN - 8);
265 
266     outputn = 9;
267     {   int i;
268         for(i = 0; i < fetchn; i++) {
269             int n = fetchlist[i];
270             if(n < 0) {
271                 outputstr[outputn  ] = '-';
272                 outputstr[outputn+1] = '-';
273             } else {
274                 sprintf(outputstr + outputn, "%02X", n);
275                 outputstr[outputn+2] = ' ';
276             }
277             outputn += 3;
278         }
279     }
280     fetchn = 0;
281     printf("%s\n", outputstr);
282 
283     va_end(ap);
284 }
285 
disassemble_one(void)286 static void disassemble_one(void) {
287     int opcode;
288     ins_address = dasm_address;
289     opcode = fetchbyte();
290     if(opcode < 0) return;
291 
292     if       (opcode == 0x00) { ins("brk %B");
293     } else if(opcode == 0x20) { ins("jsr %W");
294     } else if(opcode == 0x40) { ins("rti"); if(flag_return) { mflag = mflag_default; xflag = xflag_default; }
295     } else if(opcode == 0x60) { ins("rts"); if(flag_return) { mflag = mflag_default; xflag = xflag_default; }
296     } else if(opcode == 0x80) { ins("bra %R");
297     } else if(opcode == 0xA0) { ins("ldy #%X");
298     } else if(opcode == 0xC0) { ins("cpy #%X");
299     } else if(opcode == 0xE0) { ins("cpx #%X");
300     } else if(opcode == 0x10) { ins("bpl %R");
301     } else if(opcode == 0x30) { ins("bmi %R");
302     } else if(opcode == 0x50) { ins("bvc %R");
303     } else if(opcode == 0x70) { ins("bvs %R");
304     } else if(opcode == 0x90) { ins("bcc %R");
305     } else if(opcode == 0xB0) { ins("bcs %R");
306     } else if(opcode == 0xD0) { ins("bne %R");
307     } else if(opcode == 0xF0) { ins("beq %R");
308 
309     } else if((opcode & 0x1F) == 0x01) { ins("%A (%B,x)"  , opcode);
310     } else if((opcode & 0x1F) == 0x11) { ins("%A (%B),y"  , opcode);
311 
312     } else if(opcode == 0x02) { ins("cop %B");
313     } else if(opcode == 0x22) { ins("jsr %L");
314     } else if(opcode == 0x42) { ins("wdm %B");
315     } else if(opcode == 0x62) { ins("per %Z");
316     } else if(opcode == 0x82) { ins("brl %Z");
317     } else if(opcode == 0xA2) { ins("ldx #%X");
318     } else if(opcode == 0xC2) {
319         int n = fetchbyte();
320         if(flag_follow) if(n >= 0) {
321             if(n & 0x10) xflag = 0;
322             if(n & 0x20) mflag = 0;
323         }
324         ins("rep #%G", n);
325     } else if(opcode == 0xE2) {
326         int n = fetchbyte();
327         if(flag_follow) if(n >= 0) {
328             if(n & 0x10) xflag = 1;
329             if(n & 0x20) mflag = 1;
330         }
331         ins("sep #%G", n);
332     } else if((opcode & 0x1F) == 0x12) { ins("%A (%B)"    , opcode);
333 
334     } else if((opcode & 0x1F) == 0x03) { ins("%A %B,s"    , opcode);
335     } else if((opcode & 0x1F) == 0x13) { ins("%A (%B,s),y", opcode);
336 
337     } else if(opcode == 0x04) { ins("tsb %B");
338     } else if(opcode == 0x24) { ins("bit %B");
339     } else if(opcode == 0x44) { ins("mvp %B,%B");
340     } else if(opcode == 0x64) { ins("stz %B");
341     } else if(opcode == 0x84) { ins("sty %B");
342     } else if(opcode == 0xA4) { ins("ldy %B");
343     } else if(opcode == 0xC4) { ins("cpy %B");
344     } else if(opcode == 0xE4) { ins("cpx %B");
345     } else if(opcode == 0x14) { ins("trb %B");
346     } else if(opcode == 0x34) { ins("bit %B,x");
347     } else if(opcode == 0x54) { ins("mvn %B,%B");
348     } else if(opcode == 0x74) { ins("stz %B,x");
349     } else if(opcode == 0x94) { ins("sty %B,x");
350     } else if(opcode == 0xB4) { ins("ldy %B,x");
351     } else if(opcode == 0xD4) { ins("pei (%B)");
352     } else if(opcode == 0xF4) { ins("pea %W");
353 
354     } else if((opcode & 0x1F) == 0x05) { ins("%A %B"      , opcode);
355     } else if((opcode & 0x1F) == 0x15) { ins("%A %B,x"    , opcode);
356 
357     } else if(((opcode & 0x1F) == 0x06) && ((opcode & 0xC0) != 0x80)) {
358         ins("%M %B", opcode);
359     } else if(opcode == 0x86) { ins("stx %B");
360     } else if(opcode == 0xA6) { ins("ldx %B");
361     } else if(((opcode & 0x1F) == 0x16) && ((opcode & 0xC0) != 0x80)) {
362         ins("%M %B,x", opcode);
363     } else if(opcode == 0x96) { ins("stx %B,y");
364     } else if(opcode == 0xB6) { ins("ldx %B,y");
365 
366     } else if((opcode & 0x1F) == 0x07) { ins("%A [%B]"    , opcode);
367     } else if((opcode & 0x1F) == 0x17) { ins("%A [%B],y"  , opcode);
368 
369     } else if(opcode == 0x08) { ins("php");
370     } else if(opcode == 0x28) { ins("plp");
371     } else if(opcode == 0x48) { ins("pha");
372     } else if(opcode == 0x68) { ins("pla");
373     } else if(opcode == 0x88) { ins("dey");
374     } else if(opcode == 0xA8) { ins("tay");
375     } else if(opcode == 0xC8) { ins("iny");
376     } else if(opcode == 0xE8) { ins("inx");
377     } else if(opcode == 0x18) { ins("clc");
378     } else if(opcode == 0x38) { ins("sec");
379     } else if(opcode == 0x58) { ins("cli");
380     } else if(opcode == 0x78) { ins("sei");
381     } else if(opcode == 0x98) { ins("tya");
382     } else if(opcode == 0xB8) { ins("clv");
383     } else if(opcode == 0xD8) { ins("cld");
384     } else if(opcode == 0xF8) { ins("sed");
385 
386     } else if((opcode & 0x1F) == 0x09) {
387         if(opcode == 0x89) ins("bit #%I");
388         else ins("%A #%I", opcode);
389     } else if((opcode & 0x1F) == 0x19) { ins("%A %W,y"    , opcode);
390 
391     } else if(opcode == 0x0A) { ins("asl");
392     } else if(opcode == 0x2A) { ins("rol");
393     } else if(opcode == 0x4A) { ins("lsr");
394     } else if(opcode == 0x6A) { ins("ror");
395     } else if(opcode == 0x8A) { ins("txa");
396     } else if(opcode == 0xAA) { ins("tax");
397     } else if(opcode == 0xCA) { ins("dex");
398     } else if(opcode == 0xEA) { ins("nop");
399     } else if(opcode == 0x1A) { ins("inc");
400     } else if(opcode == 0x3A) { ins("dec");
401     } else if(opcode == 0x5A) { ins("phy");
402     } else if(opcode == 0x7A) { ins("ply");
403     } else if(opcode == 0x9A) { ins("txs");
404     } else if(opcode == 0xBA) { ins("tsx");
405     } else if(opcode == 0xDA) { ins("phx");
406     } else if(opcode == 0xFA) { ins("plx");
407 
408     } else if(opcode == 0x0B) { ins("phd");
409     } else if(opcode == 0x2B) { ins("pld");
410     } else if(opcode == 0x4B) { ins("phk");
411     } else if(opcode == 0x6B) { ins("rtl"); if(flag_return) { mflag = mflag_default; xflag = xflag_default; }
412     } else if(opcode == 0x8B) { ins("phb");
413     } else if(opcode == 0xAB) { ins("plb");
414     } else if(opcode == 0xCB) { ins("wai");
415     } else if(opcode == 0xEB) { ins("xba");
416     } else if(opcode == 0x1B) { ins("tcs");
417     } else if(opcode == 0x3B) { ins("tsc");
418     } else if(opcode == 0x5B) { ins("tcd");
419     } else if(opcode == 0x7B) { ins("tdc");
420     } else if(opcode == 0x9B) { ins("txy");
421     } else if(opcode == 0xBB) { ins("tyx");
422     } else if(opcode == 0xDB) { ins("stp");
423     } else if(opcode == 0xFB) { ins("xce");
424 
425     } else if(opcode == 0x0C) { ins("tsb %W");
426     } else if(opcode == 0x2C) { ins("bit %W");
427     } else if(opcode == 0x4C) { ins("jmp %W");
428     } else if(opcode == 0x6C) { ins("jmp (%W)");
429     } else if(opcode == 0x8C) { ins("sty %W");
430     } else if(opcode == 0xAC) { ins("ldy %W");
431     } else if(opcode == 0xCC) { ins("cpy %W");
432     } else if(opcode == 0xEC) { ins("cpx %W");
433     } else if(opcode == 0x1C) { ins("trb %W");
434     } else if(opcode == 0x3C) { ins("bit %W,x");
435     } else if(opcode == 0x5C) { ins("jmp %L");
436     } else if(opcode == 0x7C) { ins("jmp (%W,x)");
437     } else if(opcode == 0x9C) { ins("stz %W");
438     } else if(opcode == 0xBC) { ins("ldy %W,x");
439     } else if(opcode == 0xDC) { ins("jmp [%W]");
440     } else if(opcode == 0xFC) { ins("jsr (%W,x)");
441 
442     } else if((opcode & 0x1F) == 0x0D) { ins("%A %W"      , opcode);
443     } else if((opcode & 0x1F) == 0x1D) { ins("%A %W,x"    , opcode);
444 
445     } else if(((opcode & 0x1F) == 0x0E) && ((opcode & 0xC0) != 0x80)) {
446         ins("%M %W", opcode);
447     } else if(opcode == 0x8E) { ins("stx %W");
448     } else if(opcode == 0xAE) { ins("ldx %W");
449     } else if(((opcode & 0x1F) == 0x1E) && ((opcode & 0xC0) != 0x80)) {
450         ins("%M %W,x", opcode);
451     } else if(opcode == 0x9E) { ins("stz %W,x");
452     } else if(opcode == 0xBE) { ins("ldx %W,y");
453 
454     } else if((opcode & 0x1F) == 0x0F) { ins("%A %L"      , opcode);
455     } else if((opcode & 0x1F) == 0x1F) { ins("%A %L,x"    , opcode);
456     } else { ins("???"); }
457 }
458 
459 ////////////////////////////////////////////////////////////////////////////////
460 
461 static FILE* infile = NULL;
462 static uint32_t infile_bytes_left;
463 
getbyte(void)464 static int getbyte(void) {
465     if(infile_bytes_left) {
466         int n = fgetc(infile);
467         if(n == EOF) {
468             infile_bytes_left = 0;
469             return -1;
470         } else {
471             infile_bytes_left--;
472             return (n & 0xFF);
473         }
474     }
475     return -1;
476 }
477 
disasm_range(uint32_t fileoffset,uint32_t len,uint32_t addr)478 static void disasm_range(uint32_t fileoffset, uint32_t len, uint32_t addr) {
479     fseeko(infile, fileoffset, SEEK_SET);
480     dasm_address = addr;
481     infile_bytes_left = len;
482     mflag = mflag_default;
483     xflag = xflag_default;
484     fetchn = unfetchn = 0;
485     while(infile_bytes_left > 0) {
486         disassemble_one();
487     }
488 }
489 
gethex(const char * s)490 static uint32_t gethex(const char* s) {
491     if(!s[0]) return 0;
492     if(s[0] == '$') s++;
493     if(s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s += 2;
494     return (uint32_t)strtoul(s, NULL, 16);
495 }
496 
497 ////////////////////////////////////////////////////////////////////////////////
498 
main(int argc,char ** argv)499 int main(int argc, char** argv) {
500     const char* infilename;
501     uint32_t arg_start;
502     uint32_t arg_len;
503     uint32_t arg_addr;
504     int option_argn = 4;
505 
506     normalize_argv0(argv[0]);
507 
508     if(argc < 4) {
509         banner();
510         printf(
511             "Usage: %s imagefile start address [length] [options]\n"
512             "Output is written to stdout. All values must be given in hex.\n"
513             "If no length is given, disassembly will stop at the end of the bank.\n"
514             "Options:\n"
515             "  -m0         Assume M flag = 0\n"
516             "  -m1         Assume M flag = 1 (default)\n"
517             "  -x0         Assume X flag = 0 (default)\n"
518             "  -x1         Assume X flag = 1\n"
519             "  -noreturn   Disable flag reset after RTS/RTL/RTI\n"
520             "  -noguess    Disable flag guess on BRK/COP/WDM/STP\n"
521             "  -nofollow   Disable REP/SEP following (not recommended)\n",
522             argv[0]
523         );
524         return 1;
525     }
526 
527     infilename = argv[1];
528     arg_start = gethex(argv[2]);
529     arg_addr  = gethex(argv[3]);
530 
531     arg_len = ((arg_addr & 0xFFFF) ^ 0xFFFF) + 1;
532 
533     if((argc >= 5) && (argv[4][0] != '-')) {
534         arg_len = gethex(argv[4]);
535         option_argn = 5;
536     }
537 
538     while(option_argn < argc) {
539         const char* s = argv[option_argn];
540         if       (!strcmp(s, "-m0")) { mflag_default = 0;
541         } else if(!strcmp(s, "-m1")) { mflag_default = 1;
542         } else if(!strcmp(s, "-x0")) { xflag_default = 0;
543         } else if(!strcmp(s, "-x1")) { xflag_default = 1;
544         } else if(!strcmp(s, "-noreturn")) { flag_return = 0;
545         } else if(!strcmp(s, "-noguess" )) { flag_guess  = 0;
546         } else if(!strcmp(s, "-nofollow")) { flag_follow = 0;
547         } else {
548             printf("unknown option: %s\n", s);
549             return 1;
550         }
551         option_argn++;
552     }
553 
554     printf(
555         "Disassembly of %s\n"
556         "Starting at offset $%lX for $%lX bytes\n"
557         "65816 address starts at $%lX\n"
558         "return=%s guess=%s follow=%s\n"
559         "\n",
560         infilename,
561         (unsigned long)arg_start,
562         (unsigned long)arg_len,
563         (unsigned long)arg_addr,
564         flag_return ? "on" : "off",
565         flag_guess  ? "on" : "off",
566         flag_follow ? "on" : "off"
567     );
568 
569     infile = fopen(infilename, "rb");
570     if(!infile) {
571         fprintf(stderr, "Error: %s: %s\n", infilename, strerror(errno));
572         return 1;
573     }
574 
575     disasm_range(arg_start, arg_len, arg_addr);
576 
577     fclose(infile);
578 
579     return 0;
580 }
581 
582 ////////////////////////////////////////////////////////////////////////////////
583