/*==================================================================== filename: disassemble.cpp project: GameCube DSP Tool (gcdsp) created: 2005.03.04 mail: duddie@walla.com Copyright (c) 2005 Duddie 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ====================================================================*/ #include #include #include "Common.h" #include "FileUtil.h" #include "disassemble.h" #include "DSPTables.h" #ifdef _MSC_VER #pragma warning(disable:4996) #endif #ifndef MAX_PATH #include // For MAX_PATH #endif extern void nop(const UDSPInstruction opc); DSPDisassembler::DSPDisassembler(const AssemblerSettings &settings) : settings_(settings) { } DSPDisassembler::~DSPDisassembler() { // Some old code for logging unknown ops. char filename[MAX_PATH]; sprintf(filename, "%sUnkOps.txt", File::GetUserPath(D_DUMPDSP_IDX)); FILE *uo = fopen(filename, "w"); if (!uo) return; int count = 0; for (std::map::const_iterator iter = unk_opcodes.begin(); iter != unk_opcodes.end(); ++iter) { if (iter->second > 0) { count++; fprintf(uo, "OP%04x\t%d", iter->first, iter->second); for (int j = 15; j >= 0; j--) // print op bits { if ((j & 0x3) == 3) fprintf(uo, "\tb"); fprintf(uo, "%d", (iter->first >> j) & 0x1); } fprintf(uo, "\n"); } } fprintf(uo, "Unknown opcodes count: %d\n", count); fclose(uo); } bool DSPDisassembler::Disassemble(int start_pc, const std::vector &code, int base_addr, std::string &text) { const char *tmp1 = "tmp1.bin"; // First we have to dump the code to a bin file. FILE *f = fopen(tmp1, "wb"); fwrite(&code[0], 1, code.size() * 2, f); fclose(f); // Run the two passes. return DisFile(tmp1, base_addr, 1, text) && DisFile(tmp1, base_addr, 2, text); } char *DSPDisassembler::DisParams(const DSPOPCTemplate& opc, u16 op1, u16 op2, char *strbuf) { char *buf = strbuf; for (int j = 0; j < opc.param_count; j++) { if (j > 0) buf += sprintf(buf, ", "); u32 val = (opc.params[j].loc >= 1) ? op2 : op1; val &= opc.params[j].mask; if (opc.params[j].lshift < 0) val = val << (-opc.params[j].lshift); else val = val >> opc.params[j].lshift; u32 type = opc.params[j].type; if ((type & 0xff) == 0x10) type &= 0xff00; if (type & P_REG) { // Check for _D parameter - if so flip. if ((type == P_ACC_D) || (type == P_ACCM_D)) // Used to be P_ACCM_D TODO verify val = (~val & 0x1) | ((type & P_REGS_MASK) >> 8); else val |= (type & P_REGS_MASK) >> 8; type &= ~P_REGS_MASK; } switch (type) { case P_REG: if (settings_.decode_registers) sprintf(buf, "$%s", pdregname(val)); else sprintf(buf, "$%d", val); break; case P_PRG: if (settings_.decode_registers) sprintf(buf, "@$%s", pdregname(val)); else sprintf(buf, "@$%d", val); break; case P_VAL: case P_ADDR_I: case P_ADDR_D: if (settings_.decode_names) { sprintf(buf, "%s", pdname(val)); } else sprintf(buf, "0x%04x", val); break; case P_IMM: if (opc.params[j].size != 2) { if (opc.params[j].mask == 0x003f) // LSL, LSR, ASL, ASR sprintf(buf, "#%d", (val & 0x20) ? (val | 0xFFFFFFC0) : val); // 6-bit sign extension else sprintf(buf, "#0x%02x", val); } else { sprintf(buf, "#0x%04x", val); } break; case P_MEM: if (opc.params[j].size != 2) val = (u16)(s16)(s8)val; if (settings_.decode_names) sprintf(buf, "@%s", pdname(val)); else sprintf(buf, "@0x%04x", val); break; default: ERROR_LOG(DSPLLE, "Unknown parameter type: %x", opc.params[j].type); break; } buf += strlen(buf); } return strbuf; } static void MakeLowerCase(char *ptr) { int i = 0; while (ptr[i]) { ptr[i] = tolower(ptr[i]); i++; } } bool DSPDisassembler::DisOpcode(const u16 *binbuf, int base_addr, int pass, u16 *pc, std::string &dest) { char buffer[256]; char *buf = buffer; // Start with 8 spaces, if there's no label. buf[0] = ' '; buf[1] = '\0'; buf++; if ((*pc & 0x7fff) >= 0x1000) { *pc++; dest.append("; outside memory"); return false; } const u32 op1 = binbuf[*pc & 0x0fff]; const DSPOPCTemplate *opc = NULL; const DSPOPCTemplate *opc_ext = NULL; // find opcode for (int j = 0; j < opcodes_size; j++) { u16 mask = opcodes[j].opcode_mask; if ((op1 & mask) == opcodes[j].opcode) { opc = &opcodes[j]; break; } } const DSPOPCTemplate fake_op = {"CW", 0x0000, 0x0000, nop, NULL, 1, 1, {{P_VAL, 2, 0, 0, 0xffff}}, false}; if (!opc) opc = &fake_op; bool extended = false; bool only7bitext = false; if (((opc->opcode >> 12) == 0x3) && (op1 & 0x007f)) { extended = true; only7bitext = true; } else if (((opc->opcode >> 12) > 0x3) && (op1 & 0x00ff)) extended = true; else extended = false; if (extended) { // opcode has an extension // find opcode for (int j = 0; j < opcodes_ext_size; j++) { if (only7bitext) { if (((op1 & 0x7f) & opcodes_ext[j].opcode_mask) == opcodes_ext[j].opcode) { opc_ext = &opcodes_ext[j]; break; } } else { if ((op1 & opcodes_ext[j].opcode_mask) == opcodes_ext[j].opcode) { opc_ext = &opcodes_ext[j]; break; } } } } // printing if (settings_.show_pc) buf += sprintf(buf, "%04x ", *pc); u32 op2; // Size 2 - the op has a large immediate. if (opc->size == 2) { op2 = binbuf[(*pc + 1) & 0x0fff]; if (settings_.show_hex) buf += sprintf(buf, "%04x %04x ", op1, op2); } else { op2 = 0; if (settings_.show_hex) buf += sprintf(buf, "%04x ", op1); } char opname[20]; strcpy(opname, opc->name); if (settings_.lower_case_ops) MakeLowerCase(opname); char ext_buf[20]; if (extended) sprintf(ext_buf, "%s%c%s", opname, settings_.ext_separator, opc_ext->name); else sprintf(ext_buf, "%s", opname); if (settings_.lower_case_ops) MakeLowerCase(ext_buf); if (settings_.print_tabs) buf += sprintf(buf, "%s\t", ext_buf); else buf += sprintf(buf, "%-12s", ext_buf); if (opc->param_count > 0) DisParams(*opc, op1, op2, buf); buf += strlen(buf); // Handle opcode extension. if (extended) { if (opc->param_count > 0) buf += sprintf(buf, " "); buf += sprintf(buf, ": "); if (opc_ext->param_count > 0) DisParams(*opc_ext, op1, op2, buf); buf += strlen(buf); } if (opc->opcode_mask == 0) { // unknown opcode unk_opcodes[op1]++; sprintf(buf, "\t\t; *** UNKNOWN OPCODE ***"); } if (extended) *pc += opc_ext->size; else *pc += opc->size; if (pass == 2) dest.append(buffer); return true; } bool DSPDisassembler::DisFile(const char* name, int base_addr, int pass, std::string &output) { FILE* in = fopen(name, "rb"); if (in == NULL) { printf("gd_dis_file: No input\n"); return false; } fseek(in, 0, SEEK_END); int size = (int)ftell(in) & ~1; fseek(in, 0, SEEK_SET); u16 *binbuf = new u16[size / 2]; fread(binbuf, 1, size, in); fclose(in); // Actually do the disassembly. for (u16 pc = 0; pc < (size / 2);) { DisOpcode(binbuf, base_addr, pass, &pc, output); if (pass == 2) output.append("\n"); } delete [] binbuf; return true; }