La première chose à faire est de trouver de l'espace dans la RAM pour quelques variables nécessaires.
Il faut 1 octet pour la variable qui contient le nombre de pixels de large déjà occupé dans la colonne courante (que j'ai appelée Vwf.BitsUsed), cette variable est la plus importante et ne doit pas être modifié par le reste du programme tant qu'on n'a pas fini d'afficher le texte.
Il faut 1 octet pour la variable qui contient l'offset dans la table de sauts ou le compteur de boucles (Vwf.ShiftOffset), tout dépendant de la méthode utilisée, de la routine qui fait glisser les lignes de pixels. Cette variable est utilisé que jusqu'à la boucle de copie, elle n'est pas essentielle et on peux s'en passer si on modifie un peu la routine de translation en échange de plus d'espace utilisé dans la ROM et d'un ralentissement.
Il faut 1 octet pour la variable qui contient les pixels qui débordent lors de la translation (Vwf.Spill). Cette variable est essentielle mais n'est utilisée que dans la boucle de copie.
Il faut au moins 6 octets pour les entrées de tilemaps à envoyer à la VRAM (Dlg.TilemapEntry1 à Dlg.TilemapEntry3), 12 octets si on construit toutes les entrées à la fois (Dlg.TilemapEntry1 à Dlg.TilemapEntry6).
Pour les variables qui contiennent le masque de bits qui sert à éliminer les pixels indésirables (Stack.Bitmask) et le nombre de colonnes à mettre à jour (Stack.NumCols) j'ai utilisé la pile (stack en anglais).
Après investigation, ce jeu a beaucoup d'espace libre en RAM, donc j'ai programmé la routine d'affichage en utilisant cet espace, mais je vais quand même expliquer ici comment on aurait pu faire si ce n'avait pas été le cas.
Si on étudie la routine originale on peut remarquer que les adresses de $0000 à $0006 sont utilisées sans les lire donc on peut assumer qu'elles ne contiennent rien d'important. $0000 est utilisé pour le compteur de boucles donc on ne peut pas l'utiliser, $0002 est utilisé pour calculer l'adresse dans la fenêtre de dialogue donc on ne peut pas l'utiliser pour Vwf.ShiftOffset mais puisqu'elle n'est pas utilisée par la suite on peut y mettre Vwf.Spill ce qui laisse $0004 pour Vwf.ShiftOffset. Et si on construit les entrées de tilemaps de la ligne du haut en utilisant Dlg.TilemapEntry1 à Dlg.TilemapEntry3 et qu'on les envoie tout de suite, on peut réutiliser ces variables pour les entrées de la ligne du bas et ainsi libérer l'adresse de Dlg.TilemapEntry4 pour y mettre Vwf.BitsUsed.
Il faut aussi trouver de l'espace dans la ROM pour la table de largeur de caractères, 1 octet par caractère (Dlg.FontWidth).
La première partie du code ne fait pas partie du programme en tant que tel mais d'une technique que j'utilise pour réduire les risque d'erreurs lors de l'utilisation de variables sur la pile. En associant un nom à un emplacement dans la pile, on n'a qu'un endroit à modifier lorsqu'on ajoute ou enlève une variable de la pile.
- Code : Tout sélectionner
Dlg.PrintChar_S:
; stack:
.DEFINE Stack.NumCols 1 ; $01,s (word)
.DEFINE Stack.Bitmask 3 ; $03,s (word)
Suit l'initialisation qu'on n'a pas besoin de changer.
- Code : Tout sélectionner
SEP #$20 ; m
PHB
LDA.B #$7E
PHA
PLB
REP #$20 ; m
Puisque qu'à ce moment dans la routine les registre X et Y sont libres, on en profite pour initialiser toutes nos variables pour la VWF, en premier l'offset dans la table de sauts de la routine de translation. Puisque qu'il faut faire glisser chaque ligne de pixels vers la droite du nombre de pixels déjà utilisé dans la colonne courante et que chaque adresse dans la table fait 2 octets, on charge le nombre de pixels déjà utilisé et on le multiplie par 2 avant de le stocker pour plus tard dans Vwf.ShiftOffset.
- Code : Tout sélectionner
LDA.W Vwf.BitsUsed
ASL A
STA.W Vwf.ShiftOffset
On profite du fait que l'offset dans la table de masques de bits est le même que dans la table de sauts de la routine de translation pour initialiser la variable Stack.Bitmask sans avoir à recalculer l'offset, on place le résultat sur la pile.
- Code : Tout sélectionner
TAX
LDA.L Vwf.BitMasks, X
PHA ; Stack.Bitmask
La table de masques de bits est situé en dehors de la routine. Elle sert à éliminer les pixels indésirables, si il n'y a aucun pixels d'utilisé, on les efface tous, s'il y en a un d'utilisé, on conserve le premier pixels de chaque plan de bits, etc. Elle est définie comme suit:
- Code : Tout sélectionner
Vwf.BitMasks:
.DW $0000, $8080, $C0C0, $E0E0, $F0F0, $F8F8, $FCFC, $FEFE
La prochaine séquence d'instruction sert à initialiser Stack.NumCols et à mettre à jour Vwf.BitsUsed. On commence par lire la largeur du caractère en utilisant son numéro en tant qu'index.
- Code : Tout sélectionner
LDA.W Dlg.CharToPrint
AND.W #$00FF
TAX
LDA.W Dlg.FontWidth, X
AND.W #$00FF
On y ajoute le nombre de pixels déjà utilisés.
- Code : Tout sélectionner
CLC
ADC.W Vwf.BitsUsed
Le registre X va nous servir à compter le nombre de colonnes à mettre à jour, on l'initialise à 1 car dans tout les cas on doit mettre au moins une colonne à jour même si est incomplète.
- Code : Tout sélectionner
LDX.W #$0001
On soustrait la largeur d'une colonne, si on se retrouve avec un nombre négatif, nous n'avons qu'une colonne à mettre à jour, sinon on soustrait encore une colonne, si on se retrouve avec un nombre négatif, nous avons 2 colonnes à mettre à jour, dans tout les autres cas, nous avons 3 colonnes à mettre à jour, on soustrait quand même une colonne, la raison va être claire bientôt. On place le contenu final du registre X sur la pile.
- Code : Tout sélectionner
SEC
SBC.W #$0008
BCC _update_1_col
SBC.W #$0008
BCC _update_2_col
SBC.W #$0008
_update_3_col:
INX
_update_2_col:
INX
_update_1_col:
PHX ; Stack.NumCols
On rajoute la dernière colonne qu'on a soustrait pour se retrouver un nombre positif qui correspond au nombre de pixels utilisés dans la colonne après l'affichage du caractère courant, on met à jour Vwf.BitsUsed car on en aura plus besoin pour afficher le caractère courant.
- Code : Tout sélectionner
CLC
ADC.W #$0008
STA.W Vwf.BitsUsed
Les caractères ont 16 pixels de large au maximum donc on ne change pas le calcul de l'adresse du caractère dans la fonte.
- Code : Tout sélectionner
LDA.W Dlg.CharToPrint
AND.W #$0007
ASL A
STA.W $0000
LDA.W Dlg.CharToPrint
AND.W #$00F8
ASL A
ASL A
CLC
ADC.W $0000
ASL A
ASL A
ASL A
ASL A
TAX
Comme on utilise les colonnes de 1 tile de large pour indiquer l'avancement sur la ligne, on utilise le même calcul de l'adresse dans la fenêtre de dialogue que pour la version 8x16.
- Code : Tout sélectionner
LDA.W Dlg.VWFCol
AND.W #$00FF
STA.W $0000
LDA Dlg.VWFRow
AND.W #$00FF
ASL A
ASL A
STA.W $0002
ASL A
ASL A
ASL A
ASL A
SEC
SBC.W $0002
CLC
ADC.W $0000
ASL A
ASL A
ASL A
ASL A
STA.W Dlg.WndBufOfs
TAY
Avant de passer à la boucle de copie, Je vais expliquer la routine de translation qui y est utilisée. Quand on appelle la routine le registre A contient les pixels de la ligne à faire glisser vers la droite.
On commence par sauvegarder le registre X qui est l'index source dans la fonte, on utilise la variable Vwf.ShiftOffset pour sauter à l'endroit dans la séquence de shifts qui correspond au nombre de pixels déjà utilisés de manière à ce la ligne courante soit placé après celle du caractère précédent.
À chaque étape de shift on fait glisser la ligne d'un pixel vers la droite et on fait glisser le pixel qui a débordé dans Vwf.Spill.
Pour finir, on inverse les octets du registre A pour que l'octet moins significatif contienne les pixels de gauche et l'autre les pixels de droite. On restaure le registre X avant de retourner à la routine appelante.
- Code : Tout sélectionner
Vwf.ShiftLine_S:
REP #$20 ; m
PHX
LDX.W Vwf.ShiftOffset
JMP (_jumptable, X)
_jumptable:
.DW _0, _1, _2, _3, _4, _5, _6, _7
_7:
LSR A
ROR.W Vwf.Spill
_6:
LSR A
ROR.W Vwf.Spill
_5:
LSR A
ROR.W Vwf.Spill
_4:
LSR A
ROR.W Vwf.Spill
_3:
LSR A
ROR.W Vwf.Spill
_2:
LSR A
ROR.W Vwf.Spill
_1:
LSR A
ROR.W Vwf.Spill
_0:
XBA
PLX
SEP #$20 ; m
RTS
Par manque de place, j'ai du modifier la boucle de copie pour traiter les 2 lignes de tiles du caractère une après l'autre en ajustant les index après la première ligne. Cela ne fait pas partie de la VWF, si on aurait eu la place on aurait pu traiter les 2 lignes en même temps comme avant.
On initialise le compteur de boucle à 16 maintenant qu'on traite les lignes une après l'autre.
- Code : Tout sélectionner
LDA.W #$0010 ; nombre de ligne de pixels dans un caractère
STA.W $0000
La première chose qu'on fait à chaque étape de la boucle est d'effacer les pixels qui ont débordés de la ligne précédente.
- Code : Tout sélectionner
_vwf_loop:
REP #$20 ; m
STZ.W Vwf.Spill
Ensuite on efface les pixels indésirables de la première colonne.
- Code : Tout sélectionner
LDA.W DlgWndGfxBuf, Y
AND Stack.Bitmask, S
STA.W DlgWndGfxBuf, Y
Puisqu'on doit traiter une ligne de pixels complète à chaque étape de la boucle, on ne peut plus traiter la colonne de gauche et celle de droite séparément. De plus, puisqu'on utilise des tiles au format GameBoy, on ne peut placer les lignes des 2 colonnes en même temps dans le registre A parce que 8 pixels font 16 bits et on doit traiter 16 pixels à la fois (le registre A a 16 bits). Ce qu'on doit faire c'est de traiter chaque plan de bits de la ligne séparément.
On lit le premier plan de bit de la colonne gauche, on inverse les octets du registre A pour que les pixels qu'on vient de lire se retrouve dans l'octet le plus significatif, ensuite on lit le premier plan de bit de la colonne droite. Les pixels dans le registre ressemble à ce qu'on pourrait voir, c'est à dire les pixels de gauche à gauche et ceux de droite à droite.
- Code : Tout sélectionner
SEP #$20 ; m
LDA.W DlgFontGfx, X
XBA
LDA.W DlgFontGfx+$10, X
On appelle ensuite la routine de translation pour faire glisser la ligne vers la droite.
- Code : Tout sélectionner
JSR Vwf.ShiftLine_S
On combine les pixels de gauche avec ceux du caractère précédent.
- Code : Tout sélectionner
ORA.W DlgWndGfxBuf, Y
STA.W DlgWndGfxBuf, Y
on inverse pour obtenir les pixels de droite qu'on met à leur place.
- Code : Tout sélectionner
XBA
STA.W DlgWndGfxBuf+$10, Y
On lit les pixels qui ont débordés qu'on place à la suite des autres.
- Code : Tout sélectionner
LDA.W Vwf.Spill+1
STA.W DlgWndGfxBuf+$20, Y
On fait la même chose pour le deuxième plan de bits.
- Code : Tout sélectionner
LDA.W DlgFontGfx+1, X
XBA
LDA.W DlgFontGfx+$11, X
JSR Vwf.ShiftLine_S
ORA.W DlgWndGfxBuf+1, Y
STA.W DlgWndGfxBuf+1, Y
XBA
STA.W DlgWndGfxBuf+$11, Y
LDA.W Vwf.Spill+1
STA.W DlgWndGfxBuf+$21, Y
Si on vient de terminer de traiter la huitième ligne de pixels, on doit ajuster les index. La deuxième ligne de tiles est $100 octets plus loin que la première dans la font et $1E0 plus loin dans la fenêtre de dialogue et comme les index ont déjà avancés de 16 octets nous devons soustraire ce nombre pour obtenir le nombre à ajouter.
- Code : Tout sélectionner
LDA.W $0000
CMP.B #$08
BNE _inc_indices
_adjust_indices:
REP #$20 ; m
TXA
CLC
ADC.W #$00F0
TAX
TYA
CLC
ADC.W #$01D0
TAY
DEC.W $0000
BRA _vwf_loop
Si ce n'était pas la huitième ligne, on augmente les index comme d'habitude.
- Code : Tout sélectionner
_inc_indices:
INX
INX
INY
INY
DEC.W $0000
BNE _vwf_loop
La boucle de copie est maintenant terminée et notre caractère a été copié dans la mémoire tampon de la fenêtre de dialogue. On doit l'envoyer à la VRAM. Cette partie est pratiquement identique à l'originale excepté qu'on utilise la variable Stack.NumCols pour calculer le nombre d'octets à envoyer.
- Code : Tout sélectionner
_send_to_vram:
REP #$20 ; m
LDA.W Dlg.WndBufOfs
LSR A
ADC.W #$5008 ; VRAM:$A010
STA.W DMA.QueueItemToAdd.VRAMAddr
LDA.W Dlg.WndBufOfs
CLC
ADC.W #DlgWndGfxBuf
STA.W DMA.QueueItemToAdd.SrcAddr
LDA Stack.NumCols, S
ASL A
ASL A
ASL A
ASL A
STA.W DMA.QueueItemToAdd.Size
SEP #$20 ; m
LDA.B #$80
STA.W DMA.QueueItemToAdd.VMAIN
LDA.B #$7E
STA.W DMA.QueueItemToAdd.SrcAddr+2
LDA.B #$01
STA.W DMA.QueueItemToAdd.DMAP
LDA.B #$18
STA.W DMA.QueueItemToAdd.DestReg
JSL.L DMA.QueueDMATransfer_L
REP #$20 ; m
LDA.W Dlg.WndBufOfs
LSR A
CLC
ADC.W #$50F8 ; VRAM:$A1F0
STA.W DMA.QueueItemToAdd.VRAMAddr
LDA Dlg.WndBufOfs
CLC
ADC.W #DlgWndGfxBuf+$1E0
STA.W DMA.QueueItemToAdd.SrcAddr
JSL.L DMA.QueueDMATransfer_L
On calcule maintenant les valeurs qui vont servir à créer les entrées de tilemap, cette partie est pareille à l'originale excepté qu'on utilise la variable Stack.NumCols pour le nombre d'entrées à envoyer.
- Code : Tout sélectionner
_update_tilemap:
LDA.W Dlg.VWFCol
AND.W #$00FF
STA.W $0000
LDA.W Dlg.VWFRow
AND.W #$00FF
ASL A
ASL A
ASL A
ASL A
ASL A
ASL A
CLC
ADC.W $0000
ADC.W #$5C41 ; VRAM:$B882
STA.W $0003 ; VRAMAddr
LDA.W #Dlg.TilemapEntry1
STA.W $0000 ; srcAddr
SEP #$20 ; m
LDA.B #$00
STA.W $0002 ; srcBank
STZ.W $0005 ; VMAIN
LDA Stack.NumCols, S
STA.W $0006 ; numWords
REP #$20 ; m
LDA.W Dlg.WndBufOfs
LSR A
LSR A
LSR A
LSR A
INC A
STA.W Dlg.TilemapCharNum
LDA.W Dlg.TextPaletteIndex
AND.W #$00FF
XBA
ASL A
ASL A
ORA.W #$2000
STA.W Dlg.TilemapPalNum
On construit 3 entrées de tilemap pour chaque ligne de tiles même si elles ne sont peut-être pas toutes envoyées, c'est plus facile ainsi.
- Code : Tout sélectionner
ORA.W Dlg.TilemapCharNum
STA.W Dlg.TilemapEntry1
LDA.W Dlg.TilemapCharNum
CLC
ADC.W #$0001
ORA.W Dlg.TilemapPalNum
STA.W Dlg.TilemapEntry2
LDA.W Dlg.TilemapCharNum
CLC
ADC.W #$0002
ORA.W Dlg.TilemapPalNum
STA.W Dlg.TilemapEntry3
LDA.W Dlg.TilemapCharNum
CLC
ADC.W #$001E
ORA.W Dlg.TilemapPalNum
STA.W Dlg.TilemapEntry4
LDA.W Dlg.TilemapCharNum
CLC
ADC.W #$001F
ORA.W Dlg.TilemapPalNum
STA.W Dlg.TilemapEntry5
LDA.W Dlg.TilemapCharNum
CLC
ADC.W #$0020
ORA.W Dlg.TilemapPalNum
STA.W Dlg.TilemapEntry6
Ensuite on les envoie à la VRAM, puisque qu'on a 3 entrées de tilemap on doit augmenter l'adresse source de 6 au lieu de 4 et comme précédemment on utilise la variable Stack.NumCols pour le nombre d'entrées à envoyer.
- Code : Tout sélectionner
JSL.L VRAM.QueueTransfer_L
LDA.W $0003
CLC
ADC.W #$0020
STA.W $0003 ; VRAMAddr
LDA.W $0000
CLC
ADC.W #$0006 ; Maintenant 6
STA.W $0000 ; srcAddr
SEP #$20 ; m
LDA.B #$00
STA.W $0002 ; srcBank
LDA Stack.NumCols, S
STA.W $0006 ; numWords
JSL.L VRAM.QueueTransfer_L
Pour ajuster le numéro de la colonne dans la fenêtre de dialogue, on utilise encore la variable Stack.NumCols, mais on doit auparavant la décrémenter car la dernière colonne mise à jour n'est pas complète, c'est à dire qu'on peut y ajouter une partie du prochain caractère.
- Code : Tout sélectionner
LDA Stack.NumCols, S
DEC A
CLC
ADC.W Dlg.VWFCol
STA.W Dlg.VWFCol
Si on doit sauter à la ligne, on réinitialise la variable Vwf.BitsUsed pour commencer au début de la tile.
- Code : Tout sélectionner
CMP.B #030 ; max char on line
BCC _no_newline
STZ.W Vwf.BitsUsed
STZ.W Dlg.VWFCol
INC Dlg.VWFRow
Avant de quitter la routine on doit libérer l'espace qu'on a réservé sur la pile.
- Code : Tout sélectionner
_no_newline:
PLX ; libérer Stack.NumCols
PLX ; libérer Stack.Bitmask
PLB
RTS
Il resterait quelques détails à ajuster comme réinitialiser la variable Vwf.BitsUsed dans la routine d'initialisation de la fenêtre de dialogue et dans les codes de saut de ligne et de déplacement du curseur ainsi que de vérifier que les menus sont bien alignés mais cela dépasse le cadre de cette explication.
