News:

Don't forget to visit the main site! There's lots of helpful docs, patches, and more!

Main Menu

[ASM] What does BRK accomplish? And other.

Started by dewhi100, May 12, 2017, 09:17:28 PM

Previous topic - Next topic

dewhi100

1) "Software Interrupt", okay. What for? I know that in higher level languages, "break" is primarily used to escape loops, but I'm dubious about it's usage here. The 65816 Reference says it causes the PC to be pulled from... somewhere. The stack? Some sort of special memory? What exactly is going on here?

2) Immediately before BRK, I see "29FF" - AND A with #FF, according to the reference material. #FF is obviously one byte, so how does this work since A is in two-byte mode?

Scyzer

BRK and COP are both instructions that will call an interrupt routine. Each SNES game has a header which contains stuff like the region of the game, the title, and a bunch of interrupt addresses. BRK and COP use these addresses. They also contain addresses for where to start on reset or startup, the NMI address, etc.
When the game encounters a BRK or COP, it will read the address set for that particular interrupt, then jump to that location and run whatever code is there.

In Super Metroid, almost all interrupt addresses lead to $808573, which is a simple routine which just loops to itself over and over (effectively stopping the game). This is to prevent the SNES from completely crashing by forcing the game into a state where it cannot corrupt anything in the SNES registers. When this occurs, the most noticable symptom is that the HUD becomes transparent (because the code which switches the layer priorities at certain points when drawing VRAM no longer runs, thus the HUD is drawn with the same properties as all of layer 3. ie, transparent.) This means that whenever such a crash does occur, the emulator/SNES itself has not actually crashed, and is still functioning perfectly; it's just the game running the same instruction over and over, never leaving and effectively stopping gameplay.

You can change the addresses for BRK and COP to another location, so that when the game encounters these instructions it will run that code instead.
Both BRK and COP have 1 byte arguments (for eg, BRK #$FF or COP #$23), however this argument does nothing within the routine it calls. It can however be read by accessing the stack from within the routine (LDA $0000,S). Remember that these are NOT normal routines when called via BRK or COP, and you cannot use RTS or RTL to return to the previous location; you must use RTI (return to interrupt), which will jump back to the code immediately following the BRK or COP. All registers should be pushed and pulled around these interrupts to prevent any "contamination" to the parent routine.

Honestly, unless you intend to do something with your game which absolutely needs to have a higher routine priority to anything else (excep the NMI routine of course), you shouldn't be messing with the BRK or COP interrupts, as they are very effective at preventing errors from crashing and breaking the SNES hardware.

dewhi100

Thanks, Scyzer. Actually, I just came across a BRK (I think) while disassembling pre-existing code (@ 82:237E, which seems to be checking for Morph Ball) and got curious. I didn't realize that the BRK argument gets a parameter, now I see how it can be more useful.

Scyzer

BRK and COP are never run intentionally during normal SM gameplay. It's very likely that the routine you found it in has a JSR / JSL with extra arguments which may appear as instructions, or the routine switches between 8 and 16 bit registers and you / your disassembler didn't account for that.

dewhi100

Quote from: Scyzer on May 13, 2017, 12:58:54 PM
BRK and COP are never run intentionally during normal SM gameplay. It's very likely that the routine you found it in has a JSR / JSL with extra arguments which may appear as instructions, or the routine switches between 8 and 16 bit registers and you / your disassembler didn't account for that.

Likely this... looking back at the documentation, instructions like ADC are listed as having 1-byte arguments, but I just noticed the footnotes explaining that they thake 2 bytes depending on accumulator size.

Thanks again!

dewhi100

#5
New wrinkle... the code I'm following explicitly sets the processor register to #30 which puts X and A into 8-bit mode (the reference states this only happens if emulation mode is enabled but I'm assuming it is at this point, otherwise why this instruction?). Unfortunately, I get more 00 instructions if reading '29 FF 00' etc. as two-byte instructions. What's happening here?

Pardon my pseudocode.

91:82D9

PHP, PHB, PHK, PLB ;;guessing this is to make sure we are doing R/W to the same bank we're reading code from?
REP #30
JSR $8304

~~~

91:8304
LDA $0A1F
29 FF 00    ;;uh oh...




Furthermore: what does AND 00FF accomplish if we're in 8-bit? It seems useful only for masking the high byte of a two-byte value.

Scyzer

#6
REP #$30 turns OFF (REset Processor) the bits for A, X and Y in the Processor register, meaning they are 16 bit.
SEP #$30 (SEt Processor) turns them ON, meaning they are 8 bit.

Basically whenever you see REP, it means 16 bit. If you see SEP, it means 8 bit. #$10 is A, #$20 is X/Y, #$30 is A/X/Y. REP and SEP can actually affect any bits in the Processor register, but there are also other instructions specifically for those bits (such as CLC, SEC, SED, CLD, CLV, etc), so you'll never seen anything other than REP / SEP #$10, #$20 or #$30 in a normal SM ROM.

dewhi100

Phew, now it all makes sense. Can't believe I misread that. Thanks.

dewhi100

New question: Are the first and second instruction in this code pointless?


$8B8A75 A5 2A       LDA $2A    [$7E:002A]
$8B8A77 EB          XBA
$8B8A78 A5 2D       LDA $2D    [$7E:002D]


Load A, swap the high and low bytes of A (I think), then load to A again... overwriting all our hard work?

Scyzer

Depends if the accumulator is running in 8 or 16 bit. If 16, then yes, it's completely dumb.
In 8 bit, LDA $2A only changes the lower 8 bits of A, while leaving whatever is in the upper 8 bits unchanged. XBA then swaps the lower and upper bits, so $2A is now in the upper 8 bits. LDA $2D then loads that data into the lower 8 bits.

The result is that $2A is in the upper 8 bits of A, and $2D is in the lower bits. Instructions like XBA, TAX, TXA, TAY, TYA do not care whether the game is running in 8 or 16 bit; they always perform 16 bit operations.