dcc6502/dcc6502.c

791 lines
29 KiB
C

/**********************************************************************************
* 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. *
**********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <errno.h>
#define VERSION_INFO "v2.1"
#define NUMBER_OPCODES 151
/* Exceptions for cycle counting */
#define CYCLES_CROSS_PAGE_ADDS_ONE (1 << 0)
#define CYCLES_BRANCH_TAKEN_ADDS_ONE (1 << 1)
/* 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;
/** Some compilers don't have EOK in errno.h */
#ifndef EOK
#define EOK 0
#endif
typedef struct opcode_s {
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 cycles_exceptions; /* Mask of cycle-counting exceptions */
} opcode_t;
typedef struct options_s {
char *filename; /* Input filename */
int nes_mode; /* 1 if NES commenting and warnings are enabled */
int cycle_counting; /* 1 if we want cycle counting */
int hex_output; /* 1 if hex dump output is desired at beginning of line */
unsigned long max_num_bytes;
uint16_t org; /* Origin of addresses */
long offset; /* File offset to start disassembly from */
} options_t;
/* Opcode table */
static opcode_t g_opcode_table[NUMBER_OPCODES] = {
{0x69, "ADC", IMMED, 2, 0}, /* ADC */
{0x65, "ADC", ZEROP, 3, 0},
{0x75, "ADC", ZEPIX, 4, 0},
{0x6D, "ADC", ABSOL, 4, 0},
{0x7D, "ADC", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x79, "ADC", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x61, "ADC", INDIN, 6, 0},
{0x71, "ADC", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x29, "AND", IMMED, 2, 0}, /* AND */
{0x25, "AND", ZEROP, 3, 0},
{0x35, "AND", ZEPIX, 4, 0},
{0x2D, "AND", ABSOL, 4, 0},
{0x3D, "AND", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x39, "AND", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x21, "AND", INDIN, 6, 0},
{0x31, "AND", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x0A, "ASL", ACCUM, 2, 0}, /* ASL */
{0x06, "ASL", ZEROP, 5, 0},
{0x16, "ASL", ZEPIX, 6, 0},
{0x0E, "ASL", ABSOL, 6, 0},
{0x1E, "ASL", ABSIX, 7, 0},
{0x90, "BCC", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BCC */
{0xB0, "BCS", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BCS */
{0xF0, "BEQ", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BEQ */
{0x24, "BIT", ZEROP, 3, 0}, /* BIT */
{0x2C, "BIT", ABSOL, 4, 0},
{0x30, "BMI", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BMI */
{0xD0, "BNE", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BNE */
{0x10, "BPL", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BPL */
{0x00, "BRK", IMPLI, 7, 0}, /* BRK */
{0x50, "BVC", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BVC */
{0x70, "BVS", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BVS */
{0x18, "CLC", IMPLI, 2, 0}, /* CLC */
{0xD8, "CLD", IMPLI, 2, 0}, /* CLD */
{0x58, "CLI", IMPLI, 2, 0}, /* CLI */
{0xB8, "CLV", IMPLI, 2, 0}, /* CLV */
{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, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xD9, "CMP", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xC1, "CMP", INDIN, 6, 0},
{0xD1, "CMP", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xE0, "CPX", IMMED, 2, 0}, /* CPX */
{0xE4, "CPX", ZEROP, 3, 0},
{0xEC, "CPX", ABSOL, 4, 0},
{0xC0, "CPY", IMMED, 2, 0}, /* CPY */
{0xC4, "CPY", ZEROP, 3, 0},
{0xCC, "CPY", ABSOL, 4, 0},
{0xC6, "DEC", ZEROP, 5, 0}, /* DEC */
{0xD6, "DEC", ZEPIX, 6, 0},
{0xCE, "DEC", ABSOL, 6, 0},
{0xDE, "DEC", ABSIX, 7, 0},
{0xCA, "DEX", IMPLI, 2, 0}, /* DEX */
{0x88, "DEY", IMPLI, 2, 0}, /* DEY */
{0x49, "EOR", IMMED, 2, 0}, /* EOR */
{0x45, "EOR", ZEROP, 3, 0},
{0x55, "EOR", ZEPIX, 4, 0},
{0x4D, "EOR", ABSOL, 4, 0},
{0x5D, "EOR", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x59, "EOR", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x41, "EOR", INDIN, 6, 1},
{0x51, "EOR", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xE6, "INC", ZEROP, 5, 0}, /* INC */
{0xF6, "INC", ZEPIX, 6, 0},
{0xEE, "INC", ABSOL, 6, 0},
{0xFE, "INC", ABSIX, 7, 0},
{0xE8, "INX", IMPLI, 2, 0}, /* INX */
{0xC8, "INY", IMPLI, 2, 0}, /* INY */
{0x4C, "JMP", ABSOL, 3, 0}, /* JMP */
{0x6C, "JMP", INDIA, 5, 0},
{0x20, "JSR", ABSOL, 6, 0}, /* JSR */
{0xA9, "LDA", IMMED, 2, 0}, /* LDA */
{0xA5, "LDA", ZEROP, 3, 0},
{0xB5, "LDA", ZEPIX, 4, 0},
{0xAD, "LDA", ABSOL, 4, 0},
{0xBD, "LDA", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xB9, "LDA", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xA1, "LDA", INDIN, 6, 0},
{0xB1, "LDA", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xA2, "LDX", IMMED, 2, 0}, /* LDX */
{0xA6, "LDX", ZEROP, 3, 0},
{0xB6, "LDX", ZEPIY, 4, 0},
{0xAE, "LDX", ABSOL, 4, 0},
{0xBE, "LDX", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xA0, "LDY", IMMED, 2, 0}, /* LDY */
{0xA4, "LDY", ZEROP, 3, 0},
{0xB4, "LDY", ZEPIX, 4, 0},
{0xAC, "LDY", ABSOL, 4, 0},
{0xBC, "LDY", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x4A, "LSR", ACCUM, 2, 0}, /* LSR */
{0x46, "LSR", ZEROP, 5, 0},
{0x56, "LSR", ZEPIX, 6, 0},
{0x4E, "LSR", ABSOL, 6, 0},
{0x5E, "LSR", ABSIX, 7, 0},
{0xEA, "NOP", IMPLI, 2, 0}, /* NOP */
{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, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x19, "ORA", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x01, "ORA", INDIN, 6, 0},
{0x11, "ORA", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x48, "PHA", IMPLI, 3, 0}, /* PHA */
{0x08, "PHP", IMPLI, 3, 0}, /* PHP */
{0x68, "PLA", IMPLI, 4, 0}, /* PLA */
{0x28, "PLP", IMPLI, 4, 0}, /* PLP */
{0x2A, "ROL", ACCUM, 2, 0}, /* ROL */
{0x26, "ROL", ZEROP, 5, 0},
{0x36, "ROL", ZEPIX, 6, 0},
{0x2E, "ROL", ABSOL, 6, 0},
{0x3E, "ROL", ABSIX, 7, 0},
{0x6A, "ROR", ACCUM, 2, 0}, /* ROR */
{0x66, "ROR", ZEROP, 5, 0},
{0x76, "ROR", ZEPIX, 6, 0},
{0x6E, "ROR", ABSOL, 6, 0},
{0x7E, "ROR", ABSIX, 7, 0},
{0x40, "RTI", IMPLI, 6, 0}, /* RTI */
{0x60, "RTS", IMPLI, 6, 0}, /* RTS */
{0xE9, "SBC", IMMED, 2, 0}, /* SBC */
{0xE5, "SBC", ZEROP, 3, 0},
{0xF5, "SBC", ZEPIX, 4, 0},
{0xED, "SBC", ABSOL, 4, 0},
{0xFD, "SBC", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xF9, "SBC", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0xE1, "SBC", INDIN, 6, 0},
{0xF1, "SBC", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x38, "SEC", IMPLI, 2, 0}, /* SEC */
{0xF8, "SED", IMPLI, 2, 0}, /* SED */
{0x78, "SEI", IMPLI, 2, 0}, /* SEI */
{0x85, "STA", ZEROP, 3, 0}, /* STA */
{0x95, "STA", ZEPIX, 4, 0},
{0x8D, "STA", ABSOL, 4, 0},
{0x9D, "STA", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x99, "STA", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x81, "STA", INDIN, 6, 0},
{0x91, "STA", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE},
{0x86, "STX", ZEROP, 3, 0}, /* STX */
{0x96, "STX", ZEPIY, 4, 0},
{0x8E, "STX", ABSOL, 4, 0},
{0x84, "STY", ZEROP, 3, 0}, /* STY */
{0x94, "STY", ZEPIX, 4, 0},
{0x8C, "STY", ABSOL, 4, 0},
{0xAA, "TAX", IMPLI, 2, 0}, /* TAX */
{0xA8, "TAY", IMPLI, 2, 0}, /* TAY */
{0xBA, "TSX", IMPLI, 2, 0}, /* TSX */
{0x8A, "TXA", IMPLI, 2, 0}, /* TXA */
{0x9A, "TXS", IMPLI, 2, 0}, /* TXS */
{0x98, "TYA", IMPLI, 2, 0} /* TYA */
};
/* This function emits a comment header with information about the file
being disassembled */
static void emit_header(options_t *options, int fsize) {
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");
fprintf(stdout, "; FILENAME: %s, File Size: %d, ORG: $%04X\n", options->filename, fsize, options->org);
if (options->hex_output) fprintf(stdout, "; -> Hex output enabled\n");
if (options->cycle_counting) fprintf(stdout, "; -> Cycle counting enabled\n");
if (options->nes_mode) fprintf(stdout, "; -> NES mode enabled\n");
fprintf(stdout, ";---------------------------------------------------------------------------\n");
}
/* This function appends cycle counting to the comment block. See following
* for methods used:
* "Nick Bensema's Guide to Cycle Counting on the Atari 2600"
* http://www.alienbill.com/2600/cookbook/cycles/nickb.txt
*/
static char *append_cycle(char *input, uint8_t entry, uint16_t pc, uint16_t new_pc) {
char tmpstr[256];
int cycles = g_opcode_table[entry].cycles;
int exceptions = g_opcode_table[entry].cycles_exceptions;
int crosses_page = ((pc & 0xff00u) != (new_pc & 0xff00u)) ? 1 : 0;
// On some exceptional conditions, instruction will take an extra cycle, or even two
if (exceptions != 0) {
if ((exceptions & CYCLES_BRANCH_TAKEN_ADDS_ONE) && (exceptions & CYCLES_CROSS_PAGE_ADDS_ONE)) {
/* Branch case: check for page crossing, since it can be determined
* statically from the relative offset and current PC.
*/
if (crosses_page) {
/* Crosses page, always at least 1 extra cycle, two times */
sprintf(tmpstr, " Cycles: %d/%d", cycles + 1, cycles + 2);
} else {
/* Does not cross page, maybe one extra cycle if branch taken */
sprintf(tmpstr, " Cycles: %d/%d", cycles, cycles + 1);
}
} else {
/* One exception: two times, can't tell in advance whether page crossing occurs */
sprintf(tmpstr, " Cycles: %d/%d", cycles, cycles + 1);
}
} else {
/* No exceptions, no extra time */
sprintf(tmpstr, " Cycles: %d", cycles);
}
strcat(input, tmpstr);
return (input + strlen(input));
}
static void add_nes_str(char *instr, char *instr2) {
strcat(instr, " [NES] ");
strcat(instr, instr2);
}
/* This function put NES-specific info in the comment block */
static void append_nes(char *input, uint16_t arg) {
switch(arg) {
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;
}
}
/* Helper macros for disassemble() function */
#define DUMP_FORMAT (options->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))
/* This function disassembles the opcode at the PC and outputs it in *output */
static void disassemble(char *output, uint8_t *buffer, options_t *options, uint16_t *pc) {
char opcode_repr[256], hex_dump[256];
int opcode_idx;
int len = 0;
int entry = 0;
int found = 0;
uint8_t byte_operand;
uint16_t word_operand = 0;
uint16_t current_addr = *pc;
uint8_t opcode = buffer[current_addr];
const char *mnemonic;
opcode_repr[0] = '\0';
hex_dump[0] = '\0';
// Linear search for opcode
for (opcode_idx = 0; opcode_idx < NUMBER_OPCODES; opcode_idx++) {
if (opcode == g_opcode_table[opcode_idx].number) {
/* Found the opcode, record its table index */
found = 1;
entry = opcode_idx;
}
}
// For opcode not found, terminate early
if (!found) {
sprintf(opcode_repr, ".byte $%02X", opcode);
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X:", current_addr, opcode);
sprintf(output, "%-16s%-16s; INVALID OPCODE !!!\n", hex_dump, opcode_repr);
} else {
sprintf(hex_dump, "$%04X", current_addr);
sprintf(output, "%-8s%-16s; INVALID OPCODE !!!\n", hex_dump, opcode_repr);
}
return;
}
// Opcode found in table: disassemble properly according to addressing mode
mnemonic = g_opcode_table[entry].mnemonic;
// 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 (g_opcode_table[entry].addressing) {
case IMMED:
/* Get immediate value operand */
byte_operand = buffer[*pc + 1];
*pc += 1;
sprintf(opcode_repr, "%s #$%02X", mnemonic, byte_operand);
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
}
break;
case ABSOL:
/* Get absolute address operand */
word_operand = LOAD_WORD(buffer, *pc);
*pc += 2;
sprintf(opcode_repr, "%s $%02X%02X", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand));
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X%02X:", current_addr, opcode, LOW_PART(word_operand), HIGH_PART(word_operand));
}
break;
case ZEROP:
/* Get zero page address */
byte_operand = buffer[*pc + 1];
*pc += 1;
sprintf(opcode_repr, "%s $%02X", mnemonic, byte_operand);
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
}
break;
case IMPLI:
sprintf(opcode_repr, "%s", mnemonic);
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X:", current_addr, opcode);
}
break;
case INDIA:
/* Get indirection address */
word_operand = LOAD_WORD(buffer, *pc);
*pc += 2;
sprintf(opcode_repr, "%s ($%02X%02X)", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand));
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X%02X:", current_addr, opcode, LOW_PART(word_operand), HIGH_PART(word_operand));
}
break;
case ABSIX:
/* Get base address */
word_operand = LOAD_WORD(buffer, *pc);
*pc += 2;
sprintf(opcode_repr, "%s $%02X%02X,X", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand));
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X%02X:", current_addr, opcode, LOW_PART(word_operand), HIGH_PART(word_operand));
}
break;
case ABSIY:
/* Get baser address */
word_operand = LOAD_WORD(buffer, *pc);
*pc += 2;
sprintf(opcode_repr, "%s $%02X%02X,Y", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand));
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X%02X:", current_addr, opcode, LOW_PART(word_operand), HIGH_PART(word_operand));
}
break;
case ZEPIX:
/* Get zero-page base address */
byte_operand = buffer[*pc + 1];
*pc += 1;
sprintf(opcode_repr, "%s $%02X,X", mnemonic, byte_operand);
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
}
break;
case ZEPIY:
/* Get zero-page base address */
byte_operand = buffer[*pc + 1];
*pc += 1;
sprintf(opcode_repr, "%s $%02X,Y", mnemonic, byte_operand);
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
}
break;
case INDIN:
/* Get zero-page base address */
byte_operand = buffer[*pc + 1];
*pc += 1;
sprintf(opcode_repr, "%s ($%02X,X)", mnemonic, byte_operand);
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
}
break;
case ININD:
/* Get zero-page base address */
byte_operand = buffer[*pc + 1];
*pc += 1;
sprintf(opcode_repr, "%s ($%02X),Y", mnemonic, byte_operand);
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
}
break;
case RELAT:
/* Get relative modifier */
byte_operand = buffer[*pc + 1];
*pc += 1;
// Compute displacement from first byte after full instruction.
word_operand = current_addr + 2;
if (byte_operand > 0x7Fu) {
word_operand -= ((~byte_operand & 0x7Fu) + 1);
} else {
word_operand += byte_operand & 0x7Fu;
}
sprintf(opcode_repr, "%s $%04X", mnemonic, word_operand);
if (options->hex_output) {
sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand);
}
break;
case ACCUM:
sprintf(opcode_repr, "%s A", mnemonic);
if (options->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;
}
// Emit disassembly line content, prior to annotation comments
len = sprintf(output, DUMP_FORMAT, hex_dump, opcode_repr);
output += len;
/* Add cycle count if necessary */
if (options->cycle_counting) {
output = append_cycle(output, entry, *pc + 1, word_operand);
}
/* Add NES port info if necessary */
switch (g_opcode_table[entry].addressing) {
case ABSOL:
case ABSIX:
case ABSIY:
if (options->nes_mode) {
append_nes(output, word_operand);
}
break;
default:
/* Other addressing modes: not enough info to add NES register annotation */
break;
}
}
static 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");
}
static void usage(void) {
fprintf(stderr, "\nUsage: dcc6502 [options] FILENAME\n");
fprintf(stderr, " -?/-h : Show this help message\n");
fprintf(stderr, " -o ORIGIN : Set the origin (base address of disassembly) [default: 0x8000]\n");
fprintf(stderr, " -m NUM_BYTES : Only disassemble the first NUM_BYTES bytes\n");
fprintf(stderr, " -s NUM_BYTES : Disassemble after skipping NUM_BYTES from start of input file\n");
fprintf(stderr, " -d : Enable hex dump within disassembly\n");
fprintf(stderr, " -n : Enable NES register annotations\n");
fprintf(stderr, " -v : Get only version information\n");
fprintf(stderr, " -c : Enable cycle counting annotations\n");
fprintf(stderr, "\n");
}
static int str_arg_to_ulong(char *str, unsigned long *value) {
uint32_t tmp = 0;
char *endptr;
errno = EOK;
tmp = strtoul(str, &endptr, 0);
/* In case of conversion error, return error indication */
if ((EOK != errno) || (*endptr != '\0')) {
return 0;
} else {
*value = tmp;
return 1;
}
}
static void usage_and_exit(int exit_code, const char *message) {
version();
usage();
if (NULL != message) {
fprintf(stderr, "%s\n", message);
}
exit(exit_code);
}
static void parse_args(int argc, char *argv[], options_t *options) {
int arg_idx = 1;
unsigned long tmp_value;
options->cycle_counting = 0;
options->hex_output = 0;
options->nes_mode = 0;
options->org = 0x8000;
options->max_num_bytes = 65536;
options->offset = 0;
while (arg_idx < argc) {
/* First non-dash-starting argument is assumed to be filename */
if (argv[arg_idx][0] != '-') {
break;
}
/* Got a switch, process it */
switch (argv[arg_idx][1]) {
case 'h':
case '?':
usage_and_exit(0, NULL);
break;
case 'n':
options->nes_mode = 1;
break;
case 'c':
options->cycle_counting = 1;
break;
case 'd':
options->hex_output = 1;
break;
case 'v':
version();
exit(0);
break;
case 'o':
if ((arg_idx == (argc - 1)) || (argv[arg_idx + 1][0] == '-')) {
usage_and_exit(1, "Missing argument to -o switch");
}
/* Get argument and parse it */
arg_idx++;
if (!str_arg_to_ulong(argv[arg_idx], &tmp_value)) {
usage_and_exit(1, "Invalid argument to -o switch");
}
options->org = (uint16_t)(tmp_value & 0xFFFFu);
break;
case 'm':
if ((arg_idx == (argc - 1)) || (argv[arg_idx + 1][0] == '-')) {
usage_and_exit(1, "Missing argument to -m switch");
}
/* Get argument and parse it */
arg_idx++;
if (!str_arg_to_ulong(argv[arg_idx], &tmp_value)) {
usage_and_exit(1, "Invalid argument to -m switch");
}
options->max_num_bytes = tmp_value;
break;
case 's':
if ((arg_idx == (argc - 1)) || (argv[arg_idx + 1][0] == '-')) {
usage_and_exit(1, "Missing argument to -s switch");
}
/* Get argument and parse it */
arg_idx++;
if (!str_arg_to_ulong(argv[arg_idx], &tmp_value)) {
usage_and_exit(1, "Invalid argument to -s switch");
}
options->offset = (long)tmp_value;
break;
default:
version();
usage();
fprintf(stderr, "Unrecognized switch: %s\n", argv[arg_idx]);
exit(1);
}
arg_idx++;
}
/* Make sure we have a filename left to take after we stopped parsing switches */
if (arg_idx >= argc) {
usage_and_exit(1, "Missing filename from command line");
}
options->filename = argv[arg_idx];
}
int main(int argc, char *argv[]) {
int byte_count = 0;
char tmpstr[512];
uint8_t *buffer; /* Memory buffer */
FILE *input_file; /* Input file */
uint16_t pc; /* Program counter */
options_t options; /* Command-line options parsing results */
int result = 0;
parse_args(argc, argv, &options);
buffer = calloc(1, 65536);
if (NULL == buffer) {
usage_and_exit(3, "Could not allocate disassembly memory buffer.");
}
/* Read file into memory buffer */
input_file = fopen(options.filename, "rb");
if (NULL == input_file) {
version();
fprintf(stderr, "File not found or invalid filename : %s\n", options.filename);
exit(2);
}
if (options.offset) {
result = fseek(input_file, options.offset, SEEK_SET);
if (result < 0) {
fprintf(stderr, "fseek(%s, %ld, SEEK_SET) failed: %s (%d)\n", options.filename, options.offset, strerror(errno), result);
exit(2);
}
}
byte_count = 0;
while(!feof(input_file) && ((options.org + byte_count) <= 0xFFFFu) && (byte_count < options.max_num_bytes)) {
size_t bytes_read = fread(&buffer[options.org + byte_count], 1, 1, input_file);
byte_count += bytes_read;
}
fclose(input_file);
/* Disassemble contents of buffer */
emit_header(&options, byte_count);
pc = options.org;
while((pc <= 0xFFFFu) && ((pc - options.org) < byte_count)) {
disassemble(tmpstr, buffer, &options, &pc);
fprintf(stdout, "%s\n", tmpstr);
pc++;
}
free(buffer);
return 0;
}