349 lines
7.4 KiB
C++
349 lines
7.4 KiB
C++
/*====================================================================
|
|
|
|
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 <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "FileUtil.h"
|
|
#include "disassemble.h"
|
|
#include "DSPTables.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(disable:4996)
|
|
#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", FULL_DSP_DUMP_DIR);
|
|
FILE *uo = fopen(filename, "w");
|
|
if (!uo)
|
|
return;
|
|
|
|
int count = 0;
|
|
for (std::map<u16, int>::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<u16> &code, int base_addr, std::string &text)
|
|
{
|
|
const char *tmp1 = "tmp1.bin";
|
|
const char *tmp2 = "tmp.txt";
|
|
|
|
// 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) ? val = op2 : val = 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) // 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;
|
|
|
|
if (opcodes[j].size & P_EXT)
|
|
mask = opcodes[j].opcode_mask & 0xff00;
|
|
else
|
|
mask = opcodes[j].opcode_mask;
|
|
|
|
if ((op1 & mask) == opcodes[j].opcode)
|
|
{
|
|
opc = &opcodes[j];
|
|
break;
|
|
}
|
|
}
|
|
const DSPOPCTemplate fake_op = {"CW", 0x0000, 0x0000, nop, nop, 1, 1, {{P_VAL, 2, 0, 0, 0xffff}}, NULL, NULL,};
|
|
if (!opc)
|
|
opc = &fake_op;
|
|
|
|
bool extended;
|
|
if ((opc->size & P_EXT) && (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 ((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 & ~P_EXT) == 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 & ~P_EXT;
|
|
|
|
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;
|
|
}
|