SNES Development
ASM Hacking for Dummies


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 with your comments. In the end, you the user determines the future of this document.

For updates to this document check … 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!"


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.

SizeEnd Bit
4.00 Mbit0x0801f0
8.00 Mbit0x1001f0
12.00 Mbit0x1801f0
16.00 Mbit0x2001f0
20.00 Mbit0x2801f0
24.00 Mbit0x3001f0
32.00 Mbit0x4001f0
48.00 Mbit0x6001f0

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:

   LDY  #$000f   ; $0f y-reg
   LDX  #$0006   ; $06 x-reg
   DEY           ; y=y-1
   STY  $2100
   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
   LDA  $4210    ; vertical blank active?
   AND  #$80
   BEQ  dark     ; if no, do dark
   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.

You can also get many things in terri public ftp.

Edit: Both of these sites are down, and these tools aren’t necessarily the best now.


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 Copyright ©1999 SysTEmid. All rights reserved.

Last updated Sunday, April 25, 1999

Appendix A - 65c816 Instruction Set & Syntax

SEPSet Bits in Psep #$30
ADCAdd With Carryadc #$12
ANDLogical ANDand #$12
BITBit Testbit #$12
CMPCompare Accumulatorcmp #$12
CPXCompare X Registercpx #$12
CPYCompare Y Registercpy #$12
DECDecrement Accumulator or Memorydec $12
EORExclusive OR Accumulatoreor #$12
INCIncrement Accumulator or Memoryinc $12
LDALoad Accumulatorlda $12
LDXLoad X Registerldx #$12
LDYLoad Y Registerldy #$12
ORALogical OR Accumulatorora #$12
ROLRotate Left Acc or Memrol $12
RORRotate Right Acc or Memror $12
SBCSubtract With Carrysbc #$12
STAStore Accumulatorsta $12
STZStore X Registerstx $12
STYStore Y Registersty $12
CLRStore a 0 into Memoryclr $12
BCCBranch if Carry Clearbcc $601e5
BCSBranch if Carry Setbcs $601e5
BEQBranch if Equalbeq $601e5
BMIBranch if Minusbmi $601e5
BNEBranch if Not Equalbne $601e5
BPLBranch if Plusbpl $601e5
BRABranch Alwaysbra $601e5
BVCBranch if Overflow Clearbvc $601e5
BVSBranch if Overflow Setbvs $601e5
CLCClear the Carry Flagclc
CLDClear the Decimal Flagcld
CLIClear the Interrupt Flagcli
DEXDecrement X Registerdex
DEYDecrement Y Registerdey
INXIncrement X Registerinx
INYIncrement Y Registeriny
NOPNo Operationnop
PLAPop Accumulatorpla
PLPPop Pplp
PLXPop X Registerplx
PLYPop Y Registerply
SEDSet Decimal Flagsed
SEISet Interrupt Flagsri
TAXTransfer Accumulator to Xtax
TAYTransfer Accumulator to Ytay
TSXTransfer S to Xtsx
TXATransfer X to Accumulatortxa
TXSTransfer X to Stxs
TXYTransfer X to Ytxy
TYATransfer Y to Accumulatortya
XCEExchange Carry w/ Emulation Bitxce
BRKBreak Point Instructionbrk #$12
CSPCall System Procedurecsp #$12
JMPJump to New Locationjmp $1234
JSRJump to Subroutinejsr $1234
MVNBlock Move (decrement)mvn $1234
MVPBlock Move (increment)mvp $1234
RTIReturn From Interruptrti
RTLReturn From Long Subroutinertl
RTSReturn From Short Subroutinerts
HLTHalt the Clockhlt
WAIWait for Interruptwai
SWASwap Accumulatorswa

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 SysTEmid