MarioFanGamer's in-depth Tutorial for Creating Patches.
Hello, my name is MarioFanGamer and I teach to you how to create patches.What you need:
- a text editor,
- a SMW ROM,
- an assembler (Asar),
- knowledge, how to insert patches, obviously,
- SMW all.log or any other SMW disassembly,
- the ROM Map (rather optional but pretty helpful) and
- 65c816 ASM knowledge (at least some basics).
As you see, this tutorial covers how to create patches for the assembler Asar in the programming language 65c816 ASM with the help of all.log and the ROM Map.
Creating Patches:
Step 1: Creating hex edit patches.
This tutorial covers how to do this and if you understand, how to do it, you're done with step.Important note:
It is recommend to not submit hex edit to the patches section. The reason is pretty simple: They can be done by anyone who has got at least some knowledge creating of these. In additions, it's also recommend to mostly write in ASM rather entering the bytes manually unless you need to change values on one byte and/or change tables. Instead, submit them to the Tweaks section. There are a few exceptions, though, but only if the hex edits in question are on a large scale.
Step 2: Let's get serious.
Warning: This chapter is pretty long. It is even divided by sub-chapters.Foreword
Now, since you know how to create hex edit patches, we'll go a bit deeper and create patches which does a more complex job. To start with it, you need to find an idea and try to materialize it.Pro tip: Don't start with something complex. Start rather easy and try to create more complex patches later on. The only exception is if you're experienced with sprites, especially if you made complex sprites (no blocks because the more complex ones usually requires patches, sprites / generators or LevelASM) but this tutorial also covers for beginners.
Finding a good spot to hijack
Enough talking. Let's say, you want to create patch which changes the reward for getting 100 coins. The reward becomes a mushroom but if you are already big it comes to the item box and if that is not empty, you then get a 1up instead. Code is here:Code
LDA $19 ;\ Check, if Mario is small BEQ .small ;/ LDA $0DC2 ;\ Check, if Mario has got nothing in the item box BEQ .no_item ;/ INC $18E4 ; Increment lives queue RTL .no_item INC $0DC2 ; Increase reserve item LDA #$0A ;\ STA $1DF9 ;| Play "get item" SFX... LDA #$0B ;| ... and play "item to item box" SFX STA $1DFC ;/ RTL .small INC $19 ; Increase powerup LDA #$02 ;\ Get mushroom animation STA $71 ;/ LDA #$2F ;\ STA $1496 ; | Set up animation and lock timer STA $9D ;/ LDA #$0A ;\ Play "get item" SFX STA $1DF9 ;/ RTL
For that, we need to know where is the code for the reward and what to do. Go to the ROM map and search something for the life reward.
If you searched correctly, you notice that $008E1A handles the status bar and $008F2C handles the number of necessary coins for an 1up.
This means that around this address, there is the code for the 1up reward.
Now, open all.log and search a good spot for the hijack. The perfect spot would be address $008F2C. To put a code into this, create a new ASM file and use this:
Code
lorom org $008F2F
org $xxxxxx
is the position of the program counter or the ROM address you want to edit and lorom
means that the ROM type is... well, a lorom (one of the various SNES mappings). On xkas, you also need to put header
on top of each file unless you're using an unheadered ROM but Asar ignores it and check for the extension instead (.sfc for unheadered and .smc for headered).Okay, can we go on? And where should I put the code? Below the
org
? No! You'll overwrite important codes. Instead, you need to put it into freespace. That's the expanded area (assuming, the ROM is expanded, which... likely is).There are also plenty of unused space in the vanilla ROM (especially in banks $0E and $0F but these areas are used by LM and a few other tools like AddmusicK) but it is not always the best idea (in fact, some patches used to do that but that was changed at some point).
To access the freespace area, you need to jump to that place. This is done by an
autoclean JSL Labelname
(needn't to be called "Labelname", obviously) and after the org
s you put either freecode
or freedata
(more details later). Here it is, what would look like if you did it correctly:Code
lorom org $008F2F autoclean JSL new_reward freedata new_reward: LDA $19 ;\ Check, if Mario is small BEQ .small ;/ LDA $0DC2 ;\ Check, if Mario has got nothing in the item box BEQ .no_item ;/ INC $18E4 ; Increment lives queue RTL .no_item INC $0DC2 ; Increase reserve item LDA #$0A ;\ STA $1DF9 ;| Play "get item" SFX... LDA #$0B ;| ... and play "item to item box" SFX STA $1DFC ;/ RTL .small INC $19 ; Increase powerup LDA #$02 ;\ Get mushroom animation STA $71 ;/ LDA #$2F ;\ STA $1496 ; | Set up animation and lock timer STA $9D ;/ LDA #$0A ;\ Play "get item" SFX STA $1DF9 ;/ RTL
Now, we'll take a closer look at some commands:
freespace
is one of the main features of Asar. It searches automatically for freespace and it also puts the RATS tag automatically. It must be always put with either ram
or noram
. The alternatives for these are freecode
and freedata
(the former is the same as freespace ram
and the other would be not to hard to guess). See section 4 for more details on these.It can be also attached with
align
, cleaned
and static
. The former means that the code is always put at the beginning of a bank, the middle means that Asar won't complain that freespace is 'leaking' (only, and I repeat, only use it if Asar complains about freespace leaks even though it actually shouldn't) and with the latter, the freespace area can neither move nor change its size once the patch is applied.If you use
freespace
, you always must use autoclean
. This one will clean the freespace block. It must be always put into the first 512KB area (banks $00-$0F), always in front of a JML
, JSL
, dl
or a given address and even then, it'll only clean the expanded area. Be careful if the area Asar cleans is located at the end of a freespace block: It gets some problems there.Also, only one
autoclean
is needed unless you use a code which jumps to different freespace areas.Newer versions of Asar allows to enter a value after
freespace
which means that Asar will search for that number in the ROM for freespace but since freespace is by default $00 (and Asar is set to that number anyway), you likely won't need this on SMW.Restoring and NOP
'ing/BRA
'ing out
This also is not ready yet. There are still two things which must be done first: The overwritten code must be restored and you need to
NOP
out unused bytes. A JSL
takes up to four bytes. This means you need to take a look at $008F2F and you notice, you replaced INC $18E4
. However, this one just takes three bytes. You need take a look at the next opcode which is a LDA $0DFB
.That can't be simple replaced, though, because this is an opcode which loads A. As such, you need to preserve it (either by scratch RAM or stack) but it is not always recommend since you likely waste some bytes and/or cycles. Depending on how many bytes you need to restore, an often better alternative is to copy a few more lines up to the point where you can simply leave A as it is. We use various ways for comparison, starting the latter.
Taking a look at $008F32 reveals this:
Code
LDA $0DFB SEC : SBC #$64 STA $0DFB
This means, the restored code takes up nine bytes and 4 + 1 + 2 + 4 = 12 bytes. You then place it at the beginning of the custom code like this:
Code
lorom org $008F2F autoclean JSL new_reward freedata new_reward: LDA $0DFB ;\ SEC : SBC #$64 ; | Restored STA $0DFB ;/ LDA $19 ;\ Check, if Mario is small BEQ .small ;/ LDA $0DC2 ;\ Check, if Mario has got nothing in the item box BEQ .no_item ;/ INC $18E4 ; Increment lives queue RTL .no_item INC $0DC2 ; Increase reserve item LDA #$0A ;\ STA $1DF9 ;| Play "get item" SFX... LDA #$0B ;| ... and play "item to item box" SFX STA $1DFC ;/ RTL .small INC $19 ; Increase powerup LDA #$02 ;\ Get mushroom animation STA $71 ;/ LDA #$2F ;\ STA $1496 ; | Set up animation and lock timer STA $9D ;/ LDA #$0A ;\ Play "get item" SFX STA $1DF9 ;/ RTL
However, if you take a closer look, you will notice that you can optimise the code in two ways:
- Because the restored piece takes up to nine bytes, a
LDA $xxxx
takes up three bytes and there are threeRTL
s, you can simple putLDA $0DFB
in front of these. With that, you save up to 8 cycles because the code only executes oneLDA $xxxx
. - A
PHA
uses three, aPLA
four cycles. In other words, you can save two bytes (at the cost of adding 7 cycles) if you putLDA $0DFB
(3 bytes, 4 cycles) at first, thenPHA
(1 byte, 3 cycles) it and put aPLA
in front of eachRTL
(3 bytes but 4 cycles only because only one is executed) which results in total 10 bytes, 11 cycles.
That means (using the former way), you get this:
Code
lorom org $008F2F autoclean JSL new_reward freedata new_reward: LDA $19 ;\ Check, if Mario is small BEQ .small ;/ LDA $0DC2 ;\ Check, if Mario has got nothing in the item box BEQ .no_item ;/ INC $18E4 ; Increment lives queue LDA $0DFB ; Restored RTL .no_item INC $0DC2 ; Increase reserve item LDA #$0A ;\ STA $1DF9 ;| Play "get item" SFX... LDA #$0B ;| ... and play "item to item box" SFX STA $1DFC ;/ LDA $0DFB ; Restored RTL .small INC $19 ; Increase powerup LDA #$02 ;\ Get mushroom animation STA $71 ;/ LDA #$2F ;\ STA $1496 ; | Set up animation and lock timer STA $9D ;/ LDA #$0A ;\ Play "get item" SFX STA $1DF9 ;/ LDA $0DFB ; Restored RTL
The few bytes after the
JSL
still can be executed as code and need to be skipped / NOP
'd out. That isn't hard to do. All what you need to do is to put some NOP
s and likely a BRA
if there are at least 2 bytes after the JSL
(note that a BRA
takes even while doing nothing three cycles whereas two NOP
s four). How you are actually doing doesn't matter but I use the BRA
way with the help of labels for the tutorial because that's the easiest to handle.Using the least efficient restoring to show how to restore more then one byte:
Code
lorom org $008F2F autoclean JSL new_reward BRA $00 freedata new_reward: LDA $19 ;\ Check, if Mario is small BEQ .small ;/ LDA $0DC2 ;\ Check, if Mario has got nothing in the item box BEQ .no_item ;/ INC $18E4 ; Increment lives queue LDA $0DFB ; Restored RTL .no_item INC $0DC2 ; Increase reserve item LDA #$0A ;\ STA $1DF9 ;| Play "get item" SFX... LDA #$0B ;| ... and play "item to item box" SFX STA $1DFC ;/ LDA $0DFB ; Restored RTL .small INC $19 ; Increase powerup LDA #$02 ;\ Get mushroom animation STA $71 ;/ LDA #$2F ;\ STA $1496 ; | Set up animation and lock timer STA $9D ;/ LDA #$0A ;\ Play "get item" SFX STA $1DF9 ;/ LDA $0DFB ; Restored RTL
Now, you don't actually need to
NOP
out / branch over the unused bytes in this case. If you look around the code, you'll notice that the code which decreases the coins by 100 only gets executed if $13CC isn't 0 and $13CC gets decremented every frame. And because the coins rarely go over 100 (in fact, they shouldn't even), you simply can put a STZ $0DFB
into the now unused area and still have three bytes left like here:Code
lorom org $008F2F autoclean JSL new_reward STZ $0DFB BRA Skip org $008F38 Skip: freedata new_reward: LDA $19 ;\ Check, if Mario is small BEQ .small ;/ LDA $0DC2 ;\ Check, if Mario has got nothing in the item box BEQ .no_item ;/ INC $18E4 ; Increment life queue LDA #$05 ;\ Play "get 1up" SFX STA $1DF9 ;/ RTL .no_item INC $0DC2 ; Increase reserve item LDA #$0A ;\ STA $1DF9 ;| Play "get item" SFX... LDA #$0B ;| ... and play "item to item box" SFX STA $1DFC ;/ RTL .small INC $19 ; Increase powerup LDA #$02 ;\ Get mushroom animation STA $71 ;/ LDA #$2F ;\ STA $1496 ; | Set up animation and lock timer STA $9D ;/ LDA #$0A ;\ Play "get item" SFX STA $1DF9 ;/ RTL
You see, restoring codes is sometimes a bit tricky but hey, that's optimising codes for you!
Step 3: Branching a hijack.
Branching is a bit more complicated. You can't easily create a subroutine. Instead, you use aJML
. This also means that you can't use a RTL
as it's designed to be used with a JSL
. Instead, you just use a second or more JML
s.As you probably know, the main difference between a
JML
and a JSL
(and JMP
and JSR
, respectively) is that JSL
and JSR
pushes the address of the next opcode minus one*. That way, RTS
s and RTL
s can determine, where to jump. JMP
and JML
don't use that function.This changes both their uses (the former can use code which is easily reused, the latter not) as well as their speed (accessing the stack is slow so any subroutine and return opcode is slower than two jump opcodes).
*Yes, that means that you can replace the old address with a new one but that way is pretty ugly and too impractical anyway.
There is also another advantage: You don't need to
NOP
out rubbish so you can simply jump to non-rubbish directly.Step 4: Bank change and RAM mirrors.
When you created sprites, you'll often use this code:Code
PHB PHK PLB JSR Main PLB RTL Main:
This one is used for changing the program bank to allow 16-bit addressing of the current bank. The data bank doesn't get updated when you use
JML
, JSL
and RTL
so you have to do it manually.The main downside of the "data bank wrapper" is that you can't put the code into banks +$40, though. That is because banks $40+ don't features RAM mirrors unlike as banks $00-$3F.
RAM mirrors allows us to use 16-bit/absolute addressing* for WRAM (or rather for $7E0000-$7E1FFF, the rest is unmirrored), registers, enhancement chips addresses and the occasional cartridge RAM such as SRAM, depending on the mapping, ($2100-$7FFF, not everything is used) and still access the data in the current bank in absolute addressing.
Registers and most enhancement chips addresses are even only located in the mirrors.**
That one is very important because for one, 24-bit/long addressing always take one more byte than absolute addressing but more importantly, the 65816, the language of the CPU, doesn't support long addressing for every opcode such as indexed Y.
*The bank for direct page/8-bit addressing, however, is always $00, no matter whether it is in the RAM mirror banks or not or even in RAM.
**Of course, they still are mapped, more specifically in bank $00 as well as every other bank which has got the WRAM mirrors which means you still can use long addressing for these stuff and e.g. access $2100 through $002100.
There is a reason why
freecode
and freedata
exists. Again, freecode
tries put the code into the first 2MB whereas freedata
tries to put the code (or more likely, data because these don't require any RAM mirrors) into banks $40+.Sometimes, it is more efficient to not change the bank or change it to a bank to a different location (e.g. banks $7E/$7F). A long address only takes one more cycle and byte than an absolute address at the same conditions and direct page always uses bank $00.
If one use few long addresses (i.e. 4 or 7, depending whether the wrapper uses a
JSR
or not) at tables in the same freespace area and the opcodes aren't non-24-bit only or, for obvious reasons, one don't even use these (like the example patch), then it's better to not change the data bank, not to mention that it also allows you to use freedata
.Note: Asar generally assumes that the data bank is set to the same value as the bank byte of the code even if it is not actually the case. As such, it uses 16-bit addresses to tables by default. This can be fixed by either putting a
.l
right after the opcode (e.g. LDA.l Label
) or use bank noassume
which disables the label optimisation for ROM addresses.Step 5: V-blank and H-blank.
There are cases where you want to create a patch which changes graphics or do some other stuff. However, you can't easily do this within the main code. Instead, you have to make use of blanks. There are three of them: F-blank, v-blank and h-blank and each of them can access all (f-/v-blank) / most (h-blank) registers without many problems.These are somewhat tricky to access which is why the SNES provides you a way to easily access v- and h-blank with the use of interrupts. There also is f-blank, which disables the screen for the time being altogether, but it results in black scanlines on the screen which should be avoided as much as you can and therefore isn't part of the scope of this tutorial.
Interrupts are generally separated in two kinds: Regular interrupts aka Interrupt ReQuests (IRQ) and Non-Maskable Interrupts (NMI) which mostly differ on whether they can be blocked by the processor or not (IRQ can be, NMI can't). These simply tell that the currently running code should halt and jump to a different code.
On the SNES, the NMI is hardcoded to fire whenever v-blank fires off and for most intents and purposes, you can treat them synonymously. This is where large parts of the screen is refreshed, considering only there you can access OAM and VRAM.
IRQ, on the other hand, is programmable, both by hardware (e.g. SA-1 makes use of interrupts, for example) but also by software in the sense that you can set, at which scanline and at which horizontal position the IRQ should be fired (though SMW only fires IRQ at a specific scanline, not at a specific pixel). The primary purpose of IRQ on the SNES is therefore to run code in h-blank such as how SMW uses it to keep the status bar in place.
Keep in mind that you need to wait for h-blank if you use IRQ. To do that, use
Code
- BIT $4212 BVS - - BIT $4212 BCS -
which causes the CPU to wait until h-blank is active and then inactive again. The rest is timing where you waste some time (don't use $4212, though) until the electron beam is aligned correctly (just before h-blank, to be precise) and prepare for the IRQ writes. You then can start writing to the registers.
As you can see, handling IRQ to write to h-blank is very difficult and should be done so with care, though it could have been improved if SMW didn't just fire IRQ at a specific scanline but at a specific pixel instead (simplified speaking, of course).
NMI starts at $00816A and IRQ starts at $008374. Hijacking is limited and you also need to trace the code so you hijack the correct place so be careful with that. In fact, you're typically better off using UberASM for NMI, though you still need to hijack IRQ manually unless you're using new versions of SA-1 Pack (i.e. from version 1.32 onwards) in which case you just have to set the pointer, where the IRQ code runs.
Important note: The codes must as fast run as possible. Okay, it can be a bit slower but still not too much. That is because v- and h-blank time is limited, else you get screen flickering and/or black scanlines (depending which blank got overflown).
Because of that, if you have got the choice between size and cycles (e.g. rolled or unrolled loops), use the latter.
Another thing you should keep in mind is that NMI and IRQ are interrupts i.e. they temporarily halt the main loop until they finish which can easily cause crashes (particularly scratch RAM).
And if you are one of the ZSNES users: Don't use it, at least for testing these stuff. It never was really good about accuracy and the registers are one of the reasons. Use BSNES instead.
Same goes for SNES9x users here but this one is at least more forgiving.
Ultimately, the punchline is that handling v- and particular h-blank requires great care. Feel free to ask in the forums or on Discord on whether you hijacked IRQ correctly as well as look how other patches handle IRQ.
Some important/helpful information and other stuff:
This is just a minor chapter which explains some of Asar's more important features. Read the manual (located in docs in Asar's ZIP) for more information.Displaying text on the console window.
One of the most effective assembler commands are theprint
commands. It outputs the text you have written in the ASM file. It's mostly used for debugging but there a few other uses for regular users too. There are also following subcommands:pc
is used to determine the current PC whenprint
was used, useful to find out where the code is and to determine the position of subroutinesbytes
display the totally assembled bytes (even outside of freespace, use generallyfreespaceuse
instead)reset bytes
just resets the byte counter for written bytesfreespaceuse
prints, how much freespace the patch usesdec(value)
andhex(value)
display the value you entered in the brackets as a decimal and hexadecimal number, respectively (can be combined with the others).
Major changes on the first 512KB
It is also possible to overwrite some codes of the original game because you either replace them with a different code, said code becomes through your patch unused or you use (as explained) some unused parts as freespace. In these cases, it is recommend to usewarnpc $xxxxxx
. When you overstep that address, the assembler will warn you that your code is too large. This doesn't work when the PC is already set behind the overstepped area for obvious reasons.Clean freespace in freespace area
You can put more then one freespace command in a patch. How to clean it is simple except it actually isn't. If the additional freespace is accessed through a code in the original ROM, fine, butautoclean
doesn't work in freespace area.Instead you use
prot
. It must be put right after a freespace command and the labels have to be separated by a comma so you get something like this: prot label1, label2, ..., label80
(yes, 80 is the limit, but you can have up to 128 freespace commands and you likely won't need that many freespace commands anyway). Confused? Take a look at this code:Code
org $009642 autoclean JSL RandomCode freecode prot RandomData RandomCode: ; Code freedata RandomData: ; Data
That's how
prot
works.Note: A freespace block cannot clean itself. It has to be cleaned from somewhere else i.e.
Code
freespace prot Labelname Labelname:
doesn't work.
Running code every frame
From time to time, you'll end up in a situation where you have to run a code for every frame and just need a place to inject it. Sounds easy, right? Except not really: Most of these patches can work just as well for UberASM which allows you to write codes for every frame. Sure, the insertion will be difficult for normal users (yet) but once hijack conflicts arise, the trouble will be worse. Way worse. This is especially important for NMI hijacks.Here are some examples on if you have to hijack the level main code:
- Consider whether the hijack is really necessary. There are instances where the code has to run late in the frame (and UberASM doesn't support late levelASM yet) but 99.9% of the time, you can resort to UberASM instead.
- Be careful with your hijacks. There are easy ways to cause conflicts with major resources. Here is a list of hijacks which run almost every frame in levels:
- $00A1E4 is where the level code really starts (before that is the message box checker which shouldn't be hijacked). However, most of it is the goal and pause code.
- $00A242 is the last frame which runs without pause but it's also hijacked by UberASM so that's out.
- $00A295 mostly contains
JSL
s which are good hijacks but these never run during the Mode 7 bosses. - $00A2A9 is where code runs for every frame (when not paused) in every instance of a level (and the title screen). Check out the individual subroutines for more potential hijack locations.
- $008E1A is the status bar routine which gives you more code to hijack. However, custom status bars are very common which makes it somewhat of a bad location to hijack.
- $008DC4 used to be very common hijack but it's something which you should never hijack for no reasons. Not only was it a far too common hijack (to the point conflicts could easily arise with multiple patches) but it was even a very bad spot to hijack to begin with. As a homework lesson, it's something you should figure out on your own.
- Are you really sure you need to hijack the code?
Preprocessor function
Similar to compilers, assembler features them too (or at least something similar) and Asar is not an exception. Keep in mind that this section only scratches the surface and you should read Asar's manual (included in the included docs) for more information.Defines and Macros
You can skip that if you already worked with blocks and sprites.They practically exists in all compilers and assemblers and work pretty similar with some exceptions. They are defined like this:
!Define = 1
(the "!" is mandatory and basically acts like #define
in various compilers). For spaces, you have to put quotes around the arguments like here: !Define = "PHP : PHB : PLB"
. That way, you can change mass variables and addresses pretty easily and also allows a more user-friendly-to-edit-patch.It is also possible to redefine defines (as seen on some patches) but be careful with that as that should be used sparingly (e.g. when you define LoROM and SA-1 defines, see below).
Macros work a bit differently. You have to enter
macro
, a space, the name of your macro, brackets with your defines, on the next lines your code and finally, end the macro with endmacro
like here:Code
macro YourMacro(yourFirstDef, yourSecondDef, etc.) *your code* endmacro
Remember that the defines inside the code has to be put between the smaller than and greater than equal signs like
<yourFirstDef>
.Labels inside macros have to start with a question mark (i.e. instead of entering
yourLabel:
, you enter ?yourLabel:
instead). This only applies for labels to mark the (relative) position of the macro code - labels outside of macros needn't to be prefixed with a "?" because their position is absolute, not relative. Sub-, plus and minus labels in macros work just fine.This is because macros tell the assembler to put the same code with the given parameters as it is.
The callers for these macros are the percent-sign, macro name and then inside brackets the values / strings you enter like this:
%YourMacro(*yourFirstDef*, *yourSecondDef*, etc.)
.If you want to have for example a macro which does the org-autoclean-JSL-NOP on a single line you use this:
Code
macro autocleanJSL(hijack, destination, amount) org <hijack> autoclean JSL <destination> NOP <amount> endmacro
Lastly, you can also define labels by hand. For many parts, it's pure semantics except you can't redefine labels and that the values are always treated as 24-bit values. Nonetheless, it does make it clear in the code what is definitively an address (particularly ROM addresses) and what is a define which can be changed by a user.
Code
HurtPlayer = $00F5B7 KillPlayer = $00F5B7
Conditionals
Asar uses just like many compilers a kind of C-similar conditional syntax. Even the commands are pretty similar:if
: If the number/result it is zero or any negative number, Asar will skip the code on the same line / up to the next endif
(if there aren't any nested if
s, that is).else
: If the condition is not true, Asar will assemble this code instead.elseif
: If the condition on the main if
is not true, Asar will check if the condition on that line is true instead.endif
: This must be used at the end. The exception is if the code is put on the same line as the if
(e.g. if *condition* : *code*
works).Even the boolean conditions are the same or at least similar:
X == Y
- X is equal to YX != Y
- X is unequal to YX > Y
- X is smaller than YX < Y
- X is greater than YX >= Y
- X is equal to or smaller than YX <= Y
- X is equal to or greater than YX && Y
- If X and Y are trueX || Y
- If X or Y are truenot(X)
- Inverts X
That way, you can control patches more easily.
It is even mandatory for SA-1
Note: Prior to Asar 1.42,
elseif
was a bit broken.For readability, you can, if you want to, use the pointy brackets ("{" and "}") like in the C-like languages. Remember that while Asar ignores them, they are not treated as whitespace. As such, you can't put them "in the middle of opcodes" i.e.
NOP : { : RTS
works but LDA {$19}
doesn't.Source: Asar's manual.
Note: These brackets only exists for purely aesthetic reasons. They don't do anything.
Read from the ROM directly
Another of Asar strongest features arereadX($yyyyyy)
where X is any value from 1-4 and controls the amount of bytes Asar reads and yyyyyy is any ROM address. Use it to determine if the specific address got hijacked/used or not, to get the position of a code of an already existing hijack or if autoclean
is used on a movable area.Include files
Asar has got way to attach multiple files within a patch. These are calledincsrc
and incbin
. The difference between them are that the former includes a file to assemble (e.g. tables in Asar format, additional codes, files with macros and definitions, etc.) whereas the latter put the included data to the ROM as it is (e.g. graphics, already compiled codes, etc.).tl;dr To include files, use
incsrc
for codes and incbin
for binary data.Speaking of
incbin
files:You can also do also this:
incbin file.bin -> Labelname
. It kind of work as a macro for pushpc : freedata align : Labelname: incbin file.bin : pullpc
, but the implied freedata
at that opcode works a bit differently: You can include a file with a size of up to 65536 bytes or in different words: 2 SNES LoROM banks. You still have to manually use prot
, though.Note: You can also insert the file directly to an address, though the use of it is rarer because of the difficulty of insertion.
Preserving the current pc's value
If there are many ASM files (e.g. through a pack) and you need to hijack a different position other then the main patch, you should usepushpc
and pullpc
so you don't have to use multiple freespace blocks.SA-1
It is also possible to create patches for the enhancement chip SA-1. For a hybrid, use e.g. this at the beginning:Code
lorom if read1($00FFD5) == $23 !sa1 = 1 ; SA-1 base addresses !db = $3000 !addr = $6000 !bank = $000000 sa1rom else !sa1 = 0 ; Non SA-1 base addresses !db = $0000 !addr = $0000 !bank = $800000 endif
Now you just add a
|!db
to addresses from $0000-$00FF and |!addr
to addresses from $0100-$1FFF. For example, $0063
(or any other address from $7E0000-$7E00FF) and $1025
(or any other address from $7E0100-$7E1FFF) become $0063|!dp
and $1025|!addr
, respectively.!bank
is used for ROM instead of RAM addresses. On regular ROMs which are 4 MiB large or smaller, the whole ROM (minus the last 512 KiB*) are mirrored through the banks +$80
(actually, the opposite is the case: The ROM data actually exists in banks $80-$FF and are mirrored through $00-$7D but for all intents and purpose, let's treat like the opposite is the case). Said mirror is also called the FastROM area which boosts the SNES a bit (by around 1/4 of its speed) if enabled. In order to achieve FastROM (besides activation), the codes have to run there (for some reason, don't ask me).SA-1 ROMs don't feature FastROM because the SA-1 ROM type is something of an ExHiROM (meaning it can be up to 8 MiB large) so the FastROM speed boost is unusable, though SA-1 runs much faster than FastROM anyway so you ultimately don't need FastROM.
Note: SNES's speed is still unaffected but that shouldn't be a problem in most cases.
*Don't worry, they're not lost. It's just that these bytes are only located at the FastROM area. And yes, the reason is SRAM and WRAM.
Note: Direct page (addresses like $42) needn't to be or'd with
|!db
. That is because direct page is already set to $3000 and is only necessary for direct page switching as well as indirect jumps.Note: Sprites and some other addresses use completely different addresses on SA-1. Please refer to SA-1 Pack's readme for more information. These addresses also can't be easily or'd as a result.
For SA-1-only patches, you need to (or can but this one works too) use
assert
and so check if the ROM is SA-1. The format is following: assert *condition*, "Text"
where *condition* is an if-like... well, condition where, if the condition is 0, assert
gets triggered and "Text" is the output.How do I get the current level number?
There are also stuff which are based of the level number. For this, you need to save the level number which is done by this code:Code
org $05D8B7 BRA + NOP #3 ;the levelnum patch goes here in many ROMs, just skip over it + REP #$30 LDA $0E STA !level ASL CLC ADC $0E TAY LDA.w $E000,Y STA $65 LDA.w $E001,Y STA $66 LDA.w $E600,Y STA $68 LDA.w $E601,Y STA $69 BRA + ORG $05D8E0 +
You probably know this from UberASM and in fact, this is where it was taken from.
Here is a libray with some ASM codes and macros.
RATS Tag
If you want to, you can write into freespace directly but depending on the patch, you need or needn't to use a RATS tag to protect the data. Here is the RATS tag:Code
db "S","T","A","R" ;db "ST","AR" ; This could be used instead too ;db $53,$54,$41,$52 ; Or that dw Code_End-Code_Start-1 dw Code_End-Code_Start^$FFFF-1
(Note:
"S","T","A","R"
has to be entered that way because Asar will complain if you have entered "STAR"
. That is no bug but rather a check if the patch was originally made for xkas. Newer versions will remove that check alongside Xkas compatibility in general.)Last notes:
I hope, this tutorial gets useful at some point. I know, it doesn't look very deep but remember that there aren't many functions with patches than like sprites or musics and that you have got much freedom instead.Basically, all of these stuff are enough to a create full patch whereas with sprites, you need to take care of this and that and w/e and then, there is this and that and w/e function.
The reason why I have created this tutorial is because most tutorials were created before Asar existed and a few more stuff. In addition, they don't go very deep.
Changelogs (in DD.MM.YY format):
15.08.16:
Improved a few things and added defines, macros and conditions. Thanks to Ladida for pointing out for them.
21.08.16 (-ish):
Fixed some minor errors.
05.11.16:
Added a chapter, how to handle
prot
, incsrc
and incbin
.Fixed spelling on some words, especially the goddamn "label"-"lable"-misspell. >_>
05.03.21:
(Wow, four and half years since I have written the tutorial.) Fixed spelling and grammar, added a proper examples to chapter 3, rewrote half of chapter 5, added information to define labels, added information about hijacking code, replaced <tt> with <code> and <kbd>.