diff --git a/dcc6502.c b/dcc6502.c index 48d1654..c06cc31 100644 --- a/dcc6502.c +++ b/dcc6502.c @@ -56,18 +56,26 @@ typedef enum { #define EOK 0 #endif -typedef struct OPcode { +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 cross_page; /* 1 if cross-page boundaries affect cycles */ -} OPcode; +} 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 */ +} options_t; -typedef uint16_t word; /* Opcode table */ -OPcode opcode_table[NUMBER_OPCODES] = { +opcode_t opcode_table[NUMBER_OPCODES] = { {0x69, "ADC", IMMED, 2, 1}, /* ADC */ {0x65, "ADC", ZEROP, 3, 1}, {0x75, "ADC", ZEPIX, 4, 1}, @@ -276,27 +284,15 @@ OPcode opcode_table[NUMBER_OPCODES] = { {0x98, "TYA", IMPLI, 2, 0} /* TYA */ }; -// 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 */ -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 */ -char line[512]; - /* This function emits a comment header with information about the file being disassembled */ - -void emit_header(char *filename, int fsize, uint16_t org) { +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", 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, "; 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"); } @@ -358,33 +354,33 @@ void append_nes(char *input, uint16_t arg) { } } -#define DUMP_FORMAT (hex_output ? "%-16s%-16s;" : "%-8s%-16s;") +#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 */ -void disassemble(char *output) { +void disassemble(char *output, uint8_t *buffer, options_t *options, uint16_t *pc) { char opcode_repr[256], hex_dump[256]; - int i; + int opcode_idx; 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]; + 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 (i = 0; i < NUMBER_OPCODES; i++) { - if (opcode == opcode_table[i].number) { + for (opcode_idx = 0; opcode_idx < NUMBER_OPCODES; opcode_idx++) { + if (opcode == opcode_table[opcode_idx].number) { /* Found the opcode, record its table index */ found = 1; - entry = i; + entry = opcode_idx; } } @@ -392,7 +388,7 @@ void disassemble(char *output) { // For opcode not found, terminate early if (!found) { sprintf(opcode_repr, ".byte $%02x", opcode); - if (hex_output) { + if (options->hex_output) { sprintf(hex_dump, "$%04X> %02X:", current_addr, opcode); sprintf(output, "%-16s%-16s; INVALID OPCODE !!!\n", hex_dump, opcode_repr); } else { @@ -412,125 +408,125 @@ void disassemble(char *output) { switch (opcode_table[entry].addressing) { case IMMED: /* Get immediate value operand */ - byte_operand = buffer[PC + 1]; - PC++; + byte_operand = buffer[*pc + 1]; + *pc += 1; sprintf(opcode_repr, "%s #$%02x", mnemonic, byte_operand); - if (hex_output) { + 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; + word_operand = LOAD_WORD(buffer, *pc); + *pc += 2; sprintf(opcode_repr, "%s $%02X%02X", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand)); - if (hex_output) { + 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++; + byte_operand = buffer[*pc + 1]; + *pc += 1; sprintf(opcode_repr, "%s $%02X", mnemonic, byte_operand); - if (hex_output) { + if (options->hex_output) { sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand); } break; case IMPLI: sprintf(opcode_repr, "%s", mnemonic); - if (hex_output) { + 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; + word_operand = LOAD_WORD(buffer, *pc); + *pc += 2; sprintf(opcode_repr, "%s ($%02X%02X)", mnemonic, HIGH_PART(word_operand), LOW_PART(word_operand)); - if (hex_output) { + 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; + 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 (hex_output) { + 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; + 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 (hex_output) { + 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++; + byte_operand = buffer[*pc + 1]; + *pc += 1; sprintf(opcode_repr, "%s $%02X,X", mnemonic, byte_operand); - if (hex_output) { + 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++; + byte_operand = buffer[*pc + 1]; + *pc += 1; sprintf(opcode_repr, "%s $%02X,Y", mnemonic, byte_operand); - if (hex_output) { + 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++; + byte_operand = buffer[*pc + 1]; + *pc += 1; sprintf(opcode_repr, "%s ($%02X,X)", mnemonic, byte_operand); - if (hex_output) { + 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++; + byte_operand = buffer[*pc + 1]; + *pc += 1; sprintf(opcode_repr, "%s ($%02X),Y", mnemonic, byte_operand); - if (hex_output) { + 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++; + byte_operand = buffer[*pc + 1]; + *pc += 1; // Compute displacement from first byte after full instruction. word_operand = current_addr + 2; @@ -541,14 +537,14 @@ void disassemble(char *output) { } sprintf(opcode_repr, "%s $%04X", mnemonic, word_operand); - if (hex_output) { + 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 (hex_output) { + if (options->hex_output) { sprintf(hex_dump, "$%04X> %02X:", current_addr, opcode); } @@ -562,7 +558,7 @@ void disassemble(char *output) { output += len; /* Add cycle count if necessary */ - if (cycle_counting) { + if (options->cycle_counting) { output = append_cycle(output, entry); } @@ -571,7 +567,7 @@ void disassemble(char *output) { case ABSOL: case ABSIX: case ABSIY: - if (nes_mode) { + if (options->nes_mode) { append_nes(output, word_operand); } break; @@ -587,161 +583,152 @@ void version(void) { fprintf(stderr, "See source on github: https://github.com/tcarmelveilleux/dcc6502.\n"); } -void usage_helper(char *str) { - fprintf(stderr, "\t%s\n", str); -} - -// FIXME: add command line sample -// FIXME: Make these more sane and add option for decimal 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"); + fprintf(stderr, "Usage: dcc6502 [options] FILENAME"); + 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, " -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"); } -uint16_t hex2int (char *str, uint16_t dfl) { +unsigned long str_arg_to_ulong(char *str, unsigned long default_val) { uint32_t tmp = 0; errno = EOK; - tmp = strtoul(str, NULL, 16); + tmp = strtoul(str, NULL, 0); /* In case of conversion error, take default value */ if (EOK != errno) { fprintf(stderr, "WARNING -> error converting %s to a numerical value.", str); - return dfl; + return default_val; } else { - return (uint16_t)(tmp & 0xFFFFu); + return tmp; } } -void set_org(char *str) { - if (strlen(str) < 3) { - fprintf(stderr, "WARNING -> %s is not a valid ORG switch, defaulting to $8000\n", str); - org = 0x8000; - return; +void usage_and_exit(int exit_code, const char *message) { + version(); + usage(); + if (NULL != message) { + fprintf(stderr, "%s\n", message); } - - org = hex2int(&str[2], 0x8000u); + exit(exit_code); } -void set_max(char *str) { - if (strlen(str) < 3) { - max = 0xFFFF-org; - fprintf(stderr, "WARNING -> %s is not a valid MAX switch, defaulting to $%04X\n", str, max); - return; +void parse_args(int argc, char *argv[], options_t *options) { + int arg_idx = 1; + + options->cycle_counting = 0; + options->hex_output = 0; + options->nes_mode = 0; + options->org = 0x8000; + options->max_num_bytes = 65536; + + 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++; + options->org = (uint16_t)(str_arg_to_ulong(argv[arg_idx], 0x8000u) & 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++; + options->max_num_bytes = str_arg_to_ulong(argv[arg_idx], 65536u); + break; + default: + version(); + usage(); + fprintf(stderr, "Unrecognized switch: %s\n", argv[arg_idx]); + exit(1); + } + arg_idx++; } - max = hex2int(&str[2], 0xFFFFu); + /* 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 idx = 0; - char tmpstring[512]; - char filename[512]; + 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 */ - cycle_counting = 0; - hex_output = 0; - org = 0x8000; + parse_args(argc, argv, &options); - if (argc < 2) { + 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(); - usage(); - exit(1); + fprintf(stderr, "File not found or invalid filename : %s\n", options.filename); + exit(2); } - if (argc > 2) { - for (idx = 1; idx < argc - 1; idx++) { - if (argv[idx][0] != '-') { - version(); - usage(); - fprintf(stderr, "Unrecognized switch: %s\n", argv[idx]); - exit(1); - } - switch (argv[idx][1]) { - 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]); - break; - case 'm': - set_max(argv[idx]); - break; - default: - version(); - usage(); - fprintf(stderr, "Unrecognized switch: %s\n", argv[idx]); - exit(1); - } - } - } else { - if (argv[1][0] != '-') { - strncpy(filename, argv[1], 511); - } 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); - - } - } + byte_count = 0; + while(!feof(input_file) && ((options.org + byte_count) <= 0xFFFFu) && (byte_count < options.max_num_bytes)) { + fread(&buffer[options.org + byte_count], 1, 1, input_file); + byte_count++; } - strncpy(filename, argv[argc - 1], 511); + fclose(input_file); - f = fopen(filename, "rb"); - - if (NULL == f) { - version(); - fprintf(stderr, "File not found or invalid filename : %s\n", filename); - exit(1); + /* 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++; } - idx = 0; - while(!feof(f) && ((idx + org) < 65535)) { - fread(&buffer[idx], 1, 1, f); - idx++; - } - - fclose(f); - - emit_header(filename, idx, org); - PC = 0; - while(((PC + org) < 65535) && (PC <= max) && (PC < idx)) { - disassemble(tmpstring); - fprintf(stdout, "%s\n", tmpstring); - PC++; - } + free(buffer); return 0; }