dcc6502/dcc6502.c

748 lines
24 KiB
C
Raw Normal View History

/**********************************************************************************
* dcc6502.c -> Main module of: *
* Disassembler and Cycle Counter for the 6502 microprocessor *
* *
* This code is offered under the MIT License (MIT) *
* *
* Copyright (c) 1998-2014 Tennessee Carmel-Veilleux <veilleux@tentech.ca> *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy *
* of this software and associated documentation files (the "Software"), to deal *
* in the Software without restriction, including without limitation the rights *
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
* copies of the Software, and to permit persons to whom the Software is *
* furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in all *
* copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
* SOFTWARE. *
**********************************************************************************/
2014-07-23 17:52:56 +00:00
#include <stdio.h>
2014-07-23 18:57:21 +00:00
#include <stdlib.h>
2014-07-23 17:52:56 +00:00
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <errno.h>
2014-07-23 17:52:56 +00:00
#define VERSION_INFO "v2.0"
2014-07-23 17:52:56 +00:00
#define NUMBER_OPCODES 151
/* The 6502's 13 addressing modes */
typedef enum {
IMMED = 0, /* Immediate */
ABSOL, /* Absolute */
ZEROP, /* Zero Page */
IMPLI, /* Implied */
INDIA, /* Indirect Absolute */
ABSIX, /* Absolute indexed with X */
ABSIY, /* Absolute indexed with Y */
ZEPIX, /* Zero page indexed with X */
ZEPIY, /* Zero page indexed with Y */
INDIN, /* Indexed indirect (with X) */
ININD, /* Indirect indexed (with Y) */
RELAT, /* Relative */
ACCUM /* Accumulator */
} addressing_mode_e;
2014-07-23 17:52:56 +00:00
/** Some compilers don't have EOK in errno.h */
#ifndef EOK
#define EOK 0
#endif
2014-07-23 17:52:56 +00:00
typedef struct OPcode {
uint8_t number; /* Number of the opcode */
const char *mnemonic; /* Index in the name table */
addressing_mode_e addressing; /* Addressing mode */
unsigned int cycles; /* Number of cycles */
unsigned int cross_page; /* 1 if cross-page boundaries affect cycles */
2014-07-23 17:52:56 +00:00
} OPcode;
typedef uint16_t word;
2014-07-23 17:52:56 +00:00
/* Opcode table */
OPcode opcode_table[NUMBER_OPCODES] = {
{0x69, "ADC", IMMED, 2, 1}, /* ADC */
{0x65, "ADC", ZEROP, 3, 1},
{0x75, "ADC", ZEPIX, 4, 1},
{0x6D, "ADC", ABSOL, 4, 1},
{0x7D, "ADC", ABSIX, 4, 1},
{0x79, "ADC", ABSIY, 4, 1},
{0x61, "ADC", INDIN, 6, 1},
{0x71, "ADC", ININD, 5, 1},
2014-07-23 17:52:56 +00:00
{0x29, "AND", IMMED, 2, 1}, /* AND */
{0x25, "AND", ZEROP, 3, 1},
{0x35, "AND", ZEPIX, 4, 1},
{0x2D, "AND", ABSOL, 4, 1},
{0x3D, "AND", ABSIX, 4, 1},
{0x39, "AND", ABSIY, 4, 1},
{0x21, "AND", INDIN, 6, 1},
{0x31, "AND", ININD, 5, 1},
2014-07-23 17:52:56 +00:00
{0x0A, "ASL", ACCUM, 2, 0}, /* ASL */
{0x06, "ASL", ZEROP, 5, 0},
{0x16, "ASL", ZEPIX, 6, 0},
{0x0E, "ASL", ABSOL, 6, 0},
{0x1E, "ASL", ABSIX, 6, 0},
2014-07-23 18:57:21 +00:00
{0x90, "BCC", RELAT, 4, 1}, /* BCC */
2014-07-23 18:57:21 +00:00
{0xB0, "BCS", RELAT, 4, 1}, /* BCS */
2014-07-23 18:57:21 +00:00
{0xF0, "BEQ", RELAT, 4, 1}, /* BEQ */
2014-07-23 18:57:21 +00:00
{0x24, "BIT", ZEROP, 3, 0}, /* BIT */
{0x2C, "BIT", ABSOL, 4, 0},
2014-07-23 18:57:21 +00:00
{0x30, "BMI", RELAT, 4, 1}, /* BMI */
2014-07-23 18:57:21 +00:00
{0xD0, "BNE", RELAT, 4, 1}, /* BNE */
2014-07-23 18:57:21 +00:00
{0x10, "BPL", RELAT, 4, 1}, /* BPL */
2014-07-23 18:57:21 +00:00
{0x00, "BRK", IMPLI, 7, 0}, /* BRK */
2014-07-23 18:57:21 +00:00
{0x50, "BVC", RELAT, 4, 1}, /* BVC */
2014-07-23 18:57:21 +00:00
{0x70, "BVS", RELAT, 4, 1}, /* BVS */
2014-07-23 18:57:21 +00:00
{0x18, "CLC", IMPLI, 2, 0}, /* CLC */
2014-07-23 18:57:21 +00:00
{0xD8, "CLD", IMPLI, 2, 0}, /* CLD */
2014-07-23 18:57:21 +00:00
{0x58, "CLI", IMPLI, 2, 0}, /* CLI */
2014-07-23 18:57:21 +00:00
{0xB8, "CLV", IMPLI, 2, 0}, /* CLV */
2014-07-23 18:57:21 +00:00
{0xC9, "CMP", IMMED, 2, 0}, /* CMP */
{0xC5, "CMP", ZEROP, 3, 0},
{0xD5, "CMP", ZEPIX, 4, 0},
{0xCD, "CMP", ABSOL, 4, 0},
{0xDD, "CMP", ABSIX, 4, 0},
{0xD9, "CMP", ABSIY, 4, 0},
{0xC1, "CMP", INDIN, 6, 0},
{0xD1, "CMP", ININD, 5, 0},
2014-07-23 18:57:21 +00:00
{0xE0, "CPX", IMMED, 2, 0}, /* CPX */
{0xE4, "CPX", ZEROP, 3, 0},
{0xEC, "CPX", ABSOL, 4, 0},
2014-07-23 18:57:21 +00:00
{0xC0, "CPY", IMMED, 2, 0}, /* CPY */
{0xC4, "CPY", ZEROP, 3, 0},
{0xCC, "CPY", ABSOL, 4, 0},
2014-07-23 18:57:21 +00:00
{0xC6, "DEC", ZEROP, 5, 0}, /* DEC */
{0xD6, "DEC", ZEPIX, 6, 0},
{0xCE, "DEC", ABSOL, 6, 0},
{0xDE, "DEC", ABSIX, 6, 0},
2014-07-23 18:57:21 +00:00
{0xCA, "DEX", IMPLI, 2, 0}, /* DEX */
2014-07-23 18:57:21 +00:00
{0x88, "DEY", IMPLI, 2, 0}, /* DEY */
2014-07-23 18:57:21 +00:00
{0x49, "EOR", IMMED, 2, 1}, /* EOR */
{0x45, "EOR", ZEROP, 3, 1},
{0x55, "EOR", ZEPIX, 4, 1},
{0x4D, "EOR", ABSOL, 4, 1},
{0x5D, "EOR", ABSIX, 4, 1},
{0x59, "EOR", ABSIY, 4, 1},
{0x41, "EOR", INDIN, 6, 1},
{0x51, "EOR", ININD, 5, 1},
2014-07-23 18:57:21 +00:00
{0xE6, "INC", ZEROP, 5, 0}, /* INC */
{0xF6, "INC", ZEPIX, 6, 0},
{0xEE, "INC", ABSOL, 6, 0},
{0xFE, "INC", ABSIX, 6, 0},
2014-07-23 18:57:21 +00:00
{0xE8, "INX", IMPLI, 2, 0}, /* INX */
2014-07-23 18:57:21 +00:00
{0xC8, "INY", IMPLI, 2, 0}, /* INY */
2014-07-23 18:57:21 +00:00
{0x4C, "JMP", ABSOL, 3, 0}, /* JMP */
{0x6C, "JMP", INDIA, 5, 0},
2014-07-23 18:57:21 +00:00
{0x20, "JSR", ABSOL, 6, 0}, /* JSR */
2014-07-23 18:57:21 +00:00
{0xA9, "LDA", IMMED, 2, 1}, /* LDA */
{0xA5, "LDA", ZEROP, 3, 1},
{0xB5, "LDA", ZEPIX, 4, 1},
{0xAD, "LDA", ABSOL, 4, 1},
{0xBD, "LDA", ABSIX, 4, 1},
{0xB9, "LDA", ABSIY, 4, 1},
{0xA1, "LDA", INDIN, 6, 1},
{0xB1, "LDA", ININD, 5, 1},
2014-07-23 18:57:21 +00:00
{0xA2, "LDX", IMMED, 2, 1}, /* LDX */
{0xA6, "LDX", ZEROP, 3, 1},
{0xB6, "LDX", ZEPIY, 4, 1},
{0xAE, "LDX", ABSOL, 4, 1},
{0xBE, "LDX", ABSIY, 4, 1},
2014-07-23 18:57:21 +00:00
{0xA0, "LDY", IMMED, 2, 1}, /* LDY */
{0xA4, "LDY", ZEROP, 3, 1},
{0xB4, "LDY", ZEPIX, 4, 1},
{0xAC, "LDY", ABSOL, 4, 1},
{0xBC, "LDY", ABSIX, 4, 1},
2014-07-23 18:57:21 +00:00
{0x4A, "LSR", ACCUM, 2, 0}, /* LSR */
{0x46, "LSR", ZEROP, 5, 0},
{0x56, "LSR", ZEPIX, 6, 0},
{0x4E, "LSR", ABSOL, 6, 0},
{0x5E, "LSR", ABSIX, 6, 0},
2014-07-23 18:57:21 +00:00
{0xEA, "NOP", IMPLI, 2, 0}, /* NOP */
2014-07-23 18:57:21 +00:00
{0x09, "ORA", IMMED, 2, 0}, /* ORA */
{0x05, "ORA", ZEROP, 3, 0},
{0x15, "ORA", ZEPIX, 4, 0},
{0x0D, "ORA", ABSOL, 4, 0},
{0x1D, "ORA", ABSIX, 4, 0},
{0x19, "ORA", ABSIY, 4, 0},
{0x01, "ORA", INDIN, 6, 0},
{0x11, "ORA", ININD, 5, 0},
2014-07-23 18:57:21 +00:00
{0x48, "PHA", IMPLI, 3, 0}, /* PHA */
2014-07-23 18:57:21 +00:00
{0x08, "PHP", IMPLI, 3, 0}, /* PHP */
2014-07-23 18:57:21 +00:00
{0x68, "PLA", IMPLI, 4, 0}, /* PLA */
2014-07-23 18:57:21 +00:00
{0x28, "PLP", IMPLI, 4, 0}, /* PLP */
2014-07-23 18:57:21 +00:00
{0x2A, "ROL", ACCUM, 2, 0}, /* ROL */
{0x26, "ROL", ZEROP, 5, 0},
{0x36, "ROL", ZEPIX, 6, 0},
{0x2E, "ROL", ABSOL, 6, 0},
{0x3E, "ROL", ABSIX, 6, 0},
2014-07-23 18:57:21 +00:00
{0x6A, "ROR", ACCUM, 2, 0}, /* ROR */
{0x66, "ROR", ZEROP, 5, 0},
{0x76, "ROR", ZEPIX, 6, 0},
{0x6E, "ROR", ABSOL, 6, 0},
{0x7E, "ROR", ABSIX, 6, 0},
2014-07-23 18:57:21 +00:00
{0x40, "RTI", IMPLI, 6, 0}, /* RTI */
2014-07-23 18:57:21 +00:00
{0x60, "RTS", IMPLI, 6, 0}, /* RTS */
2014-07-23 18:57:21 +00:00
{0xE9, "SBC", IMMED, 2, 1}, /* SBC */
{0xE5, "SBC", ZEROP, 3, 1},
{0xF5, "SBC", ZEPIX, 4, 1},
{0xED, "SBC", ABSOL, 4, 1},
{0xFD, "SBC", ABSIX, 4, 1},
{0xF9, "SBC", ABSIY, 4, 1},
{0xE1, "SBC", INDIN, 6, 1},
{0xF1, "SBC", ININD, 5, 1},
2014-07-23 18:57:21 +00:00
{0x38, "SEC", IMPLI, 2, 0}, /* SEC */
2014-07-23 18:57:21 +00:00
{0xF8, "SED", IMPLI, 2, 0}, /* SED */
2014-07-23 18:57:21 +00:00
{0x78, "SEI", IMPLI, 2, 0}, /* SEI */
2014-07-23 18:57:21 +00:00
{0x85, "STA", ZEROP, 3, 0}, /* STA */
{0x95, "STA", ZEPIX, 4, 0},
{0x8D, "STA", ABSOL, 4, 0},
{0x9D, "STA", ABSIX, 4, 0},
{0x99, "STA", ABSIY, 4, 0},
{0x81, "STA", INDIN, 6, 0},
{0x91, "STA", ININD, 5, 0},
2014-07-23 18:57:21 +00:00
{0x86, "STX", ZEROP, 3, 0}, /* STX */
{0x96, "STX", ZEPIY, 4, 0},
{0x8E, "STX", ABSOL, 4, 0},
2014-07-23 18:57:21 +00:00
{0x84, "STY", ZEROP, 3, 0}, /* STY */
{0x94, "STY", ZEPIX, 4, 0},
{0x8C, "STY", ABSOL, 4, 0},
2014-07-23 18:57:21 +00:00
{0xAA, "TAX", IMPLI, 2, 0}, /* TAX */
2014-07-23 18:57:21 +00:00
{0xA8, "TAY", IMPLI, 2, 0}, /* TAY */
2014-07-23 18:57:21 +00:00
{0xBA, "TSX", IMPLI, 2, 0}, /* TSX */
2014-07-23 18:57:21 +00:00
{0x8A, "TXA", IMPLI, 2, 0}, /* TXA */
2014-07-23 18:57:21 +00:00
{0x9A, "TXS", IMPLI, 2, 0}, /* TXS */
2014-07-23 18:57:21 +00:00
{0x98, "TYA", IMPLI, 2, 0} /* TYA */
2014-07-23 17:52:56 +00:00
};
2014-07-23 18:57:21 +00:00
// FIXME: use g_ nomenclature for globals
uint16_t org; /* Origin of addresses */
int hex_output = 0; /* 1 if hex output is desired at beginning of line */
int cycle_counting = 0; /* 1 if we want cycle counting */
int nes_mode = 0; /* 1 if NES commenting and warnings are enabled */
2014-07-23 17:52:56 +00:00
FILE *f; /* Input file */
uint8_t buffer[0xffff]; /* Memory buffer */
uint16_t PC = 0; /* Program counter */
uint16_t max = 0xffff; /* Maximum number of bytes to disassemble */
2014-07-23 17:52:56 +00:00
char line[512];
/* This function emits a comment header with information about the file
2014-07-24 18:13:24 +00:00
being disassembled */
2014-07-23 17:52:56 +00:00
void emit_header(char *filename, int fsize, uint16_t org) {
2014-07-23 19:07:19 +00:00
fprintf(stdout, "; Source generated by DCC6502 version %s\n", VERSION_INFO);
fprintf(stdout, "; For more info about DCC6502, see https://github.com/tcarmelveilleux/dcc6502\n");
2014-07-23 19:07:19 +00:00
fprintf(stdout, "; FILENAME: %s, File Size: %d, ORG: $%04X\n", filename, fsize, org);
if (hex_output) fprintf(stdout, "; -> Hex output enabled\n");
if (cycle_counting) fprintf(stdout, "; -> Cycle counting enabled\n");
if (nes_mode) fprintf(stdout, "; -> NES mode enabled\n");
fprintf(stdout, ";---------------------------------------------------------------------------\n");
2014-07-23 17:52:56 +00:00
}
2014-07-23 18:57:21 +00:00
2014-07-23 17:52:56 +00:00
/* This function appends cycle counting to the comment block */
char *append_cycle(char *input, uint8_t entry) {
2014-07-23 18:57:21 +00:00
char tmpstr[256];
int cycles = opcode_table[entry].cycles;
2014-07-23 17:52:56 +00:00
// On page boundary crossing, instruction will take an extra cycle
if (opcode_table[entry].cross_page) {
sprintf(tmpstr, " Cycles: %d/%d", cycles, cycles + 1);
} else {
sprintf(tmpstr, " Cycles: %d", cycles);
}
2014-07-23 19:07:19 +00:00
strcat(input, tmpstr);
return (input + strlen(input));
2014-07-23 17:52:56 +00:00
}
void add_nes_str(char *instr, char *instr2) {
2014-07-23 19:07:19 +00:00
strcat(instr, " [NES] ");
strcat(instr, instr2);
2014-07-23 17:52:56 +00:00
}
/* This function put NES-specific info in the comment block */
void append_nes(char *input, uint16_t arg) {
2014-07-23 18:57:21 +00:00
switch(arg) {
2014-07-23 19:07:19 +00:00
case 0x2000: add_nes_str(input, "PPU setup #1"); break;
case 0x2001: add_nes_str(input, "PPU setup #2"); break;
case 0x2002: add_nes_str(input, "PPU status"); break;
case 0x2003: add_nes_str(input, "SPR-RAM address select"); break;
case 0x2004: add_nes_str(input, "SPR-RAM data"); break;
case 0x2005: add_nes_str(input, "PPU scroll"); break;
case 0x2006: add_nes_str(input, "VRAM address select"); break;
case 0x2007: add_nes_str(input, "VRAM data"); break;
case 0x4000: add_nes_str(input, "Audio -> Square 1"); break;
case 0x4001: add_nes_str(input, "Audio -> Square 1"); break;
case 0x4002: add_nes_str(input, "Audio -> Square 1"); break;
case 0x4003: add_nes_str(input, "Audio -> Square 1"); break;
case 0x4004: add_nes_str(input, "Audio -> Square 2"); break;
case 0x4005: add_nes_str(input, "Audio -> Square 2"); break;
case 0x4006: add_nes_str(input, "Audio -> Square 2"); break;
case 0x4007: add_nes_str(input, "Audio -> Square 2"); break;
case 0x4008: add_nes_str(input, "Audio -> Triangle"); break;
case 0x4009: add_nes_str(input, "Audio -> Triangle"); break;
case 0x400a: add_nes_str(input, "Audio -> Triangle"); break;
case 0x400b: add_nes_str(input, "Audio -> Triangle"); break;
case 0x400c: add_nes_str(input, "Audio -> Noise control reg"); break;
case 0x400e: add_nes_str(input, "Audio -> Noise Frequency reg #1"); break;
case 0x400f: add_nes_str(input, "Audio -> Noise Frequency reg #2"); break;
case 0x4010: add_nes_str(input, "Audio -> DPCM control"); break;
case 0x4011: add_nes_str(input, "Audio -> DPCM D/A data"); break;
case 0x4012: add_nes_str(input, "Audio -> DPCM address"); break;
case 0x4013: add_nes_str(input, "Audio -> DPCM data length"); break;
case 0x4014: add_nes_str(input, "Sprite DMA trigger"); break;
case 0x4015: add_nes_str(input, "IRQ status / Sound enable"); break;
case 0x4016: add_nes_str(input, "Joypad & I/O port for port #1"); break;
case 0x4017: add_nes_str(input, "Joypad & I/O port for port #2"); break;
2014-07-23 18:57:21 +00:00
}
2014-07-23 17:52:56 +00:00
}
#define DUMP_FORMAT (hex_output ? "%-16s%-16s;" : "%-8s%-16s;")
#define HIGH_PART(val) (((val) >> 8) & 0xFFu)
#define LOW_PART(val) ((val) & 0xFFu)
#define LOAD_WORD(buffer, current_pc) ((uint16_t)buffer[(current_pc) + 1] | (((uint16_t)buffer[(current_pc) + 2]) << 8))
2014-07-23 17:52:56 +00:00
/* This function disassembles the opcode at the PC and outputs it in *output */
2014-07-23 18:57:21 +00:00
void disassemble(char *output) {
2014-07-24 21:22:40 +00:00
char opcode_repr[256], hex_dump[256];
int i;
2014-07-24 21:22:40 +00:00
int len = 0;
int entry = 0;
int found = 0;
uint8_t byte_operand;
word word_operand = 0;
uint16_t current_addr = org + PC;
uint8_t opcode = buffer[current_addr - org];
const char *mnemonic;
2014-07-23 18:57:21 +00:00
2014-07-24 21:22:40 +00:00
opcode_repr[0] = '\0';
hex_dump[0] = '\0';
2014-07-23 18:57:21 +00:00
2014-07-24 21:22:40 +00:00
// Linear search for opcode
2014-07-23 18:57:21 +00:00
for (i = 0; i < NUMBER_OPCODES; i++) {
if (opcode == opcode_table[i].number) {
2014-07-24 21:22:40 +00:00
/* Found the opcode, record its table index */
found = 1;
entry = i;
2014-07-23 18:57:21 +00:00
}
}
2014-07-24 21:22:40 +00:00
// TODO: Normalize %02x versus %02X
// For opcode not found, terminate early
2014-07-23 18:57:21 +00:00
if (!found) {
sprintf(opcode_repr, ".byte $%02x", opcode);
2014-07-23 18:57:21 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X:", current_addr, opcode);
sprintf(output, "%-16s%-16s; INVALID OPCODE !!!\n", hex_dump, opcode_repr);
2014-07-23 18:57:21 +00:00
} else {
sprintf(hex_dump, "$%04X", current_addr);
sprintf(output, "%-8s%-16s; INVALID OPCODE !!!\n", hex_dump, opcode_repr);
2014-07-23 18:57:21 +00:00
}
2014-07-24 21:22:40 +00:00
return;
}
// Opcode found in table: disassemble properly according to addressing mode
mnemonic = opcode_table[entry].mnemonic;
2014-07-24 21:22:40 +00:00
// Set hex dump to default single address format. Will be overwritten
// by more complex output in case of hex dump mode enabled
sprintf(hex_dump, "$%04X", current_addr);
switch (opcode_table[entry].addressing) {
case IMMED:
/* Get immediate value operand */
byte_operand = buffer[PC + 1];
2014-07-24 21:22:40 +00:00
PC++;
sprintf(opcode_repr, "%s #$%02x", mnemonic, byte_operand);
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
2014-07-24 21:22:40 +00:00
}
break;
case ABSOL:
/* Get absolute address operand */
word_operand = LOAD_WORD(buffer, PC);
2014-07-24 21:22:40 +00:00
PC += 2;
sprintf(opcode_repr, "%s $%02X%02X", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand));
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X%02X:", current_addr, opcode, LOW_PART(word_operand), HIGH_PART(word_operand));
2014-07-24 21:22:40 +00:00
}
break;
case ZEROP:
/* Get zero page address */
byte_operand = buffer[PC + 1];
2014-07-24 21:22:40 +00:00
PC++;
sprintf(opcode_repr, "%s $%02X", mnemonic, byte_operand);
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
2014-07-24 21:22:40 +00:00
}
break;
case IMPLI:
sprintf(opcode_repr, "%s", mnemonic);
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X:", current_addr, opcode);
}
break;
case INDIA:
/* Get indirection address */
word_operand = LOAD_WORD(buffer, PC);
2014-07-24 21:22:40 +00:00
PC += 2;
sprintf(opcode_repr, "%s ($%02X%02X)", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand));
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X%02X:", current_addr, opcode, LOW_PART(word_operand), HIGH_PART(word_operand));
2014-07-24 21:22:40 +00:00
}
break;
case ABSIX:
/* Get base address */
word_operand = LOAD_WORD(buffer, PC);
2014-07-24 21:22:40 +00:00
PC += 2;
sprintf(opcode_repr, "%s $%02X%02X,X", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand));
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X%02X:", current_addr, opcode, LOW_PART(word_operand), HIGH_PART(word_operand));
2014-07-24 21:22:40 +00:00
}
break;
case ABSIY:
/* Get baser address */
word_operand = LOAD_WORD(buffer, PC);
2014-07-24 21:22:40 +00:00
PC += 2;
sprintf(opcode_repr, "%s $%02X%02X,Y", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand));
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X%02X:", current_addr, opcode, LOW_PART(word_operand), HIGH_PART(word_operand));
2014-07-24 21:22:40 +00:00
}
break;
case ZEPIX:
/* Get zero-page base address */
byte_operand = buffer[PC + 1];
2014-07-24 21:22:40 +00:00
PC++;
sprintf(opcode_repr, "%s $%02X,X", mnemonic, byte_operand);
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
2014-07-24 21:22:40 +00:00
}
break;
case ZEPIY:
/* Get zero-page base address */
byte_operand = buffer[PC + 1];
2014-07-24 21:22:40 +00:00
PC++;
sprintf(opcode_repr, "%s $%02X,Y", mnemonic, byte_operand);
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
2014-07-24 21:22:40 +00:00
}
break;
case INDIN:
/* Get zero-page base address */
byte_operand = buffer[PC + 1];
2014-07-24 21:22:40 +00:00
PC++;
sprintf(opcode_repr, "%s ($%02X,X)", mnemonic, byte_operand);
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
2014-07-24 21:22:40 +00:00
}
break;
case ININD:
/* Get zero-page base address */
byte_operand = buffer[PC + 1];
2014-07-24 21:22:40 +00:00
PC++;
sprintf(opcode_repr, "%s ($%02X),Y", mnemonic, byte_operand);
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
2014-07-24 21:22:40 +00:00
}
break;
case RELAT:
/* Get relative modifier */
byte_operand = buffer[PC + 1];
2014-07-24 21:22:40 +00:00
PC++;
// Compute displacement from first byte after full instruction.
word_operand = current_addr + 2;
if (byte_operand > 0x7Fu) {
word_operand -= ((~byte_operand & 0x7Fu) + 1);
2014-07-24 21:22:40 +00:00
} else {
word_operand += byte_operand & 0x7Fu;
2014-07-24 21:22:40 +00:00
}
sprintf(opcode_repr, "%s $%04X", mnemonic, word_operand);
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
2014-07-24 21:22:40 +00:00
}
break;
case ACCUM:
sprintf(opcode_repr, "%s A", mnemonic);
2014-07-24 21:22:40 +00:00
if (hex_output) {
sprintf(hex_dump, "$%04X> %02X:", current_addr, opcode);
}
break;
default:
// Will not happen since each entry in opcode_table has address mode set
break;
}
2014-07-24 21:22:40 +00:00
len = sprintf(output, DUMP_FORMAT, hex_dump, opcode_repr);
output += len;
2014-07-24 21:22:40 +00:00
/* Add cycle count if necessary */
if (cycle_counting) {
output = append_cycle(output, entry);
}
2014-07-23 18:57:21 +00:00
2014-07-24 21:22:40 +00:00
/* Add NES port info if necessary */
switch (opcode_table[entry].addressing) {
case ABSOL:
case ABSIX:
case ABSIY:
if (nes_mode) {
append_nes(output, word_operand);
2014-07-24 21:22:40 +00:00
}
break;
default:
/* Other addressing modes: not enough info to add NES register annotation */
break;
2014-07-23 18:57:21 +00:00
}
2014-07-23 17:52:56 +00:00
}
void version(void) {
fprintf(stderr, "DCC6502 %s (C)1998-2014 Tennessee Carmel-Veilleux <veilleux@tentech.ca>\n", VERSION_INFO);
fprintf(stderr, "This software is licensed under the MIT license. See the LICENSE file.\n");
fprintf(stderr, "See source on github: https://github.com/tcarmelveilleux/dcc6502.\n");
2014-07-23 17:52:56 +00:00
}
void usage_helper(char *str) {
2014-07-23 19:07:19 +00:00
fprintf(stderr, "\t%s\n", str);
2014-07-23 17:52:56 +00:00
}
// FIXME: add command line sample
// FIXME: Make these more sane and add option for decimal
2014-07-23 17:52:56 +00:00
void usage(void) {
usage_helper("-?: Show this help message");
usage_helper("-o ORG: Set the origin to ORG [default: 0x8000]");
usage_helper("-h: Enable hex dump within disassembly");
usage_helper("-m NUM_BYTES: Only disassemble the first NUM_BYTES bytes");
usage_helper("-n: Enable NES register annotations");
usage_helper("-v: Get only version information");
usage_helper("-c: Enable cycle counting annotations");
2014-07-23 19:07:19 +00:00
fprintf(stderr, "\n");
2014-07-23 17:52:56 +00:00
}
uint16_t hex2int (char *str, uint16_t dfl) {
uint32_t tmp = 0;
errno = EOK;
tmp = strtoul(str, NULL, 16);
/* In case of conversion error, take default value */
if (EOK != errno) {
fprintf(stderr, "WARNING -> error converting %s to a numerical value.", str);
return dfl;
} else {
return (uint16_t)(tmp & 0xFFFFu);
2014-07-23 18:57:21 +00:00
}
2014-07-23 17:52:56 +00:00
}
void set_org(char *str) {
if (strlen(str) < 3) {
2014-07-23 19:07:19 +00:00
fprintf(stderr, "WARNING -> %s is not a valid ORG switch, defaulting to $8000\n", str);
2014-07-23 18:57:21 +00:00
org = 0x8000;
return;
}
org = hex2int(&str[2], 0x8000u);
2014-07-23 17:52:56 +00:00
}
void set_max(char *str) {
if (strlen(str) < 3) {
2014-07-23 18:57:21 +00:00
max = 0xFFFF-org;
2014-07-23 19:07:19 +00:00
fprintf(stderr, "WARNING -> %s is not a valid MAX switch, defaulting to $%04X\n", str, max);
2014-07-23 18:57:21 +00:00
return;
}
max = hex2int(&str[2], 0xFFFFu);
2014-07-23 17:52:56 +00:00
}
int main(int argc, char *argv[]) {
int idx = 0;
2014-07-23 18:57:21 +00:00
char tmpstring[512];
char filename[512];
cycle_counting = 0;
hex_output = 0;
org = 0x8000;
if (argc < 2) {
version();
usage();
exit(1);
}
if (argc > 2) {
for (idx = 1; idx < argc - 1; idx++) {
if (argv[idx][0] != '-') {
2014-07-23 18:57:21 +00:00
version();
usage();
fprintf(stderr, "Unrecognized switch: %s\n", argv[idx]);
2014-07-23 18:57:21 +00:00
exit(1);
}
switch (argv[idx][1]) {
2014-07-23 18:57:21 +00:00
case '?':
version();
usage();
exit(0);
break;
case 'n':
nes_mode = 1;
break;
case 'c':
cycle_counting = 1;
break;
case 'h':
hex_output = 1;
break;
case 'v':
version();
exit(0);
break;
case 'o':
set_org(argv[idx]);
2014-07-23 18:57:21 +00:00
break;
case 'm':
set_max(argv[idx]);
2014-07-23 18:57:21 +00:00
break;
default:
version();
usage();
fprintf(stderr, "Unrecognized switch: %s\n", argv[idx]);
2014-07-23 18:57:21 +00:00
exit(1);
}
}
} else {
if (argv[1][0] != '-') {
2014-07-23 19:07:19 +00:00
strncpy(filename, argv[1], 511);
2014-07-23 18:57:21 +00:00
} else {
switch (argv[1][1]) {
case '?':
version();
usage();
exit(0);
break;
case 'v':
version();
exit(0);
break;
default:
version();
usage();
fprintf(stderr, "Unrecognized switch: %s\n", argv[1]);
exit(1);
}
}
}
2014-07-23 19:07:19 +00:00
strncpy(filename, argv[argc - 1], 511);
2014-07-23 18:57:21 +00:00
2014-07-23 19:07:19 +00:00
f = fopen(filename, "rb");
2014-07-23 18:57:21 +00:00
2014-07-23 19:07:19 +00:00
if (NULL == f) {
2014-07-23 18:57:21 +00:00
version();
2014-07-23 19:07:19 +00:00
fprintf(stderr, "File not found or invalid filename : %s\n", filename);
2014-07-23 18:57:21 +00:00
exit(1);
}
idx = 0;
while(!feof(f) && ((idx + org) < 65535)) {
fread(&buffer[idx], 1, 1, f);
idx++;
2014-07-23 18:57:21 +00:00
}
fclose(f);
emit_header(filename, idx, org);
2014-07-23 18:57:21 +00:00
PC = 0;
while(((PC + org) < 65535) && (PC <= max) && (PC < idx)) {
2014-07-23 18:57:21 +00:00
disassemble(tmpstring);
2014-07-23 19:07:19 +00:00
fprintf(stdout, "%s\n", tmpstring);
2014-07-23 18:57:21 +00:00
PC++;
}
return 0;
2014-07-23 17:52:56 +00:00
}