Introduction
This document was written for the aspiring ROM hacker with little to no experience in 65c816 programming. The basics have been thoroughly covered and I believe this should prove a significant aid to anyone interested in 65c816 ROM hacking. Hopefully this proves true. If you benefit from this document or have suggestions for changes to make to it, please eMail me at shadow@chan.co.jp with your comments. In the end, you the user determines the future of this document.
For updates to this document check http://id.dragonfire.net/
... Unless of course it is now the year 2001 and this has become another document floating around on an FTP with some author nobody knows what happened to. Then yer up shit creek :-)
Edit: This did happen, but it will live on here.
About the 65c816
This processor was Nintendo's pride and joy for quite a few years. Basically, it is an upgrade to the good ole 6502 processor used in such popular systems as the NES and the Commodore Amiga. The main feature added was 24 bit addressing but the processor also supported a whole slew of new instructions and lots of other features you don't need to know about.
Basically, this little guy chugged along at a whopping 2.68Mhz, not even as fast as the Sega Genesis though a couple years later. But the main power was it's ability to display 256 colors in four different layers on screen at a time. That and a lot of other super-neat things helped it in competing on the 16-bit market and earn a place in all our hearts.
Language Structure
Well, time to make things get very very ugly. I'm sure you all are used to things like QBasic or C++ where you get to type in commands like:
10 Print "Hello World!"
or
cout << "Hello World!" << endl;
well not so in the wonderus world of assembler. We don't have all those luxuries. But here's what we do have. Lots and lots of code that will make you scream in agony. For example;
jmp $ff00
This line would jump to whatever code is at 0xff00
and being executing it. So say at 0xff00
you have something like:
ldx #$2b60
lda #$20
ldx #$9200
This basically grabs the first 1104 bytes at 0x200092
, a LoROM address (see later about this), and chunks it into the VRAM. When you break up the code and look at what each section does by itself, it's really not that hard to understand. Now hopefully this gives you some vague idea of how the language is set up.
Expanding a ROM
I'm sure you've all heard of this many many times. About how such and such ROM was expanded to hold the complete script, etc. Expanding a rom is a very simple process. Look at what the size of your ROM is. All expanding it is is padding it to the next size with empty bits. First, open up MS-DOS prompt and go to whatever directory you have ucon.exe in. Type: ucon o [rom file]
at the prompt. When the blue screen comes up, check how many Mbits the ROM is in size. The chart following this section will tell you which size to choose and what to pad to.
If the screen also tells you the ROM is LoRom, you must remember to include 32k empty banks off 00's in the expanded region. LoRom games are stored with one page of data, one empty page. So break up what you're storing in the end. If you flood into a blank page, the emulator generally sees it as a HiRom game, in which case ever single pointer in the game will point to the wrong place and it'll be all messed up.
Other than that, simply follow the padding chart below.
Chart of ROM sizes in Mbits
For whatever size yours is in Mbits, you want to pad to the next higher size or whatever the ending hex bit should be. For example, if you're doing a 4.00 Mbit ROM, you want to fill 0x801f0
to 0x1001f0
with empty bits to pad it to 8.00 Mbits.
Size | End Bit |
---|---|
4.00 Mbit | 0x0801f0 |
8.00 Mbit | 0x1001f0 |
12.00 Mbit | 0x1801f0 |
16.00 Mbit | 0x2001f0 |
20.00 Mbit | 0x2801f0 |
24.00 Mbit | 0x3001f0 |
32.00 Mbit | 0x4001f0 |
48.00 Mbit | 0x6001f0 |
Note: This chart already allots for a $200
bit header in the rom. If you are operating on a ROM with no header, subtract $200
from the address.
Pointer Structure
A pointer is not strictly text related as the MadHacker's Guide would lead one to believe. Pointers themselves tend to come in three forms: a table in which three digits of offsets are specified, jumps to another section of the ROM and a series of statements too load data from another section of the ROM. All of these are pointers.
For example, in your standard RPG, the text for the game will probably have each offset stored in a pointer table. Whatever the offset in the hex editor is, subtract $200
and you'll get the one the ROM is operating on (unless it's LoRom which is covered below). So, say some sleepy drunk in town says "...gurgle...{stopbit}". Well, most likely the pointer is going to that first ".". For the sake of simplicity, lets pretend that it's offset is 0xabcdef
. From that point, click Find and choose up and search for $efcd
. In pointers, the bits are ALWAYS reversed, so instead of searching for $abcdef
, you look for $efcdab
. Which leaves the question of where did $ab
go? In most pointer tables, a page number is pre-specified elsewhere in the ROM and it looks within either that page or the page the table is in. So you may wanna check when you find $efcd
if the next bit happens to be $ab
. If it is, you're in luck :-) Now you can change pages as you please.
The next type is jumps. This is very commonly used in intros but seldom used in games. But basically, the code in assembler would be;
at offset 0xabcd
jmp $ff00
at offset 0xff00
.dcb "Text junk here."
jmp $ff04
This would jump the rom from 0xabcd
to 0xff00
and execute whatever junk is there till it hits the end which jumps it back to 0xff04
(three bytes added to alot for first jump). This is easy to hack. Just make it jump to some spot way out in the padding and have that jump back to where it would have gone. The .dcb is just a simple psudo-op to chunk out "Text junk here." according to the ROM's font table.
Next us is loading statements. You often find these used in title screens and the like. Basically, it would go something like this:
ldx #$abcd
lda #$ab
ldx #$f001
This is really simple. Your first line makes x big enough to hold whatever it's gonna grab. The second opens up page ab, and the third grabs the first $abcd
bytes at address $f001
in page ab. So then you'll have junk elsewhere to process this data. This is generally how tile screens get chunked into the VRAM, etc. Just change where the info is going and the size of it to account for your new stuff and you'll be all set.
LoROM Address Equation
You have seen twice now in this doc notations about LoROM address. LoROM games have 32k hunks of empty bits between pages, so when you hack a LoROM address, you need to account for that. in other words, you follow a simple equation.
In the case of $081fdc
, that is the address seen in a hex editor. Here is the process performed. First, subtract $200
to account for the header. This will give you $081ddc
. Now, you must first evaluate the page number. Open up WinCalc (calc.exe) and switch to hex mode (you must be in scientific mode). Punch in 081ddc then hit / (divide) by 8000. This will give you two digits, 10. This address is in page 10. Now wen need to evaluate where in page 10 is it, so hit clear. Now, punch in 81ddc and this time, hit Mod and punch in 8000. Theis will give you four digits, 1ddc. Bingo. Your LoROM address is 0x101ddc
.
If you are working on a LoROM game, guess what, you get to do that for EVERY pointer!
Wai! Wai!!! (not)
Pointer Equations
Some games involve pointer equations. In such a case, instead of having a normal pointer, you need to add or subtract and extra amount from the address. You will need to do some asm hacking I can't possibly cover in this doc to find it. Sorry I can't help with this one.
Title Screen Replacement
When you run your GIF through GIF2SOPT it will chunk out three files. Unless you're coding you own game you only need the .set file. Whatever is in this file, stick it somewhere where you have open space (NOT IN ONE OF THE BLANK BANKS IF IT'S A LOROM GAME!!!). Then just look around in a sprite editor to see where the current title screen data starts. Take that offset and open up hex workshop. Subtract $200
and look for it reversed (all address are stored as backwards bits, abcd = cdab). When you find it, it will probably be something like a2 cd ab. a2 is the opcode for LDX #$abcd
. You will only search for the last four digits because the page number is specified in an earlier statement (eg lda #$20
). If you must change the page, back it up from there and look for whatever the firs two bits of the location were, but if it was a current bank call, you're screwed.
Programs to Use
I recommend GIF2SOPT as it allows you to convert 16 or 256 color GIF files into fully optimized SNES format data. It should be a 256x256 GIF in order to be used. But if your game blanks the bottom or side row of the title screen (eg the Final Fantasy games), you may need to make it 248x248 pixels.
A Simple Fadeout Routine
This is a very very simple fadeout routine. You don't need to know how it works mainly since I don't wanna explain it:
fadeout
LDY #$000f ; $0f y-reg
loop
LDX #$0006 ; $06 x-reg
DEY ; y=y-1
STY $2100
wait
LDA $4210 ; vertical blank active?
AND #$80
BEQ wait ; if no wait
DEX ; if yes
CPX #$0000
BNE wait ; if x<>0 wait
CPY #$0000 ; x=0
BNE loop ; if y<>1 loop
LDX #$00cf
dark
LDA $4210 ; vertical blank active?
AND #$80
BEQ dark ; if no, do dark
DEX
CPX #$0000
BNE dark ; if x<>0 then dark
This is closely based off of one of BeXXX's examples for a fadeout. But instead of drunked german notation, it has fairly understandable english notation so hopefully you can now see the logic of this. Basically it's a looping routine that keeps decreasing the lightness to dark until the screen is blanked.
Where to Get Needed Tools
I have a wide selection of tools at my webpage in the 65c816 section. http://id.dragonfire.net/
You can also get many things in terri public ftp. ftp://teeri.oulu.fi/pub/console/nintendo/
Editor's Note: Both of these sites are down, and these tools aren't necessarily the best now.
Credits
The following people have contributed to this text (whether they know it or not). Many many thanks go out to them.
- Neill Corlett - reminded me of the lorom equation one time when I forgot it
- Frank Hughes - tons of help and elpaling back when I was working on Ranma
- Jeremy Chadwick - putting up with my stupid questions back when I was first learning
- Carnivore - made a super-keen instruction -> ouput table I use
- Amalgam - making sure this was semi-comprehensible
Document Information
Questions, comments or complaints can be sent to me via eMail at shadow@chan.co.jp. Copyright ©1999 SysTEm[id]. All rights reserved.
Last updated Sunday, April 25, 1999
Appendix A - 65c816 Instruction Set & Syntax
OpCode | Description | Syntax |
---|---|---|
SEP | Set Bits in P | sep #$30 |
ADC | Add With Carry | adc #$12 |
AND | Logical AND | and #$12 |
BIT | Bit Test | bit #$12 |
CMP | Compare Accumulator | cmp #$12 |
CPX | Compare X Register | cpx #$12 |
CPY | Compare Y Register | cpy #$12 |
DEC | Decrement Accumulator or Memory | dec $12 |
EOR | Exclusive OR Accumulator | eor #$12 |
INC | Increment Accumulator or Memory | inc $12 |
LDA | Load Accumulator | lda $12 |
LDX | Load X Register | ldx #$12 |
LDY | Load Y Register | ldy #$12 |
ORA | Logical OR Accumulator | ora #$12 |
ROL | Rotate Left Acc or Mem | rol $12 |
ROR | Rotate Right Acc or Mem | ror $12 |
SBC | Subtract With Carry | sbc #$12 |
STA | Store Accumulator | sta $12 |
STZ | Store X Register | stx $12 |
STY | Store Y Register | sty $12 |
CLR | Store a 0 into Memory | clr $12 |
BCC | Branch if Carry Clear | bcc $601e5 |
BCS | Branch if Carry Set | bcs $601e5 |
BEQ | Branch if Equal | beq $601e5 |
BMI | Branch if Minus | bmi $601e5 |
BNE | Branch if Not Equal | bne $601e5 |
BPL | Branch if Plus | bpl $601e5 |
BRA | Branch Always | bra $601e5 |
BVC | Branch if Overflow Clear | bvc $601e5 |
BVS | Branch if Overflow Set | bvs $601e5 |
CLC | Clear the Carry Flag | clc |
CLD | Clear the Decimal Flag | cld |
CLI | Clear the Interrupt Flag | cli |
DEX | Decrement X Register | dex |
DEY | Decrement Y Register | dey |
INX | Increment X Register | inx |
INY | Increment Y Register | iny |
NOP | No Operation | nop |
PLA | Pop Accumulator | pla |
PLP | Pop P | plp |
PLX | Pop X Register | plx |
PLY | Pop Y Register | ply |
SED | Set Decimal Flag | sed |
SEI | Set Interrupt Flag | sri |
TAX | Transfer Accumulator to X | tax |
TAY | Transfer Accumulator to Y | tay |
TSX | Transfer S to X | tsx |
TXA | Transfer X to Accumulator | txa |
TXS | Transfer X to S | txs |
TXY | Transfer X to Y | txy |
TYA | Transfer Y to Accumulator | tya |
XCE | Exchange Carry w/ Emulation Bit | xce |
BRK | Break Point Instruction | brk #$12 |
CSP | Call System Procedure | csp #$12 |
JMP | Jump to New Location | jmp $1234 |
JSR | Jump to Subroutine | jsr $1234 |
MVN | Block Move (decrement) | mvn $1234 |
MVP | Block Move (increment) | mvp $1234 |
RTI | Return From Interrupt | rti |
RTL | Return From Long Subroutine | rtl |
RTS | Return From Short Subroutine | rts |
HLT | Halt the Clock | hlt |
WAI | Wait for Interrupt | wai |
SWA | Swap Accumulator | swa |
I know this list is missing a lot of instructions, this is just the list of instructions I know for certain is supported by all the assemblers out there. Sorry to tell you but there is even more to learn.
Hope this helps you to make sense out of some of assembler mess early on in the document.
ASM Hacking for Dummies - v1.0 by SysTEm[id]