Initial commit.

This commit is contained in:
James Churchill 2011-04-05 03:02:46 +10:00
commit cb38eeee09
4 changed files with 1805 additions and 0 deletions

1011
65816.c Normal file

File diff suppressed because it is too large Load diff

295
Readme.txt Normal file
View file

@ -0,0 +1,295 @@
------------------------------------
DisPel v1.0
65816/SMC Disassembler
by James Churchill of Naruto (C)2001
------------------------------------
10th of July, 2001.
pelrun@gmail.com
The Problem
-----------
[snip]
Hi, pelrun from 2011 here. I wrote this document so long ago that I don't
think it's fair to leave my griping about the tools of the time still in
this readme. It just boils down to one statement - what was available at
the time had a bunch of quirks that made them difficult to use for SNES
disassembly. So I wrote my own.
The Solution
------------
And so I present DisPel, my own 65816 disassembler. It's not nearly as
"full-featured" as Tracer (for instance, it's not really for use with NES
roms), but it does everything I need it to. And since it only took a day to
write (most of which was working on the commandline parser, not the
disassembler itself!) I wonder why I didn't do it ages ago.
The v0.9 is only because I haven't done exhaustive testing to ensure the
disassembly is correct. But every piece of code I have disassembled with it
matched exactly with code I've disassembled in the past (which was corrected
by hand, of course!)
Features
--------
Able to disassemble a section of a rom, or a whole bank. Or all of it :)
Correct REP/SEP instruction handling.
Correct bank-boundary handling.
Automatic (but overridable) HiROM and LoROM support.
Shadow ROM support.
User-specifiable listing origin (see below.)
True SNES addressing - no need to worry about LoROM-offset conversion or
headers.
Control-C *will* stop it!
True SNES addressing
--------------------
Now you no longer need to worry about converting to-from LoROM addresses to
disassemble the code you want - just enter the SNES bank/addresses you want,
and DisPel does the conversion automatically. The SMC header is also
automatically ignored, unless disabled with the "-n" option.
And now that you can select any section of the rom to disassemble, you don't
have to worry about a 20MB+ file containing "disassembled" graphics data,
which is just a waste of space.
Addresses/banks are specified using plain hex - no $ or 0x prefixes added.
HiROM addressing is switched on using the "-h" option. Use inSNESt or a
similar utility to determine whether it's needed.
v0.91 update: I've replaced the half-assed HiROM support with a newer half-assed
version. Specifying HiROM addresses (bank $40-$6F and $C0-$EF) will switch HiROM on
automatically, and HiROM listings use the correct banks now (instead of bank $00+).
v0.95 update: HiROM is now detected automatically. If DisPel gets it wrong, you can use
the -h/-l options to force HiROM/LoROM modeS respectively.
You can disassemble a single bank using the "-b" option.
e.g.
dispel -b 1D rom.smc
Will disassemble from $1D8000 to $1DFFFF.
dispel -b 1D -h rom.smc
Will disassemble from $1D0000 to $1DFFFF.
Address ranges can be specified using the "-r" option. If the end address is
omitted, the disassembly will proceed from the supplied address to the end
of the file.
e.g.
dispel -r 58600-79700 rom.smc
Will disassemble the range $58600 to $79700... only the valid LoROM banks,
of course :)
dispel -r 58600 rom.smc
Will disassemble from $58600 onwards.
Correct REP/SEP state parsing
-----------------------------
As a byproduct of the need to maintain backward compatibility with the 6502,
certain opcodes take either a 1-byte or a 2-byte operand. Which is used is
dictated by processor state flags, and so is software selectable. This is a
problem with disassembly because normally the disassembler does not know
which version of the instruction to use. This leads to the instruction
alignment being lost and gibberish instructions being output.
Tracer tried to address the problem by maintaining similar state flags, and
updating them when an instruction that would normally alter them is
disassembled. Unfortunately, it didn't do it correctly for all the
instructions that are affected. DisPel however should produce a correct
listing for a subroutine when the state is set properly at the start.
Since most 65816 code I've seen explicitly sets the size flags at the
beginning of each subroutine, you needn't worry about it a lot of the time.
However, if you are disassembling a small block where the flags are set
differently at the beginning, you can affect the initial state of the flags
using the "-a" and "-x" options.
"-a" forces 8-bit accumulator mode.
"-x" forces 8-bit X/Y register mode.
DisPel defaults to 16-bit accumulator and X/Y register mode.
Of course, this only applies at the beginning of the listing. Subsequent REP
and SEP commands will alter the states appropriately.
Bank-boundary handling
----------------------
SNES code doesn't run across bank boundaries. It might in a HiROM (though
I doubt it), but never ever in a LoROM image. So the disassembler shouldn't
place instructions that straddle the bank boundary. If that happens, then the
instruction alignment is definitely compromised, as one or more bytes are
"eaten" at the start of the next bank that shouldn't be. This results in an
incorrect listing at the start of the bank, which continues until the
disassembler (through pure luck) re-establishes the alignment.
This was of great concern in Tracer, which had woeful support for
disassembling sections of a ROM. If it didn't get it right (and you were
bound to have some banks that started like this) then replacing them with
a correct listing was a real pain.
So in DisPel I enforced the bank boundaries. If an disassembled instruction
would go over a boundary then it is ignored. The surplus bytes up to the
boundary are displayed without an accompanying instruction, and disassembly
continues at the boundary onwards.
Just in case I'm completely wrong about the boundary-crossing on HiROMs, you
can disable the enforcement using the -d option. But I suggest leaving it on
to start with.
Shadow ROM support
------------------
Shadow ROM is a feature of the SNES hardware - the entire rom image is
mirrored at bank 80 onwards. When game code executes in those banks the
hardware runs at a higher clock-rate than when it's running at the lower
copy. (I think it's called FastROM and SlowROM in other documentation - for
obvious reasons. Note I'm not talking about Fast/SlowROM *protection* here.)
65816 code is largely relocatable. However, long absolute addressing modes
mean that most code *must* be run where it was assembled to run - it's
origin. Therefore FastROM code won't work properly if attempted to be
executed in the SlowROM banks and vice versa. Unfortunately, when you
disassemble FastROM game code with the other tools, you get a listing based
in the SlowROM banks, which gives you a very inconsistent view of the code -
relative addresses point at expected places, but long jumps and the like all
go somewhere completely unexpected!
If you know what's going on, there's no problem. But it's still a hassle to
have to worry about it. Therefore DisPel has the ability to produce a listing
using the FastROM addresses. You'll know if you need it if you see JMP's
going to addresses above $800000.
For HiROMs, the SlowROM code begins at bank $40, and the FastROM copy at $C0.
To turn it on, either specify the address range/bank in the $80xxxx+ area
directly, or use the -s option to convert the specified addresses to FastROM
ones.
v0.95 update: FastROM is detected automatically. You can force enable/disable it
by using the -s and -i options.
e.g.
dispel -bank 02 -s rom.smc
Will disassemble bank 02 as bank 82.
dispel -bank 82 rom.smc
Identical to above.
dispel -r 20000-2FFFF -s rom.smc
Will disassemble the region $20000-$2FFFF as $820000-$82FFFF.
dispel -r 820000-82FFFF rom.smc
Same as above.
User-specified origin support
-----------------------------
This is what I first meant to use to implement Shadow ROM support. I did it
better above, but I decided to leave this in in case someone might find it
useful.
Not all SNES code is run where it is in the rom. Some of it is copied
elsewhere then executed at the new location (the sound code does this, but
that's a different discussion.) Such won't work at it's original location -
the code is assembled to run somewhere else, and all absolute addresses will
point to incorrect places. If you ever encounter something like this, the
"-g" option will force DisPel to assume the code is assembled somewhere other
than it's actual location. Relatively addressed operands will be altered to
fit.
You shouldn't need this. If you do, you'll probably understand what this
does anyway. Otherwise, don't worry about it.
e.g.
dispel -b 0 -g 30000 rom.smc
Disassemble LoROM bank 0 ($8000-$FFFF) as if it was really at $30000-$37FFF.
Miscellaneous
-------------
You can output only the instructions (no address/hexdump fields) using the -t option.
You can also place blank lines after RTS,RTI,RTL instructions using the -p option.
This will help show where the subroutines start/end in the code.
Usage
-----
dispel [-n] [-h] [-s] [-a] [-x] [-d]
[-b <bank>|-r <startaddr>-<endaddr>] [-g <origin>]
[-o <outfile>] <infile>
Options: (numbers are hex-only, no prefixes)
-n Don't skip SMC header
-h HiROM memory mapping. Default is LoROM.
-s Use shadow ROM addresses (see above.)
-a Start in 8-bit accumulator mode. Default is 16-bit.
-x Start in 8-bit X/Y mode. Default is 16-bit.
-d Turn off bank-boundary enforcement (see above.)
-b <bank> Disassemble bank <bank> only. Overrides -r.
-r <start>-<end> Disassemble block from <start> to <end>.
Omit -<end> to disassemble to end of file.
-g <origin> Set origin of disassembled code (see above.)
-o <outfile> Set file to redirect output to. Default is to the screen.
<infile> File to disassemble.
Release history
---------------
v1.0001 - 5/4/2011
No code changes; put the source into roughly the code style I use nowadays
before making the source public, and changed from a VC5 project to GCC/Make.
v1.00 - 10/7/2002
Fixed a stupid bug that made DisPel crash if the input file didn't exist.
Lowercased the mnemonics.
Changed the parameter format for BRK and COP
v0.99 - 10/7/2001
Fixed a couple of errors that prevented the 8-bit register options from working,
as well as the shadow/hirom support.
Also fixed a small error in this document; -b overrides -r, not -a.
v0.96 - 3/2/2001
Turns out I was erroneously outputting ",X" for the
"Direct Page Indirect Indexed, Y" and
"Direct Page Indirect Long Indexed, Y" opcodes.
That's 16 opcodes with incorrect output - Ouch.
Thanks to Skeud for pointing it out.
v0.95 - 19/11/2000
Autodetects HiROM/LoROM and FastROM/SlowROM modes.
Added option to put blank lines after RTS,RTL,RTI instructions
Added option to output raw assembly (minus address/hex data fields)
v0.91 - 25/09/2000
Fixed HiROM addressing to start from bank $40 instead of $00.
Serves me right for not looking up the HiROM docs...
v0.9 - 24/09/2000
Initial Release

7
dispel.h Normal file
View file

@ -0,0 +1,7 @@
/* dispel.h
* Header file for DisPel
* James Churchill
* Created 240900
*/
int disasm(unsigned char *mem, unsigned long pos, unsigned char *flag, char *inst, unsigned char tsrc);

492
main.c Normal file
View file

@ -0,0 +1,492 @@
/* main.c
* DisPel 65816 Disassembler
* James Churchill
* Created 20000924
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include "dispel.h"
void usage(void)
{
printf("\nDisPel v0.99 by James Churchill of Naruto (C)2001\n"
"65816/SMC Disassembler\n"
"Usage: dispel [-n] [-t] [-h] [-l] [-s] [-i] [-a] [-x] [-e] [-p]\n"
" [-b <bank>|-r <startaddr>-<endaddr>] [-g <origin>]\n"
" [-d <width>] [-o <outfile>] <infile>\n\n"
"Options: (numbers are hex-only, no prefixes)\n"
" -n Don't skip SMC header\n"
" -t Don't output addresses/hex dump.\n"
" -h/-l Force HiROM/LoROM memory mapping.\n"
" -s/-i Force enable/disable shadow ROM addresses (see readme.)\n"
" -a Start in 8-bit accumulator mode. Default is 16-bit.\n"
" -x Start in 8-bit X/Y mode. Default is 16-bit.\n"
" -e Turn off bank-boundary enforcement. (see readme.)\n"
" -p Split subroutines by placing blank lines after RTS,RTL,RTI\n"
" -b <bank> Disassemble bank <bank> only. Overrides -r.\n"
" -r <start>-<end> Disassemble block from <start> to <end>.\n"
" Omit -<end> to disassemble to end of file.\n"
" -g <origin> Set origin of disassembled code (see readme.)\n"
" -d <width> No disassembly - produce a hexdump with <width> bytes/line.\n"
" -o <outfile> Set file to redirect output to. Default is stdout.\n"
" <infile> File to disassemble.\n");
}
/* Snes9x Hi/LoROM autodetect code */
int AllASCII(unsigned char *b, int size)
{
int i;
for (i = 0; i < size; i++)
{
if (b[i] < 32 || b[i] > 126)
{
return 0;
}
}
return 1;
}
int ScoreHiROM(unsigned char *data)
{
int score = 0;
if ((data[0xFFDC] + data[0xFFDD]*256 + data[0xFFDE] + data[0xFFDF]*256) == 0xFFFF)
{
score += 2;
}
if (data[0xFFDA] == 0x33)
{
score += 2;
}
if ((data[0xFFD5] & 0xf) < 4)
{
score += 2;
}
if (!(data[0xFFFD] & 0x80))
{
score -= 4;
}
if ((1 << (data[0xFFD7] - 7)) > 48)
{
score -= 1;
}
if (!AllASCII(&data[0xFFB0], 6))
{
score -= 1;
}
if (!AllASCII(&data[0xFFC0], 20))
{
score -= 1;
}
return (score);
}
int ScoreLoROM(unsigned char *data)
{
int score = 0;
if ((data[0x7FDC] + data[0x7FDD]*256 + data[0x7FDE] + data[0x7FDF]*256) == 0xFFFF)
{
score += 2;
}
if (data[0x7FDA] == 0x33)
{
score += 2;
}
if ((data[0x7FD5] & 0xf) < 4)
{
score += 2;
}
if (!(data[0x7FFD] & 0x80))
{
score -= 4;
}
if ((1 << (data[0x7FD7] - 7)) > 48)
{
score -= 1;
}
if (!AllASCII(&data[0x7FB0], 6))
{
score -= 1;
}
if (!AllASCII(&data[0x7FC0], 20))
{
score -= 1;
}
return (score);
}
int hexdump(unsigned char *data,unsigned long pos,unsigned long rpos,
unsigned long len,char *inst, unsigned char dwidth)
{
int i;
sprintf(inst, "%02lX/%04lX:\t", (pos >> 16) & 0xFF, pos & 0xFFFF);
for(i=0; i<dwidth && i+rpos<len; i++)
{
sprintf(inst + i*2 + 9, "%02X", data[rpos+i]);
}
return dwidth;
}
int main(int argc, char *argv[])
{
FILE *fin,*fout;
char infile[BUFSIZ],outfile[BUFSIZ],inst[521];
unsigned char dmem[4],flag=0,*data;
unsigned long len,pos=0,origin=0x1000000,start=0,end=0,rpos;
unsigned char opt,skip=1,hirom=2,shadow=2,bound=1,tsrc=0;
unsigned int offset,bank=0x100,i,tmp,dwidth=0;
int hiscore,loscore;
outfile[0]=0;
// Parse the commandline
if (argc < 2)
{
usage();
exit(1);
}
for (i=1; i<(argc-1); i++)
{
if (sscanf(argv[i], "-%c", &opt) == 0)
{
usage();
exit(1);
}
switch(opt)
{
case 'n':
skip = 0;
break;
case 't':
tsrc |= 1;
break;
case 'h':
hirom = 1;
break;
case 'l':
hirom = 0;
break;
case 's':
shadow = 1;
break;
case 'i':
shadow = 0;
break;
case 'a':
flag |= 0x20;
break;
case 'x':
flag |= 0x10;
break;
case 'e':
bound = 0;
break;
case 'p':
tsrc |= 2;
break;
case 'd':
i++;
if ((sscanf(argv[i], "%2X", &dwidth) == 0) || dwidth==0)
{
usage();
printf("\n-d requires a hex value between 01 and FF after it.\n");
exit(1);
}
break;
case 'b':
i++;
if (sscanf(argv[i], "%2X", &bank) == 0)
{
usage();
printf("\n-b requires a 1-byte hex value after it.\n");
exit(1);
}
break;
case 'r':
i++;
if (sscanf(argv[i], "%6lX-%6lX", &start, &end) == 0)
{
usage();
printf("\n-a requires at least one hex value after it.\n");
exit(1);
}
break;
case 'g':
i++;
if (sscanf(argv[i], "%6lX", &origin) == 0)
{
usage();
printf("\n-r requires one hex value after it.\n");
exit(1);
}
break;
case 'o':
i++;
strcpy(outfile, argv[i]);
break;
default:
usage();
printf("\nUnknown option: -%c\n", opt);
exit(1);
}
}
// Get the input filename and open it
strcpy(infile, argv[i]);
fin = fopen(infile, "rb");
if (!fin)
{
printf("Cannot open %s for reading.\n", infile);
exit(1);
}
// Set up the output
if (outfile[0] == 0)
{
strcpy(outfile,"STDOUT");
fout = stdout;
}
else
{
fout = fopen(outfile, "w");
if (!fout)
{
printf("Cannot open %s for writing.\n",outfile);
exit(1);
}
}
// Read the file into memory
len = filelength(fileno(fin));
// Make sure the image is big enough
if (len < 0x10000 || (skip == 1 && len < 0x10200))
{
printf("This file looks too small to be a rom image.\n");
exit(1);
}
// Allocate mem for file. Extra 3 bytes to prevent segfault during memcpy
if ((data = malloc(len+3)) == NULL)
{
printf("Cant alloc %ld bytes.\n", len+3);
exit(1);
}
if (skip)
{
len -= 0x200;
fread(data, 0x200, 1, fin);
}
fread(data, len, 1, fin);
fclose(fin);
// Autodetect the HiROM/LoROM state
if (hirom==2)
{
hiscore = ScoreHiROM(data);
loscore = ScoreLoROM(data);
if (hiscore>loscore)
{
// fprintf(stderr,"Autodetected HiROM image.\n");
hirom = 1;
}
else
{
// fprintf(stderr,"Autodetected LoROM image.\n");
hirom = 0;
}
}
// Unmangle the address options
pos = start;
// If shadow addresses given, convert to unshadowed and set shadow on.
if ((bank == 0x100 && start & 0x800000) | (bank & 0x80))
{
shadow = 1;
}
// If HiROM addresses given, convert to normal and set hirom on.
if ((bank == 0x100 && start & 0x400000) | (bank & 0x40))
{
hirom=1;
}
bank &= 0x13F;
start &= 0x3FFFFF;
end &= 0x3FFFFF;
// Autodetect shadow
if(shadow == 2)
{
// fprintf(stderr,"%02X\n",data[hirom?0xFFD5:0x7FD5]);
if(data[hirom?0xFFD5:0x7FD5] & 0x30)
{
shadow=1;
}
else
{
shadow=0;
}
}
// If the bank byte is set, apply it to the address range
if (bank < 0x100)
{
if(hirom)
{
pos = bank << 16;
start = pos;
end = start | 0xFFFF;
}
else
{
pos = (bank << 16) + 0x8000;
start = bank * 0x8000;
end = start + 0x7FFF;
}
}
else
{
if(!hirom)
{
// Convert the addresses to offsets
if ((start & 0xFFFF) < 0x8000)
{
start += 0x8000;
}
pos = start;
start = ((start >> 16) & 0xFF) * 0x8000 + (start & 0x7FFF);
end = ((end >> 16) & 0xFF) * 0x8000 + (end & 0x7FFF);
}
}
// If end isn't after start, set end to end-of-file.
if(end <= start)
{
end = len-1;
}
// If new origin set, apply it.
if (origin<0x1000000)
{
pos=origin;
}
// If shadow set, apply it
if (shadow)
{
pos |= 0x800000;
}
// If hirom, apply the mapping
if (hirom)
{
pos |= 0x400000;
}
#ifdef _DEBUG
fprintf(stderr,"Start: $%06X End: $%06X Pos: $%06X\n", start, end, pos);
fprintf(stderr,"Input: %s\nOutput: %s\n", infile, outfile);
if(shadow)
{
fprintf(stderr,"Autodetected FastROM.\n");
}
else
{
fprintf(stderr,"Autodetected SlowROM.\n");
}
#endif
// Begin disassembly
rpos = start;
while (rpos < len && rpos <= end)
{
// copy some data to the staging area
memcpy(dmem, data+rpos, 4);
// disassemble one instruction, or produce one line of hexdump
if (dwidth == 0)
{
offset = disasm(dmem, pos, &flag, inst, tsrc);
}
else
{
offset = hexdump(data, pos, rpos, len, inst, dwidth);
}
// Check for a file/block overrun
if ((rpos + offset) > len || (rpos + offset) > (end+1))
{
// print out remaining bytes and finish
fprintf(fout,"%02lX/%04lX:\t", (pos >> 16) & 0xFF, pos & 0xFFFF);
for (i=rpos; i<len && i<=end; i++)
{
fprintf(fout,"%02X", data[rpos]);
}
fprintf(fout,"\n");
break;
}
// Check for a bank overrun
if (bound && ((pos & 0xFFFF) + offset) > 0x10000)
{
// print out remaining bytes
fprintf(fout, "%02lX/%04lX:\t",(pos >> 16) & 0xFF, pos & 0xFFFF);
tmp = 0x10000 - (pos & 0xFFFF);
for (i=0; i<tmp; i++)
{
fprintf(fout, "%02X", data[rpos+i]);
}
fprintf(fout, "\n");
// Move to next bank
if(!hirom)
{
pos = (pos & 0xFF0000) + 0x18000;
}
else
{
pos += tmp;
}
rpos += tmp;
continue;
}
fprintf(fout, "%s\n", inst);
// Move to next instruction
if (!hirom && ((pos & 0xFFFF) + offset) > 0xFFFF)
{
pos = (pos & 0xFF0000) + 0x18000;
}
else
{
pos += offset;
}
rpos+=offset;
}
fclose(fout);
return 0;
}