- Text for the opening crawl is actually on the sprite layer instead of BG layer 2.
- Presumably this is to allow the text to scroll upward while the background scrolls horizontally
- There are 128 16x16 sprite slots availabe.
- Sprite OAM data is copied from $7e0210
- Unlike in other text types, not all text in VRAM is shown, and it's not neccesarily shown in order.
Some quick terminology clarification: the opening is split into 6 "sections." Each "section" mattches up with an image shown in the opening. Each section is then divided into 8 "sub-sections" which can be shown at various (relative) positions.
The position of each sub-section of text is defined in a table at $8DF9C4.
- The first byte is the X coordinate.
- If X is 0 the game skips processing the fields and shows no data.
- The second byte is the Y coordinate.
- The 3rd byte is the number of sprites to use.
- AKA half the number of characters assuming the font is 8 pixels wide.
- The 4th byte is the starting tile number.
- Some values are skipped because they would correspond to half-tiles.
For example, here are the positions for the text of the first section of the opening.
ORG $8DF9C4
30 6D 0A 00 ; show 20 characters starting from tile 0, $30 pixels from the left, $6D from the top
30 80 09 24 ; show 18 characters starting from tile $24, $30 pixels from the left, $80 from the top
40 93 07 48 ; ...etc
00 6D 00 00 ; this pattern = no data
00 6D 00 00
00 6D 00 00
00 6D 00 00
00 6D 00 00
And here is the algorithm that parses the sub-segments.
8d/f94f: lda $121e
8d/f952: beq $f9c3
8d/f954: xba ; it loads $121E but actually checks $121F
8d/f955: and #$00ff
8d/f958: dec
8D/F959: 0A asl A
8D/F95A: 0A asl A
8D/F95B: 0A asl A
8D/F95C: 0A asl A
8D/F95D: 0A asl A
8D/F95E: AA tax
8D/F95F: A00800 ldy #$0008
8D/F962: 5A phy
8D/F963: BFC4F98D lda $8DF9C4,X
8D/F967: 29FF00 and #$00FF
8D/F96A: F032 beq $F99E
8D/F96C: 8556 sta $56 ; starting x coordinate
8D/F96E: BFC5F98D lda $8DF9C5,X
8D/F972: 38 sec
8D/F973: E96D00 sbc #$006D
8D/F976: 29FF00 and #$00FF
8D/F979: 18 clc
8D/F97A: 6D2012 adc $1220
8D/F97D: C9F0FF cmp #$FFF0
8D/F980: B005 bcs $F987
8D/F982: C9F000 cmp #$00F0
8D/F985: B017 bcs $F99E
8D/F987: 29FF00 and #$00FF
8D/F98A: 8558 sta $58 ; Y coordinate
8D/F98C: BFC6F98D lda $8DF9C6,X
8D/F990: 29FF00 and #$00FF
8D/F993: A8 tay
8D/F994: BFC7F98D lda $8DF9C7,X ; character #
8D/F998: 29FF00 and #$00FF
8D/F99B: 2084FA jsr $FA84
8D/FA84: 08 php
8D/FA85: DA phx
8D/FA86: C230 rep #$30
8D/FA88: A20038 ldx #$3800 ; pallete 4, both priority bits set
8D/FA8B: 8619 stx $19
8D/FA8D: 8517 sta $17
8D/FA8F: A650 ldx $50
8D/FA91: A556 lda $56
8D/FA93: 8515 sta $15
8D/FA95: A515 lda $15
8D/FA97: 9D1002 sta $0210,X
8D/FA9A: 18 clc
8D/FA9B: 691000 adc #$0010
8D/FA9E: 8515 sta $15
8D/FAA0: A558 lda $58
8D/FAA2: 9D1102 sta $0211,X
8D/FAA5: A517 lda $17
8D/FAA7: 0519 ora $19
8D/FAA9: 9D1202 sta $0212,X ; writes both byte 3 (character #) and 4 (pallete/etc)
8D/FAAC: A517 lda $17
8D/FAAE: 18 clc
8D/FAAF: 690200 adc #$0002
8D/FAB2: 891000 bit #$0010
8D/FAB5: F004 beq $FABB
8D/FAB7: 18 clc
8D/FAB8: 691000 adc #$0010
8D/FABB: 8517 sta $17
8D/FABD: E8 inx
8D/FABE: E8 inx
8D/FABF: E8 inx
8D/FAC0: E8 inx
8D/FAC1: 88 dey
8D/FAC2: D0D1 bne $FA95
8D/FAC4: 8650 stx $50
8D/FAC6: FA plx
8D/FAC7: 28 plp
8D/FAC8: 60 rts
8D/F99E: E8 inx
8D/F99F: E8 inx
8D/F9A0: E8 inx
8D/F9A1: E8 inx
8D/F9A2: 7A ply
8D/F9A3: 88 dey
8D/F9A4: D0BC bne $F962
The overall slow of the opening is controlled by another table at $8DDE6F
The table is divided into entries of 3 words each (aka 6 bytes).
- The first word is compared with a counter at
$1202to determine if it should proceed or wait.- Some values are reserved though and will be processed regardless of the counter value.
Mostly they just write something to some offset or execute some subroutine.
I haven't investigated what they do yet.
- 0 stores data to $89
- 1 stores data to $8D
- 2 stores data to $80
- 3 stores data to $81
- 4 and 5 jump to different subroutines
- Some values are reserved though and will be processed regardless of the counter value.
Mostly they just write something to some offset or execute some subroutine.
I haven't investigated what they do yet.
Here are the contents:
00 00 00 00 80 FF
00 00 08 00 05 00
00 00 0E 00 08 00
00 00 10 00 01 00
00 00 12 00 00 00
00 00 16 00 40 00
00 01 1A 00 40 00
F0 00 04 00 03 00
02 00 04 00 06 00
02 00 04 00 09 00
02 00 04 00 1E 00
02 00 04 00 21 00
02 00 04 00 24 00
02 00 04 00 27 00
40 00 1E 00 00 01 ; text section 1
80 00 1C 00 00 02
D0 01 01 00 C0 FF
40 00 1C 00 00 04
00 00 14 00 80 00
00 00 18 00 00 00
80 00 0C 00 20 00
40 00 1E 00 00 02 ; text section 2
00 01 1C 00 00 02
C0 00 04 00 0C 00
01 00 04 00 0F 00
00 00 14 00 00 00
00 00 18 00 80 00
80 00 1C 00 00 04
80 00 0C 00 20 00
00 00 00 00 B0 FF
00 01 1E 00 00 03 ; text section 3
40 00 1C 00 00 02
C0 00 1C 00 00 04
01 00 04 00 12 00
01 00 04 00 15 00
00 00 14 00 80 00
00 00 18 00 00 00
40 01 1C 00 00 02
40 01 1C 00 00 04
80 00 0C 00 20 00
00 00 01 00 E8 FF
80 01 1E 00 00 04 ; text section 4
00 00 1C 00 00 02
80 00 04 00 18 00
01 00 04 00 1B 00
00 00 14 00 00 00
00 00 18 00 80 00
80 01 1C 00 00 04
80 00 0C 00 20 00
00 00 00 00 B0 FF
00 00 08 00 05 00
00 01 1E 00 00 05 ; text section 5
40 00 1C 00 00 02
C0 00 04 00 09 00
00 00 14 00 80 00
00 00 18 00 00 00
00 01 1C 00 00 04
C0 00 1C 00 00 02
40 01 1C 00 00 04
80 00 0C 00 20 00
00 00 01 00 00 01
00 01 1E 00 00 06 ; text section 6
08 00 05 00 00 00
38 00 1C 00 00 02
C0 00 1C 00 00 04
D0 00 FF FF FF FF
Here is the code that initializes the counters for the opening
$121fcontains the index for which section of text to show.- I don't know what
$121C-Dare for yet.
8dd9e4 lda $074b
8dd9e7 cmp #$000b
8dd9ea beq $d9f9
8dd9ec stz $121c
8dd9ef stz $121e ; also sets $121F because we're in 16-bit accumulator mode.
8dd9f2 sep #$20
8dd9f4 lda #$8d01
8dd9f6 sta $074b
8dd9f9 plp
8dd9fa rts
And here is the code that controls the overall state of the opening.
8D/E12E: 08 php
8D/E12F: C230 rep #$30
8D/E131: AE0012 ldx $1200 ; overall index for opening position (I think)
8D/E134: AD0212 lda $1202 ; sub-index for opening position
8D/E137: DD6FDE cmp $DE6F,X
8D/E13A: D03A bne $E176
8D/E13C: 9C0212 stz $1202
8D/E13F: BD71DE lda $DE71,X
8D/E142: F037 beq $E17B
8D/E144: C90100 cmp #$0001
8D/E147: F041 beq $E18A
8D/E149: C90200 cmp #$0002
8D/E14C: F04B beq $E199
8D/E14E: C90300 cmp #$0003
8D/E151: F059 beq $E1AC
8D/E153: C90400 cmp #$0004
8D/E156: F068 beq $E1C0
8D/E158: C90500 cmp #$0005
8D/E15B: F016 beq $E173
8D/E15D: C9FFFF cmp #$FFFF ; opening is over
8D/E160: F071 beq $E1D3
8D/E162: A8 tay
8D/E163: BD73DE lda $DE73,X
8D/E166: 990012 sta $1200,Y
8D/E169: 8A txa
8D/E16A: 18 clc
8D/E16B: 690600 adc #$0006
8D/E16E: 8D0012 sta $1200
8D/E171: 80BE bra $E131
8D/E173: 4CEDE1 jmp $E1ED
8D/E176: EE0212 inc $1202
8D/E179: 28 plp
8D/E17A: 60 rts
8D/E17B: BD73DE lda $DE73,X
8D/E17E: 8589 sta $89
8D/E180: 8A txa
8D/E181: 18 clc
8D/E182: 690600 adc #$0006
8D/E185: 8D0012 sta $1200
8D/E188: 80A7 bra $E131
8D/E18A: BD73DE lda $DE73,X
8D/E18D: 858D sta $8D
8D/E18F: 8A txa
8D/E190: 18 clc
8D/E191: 690600 adc #$0006
8D/E194: 8D0012 sta $1200
8D/E197: 8098 bra $E131
8D/E199: E220 sep #$20
8D/E19B: BD73DE lda $DE73,X
8D/E19E: 8580 sta $80
8D/E1A0: C220 rep #$20
8D/E1A2: 8A txa
8D/E1A3: 18 clc
8D/E1A4: 690600 adc #$0006
8D/E1A7: 8D0012 sta $1200
8D/E1AA: 8085 bra $E131
8D/E1AC: E220 sep #$20
8D/E1AE: BD73DE lda $DE73,X
8D/E1B1: 8581 sta $81
8D/E1B3: C220 rep #$20
8D/E1B5: 8A txa
8D/E1B6: 18 clc
8D/E1B7: 690600 adc #$0006
8D/E1BA: 8D0012 sta $1200
8D/E1BD: 4C31E1 jmp $E131
8D/E1C0: BD73DE lda $DE73,X
8D/E1C3: 9B txy
8D/E1C4: AA tax
8D/E1C5: 98 tya
8D/E1C6: 18 clc
8D/E1C7: 690600 adc #$0006
8D/E1CA: 8D0012 sta $1200
8D/E1CD: 20A3E0 jsr $E0A3
8D/E1D0: 4C31E1 jmp $E131
8D/E1D3: A9F010 lda #$10F0
8D/E1D6: 22E3B480 jsr $80B4E3
8D/E1DA: A93C00 lda #$003C
8D/E1DD: 206CED jsr $ED6C
8D/E1E0: 227AF78D jsr $8DF77A
8D/E1E4: E220 sep #$20
8D/E1E6: A904 lda #$04
8D/E1E8: 8D4B07 sta $074B
8D/E1EB: 8089 bra $E176
8D/E1ED: DA phx
; ...and beynd here is another long subroutine I don't understand yet.
I wrote a few Asar macros that can be used to more easily control the layout of text in the opening.
Here are the macros, plus showing how to use them for the vanilla opening layout.
includeonce
!baseX = $0
!baseY = $6D
!baseY2 = $67 ; lines in the opening after position 5 or thereabouts are
; offset by 6 from the usual pattern
; so I added a second constant for compatibility
!offsetX = 8
!offsetY = $13
; index must be a multiple of 2 assuming sprites are 16x16 and text is 8x16
macro OpeningLine(x, y, count, index)
db !baseX+(<x>*!offsetX), !baseY+(<y>*!offsetY), <count>>>1, ((<index>&$F0)<<1)|(<index>&$0F)
endmacro
; index must be a multiple of 2
macro OpeningLine2(x, y, count, index)
db !baseX+(<x>*!offsetX), !baseY2+(<y>*!offsetY), <count>>>1, ((<index>&$F0)<<1)|(<index>&$0F)
endmacro
macro EmptyLine()
%OpeningLine(0, 0, 0, 0)
endmacro
ORG $8DF9C4
; opening part 1
%OpeningLine(6, 0, 20, 0)
%OpeningLine(6, 1, 18, 20)
%OpeningLine(8, 2, 14, 40)
%EmptyLine()
%EmptyLine()
%EmptyLine()
%EmptyLine()
%EmptyLine()
; part 2
%OpeningLine(6, 0, 24, 0)
%OpeningLine(9, 1, 14, 24)
%EmptyLine()
%OpeningLine(9, 3, 14, 44)
%EmptyLine()
%EmptyLine()
%EmptyLine()
%EmptyLine()
; part 3
%OpeningLine(5, 0, 22, 0)
%OpeningLine(10, 1, 12, 24)
%OpeningLine(11, 2, 10, 38)
%EmptyLine()
%OpeningLine2(7, 5, 18, 50)
%OpeningLine2(4, 6, 24, 70)
%OpeningLine2(8, 7, 14, 96)
%OpeningLine2(8, 8, 16, 112)
; part 4
%OpeningLine(8, 0, 16, 0)
%OpeningLine(5, 1, 22, 18)
%EmptyLine()
%EmptyLine()
%EmptyLine()
%EmptyLine()
%EmptyLine()
%EmptyLine()
; part 5
%OpeningLine(10, 0, 12, 0)
%OpeningLine(4, 1, 24, 12)
%OpeningLine(9, 2, 14, 38)
%EmptyLine()
%OpeningLine2(7, 6, 18, 54)
%OpeningLine2(7, 7, 18, 74)
%EmptyLine()
%EmptyLine()
; part 6
%OpeningLine(8, 0, 16, 0)
%OpeningLine(8, 1, 16, 18)
%OpeningLine(7, 2, 18, 36)
%EmptyLine()
%EmptyLine()
%EmptyLine()
%EmptyLine()
%EmptyLine()