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]