summaryrefslogtreecommitdiff
path: root/src/libs/libgcnmultiboot.s
diff options
context:
space:
mode:
authorcamthesaxman <cameronghall@cox.net>2018-01-10 23:20:32 -0600
committercamthesaxman <cameronghall@cox.net>2018-01-10 23:20:32 -0600
commitb5667bd097347c1d3958fa8f9eb6d39788c0ff50 (patch)
tree29aca119bf365256255da97e8e768cd9216688a8 /src/libs/libgcnmultiboot.s
parentd4afe49b06a7ef2422839fcd0340c06c6a8e541b (diff)
refactor build system
Diffstat (limited to 'src/libs/libgcnmultiboot.s')
-rw-r--r--src/libs/libgcnmultiboot.s641
1 files changed, 641 insertions, 0 deletions
diff --git a/src/libs/libgcnmultiboot.s b/src/libs/libgcnmultiboot.s
new file mode 100644
index 000000000..8c8b94998
--- /dev/null
+++ b/src/libs/libgcnmultiboot.s
@@ -0,0 +1,641 @@
+@ This library can be used to download and execute a multi-boot image from
+@ a GameCube using the JOY Bus protocol over the link cable.
+
+ .include "include/macros.inc"
+ .include "constants/constants.inc"
+
+ .equiv GCMB_STRUCT_COUNTER1, 0x00
+ .equiv GCMB_STRUCT_COUNTER2, 0x01
+ .equiv GCMB_STRUCT_MBPROGRESS, 0x02
+ .equiv GCMB_STRUCT_SAVEDVCOUNT, 0x03
+ .equiv GCMB_STRUCT_KEYA, 0x04
+ .equiv GCMB_STRUCT_KEYB, 0x08
+ .equiv GCMB_STRUCT_KEYC, 0x0C
+ .equiv GCMB_STRUCT_BOOT_KEY, 0x10
+ .equiv GCMB_STRUCT_IMAGE_SIZE, 0x12
+ .equiv GCMB_STRUCT_SESSION_KEY, 0x14
+ .equiv GCMB_STRUCT_HASH_VAL, 0x18
+ .equiv GCMB_STRUCT_KEYC_DERIVATION, 0x1C
+ .equiv GCMB_STRUCT_BASE_DEST_PTR, 0x20
+ .equiv GCMB_STRUCT_CUR_DEST_PTR, 0x24
+ .equiv GCMB_STRUCT_SERIAL_INTR_HANDLER, 0x28
+
+ .equiv ROM_HEADER_NINTENDO_LOGO_OFFSET, 0x04
+ .equiv ROM_HEADER_NINTENDO_LOGO_LENGTH, 0x98
+ .equiv ROM_HEADER_NINTENDO_LOGO_END, 0xA0
+
+ .equiv MBPROGRESS_NONE, 0x00
+ .equiv MBPROGRESS_LOGO_CORRECT, 0x01
+ .equiv MBPROGRESS_READY_TO_BOOT, 0x02
+
+ .equiv GCMB_MAGIC_BOOTKEY_HASHVAL, 0xBB
+ .equiv GCMB_MAGIC_BOOTKEY, 0xBB
+ .equiv GCMB_MAGIC_COUNTER2, 0xCC
+ .equiv GCMB_MAGIC_KEYA, 0xDD
+ .equiv GCMB_MAGIC_KEYB, 0xEE
+ .equiv GCMB_MAGIC_KEYCDERIVATION, 0xFF
+
+ .syntax unified
+
+ .text
+
+ thumb_func_start GameCubeMultiBoot_Hash
+GameCubeMultiBoot_Hash: @ 81DCB38
+ push {r4,lr}
+ ldr r4, pool_HashVal
+ eors r3, r1
+ movs r2, 0x20
+
+GameCubeMultiBoot_Hash_Loop:
+ lsrs r3, 1
+ bcc GameCubeMultiBoot_Hash_SkipEor
+
+ eors r3, r4
+
+GameCubeMultiBoot_Hash_SkipEor:
+ subs r2, 0x1
+ bne GameCubeMultiBoot_Hash_Loop
+
+ pop {r4,pc}
+ thumb_func_end GameCubeMultiBoot_Hash
+
+ thumb_func_start GameCubeMultiBoot_Main
+@ void GameCubeMultiBoot_Main(struct GameCubeMultiBoot *mb)@
+GameCubeMultiBoot_Main: @ 81DCB4C
+ @ If there is no interrupt handler, skip counter manipulation
+ ldr r1, [r0, GCMB_STRUCT_SERIAL_INTR_HANDLER]
+ cmp r1, 0
+ beq GameCubeMultiBoot_Main_SkipCounters
+ @ Increment the second counter
+ ldrb r1, [r0, GCMB_STRUCT_COUNTER2]
+ adds r1, 0x1
+ strb r1, [r0, GCMB_STRUCT_COUNTER2]
+ @ If there is nothing more to do, bail out
+ ldrb r1, [r0, GCMB_STRUCT_MBPROGRESS]
+ cmp r1, MBPROGRESS_READY_TO_BOOT
+ beq GameCubeMultiBoot_Main_Return
+ @ Save current interrupt master register value
+ ldr r3, pool_InterruptRegs
+ ldrh r2, [r3, OFFSET_REG_IME - 0x200]
+ @ Disable all interrupts
+ movs r1, 0
+ strh r1, [r3, OFFSET_REG_IME - 0x200]
+ @ Increment the first counter, if it's less than or equal to 10.
+ ldrb r1, [r0, GCMB_STRUCT_COUNTER1]
+ cmp r1, 0xA
+ bgt GameCubeMultiBoot_Main_SkipCounter1Inc
+ adds r1, 0x1
+ strb r1, [r0, GCMB_STRUCT_COUNTER1]
+GameCubeMultiBoot_Main_SkipCounter1Inc:
+ @ Load the saved interrupt master register value (re-enables interrupts if they were enabled before)
+ strh r2, [r3, OFFSET_REG_IME - 0x200]
+GameCubeMultiBoot_Main_SkipCounters:
+ @ Initialise multiboot structures if required
+ bcs GameCubeMultiBoot_Init
+ @ Skip this section (check Nintendo logo) if the check has already passed
+ ldrb r1, [r0, GCMB_STRUCT_MBPROGRESS]
+ cmp r1, MBPROGRESS_NONE
+ bne GameCubeMultiBoot_Main_SkipLogoCheck
+ @ Bail out if no multiboot image data has been transferred yet
+ ldr r1, [r0, GCMB_STRUCT_CUR_DEST_PTR]
+ ldr r2, [r0, GCMB_STRUCT_BASE_DEST_PTR]
+ subs r1, r2
+ beq GameCubeMultiBoot_Main_Return2
+ @ Also bail out if not enough data has been transferred
+ cmp r1, ROM_HEADER_NINTENDO_LOGO_END
+ bcc GameCubeMultiBoot_Main_Return2
+ @ Compare the Nintendo logo of the transferred multiboot image header, with the one in the ROM image of the inserted cart
+ push {r4-r6}
+ movs r1, ROM_HEADER_NINTENDO_LOGO_LENGTH
+ adds r2, ROM_HEADER_NINTENDO_LOGO_OFFSET
+ ldr r4, pool_NintendoLogo
+GameCubeMultiBoot_Main_LogoCmpLoop:
+ ldm r2!, {r5}
+ ldm r4!, {r6}
+ cmp r5, r6
+ bne GameCubeMultiBoot_Main_LogoCmpEnd
+ subs r1, 0x4
+ bne GameCubeMultiBoot_Main_LogoCmpLoop
+ ldm r2!, {r5}
+ ldm r4!, {r6}
+ eors r5, r6
+ lsrs r5, 8
+ str r2, [r0, GCMB_STRUCT_BASE_DEST_PTR]
+GameCubeMultiBoot_Main_LogoCmpEnd:
+ pop {r4-r6}
+ @ Throw everything away if the logo data didn't match
+ bne GameCubeMultiBoot_Init
+ @ Logo matched, set the relevent multiboot progress bit
+ movs r1, MBPROGRESS_LOGO_CORRECT
+ strb r1, [r0, GCMB_STRUCT_MBPROGRESS]
+ @ XOR together KeyA and KeyB to get the initial multiboot image checksum value
+ ldr r1, [r0, GCMB_STRUCT_KEYA]
+ ldr r2, [r0, GCMB_STRUCT_KEYB]
+ eors r1, r2
+ str r1, [r0, GCMB_STRUCT_HASH_VAL]
+ @ ...also use it as the initial value for the image encryption session key. Algorithm is the same as the GBA BIOS multiboot: sessionkey = (initialvalue * 0x6177614b) + 1
+ ldr r2, pool_Kawa
+ muls r1, r2
+ adds r1, 0x1
+ str r1, [r0, GCMB_STRUCT_SESSION_KEY]
+GameCubeMultiBoot_Main_Return:
+ bx lr
+GameCubeMultiBoot_Main_SkipLogoCheck:
+ @ If this code is executed, then the logo check has passed, and the data being transferred in is encrypted.
+ @ Set up registers.
+ ldr r1, [r0, GCMB_STRUCT_CUR_DEST_PTR]
+ mov r12, r1
+ ldr r3, [r0, GCMB_STRUCT_HASH_VAL]
+ push {r4-r7}
+ ldr r4, [r0, GCMB_STRUCT_BASE_DEST_PTR]
+ ldr r5, pool_Kawa
+ ldr r6, [r0, GCMB_STRUCT_SESSION_KEY]
+ ldr r7, pool_HashVal
+GameCubeMultiBoot_Main_ImageDecryptHashLoop:
+ @ If there's no more data, break out of the loop
+ cmp r4, r12
+ bcs GameCubeMultiBoot_Main_ImageDecryptHashEnd
+ @ Get the next uint32
+ ldr r1, [r4]
+ @ Decrypt the ciphertext: plaintext = (ciphertext ^ sessionkey) + hashval
+ eors r1, r6
+ adds r1, r3
+ @ Save the current uint32 of plaintext and advance the pointer
+ stm r4!, {r1}
+ @ Advance the hashval with this uint32 of plaintext -- this is the same code as GameCubeMultiBoot_Hash.
+ eors r3, r1
+ movs r2, 0x20
+GameCubeMultiBoot_Main_HashLoop:
+ lsrs r3, 1
+ bcc GameCubeMultiBoot_Main_HashSkipEor
+ eors r3, r7
+GameCubeMultiBoot_Main_HashSkipEor:
+ subs r2, 0x1
+ bne GameCubeMultiBoot_Main_HashLoop
+ @ Advance the sessionkey with the usual algorithm: sessionkey = (sessionkey * 0x6177614b) + 1
+ muls r6, r5
+ adds r6, 0x1
+ b GameCubeMultiBoot_Main_ImageDecryptHashLoop
+GameCubeMultiBoot_Main_ImageDecryptHashEnd:
+ @ Save the new pointer, sessionkey, hashval
+ str r4, [r0, GCMB_STRUCT_BASE_DEST_PTR]
+ str r6, [r0, GCMB_STRUCT_SESSION_KEY]
+ pop {r4-r7}
+ str r3, [r0, GCMB_STRUCT_HASH_VAL]
+ @ Bail out if the image size is unknown
+ ldrh r1, [r0, GCMB_STRUCT_IMAGE_SIZE]
+ cmp r1, 0
+ bne GameCubeMultiBoot_Main_Return2
+ @ Bail out if no image data has been transferred
+ ldr r1, [r0, GCMB_STRUCT_CUR_DEST_PTR]
+ ldr r2, [r0, GCMB_STRUCT_BASE_DEST_PTR]
+ cmp r1, r2
+ bne GameCubeMultiBoot_Main_Return2
+ @ If KeyC hasn't been generated yet, go generate it
+ ldr r1, [r0, GCMB_STRUCT_KEYC]
+ cmp r1, 0
+ beq GameCubeMultiBoot_Main_GenerateKeyC
+ @ If the other side hasn't sent its boot key yet, bail out
+ ldrh r1, [r0, GCMB_STRUCT_BOOT_KEY]
+ cmp r1, 0
+ beq GameCubeMultiBoot_Main_Return
+ @ Save off LR so it doesn't get clobbered by the upcoming function call
+ mov r12, lr
+ @ Generate the real boot key, which is the checksum of a hardcoded value and KeyC
+ movs r1, GCMB_MAGIC_BOOTKEY_HASHVAL
+ ldr r3, [r0, GCMB_STRUCT_KEYC]
+ bl GameCubeMultiBoot_Hash
+ ldrh r1, [r0, GCMB_STRUCT_BOOT_KEY]
+ @ Restore the saved LR value
+ mov lr, r12
+ @ Compare the two boot keys (real and passed in), if they don't match then throw everything away
+ subs r1, r3
+ bne GameCubeMultiBoot_Init
+ @ The two boot keys matched, tell the caller that the image is ready to boot
+ movs r1, MBPROGRESS_READY_TO_BOOT
+ strb r1, [r0, GCMB_STRUCT_MBPROGRESS]
+ @ Nothing more to do, return.
+ bx lr
+GameCubeMultiBoot_Main_GenerateKeyC:
+ @ Save off LR so it doesn't get clobbered by the upcoming function call
+ mov r12, lr
+ @ KeyC = (SavedVCount << 24) - 1
+ ldrb r1, [r0, GCMB_STRUCT_SAVEDVCOUNT]
+ lsls r1, 24
+ subs r1, 0x1
+ str r1, [r0, GCMB_STRUCT_KEYC]
+ @ Hash the KeyC with the multiboot image checksum to generate the KeyC derivation material to be sent to the other side of the link
+ bl GameCubeMultiBoot_Hash
+ @ Make sure the sent KeyC derivation material contains a magic value so that the other side can detect it
+ lsls r3, 8
+ adds r3, GCMB_MAGIC_KEYCDERIVATION
+ @ Save off the KeyC derivation material and return to caller
+ str r3, [r0, GCMB_STRUCT_KEYC_DERIVATION]
+ bx r12
+GameCubeMultiBoot_Main_Return2:
+ bx lr
+ thumb_func_end GameCubeMultiBoot_Main
+
+ .align 2, 0
+
+pool_HashVal: .4byte 0xa1c1
+
+pool_Kawa: .ascii "Kawa" @ name of BIOS developer
+
+pool_NintendoLogo: .4byte RomHeaderNintendoLogo
+
+ thumb_func_start GameCubeMultiBoot_ExecuteProgram
+@ void GameCubeMultiBoot_ExecuteProgram(struct GameCubeMultiBoot *mb)@
+GameCubeMultiBoot_ExecuteProgram: @ 81DCC4C
+ @ If there's no multiboot image ready, just return to caller
+ ldrb r1, [r0, GCMB_STRUCT_MBPROGRESS]
+ cmp r1, MBPROGRESS_READY_TO_BOOT
+ bne GameCubeMultiBoot_ExecuteProgram_Fail
+ @ Disable interrupts
+ ldr r3, pool_InterruptRegs
+ movs r1, 0
+ strh r1, [r3, OFFSET_REG_IME - 0x200]
+ @ Jump to the real entry point of the multiboot image (past the image header), in ARM mode
+ ldr r1, pool_MultiBootLoadAddr
+ adds r1, 0xC0
+ bx r1
+GameCubeMultiBoot_ExecuteProgram_Fail:
+ bx lr
+ thumb_func_end GameCubeMultiBoot_ExecuteProgram
+
+ thumb_func_start GameCubeMultiBoot_Init
+@ void GameCubeMultiBoot_Init(struct GameCubeMultiBoot *mb)@
+GameCubeMultiBoot_Init: @ 81DCC60
+ ldr r3, pool_InterruptRegs
+
+@ Save IME register.
+ ldrh r2, [r3, OFFSET_REG_IME - 0x200]
+
+@ Disable interrupts.
+ movs r1, 0
+ strh r1, [r3, OFFSET_REG_IME - 0x200]
+
+@ Set the handler to the "Stop" routine.
+@ Unless the first command that is received is a device reset command, the
+@ "Stop" routine will be executed and no further commands will be processed.
+ adr r3, GcMbIntrHandler_Stop
+ str r3, [r0, GCMB_STRUCT_SERIAL_INTR_HANDLER]
+
+ ldrb r3, [r0, 0x3]
+ push {r3}
+ ldrb r3, [r0, 0x1]
+ push {r0,r3}
+
+ adds r3, r0, 0
+ adds r3, GCMB_STRUCT_BASE_DEST_PTR
+
+@ clear all but the last 3 fields of the struct
+GameCubeMultiBoot_Init_ClearStructLoop:
+ stm r0!, {r1}
+ cmp r0, r3
+ blo GameCubeMultiBoot_Init_ClearStructLoop
+
+ pop {r0,r3}
+ lsrs r3, 1
+ strb r3, [r0, 0x3]
+ pop {r3}
+ strb r3, [r0, 0x1]
+
+ ldr r3, pool_SerialRegs
+
+@ Turn off JOY Bus mode.
+ lsls r0, r3, 10
+ strh r0, [r3, OFFSET_REG_RCNT - 0x120]
+
+@ Turn on JOY Bus mode.
+ movs r0, 0xC0
+ lsls r0, 8
+ strh r0, [r3, OFFSET_REG_RCNT - 0x120]
+
+@ Init JOY Bus registers.
+ movs r0, 0x47
+ strh r0, [r3, OFFSET_REG_JOYCNT - 0x120]
+ strh r1, [r3, OFFSET_REG_JOYSTAT - 0x120]
+
+ ldr r3, pool_InterruptRegs
+
+@ Acknowledge serial interrupt.
+ movs r0, INTR_FLAG_SERIAL
+ strh r0, [r3, OFFSET_REG_IF - 0x200]
+
+@ Enable serial interrupt.
+ ldrh r1, [r3, OFFSET_REG_IE - 0x200]
+ orrs r1, r0
+ strh r1, [r3, OFFSET_REG_IE - 0x200]
+
+@ Restore IME register.
+ strh r2, [r3, OFFSET_REG_IME - 0x200]
+
+ bx lr
+ thumb_func_end GameCubeMultiBoot_Init
+
+ non_word_aligned_thumb_func_start GameCubeMultiBoot_HandleSerialInterrupt
+@ void GameCubeMultiBoot_HandleSerialInterrupt(struct GameCubeMultiBoot *mb)@
+GameCubeMultiBoot_HandleSerialInterrupt: @ 81DCCAA
+ ldr r3, pool_SerialRegs
+
+@ Acknowledge reset/receive/send flags.
+ ldrh r1, [r3, OFFSET_REG_JOYCNT - 0x120]
+ strh r1, [r3, OFFSET_REG_JOYCNT - 0x120]
+
+ movs r2, 0
+ strb r2, [r0]
+
+ ldr r2, [r0, GCMB_STRUCT_SERIAL_INTR_HANDLER]
+ cmp r2, 0
+ beq GameCubeMultiBoot_HandleSerialInterruptDone
+
+ lsrs r1, 1 @ was a device reset command received?
+ bcs GameCubeMultiBoot_BeginHandshake @ branch if so
+
+ mov pc, r2
+
+ .align 2, 0
+
+@ Zero the status and the interrupt handler pointer.
+@ Commands from the GameCube will not be processed after this is executed
+@ unless GameCubeMultiBoot_Init() is called again.
+GcMbIntrHandler_Stop:
+ movs r2, 0
+ strh r2, [r3, OFFSET_REG_JOYSTAT - 0x120]
+
+GameCubeMultiBoot_SetInterruptHandler:
+ str r2, [r0, GCMB_STRUCT_SERIAL_INTR_HANDLER]
+
+GameCubeMultiBoot_ReadVCount:
+ ldr r3, pool_RegDispstat
+ ldrh r1, [r3, OFFSET_REG_VCOUNT - OFFSET_REG_DISPSTAT]
+ strb r1, [r0, 0x3]
+
+GameCubeMultiBoot_HandleSerialInterruptDone:
+ bx lr
+
+GameCubeMultiBoot_BeginHandshake:
+ @ Throw away anything that got sent
+ ldr r1, [r3, OFFSET_REG_JOY_RECV - 0x120]
+ @ Send the game code, the other side of the link must send back the same game code
+ ldr r1, pool_RubyUSAGameCode
+ str r1, [r3, OFFSET_REG_JOY_TRANS - 0x120]
+ movs r1, 0x10
+ strh r1, [r3, OFFSET_REG_JOYSTAT - 0x120]
+ @ Use the saved VCount value to provide 8 bits of entropy for KeyB
+ ldrb r1, [r0, GCMB_STRUCT_SAVEDVCOUNT]
+ strb r1, [r0, GCMB_STRUCT_KEYB + 1]
+ @ If a multiboot image has been transferred at least enough such that the Nintendo logo check has passed, stop everything.
+ ldrb r1, [r0, GCMB_STRUCT_MBPROGRESS]
+ cmp r1, 0
+ bne GcMbIntrHandler_Stop
+ @ Set the image destination pointers.
+ ldr r1, pool_MultiBootLoadAddr
+ str r1, [r0, GCMB_STRUCT_BASE_DEST_PTR]
+ str r1, [r0, GCMB_STRUCT_CUR_DEST_PTR]
+ @ Set the new interrupt handler.
+ adr r2, GcMbIntrHandler_CheckGameCodeSent
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_CheckGameCodeSent: @ 81DCCEC
+ lsls r1, 31
+ bcc GcMbIntrHandler_Stop @ stop if send failed
+ bmi GameCubeMultiBoot_CheckHandshakeResponse @ branch if receive is complete
+
+@ If the response hasn't been fully received yet,
+@ check again upon the next interrupt.
+ adr r2, GcMbIntrHandler_CheckHandshakeResponse
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_CheckHandshakeResponse: @ 81DCCF8
+ lsrs r1, 1 @ is receive complete?
+ bcc GcMbIntrHandler_Stop @ stop if not
+
+GameCubeMultiBoot_CheckHandshakeResponse:
+ ldr r1, [r3, OFFSET_REG_JOY_RECV - 0x120]
+ ldr r2, pool_RubyUSAGameCode
+ cmp r1, r2
+ bne GcMbIntrHandler_Stop @ stop if the GameCube didn't reply with the same game code
+ @ Use the saved VCount value to provide another 8 bits of entropy for KeyB.
+ ldrb r1, [r0, GCMB_STRUCT_SAVEDVCOUNT]
+ strb r1, [r0, GCMB_STRUCT_KEYB + 3]
+ adr r2, GcMbIntrHandler_ReceiveKeyA
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_ReceiveKeyA: @ 81DCD0C
+ lsrs r1, 1 @ is receive complete?
+ bcc GcMbIntrHandler_Stop @ branch if not
+ ldr r1, [r3, OFFSET_REG_JOY_RECV - 0x120]
+ @ make sure top 8 bits of the received value is the KeyA magic number, stop if KeyA is invalid
+ lsrs r2, r1, 24
+ cmp r2, GCMB_MAGIC_KEYA
+ bne GcMbIntrHandler_Stop
+ @ save received KeyA
+ str r1, [r0, GCMB_STRUCT_KEYA]
+ @ use the second GameCubeMultiBoot_Main() counter as another 8 bits of entropy for KeyB
+ ldrb r1, [r0, GCMB_STRUCT_COUNTER2]
+ strb r1, [r0, GCMB_STRUCT_KEYB + 2]
+ movs r2, 0
+ movs r3, 0
+ ldr r1, [r0, GCMB_STRUCT_KEYB]
+ lsrs r1, 8
+ @ make sure KeyB is valid (other side of the link is supposed to check KeyB too), if it's not then change the byte that was just set so it is
+GameCubeMultiBoot_KeyBCheckLoop:
+ lsrs r1, 1
+ adcs r2, r3
+ cmp r1, 0
+ bne GameCubeMultiBoot_KeyBCheckLoop
+ cmp r2, 0xE
+ bgt GameCubeMultiBoot_KeyBSaveNewByte
+ cmp r2, 0x7
+ bge GameCubeMultiBoot_KeyBCheckEnd
+ movs r1, 0xFF
+GameCubeMultiBoot_KeyBSaveNewByte:
+ strb r1, [r0, GCMB_STRUCT_KEYB + 2]
+GameCubeMultiBoot_KeyBCheckEnd:
+ @ add in the KeyB magic number and send off KeyB
+ ldr r1, [r0, GCMB_STRUCT_KEYB]
+ adds r1, GCMB_MAGIC_KEYB
+ ldr r3, pool_SerialRegs
+ str r1, [r3, OFFSET_REG_JOY_TRANS - 0x120]
+ movs r1, 0x30
+ strh r1, [r3, OFFSET_REG_JOYSTAT - 0x120]
+ @ set new interrupt handler
+ adr r2, GcMbIntrHandler_CheckKeyBSent
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_CheckKeyBSent: @ 81DCD4C
+ lsls r1, 31
+ bcc GcMbIntrHandler_Stop @ stop if send failed
+ bmi GameCubeMultiBoot_CheckImageSizeResponse @ branch if receive is complete
+ adr r2, GcMbIntrHandler_CheckImageSizeResponse
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_CheckImageSizeResponse: @ 81DCD58
+ lsrs r1, 1 @ is receive complete?
+ bcc GcMbIntrHandler_Stop @ branch if not
+GameCubeMultiBoot_CheckImageSizeResponse:
+ ldr r1, [r3, OFFSET_REG_JOY_RECV - 0x120]
+ ldr r2, GameCubeMultiBoot_MaximumImageSizeUInt32s
+ cmp r1, r2
+ bhs GcMbIntrHandler_Stop
+ adds r1, 0x1
+ adds r1, r1
+ strh r1, [r0, GCMB_STRUCT_IMAGE_SIZE]
+ ldrb r1, [r0, GCMB_STRUCT_MBPROGRESS]
+ cmp r1, 0
+GcMbIntrHandler_StopIfNotEqual:
+ bne GcMbIntrHandler_Stop
+ ldr r1, pool_MultiBootLoadAddr
+ str r1, [r0, GCMB_STRUCT_BASE_DEST_PTR]
+ str r1, [r0, GCMB_STRUCT_CUR_DEST_PTR]
+ adr r2, GcMbIntrHandler_CheckImageResponse
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_CheckImageResponse: @ 81DCD7C
+ lsrs r1, 1 @ is receive complete?
+ bcc GcMbIntrHandler_Stop @ branch if not
+ ldr r2, [r0, GCMB_STRUCT_CUR_DEST_PTR]
+ movs r1, 0x4
+ ands r1, r2
+ adds r1, 0x8
+ lsls r1, 2
+ strh r1, [r3, OFFSET_REG_JOYSTAT - 0x120]
+ @ get the recieved uint32
+ ldr r1, [r3, OFFSET_REG_JOY_RECV - 0x120]
+ @ put it in the current destination pointer and advance that pointer
+ stm r2!, {r1}
+ @ save off the advanced pointer
+ str r2, [r0, GCMB_STRUCT_CUR_DEST_PTR]
+ @ decrease the image size (in uint32s)
+ ldrh r1, [r0, GCMB_STRUCT_IMAGE_SIZE]
+ subs r1, 0x1
+ strh r1, [r0, GCMB_STRUCT_IMAGE_SIZE]
+ @ branch away if the transfer is not yet complete
+ bne GameCubeMultiBoot_ReadVCount
+
+GcMbIntrHandler_SendCounter2:
+ @ send counter2 with magic number
+ ldrb r1, [r0, GCMB_STRUCT_COUNTER2]
+ lsls r1, 8
+ adds r1, GCMB_MAGIC_COUNTER2
+ str r1, [r3, OFFSET_REG_JOY_TRANS - 0x120]
+ adr r2, GcMbIntrHandler_CheckCounter2Sent
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_CheckCounter2Sent:
+ lsls r1, 31
+
+GcMbIntrHandler_StopIfSendFailed:
+ bcc GcMbIntrHandler_Stop @ stop if send failed
+ @ if KeyC derivation value has not yet been generated, send Counter2 again, otherwise, send KeyC derivation
+ ldr r1, [r0, GCMB_STRUCT_KEYC_DERIVATION]
+ cmp r1, 0
+ beq GcMbIntrHandler_SendCounter2
+ str r1, [r3, OFFSET_REG_JOY_TRANS - 0x120]
+ adr r2, GcMbIntrHandler_CheckKeyCDerivationSent
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_CheckKeyCDerivationSent: @ 81DCDB8
+ lsls r1, 31
+ bcc GcMbIntrHandler_StopIfSendFailed @ branch if send failed
+ bmi GameCubeMultiBoot_CheckBootKeyResponse @ branch if receive is complete
+ adr r2, GcMbIntrHandler_CheckBootKeyResponse
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_CheckBootKeyResponse: @ 81DCDC4
+ lsrs r1, 1 @ is receive complete?
+ bcc GcMbIntrHandler_StopIfSendFailed @ branch if not
+
+GameCubeMultiBoot_CheckBootKeyResponse:
+ ldr r1, [r3, OFFSET_REG_JOY_RECV - 0x120]
+ @ make sure received boot key contains expected magic number, stop if not
+ lsrs r2, r1, 24
+ cmp r2, GCMB_MAGIC_BOOTKEY
+ bne GcMbIntrHandler_StopIfNotEqual
+ @ save received bootkey to be checked in GameCubeMultiBoot_Main()
+ strh r1, [r0, GCMB_STRUCT_BOOT_KEY]
+ @ stop if anything more gets sent
+ adr r2, GcMbIntrHandler_StopUnconditionally
+ b GameCubeMultiBoot_SetInterruptHandler
+
+ .align 2, 0
+
+GcMbIntrHandler_StopUnconditionally: @ 81DCDD8
+ b GcMbIntrHandler_Stop
+
+ thumb_func_end GameCubeMultiBoot_HandleSerialInterrupt
+
+ non_word_aligned_thumb_func_start GameCubeMultiBoot_Quit
+@ void GameCubeMultiBoot_Quit()@
+GameCubeMultiBoot_Quit: @ 81DCDDA
+ ldr r3, pool_InterruptRegs
+
+@ Save IME register.
+ ldrh r2, [r3, OFFSET_REG_IME - 0x200]
+
+@ Disable interrupts.
+ movs r1, 0
+ strh r1, [r3, OFFSET_REG_IME - 0x200]
+
+ ldr r3, pool_SerialRegs
+
+@ Acknowledge all JOYCNT flags.
+ movs r0, 0x7
+ strh r0, [r3, OFFSET_REG_JOYCNT - 0x120]
+
+@ Turn off JOY Bus mode.
+ lsls r0, r3, 10
+ strh r0, [r3, OFFSET_REG_RCNT - 0x120] @ store 0x8000
+
+ ldr r3, pool_InterruptRegs
+
+@ Acknowledge serial interrupt.
+ movs r0, INTR_FLAG_SERIAL
+ strh r0, [r3, OFFSET_REG_IF - 0x200]
+
+@ Disable serial interrupt.
+ ldrh r1, [r3, OFFSET_REG_IE - 0x200]
+ bics r1, r0
+ strh r1, [r3, OFFSET_REG_IE - 0x200]
+
+@ Restore IME register.
+ strh r2, [r3, OFFSET_REG_IME - 0x200]
+
+ bx lr
+ thumb_func_end GameCubeMultiBoot_Quit
+
+ .align 2, 0
+
+GameCubeMultiBoot_MaximumImageSizeUInt32s: .4byte 0x00004000
+
+pool_InterruptRegs: .4byte REG_BASE + 0x200
+
+pool_SerialRegs: .4byte REG_BASE + 0x120
+
+pool_RegDispstat: .4byte REG_DISPSTAT
+
+pool_RubyUSAGameCode: .ascii "AXVE"
+
+pool_MultiBootLoadAddr: .4byte EWRAM_START
+
+ .align 2, 0 @ Don't pad with nop.