Skip to content

Instantly share code, notes, and snippets.

@RobertTheSable
Last active November 19, 2021 23:18
Show Gist options
  • Select an option

  • Save RobertTheSable/fdfe18b87f8cb6c644c7020f16e41ce5 to your computer and use it in GitHub Desktop.

Select an option

Save RobertTheSable/fdfe18b87f8cb6c644c7020f16e41ce5 to your computer and use it in GitHub Desktop.

Opening text unique characteristics.

  • 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.

Opening Text Positioning

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

Numer of opening text sections.

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 $1202 to 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

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

  • $121f contains the index for which section of text to show.
  • I don't know what $121C-D are 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.

Altering the opening text layout

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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment