SESSION 2:
Table of Contents:
- Blocks
- X and Y
- Processor Flags
- The Stack
- Hijacking
- Debugging
- Assignments
Blocks:
Blocks, in the newest version of BTSD,
always follow this format:
Codedb $42
JMP MarioBelow : JMP MarioAbove : JMP MarioSide
JMP SpriteV : JMP SpriteH : JMP MarioCape : JMP MarioFireball
JMP TopCorner : JMP BodyInside : JMP HeadInside
MarioBelow:
MarioAbove:
MarioSide:
TopCorner:
BodyInside:
HeadInside:
SpriteV:
SpriteH:
MarioCape:
MarioFireball:
RTL
The
db $42 indicates that we're using the newest block version, with the added offsets. Each JMP tells the game exactly what code runs whenever Mario hits the block from below, above, or if a fireball hits the block, and so on. RTL always ends the blocks.
The first JMP tells the game what code runs when Mario hits the block from below, hence the label name "MarioBelow." The second JMP tells the game what code runs when Mario hits the block from above, leading to the label name "MarioAbove," and so on. The important thing to know is that the label name does not actually matter -- only the order of the JMPs matters!
So, for example, consider this block:
Codedb $42
JMP Main : JMP Main : JMP Main2
JMP Return : JMP Return : JMP Return : JMP Return
JMP Main : JMP Main : JMP Main
Main:
INC $0DBF ; Increase Mario's coins
RTL
Main2:
INC $0DBE ; Increase Mario's lives
Return:
RTL
When Mario hits the block from below, the game executes the first JMP, which means that Mario's coins will be increased. When Mario hits the block from above, it executes the second JMP, also increasing Mario's coins. However, when Mario hits the block from a side, it executes the third JMP, increasing Mario's lives instead. The same logic applies to every other JMP.
X and Y:
Definitions:
- CPX - Compares a value with X
- CPY - Compares a value with Y
- db - Inserts a direct byte
- dd - Inserts a direct double word
- DEX - Decrements X by one
- DEY - Decrements Y by one
- dl - Inserts a direct long
- dw - Inserts a direct word
- Indexing - Addressing an address with an offset of either X or Y
- INX - Increments X by one
- INY - Increments Y by one
- Loop - Executing code more than once
- LDX - Loads a value into X
- LDY - Loads a value into Y
- STX - Stores X to an address
- STY - Stores Y to an address
- TAX - Transfers A to X
- TAY - Transfers A to Y
- TXA - Transfers X to A
- TXY - Transfers X to Y
- TYA - Transfers Y to A
- TYX - Transfers Y to X
X and Y are two more registers that the S-CPU has -- they're like less functional accumulators in some case (none of these opcodes can use long addressing, and addition/subtraction can't directly be done on X and Y), but they are able to do things that the accumulator cannot.
Loading and storing values with X and Y is the same as loading and storing values with A.
LDX and
LDY load a value into X and Y, respectively, and
STX and
STY store X and Y into an address, respectively:
Code LDX #$01 ; Load $01 into X
STY $19 ; Store Y into $19
You can also compare values with X and Y, like the accumulator, with
CPX and
CPY, respectively:
Code CPX #$01 ; If X is $01,
BEQ label ; Branch to okay
CPY $19 ; If Y is the value of address $19,
BEQ label ; Branch to okay
label:
The X and Y registers can also be incremented and decremented by one using
INX,
INY,
DEX, and
DEY:
Code DEX ; Decrement X by one
INY : INY ; Increment Y by two
DEY : DEY : DEY : DEY ; Decrement Y by four
However, math is not directly possible on the X and Y registers, and you might see that repeating the decrementing or incrementing opcodes is highly inefficient. We can use
some of the transfer opcodes (
TAX,
TAY,
TXA,
TXY,
TYA, and
TYX) to achieve our goal:
Code TXA ; Transfer X to A
CLC
ADC #20 ; Add 20 to A
TAX ; Transfer A back to X
With all of the transfer opcodes, the register denoted by the second letter is transferred to the register denoted by the third letter (T
AX => Transfer
A to
X). Also, the first register's value is not modified:
Code LDX #$20 ; Load $20 into X
TXA ; Transfer X to A
; X is still $20 here, but A is also $20
So, what exactly is the point of these registers? First of all, Y is used as the high byte of the acts-like setting in custom blocks. Since $1693 is the RAM address for the low byte of the acts-like setting, we can use this code to make our block solid (i.e., act like a cement block, which is $0130):
Code LDY #$01 ; Set high byte to $01
LDA #$30
STA $1693 ; Set low byte to $30, so our block acts like $0130 in total
Second, the X and Y registers can be used for indexing, and they are called the index registers for this reason. Indexing is where the values of X and Y are added to the address that you use. To better explain, here is an example:
Code LDX #$02 ; Load $02 into X
LDA $157C,x ; LDA $157C+x = LDA $157C+$02 = LDA $157E
Most of the opcodes involving A can be indexed by X and Y, although addressing coverage varies (for example,
LDA $7E0000,x exists, but
LDA $7E0000,y doesn't). Some of the opcodes involving X can be indexed by Y, and vice versa -- simply try and see what does and doesn't exist.
The main use for indexing is in tables or loops. Tables contain direct bytes, and we load different bytes from them using indexing by X or Y. To specify direct bytes, we use
db, as in this example:
Codetable:
db $1A,$2B,$3C
Bigger values can be inserted using
dw,
dl, and
dd, which are two bytes, three bytes, and four bytes, respectively:
Codetable:
dw $0100,$0300,$0F00
dl $017AB0,$B103A8,$FFEE11
Here is an example of the use of tables -- suppose I wanted to give Mario coins depending on what powerup he has, and then clear his powerup. Without tables, one would have to do this:
Code LDA $19 ; If Mario is small,
BEQ small ; Branch to small
CMP #$01 ; If Mario is big,
BEQ big ; Branch to big
LDA #$20
CLC
ADC $13CC
STA $13CC ; Give $20 coins here (Mario is caped or fiery)
RTL
big:
LDA #$10
CLC
ADC
STA $13CC ; Give $10 coins
RTL
small:
RTL ; Give no coins
However, tables make this task easy:
Code LDX $19 ; Load Mario's powerup into X
LDA table,x
CLC
ADC $13CC
STA $13CC ; Set coins to give based on Mario's powerup
STZ $19 ; Clear Mario's powerup
RTL
table:
db $00,$10,$20,$20
If Mario has no powerup ($19 = $00), then
LDA table,x would be equivalent to
LDA table+$00 =
LDA #$00. If Mario is big ($19 = $01), then
LDA table,x would be equivalent to
LDA table+$01 =
LDA #$10, and so on.
The other main use of the index registers is looping (executing code more than once), especially when combined with indexing. Take this example:
Code LDA #$20
LDX #$00
loop:
STA $00,x ; Store A into address $00+x
INX ; Increment X by one
CPX #$20 ; If X is not $20,
BNE loop ; Branch to loop
The red code is executed exactly $20 times -- why? Each time, at the end of the loop, it increments X. If X hasn't reached $20, then it'll keep running that code. The overall effect of this code is storing zero to addresses $00-$1F, in effect, replicating this code:
Code LDA #$20
STA $00 ; Store A into $00
STA $01 ; Store A into $01
STA $02 ; ...
* ... *
STA $1F ; Store A into $1F
As you can see, looping is a major space optimization. However, it decreases speed -- "unrolling" the loop would mean going from the first code to the second code, increasing speed but severely increasing space used. This is the drawback of using a loop.
Processor Flags:
Definitions:
- ADC - Add with carry
- BVC - Branch if overflow set
- BVS - Branch if overflow clear
- c - Carry flag
- CLC - Clears the carry flag
- CLV - Clears the overflow flag
- d - Decimal flag
- e - Emulation flag
- i - Disable-IRQ (or interrupt) flag
- n - Negative flag
- m - Accumulator width flag
- REP - Resets processor flags
- ROL - Rotate bits left with carry
- ROR - Rotate bits right with carry
- SBC - Subtract with borrow (opposite of carry)
- SEC - Sets the carry flag
- SEP - Sets processor flags
- v - Overflow flag
- x - Index width flag
- XCE - Exchanges carry flag with emulation flag
- z - Zero flag
The SNES has another register called the processor flags register. They're a group of flags which are altered by most opcodes, and also influence what some opcodes do. The register can be represented in this form:
e nvmxdizc, where each letter is an individual bit. Here are what each of the letters represent:
- e - Emulation flag. If this flag is set, then the S-CPU will act like its predecessor, the 6502. It is very rarely useful, but it important to note that when the SNES first starts up, it will be in emulation mode. One can use XCE to exchange this flag with the carry flag, which will be discussed a bit later.
- n - Negative flag. If any operation results in a negative number ($80-$FF), then this flag is set. Otherwise, it's cleared. For example:
Code LDA #$80 ; Sets n flag
LDA #$00 ; Clears n flag
CLC
ADC #$90 ; Sets n flag, since A is now $90, which is negative
LDX #$FF ; Sets n flag
- v - Overflow flag. If a non-decrement/increment arithmetic with both numbers being of the same sign results in a new number of a different sign, then this flag will be set. Otherwise, it's cleared. For example:
Code LDA #$7F
CLC
ADC #$20 ; Sets v flag, since A went from $7F to $9F, which is negative
CLV ; Clears v flag
LDA #$20
CLC
ADC #$10 ; Clears v flag
LDA #$7F
INC ; Does not set v flag
CLV can be used to clear the v flag manually.
- m - Accumulator width flag. If it's set, then A will be 8-bit. Otherwise, it will be 16-bit. If A is 16-bit, then the requirement for being negative is now $8000-$FFFF, and all operations involving A will be 16-bit. For example:
Code LDA #$2000
STA $00
; $00 = $00, $01 = $20
Note that the order is reversed because of the little-endian order! Little-endian is where the most significant bytes are stored at the end (i.e., $2000 is stored as 00 20).
Also, be careful to use opcodes with the right number of bytes. This may cause crashes if you do not do this:
Code ; A is 16-bit
LDA #$20
STA $00
- x - Index width flag. If it's set, then X/Y will be one byte. Otherwise, it will be two bytes. This acts roughly the same way as the m flag.
- d - Decimal flag. If it's set, then arithmetic operations will become decimal-like. This should never be used since calculations can be done entirely in hex. To set and clear decimal mode, SED and CLD are used, respectively.
- i - IRQ (interrupt) disable flag. If it's set, then it'll disable IRQs -- not important for now.
- z - Zero flag. If an operation results in a zero, then this flag will be set. Otherwise, it's cleared. For example:
Code LDA #$00 ; Sets z flag
LDA #$01 ; Clears z flag
DEC ; Sets z flag, since A is now $00
- c - Carry flag. If addition results in a byte overflow, then this flag will be set. Otherwise, it's cleared. If subtraction results in a byte underflow, then this flag will be cleared. Otherwise, it's set. For example:
Code LDA #$FE
CLC
ADC #$20 ; Sets c flag
CLC
ADC #$10 ; Clears c flag, since the addition did not byte overflow
LDA #$20
SEC
SBC #$30 ; Clears c flag
SEC
SBC #$10 ; Sets c flag, since the subtraction did not byte underflow
CLC and SEC can be used to clear or set the carry flag, respectively.
ADC and SBC actually add or subtract depending on the carry flag. ADC adds the carry flag as well as the operand, and SBC subtracts the opposite of the carry flag as well as the operand:
Code SEC
ADC #$20 ; Add #$20 + 1 to A
CLC
SBC #$20 ; Subtract (#$20 + 1) from A
Using this fact, one can simulate 16-bit addition or subtraction. 16-bit addition is simulated this way:
Code LDA $94
CLC
ADC #$20
STA $94
LDA $95
ADC #$00
STA $95
If the low byte addition overflows, then this sets the carry flag. Thus, the ADC #$00 afterwards will actually add an extra one, leading to the correct 16-bit value. If the low byte addition doesn't overflow, then the carry flag is cleared, meaning the ADC #$00 afterwards just does nothing. The same sort of logic applies to the simulation of 16-bit subtraction.
ASL and LSR modify the carry bit, as well. They actually shift the bits (e.g., #%00110001 => #%00011000). The seventh bit of the operand is shifted into the carry bit with ASL, and the zeroth bit of the operand is shifted into the carry bit with LSR:
Code LDA #$80
ASL ; Sets c flag
LDA #$01
LSR ; Sets c flag
ROL and ROR are full bit shifts -- they shift carry into the zeroth and seventh bit, respectively:
Code LDA #$22
SEC ; (#%00100010 1)
ROL ; (#%01000101 0)
where the last number is the carry flag.
Now, how does one exactly modify, for example, the m or x flags?
REP resets all of the processor flags corresponding to each bit in the operand. That is, consider this:
Code ; Processor flags: xxxxxxxx
REP #$30 ; REP #%00110000
; Processor flags: xx00xxxx
SEP sets all of the processor flags corresponding to each bit in the operand:
Code ; Processor flags: xxxxxxxx
SEP #$30 ; REP $%00110000
; Processor flags: xx11xxxx
The CMP, CPX, and CPY opcodes actually do subtraction, modifying the processor flags but
not the actual registers' values. The branch opcodes actually branch based on processor flags. BNE branches if the z flag is cleared, BEQ branches if the z flag is set, BCC branches if the carry flag is cleared, BCS branches if the carry flag is set, BPL branches if the n flag is cleared, and BMI branches if the n flag is set. There are two extra opcodes,
BVC and
BVS, which branch if the v flag is cleared or set, respectively:
Code LDA #$20
CMP #$1F ; $20-$1F => set c flag
BCS greater_than ; Since carry is set, branch to greater_than
greater_than:
LDA $19 ; Clear/set z flag depending on $19
BEQ zero ; Branch to zero if z flag is set
zero:
The Stack:
Definitions:
- Data bank - The bank used in absolute addressing ($xxxx)
- PEA - Pushes the 16-bit operand onto the stack
- PEI - Pushes the value of the address onto the stack
- PHA - Pushes A onto the stack
- PHB - Pushes the data bank onto the stack
- PHK - Pushes the program bank onto the stack
- PHP - Pushes processor flags onto the stack
- PHX - Pushes X onto the stack
- PHY - Pushes Y onto the stack
- PLA - Pulls from the stack into A
- PLB - Pulls from the stack into the data bank
- PLP - Pulls from the stack into the processor flags
- PLX - Pulls from the stack into X
- PLY - Pulls from the stack into Y
- Program bank - The bank that the current code is in
- Program counter - The lower bytes of the location of the current code
- Stack pointer - Where the next pushed value onto the stack will go
- Stack relative - Addressing a part of the stack via the stack pointer
The stack is a sort of temporary storage solution for the S-CPU. We can use a stack of papers on a secretary's desk as an analogy. You can only (easily) add papers onto the top of the stack, which is called "pushing." You can only (easily) take papers off from the top of the stack, which is called "pulling."
To push A, X, Y, or the processor flags onto the stack,
PHA,
PHX,
PHY, and
PHP are used. To pull from the stack into A, X, Y, or the processor flags,
PLA,
PLX,
PLY, and
PLP are used. When a register is pushed onto the stack, its value is not changed:
Code LDA #$20
PHA ; Pushed A, topmost value is now $20, A is still $20
PLX ; Pulled topmost value into X, X is now $20.
These opcodes push two bytes if their corresponding registers are 16-bit.
The most important use of the stack is short-term preservation of the registers. As you may recall, the Y register is used as the high byte of the acts-like setting in custom blocks. This means that modifying the register is quite hazardous; indeed, many blocks make this mistake and act like, for example, tile 125 instead of tile 25 in some cases.
Code PHY ; Push Y -- now, Y can be freely changed
LDY #$20
LDY #$10
DEY
TAY
TXY
PLY ; Pull Y, now the acts-like setting is the same
The number of bytes pushed and the number of bytes pulled
must match. Attempting to pull too many bytes will cause the stack to run out of paper, leading to bad behavior as there is no more stack to pull from. Likewise, attempting to push too many bytes will cause the stack of paper to overflow, causing more bad behavior:
Code PHA
PHX
PHA
PHP
RTL ; This is bad!
PLA
PLX
RTL ; This is also bad!
The stack is also important for absolute addressing. Absolute addressing uses the so-called data bank register (DBR) in order to generate the full long address to use. The program bank register (PBR) is the current bank that the code is in, and the program counter (PC) is the lower bytes of the location that the code is in.
It is not required that DBR = PBR. One uses
PHB and
PLB to push or pull DBR. The PBR can be pushed via
PHK, but there is no meaningful "PLK." Thus, to correctly use absolute addressing, one needs to have DBR = PBR:
Code PHK ; Push program bank
PLB ; Pull into data bank
However, one should also preserve the data bank (otherwise, there could be some odd glitches):
Code PHB ; Preserve data bank
PHK ; Push program bank
PLB ; Pull into data bank
* code *
PLB ; Restore previous data bank
You'll see the above formation of code a lot in sprites and if you dig around in quite a few patches.
Suppose you wanted to quickly push a 16-bit value onto the stack.
PEA is ideal for this. The syntax is strange, though -- it appears that it would push the value of a 16-bit address, but it does not:
Code PEA $7F7F ; Pushes the constant $7F7F onto the stack
If you wanted to push the 16-bit value of a direct page address onto the stack, then you'd use
PEI:
Code REP #$20
LDA #$1337
STA $00 ; Set $00 to $1337.
SEP #$20
PEI ($00) ; This will push the value of $00, which is $1337, onto the stack.
Now, for how the stack works. The stack is actually stored in WRAM, and there is a stack pointer register (SP) which points to the where the
next pushed value will go. If SP = $01FE, then pushing A will push A onto $01FE. After each push, the SP is decreased by how many bytes were pushed. After each pull, the SP is increased by how many bytes were pushed:
Code ; SP = $01FF, A = 8-bit
PHA ; SP = $01FE
PHB ; SP = $01FD
PLA ; SP = $01FE
PLA ; SP = $01FF
With this comes a new addressing mode, stack relative addressing. It's essentially the same as indexing with X and Y, except ,s is used instead:
Code LDA #$20 ; SP = $01FF
PHA ; SP = $01FE
LDA $01,s ; LDA $01+s = LDA $01+$01FE = LDA $01FF = previous pushed value
PLA ; SP = $01FF
LDA $00,s ; LDA $00+s = LDA $00+$01FF = LDA $01FF = what was just pulled
LDA $00,s should never be used -- it can be highly unpredictable, but the reason for it being so will be explained much later.
Hijacking:
Definitions:
- Autoclean - Removes RATS tags inserted by freecode/freedata
- Freecode - Automatically finds freespace suitable for code and inserts a RATS tag, as well
- Freedata - Automatically finds freespace suitable for data and inserts a RATS tag, as well
- Freespace - Parts of the ROM which are not used at all
- Hijack - A modification of the ROM to make it execute custom code
- Org - Changes the current SNES address in the assembler
- NOP - Does nothing, wastes a byte
- RATS tag - Protects an area of the ROM
Hijacking is pretty much essential if you want to make patches. It's basically where parts of the (SMW) ROM are changed to execute custom code instead of the normal SMW code. To begin the process of hijacking, you use the
org assembler directive, which changes the assembler's current SNES address:
Codeorg $00F606 ; SNES address for the assembler is now $00F606
org $008000 ; But now, it's $008000
Any ASM after an org will
overwrite the stuff at the current SNES address --
LDA $1337 would overwrite the three bytes at $008000 if appended to the bottom of the code above. This is why knowing byte counts is so important -- one needs to know exactly how much to overwrite, and how much the code inserted overwrites.
To make hijacking actually possible, we will use all.log, which can be found
here. This is a disassembly of a clean SMW ROM; essentially, it's the source code for the game. Now, suppose we want to make a patch which resets Mario's coins and score when he dies. You may know that $00F606 is the address for the "kill Mario" subroutine. However, you may not know that when Mario dies from falling into a pit, the game jumps to $00F60A. So, search for "00F60A" in the document -- you'll find this:
CodeCODE_00F60A: A9 09 LDA.B #$09 ; \
CODE_00F60C: 8D FB 1D STA.W $1DFB ; / Change music
With
org $00F60A, you'll start overwriting the code here. The longer the code, the more that's overwritten. Thus, the solution is to use freespace -- you'll overwrite unused ROM data with your code instead of ROM data that is actually used:
Codeorg $00F60A
autoclean JSL main ; Automatically clean the freespace that our patch uses
freecode ; Search for freespace
main:
RTL
The JSL is
still overwriting stuff, though! How big is a JSL? The opcode takes up one byte, and the operand takes up three bytes, as long addresses are three bytes long. Thus, our JSL takes four bytes. The JSL partially overwrites the two instructions that were already there:
CodeCODE_00F60A: 22 XX XX XX JSL $XXXXXX
CODE_00F60E: 1D ORA $XXXX,x
As you can see, we have problems. The S-CPU will interpret the four bytes as the JSL, but then it'll interpret the next byte as an ORA absolute,x. So, what can we do about this? We can overwrite that extra byte with a
NOP, which literally does nothing -- it only takes up space:
CodeCODE_00F60A: 22 XX XX XX JSL $XXXXXX
CODE_00F60E: EA NOP
That portion is now much less hazardous, and we are now free to write the code which clears Mario's coins and score:
Codeorg $00F60A
autoclean JSL main
NOP
freecode
main:
STZ $0DBF ; Zero Mario's coins
STZ $0F34
STZ $0F35
STZ $0F36 ; Zero Mario's score
RTL
But wait! We're still not correct! The code that we overwrote definitely did something -- it set the death music. But now, we're not doing that, so if we still want that function, we have to
replicate it, which we can usually do by just sticking the original code at the end of our code:
Codeorg $00F60A
autoclean JSL main
NOP
freecode
main:
STZ $0DBF ; Zero Mario's coins
STZ $0F34
STZ $0F35
STZ $0F36 ; Zero Mario's score
LDA #$09
STA $1DFB ; Replicate the overwritten code!
RTL
The process is similar for other types of patches. If you decide to hijack a branch, then it will be a little bit tricky to restore its effects -- you'll have to resort to JMLs instead of using JSL. If you hijack a comparison, then the comparison has to be at the end of your custom code.
Debugging:
Definitions:
- Breakpoint - An address which triggers the debugger when it is executed, read from, or written to
Programmers run into lots of problems when they code.
Assembly programmers run into even more, as it is a very low-level language -- that's why we have debuggers. For the purposes of this summary, we'll be using
this debugger.
You load a ROM just like any ordinary version of snes9x. When you first load a ROM, the debug console will pop up:
Start the ROM by clicking "Run." To see the hex data, click "Hex Data," and a window like this will show:
There are several options for the memory type, but we will only be concerned about
RAM. If you switch to show RAM and play through SMW, you'll see that the values update in real time. You can set the range from 7E0019 to 7E0019 to see Mario's powerup in real time:
You can also freeze values by selecting the RAM region you want to freeze and clicking the "Freeze" button:
To undo this, simply click "Unfreeze."
Finally, you can modify the RAM addresses by simply typing in values into.
One of the other important features of the debugger is stepping through opcodes/instructions one by one. To do this
effectively, we're going to need breakpoints. Breakpoints are addresses that stop (normal) execution of the game when they are either executed, written to, or loaded from, allowing the debugger to step through the individual opcodes.
To add a breakpoint, click the "Breakpoints" button on the debug console. This window should pop up:
Add an entry for 7E0DBF, and check the "Write" checkbox. Then, in the game, collect a coin. Execution should immediately halt, and some text will be printed on the debug console:
Code$00/8F25 EE BF 0D INC $0DBF [$00:0DBF] A:FF01 X:0003 Y:0018 P:envMXdiZc
$00/8F25 is the SNES address of the current code. The hex values afterwards are the hex values for the instruction. The actual instruction is printed afterwards. The number after A is the value of A, the number after X is the value of X, the number after Y is the value of Y, and the letters after P indicate the processor flag status. If the letter is capitalized, that means the corresponding processor flag is set; otherwise, it's cleared.
This text is printed before the instruction is executed:
Code$00/9045 A2 00 LDX #$00 A:FF05 X:0003 Y:0018 P:envMXdizc
As you can see, X hasn't been updated to be $0000 quite yet.
Anyway, to step through instructions one-by-one, click the "Step Into" button. If you're stepping through instructions and you come across a JSR or a JSL and you want to skip it, then click the "Step Over" button. If you want to get out of the current subroutine, then click the "Step Out" button. If you want to skip the current opcode, then click the "Skip Op" button --
this can have a lot of side effects. If you want more information, then you can uncheck "Squelch" under the "CPU Trace Options" menu. The new output will be in this form:
Code$00/8F25 EE BF 0D INC $0DBF [$00:0DBF] A:FF01 X:0003 Y:0018 D:0000 DB:00 S:01F9 P:envMXdiZc HC:1024 VC:022 FC:40 I:00
Chances are, though, that you won't know anything more except for the value after DB and S, which are the DBR and SP, respectively.
You can also dump RAM addresses using the "Dump RAM" button on the debug console. You can edit the values of the registers using the "Edit Registers" button.
One more important feature is the S-CPU tracing feature, meaning that all of the instructions that the S-CPU executes will be logged to a file, specifically (ROM name)#####.log in the Logs folder. To begin logging, just click the "CPU" checkbox under the "Logging" panel in the debug console.
Assignments:
If you want, you may PM me your results to some of these assignments. Don't post them in this thread.
- Write code which gives Mario an appropriate number of lives depending on his powerup and then remove his powerup.
- Write code which moves every sprite to the left one pixel in the X direction.
- Write a patch which hijacks the hurt routine, making it give Mario coins instead.
- Find out where the code is that gives Mario a 1-up.
Follow Us On