shithub: pokecrystal

Download patch

ref: 446f1846b1c614ab1538d1ce11aa2781c22717f2
parent: 2738858985dbcff55c2ec237ac6075b4263f3120
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Tue Oct 27 09:22:27 EDT 2020

Identify remaining Mystery Gift labels and constants

Source: https://projectpokemon.org/home/forums/topic/43930-mystery-gift-reverse-engineering-of-ir-protocol/

--- a/constants/hardware_constants.asm
+++ b/constants/hardware_constants.asm
@@ -71,10 +71,10 @@
 rTMA        EQU $ff06 ; Timer Modulo (R/W)
 rTAC        EQU $ff07 ; Timer Control (R/W)
 rTAC_ON        EQU 2
-rTAC_4096_HZ   EQU 0
-rTAC_262144_HZ EQU 1
-rTAC_65536_HZ  EQU 2
-rTAC_16384_HZ  EQU 3
+rTAC_4096_HZ   EQU %00
+rTAC_262144_HZ EQU %01
+rTAC_65536_HZ  EQU %10
+rTAC_16384_HZ  EQU %11
 rIF         EQU $ff0f ; Interrupt Flag (R/W)
 rNR10       EQU $ff10 ; Channel 1 Sweep register (R/W)
 rNR11       EQU $ff11 ; Channel 1 Sound length/Wave pattern duty (R/W)
@@ -147,6 +147,9 @@
 rHDMA4      EQU $ff54 ; CGB Mode Only - New DMA Destination, Low
 rHDMA5      EQU $ff55 ; CGB Mode Only - New DMA Length/Mode/Start
 rRP         EQU $ff56 ; CGB Mode Only - Infrared Communications Port
+rRP_LED_ON EQU 0
+rRP_RECEIVING EQU 1
+rRP_ENABLE_READ_MASK EQU %11000000
 rBGPI       EQU $ff68 ; CGB Mode Only - Background Palette Index
 rBGPI_AUTO_INCREMENT EQU 7 ; increment rBGPI after write to rBGPD
 rBGPD       EQU $ff69 ; CGB Mode Only - Background Palette Data
--- a/constants/misc_constants.asm
+++ b/constants/misc_constants.asm
@@ -14,6 +14,8 @@
 
 ; G/S version ID: 0 = Gold, 1 = Silver (used by checkver)
 GS_VERSION EQU 0
+; Pokémon Pikachu 2, a step counter / virtual pet device (used by Mystery Gift)
+POKEMON_PIKACHU_2_VERSION EQU 3
 
 ; save file corruption check values
 SAVE_CHECK_VALUE_1 EQU 99
--- a/constants/serial_constants.asm
+++ b/constants/serial_constants.asm
@@ -30,3 +30,5 @@
 SERIAL_PATCH_LIST_PART_TERMINATOR EQU $ff
 
 SERIAL_PREAMBLE_LENGTH EQU 6
+
+MAX_MYSTERY_GIFT_PARTNERS EQU 5
--- a/engine/link/mystery_gift.asm
+++ b/engine/link/mystery_gift.asm
@@ -1,3 +1,27 @@
+; hMGRole values
+IR_RECEIVER EQU 1
+IR_SENDER   EQU 2
+
+; hMGStatusFlags error bits
+MG_WRONG_CHECKSUM_F EQU 0
+MG_TIMED_OUT_F      EQU 1
+MG_CANCELED_F       EQU 4
+MG_WRONG_PREFIX_F   EQU 7
+
+; hMGStatusFlags values
+MG_WRONG_CHECKSUM EQU 1 << MG_WRONG_CHECKSUM_F
+MG_TIMED_OUT      EQU 1 << MG_TIMED_OUT_F
+MG_CANCELED       EQU 1 << MG_CANCELED_F
+MG_WRONG_PREFIX   EQU 1 << MG_WRONG_PREFIX_F
+MG_NOT_OKAY       EQU MG_WRONG_CHECKSUM | MG_TIMED_OUT | MG_CANCELED | MG_WRONG_PREFIX
+MG_OKAY           EQU $ff ^ MG_NOT_OKAY
+MG_START_END      EQU %11111111
+
+REGION_PREFIX EQU $96
+REGION_CODE   EQU $90 ; USA
+
+MESSAGE_PREFIX EQU $5a
+
 DoMysteryGift:
 	call ClearTilemap
 	call ClearSprites
@@ -7,22 +31,24 @@
 	ld de, .String_PressAToLink_BToCancel
 	call PlaceString
 	call WaitBGMap
-	farcall PrepMysteryGiftDataToSend
-	call MysteryGift_ClearTrainerData
-	ld a, $2
-	ld [wca01], a
-	ld a, $14
-	ld [wca02], a
-	ldh a, [rIE]
-	push af
 
-	call Function104a95
+	; Prepare the first of two messages for wMysteryGiftPartnerData
+	farcall StageDataForMysteryGift
+	call ClearMysteryGiftTrainer
+	ld a, 2
+	ld [wMysteryGiftMessageCount], a
+	ld a, wMysteryGiftPartnerDataEnd - wMysteryGiftPartnerData
+	ld [wMysteryGiftStagedDataLength], a
 
+	ldh a, [rIE]
+	push af
+	call ExchangeMysteryGiftData
 	ld d, a
 	xor a
 	ldh [rIF], a
 	pop af
 	ldh [rIE], a
+
 	push de
 	call ClearTilemap
 	call EnableLCD
@@ -31,15 +57,16 @@
 	call GetSGBLayout
 	call SetPalettes
 	pop de
+
 	hlcoord 2, 8
 	ld a, d
 	ld de, .MysteryGiftCanceledText ; Link has been canceled
-	cp $10
+	cp MG_CANCELED
 	jp z, .LinkCanceled
-	cp $6c
+	cp MG_OKAY
 	jp nz, .CommunicationError
-	ld a, [wc900]
-	cp 3
+	ld a, [wMysteryGiftGameVersion]
+	cp POKEMON_PIKACHU_2_VERSION
 	jr z, .skip_checks
 	call .CheckAlreadyGotFiveGiftsToday
 	ld hl, .MysteryGiftFiveADayText ; Only 5 gifts a day
@@ -54,12 +81,12 @@
 	ld a, [wMysteryGiftPartnerBackupItem]
 	and a
 	jp nz, .FriendNotReady
-	ld a, [wc900]
-	cp 3
+	ld a, [wMysteryGiftGameVersion]
+	cp POKEMON_PIKACHU_2_VERSION
 	jr z, .skip_append_save
 	call .AddMysteryGiftPartnerID
-	ld a, [wc900]
-	cp 4
+	ld a, [wMysteryGiftGameVersion]
+	cp 4 ; ???
 	jr z, .skip_append_save
 	call .SaveMysteryGiftTrainerName
 	farcall RestoreMobileEventIndex
@@ -68,14 +95,16 @@
 .skip_append_save
 	ld a, [wMysteryGiftPartnerSentDeco]
 	and a
-	jr z, .item
+	jr z, .SentItem
+; sent decoration
 	ld a, [wMysteryGiftPartnerWhichDeco]
 	ld c, a
 	farcall MysteryGiftGetDecoration
 	push bc
-	call MysteryGift_CheckAndSetDecorationAlreadyReceived
+	call CheckAndSetMysteryGiftDecorationAlreadyReceived
 	pop bc
-	jr nz, .item
+	jr nz, .SentItem
+; keep the decoration if it wasn't already received
 	callfar GetDecorationName_c
 	ld h, d
 	ld l, e
@@ -85,17 +114,17 @@
 	ld hl, .MysteryGiftSentHomeText ; sent decoration to home
 	jr .PrintTextAndExit
 
-.item
+.SentItem:
 	call GetMysteryGiftBank
 	ld a, [wMysteryGiftPartnerWhichItem]
 	ld c, a
-	farcall MysteryGiftGetItemHeldEffect
+	farcall MysteryGiftGetItem
 	ld a, c
 	ld [sBackupMysteryGiftItem], a
 	ld [wNamedObjectIndexBuffer], a
 	call CloseSRAM
 	call GetItemName
-	ld hl, .MysteryGiftSentText ; sent item
+	ld hl, .MysteryGiftSentText ; sent item/decoration
 	jr .PrintTextAndExit
 
 .LinkCanceled:
@@ -113,6 +142,7 @@
 
 .FriendNotReady:
 	ld hl, .YourFriendIsNotReadyText ; friend not ready
+	; fallthrough
 
 .PrintTextAndExit:
 	call PrintText
@@ -162,7 +192,7 @@
 .CheckAlreadyGotFiveGiftsToday:
 	call GetMysteryGiftBank
 	ld a, [sNumDailyMysteryGiftPartnerIDs]
-	cp $5
+	cp MAX_MYSTERY_GIFT_PARTNERS
 	jp CloseSRAM
 
 .CheckAlreadyGotAGiftFromThatPerson:
@@ -198,9 +228,9 @@
 	ld hl, sNumDailyMysteryGiftPartnerIDs
 	ld a, [hl]
 	inc [hl]
-	ld hl, sDailyMysteryGiftPartnerIDs ; inc hl
+	ld hl, sDailyMysteryGiftPartnerIDs ; could have done "inc hl" instead
 	ld e, a
-	ld d, $0
+	ld d, 0
 	add hl, de
 	add hl, de
 	ld a, [wMysteryGiftPartnerID]
@@ -211,226 +241,268 @@
 
 .SaveMysteryGiftTrainerName:
 	call GetMysteryGiftBank
-	ld a, $1
+	ld a, TRUE
 	ld [sMysteryGiftTrainerHouseFlag], a
 	ld hl, wMysteryGiftPartnerName
 	ld de, sMysteryGiftPartnerName
 	ld bc, NAME_LENGTH
 	call CopyBytes
-	ld a, $1
+	assert sMysteryGiftPartnerName + NAME_LENGTH == sMysteryGiftUnusedFlag
+	ld a, TRUE
 	ld [de], a
 	inc de
-	ld hl, wMysteryGiftTrainerData
-	ld bc, (1 + 1 + NUM_MOVES) * PARTY_LENGTH + 2
+	assert sMysteryGiftUnusedFlag + 1 == sMysteryGiftTrainer
+	ld hl, wMysteryGiftTrainer
+	ld bc, wMysteryGiftTrainerEnd - wMysteryGiftTrainer
 	call CopyBytes
 	jp CloseSRAM
 
-Function104a95:
+ExchangeMysteryGiftData:
 	di
 	farcall ClearChannels
-	call Function104d5e
+	call InitializeMysteryGiftInterrupts
 
-.loop2
-	call Function104d96
-	call Function104ddd
+.restart
+	call BeginIRCommunication
+	call InitializeIRCommunicationRoles
 	ldh a, [hMGStatusFlags]
-	cp $10
-	jp z, Function104bd0
-	cp $6c
-	jr nz, .loop2
+	cp MG_CANCELED
+	jp z, EndOrContinueIRCommunication
+	cp MG_OKAY
+	jr nz, .restart
 
-	ldh a, [hPrintNumBuffer + 8]
-	cp $2
-	jr z, Function104b22
-	ld hl, hPrintNumBuffer
-	ld b, $1
-	call Function104d56
-	jr nz, .ly_loop
-	call Function104b49
-	jp nz, Function104bd0
-	jr Function104b0a
+	ldh a, [hMGRole]
+	cp IR_SENDER
+	jr z, SenderExchangeMysteryGiftDataPayloads
+; receiver
+	ld hl, hMGExchangedByte
+	ld b, 1
+	call TryReceivingIRDataBlock
+	jr nz, .failed
+	call ReceiveIRDataPayload_GotRegionPrefix
+	jp nz, EndOrContinueIRCommunication
+	jr ReceiverExchangeMysteryGiftDataPayloads_GotPayload
+
+.failed
 	; Delay frame
-.ly_loop
+.wait_frame
 	ldh a, [rLY]
 	cp LY_VBLANK
-	jr c, .ly_loop
+	jr c, .wait_frame
+
 	ld c, LOW(rRP)
-	ld a, $c0
+	ld a, rRP_ENABLE_READ_MASK
 	ldh [c], a
-	ld b, 240 ; This might have been intended as a 4-second timeout buffer.
-	          ; However, it is reset with each frame.
-.loop3
-	push bc
-	call MysteryGift_ReadJoypad
 
-	ld b, $2
+	ld b, 60 * 4 ; 4 seconds
+.continue
+	push bc
+	call MysteryGift_UpdateJoypad
+	ld b, 1 << rRP_RECEIVING
 	ld c, LOW(rRP)
-	; Delay frame
-.ly_loop2
+.in_vblank
 	ldh a, [c]
 	and b
 	ld b, a
 	ldh a, [rLY]
 	cp LY_VBLANK
-	jr nc, .ly_loop2
-.ly_loop3
+	jr nc, .in_vblank
+.wait_vblank
 	ldh a, [c]
 	and b
 	ld b, a
 	ldh a, [rLY]
 	cp LY_VBLANK
-	jr c, .ly_loop3
-
+	jr c, .wait_vblank
 	ld a, b
 	pop bc
+	; Restart if the 4-second timeout has elapsed
 	dec b
-	jr z, .loop2 ; we never jump here
+	jr z, .restart
+	; Restart if rRP is not receiving data
 	or a
-	jr nz, .loop2
-	; Check if we've pressed the B button
+	jr nz, .restart
+	; Check if we've pressed the B button to cancel
 	ldh a, [hMGJoypadReleased]
 	bit B_BUTTON_F, a
-	jr z, .loop3
-	ld a, $10
+	jr z, .continue
+	ld a, MG_CANCELED
 	ldh [hMGStatusFlags], a
-	jp Function104bd0
+	jp EndOrContinueIRCommunication
 
-Function104b04:
-	call Function104b40
-	jp nz, Function104bd0
-Function104b0a:
-	call Function104d38
-	jp nz, Function104bd0
-	call Function104b88
-	jp nz, Function104bd0
-	call Function104d43
-	jp nz, Function104bd0
-	call Function105033
-	jp Function104bd0
+ReceiverExchangeMysteryGiftDataPayloads:
+	; Receive the data payload
+	call ReceiveIRDataPayload
+	jp nz, EndOrContinueIRCommunication
+	; fallthrough
+ReceiverExchangeMysteryGiftDataPayloads_GotPayload:
+	; Switch roles
+	call BeginSendingIRCommunication
+	jp nz, EndOrContinueIRCommunication
+	; Send the data payload
+	call SendIRDataPayload
+	jp nz, EndOrContinueIRCommunication
+	; Switch roles
+	call BeginReceivingIRCommunication
+	jp nz, EndOrContinueIRCommunication
+	; Receive an empty block
+	call ReceiveEmptyIRDataBlock
+	jp EndOrContinueIRCommunication
 
-Function104b22:
-	call Function104b88
-	jp nz, Function104bd0
-	call Function104d43
-	jp nz, Function104bd0
-	call Function104b40
-	jp nz, Function104bd0
-	call Function104d38
-	jp nz, Function104bd0
-	call Function10502e
-	jp Function104bd0
+SenderExchangeMysteryGiftDataPayloads:
+	; Send the data payload
+	call SendIRDataPayload
+	jp nz, EndOrContinueIRCommunication
+	; Switch roles
+	call BeginReceivingIRCommunication
+	jp nz, EndOrContinueIRCommunication
+	; Receive the data payload
+	call ReceiveIRDataPayload
+	jp nz, EndOrContinueIRCommunication
+	; Switch roles
+	call BeginSendingIRCommunication
+	jp nz, EndOrContinueIRCommunication
+	; Send an empty block
+	call SendEmptyIRDataBlock
+	jp EndOrContinueIRCommunication
 
-Function104b40:
-	ld hl, hPrintNumBuffer
-	ld b, $1
-	call Function104d56
+ReceiveIRDataPayload:
+	; Receive the region prefix
+	ld hl, hMGExchangedByte
+	ld b, 1
+	call TryReceivingIRDataBlock
 	ret nz
-
-Function104b49:
-	call Function105033
+	; fallthrough
+ReceiveIRDataPayload_GotRegionPrefix:
+	; Receive an empty block
+	call ReceiveEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret nz
-	ldh a, [hPrintNumBuffer]
-	cp $96
-	jp nz, Function104d32
-	ld a, $90
-	ldh [hPrintNumBuffer], a
-	call Function104d38
+	; Verify the received region prefix
+	ldh a, [hMGExchangedByte]
+	cp REGION_PREFIX
+	jp nz, WrongMysteryGiftRegion
+	ld a, REGION_CODE
+	ldh [hMGExchangedByte], a
+	; Switch roles
+	call BeginSendingIRCommunication
 	ret nz
-	ld hl, hPrintNumBuffer
-	ld b, $1
-	call Function104d4e
+	; Send the region code
+	ld hl, hMGExchangedByte
+	ld b, 1
+	call TrySendingIRDataBlock
 	ret nz
-	call Function10502e
+	; Send an empty block
+	call SendEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret nz
-	call Function104d43
+	; Switch roles
+	call BeginReceivingIRCommunication
 	ret nz
-	ld hl, wMysteryGiftTrainerData
-	ld a, [wca02]
+	; Receive the staged data
+	ld hl, wMysteryGiftTrainer
+	ld a, [wMysteryGiftStagedDataLength]
 	ld b, a
-	call Function104d56
+	call TryReceivingIRDataBlock
 	ret nz
-	call Function105033
+	; Receive an empty block
+	call ReceiveEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret
 
-Function104b88:
-	ld a, $96
-	ldh [hPrintNumBuffer], a
-	ld hl, hPrintNumBuffer
-	ld b, $1
-	call Function104d4e
+SendIRDataPayload:
+	; Send the region prefix
+	ld a, REGION_PREFIX
+	ldh [hMGExchangedByte], a
+	ld hl, hMGExchangedByte
+	ld b, 1
+	call TrySendingIRDataBlock
 	ret nz
-	call Function10502e
+	; Send an empty block
+	call SendEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret nz
-	call Function104d43
+	; Switch roles
+	call BeginReceivingIRCommunication
 	ret nz
-	ld hl, hPrintNumBuffer
-	ld b, $1
-	call Function104d56
+	; Receive the region code
+	ld hl, hMGExchangedByte
+	ld b, 1
+	call TryReceivingIRDataBlock
 	ret nz
-	call Function105033
+	; Receive an empty block
+	call ReceiveEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret nz
-	ldh a, [hPrintNumBuffer]
-	cp $90
-	jp nz, Function104d32
-	call Function104d38
+	; Verify the received region code
+	ldh a, [hMGExchangedByte]
+	cp REGION_CODE
+	jp nz, WrongMysteryGiftRegion
+	; Switch roles
+	call BeginSendingIRCommunication
 	ret nz
-	ld hl, wLinkData
-	ld a, [wca02]
+	; Send the staged data
+	ld hl, wMysteryGiftStaging
+	ld a, [wMysteryGiftStagedDataLength]
 	ld b, a
-	call Function104d4e
+	call TrySendingIRDataBlock
 	ret nz
-	call Function10502e
+	; Send an empty block
+	call SendEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret
 
-Function104bd0:
+EndOrContinueIRCommunication:
 	nop
 	ldh a, [hMGStatusFlags]
-	cp $10
+	; Quit if player canceled
+	cp MG_CANCELED
 	jr z, .quit
-	cp $6c
+	; Quit if there was a communication error
+	cp MG_OKAY
 	jr nz, .quit
-	ld hl, wca01
+	; Quit if all messages are sent/received
+	ld hl, wMysteryGiftMessageCount
 	dec [hl]
 	jr z, .quit
-	ld hl, wMysteryGiftTrainerData
+	; Quit if communicating with Pokémon Pikachu 2 device
+	ld hl, wMysteryGiftTrainer
 	ld de, wMysteryGiftPartnerData
 	ld bc, wMysteryGiftPartnerDataEnd - wMysteryGiftPartnerData
 	call CopyBytes
-	ld a, [wMysteryGiftTrainerData]
-	cp $3
+	ld a, [wMysteryGiftTrainer] ; first byte is the version
+	cp POKEMON_PIKACHU_2_VERSION
 	jr nc, .quit
+
+	; Prepare the second message for wMysteryGiftTrainer
 	farcall StagePartyDataForMysteryGift
-	call MysteryGift_ClearTrainerData
-	ld a, $26
-	ld [wca02], a
-	ldh a, [hPrintNumBuffer + 8]
-	cp $2
-	jr z, .asm_104c10
-	call Function104d43
-	jr nz, Function104bd0
-	jp Function104b04
+	call ClearMysteryGiftTrainer
+	ld a, wMysteryGiftTrainerEnd - wMysteryGiftTrainer
+	ld [wMysteryGiftStagedDataLength], a
 
-.asm_104c10
-	call Function104d38
-	jr nz, Function104bd0
-	jp Function104b22
+	ldh a, [hMGRole]
+	cp IR_SENDER
+	jr z, .sender
+; receiver
+	call BeginReceivingIRCommunication
+	jr nz, EndOrContinueIRCommunication
+	jp ReceiverExchangeMysteryGiftDataPayloads
 
+.sender
+	call BeginSendingIRCommunication
+	jr nz, EndOrContinueIRCommunication
+	jp SenderExchangeMysteryGiftDataPayloads
+
 .quit
 	ldh a, [hMGStatusFlags]
 	push af
-	call Function104da0
+	call EndIRCommunication
 	xor a
 	ldh [rIF], a
 	ldh a, [rIE]
@@ -444,111 +516,115 @@
 Function104c2d:
 	di
 	farcall ClearChannels
-	call Function104d5e
-.asm_104c37
-	call Function104d96
-	call Function104ddd
+	call InitializeMysteryGiftInterrupts
+
+.loop2
+	call BeginIRCommunication
+	call InitializeIRCommunicationRoles
 	ldh a, [hMGStatusFlags]
-	cp $10
+	cp MG_CANCELED
 	jp z, Function104d1c
-	cp $6c
-	jr nz, .asm_104c37
-	ldh a, [hPrintNumBuffer + 8]
-	cp $2
-	jr z, .asm_104c6c
+	cp MG_OKAY
+	jr nz, .loop2
+
+	ldh a, [hMGRole]
+	cp IR_SENDER
+	jr z, .sender
+; receiver
 	call Function104c8a
 	jp nz, Function104d1c
-	call Function104d38
+	call BeginSendingIRCommunication
 	jp nz, Function104d1c
 	call Function104cd2
 	jp nz, Function104d1c
-	call Function104d43
+	call BeginReceivingIRCommunication
 	jp nz, Function104d1c
-	call Function105033
+	call ReceiveEmptyIRDataBlock
 	jp Function104d1c
-.asm_104c6c
+
+.sender
 	call Function104cd2
 	jp nz, Function104d1c
-	call Function104d43
+	call BeginReceivingIRCommunication
 	jp nz, Function104d1c
 	call Function104c8a
 	jp nz, Function104d1c
-	call Function104d38
+	call BeginSendingIRCommunication
 	jp nz, Function104d1c
-	call Function10502e
+	call SendEmptyIRDataBlock
 	jp Function104d1c
 
 Function104c8a:
-	ld hl, hPrintNumBuffer
-	ld b, $1
-	call Function104d56
+	ld hl, hMGExchangedByte
+	ld b, 1
+	call TryReceivingIRDataBlock
 	ret nz
-	call Function105033
+	call ReceiveEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret nz
-	ldh a, [hPrintNumBuffer]
+	ldh a, [hMGExchangedByte]
 	cp $3c
-	jp nz, Function104d32
+	jp nz, WrongMysteryGiftRegion
 	swap a
-	ldh [hPrintNumBuffer], a
-	call Function104d38
+	ldh [hMGExchangedByte], a
+	call BeginSendingIRCommunication
 	ret nz
-	ld hl, hPrintNumBuffer
-	ld b, $1
-	call Function104d4e
+	ld hl, hMGExchangedByte
+	ld b, 1
+	call TrySendingIRDataBlock
 	ret nz
-	call Function10502e
+	call SendEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret nz
-	call Function104d43
+	call BeginReceivingIRCommunication
 	ret nz
-	ld hl, wMysteryGiftTrainerData
-	ld a, [wca02]
+	ld hl, wMysteryGiftTrainer
+	ld a, [wMysteryGiftStagedDataLength]
 	ld b, a
-	call Function104d56
+	call TryReceivingIRDataBlock
 	ret nz
-	call Function105033
+	call ReceiveEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret
 
 Function104cd2:
 	ld a, $3c
-	ldh [hPrintNumBuffer], a
-	ld hl, hPrintNumBuffer
-	ld b, $1
-	call Function104d4e
+	ldh [hMGExchangedByte], a
+	ld hl, hMGExchangedByte
+	ld b, 1
+	call TrySendingIRDataBlock
 	ret nz
-	call Function10502e
+	call SendEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret nz
-	call Function104d43
+	call BeginReceivingIRCommunication
 	ret nz
-	ld hl, hPrintNumBuffer
-	ld b, $1
-	call Function104d56
+	ld hl, hMGExchangedByte
+	ld b, 1
+	call TryReceivingIRDataBlock
 	ret nz
-	call Function105033
+	call ReceiveEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret nz
-	ldh a, [hPrintNumBuffer]
+	ldh a, [hMGExchangedByte]
 	swap a
 	cp $3c
-	jp nz, Function104d32
-	call Function104d38
+	jp nz, WrongMysteryGiftRegion
+	call BeginSendingIRCommunication
 	ret nz
-	ld hl, wLinkData
-	ld a, [wca02]
+	ld hl, wMysteryGiftStaging
+	ld a, [wMysteryGiftStagedDataLength]
 	ld b, a
-	call Function104d4e
+	call TrySendingIRDataBlock
 	ret nz
-	call Function10502e
+	call SendEmptyIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret
 
 Function104d1c:
@@ -555,7 +631,7 @@
 	nop
 	ldh a, [hMGStatusFlags]
 	push af
-	call Function104da0
+	call EndIRCommunication
 	xor a
 	ldh [rIF], a
 	ldh a, [rIE]
@@ -566,46 +642,46 @@
 	pop af
 	ret
 
-Function104d32:
-	ld a, $80
+WrongMysteryGiftRegion:
+	ld a, MG_WRONG_PREFIX
 	ldh [hMGStatusFlags], a
 	and a
 	ret
 
-Function104d38:
-	call Function104d96
-	call Function104e46
+BeginSendingIRCommunication:
+	call BeginIRCommunication
+	call SendIRHelloMessage
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret
 
-Function104d43:
-	call Function104d96
-	call Function104dfe
+BeginReceivingIRCommunication:
+	call BeginIRCommunication
+	call ReceiveIRHelloMessage
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret
 
-Function104d4e:
-	call Function104e93
+TrySendingIRDataBlock:
+	call SendIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret
 
-Function104d56:
-	call Function104f57
+TryReceivingIRDataBlock:
+	call ReceiveIRDataBlock
 	ldh a, [hMGStatusFlags]
-	cp $6c
+	cp MG_OKAY
 	ret
 
-Function104d5e:
-	call Function104d74
+InitializeMysteryGiftInterrupts:
+	call StartFastIRTimer
 	ld a, 1 << TIMER
 	ldh [rIE], a
 	xor a
 	ldh [rIF], a
-	call Function104d96
-; runs for ~$40400 cycles
+	call BeginIRCommunication
+; waits for ~$40400 cycles = ~0.25 seconds
 	xor a
 	ld b, a
 .busy_wait
@@ -615,44 +691,48 @@
 	jr nz, .busy_wait
 	ret
 
-Function104d74:
+StartFastIRTimer:
+; Starts a 65,536 Hz timer that interrupts every 3 increments (21,845 Hz).
 	xor a
 	ldh [rTAC], a
-	ld a, $fe
+	ld a, -2
 	ldh [rTMA], a
 	ldh [rTIMA], a
-	ld a, $2
+	ld a, rTAC_65536_HZ
 	ldh [rTAC], a
-	or $4
+	or 1 << rTAC_ON
 	ldh [rTAC], a
 	ret
 
-Function104d86:
+StartSlowIRTimer:
+; Starts a 65,536 Hz timer that interrupts every 256 increments (256 Hz).
 	xor a
 	ldh [rTAC], a
 	ldh [rTMA], a
 	ldh [rTIMA], a
-	ld a, $2
+	ld a, rTAC_65536_HZ
 	ldh [rTAC], a
-	or $4
+	or 1 << rTAC_ON
 	ldh [rTAC], a
 	ret
 
-Function104d96:
-	ld a, $c0
-	call Function104e8c
-	ld a, $1
-	ldh [hPrintNumBuffer + 8], a
+BeginIRCommunication:
+	ld a, rRP_ENABLE_READ_MASK
+	call ToggleIRCommunication
+	ld a, IR_RECEIVER
+	ldh [hMGRole], a
 	ret
 
-Function104da0:
+EndIRCommunication:
 	xor a
-	call Function104e8c
-	ld a, $2
+	call ToggleIRCommunication
+	ld a, rTAC_65536_HZ
 	ldh [rTAC], a
 	ret
 
-Function104da9:
+ReceiveInfraredLEDOn:
+; Count interrupts of the partner's IR LED on; quit after 256-d interrupts.
+.recv_loop
 	inc d
 	ret z
 	xor a
@@ -659,12 +739,14 @@
 	ldh [rIF], a
 	halt
 	ldh a, [c]
-	bit 1, a
-	jr z, Function104da9
+	bit rRP_RECEIVING, a
+	jr z, .recv_loop
 	or a
 	ret
 
-Function104db7:
+ReceiveInfraredLEDOff:
+; Count interrupts of the partner's IR LED off; quit after 256-d interrupts.
+.no_recv_loop
 	inc d
 	ret z
 	xor a
@@ -671,13 +753,14 @@
 	ldh [rIF], a
 	halt
 	ldh a, [c]
-	bit 1, a
-	jr nz, Function104db7
+	bit rRP_RECEIVING, a
+	jr nz, .no_recv_loop
 	or a
 	ret
 
-Function104dc5:
-	ld a, $c1
+SendInfraredLEDOn:
+; Holds the IR LED on for d-1 interrupts.
+	ld a, rRP_ENABLE_READ_MASK | (1 << rRP_LED_ON)
 	ldh [c], a
 .wait
 	dec d
@@ -687,8 +770,9 @@
 	halt
 	jr .wait
 
-Function104dd1:
-	ld a, $c0
+SendInfraredLEDOff:
+; Holds the IR LED off for d-1 interrupts.
+	ld a, rRP_ENABLE_READ_MASK
 	ldh [c], a
 .wait
 	dec d
@@ -698,175 +782,204 @@
 	halt
 	jr .wait
 
-Function104ddd:
-	ld d, $0
+InitializeIRCommunicationRoles:
+	ld d, 0
 	ld e, d
-	ld a, $1
-	ldh [hPrintNumBuffer + 8], a
+
+	ld a, IR_RECEIVER
+	ldh [hMGRole], a
 .loop
-	call MysteryGift_ReadJoypad
-	ld b, $2
+	call MysteryGift_UpdateJoypad
+	ld b, 1 << rRP_RECEIVING
 	ld c, LOW(rRP)
+	; Check if we've pressed the B button to cancel
 	ldh a, [hMGJoypadReleased]
 	bit B_BUTTON_F, a
-	jr z, .next
-	ld a, $10
+	jr z, .not_canceled
+	ld a, MG_CANCELED
 	ldh [hMGStatusFlags], a
 	ret
 
-.next
-	bit 0, a
-	jr nz, Function104e3a
+.not_canceled
+	; Check if we've pressed the A button to start sending
+	bit A_BUTTON_F, a
+	jr nz, SendIRHelloMessageAfterDelay
+	; If rRP is not receiving data, keep checking for input
 	ldh a, [c]
 	and b
 	jr nz, .loop
+	; fallthrough
 
-Function104dfe:
+ReceiveIRHelloMessage:
 	ld c, LOW(rRP)
-	ld d, $0
+	ld d, 0
 	ld e, d
-	call Function104db7
-	jp z, Function104f42
+
+	call ReceiveInfraredLEDOff
+	jp z, InfraredLEDReceiveTimedOut
 	ld d, e
-	call Function104da9
-	jp z, Function104f42
-	call Function104db7
-	jp z, Function104f42
-	call Function104da9
-	jp z, Function104f42
-	ld a, $6c
+	call ReceiveInfraredLEDOn
+	jp z, InfraredLEDReceiveTimedOut
+	call ReceiveInfraredLEDOff
+	jp z, InfraredLEDReceiveTimedOut
+	call ReceiveInfraredLEDOn
+	jp z, InfraredLEDReceiveTimedOut
+
+	ld a, MG_OKAY
 	ldh [hMGStatusFlags], a
-	ld d, $3d
-	call Function104dd1
-	ld d, $5
-	call Function104dc5
-	ld d, $15
-	call Function104dd1
-	ld d, $5
-	call Function104dc5
-	ld d, $5
-	call Function104dd1
+
+	ld d, 61
+	call SendInfraredLEDOff
+	ld d, 5
+	call SendInfraredLEDOn
+	ld d, 21
+	call SendInfraredLEDOff
+	ld d, 5
+	call SendInfraredLEDOn
+	ld d, 5
+	call SendInfraredLEDOff
 	ret
 
-Function104e3a:
+SendIRHelloMessageAfterDelay:
 	; Wait a random amount of time
 	call Random
 	ld e, a
 	and $f
 	ld d, a
-.loop
+.wait_loop
 	dec de
 	ld a, d
 	or e
-	jr nz, .loop
-Function104e46:
-	ld a, $2
-	ldh [hPrintNumBuffer + 8], a
+	jr nz, .wait_loop
+	; fallthrough
+
+SendIRHelloMessage:
+	ld a, IR_SENDER
+	ldh [hMGRole], a
+
 	ld c, LOW(rRP)
-	ld d, $0
+	ld d, 0
 	ld e, d
-	ld d, $3d
-	call Function104dd1
-	ld d, $5
-	call Function104dc5
-	ld d, $15
-	call Function104dd1
-	ld d, $5
-	call Function104dc5
-	ld d, $5
-	call Function104dd1
+
+	ld d, 61
+	call SendInfraredLEDOff
+	ld d, 5
+	call SendInfraredLEDOn
+	ld d, 21
+	call SendInfraredLEDOff
+	ld d, 5
+	call SendInfraredLEDOn
+	ld d, 5
+	call SendInfraredLEDOff
+
 	ld d, e
-	call Function104db7
-	jp z, Function104f42
+	call ReceiveInfraredLEDOff
+	jp z, InfraredLEDReceiveTimedOut
 	ld d, e
-	call Function104da9
-	jp z, Function104f42
-	call Function104db7
-	jp z, Function104f42
-	call Function104da9
-	jp z, Function104f42
-	ld d, $3d
-	call Function104dd1
-	ld a, $6c
+	call ReceiveInfraredLEDOn
+	jp z, InfraredLEDReceiveTimedOut
+	call ReceiveInfraredLEDOff
+	jp z, InfraredLEDReceiveTimedOut
+	call ReceiveInfraredLEDOn
+	jp z, InfraredLEDReceiveTimedOut
+
+	ld d, 61
+	call SendInfraredLEDOff
+
+	ld a, MG_OKAY
 	ldh [hMGStatusFlags], a
 	ret
 
-Function104e8c:
+ToggleIRCommunication:
 	ldh [rRP], a
-	ld a, $ff
+	ld a, MG_START_END
 	ldh [hMGStatusFlags], a
 	ret
 
-Function104e93:
+SendIRDataBlock:
+; Send b bytes of data in three messages:
+; 1. two bytes: MESSAGE_PREFIX and the length b
+; 2. b bytes: the actual data
+; 3. two bytes: a little-endian checksum
+; Then receive a one-byte acknowledgement message: the status.
 	xor a
-	ldh [hPrintNumBuffer + 4], a
-	ldh [hPrintNumBuffer + 5], a
+	ldh [hMGChecksum + 0], a
+	ldh [hMGChecksum + 1], a
 	push hl
 	push bc
 	ld c, LOW(rRP)
-	ld d, $3d
-	call Function104dd1
-	ld hl, hPrintNumBuffer + 1
-	ld a, $5a
+	ld d, 61
+	call SendInfraredLEDOff
+	ld hl, hMGExchangedWord
+	ld a, MESSAGE_PREFIX
 	ld [hli], a
 	ld [hl], b
 	dec hl
-	ld b, $2
-	call Function104ed6
+	ld b, 2
+	call SendIRDataMessage
 	pop bc
 	pop hl
-	call Function104ed6
-	ldh a, [hPrintNumBuffer + 4]
-	ldh [hPrintNumBuffer + 1], a
-	ldh a, [hPrintNumBuffer + 5]
-	ldh [hPrintNumBuffer + 2], a
+	call SendIRDataMessage
+	ldh a, [hMGChecksum + 0]
+	ldh [hMGExchangedWord + 0], a
+	ldh a, [hMGChecksum + 1]
+	ldh [hMGExchangedWord + 1], a
 	push hl
-	ld hl, hPrintNumBuffer + 1
-	ld b, $2
-	call Function104ed6
+	ld hl, hMGExchangedWord
+	ld b, 2
+	call SendIRDataMessage
 	ld hl, hMGStatusFlags
-	ld b, $1
-	call Function104faf
-	ldh a, [hPrintNumBuffer + 1]
-	ldh [hPrintNumBuffer + 4], a
-	ldh a, [hPrintNumBuffer + 2]
-	ldh [hPrintNumBuffer + 5], a
+	ld b, 1
+	call ReceiveIRDataMessage
+	ldh a, [hMGExchangedWord + 0]
+	ldh [hMGChecksum + 0], a
+	ldh a, [hMGExchangedWord + 1]
+	ldh [hMGChecksum + 1], a
 	pop hl
 	ret
 
-Function104ed6:
+SendIRDataMessage:
+; Send b bytes of data one bit at a time, and update the checksum.
 	ld c, LOW(rRP)
-	ld d, $5
-	call Function104dd1
-	ld d, $5
-	call Function104dc5
-	ld d, $15
-	call Function104dd1
+
+	ld d, 5
+	call SendInfraredLEDOff
+	ld d, 5
+	call SendInfraredLEDOn
+	ld d, 21
+	call SendInfraredLEDOff
+
+	; b = -b - 1; then count up to 0
 	ld a, b
 	cpl
 	ld b, a
-	ld a, $f4
+
+	ld a, -12
 	ldh [rTMA], a
-.main_loop
+.byte_loop
 	inc b
 	jr z, .done
-	ld a, $8
-	ldh [hPrintNumBuffer + 3], a
+	ld a, 8
+	ldh [hMGNumBits], a
+	; Get the next data byte
 	ld a, [hli]
 	ld e, a
-	ldh a, [hPrintNumBuffer + 4]
+	; Add the next data byte to the checksum
+	ldh a, [hMGChecksum + 0]
 	add e
-	ldh [hPrintNumBuffer + 4], a
-	ldh a, [hPrintNumBuffer + 5]
+	ldh [hMGChecksum + 0], a
+	ldh a, [hMGChecksum + 1]
 	adc 0
-	ldh [hPrintNumBuffer + 5], a
-.inner_loop
+	ldh [hMGChecksum + 1], a
+	; Send each bit of the byte
+.bit_loop
 	xor a
 	ldh [rIF], a
 	halt
-	ld a, $c1
+	ld a, rRP_ENABLE_READ_MASK | (1 << rRP_LED_ON)
 	ldh [rRP], a
-	ld d, $1
+	; Turn the LED off for longer if the bit is 1
+	ld d, 1
 	ld a, e
 	rlca
 	ld e, a
@@ -874,9 +987,9 @@
 	inc d
 .wait
 	ldh a, [rTIMA]
-	cp $f8
+	cp -8
 	jr c, .wait
-	ld a, $c0
+	ld a, rRP_ENABLE_READ_MASK
 	ldh [rRP], a
 	dec d
 	jr z, .no_halt
@@ -884,73 +997,79 @@
 	ldh [rIF], a
 	halt
 .no_halt
-	ldh a, [hPrintNumBuffer + 3]
+	ldh a, [hMGNumBits]
 	dec a
-	jr z, .main_loop
-	ldh [hPrintNumBuffer + 3], a
-	jr .inner_loop
+	jr z, .byte_loop
+	ldh [hMGNumBits], a
+	jr .bit_loop
 
 .done
-	ld a, $fe
+	ld a, -2
 	ldh [rTMA], a
 	xor a
 	ldh [rIF], a
 	halt
-	ld d, $5
-	call Function104dc5
-	ld d, $11
-	call Function104dd1
+
+	ld d, 5
+	call SendInfraredLEDOn
+	ld d, 17
+	call SendInfraredLEDOff
 	ret
 
-Function104f42:
+InfraredLEDReceiveTimedOut:
 	ldh a, [hMGStatusFlags]
-	or $2
+	or MG_TIMED_OUT
 	ldh [hMGStatusFlags], a
 	ret
 
-Function104f49:
+ReceivedWrongIRChecksum:
 	ldh a, [hMGStatusFlags]
-	or $1
+	or MG_WRONG_CHECKSUM
 	ldh [hMGStatusFlags], a
 	ret
 
-Function104f50:
+ReceivedWrongIRMessagePrefix:
 	ldh a, [hMGStatusFlags]
-	or $80
+	or MG_WRONG_PREFIX
 	ldh [hMGStatusFlags], a
 	ret
 
-Function104f57:
+ReceiveIRDataBlock:
+; Receive b bytes of data in three messages:
+; 1. two bytes: MESSAGE_PREFIX and the length b
+; 2. b bytes: the actual data
+; 3. two bytes: a little-endian checksum
+; Then send a one-byte acknowledgement message: the status.
 	xor a
-	ldh [hPrintNumBuffer + 4], a
-	ldh [hPrintNumBuffer + 5], a
+	ldh [hMGChecksum + 0], a
+	ldh [hMGChecksum + 1], a
 	push bc
 	push hl
-	ld hl, hPrintNumBuffer + 1
-	ld b, $2
-	call Function104faf
-	ldh a, [hPrintNumBuffer + 2]
-	ldh [hPrintNumBuffer + 7], a
+	ld hl, hMGExchangedWord
+	ld b, 2
+	call ReceiveIRDataMessage
+	ldh a, [hMGExchangedWord + 1]
+	ldh [hMGUnusedMsgLength], a
 	ld b, a
 	pop hl
 	pop af
 	cp b
-	jp c, Function104f50
-	ldh a, [hPrintNumBuffer + 1]
-	cp $5a
-	jp nz, Function104f50
-	call Function104faf
-	ldh a, [hPrintNumBuffer + 4]
+	jp c, ReceivedWrongIRMessagePrefix
+	ldh a, [hMGExchangedWord + 0]
+	cp MESSAGE_PREFIX
+	jp nz, ReceivedWrongIRMessagePrefix
+	call ReceiveIRDataMessage
+	ldh a, [hMGChecksum + 0]
 	ld d, a
-	ldh a, [hPrintNumBuffer + 5]
+	ldh a, [hMGChecksum + 1]
 	ld e, a
 	push hl
 	push de
-	ld hl, hPrintNumBuffer + 1
-	ld b, $2
-	call Function104faf
+	ld hl, hMGExchangedWord
+	ld b, 2
+	call ReceiveIRDataMessage
 	pop de
-	ld hl, hPrintNumBuffer + 1
+	ld hl, hMGExchangedWord
 	ld a, [hli]
 	xor d
 	ld b, a
@@ -957,60 +1076,66 @@
 	ld a, [hl]
 	xor e
 	or b
-	call nz, Function104f49
+	call nz, ReceivedWrongIRChecksum
 	push de
-	ld d, $3d
-	call Function104dd1
+
+	ld d, 61
+	call SendInfraredLEDOff
+
 	ld hl, hMGStatusFlags
-	ld b, $1
-	call Function104ed6
+	ld b, 1
+	call SendIRDataMessage
+
 	pop de
 	pop hl
 	ld a, d
-	ldh [hPrintNumBuffer + 4], a
+	ldh [hMGChecksum + 0], a
 	ld a, e
-	ldh [hPrintNumBuffer + 5], a
+	ldh [hMGChecksum + 1], a
 	ret
 
-Function104faf:
+ReceiveIRDataMessage:
 	ld c, LOW(rRP)
-	ld d, $0
-	call Function104db7
-	jp z, Function104f42
-	ld d, $0
-	call Function104da9
-	jp z, Function104f42
-	ld d, $0
-	call Function104db7
-	jp z, Function104f42
+
+	ld d, 0
+	call ReceiveInfraredLEDOff
+	jp z, InfraredLEDReceiveTimedOut
+	ld d, 0
+	call ReceiveInfraredLEDOn
+	jp z, InfraredLEDReceiveTimedOut
+	ld d, 0
+	call ReceiveInfraredLEDOff
+	jp z, InfraredLEDReceiveTimedOut
+
 	ld a, b
 	cpl
 	ld b, a
 	xor a
 	ldh [hMGPrevTIMA], a
-	call Function104d86
+
+	call StartSlowIRTimer
 .main_loop
 	inc b
 	jr z, .done
-	ld a, $8
-	ldh [hPrintNumBuffer + 3], a
+	ld a, 8
+	ldh [hMGNumBits], a
 .inner_loop
-	ld d, $0
-.wait_one
+	ld d, 0
+.recv_loop
 	inc d
-	jr z, .got_one
+	jr z, .recv_done
 	ldh a, [c]
-	bit 1, a
-	jr z, .wait_one
-	ld d, $0
-.got_one
-.wait_zero
+	bit rRP_RECEIVING, a
+	jr z, .recv_loop
+	ld d, 0
+.recv_done
+.send_loop
 	inc d
-	jr z, .got_zero
+	jr z, .send_done
 	ldh a, [c]
-	bit 1, a
-	jr nz, .wait_zero
-.got_zero
+	bit rRP_RECEIVING, a
+	jr nz, .send_loop
+.send_done
 	ldh a, [hMGPrevTIMA]
 	ld d, a
 	ldh a, [rTIMA]
@@ -1023,9 +1148,9 @@
 .zero
 	res 0, e
 .ok
-	ldh a, [hPrintNumBuffer + 3]
+	ldh a, [hMGNumBits]
 	dec a
-	ldh [hPrintNumBuffer + 3], a
+	ldh [hMGNumBits], a
 	jr z, .continue
 	ld a, e
 	rlca
@@ -1035,34 +1160,34 @@
 .continue
 	ld a, e
 	ld [hli], a
-	ldh a, [hPrintNumBuffer + 4]
+	ldh a, [hMGChecksum + 0]
 	add e
-	ldh [hPrintNumBuffer + 4], a
-	ldh a, [hPrintNumBuffer + 5]
+	ldh [hMGChecksum + 0], a
+	ldh a, [hMGChecksum + 1]
 	adc 0
-	ldh [hPrintNumBuffer + 5], a
+	ldh [hMGChecksum + 1], a
 	jr .main_loop
 
 .done
-	call Function104d74
+	call StartFastIRTimer
 	xor a
 	ldh [rIF], a
-	ld d, $0
-	call Function104da9
-	jp z, Function104f42
-	ld d, $10
-	call Function104dd1
+	ld d, 0
+	call ReceiveInfraredLEDOn
+	jp z, InfraredLEDReceiveTimedOut
+	ld d, 16
+	call SendInfraredLEDOff
 	ret
 
-Function10502e:
-	ld b, $0
-	jp Function104e93
+SendEmptyIRDataBlock:
+	ld b, 0
+	jp SendIRDataBlock
 
-Function105033:
-	ld b, $0
-	jp Function104f57
+ReceiveEmptyIRDataBlock:
+	ld b, 0
+	jp ReceiveIRDataBlock
 
-MysteryGift_ReadJoypad:
+MysteryGift_UpdateJoypad:
 ; We can only get four inputs at a time.
 ; We take d-pad first for no particular reason.
 	ld a, R_DPAD
@@ -1107,9 +1232,10 @@
 	ldh [rJOYP], a
 	ret
 
-MysteryGift_CheckAndSetDecorationAlreadyReceived:
+CheckAndSetMysteryGiftDecorationAlreadyReceived:
+; Return nz if decoration c was already received; otherwise receive it.
 	call GetMysteryGiftBank
-	ld d, $0
+	ld d, 0
 	ld b, CHECK_FLAG
 	ld hl, sMysteryGiftDecorationsReceived
 	lda_predef SmallFarFlagAction
@@ -1129,12 +1255,12 @@
 	xor a
 	ret
 
-MysteryGift_CopyReceivedDecosToPC:
+CopyMysteryGiftReceivedDecorationsToPC:
 	call GetMysteryGiftBank
-	ld c, $0
+	ld c, 0
 .loop
 	push bc
-	ld d, $0
+	ld d, 0
 	ld b, CHECK_FLAG
 	ld hl, sMysteryGiftDecorationsReceived
 	predef SmallFarFlagAction
@@ -1153,6 +1279,8 @@
 	jp CloseSRAM
 
 UnlockMysteryGift:
+; If [sMysteryGiftUnlocked] was -1, this sets both
+; [sMysteryGiftUnlocked] and [sMysteryGiftItem] to 0.
 	call GetMysteryGiftBank
 	ld hl, sMysteryGiftUnlocked
 	ld a, [hl]
@@ -1159,21 +1287,24 @@
 	inc a
 	jr nz, .ok
 	ld [hld], a
+	assert sMysteryGiftUnlocked - 1 == sMysteryGiftItem
 	ld [hl], a
 .ok
 	jp CloseSRAM
 
-Function1050c8:
+ResetDailyMysteryGiftLimitIfUnlocked:
 	call GetMysteryGiftBank
 	ld a, [sNumDailyMysteryGiftPartnerIDs]
-	cp $ff
-	jr z, .okay
+	cp -1 ; locked?
+	jr z, .dont_clear
 	xor a
 	ld [sNumDailyMysteryGiftPartnerIDs], a
-.okay
+.dont_clear
 	jp CloseSRAM
 
 BackupMysteryGift:
+; Copies [sMysteryGiftItem] to [sBackupMysteryGiftItem],
+; and [sMysteryGiftUnlocked] to [sNumDailyMysteryGiftPartnerIDs].
 	call GetMysteryGiftBank
 	ld hl, sMysteryGiftItem
 	ld de, sBackupMysteryGiftItem
@@ -1180,11 +1311,15 @@
 	ld a, [hli]
 	ld [de], a
 	inc de
+	assert sMysteryGiftItem + 1 == sMysteryGiftUnlocked
+	assert sBackupMysteryGiftItem + 1 == sNumDailyMysteryGiftPartnerIDs
 	ld a, [hl]
 	ld [de], a
 	jp CloseSRAM
 
 RestoreMysteryGift:
+; Copies [sBackupMysteryGiftItem] to [sMysteryGiftItem],
+; and [sNumDailyMysteryGiftPartnerIDs] to [sMysteryGiftUnlocked].
 	call GetMysteryGiftBank
 	ld hl, sBackupMysteryGiftItem
 	ld de, sMysteryGiftItem
@@ -1191,14 +1326,16 @@
 	ld a, [hli]
 	ld [de], a
 	inc de
+	assert sBackupMysteryGiftItem + 1 == sNumDailyMysteryGiftPartnerIDs
+	assert sMysteryGiftItem + 1 == sMysteryGiftUnlocked
 	ld a, [hl]
 	ld [de], a
 	jp CloseSRAM
 
-MysteryGift_ClearTrainerData:
-	ld hl, wMysteryGiftTrainerData
+ClearMysteryGiftTrainer:
+	ld hl, wMysteryGiftTrainer
 	xor a
-	ld b, wMysteryGiftTrainerDataEnd - wMysteryGiftTrainerData
+	ld b, wMysteryGiftTrainerEnd - wMysteryGiftTrainer
 .loop
 	ld [hli], a
 	dec b
@@ -1206,7 +1343,7 @@
 	ret
 
 GetMysteryGiftBank:
-	ld a, BANK(sBackupMysteryGiftItem)
+	ld a, BANK(sMysteryGiftData)
 	jp OpenSRAM
 
 StagePartyDataForMysteryGift:
@@ -1256,8 +1393,8 @@
 .party_end
 	ld a, -1
 	ld [de], a
-	ld a, $26
-	ld [wca00], a
+	ld a, wMysteryGiftTrainerEnd - wMysteryGiftTrainer
+	ld [wUnusedMysteryGiftStagedDataLength], a
 	jp CloseSRAM
 
 InitMysteryGiftLayout:
@@ -1409,9 +1546,9 @@
 	call PlaceString
 	call WaitBGMap
 	call Function10578c
-	call MysteryGift_ClearTrainerData
+	call ClearMysteryGiftTrainer
 	ld a, $24
-	ld [wca02], a
+	ld [wMysteryGiftStagedDataLength], a
 	ldh a, [rIE]
 	push af
 	call Function104c2d
@@ -1423,7 +1560,7 @@
 	ld a, d
 	cp $10
 	jp z, Function105712
-	cp $6c
+	cp %01101100
 	jp nz, Function10571a
 	call Function1056eb
 	ld c, 60
@@ -1431,7 +1568,7 @@
 	call Function105777
 	ld hl, MysteryGiftReceivedCardText
 	call PrintText
-	ld de, wMysteryGiftTrainerData
+	ld de, wMysteryGiftTrainer
 	farcall Function8ac70
 	ld a, c
 	ld [wDeciramBuffer], a
--- a/engine/link/mystery_gift_2.asm
+++ b/engine/link/mystery_gift_2.asm
@@ -1,6 +1,6 @@
-PrepMysteryGiftDataToSend:
+StageDataForMysteryGift:
 	ld de, wMysteryGiftStaging
-	ld a, $1 + GS_VERSION
+	ld a, GS_VERSION + 1
 	ld [de], a
 	inc de ; wMysteryGiftStaging+1
 	ld a, BANK(sGameData)
@@ -46,10 +46,10 @@
 	ld a, [sBackupMysteryGiftItem]
 	ld [de], a
 	inc de
-	ld a, [sBackupMysteryGiftItem + 1]
+	ld a, [sNumDailyMysteryGiftPartnerIDs]
 	ld [de], a
-	ld a, $14
-	ld [wca00], a
+	ld a, wMysteryGiftPlayerDataEnd - wMysteryGiftPlayerData
+	ld [wUnusedMysteryGiftStagedDataLength], a
 	call CloseSRAM
 	ld hl, wMysteryGiftStaging
 	ld de, wMysteryGiftPlayerData
@@ -121,7 +121,7 @@
 	pop de
 	ret
 
-MysteryGiftGetItemHeldEffect:
+MysteryGiftGetItem:
 	ld a, c
 	cp MysteryGiftItems.End - MysteryGiftItems
 	jr nc, MysteryGiftFallbackItem
--- a/engine/menus/intro_menu.asm
+++ b/engine/menus/intro_menu.asm
@@ -173,12 +173,13 @@
 	ld [wRoamMon2MapNumber], a
 	ld [wRoamMon3MapNumber], a
 
-	ld a, BANK(sMysteryGiftItem)
+	ld a, BANK(sMysteryGiftItem) ; aka BANK(sMysteryGiftUnlocked)
 	call OpenSRAM
 	ld hl, sMysteryGiftItem
 	xor a
 	ld [hli], a
-	dec a
+	assert sMysteryGiftItem + 1 == sMysteryGiftUnlocked
+	dec a ; -1
 	ld [hl], a
 	call CloseSRAM
 
@@ -370,7 +371,7 @@
 	ld c, 20
 	call DelayFrames
 	farcall JumpRoamMons
-	farcall MysteryGift_CopyReceivedDecosToPC
+	farcall CopyMysteryGiftReceivedDecorationsToPC
 	farcall ClockContinue
 	ld a, [wSpawnAfterChampion]
 	cp SPAWN_LANCE
--- a/engine/menus/main_menu.asm
+++ b/engine/menus/main_menu.asm
@@ -206,7 +206,7 @@
 	ld a, BANK(sNumDailyMysteryGiftPartnerIDs)
 	call OpenSRAM
 	ld a, [sNumDailyMysteryGiftPartnerIDs]
-	cp -1
+	cp -1 ; locked?
 	call CloseSRAM
 	jr nz, .mystery_gift
 	; This check makes no difference.
--- a/engine/overworld/time.asm
+++ b/engine/overworld/time.asm
@@ -272,7 +272,7 @@
 	ld hl, wBuffer1
 	call InitOneDayCountdown
 	call CloseSRAM
-	farcall Function1050c8
+	farcall ResetDailyMysteryGiftLimitIfUnlocked
 
 .not_timed_out
 	ld a, BANK(sMysteryGiftTimer)
--- a/hram.asm
+++ b/hram.asm
@@ -88,8 +88,14 @@
 hPrintNumBuffer:: ds 10
 
 NEXTU
-; miscellaneous
-	ds 9
+; Mystery Gift
+hMGExchangedByte:: db
+hMGExchangedWord:: dw
+hMGNumBits:: db
+hMGChecksum:: dw
+	ds 1
+hMGUnusedMsgLength:: db
+hMGRole:: db
 hMGStatusFlags:: db
 ENDU
 
--- a/sram.asm
+++ b/sram.asm
@@ -47,11 +47,12 @@
 sMailbox9Backup::  mailmsg sMailbox9Backup
 sMailbox10Backup:: mailmsg sMailbox10Backup
 
+sMysteryGiftData::
 sMysteryGiftItem:: db
 sMysteryGiftUnlocked:: db
 sBackupMysteryGiftItem:: db
 sNumDailyMysteryGiftPartnerIDs:: db
-sDailyMysteryGiftPartnerIDs:: ds 5 * 2 ; maximum 5 per day, 2 bytes per ID
+sDailyMysteryGiftPartnerIDs:: ds MAX_MYSTERY_GIFT_PARTNERS * 2
 sMysteryGiftDecorationsReceived:: flag_array NUM_NON_TROPHY_DECOS
 	ds 4
 sMysteryGiftTimer:: dw
@@ -58,8 +59,8 @@
 	ds 1
 sMysteryGiftTrainerHouseFlag:: db
 sMysteryGiftPartnerName:: ds NAME_LENGTH
-	ds 1
-sMysteryGiftTrainer:: ds (1 + 1 + NUM_MOVES) * PARTY_LENGTH + 2
+sMysteryGiftUnusedFlag:: db
+sMysteryGiftTrainer:: ds wMysteryGiftTrainerEnd - wMysteryGiftTrainer
 sBackupMysteryGiftItemEnd::
 
 	ds $30
--- a/wram.asm
+++ b/wram.asm
@@ -1000,29 +1000,11 @@
 
 NEXTU
 ; mystery gift data
-UNION
-wMysteryGiftPartyTemp:: ds (1 + 1 + NUM_MOVES) * PARTY_LENGTH
-
-NEXTU
 wMysteryGiftStaging:: ds 80
 
-NEXTU
-	ds 7
-wc807:: ds 1
-	ds 10
-wMobileSDK_PacketChecksum:: dw
-	ds 4
-wMobileSDK_AdapterType:: db
-	ds 5
-wMobileSDK_SendCommandID:: db
-	ds 2
-wc821:: ds 1
-wc822:: ds 46
-ENDU
-
 UNION
-wMysteryGiftTrainerData:: ds (1 + 1 + NUM_MOVES) * PARTY_LENGTH + 2
-wMysteryGiftTrainerDataEnd::
+wMysteryGiftTrainer:: ds 1 + (1 + 1 + NUM_MOVES) * PARTY_LENGTH + 1
+wMysteryGiftTrainerEnd::
 
 NEXTU
 wMysteryGiftCardHolderName:: ds PLAYER_NAME_LENGTH
@@ -1031,7 +1013,7 @@
 	ds 138
 
 wMysteryGiftPartnerData::
-wc900:: db
+wMysteryGiftGameVersion:: db
 wMysteryGiftPartnerID:: dw
 wMysteryGiftPartnerName:: ds NAME_LENGTH
 wMysteryGiftPartnerDexCaught:: db
@@ -1069,9 +1051,9 @@
 
 NEXTU
 ; mystery gift data
-wca00:: db
-wca01:: db
-wca02:: db
+wUnusedMysteryGiftStagedDataLength:: db
+wMysteryGiftMessageCount:: db
+wMysteryGiftStagedDataLength:: db
 
 NEXTU
 ; link data
@@ -1107,6 +1089,20 @@
 wccb8:: ds 1
 wccb9:: ds 1
 wccba:: ds 102
+
+NEXTU
+; mobile
+	ds 7
+wc807:: ds 1
+	ds 10
+wMobileSDK_PacketChecksum:: dw
+	ds 4
+wMobileSDK_AdapterType:: db
+	ds 5
+wMobileSDK_SendCommandID:: db
+	ds 2
+wc821:: ds 1
+wc822:: ds 46
 
 if DEF(_DEBUG)
 NEXTU