/* ======================================================================== */ /* DIS-1600 Advanced(?) CP-1600 Disassembler. */ /* By Joseph Zbiciak */ /* ------------------------------------------------------------------------ */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation; either version 2 of the License, or */ /* (at your option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ /* General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program; if not, write to the Free Software */ /* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* ------------------------------------------------------------------------ */ /* Copyright (c) 2006, Joseph Zbiciak */ /* ======================================================================== */ #include "dasm/dis1600.h" icartrom_t temp_icart, icart; symtab_t *symtab; /* ======================================================================== */ /* INSTRUCTION MNEMONICS */ /* ======================================================================== */ char *mnemonic[] = { "err!", "HLT", "SDBD", "EIS", "DIS", "TCI", "CLRC", "SETC", "JSR", "JSRE", "JSRD", "J", "JE", "JD", "SWAP", "SLL", "RLC", "SLLC", "SLR", "SAR", "RRC", "SARC", "B", "BC", "BOV", "BPL", "BEQ", "BLT", "BLE", "BUSC", "NOPP", "BNC", "BNOV", "BMI", "BNEQ", "BGE", "BGT", "BESC", "BEXT", "MVO", "MVI", "ADD", "SUB", "CMP", "AND", "XOR", "MVOI", "MVII", "ADDI", "SUBI", "CMPI", "ANDI", "XORI", "MVO@", "MVI@", "ADD@", "SUB@", "CMP@", "AND@", "XOR@", "MOVR", "ADDR", "SUBR", "CMPR", "ANDR", "XORR", "INCR", "DECR", "COMR", "NEGR", "ADCR", "RSWD", "NOP", "SIN", "GSWD", "PSHR", "PULR", "CLRR", "TSTR", "DECLE", "BIDECLE", "STRING", }; mnm_t mnm_dir_2op[8] = { M_err, M_MVO, M_MVI, M_ADD, M_SUB, M_CMP, M_AND, M_XOR }; mnm_t mnm_ind_2op[8] = { M_err, M_MVO_, M_MVI_, M_ADD_, M_SUB_, M_CMP_, M_AND_, M_XOR_ }; mnm_t mnm_imm_2op[8] = { M_err, M_MVOI, M_MVII, M_ADDI, M_SUBI, M_CMPI, M_ANDI, M_XORI }; mnm_t mnm_reg_2op[8] = { M_err, M_err, M_MOVR, M_ADDR, M_SUBR, M_CMPR, M_ANDR, M_XORR }; mnm_t mnm_rot_1op[8] = { M_SWAP, M_SLL, M_RLC, M_SLLC, M_SLR, M_SAR, M_RRC, M_SARC }; mnm_t mnm_reg_1op[8] = { M_err, M_INCR, M_DECR, M_COMR, M_NEGR, M_ADCR, M_err, M_RSWD }; mnm_t mnm_cond_br[16] = { M_B, M_BC, M_BOV, M_BPL, M_BEQ, M_BLT, M_BLE, M_BUSC, M_NOPP, M_BNC, M_BNOV, M_BMI, M_BNEQ, M_BGE, M_BGT, M_BESC }; mnm_t mnm_impl_1op_a[4] = { M_HLT, M_SDBD, M_EIS, M_DIS }; mnm_t mnm_impl_1op_b[4] = { M_err, M_TCI, M_CLRC, M_SETC }; mnm_t mnm_jsr[8] = { M_JSR, M_JSRE, M_JSRD, M_err, M_J, M_JE, M_JD, M_err }; /* ======================================================================== */ /* INSTRUCTION PRINTER TABLE (forward references) */ /* ======================================================================== */ ins_prt_t instr_printer[] = { /* "err!" */ prt_err, /* "HLT", "SDBD", "EIS", "DIS", "TCI", "CLRC", "SETC", */ prt_imp,prt_imp,prt_imp,prt_imp, prt_imp,prt_imp,prt_imp, /* "JSR", "JSRE", "JSRD", "J", "JE", "JD", */ prt_jsr,prt_jsr,prt_jsr, prt_cbr,prt_cbr,prt_cbr, /* "SWAP", "SLL", "RLC", "SLLC", "SLR", "SAR", "RRC", "SARC", */ prt_rot,prt_rot,prt_rot,prt_rot,prt_rot,prt_rot,prt_rot,prt_rot, /* "B", "BC", "BOV", "BPL", "BEQ", "BLT", "BLE", "BUSC", */ prt_cbr,prt_cbr,prt_cbr,prt_cbr,prt_cbr,prt_cbr,prt_cbr,prt_cbr, /* "NOPP", "BNC", "BNOV", "BMI", "BNEQ", "BGE", "BGT", "BESC", */ prt_imp,prt_cbr,prt_cbr,prt_cbr,prt_cbr,prt_cbr,prt_cbr,prt_cbr, /* "BEXT", */ prt_bxt, /* "MVO", "MVI", "ADD", "SUB", "CMP", "AND", "XOR", */ prt_jsr,prt_dir,prt_dir,prt_dir,prt_dir,prt_dir,prt_dir, /* "MVOI", "MVII", "ADDI", "SUBI", "CMPI", "ANDI", "XORI", */ prt_imo,prt_imm,prt_imm,prt_imm,prt_imm,prt_imm,prt_imm, /* "MVO@", "MVI@", "ADD@", "SUB@", "CMP@", "AND@", "XOR@", */ prt_2rg,prt_2rg,prt_2rg,prt_2rg,prt_2rg,prt_2rg,prt_2rg, /* "MOVR", "ADDR", "SUBR", "CMPR", "ANDR", "XORR", */ prt_2rg,prt_2rg,prt_2rg,prt_2rg,prt_2rg,prt_2rg, /* "INCR", "DECR", "COMR", "NEGR", "ADCR", "RSWD", */ prt_1rg,prt_1rg,prt_1rg,prt_1rg,prt_1rg, prt_1rg, /* "NOP", "SIN", "GSWD", */ prt_imp,prt_imp,prt_1rg, /* "PSHR", "PULR", "CLRR", "TSTR" */ prt_1rg,prt_plr,prt_1rg,prt_1rg, /* "DECLE", "BIDECLE", "STRING" */ prt_dcl, prt_bid, prt_str }; /* ======================================================================== */ /* Declare storage for the ROM image. */ /* ======================================================================== */ dis_instr_t instr[65539]; /* Yes, an instr for every address. why not? */ /* ======================================================================== */ /* ======================================================================== */ /* DEFAULT SYMBOL TABLE */ /* ======================================================================== */ /* ======================================================================== */ struct defsym_t defsym[] = { { ".PSG0.chn_a_lo", 0x01F0, 1, 1 }, { ".PSG0.chn_b_lo", 0x01F1, 1, 1 }, { ".PSG0.chn_c_lo", 0x01F2, 1, 1 }, { ".PSG0.envlp_lo", 0x01F3, 1, 1 }, { ".PSG0.chn_a_hi", 0x01F4, 1, 1 }, { ".PSG0.chn_b_hi", 0x01F5, 1, 1 }, { ".PSG0.chn_c_hi", 0x01F6, 1, 1 }, { ".PSG0.envlp_hi", 0x01F7, 1, 1 }, { ".PSG0.chan_enable", 0x01F8, 1, 1 }, { ".PSG0.noise", 0x01F9, 1, 1 }, { ".PSG0.envelope", 0x01FA, 1, 1 }, { ".PSG0.chn_a_vol", 0x01FB, 1, 1 }, { ".PSG0.chn_b_vol", 0x01FC, 1, 1 }, { ".PSG0.chn_c_vol", 0x01FD, 1, 1 }, { ".PSG0.rgt_hand", 0x01FE, 1, 1 }, { ".PSG0.lft_hand", 0x01FF, 1, 1 }, { ".PSG1.chn_a_lo", 0x00F0, 1, 1 }, { ".PSG1.chn_b_lo", 0x00F1, 1, 1 }, { ".PSG1.chn_c_lo", 0x00F2, 1, 1 }, { ".PSG1.envlp_lo", 0x00F3, 1, 1 }, { ".PSG1.chn_a_hi", 0x00F4, 1, 1 }, { ".PSG1.chn_b_hi", 0x00F5, 1, 1 }, { ".PSG1.chn_c_hi", 0x00F6, 1, 1 }, { ".PSG1.envlp_hi", 0x00F7, 1, 1 }, { ".PSG1.chan_enable", 0x00F8, 1, 1 }, { ".PSG1.noise", 0x00F9, 1, 1 }, { ".PSG1.envelope", 0x00FA, 1, 1 }, { ".PSG1.chn_a_vol", 0x00FB, 1, 1 }, { ".PSG1.chn_b_vol", 0x00FC, 1, 1 }, { ".PSG1.chn_c_vol", 0x00FD, 1, 1 }, { ".PSG1.rgt_hand", 0x00FE, 1, 1 }, { ".PSG1.lft_hand", 0x00FF, 1, 1 }, { ".ISRVEC", 0x0100, 2, 1 }, { ".RAM", 0x0100, 0x00F0, 2 }, { ".BTAB", 0x0200, 0x00F0, 2 }, { ".SYS", 0x02F0, 0x0070, 2 }, { ".GRAM", 0x3800, 0x0800, 3 }, { ".GROM", 0x3000, 0x0800, 3 }, { ".STIC.X", 0x0000, 8, 1 }, { ".STIC.Y", 0x0008, 8, 1 }, { ".STIC.A", 0x0010, 8, 1 }, { ".STIC.C", 0x0018, 8, 1 }, { ".STIC.VIDEN", 0x0020, 1, 1 }, { ".STIC.MODE", 0x0021, 1, 1 }, { ".STIC.CS", 0x0028, 4, 1 }, { ".STIC.BORD", 0x002C, 1, 1 }, { ".STIC.HDLY", 0x0030, 1, 1 }, { ".STIC.VDLY", 0x0031, 1, 1 }, { ".STIC.EDGE", 0x0032, 1, 1 }, { ".HEADER", 0x5000, 1, 1 }, { ".ISRRET", 0x1014, 1, 1 }, { ".EXEC", 0x1000, 0x1000, 3 }, { ".UART", 0x00E0, 4, 1 }, { ".ECSRAM", 0x4000, 0x0800, 3 }, { ".IV.ALD", 0x0080, 1, 1 }, { ".IV.FIFO", 0x0081, 1, 1 }, { ".ECSCBL.POLL", 0xCF01, 1, 1 }, { ".ECSCBL", 0xCF00, 0x100, 2 }, { NULL, 0, 0, 0 } }; /* ======================================================================== */ /* MAYBE_DEFSYM -- Helper: Define a symbol if it's not def'd yet */ /* ======================================================================== */ void maybe_defsym(char *sym, uint_32 addr) { uint_32 unused; addr <<= 3; /* -------------------------------------------------------------------- */ /* Make sure there is no other symbol at this address and that this */ /* name isn't already taken. */ /* -------------------------------------------------------------------- */ if (symtab_getsym (symtab, addr, 0, 0) == NULL && /* name not found? */ symtab_getaddr(symtab, sym, &unused) != 0) /* addr not found? */ symtab_defsym (symtab, sym, addr); /* def it! */ } /* ======================================================================== */ /* SETUP_DEFSYM -- Set up the default symbol table. */ /* ======================================================================== */ void setup_defsym(void) { int i, j; int addr; char symbuf[256], *sym; for (i = 0; defsym[i].name; i++) { for (j = 0; j < defsym[i].len; j++) { addr = (defsym[i].addr + j); sym = defsym[i].name; if (defsym[i].len > 1) { sprintf(symbuf, "%s.%.*X", defsym[i].name, defsym[i].width, j); sym = symbuf; } maybe_defsym(sym, addr); } } } /* ======================================================================== */ /* ======================================================================== */ /* ANALYSIS PASSES AND DECODER FUNCTIONS */ /* ======================================================================== */ /* ======================================================================== */ int hlt_is_invalid = 1; int rare_ops_are_invalid = 1; int allow_branch_target_wrap = 0; int allow_branch_to_bad_addr = 0; int suspicious_pc_math_is_invalid = 1; int verbose = 0; int skip_advanced_analysis = 0; int skip_mark_cart_header = 0; int skip_funky_branch_detect = 0; int skip_propagate_invalid = 0; int skip_kill_bad_branches = 0; int skip_brtrg_vs_sdbd = 0; int skip_find_jsr_data = 0; int skip_mark_args_invalid = 0; int skip_exec_sound_interp = 0; int skip_exec_print = 0; int dont_loop_analysis = 0; int allow_global_branches = 0; int no_exec_branches = 0; int debug_find_jsr_data = 0; int debug_show_instr_flags = 0; int no_default_symbols = 0; int no_exec_routine_symbols = 0; int forced_entry_points = 0; uint_32 entry_point[MAX_ENTRY]; /* ======================================================================== */ /* MARK_INVALID -- Mark an address as invalid. */ /* ======================================================================== */ int mark_invalid(uint_32 addr) { int changed = (instr[addr].flags & FLAG_INVOP) == 0; instr[addr].flags |= FLAG_INVOP; instr[addr].flags &= ~MASK_CODE; return changed; } /* ======================================================================== */ /* MARK_VALID -- Mark an address as valid, w/ optional add'l flags. */ /* ======================================================================== */ int mark_valid(uint_32 addr, uint_32 flags) { uint_32 old_flags = instr[addr].flags; instr[addr].flags &= MASK_CODE; instr[addr].flags |= flags; return old_flags != instr[addr].flags; } /* ======================================================================== */ /* MARK_INTERP -- Mark address range as interpreted, w/ opt flags & cmts. */ /* ======================================================================== */ int mark_interp(uint_32 addr, uint_32 flags, int len, char *cmt) { int i; int changed = 0; for (i = 0; i < len; i++) { uint_32 old_flags = instr[addr + i].flags; instr[addr + i].flags &= ~MASK_CODE; instr[addr + i].flags |= flags | FLAG_INTERP | FLAG_INVOP; instr[addr + i].len = len - i; instr[addr + i].cmt = cmt; printf("addr=%.4X flags %.8X to %.8X\n", addr, old_flags, instr[addr+i].flags); changed += ((old_flags ^ instr[addr + i].flags) & (FLAG_INTERP|FLAG_INVOP)) != 0; } return changed; } /* ======================================================================== */ /* MARK_EMPTY -- Mark an address range as not holding "local code." */ /* ======================================================================== */ void mark_empty(int addr_lo, int addr_hi) { int addr; for (addr = addr_lo; addr <= addr_hi; addr++) instr[addr].flags |= FLAG_EMPTY; } /* ======================================================================== */ /* MARK_STRING -- Given a starting address, mark locations until a NUL. */ /* ======================================================================== */ int mark_string(int addr) { int changes = 0; while (!IS_EMPTY(addr) && (instr[addr].flags & FLAG_CODE) == 0) { uint_32 old_flags = instr[addr].flags; instr[addr].flags = (old_flags & ~MASK_CODE) | FLAG_INVOP|FLAG_STRING; changes += old_flags != instr[addr].flags; if (GET_WORD(addr) == 0) break; addr++; } return changes; } /* ======================================================================== */ /* ADD_ENTRY_POINT -- List a location as a ROM entry point. */ /* ======================================================================== */ void add_entry_point(uint_32 addr) { int i; for (i = 0; i < forced_entry_points; i++) if (entry_point[i] == addr) return; if (forced_entry_points == MAX_ENTRY) { fprintf(stderr, "Too many entry points (max %d)\n", MAX_ENTRY); exit(1); } entry_point[forced_entry_points++] = addr; } /* ======================================================================== */ /* DIS_JUMP -- decode J/JE/JD/JSR/JSRE/JSRD */ /* ======================================================================== */ void dis_jump (uint_32 addr) { uint_16 w0, w1, w2; uint_16 btarg; int ret_reg; int int_mode; /* -------------------------------------------------------------------- */ /* Make sure all three words are in implemented memory and are 10-bit. */ /* -------------------------------------------------------------------- */ if (BAD_ARG10(addr + 1) || BAD_ARG10(addr + 2)) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Get all three words of the jump instruction. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); w1 = GET_WORD(addr + 1); w2 = GET_WORD(addr + 2); /* -------------------------------------------------------------------- */ /* Decode "interrupt mode". This is in bits 0 and 1 of word 2. */ /* 00 means "No Change." */ /* 01 means "Jump and Enable Interrupts" */ /* 10 means "Jump and Disable Interrupts" */ /* 11 is an invalid opcode. */ /* -------------------------------------------------------------------- */ int_mode = (w1 & 3); if (int_mode == 3) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Decode return register. This is in bits 8 and 9 of 2nd word. */ /* 00 means R4 10 means R6 */ /* 01 means R5 11 means "Do not save return address." */ /* -------------------------------------------------------------------- */ ret_reg = 4 + ((w1 & 0x300) >> 8); /* -------------------------------------------------------------------- */ /* Decode branch target. Lower 10 bits come from third word. Upper */ /* 6 bits come from bits 2 thru 7 of second word. */ /* -------------------------------------------------------------------- */ btarg = ((w1 & 0x0FC) << 8) | (w2 & 0x3FF); /* -------------------------------------------------------------------- */ /* Generate the decoded instruction. */ /* -------------------------------------------------------------------- */ instr[addr].len = 3; instr[addr].br_target = btarg; instr[addr].flags |= FLAG_BRANCH; if (ret_reg < 7) /* JSR/JSRE/JSRD */ { instr[addr].op1.type = OP_REG; instr[addr].op1.op = ret_reg; instr[addr].op1.flags = OPF_DST; instr[addr].op2.type = OP_BRTRG; instr[addr].op2.op = btarg; instr[addr].op2.flags = OPF_SRC | OPF_ADDR; instr[addr].flags |= FLAG_JSR; instr[addr].mnemonic = mnm_jsr[int_mode]; } else /* J/JE/JD */ { instr[addr].op1.type = OP_BRTRG; instr[addr].op1.op = btarg; instr[addr].op1.flags = OPF_SRC | OPF_ADDR; instr[addr].mnemonic = mnm_jsr[int_mode + 4]; } } /* ======================================================================== */ /* DIS_IMPL_1OP_A -- decode HLT/SDBD/EIS/DIS */ /* ======================================================================== */ void dis_impl_1op_a(uint_32 addr) { uint_32 w0, w1; /* -------------------------------------------------------------------- */ /* Make sure it's a valid opcode. */ /* -------------------------------------------------------------------- */ if ((w0 = GET_WORD(addr)) > 3) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* If this is a HLT instruction, mark it invalid if asked to do so. */ /* -------------------------------------------------------------------- */ if (hlt_is_invalid && w0 == 0x000) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* If this is an SDBD, make sure the instruction it modifies is an */ /* indirect mode or immediate mode instruction that isn't MVO. */ /* -------------------------------------------------------------------- */ if (w0 == 0x001) { w1 = GET_WORD(addr + 1); /* ---------------------------------------------------------------- */ /* The '(w1 & 0x3C0) < 0x280' makes sure it's MVI, ADD, SUB, CMP */ /* AND or XOR. The '(w1 & 0x038) < 0x008' makes sure it's not a */ /* direct-mode instruction. */ /* ---------------------------------------------------------------- */ if (NOT_CODE(addr + 1) || (w1 & 0x3C0) < 0x280 || (w1 & 0x038) < 0x008) { mark_invalid(addr); return; } /* ---------------------------------------------------------------- */ /* Go ahead and mark the next instruction as being modified by */ /* SDBD. We may revise this later once we discover br targets. */ /* ---------------------------------------------------------------- */ instr[0xFFFF & (addr + 1)].flags |= FLAG_SDBD; } /* -------------------------------------------------------------------- */ /* Copy in the mnemonic and set the length. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = mnm_impl_1op_a[w0]; instr[addr].len = 1; } /* ======================================================================== */ /* DIS_IMPL_1OP_B -- decode TCI/CLRC/SETC */ /* ======================================================================== */ void dis_impl_1op_b(uint_32 addr) { uint_32 w0; /* -------------------------------------------------------------------- */ /* Nuke invalid ops, and TCI if we're treating rare ops as invalid. */ /* -------------------------------------------------------------------- */ if ((w0 = GET_WORD(addr)) < 5 || w0 > 7 || (rare_ops_are_invalid && w0 == 0x005)) /* TCI */ { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Copy in the mnemonic and set the length. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = mnm_impl_1op_b[w0 - 4]; instr[addr].len = 1; } /* ======================================================================== */ /* DIS_NOP_SIN -- Decode NOP and SIN instructions. */ /* ======================================================================== */ void dis_nop_sin (uint_32 addr) { uint_32 w0; /* -------------------------------------------------------------------- */ /* If LSB != 0, treat it as invalid opcode no matter what. Also, if */ /* we're treating "rare as invalid", treat all SIN as invalid. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); if ((w0 & 1) || (rare_ops_are_invalid && (w0 & 3) >= 2)) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Copy in the mnemonic and set the length. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = w0 & 2 ? M_SIN : M_NOP; instr[addr].len = 1; } /* ======================================================================== */ /* DIS_GSWD -- Decode Get Status WorD instructions. */ /* ======================================================================== */ void dis_gwsd (uint_32 addr) { /* -------------------------------------------------------------------- */ /* Dirt simple. One mnemonic. Bits 0..1 are the destination. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = M_GSWD; instr[addr].op1.type = OP_REG; instr[addr].op1.op = GET_WORD(addr) & 3; instr[addr].op1.flags = OPF_DST; instr[addr].len = 1; } /* ======================================================================== */ /* DIS_REG_1OP -- Decode single-operand register instructions. */ /* ======================================================================== */ void dis_reg_1op (uint_32 addr) { uint_32 w0, op, dst; /* -------------------------------------------------------------------- */ /* Bits 0..2 are the destination (R0..R7). Bits 3..5 are the opcode. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); op = (w0 >> 3) & 7; dst = w0 & 7; /* -------------------------------------------------------------------- */ /* Opcode values 000 and 011 are invalid -- these alias other opcode */ /* spaces. We only check for them here for debug purposes. */ /* -------------------------------------------------------------------- */ if (op == 0x000 || op == 0x006) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Build the decoded instruction. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = mnm_reg_1op[op]; instr[addr].op1.type = OP_REG; instr[addr].op1.op = dst; instr[addr].op1.flags = OPF_SRCDST; instr[addr].len = 1; } /* ======================================================================== */ /* DIS_ROT_1OP -- Decode rotates and shifts. */ /* ======================================================================== */ void dis_rot_1op (uint_32 addr) { uint_32 w0, op, reg, amt; /* -------------------------------------------------------------------- */ /* Bits 0..1 are the destination (R0 thru R3). Bit 2 determines */ /* whether we shift by 1 or by 2. Bits 3..5 are the opcode. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); reg = (w0 >> 0) & 3; /* R0 thru R3 */ amt = (w0 >> 2) & 1; /* ,1 or ,2 */ op = (w0 >> 3) & 7; /* which opcode */ /* -------------------------------------------------------------------- */ /* Build the instruction. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = mnm_rot_1op[op]; instr[addr].op1.type = OP_REG; instr[addr].op1.op = reg; instr[addr].op1.flags = OPF_SRCDST; instr[addr].op2.type = OP_IMMED; instr[addr].op2.op = amt + 1; instr[addr].op2.flags = OPF_SRC; instr[addr].len = 1; } /* ======================================================================== */ /* DIS_REG_2OP -- Decode MOVR/ADDR/SUBR/CMPR/ANDR/XORR. */ /* ======================================================================== */ void dis_reg_2op (uint_32 addr) { uint_32 w0, src, dst, op; mnm_t m; /* -------------------------------------------------------------------- */ /* Bits 0..2 are the source, bits 3..5 are the dest, and bits 6..8 */ /* are the opcode. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); dst = (w0 >> 0) & 7; src = (w0 >> 3) & 7; op = (w0 >> 6) & 7; /* -------------------------------------------------------------------- */ /* Opcodes 000 and 001 are illegal -- they alias other opcode spaces. */ /* -------------------------------------------------------------------- */ if (op < 2) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Build the instruction. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = m = mnm_reg_2op[op]; instr[addr].op1.type = OP_REG; instr[addr].op1.op = src; instr[addr].op1.flags = OPF_SRC; instr[addr].op2.type = OP_REG; instr[addr].op2.op = dst; instr[addr].op2.flags = m == M_MOVR ? OPF_DST : m == M_CMPR ? OPF_SRC : OPF_SRCDST; if (m == M_MOVR && src == dst) instr[addr].mnemonic = M_TSTR; if (m == M_XORR && src == dst) instr[addr].mnemonic = M_CLRR; instr[addr].len = 1; } /* ======================================================================== */ /* DIS_COND_BR -- Decode all "internal" conditional branches, and NOPP. */ /* ======================================================================== */ void dis_cond_br (uint_32 addr) { uint_32 w0, cnd, ofs, dir, trg; /* -------------------------------------------------------------------- */ /* Make sure target word is in implemented memory. */ /* -------------------------------------------------------------------- */ if (IS_EMPTY(addr + 1)) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Bits 0..3 are the condition code. Bit 5 determines branch target */ /* direction. Displacement is in second word. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); cnd = (w0 >> 0) & 0xF; dir = (w0 >> 5) & 1; ofs = GET_WORD(addr + 1); trg = addr + 2 + (dir ? ~ofs : ofs); /* -------------------------------------------------------------------- */ /* Make sure NOPP has a 'zero' for its offset. Both 'dir' and 'ofs' */ /* must be zero for NOPP to be valid, as far as the disassembler is */ /* concerned. */ /* -------------------------------------------------------------------- */ if (cnd == 0x8 && (dir != 0 || ofs != 0)) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Check to make sure branch target doesn't wrap, unless user has */ /* explicitly allowed it. */ /* -------------------------------------------------------------------- */ if (cnd != 0x8 && !allow_branch_target_wrap && (trg & 0xFFFF0000)) { mark_invalid(addr); return; } trg &= 0xFFFF; /* -------------------------------------------------------------------- */ /* Build the instruction. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = mnm_cond_br[cnd]; if (cnd != 0x8) { instr[addr].op1.type = OP_BRTRG; instr[addr].op1.op = trg; instr[addr].op1.flags = OPF_SRC | OPF_ADDR; instr[addr].flags |= FLAG_BRANCH; instr[addr].br_target = trg; /* ---------------------------------------------------------------- */ /* All branches except "B" are conditional branches. */ /* ---------------------------------------------------------------- */ if (cnd != 0x0) instr[addr].flags |= FLAG_CONDBR; } instr[addr].len = 2; } /* ======================================================================== */ /* DIS_BEXT -- Decode external-condition branches. */ /* ======================================================================== */ void dis_bext (uint_32 addr) { uint_32 w0, cnd, ofs, dir, trg; /* -------------------------------------------------------------------- */ /* Treat BEXT as 'rare' opcode, since it shouldn't appear in any */ /* Intellivision program. */ /* -------------------------------------------------------------------- */ if (rare_ops_are_invalid || IS_EMPTY(addr + 1)) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Make sure target word is in implemented memory. */ /* -------------------------------------------------------------------- */ if (IS_EMPTY(addr + 1)) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Bits 0..3 are the condition code. Bit 5 determines branch target */ /* direction. Displacement is in second word. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); cnd = (w0 >> 0) & 0xF; dir = (w0 >> 5) & 1; ofs = GET_WORD(addr + 1); trg = addr + 2 + (dir ? ~ofs : ofs); /* -------------------------------------------------------------------- */ /* Check to make sure branch target doesn't wrap, unless user has */ /* explicitly allowed it. */ /* -------------------------------------------------------------------- */ if (!allow_branch_target_wrap && (trg & 0xFFFF0000)) { mark_invalid(addr); return; } trg &= 0xFFFF; /* -------------------------------------------------------------------- */ /* Build the instruction. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = M_BEXT; instr[addr].op1.type = OP_IMMED; instr[addr].op1.op = cnd; instr[addr].op1.flags = OPF_SRC; instr[addr].op2.type = OP_BRTRG; instr[addr].op2.op = trg; instr[addr].op2.flags = OPF_SRC | OPF_ADDR; instr[addr].flags |= FLAG_BRANCH | FLAG_CONDBR; instr[addr].br_target = trg; instr[addr].len = 2; } /* ======================================================================== */ /* DIS_IMM_2OP -- Decode immediate mode instructions */ /* ======================================================================== */ void dis_imm_2op (uint_32 addr) { uint_32 w0, w1, w2, op, dst, imm; mnm_t m; int is_sdbd = 0; /* -------------------------------------------------------------------- */ /* Destination is in bits 0..2. Opcode is in bits 6..8. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); op = (w0 >> 6) & 7; dst = (w0 >> 0) & 7; /* -------------------------------------------------------------------- */ /* Chuck opcode 000, as it's illegal. Also, if opcode is MVOI (001) */ /* and we treat 'rare' as 'illegal', then chuck it as well. */ /* -------------------------------------------------------------------- */ if (op == 0 || (rare_ops_are_invalid && op == 1)) { mark_invalid(addr); if (instr[addr].flags & FLAG_SDBD) mark_invalid(0xFFFF & (addr - 1)); return; } /* -------------------------------------------------------------------- */ /* Read our immediate constant. */ /* */ /* We examine our own "FLAG_SDBD" to decide if our immediate constant */ /* spans 1 or 2 words. If FLAG_SDBD is set, but we fail validity */ /* tests, we clear FLAG_SDBD and mark the SDBD as invalid code. */ /* -------------------------------------------------------------------- */ w1 = GET_WORD(addr + 1); w2 = GET_WORD(addr + 2); imm = w1; /* default is that word 1 is the immediate operand */ if (IS_EMPTY(addr + 1)) { mark_invalid(addr); if (instr[addr].flags & FLAG_SDBD) mark_invalid(0xFFFF & (addr - 1)); return; } if (instr[addr].flags & FLAG_SDBD) { /* ---------------------------------------------------------------- */ /* SDBD is valid only if: */ /* -- second word is in implemented memory. */ /* -- both words are empty in bits 8..15. */ /* -- instruction is not MVOI (opcode 001). */ /* ---------------------------------------------------------------- */ if (op == 1 || IS_EMPTY(addr + 2) || (w1 & 0xFF00) || (w2 & 0xFF00)) { mark_invalid(0xFFFF & (addr - 1)); instr[addr].flags &= ~FLAG_SDBD; } else { imm = (w1 & 0x00FF) | ((w2 << 8) & 0xFF00); is_sdbd = 1; } } /* -------------------------------------------------------------------- */ /* Build the instruction. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = m = mnm_imm_2op[op]; instr[addr].len = 2 + is_sdbd; if (m == M_MVOI) { instr[addr].op1.type = OP_REG; instr[addr].op1.op = dst; instr[addr].op1.flags = OPF_SRC; instr[addr].op2.type = OP_IMMED; instr[addr].op2.op = imm; instr[addr].op2.flags = OPF_DST; } else { instr[addr].op1.type = OP_IMMED; instr[addr].op1.op = imm; instr[addr].op1.flags = OPF_SRC; instr[addr].op2.type = OP_REG; instr[addr].op2.op = dst; instr[addr].op2.flags = m == M_MVII ? OPF_DST : m == M_CMPI ? OPF_SRC : OPF_SRCDST; } } /* ======================================================================== */ /* DIS_DIR_2OP -- Decode direct mode instructions */ /* ======================================================================== */ void dis_dir_2op (uint_32 addr) { uint_32 w0, w1, op, dst; mnm_t m; /* -------------------------------------------------------------------- */ /* Destination is in bits 0..2. Opcode is in bits 6..8. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); w1 = GET_WORD(addr + 1); /* direct address */ op = (w0 >> 6) & 7; dst = (w0 >> 0) & 7; /* -------------------------------------------------------------------- */ /* SDBD is always illegal for direct-mode. */ /* -------------------------------------------------------------------- */ if (instr[addr].flags & FLAG_SDBD) mark_invalid(0xFFFF & (addr - 1)); /* -------------------------------------------------------------------- */ /* Chuck opcode 000, as it's illegal. */ /* Also, make sure our direct address is legal. */ /* -------------------------------------------------------------------- */ if (op == 0 || IS_EMPTY(addr + 1)) { mark_invalid(addr); return; } /* -------------------------------------------------------------------- */ /* Build the instruction. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = m = mnm_dir_2op[op]; instr[addr].len = 2; if (m == M_MVO) /* MVO */ { instr[addr].op1.type = OP_REG; instr[addr].op1.op = dst; instr[addr].op1.flags = OPF_SRC; instr[addr].op2.type = OP_DIRADDR; instr[addr].op2.op = w1; instr[addr].op2.flags = OPF_DST | OPF_ADDR; } else { instr[addr].op1.type = OP_DIRADDR; instr[addr].op1.op = w1; instr[addr].op1.flags = OPF_SRC | OPF_ADDR; instr[addr].op2.type = OP_REG; instr[addr].op2.op = dst; instr[addr].op2.flags = m == M_MVI ? OPF_DST : m == M_CMP ? OPF_SRC : OPF_SRCDST; } } /* ======================================================================== */ /* DIS_IND_2OP -- Decode indirect mode instructions */ /* ======================================================================== */ void dis_ind_2op (uint_32 addr) { uint_32 w0, op, op1, op2; mnm_t m; /* -------------------------------------------------------------------- */ /* Destination is in bits 0..2. Source is in bits 3..5. Opcode is */ /* in bits 6..8. */ /* -------------------------------------------------------------------- */ w0 = GET_WORD(addr); op = (w0 >> 6) & 7; op1 = (w0 >> 3) & 7; op2 = (w0 >> 0) & 7; /* -------------------------------------------------------------------- */ /* Chuck opcode 000, as it's illegal. */ /* -------------------------------------------------------------------- */ if (op == 0) { mark_invalid(addr); if (instr[addr].flags & FLAG_SDBD) mark_invalid(0xFFFF & (addr - 1)); return; } /* -------------------------------------------------------------------- */ /* If FLAG_SDBD is set but we're MVO@, or we're reading via R6, nuke */ /* FLAG_SDBD. (SDBD is incompatible w/ MVO@ and stack accesses.) */ /* -------------------------------------------------------------------- */ if ((instr[addr].flags & FLAG_SDBD) && (op == 1 || op1 == 6)) { mark_invalid(0xFFFF & (addr - 1)); instr[addr].flags &= ~FLAG_SDBD; } /* -------------------------------------------------------------------- */ /* Build the instruction. */ /* -------------------------------------------------------------------- */ instr[addr].mnemonic = m = mnm_ind_2op[op]; instr[addr].len = 1; if (m == M_MVO_) { instr[addr].op1.type = OP_REG; instr[addr].op1.op = op2; instr[addr].op1.flags = OPF_SRC; instr[addr].op2.type = OP_REG; instr[addr].op2.op = op1; instr[addr].op2.flags = OPF_DST | OPF_IND; if (op1 == 6) instr[addr].mnemonic = m = M_PSHR; } else { instr[addr].op1.type = OP_REG; instr[addr].op1.op = op1; instr[addr].op1.flags = OPF_SRC | OPF_IND; instr[addr].op2.type = OP_REG; instr[addr].op2.op = op2; instr[addr].op2.flags = m == M_MVI_ ? OPF_DST : m == M_CMP_ ? OPF_SRC : OPF_SRCDST; if (op1 == 6) instr[addr].mnemonic = m = M_PULR; } } /* ======================================================================== */ /* DECODE_INSTRS -- Try to decode all instructions. Mark obviously */ /* invalid instructions as such. If enabled, mark */ /* "rare" instructions illegal too. */ /* ======================================================================== */ void decode_instrs(uint_32 addr_lo, uint_32 addr_hi) { uint_32 addr, w0; /* -------------------------------------------------------------------- */ /* Step by single addresses within range. Although some instructions */ /* are multi-word, we don't know where instruction boundaries are yet. */ /* Just decode all words as if they are instructions and then sort it */ /* out more fully later. */ /* -------------------------------------------------------------------- */ for (addr = addr_lo; addr <= addr_hi; addr++) { /* ---------------------------------------------------------------- */ /* Instructions that set bits 10..15 are invalid. Skip them. */ /* ---------------------------------------------------------------- */ if (GET_WORD(addr) & 0xFC00) { mark_invalid(addr); continue; } /* ---------------------------------------------------------------- */ /* Skip over addresses we've previously identified as not code. */ /* Examples are explicit data, and invalid opcodes. */ /* ---------------------------------------------------------------- */ if (NOT_CODE(addr)) continue; w0 = GET_WORD(addr); assert((w0 & 0xFC00) == 0); /* ---------------------------------------------------------------- */ /* Pull apart the opcode by opcode space. */ /* */ /* */ /* 00 0000 0100 Absolute jump instructions */ /* 00 0000 00oo Implied 1-op arithmetic (a) */ /* 00 0000 01oo Implied 1-op arithmetic (b) */ /* 00 0011 01oo NOP, SIN instructions */ /* 00 0011 00oo GSWD -- Get Status WorD insn. */ /* 00 00oo oddd Combined Src/Dst Register 1-op */ /* 00 01oo omrr Rotate/Shift Register 1-op */ /* 0o ooss sddd Register -> Register 2-op arith */ /* 10 00z0 cccc Conditional branch (internal condition) */ /* 10 00z1 cccc Conditional branch (external condition) */ /* 1o oo11 1ddd Immediate -> Register 2-op arith */ /* 1o oo00 0ddd Direct -> Register 2-op arith */ /* 1o oomm mddd Indirect -> Register 2-op arith */ /* */ /* ---------------------------------------------------------------- */ if ((w0 & 0x3FF)==0x004) dis_jump (addr); /* 00 0000 0100 */ else if ((w0 & 0x3FC)==0x000) dis_impl_1op_a(addr); /* 00 0000 00oo */ else if ((w0 & 0x3FC)==0x004) dis_impl_1op_b(addr); /* 00 0000 01oo */ else if ((w0 & 0x3FC)==0x034) dis_nop_sin (addr); /* 00 0011 01oo */ else if ((w0 & 0x3FC)==0x030) dis_gwsd (addr); /* 00 0011 00oo */ else if ((w0 & 0x3C0)==0x000) dis_reg_1op (addr); /* 00 00oo oddd */ else if ((w0 & 0x3C0)==0x040) dis_rot_1op (addr); /* 00 01oo omrr */ else if ((w0 & 0x200)==0x000) dis_reg_2op (addr); /* 0o ooss sddd */ else if ((w0 & 0x3D0)==0x200) dis_cond_br (addr); /* 10 00z0 cccc */ else if ((w0 & 0x3D0)==0x210) dis_bext (addr); /* 10 00z1 cccc */ else if ((w0 & 0x238)==0x238) dis_imm_2op (addr); /* 1o oo11 1ddd */ else if ((w0 & 0x238)==0x200) dis_dir_2op (addr); /* 1o oo00 0ddd */ else dis_ind_2op (addr); /* 1o oomm mddd */ } for (addr = addr_lo; addr < addr_hi; addr++) { if (NOT_CODE(addr)) instr[addr].len = 1; } } /* ======================================================================== */ /* FIND_FUNKY_BRANCHES() -- Look for arithmetic on R7 and convert these */ /* instructions to branches, *or* treat them as */ /* invalid if they look invalid. */ /* ======================================================================== */ int find_funky_branches() { uint_32 addr; int changed = 0; /* -------------------------------------------------------------------- */ /* Examine all valid decoded instructions, and see if any of them */ /* operate directly on the program counter as a destination. Some */ /* of these are highly suspicious and can be converted into invalid */ /* opcodes. Other instructions are useful -- INCR PC, DECR PC, ADCR */ /* PC and ADDR PC are all common tricks. */ /* -------------------------------------------------------------------- */ for (addr = 0; addr <= 0xFFFF; addr++) { /* ---------------------------------------------------------------- */ /* Skip code that's already identified as 'bad'. */ /* Skip code that's already identified as branches. */ /* ---------------------------------------------------------------- */ if (NOT_CODE(addr) || IS_BRANCH(addr)) continue; /* ---------------------------------------------------------------- */ /* Check the first operand. Only 1op Reg format can set this to */ /* "R7 is destination". */ /* ---------------------------------------------------------------- */ if ( instr[addr].op1.type == OP_REG && instr[addr].op1.op == 7 && (instr[addr].op1.flags & OPF_DST)) { switch (instr[addr].mnemonic) { /* -------------------------------------------------------- */ /* ADCR looks like a short forward conditional branch. */ /* -------------------------------------------------------- */ case M_ADCR: { instr[addr].br_target = addr + 2; instr[addr].flags |= FLAG_BRANCH | FLAG_CONDBR; break; } /* -------------------------------------------------------- */ /* INCR/DECR are unconditional branches. */ /* -------------------------------------------------------- */ case M_INCR: { instr[addr].br_target = addr + 2; instr[addr].flags |= FLAG_BRANCH; break; } case M_DECR: { instr[addr].br_target = addr; instr[addr].flags |= FLAG_BRANCH; break; } case M_COMR: { if (suspicious_pc_math_is_invalid) { changed += mark_invalid(addr); } else { instr[addr].br_target = addr ^ 0xFFFF; instr[addr].flags |= FLAG_BRANCH; } break; } case M_NEGR: { if (suspicious_pc_math_is_invalid) { changed += mark_invalid(addr); } else { instr[addr].br_target = (-addr) & 0xFFFF; instr[addr].flags |= FLAG_BRANCH; } break; } case M_RSWD: /* Treat RSWD R7 as always illegal, period */ { changed += mark_invalid(addr); break; } default: /* Uhoh?! */ { fprintf(stderr, "Instruction '%s' at $%.4X sets op1 as dst R7\n", mnemonic[instr[addr].mnemonic], addr); break; } } continue; } /* ---------------------------------------------------------------- */ /* Check the second operand. */ /* ---------------------------------------------------------------- */ if ( instr[addr].op2.type == OP_REG && instr[addr].op2.op == 7 && ((instr[addr].op2.flags & (OPF_DST | OPF_IND)) == OPF_DST)) { /* ------------------------------------------------------------ */ /* In general, we permit MOV, ADD, SUB. Anything else looks */ /* pretty darn suspicious. We can calculate branch targets */ /* for ADDI, SUBI, MVII, but nothing else for now. */ /* */ /* Also, ADDI/SUBI that result in targets outside the 16-bit */ /* range are suspicious. */ /* ------------------------------------------------------------ */ switch (instr[addr].mnemonic) { /* -------------------------------------------------------- */ /* MVII is basically identical to J, only shorter. */ /* In a 16-bit ROM, B is a better choice though. */ /* -------------------------------------------------------- */ case M_MVII: { instr[addr].br_target = instr[addr].op1.op; instr[addr].flags |= FLAG_BRANCH; break; } /* -------------------------------------------------------- */ /* These are still somewhat suspicious -- why not use "B"? */ /* -------------------------------------------------------- */ case M_ADDI: case M_SUBI: { uint_32 targ; if (instr[addr].mnemonic == M_ADDI) { targ = addr + instr[addr].len + instr[addr].op1.op; } else { targ = addr + instr[addr].len - instr[addr].op1.op; } if (suspicious_pc_math_is_invalid && targ > 0xFFFF) { changed += mark_invalid(addr); break; } instr[addr].br_target = targ & 0xFFFF; instr[addr].flags |= FLAG_BRANCH; break; } /* -------------------------------------------------------- */ /* These are very suspicious. */ /* -------------------------------------------------------- */ case M_ANDI: case M_XORI: { uint_32 targ; if (suspicious_pc_math_is_invalid) { changed += mark_invalid(addr); break; } if (instr[addr].mnemonic == M_ANDI) { targ = (addr + instr[addr].len) & instr[addr].op1.op; } else { targ = (addr + instr[addr].len) ^ instr[addr].op1.op; } instr[addr].br_target = targ & 0xFFFF; instr[addr].flags |= FLAG_BRANCH; break; } /* -------------------------------------------------------- */ /* These are fairly common. */ /* -------------------------------------------------------- */ case M_PULR: case M_MVI: case M_MVI_: { instr[addr].br_target = BTARG_UNK; instr[addr].flags |= FLAG_BRANCH; break; } /* -------------------------------------------------------- */ /* MOVR (aka. JR) is fairly common. ADDR is also common */ /* for switch-case constructions. SUBR is less common. */ /* Allow these unless the src is R6 or R7. */ /* -------------------------------------------------------- */ case M_MOVR: case M_ADDR: case M_SUBR: { if (suspicious_pc_math_is_invalid && instr[addr].op1.op >= 6) { changed += mark_invalid(addr); break; } instr[addr].br_target = BTARG_UNK; instr[addr].flags |= FLAG_BRANCH; break; } /* -------------------------------------------------------- */ /* Don't trust direct or indirect ADD/SUB to PC by default */ /* -------------------------------------------------------- */ case M_ADD: case M_SUB: case M_ADD_: case M_SUB_: { if (suspicious_pc_math_is_invalid) { changed += mark_invalid(addr); break; } instr[addr].br_target = BTARG_UNK; instr[addr].flags |= FLAG_BRANCH; break; } /* -------------------------------------------------------- */ /* Note that I shouldn't see MVO or CMP. This is for */ /* error checking purposes only. */ /* -------------------------------------------------------- */ case M_MVO: case M_CMP: case M_MVO_: case M_CMP_: case M_MVOI: case M_CMPI: { fprintf(stderr, "WARNING: '%s' at $%.4x appears to write R7?\n", mnemonic[instr[addr].mnemonic], addr); break; } /* -------------------------------------------------------- */ /* Treat everything else as invalid/unknown target. */ /* -------------------------------------------------------- */ default: { if (suspicious_pc_math_is_invalid) { changed += mark_invalid(addr); } else { instr[addr].br_target = BTARG_UNK; instr[addr].flags |= FLAG_BRANCH; } break; } } continue; } } return changed; } /* ======================================================================== */ /* MARK_ARGS_INVALID -- Mark arguments to instructions as invalid. This */ /* allows us to catch branches into the middle of */ /* instructions, and so on. */ /* ======================================================================== */ int mark_args_invalid(void) { int addr, step = 0, i, changes = 0; for (addr = 0; addr <= 0xFFFF; addr += step) { step = 1; if (NOT_CODE(addr) || !instr[addr].len) continue; step = instr[addr].len; for (i = 1; i < step; i++) changes += mark_invalid(addr + i); } return changes; } /* ======================================================================== */ /* PROPAGATE_INVOP -- Scan code backwards, propagating "INVOP" attrs */ /* back towards unconditional control transfers. */ /* The idea is that you can't reach an invalid op by */ /* "falling through" from a valid op. */ /* ======================================================================== */ int propagate_invop(void) { int addr, span = -1, last_br = 0x10000, changed = 0; int prev_was_invalid = 0; for (addr = 0; addr <= 0xFFFF; addr++) { if (IS_EMPTY(addr)) { span = addr; prev_was_invalid = 1; continue; } if (NOT_CODE(addr)) { if (span == -1) continue; while (span < addr) changed += mark_invalid(span++); prev_was_invalid = 1; continue; } if (IS_BRANCH(addr) && !IS_CONDBR(addr)) { if (!prev_was_invalid || IS_BRTRG(addr) || IS_JSR(last_br)) { last_br = addr; span = addr + instr[addr].len; } } if (instr[addr].flags & FLAG_CODE) /* someone forced this on? */ span = addr + instr[addr].len; prev_was_invalid = 0; addr += instr[addr].len - 1; } return changed; } #if 0 int propagate_invop_old(void) { int addr, step = 0; int changed = 0; /* -------------------------------------------------------------------- */ /* This is tricky. We scan for "truly invalid" instructions. To */ /* find these, though, during a *backwards* scan is tricky. Why? */ /* Instructions can be up to 3 words long. In a 3-word-long instr, */ /* the 2nd and 3rd words might look invalid even though the 3-word */ /* combo is perfectly valid. Thus, when we step backwards looking */ /* for starting points, we do a 'greedy gobble', looking for the */ /* longest instruction that ends at our current position, and step to */ /* there. */ /* -------------------------------------------------------------------- */ /* Find first piece of ROM we know about. */ for (addr = 0xFFFF; addr > 0x0002; addr--) { if (!IS_EMPTY(addr)) break; } for (; addr > 0x0002; addr -= step) { step = 1; /* ---------------------------------------------------------------- */ /* Only analyse ROM we know about. */ /* ---------------------------------------------------------------- */ if (IS_EMPTY(addr)) { continue; } //printf("addr %.4X\n", addr); /* ---------------------------------------------------------------- */ /* If this word is 'ok', try stepping to a previous instruction. */ /* ---------------------------------------------------------------- */ if (MAYBE_CODE(addr)) { if (MAYBE_CODE(addr - 3) && instr[addr-3].len == 3) step = 3; else if (MAYBE_CODE(addr - 2) && instr[addr-2].len == 2) step = 2; else step = 1; continue; } //printf("is: %.4X\n", addr); /* ---------------------------------------------------------------- */ /* Ok, we're at an invalid instruction. Now chase this to */ /* earlier addresses until we hit an unconditional branch. */ /* ---------------------------------------------------------------- */ while (addr >= 2 && !IS_EMPTY(addr)) { /* len 1 branches are "INCR PC", "DECR PC", etc. */ if (MAYBE_CODE(addr) && IS_BRANCH (addr) && instr[addr].len == 1) { //printf("a\n"); break; } /* len 2 branches are "B", "Bcond", and some PC arith. */ if (MAYBE_CODE(addr - 1) && IS_BRANCH (addr - 1) && !IS_CONDBR(addr - 1) && instr[addr - 1].len == 2) { //printf("b\n"); break; } /* len 3 branches are J/JSR, and some PC arith. */ if (MAYBE_CODE(addr - 2) && IS_BRANCH (addr - 2) && instr[addr - 2].len == 3) { //printf("c\n"); break; } changed += mark_invalid(addr); addr--; step = 0; } //printf("ie: %.4X\n", addr); } return changed; } #endif /* ======================================================================== */ /* KILL_BAD_BRANCHES -- Look for branches to invalid opcodes, and mark */ /* those branches as invalid instructions. */ /* ======================================================================== */ int kill_bad_branches(void) { uint_32 addr, targ; int changed = 0; for (addr = 0; addr <= 0xFFFF; addr++) { /* ---------------------------------------------------------------- */ /* Skip already-invalid instructions. */ /* ---------------------------------------------------------------- */ if (NOT_CODE(addr)) continue; /* ---------------------------------------------------------------- */ /* See if this is a branch to a known target. */ /* ---------------------------------------------------------------- */ if ((instr[addr].flags & FLAG_BRANCH) && (targ = instr[addr].br_target) <= 0xFFFF) { /* ------------------------------------------------------------ */ /* If so, see if it's a branch into ROM we know about. */ /* ------------------------------------------------------------ */ if (IS_EMPTY(targ)) continue; /* ------------------------------------------------------------ */ /* If the target is known, but not valid code, mark us inval. */ /* ------------------------------------------------------------ */ if (NOT_CODE(targ)) changed += mark_invalid(addr); } } /* -------------------------------------------------------------------- */ /* Scan again, and see if any branches are to areas of the memory */ /* map that we *know* can't be valid branch targets. Skip this pass */ /* if we allow for these targets. */ /* -------------------------------------------------------------------- */ if (allow_branch_to_bad_addr) return changed; for (addr = 0; addr <= 0xFFFF; addr++) { if (NOT_CODE(addr)) continue; if (IS_BRANCH(addr) && (targ = instr[addr].br_target) <= 0xFFFF && IS_EMPTY(targ) && (targ != 0xCF01)) /* hack for ECScable */ { if ((!allow_global_branches && (targ < 0x1000||targ >= 0x2000)) || ( targ < 0x0400) || (targ >= 0x0500 && targ < 0x1000) || (targ >= 0x3000 && targ < 0x4800) || (targ >= 0x1015 && targ < 0x2000 && no_exec_branches)) { changed += mark_invalid(addr); } } } return changed; } /* ======================================================================== */ /* BRTRG_VS_SDBD -- Scan for branches that target instructions w/ */ /* FLAG_SDBD set. Decide whether branch or instr */ /* needs changing. */ /* ======================================================================== */ int brtrg_vs_sdbd(void) { uint_32 addr, targ; int changed = 0; /* -------------------------------------------------------------------- */ /* For each potential branch target, do the following: */ /* */ /* -- If target's exact address is unknown, skip it. */ /* -- If the target is not modified by SDBD, ignore it. */ /* -- If the target is modified by SDBD, but is indirect mode, */ /* remove FLAG_SDBD and move on. */ /* -- If the target is an immediate-mode instruction modified by */ /* SDBD: */ /* -- If its third word is INVALID and the target is itself */ /* not a "branch" (eg. ADDI #foo, PC), mark this branch */ /* as an invalid instruction. */ /* -- Otherwise, remove FLAG_SDBD from instruction and redecode. */ /* -------------------------------------------------------------------- */ for (addr = 0; addr <= 0xFFFF; addr++) { /* ---------------------------------------------------------------- */ /* Only handle stuff that still looks like code. */ /* ---------------------------------------------------------------- */ if (NOT_CODE(addr)) continue; if (IS_BRANCH(addr)) { targ = instr[addr].br_target; /* ------------------------------------------------------------ */ /* Only deal w/ known targets to SDBD-affected instructions. */ /* ------------------------------------------------------------ */ if (targ > 0xFFFF || /* unknown target */ (instr[targ].flags & FLAG_SDBD) == 0) /* not SDBD target */ continue; /* ------------------------------------------------------------ */ /* Handle direct mode by just nuking 'SDBD' flag. */ /* ------------------------------------------------------------ */ if (instr[targ].op1.type == OP_REG) /* indirect mode */ { instr[targ].flags &= ~FLAG_SDBD; mark_invalid((targ - 1) & 0xFFFF); continue; } /* ------------------------------------------------------------ */ /* The other case we handle is immediate-mode, so skip out on */ /* anything else 'weird'. */ /* ------------------------------------------------------------ */ if (instr[targ].op1.type != OP_IMMED) /* not immedate */ continue; /* ------------------------------------------------------------ */ /* Ok, if we remove SDBD, do we expose bad code? */ /* ------------------------------------------------------------ */ if (NOT_CODE(targ + 2) && !IS_BRANCH(targ)) { /* -------------------------------------------------------- */ /* Yes: Mark the branch invalid. */ /* -------------------------------------------------------- */ changed += mark_invalid(addr); } else { /* -------------------------------------------------------- */ /* No: Remove SDBD from the target and mark the SDBD as */ /* invalid. */ /* -------------------------------------------------------- */ instr[targ].flags &= ~(FLAG_SDBD); changed += mark_invalid((targ - 1) & 0xFFFF); changed++; decode_instrs(targ, targ + 2); } } } return changed; } /* ======================================================================== */ /* FIND_JSR_DATA -- Look for data after JSRs by examining the JSR's */ /* target code, looking for indirect reads via the */ /* return address register. */ /* ======================================================================== */ int find_jsr_data(void) { int addr, targ, reg; int ind_addr; int changed = 0; /* -------------------------------------------------------------------- */ /* Scan looking for JSR R4 and JSR R5. */ /* -------------------------------------------------------------------- */ for (addr = 0; addr <= 0xFFFF; addr++) { if (NOT_CODE(addr)) continue; if (!IS_JSR(addr)) continue; if (instr[addr + 3].flags & FLAG_INTERP) continue; reg = instr[addr].op1.op; if (reg != 4 && reg != 5) continue; /* ---------------------------------------------------------------- */ /* Found one. Look at the first few instructions of the target, */ /* looking for indirect reads via the target. We stop when we */ /* find an invalid instruction, control transfer, or other instr */ /* we don't understand. */ /* ---------------------------------------------------------------- */ targ = instr[addr].op2.op; ind_addr = addr + 3; if (debug_find_jsr_data) printf("fjd: JSR R%d, %.4X; ind_addr=%.4X\n", reg, targ, ind_addr); while (MAYBE_CODE(targ)) { if (debug_find_jsr_data > 1) printf("fjd: target %.4X, flags %.4X\n", targ, instr[targ].flags); /* ------------------------------------------------------------ */ /* If we see SDBD, just move to the next instruction. */ /* ------------------------------------------------------------ */ if (instr[targ].mnemonic == M_SDBD) { if (debug_find_jsr_data) printf("fjd: SDBD %.4X\n", targ); targ++; continue; } /* ------------------------------------------------------------ */ /* If we see a JSR or a conditional branch, stop, since we */ /* aren't smart enough to handle arbitrary control flow. */ /* ------------------------------------------------------------ */ if (IS_JSR(targ) || IS_CONDBR(targ)) { if (debug_find_jsr_data) printf("fjd: saw %s at %.4X\n", IS_JSR(targ)?"JSR":"CONDBR", targ); break; } /* ------------------------------------------------------------ */ /* If we see an unconditional forward branch, follow it. */ /* ------------------------------------------------------------ */ if (IS_BRANCH(targ)) { if (instr[targ].br_target <= 0xFFFF && instr[targ].br_target > targ) { if (debug_find_jsr_data) printf("fjd: taking BR %.4X at %.4X\n", instr[targ].br_target, targ); targ = instr[targ].br_target; continue; } else { if (debug_find_jsr_data) printf("fjd: not taking branch at %.4X\n", targ); break; } } /* ------------------------------------------------------------ */ /* If we see an indirect read via R5, go ahead and mark */ /* another location as invalid. If we are modified by SDBD, */ /* mark two locations, and mark them as "BIDECLE". */ /* ------------------------------------------------------------ */ if (instr[targ].mnemonic >= M_MVI_ && instr[targ].mnemonic <= M_XOR_ && instr[targ].op1.op == reg && (instr[targ].op1.flags&(OPF_SRC|OPF_IND)) ==(OPF_SRC|OPF_IND)) { uint_32 flag = FLAG_INVOP; int i; i = 1; if (instr[targ].flags & FLAG_SDBD) { i = 2; flag |= FLAG_DBDATA; } if (debug_find_jsr_data) printf("fjd: saw ind read of %d at %.4X\n", i, targ); targ += instr[targ].len; if (instr[targ].len == 0) targ++; while (i-- > 0) { if (debug_find_jsr_data) printf("fjd: marking %.4X invop\n", ind_addr); instr[ind_addr].flags &= ~MASK_DATA; changed += mark_invalid(ind_addr); instr[ind_addr].flags |= flag; ind_addr++; } continue; } /* ------------------------------------------------------------ */ /* Make sure we don't see any instructions that write to this */ /* register, or use it as an 'indirect' in a manner we haven't */ /* already grokked. */ /* ------------------------------------------------------------ */ if (( instr[targ].op1.type != OP_REG || instr[targ].op1.op != reg || (instr[targ].op1.flags & (OPF_DST|OPF_IND)) == 0) && ( instr[targ].op2.type != OP_REG || instr[targ].op2.op != reg || (instr[targ].op2.flags & (OPF_DST|OPF_IND)) == 0) && instr[targ].len) { targ += instr[targ].len; continue; } if (debug_find_jsr_data) printf("fjd: instr at %.4X broke it\n", targ); break; } } return changed; } /* ======================================================================== */ /* MARK_DATA -- Scan for locations marked as "INVALID" and convert */ /* those to DECLE or BIDECLE directives. */ /* ======================================================================== */ void mark_data(void) { uint_32 addr, addr2, span, dtype; /* -------------------------------------------------------------------- */ /* Convert all INVOP to DATA if they're not already marked as some */ /* form of 'DATA'. */ /* -------------------------------------------------------------------- */ for (addr = 0; addr <= 0xFFFF; addr++) { if (instr[addr].flags & FLAG_INVOP) instr[addr].flags &= ~MASK_CODE; if ((instr[addr].flags & (FLAG_INVOP|MASK_DATA)) == FLAG_INVOP) instr[addr].flags |= FLAG_DATA; } /* -------------------------------------------------------------------- */ /* Convert BIDECLEs back to DECLEs if they aren't valid BIDECLEs. */ /* -------------------------------------------------------------------- */ for (addr = 0; addr <= 0xFFFF; addr++) { if ((instr[addr].flags & MASK_DATA) == FLAG_DBDATA && ((GET_WORD(addr) & 0xFF00) != 0 || (instr[addr+1].flags & MASK_DATA) != FLAG_DBDATA)) { instr[addr].flags &= ~MASK_DATA; instr[addr].flags |= FLAG_DATA; } /* always step in pairs when we see successful BIDECLEs. */ if ((instr[addr].flags & MASK_DATA) == FLAG_DBDATA) addr++; } /* -------------------------------------------------------------------- */ /* Convert STRING back to DECLE if we reach a non-printing character. */ /* -------------------------------------------------------------------- */ for (addr = 0; addr <= 0xFFFF; addr++) { if ((instr[addr].flags & MASK_DATA) == FLAG_STRING && (GET_WORD(addr) > 0xFF || !isprint(GET_WORD(addr)))) { instr[addr].flags &= ~MASK_DATA; instr[addr].flags |= FLAG_DATA; } } /* -------------------------------------------------------------------- */ /* Scan forward for spans of INVOP | (DATA & !CODE). */ /* -------------------------------------------------------------------- */ addr = 0; while (addr <= 0xFFFF) { if (IS_EMPTY(addr) || MAYBE_CODE(addr)) { addr++; continue; } dtype = instr[addr].flags & MASK_DATA; for (addr2 = addr + 1; addr2 <= 0xFFFF; addr2++) if (IS_EMPTY(addr2) || instr[addr2].label || (instr[addr2].flags & MASK_DATA) != dtype) break; switch (dtype & ~FLAG_INTERP) { case FLAG_DBDATA: if ((addr2 & 1) != (addr & 1)) addr2--; if (addr2 == addr) { addr2++; goto data; } while (addr < addr2) { span = addr2 - addr < 4 ? addr2 - addr : 4; instr[addr].mnemonic = M_BIDECLE; if (!IS_INTERP(addr)) instr[addr].len = span; addr += 2; } break; case FLAG_DATA: data: while (addr < addr2) { span = addr2 - addr < 4 ? addr2 - addr : 4; instr[addr].mnemonic = M_DECLE; if (!IS_INTERP(addr)) instr[addr].len = span; addr++; } break; case FLAG_STRING: while (addr < addr2) { span = addr2 - addr < 20 ? addr2 - addr : 20; instr[addr].mnemonic = M_STRING; if (!IS_INTERP(addr)) instr[addr].len = span; addr++; } break; default: fprintf(stderr, "INTERNAL ERROR: Unknown data type %d at " "%.4X\n", dtype, addr); exit(1); } } } #if 0 /* ======================================================================== */ /* ======================================================================== */ /* EXEC DECODERS */ /* ======================================================================== */ /* ======================================================================== */ typedef struct exec_hdr_dsc_t { const char *cmt; int len; } exec_hdr_dsc_t; static const exec_hdr_dsc_t exec_hdr_dsc[] = { { "Ptr: MOB graphic images", 2 }, { "Ptr: EXEC timer table", 2 }, { "Ptr: Start of game", 2 }, { "Ptr: Backgnd gfx list", 2 }, { "Ptr: GRAM init sequence", 2 }, { "Ptr: Date/Title", 2 }, { "Key-click / flags", 1 }, { "Border extension", 1 }, { "Color Stack / FGBG", 1 }, { "Color Stack init (0, 1)", 2 }, { "Color Stack init (2, 3)", 2 }, { "Border color init\n", 1 } }; uint_32 used_mob_pics[ 8]; uint_32 used_gfx_pics[16]; /* ======================================================================== */ /* DECODE_GRAM_INIT */ /* ======================================================================== */ int decode_gram_init(uint_32 addr) { int i, num_pic, changed = 0; char buf[30]; int pic_num = 0; int rpt, xmr, ymr, inv, rot, crd; char *src; num_pic = GET_WORD(addr); /* -------------------------------------------------------------------- */ /* Skip bogus init lists. */ /* -------------------------------------------------------------------- */ if (IS_EMPTY(addr) || IS_INTERP(addr) || num_pic == 0 || num_pic > 64) return 0; /* -------------------------------------------------------------------- */ /* List starts with # of GRAM cards to initialize. Must be 1..64. */ /* -------------------------------------------------------------------- */ sprintf(buf, ".GRAM_INIT_%.4X", addr); if (!no_default_symbols) maybe_defsym(buf, addr); instr[addr].cmt_len = CMT_LONG; changed += mark_interp(addr++, FLAG_DATA, 1, "# of GRAM cards to init"); /* -------------------------------------------------------------------- */ /* Iterate over the entries. Some entries have repeat counts, so we */ /* need to keep careful tally. */ /* -------------------------------------------------------------------- */ while (pic_num < num_pic) { int w0, w1; int s_pic = -1, e_pic = -1; /* ---------------------------------------------------------------- */ /* Assume a single-decle record at first. */ /* ---------------------------------------------------------------- */ w0 = GET_WORD(addr++); xmr = w0 & 0x200; ymr = w0 & 0x100; inv = w0 & 0x080; /* ---------------------------------------------------------------- */ /* If bit 0 == 0, it's a 1-decle record. This specifies a cart */ /* to GRAM transfer of 1 pic. Only XM/YM/INV flags may be used. */ /* ---------------------------------------------------------------- */ if ((w0 & 1) == 0) { crd = 0x3F & (w0 >> 1); sprintf(buf, "#%.2X : CART #%.2X %c%c%c-", pic_num, crd, xmr ? 'X' : '-', ymr ? 'Y' : '-', inv ? 'I' : '-'); if (pic_num == num_pic - 1) strcat(buf, "\n"); s_pic = e_pic = crd; instr[addr - 1].cmt_len = CMT_LONG; changed += mark_interp(addr-1, FLAG_DATA, 1, strdup(buf)); } else { /* ------------------------------------------------------------ */ /* All other record types have 2 words. */ /* ------------------------------------------------------------ */ w1 = GET_WORD(addr++); /* ------------------------------------------------------------ */ /* If the 3 LSBs of the second word are 0, this is an extended */ /* picture copy. It could have rpt > 0, source a card from */ /* GRAM or GROM, specify rotation, specify a picture # > 63, */ /* or any combo of the above. */ /* ------------------------------------------------------------ */ if ((w1 & 7) == 0) { rpt = (w1 >> 7) & 7; /* rpt - 1 in bits 9:7 */ rot = w1 & 0x008; src = w1 & 0x010 ? "GROM" : w1 & 0x040 ? "GRAM" : "CART"; /* Get the source card #. If the source is GRAM, truncate */ crd = (0xC0 & (w1 << 1)) | (0x3F & (w0 >> 1)); if (w1 & 0x40) crd &= 0x3F; sprintf(buf, "#%.2X-%.2X: %4s #%.2X-%.2X %c%c%c%c", pic_num, pic_num + rpt, src, crd, crd + rpt, xmr ? 'X' : '-', ymr ? 'Y' : '-', inv ? 'I' : '-', inv ? 'R' : '-'); if ((w1 & 0x50) == 0) { s_pic = crd; e_pic = crd + rpt; } pic_num += rpt; } else /* ------------------------------------------------------------ */ /* If the 3 LSBs are 100, then it's a 4x4 tile that gets */ /* expanded to 8x8 through replication. */ /* ------------------------------------------------------------ */ if ((w1 & 7) == 4) { sprintf(buf, "#%.2X : 4x4 tile", pic_num); } else /* ------------------------------------------------------------ */ /* If the 2 LSBs are 10, then the remaining bits drive a */ /* generator algorithm. */ /* ------------------------------------------------------------ */ if ((w1 & 3) == 2) { sprintf(buf, "#%.2X : Algo #1", pic_num); } else /* ------------------------------------------------------------ */ /* Otherwise, those bits drive a different generator algo. */ /* ------------------------------------------------------------ */ { sprintf(buf, "#%.2X : Algo #2", pic_num); } if (pic_num == num_pic - 1) strcat(buf, "\n"); instr[addr - 2].cmt_len = CMT_LONG; changed += mark_interp(addr-2, FLAG_DATA, 2, strdup(buf)); } /* ---------------------------------------------------------------- */ /* Keep track of which pics we've loaded from the bkgd grx list. */ /* ---------------------------------------------------------------- */ if (s_pic >= 0) for (i = s_pic; i <= e_pic; i++) SET_BIT(used_gfx_pics, i); pic_num++; } return changed; } /* ======================================================================== */ /* DECODE_TIMER_TABLE -- Decode list of periodically-executed functions */ /* ======================================================================== */ int decode_timer_table(uint_32 addr) { int changed = 0, i; if (!no_default_symbols) maybe_defsym(".TIMER", addr); for (; GET_WORD(addr) != 0; addr += 4) { changed += mark_interp(addr, FLAG_DBDATA, 4, "Timer dispatch/interval"); for (i = 0; i < 4; i++) instr[addr + i].cmt_len = CMT_LONG; add_entry_point(GET_WORD(addr)); } changed += mark_interp(addr, FLAG_DBDATA, 2, "End of timer table\n"); instr[addr].cmt_len = CMT_LONG; return changed; } /* ======================================================================== */ /* MARK_CART_HEADER_PRE -- Mark the cartridge header as known-data. */ /* These are the steps we do "early." The post */ /* routine goes and backfills based on final info */ /* ======================================================================== */ int mark_cart_header_pre(void) { int addr, i; int changed = 0; int after_title_addr = 0; if (IS_EMPTY(0x5000)) return 0; /* -------------------------------------------------------------------- */ /* First, prep the header to be marked as 'data' of various sorts. */ /* -------------------------------------------------------------------- */ for (addr = 0x5000; addr < 0x5014; addr++) { if ((instr[addr].flags & FLAG_INVOP) == 0) changed++; instr[addr].flags &= ~MASK_DATA; } /* -------------------------------------------------------------------- */ /* The first 12 words are all BIDECLEs. */ /* -------------------------------------------------------------------- */ for (addr = 0x5000; addr < 0x500C; addr++) instr[addr].flags = FLAG_DBDATA | FLAG_INVOP | FLAG_INTERP; /* -------------------------------------------------------------------- */ /* The next several are DECLEs. */ /* -------------------------------------------------------------------- */ for (addr = 0x500C; addr < 0x5014; addr++) instr[addr].flags = FLAG_DATA | FLAG_INVOP | FLAG_INTERP; /* -------------------------------------------------------------------- */ /* Slap comments on these guys. */ /* -------------------------------------------------------------------- */ for (addr = 0x5000, i = 0; addr < 0x5014; addr += exec_hdr_dsc[i++].len) { instr[addr].cmt = exec_hdr_dsc[i].cmt; instr[addr].len = exec_hdr_dsc[i].len; instr[addr].cmt_len = CMT_LONG; } /* -------------------------------------------------------------------- */ /* Find the title string and cart year, and mark those words. */ /* -------------------------------------------------------------------- */ addr = GET_DWORD(0x500A); if (!IS_EMPTY(addr)) { if (!no_default_symbols) maybe_defsym(".TITLE", addr); /* ---------------------------------------------------------------- */ /* First word is 'year'. */ /* ---------------------------------------------------------------- */ changed += mark_interp(addr, FLAG_DATA, 1, "Cartridge year"); /* ---------------------------------------------------------------- */ /* Remaining words up to NUL are the title string. */ /* ---------------------------------------------------------------- */ instr[++addr].cmt = "Title string"; while (addr < 0xFFFF && GET_WORD(addr) != 0 && !IS_EMPTY(addr)) { instr[addr].flags &= ~MASK_DATA; changed += mark_invalid(addr); instr[addr].flags |= FLAG_INVOP | FLAG_STRING; addr++; } changed += mark_interp(addr++, FLAG_DATA, 1, "\n"); after_title_addr = addr; } /* -------------------------------------------------------------------- */ /* Find the game entry point and code-after-title (if enabled), and */ /* mark those as entry points. */ /* -------------------------------------------------------------------- */ if ((GET_WORD(0x500C) & 0x80) && !IS_EMPTY(after_title_addr)) { if (!no_default_symbols) maybe_defsym(".TITLECODE", addr); changed += mark_valid(after_title_addr, FLAG_CODE|FLAG_BRTRG); add_entry_point(after_title_addr); } addr = GET_DWORD(0x5004); if (!IS_EMPTY(addr)) { if (!no_default_symbols) maybe_defsym(".START", addr); changed += mark_valid(addr, FLAG_CODE|FLAG_BRTRG); add_entry_point(addr); } /* -------------------------------------------------------------------- */ /* Go interpret the timer table. Mark timer processes as entry pts. */ /* -------------------------------------------------------------------- */ addr = GET_DWORD(0x5002); changed += decode_timer_table(addr); /* -------------------------------------------------------------------- */ /* Go interpret the GRAM initialization sequence. This is a separate */ /* function, as it's possible to call it w/ a different init list */ /* outside header interpretation. */ /* -------------------------------------------------------------------- */ changed += decode_gram_init(GET_DWORD(0x5008)); return changed; } /* ======================================================================== */ /* DECODE_GFX_LIST */ /* ======================================================================== */ int decode_gfx_list(uint_32 addr) { int changed = 0; int i; char buf[32]; sprintf(buf, ".GFX_LIST_%.4X", addr); if (!no_default_symbols) maybe_defsym(buf, addr); for (i = 0; i < 256; i++, addr += 8) { if (GET_BIT(used_gfx_pics, i) && (instr[addr].flags & (FLAG_CODE | FLAG_INTERP)) == 0) { sprintf(buf, "CART PIC #%.2X", i); changed += mark_interp(addr, FLAG_DATA, 4, strdup(buf)); changed += mark_interp(addr + 4, FLAG_DATA, 4, ""); } } return changed; } /* ======================================================================== */ /* MARK_CART_HEADER_POST -- Finish marking up data spec'd by header. */ /* ======================================================================== */ int mark_cart_header_post(void) { int changed = 0; changed += decode_gfx_list(GET_DWORD(0x5006)); return changed; } /* ======================================================================== */ /* DECODE_EXEC_MUSIC -- Try to decode music played by EXEC music engine */ /* ======================================================================== */ int decode_exec_music(void) { uint_32 addr; int changed = 0; for (addr = 0; addr < 0xFFFD; addr++) { uint_32 targ; if (!IS_JSR(addr) || IS_INTERP(addr + 3)) continue; targ = instr[addr].br_target; if (targ != 0x1B27 && targ != 0x1B5D && targ != 0x1B95 && targ != 0x1A94) continue; addr += 3; while (addr < 0x10000 && !IS_EMPTY(addr)) { if (GET_WORD(addr) >= 0x3F0) { changed += mark_interp(addr++, FLAG_DATA, 2, "Note (long)"); } else if (GET_WORD(addr) == 0) { changed += mark_interp(addr, FLAG_DATA, 1, "End of music"); break; } else changed += mark_interp(addr, FLAG_DATA, 1, "Note (short)"); addr++; } } return changed; } /* opcode spaces: 0010 1111 O xx00101111 One word (STUNIT) 0100 1111 T xx01001111 Two words (SETR, long version) 0111 0111 T 0001110111 Three words (UCALL) 1100 1111 O xx11001111 One word (FIN) x000 1111 T 0000001111 Two words (NFREQV / PAUSE long dly) x011 0111 O xxx0110111 One word (EUSE) xx01 0111 T xxxx010111 Two words (RPAUSE) xxx0 0111 O xxxx000111 One word (RNFREQ / RNFREQV) xxxx 0001 T xxxxxx0001 Two words (FREQV) xxxx 0011 T xxxxx00011 Two words (PDJNZ / RSETR) xxxx 0101 O xxxx000101 One word (VOL) xxxx 1001 O xxxxxx1001 One word (Enable) xxxx 1011 O xxxx001011 One word (ECHAR / PAUSE short dly / RFREQ / RFREQV) xxxx 1101 O xxxxx01101 One word (NFREQ / SETR short ver) xxxx xx10 O xxxx000010 One word (VOLV) xxxx xx00 T xxxxxxxx00 Two words (FREQ/EFREQ) */ struct sfx_ops_t { uint_8 mask, sigbits, len; }; static const struct sfx_ops_t sfx_ops[] = { { 0xFF, 0x2F, 1 }, { 0xFF, 0x4F, 2 }, { 0xFF, 0x77, 3 }, { 0xFF, 0xCF, 1 }, { 0x7F, 0x0F, 2 }, { 0x7F, 0x37, 1 }, { 0x3F, 0x17, 2 }, { 0x1F, 0x07, 1 }, { 0x0F, 0x01, 2 }, { 0x0F, 0x03, 2 }, { 0x0F, 0x05, 1 }, { 0x0F, 0x09, 1 }, { 0x0F, 0x0B, 1 }, { 0x0F, 0x0D, 1 }, { 0x03, 0x02, 1 }, { 0x03, 0x00, 2 }, { 0, 0, 1 } }; /* ======================================================================== */ /* DECODE_EXEC_SFX -- Try to decode music played by EXEC sound fx engine */ /* ======================================================================== */ int decode_exec_sfx(void) { uint_32 addr; int changed = 0; int i; for (addr = 0; addr < 0xFFFD; addr++) { uint_32 targ; if (!IS_JSR(addr) || IS_INTERP(addr + 3)) continue; targ = instr[addr].br_target; if (targ != 0x1BBB && targ != 0x1BBE) continue; addr += 3; if (targ == 0x1BBE) changed += mark_interp(addr++, FLAG_DATA, 1, "SFX prio"); while (addr < 0xFFFF && !IS_EMPTY(addr)) { uint_32 word = GET_WORD(addr); for (i = 0; sfx_ops[i].mask; i++) if ((word & sfx_ops[i].mask) == sfx_ops[i].sigbits) break; if (!sfx_ops[i].mask) changed += mark_interp(addr, FLAG_DATA, 1, "Unknown"); else if ((word & 0xFF) == 0xCF) { changed += mark_interp(addr, FLAG_DATA, 1, "SFX end\n"); break; } else changed += mark_interp(addr, FLAG_DATA, sfx_ops[i].len, "SFX data"); addr += sfx_ops[i].len; } } return changed; } #endif /* ======================================================================== */ /* ======================================================================== */ /* TOP LEVEL DRIVING CODE */ /* ======================================================================== */ /* ======================================================================== */ /* ======================================================================== */ /* GENERATE_LABELS -- Scan for all hard memory reference and generate / */ /* look up labels for them. */ /* ======================================================================== */ void generate_labels(void) { int addr; int targ; char *lbl; /* -------------------------------------------------------------------- */ /* Scan for *all* memory references. References that point within */ /* our readable, preloaded pages are "local". All others are */ /* considered "global". */ /* -------------------------------------------------------------------- */ for (addr = 0; addr <= 0xFFFF; addr++) { if (NOT_CODE(addr)) continue; /* ---------------------------------------------------------------- */ /* Instructions can have only one address operand. We look at */ /* the two operands first, and then at the branch target (if this */ /* is marked as a branch). */ /* ---------------------------------------------------------------- */ if (instr[addr].op1.flags & OPF_ADDR) targ=instr[addr].op1.op; else if (instr[addr].op2.flags & OPF_ADDR) targ=instr[addr].op2.op; else if (IS_BRANCH(addr) && instr[addr].br_target <= 0xFFFF) targ=instr[addr].br_target; else continue; /* ---------------------------------------------------------------- */ /* Avoid oddball occurrences. */ /* ---------------------------------------------------------------- */ if (targ < 0 || targ > 0xFFFF) continue; /* ---------------------------------------------------------------- */ /* Look up the label. If one doesn't exist, symtab() will make */ /* one up for us with the prefix we specify. */ /* ---------------------------------------------------------------- */ lbl = symtab_getsym(symtab, targ << 3, IS_EMPTY(targ) ? 'G' : 'L', 0); if (lbl && !instr[targ].label) instr[targ].label = strdup(lbl); } /* -------------------------------------------------------------------- */ /* Next, scan every address that still lacks a label, and ask the */ /* symbol table if there's a known label for that address. */ /* -------------------------------------------------------------------- */ for (addr = 0; addr <= 0xFFFF; addr++) { if (instr[addr].label) continue; lbl = symtab_getsym(symtab, addr << 3, 0, 0); if (lbl) instr[addr].label = strdup(lbl); } } /* ======================================================================== */ /* GENERATE_TEXT -- Scan all instructions and generate disassembly. */ /* ======================================================================== */ void generate_text(void) { int addr; /* -------------------------------------------------------------------- */ /* Go decode all the instructions. */ /* -------------------------------------------------------------------- */ for (addr = 0; addr <= 0xFFFF; addr++) { if (IS_EMPTY(addr)) continue; instr[addr].fmtline = instr_printer[instr[addr].mnemonic](addr); } } /* ======================================================================== */ /* DO_DISASM -- Top level driver that disassembles everything */ /* ======================================================================== */ void do_disasm(void) { int addr, p, sa = -1, ea = -1; int tot_changes = 0, changes, c; /* -------------------------------------------------------------------- */ /* Scan for preload pages that are also marked readable and not */ /* bankswitched. */ /* -------------------------------------------------------------------- */ VB_PRINTF(0, ("Initial decode pass...\n")); for (addr = 0; addr <= 0xFF00; addr += 256) { p = addr >> 8; if ((GET_BIT(icart.preload, p) != 0) && (GET_BIT(icart.readable, p) != 0) && (GET_BIT(icart.dobanksw, p) == 0)) { if (sa == -1) sa = addr; ea = addr + 255; decode_instrs(addr, addr + 255); } else { mark_empty(addr, addr + 255); if (sa >= 0) VB_PRINTF(1, ("> $%.4X - $%.4X\n", sa, ea)); sa = ea = -1; } } if (sa >= 0) VB_PRINTF(1, ("> $%.4X - $%.4X\n", sa, ea)); sa = ea = -1; /* -------------------------------------------------------------------- */ /* Now, if enabled, do various "analysis passes". */ /* -------------------------------------------------------------------- */ if (!skip_advanced_analysis) { VB_PRINTF(0, ("Performing basic analysis.\n")); tot_changes = c =0; if (!skip_mark_cart_header) { VB_PRINTF(1, ("> Marking the standard cartridge header (pre)\n")); tot_changes += mark_cart_header_pre(); } if (!skip_funky_branch_detect) { VB_PRINTF(1, ("> Identifying arithmetic branches...\n")); tot_changes += find_funky_branches(); } again: do { changes = 0; if (!skip_kill_bad_branches) { VB_PRINTF(1, ("> Marking invalid branches...\n")); changes += c = kill_bad_branches(); VB_PRINTF(2, (">> %d words marked invalid\n", c)); } if (!skip_brtrg_vs_sdbd) { VB_PRINTF(1, ("> Analysing SDBD/branch interaction...\n")); changes += c = brtrg_vs_sdbd(); VB_PRINTF(2, (">> %d words marked invalid\n", c)); } if (!skip_find_jsr_data) { VB_PRINTF(1, ("> Finding data after JSR instructions...\n")); changes += c = find_jsr_data(); VB_PRINTF(2, (">> %d words marked invalid\n", c)); } if (!skip_exec_print) { VB_PRINTF(1, ("> Finding data after EXEC print calls...\n")); changes += c = decode_print_calls(); VB_PRINTF(2, (">> %d words marked invalid\n", c)); } if (!skip_mark_args_invalid) { VB_PRINTF(1, ("> Marking instruction arguments invalid...\n")); changes += c = mark_args_invalid(); VB_PRINTF(2, (">> %d words marked invalid\n", c)); } if (!skip_exec_sound_interp) { VB_PRINTF(1, ("> Looking for EXEC music...\n")); changes += c = decode_exec_music(); VB_PRINTF(2, (">> %d words marked as data\n", c)); #if 1 VB_PRINTF(1, ("> Looking for EXEC sound effects...\n")); changes += c = decode_exec_sfx(); VB_PRINTF(2, (">> %d words marked as data\n", c)); #endif } if (!skip_propagate_invalid) { VB_PRINTF(1, ("> Propagating invalid opcodes...\n")); changes += c = propagate_invop(); VB_PRINTF(2, (">> %d words marked invalid\n", c)); } #if 0 if (forced_entry_points) { VB_PRINTF(1, ("> Restoring code at user-forced entry points...\n")); changes += c = process_forced_entry_points(); VB_PRINTF(2, (">> %d words marked valid\n", c)); } #endif tot_changes += changes; if (dont_loop_analysis) break; if (changes > 0) VB_PRINTF(0, ("%6d changes: Repeating analysis passes.\n", changes)); } while (changes > 0); changes = 0; if (!skip_mark_cart_header) { VB_PRINTF(1, ("> Marking the standard cartridge header (post)\n")); tot_changes += changes = mark_cart_header_post(); } if (changes && !dont_loop_analysis) goto again; VB_PRINTF(0, ("%6d words marked invalid during analysis\n", tot_changes)); } /* -------------------------------------------------------------------- */ /* Generate labels as appropriate. */ /* -------------------------------------------------------------------- */ VB_PRINTF(0, ("Generating labels...\n")); generate_labels(); /* -------------------------------------------------------------------- */ /* Convert invalid and data areas to DECLE instructions. */ /* -------------------------------------------------------------------- */ VB_PRINTF(0, ("Marking DECLE/BIDECLE/STRING.\n")); mark_data(); /* -------------------------------------------------------------------- */ /* Generate the disassembled text for each valid instruction. */ /* -------------------------------------------------------------------- */ VB_PRINTF(0, ("Generating disassembled text...\n")); generate_text(); } /* ======================================================================== */ /* WRITE_DISASM -- this is a mess, but it's Good Enough For Now(TM). */ /* ======================================================================== */ void write_disasm(char *fname) { FILE *f; uint_32 next_addr, addr = 0, skip = 0, last_was_invop = 0; int i; if (!(f = fopen(fname, "w"))) { fprintf(stderr, "ERROR: Could not open output file %s for writing\n", fname); exit(1); } addr = 0; while (addr <= 0xFFFF) { int ilen; if (IS_EMPTY(addr)) { skip++; addr++; continue; } if (instr[addr].len == 0) instr[addr].len = 1; if (skip) fprintf(f, "\n ORG $%.4X\n", addr); if (instr[addr].label) fprintf(f, "%s:\n", instr[addr].label); else if (MAYBE_CODE(addr) && last_was_invop) fprintf(f, "\n"); ilen = instr[addr].cmt_len == CMT_LONG ? 24 : instr[addr].cmt_len == CMT_MED ? 32 : 40; fprintf(f, " %-*s; %.4X ", ilen, instr[addr].fmtline, addr); if (!instr[addr].cmt) { if (instr[addr].len <= 4) { for (i = 0; i < instr[addr].len; i++) if (debug_show_instr_flags) fprintf(f, " %.4X", instr[addr + i].flags); else fprintf(f, " %.4X", GET_WORD(addr + i)); } } else { fputs(instr[addr].cmt, f); } fprintf(f, "\n"); next_addr = addr + instr[addr].len; /* if (IS_BRANCH(addr) && !IS_JSR(addr) && !IS_BRANCH(addr + instr[addr].len)) fprintf(f, "\n"); */ if (IS_BRANCH(addr) && ((IS_JSR(addr) && !IS_JSR(next_addr) && MAYBE_CODE(next_addr))|| (!IS_JSR(addr) && (!IS_BRANCH(next_addr)||IS_JSR(next_addr))))) fprintf(f, "\n"); if (!IS_BRANCH(addr) && IS_JSR(next_addr) && MAYBE_CODE(addr)) fprintf(f, "\n"); last_was_invop = NOT_CODE(addr); addr = next_addr; skip = 0; } } /* ======================================================================== */ /* ======================================================================== */ /* INSTRUCTION PRINTER FUNCTIONS */ /* ======================================================================== */ /* ======================================================================== */ static char prt_buf[256], prt_buf2[256]; /* ------------------------------------------------------------------------ */ /* PRT_ERR -- print out an internal error. */ /* ------------------------------------------------------------------------ */ char *prt_err(uint_32 addr) /* Internal error printer */ { sprintf(prt_buf, "error! flags=%.8X ", instr[addr].flags); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_IMP -- instruction with implied operands */ /* ------------------------------------------------------------------------ */ char *prt_imp(uint_32 addr) /* Implied operands */ { return strdup(mnemonic[instr[addr].mnemonic]); } /* ------------------------------------------------------------------------ */ /* PRT_JSR -- JSR-type instructions: OPC REG, LABEL */ /* ------------------------------------------------------------------------ */ char *prt_jsr(uint_32 addr) /* JSR reg, label */ { sprintf(prt_buf, "%-7s R%d, %s", mnemonic[instr[addr].mnemonic], instr[addr].op1.op, symtab_getsym(symtab, instr[addr].op2.op<<3, 'X', 0)); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_ROT -- Shift/Rotate instructions: OPC REG, [1|2] */ /* ------------------------------------------------------------------------ */ char *prt_rot(uint_32 addr) /* ROT reg, [1|2] */ { sprintf(prt_buf, "%-7s R%d, %d", mnemonic[instr[addr].mnemonic], instr[addr].op1.op, instr[addr].op2.op); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_CBR -- Conditional Branch instructions. */ /* ------------------------------------------------------------------------ */ char *prt_cbr(uint_32 addr) /* BRANCH label */ { sprintf(prt_buf, "%-7s %s", mnemonic[instr[addr].mnemonic], symtab_getsym(symtab, instr[addr].op1.op<<3, 'X', 0)); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_BXT -- Branch External instructions */ /* ------------------------------------------------------------------------ */ char *prt_bxt(uint_32 addr) /* BEXT cond, label */ { sprintf(prt_buf, "%-7s $%.1X, %s", mnemonic[instr[addr].mnemonic], instr[addr].op1.op, symtab_getsym(symtab, instr[addr].op2.op<<3, 'X', 0)); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_DIR -- Direct-mode instructions */ /* ------------------------------------------------------------------------ */ char *prt_dir(uint_32 addr) /* OP label, reg */ { sprintf(prt_buf2, "%s,", symtab_getsym(symtab, instr[addr].op1.op<<3, 'X', 0)); sprintf(prt_buf, "%-7s %-8sR%d", mnemonic[instr[addr].mnemonic], prt_buf2, instr[addr].op2.op); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_IMM -- Immediate-mode instructions */ /* ------------------------------------------------------------------------ */ char *prt_imm(uint_32 addr) /* OP #imm, reg */ { if (instr[addr].op1.flags & OPF_ADDR) { sprintf(prt_buf2, "#%s,", symtab_getsym(symtab, instr[addr].op1.op<<3, 'X', 0)); } else { sprintf(prt_buf2, "#$%.4X,", instr[addr].op1.op); } sprintf(prt_buf, "%-7s %-8sR%d", mnemonic[instr[addr].mnemonic], prt_buf2, instr[addr].op2.op); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_IMO -- MVOI instructions. */ /* ------------------------------------------------------------------------ */ char *prt_imo(uint_32 addr) /* OP reg, #imm */ { sprintf(prt_buf, "%-7s R%d, #$%.4X", mnemonic[instr[addr].mnemonic], instr[addr].op1.op, instr[addr].op2.op); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_2RG -- Register-to-register instructions */ /* ------------------------------------------------------------------------ */ char *prt_2rg(uint_32 addr) /* OP reg, reg */ { sprintf(prt_buf, "%-7s R%d, R%d", mnemonic[instr[addr].mnemonic], instr[addr].op1.op, instr[addr].op2.op); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_1RG -- Single-register instructions */ /* ------------------------------------------------------------------------ */ char *prt_1rg(uint_32 addr) /* OP reg */ { sprintf(prt_buf, "%-7s R%d", mnemonic[instr[addr].mnemonic], instr[addr].op1.op); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_PLR -- PULR Rx. */ /* ------------------------------------------------------------------------ */ char *prt_plr(uint_32 addr) /* OP reg */ { sprintf(prt_buf, "%-7s R%d", mnemonic[instr[addr].mnemonic], instr[addr].op2.op); return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_DCL -- DECLE directives. */ /* ------------------------------------------------------------------------ */ char *prt_dcl(uint_32 addr) /* DECLE [up to four words] */ { if (instr[addr].len > 4) { fprintf(stderr, "ERROR: DECLE w/ len > 4 at address %.4X\n", addr); exit(1); } if (instr[addr].len <= 0) { fprintf(stderr, "ERROR: DECLE w/ len <= 0 at address %.4X\n", addr); exit(1); } switch (instr[addr].len) { case 1: sprintf(prt_buf, "DECLE $%.4X", GET_WORD(addr)); break; case 2: sprintf(prt_buf, "DECLE $%.4X, $%.4X", GET_WORD(addr ), GET_WORD(addr + 1)); break; case 3: sprintf(prt_buf, "DECLE $%.4X, $%.4X, $%.4X", GET_WORD(addr ), GET_WORD(addr + 1), GET_WORD(addr + 2)); break; case 4: sprintf(prt_buf, "DECLE $%.4X, $%.4X, $%.4X, $%.4X", GET_WORD(addr ), GET_WORD(addr + 1), GET_WORD(addr + 2), GET_WORD(addr + 3)); break; } return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_BID -- BIDECLE directives. */ /* ------------------------------------------------------------------------ */ char *prt_bid(uint_32 addr) /* BIDECLE [up to two bidecles] */ { /* implementation note: BIDECLE relies on op1, op2 having been set up */ /* rather than reading from the memory image. */ switch (instr[addr].len) { case 2: sprintf(prt_buf, "BIDECLE $%.4X", ( GET_WORD(addr ) & 0x00FF) | ((GET_WORD(addr + 1) << 8) & 0xFF00)); break; case 4: sprintf(prt_buf, "BIDECLE $%.4X, $%.4X", ( GET_WORD(addr ) & 0x00FF) | ((GET_WORD(addr + 1) << 8) & 0xFF00), ( GET_WORD(addr + 2) & 0x00FF) | ((GET_WORD(addr + 3) << 8) & 0xFF00)); break; default: fprintf(stderr, "ERROR: BIDECLE w/ len != 2 or 4 at addr %.4X\n", addr); exit(1); } return strdup(prt_buf); } /* ------------------------------------------------------------------------ */ /* PRT_STR -- STRING directives. */ /* ------------------------------------------------------------------------ */ char *prt_str(uint_32 addr) /* STRING [up to 20 characters] */ { char strbuf[21]; int i; memset(strbuf, 0, 21); for (i = 0; i < instr[addr].len; i++) strbuf[i] = GET_WORD(addr + i); sprintf(prt_buf, "STRING \"%s\"", strbuf); return strdup(prt_buf); } /* ======================================================================== */ /* ======================================================================== */ /* MAIN PROGRAM, GENERIC CRAPOLA(TM) */ /* ======================================================================== */ /* ======================================================================== */ /* ======================================================================== */ /* MERGE_ICARTS -- Given two icartrom_t's, copy the second into the */ /* first. The "replace" argument controls whether */ /* the second ROM is allowed to replace segments in */ /* the first. */ /* ======================================================================== */ void merge_icarts(icartrom_t *dst, icartrom_t *src, int replace) { uint_32 a, p, attr_src, attr_dst; /* -------------------------------------------------------------------- */ /* Look through 256-word pages of 'src' for preload hunks to copy to */ /* 'dst'. Preload is orthogonal to memory attribute settings. */ /* -------------------------------------------------------------------- */ for (p = 0; p < 256; p++) { if (!GET_BIT(src->preload, p)) continue; a = p << 8; if (!replace) { if (GET_BIT(dst->preload, p)) { fprintf(stderr, "ERROR: [preload] hunk conflict at " "$%.4X - $%.4x\n" " Use '-r' to override\n", a, a + 255); exit(1); } } icartrom_addseg(dst, &src->image[a], a, 256, 0, 0); } /* -------------------------------------------------------------------- */ /* Now look through 256-word segments of 'src' for various memory */ /* attributes and try to set them in 'dst'. We allow non-empty flags */ /* in 'src' to merge into 'dst' in the following circumstances: */ /* */ /* -- The 'replace' flag is set, or */ /* -- The corresponding 'dst' flags are empty, or */ /* -- The corresponding 'dst' flags are equal to the 'src' flags. */ /* */ /* -------------------------------------------------------------------- */ for (p = 0; p < 256; p++) { a = p << 8; attr_src = 0; if (GET_BIT(src->readable, p)) attr_src |= ICARTROM_READ; if (GET_BIT(src->writable, p)) attr_src |= ICARTROM_WRITE; if (GET_BIT(src->narrow, p)) attr_src |= ICARTROM_NARROW; if (GET_BIT(src->dobanksw, p)) attr_src |= ICARTROM_BANKSW; attr_dst = 0; if (GET_BIT(dst->readable, p)) attr_dst |= ICARTROM_READ; if (GET_BIT(dst->writable, p)) attr_dst |= ICARTROM_WRITE; if (GET_BIT(dst->narrow, p)) attr_dst |= ICARTROM_NARROW; if (GET_BIT(dst->dobanksw, p)) attr_dst |= ICARTROM_BANKSW; if (!attr_src) continue; if (replace || !attr_dst || attr_dst == attr_src) { icartrom_addseg(dst, NULL, a, 256, attr_src, 0); } else { fprintf(stderr, "INTERNAL ERROR: Cannot merge incompatible attributes on " "$%.4X - $%.4X\n", a, a + 255); exit(1); } } } static const char rcs_id[] UNUSED = "$Id$"; static struct option long_opts[] = { { "no-default-symbols", 0, NULL, 'S' }, { "no-exec-routine-symbols", 0, NULL, 'X' }, { "allow-branch-target-wrap", 0, NULL, 'b' }, { "allow-branch-to-bad-addr", 0, NULL, 'B' }, { "allow-suspicious-pc-math", 0, NULL, 'p' }, { "allow-rare-opcodes", 0, NULL, 'r' }, { "allow-hlt", 0, NULL, 'H' }, { "disable-analysis", 0, NULL, 'a' }, { "verbose", 0, NULL, 'v' }, { "force-overwrite", 0, NULL, 'f' }, { "disable-mark-cart-header", 0, NULL, '0' }, { "disable-funky-branch-detect", 0, NULL, '1' }, { "disable-kill-bad-branches", 0, NULL, '2' }, { "disable-brtrg-vs-sdbd", 0, NULL, '3' }, { "disable-find-jsr-data", 0, NULL, '4' }, { "disable-invalid-propagation", 0, NULL, '5' }, { "disable-exec-sound-interp", 0, NULL, '6' }, { "disable-exec-print", 0, NULL, '7' }, { "dont-loop-analysis", 0, NULL, 'L' }, { "help", 0, NULL, 'h' }, { "?", 0, NULL, '?' }, { "license", 0, NULL, 'l' }, { "debug-find-jsr-data", 0, NULL, 1 }, { "debug-show-instr-flags", 0, NULL, 2 }, { NULL, 0, NULL, 0 } }; static const char *optchars = "?01234567BEGHLSabdfhlprv"; extern char *optarg; extern int optind, opterr, optopt; int force_overwrite = 0; /* ======================================================================== */ /* USAGE -- Just give usage info and exit. */ /* ======================================================================== */ void usage(void) { fprintf(stderr, "\n" "DIS-1600 Advanced(?) CP-1600 Disassembler" "\n" "Copyright 2003, Joseph Zbiciak" "\n" "\n" "USAGE:" "\n" " dis1600 [flags] file0.rom [file1.rom [file2.rom [...]]] out.asm" "\n" "\n" " Input files can be .ROM or .BIN. For .BIN files, DIS-1600 will use the" "\n" " .CFG file if present, or assume default map if absent. If multiple" "\n" " input files are given, they will be merged in the order specified." "\n" "\n" " Branch analysis flags:" "\n" " -p --allow-suspicious-pc-math Odd math on R7 is legal" "\n" " -b --allow-branch-target-wrap Branch targets <$0000 or >$FFFF are ok" "\n" " -B --allow-branch-to-bad-addr Branches into STIC, PSG, etc. are ok" "\n" " -G --allow-global-branches Allow branch outside given ROM and EXEC""\n" " -E --no-exec-branches No branches to $1015-$1FFF" "\n" "\n" " Opcode analysis flags:" "\n" " -r --allow-rare-opcodes Permit SIN, TCI, MVOI, others." "\n" " -H --allow-hlt Permit HLT opcode" "\n" "\n" " Analysis pass control flags:" "\n" " -L --dont-loop-analysis Only iterate analysis passes once." "\n" " -a --disable-analysis Disable all advanced analysis passes" "\n" "\n" " Individual analysis phase controls:" "\n" " -0 --disable-mark-cart-header Don't try to interpret cart header" "\n" " -1 --disable-funky-branch-detect Don't detect non-JSR/J/B branches" "\n" " -2 --disable-kill-bad-branches Don't kill branches to invalid ops" "\n" " -3 --disable-brtrg-vs-sdbd Branch targets don't affect SDBD" "\n" " -4 --disable-find-jsr-data Don't try to find data after JSR" "\n" " -5 --disable-invalid-propagation Don't propagate invalid instrs" "\n" " -6 --disable-exec-sound-interp Don't interpret EXEC music/sfx" "\n" " -7 --disable-exec-print Don't look for EXEC print calls" "\n" "\n" " Miscellaneous flags:" "\n" " -S --no-default-symbols Do not pre-seed symbol table" "\n" " -X --no-exec-routine-symbols Do not include EXEC routine names" "\n" " -v --verbose Output details during analysis" "\n" " -f --force-overwrite Allow output to overwrite existing file""\n" " -l --license License information" "\n" " -? --? This usage info" "\n" " -h --help This usage info" "\n" "\n" "SDK-1600 Website:" "\n" " http://SDK-1600.spatula-city.org/" "\n" "\n" ); exit(0); } /* ======================================================================== */ /* LICENSE -- Just give license/authorship info and exit. */ /* ======================================================================== */ void license(void) { fprintf(stderr, "\n" " DIS-1600 Advanced(?) CP-1600 Disassembler" "\n" " Copyright 2003, Joseph Zbiciak" "\n" "\n" " LICENSE:" "\n" "\n" " This program is free software; you can redistribute it and/or modify" "\n" " it under the terms of the GNU General Public License as published by" "\n" " the Free Software Foundation; either version 2 of the License, or" "\n" " (at your option) any later version." "\n" "\n" " This program is distributed in the hope that it will be useful," "\n" " but WITHOUT ANY WARRANTY; without even the implied warranty of" "\n" " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU" "\n" " General Public License for more details." "\n" "\n" " You should have received a copy of the GNU General Public License" "\n" " along with this program; if not, write to the Free Software" "\n" " Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA." "\n" "\n" " Run \"dis1600 --help\" for usage information." "\n" "\n" ); exit(0); } /* ======================================================================== */ /* MAIN -- In The Beginning, there was MAIN, and C was with */ /* CONST and VOID, and Darkness was on the face of */ /* the Programmer. */ /* ======================================================================== */ int main(int argc, char *argv[]) { int c, option_idx = 0, value; char *output_file; int i; FILE *f; symtab = symtab_create(); for (i = 65536; i < 65539; i++) instr[i].flags |= FLAG_INVOP | FLAG_EMPTY; /* -------------------------------------------------------------------- */ /* Parse command-line arguments. */ /* -------------------------------------------------------------------- */ while ((c = getopt_long(argc, argv, optchars, long_opts, &option_idx)) != EOF) { value = 1; if (optarg) value = atoi(optarg); switch (c) { case 'G': allow_global_branches = 1; break; case 'E': no_exec_branches = 1; break; case 'b': allow_branch_target_wrap = 1; break; case 'B': allow_branch_to_bad_addr = 1; break; case 'p': suspicious_pc_math_is_invalid = 0; break; case 'r': rare_ops_are_invalid = 0; break; case 'H': hlt_is_invalid = 0; break; case 'f': force_overwrite = 1; break; case 'S': no_default_symbols = 1; break; case 'v': verbose++; break; case 'a': skip_advanced_analysis = 1; break; case '0': skip_mark_cart_header = 1; break; case '1': skip_funky_branch_detect = 1; break; case '2': skip_kill_bad_branches = 1; break; case '3': skip_brtrg_vs_sdbd = 1; break; case '4': skip_find_jsr_data = 1; break; case '5': skip_propagate_invalid = 1; break; case '6': skip_exec_sound_interp = 1; break; case '7': skip_exec_print = 1; break; case 'L': dont_loop_analysis = 1; break; case 'h': case '?': usage(); break; case 'l': license(); break; case 1: debug_find_jsr_data++; break; case 2: debug_show_instr_flags = 1; break; default: { fprintf(stderr, "Unrecognized argument: '%c'\n", c); fprintf(stderr, "Use \"%s -h\" for usage info\n", argv[0]); exit(1); } } } /* -------------------------------------------------------------------- */ /* Must have at least three additional arguments: Two input files */ /* and an output file. */ /* -------------------------------------------------------------------- */ if (optind + 1 > argc) { fprintf(stderr, "ERROR: Must provide at least one input file and " "an output file\n"); exit(1); } /* -------------------------------------------------------------------- */ /* Initialize our main Intellicart image. We work by merging all */ /* others into this one. */ /* -------------------------------------------------------------------- */ icartrom_init(&icart); /* -------------------------------------------------------------------- */ /* Remaining arguments are .ROM image filenames. The first N-1 names */ /* are input files. The last name is the output file. The output */ /* file is not allowed to be the same as any of the input files names */ /* unless the overwrite flag is used. */ /* -------------------------------------------------------------------- */ output_file = argv[argc - 1]; if (!force_overwrite && (f = fopen(output_file, "r"))) { fprintf(stderr, "ERROR: Output file '%s' exists.\n" " Use \"-f\" flag to force overwrite.\n", output_file); exit(1); } for (i = optind; i < argc - 1; i++) { /* ---------------------------------------------------------------- */ /* Check for accidental over write of output file. */ /* ---------------------------------------------------------------- */ if (!force_overwrite && !strcmp(argv[i], output_file)) { fprintf(stderr, "ERROR: Input file '%s' overwrites output file\n" " Use \"-f\" flag to force overwrite.\n", argv[i]); exit(1); } /* ---------------------------------------------------------------- */ /* Re-initialize the temporary Intellicart. */ /* ---------------------------------------------------------------- */ icartrom_init(&temp_icart); /* ---------------------------------------------------------------- */ /* Read in the requested file. */ /* ---------------------------------------------------------------- */ icart_readfile(argv[i], &temp_icart); /* ---------------------------------------------------------------- */ /* Merge this image into our total image. */ /* ---------------------------------------------------------------- */ merge_icarts(&icart, &temp_icart, 1); } /* -------------------------------------------------------------------- */ /* Set up default symbols. */ /* -------------------------------------------------------------------- */ if (!no_exec_routine_symbols) setup_exec_routine_sym(); if (!no_default_symbols) setup_defsym(); /* -------------------------------------------------------------------- */ /* Do the disassembly. */ /* -------------------------------------------------------------------- */ do_disasm(); /* -------------------------------------------------------------------- */ /* Write the output file. */ /* -------------------------------------------------------------------- */ write_disasm(output_file); return 0; } /* ======================================================================== */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation; either version 2 of the License, or */ /* (at your option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ /* General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program; if not, write to the Free Software */ /* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* ------------------------------------------------------------------------ */ /* Copyright (c) 2006, Joseph Zbiciak */ /* ======================================================================== */