Déplacer et agrandir des cadres de menus
sur Captain Tsubasa IV (SNES)


Tout d'abord, vous aurez besoin d'un émulateur offrant des options de "debug", dans mon cas j'utilise "Snes9x Geiger".

Pour les options, il suffit simplement de cocher "Squelch" (dans "CPU Trace Options") et "Auto Usage Map" (dans "Misc Options")

Cliquez sur "Run" pour lancer la rom (je vous conseille d'utiliser la fonction Turbo de l'émulateur pour vite vous rendre à l'endroit du cadre que vous désirez agrandir. La fonction Turbo s'active/se désactive en appuyant sur la touche "TAB") et rendez-vous à l'endroit où le cadre doit s'afficher (un peu avant, en fait)

Ici, je veux déplacer puis agrandir le cadre des tirs spéciaux :

Cadre qui apparait lorsqu'on choisit "Tir" avant certains personnages. Je sais que le n°8 de l'équipe que l'on contrôle lors du premier match possède des tirs spéciaux, donc je me débrouille pour lui faire passer le ballon et j'appuie sur "B" de la manette pour faire apparaitre le menu des actions.

Comme je sais que le cadre que je veux déplacer/agrandir va apparaître lors de mon prochain choix, je vais lancer le "trace" des instructions maintenant (juste avant de le faire apparaître)


Première partie : Déplacer le cadre

Pour lancer le "trace", je coche simplement l'option "CPU" (dans "Logging"), je clique sur la fenêtre du jeu pour que le jeu reprenne, je fais apparaître le cadre en question (il est possible que le jeu rame un peu à ce moment là, c'est normal, ça vient du fichier "trace" en cours de création qui va peser dans les 100-200 Mo) Une fois le cadre apparu, décochez l'option "CPU" pour arrêter le "trace".

Retournez sur la fenêtre de jeu et jouez avec les boutons 1-2-3-4 (les chiffres au dessus des lettres sur votre clavier), vous verrez que selon la touche pressée, divers morceaux de l'écran disparaissent/réaparaissent. Il vous faut trouver celle qui fait disparaître le cadre que vous voulez déplacer/agrandir. Ici, c'est la touche "3". Sachant que les touches "1-2-3-4" correspondent, dans l'ordre, aux "BG 0-1-2-3", cela signifie que le cadre en question se trouve sur le "BG" (background) 2.

Cliquez sur le bouton "What's Used" dans le menu debug, ce qui vous affichera ceci :

VRAM write address: 0x7e7e(nByte), Full Graphic: 0, Address inc: 1
BG0: VOffset:0, HOffset:0, W:32, H:32, TS:8, BA:0x3800, TA:0x6000
BG1: VOffset:0, HOffset:7, W:32, H:32, TS:8, BA:0x5800, TA:0x6000
BG2: VOffset:0, HOffset:0, W:32, H:32, TS:8, BA:0x7c00, TA:0x7000
BG3: VOffset:0, HOffset:0, W:32, H:32, TS:8, BA:0x0000, TA:0x0000
Main screen (always on): BG1,BG2,OBJ,
Sub-screen (always on):

Window 1 (0, 0, 13, 00): BG0(O-OR),BG1(O-OR),OBJ(O-OR),
Window 2 (0, 0):
Fixed colour: 000000

Seule la ligne suivante nous intéresse ici :

BG2: VOffset:0, HOffset:0, W:32, H:32, TS:8, BA:0x7c00, TA:0x7000

"BA" correspond à l'adresse de la tilemap et "TA" à l'adresse des tiles en VRAM, et ici, c'est l'adresse du tilemap (le "BA") qui nous intéresse.

Attention, il faut multiplier cette adresse par 2.

Cela donne donc "7C00 * 2 = F800".

Si vous affichez la représentation hexadécimal de la rom (bouton "Show Hex"), que vous sélectionnez "VRAM", et que vous entrez l'adresse "F800", vous trouvez la tilemap.

Après, il y a, en gros, deux façons d'écrire en VRAM.

Soit directement, soit par transfert DMA.

Mais quoi qu'il arrive, le jeu utilise le registre $2116 pour dire où écrire en VRAM.

A noter que ce registre utilise l'adresse divisée par 2 (donc "7C00" pour notre cas, la valeur que l'on a multiplié par deux tout à l'heure pour trouver "F800")

Ouvrez donc ce gros fichier .log en question avec un éditeur de texte et commencez par chercher tous les écritures dans le registre 2116, en vérifiant si l'adresse envoyé se rapproche de "7C00".

Je traduis en langage compréhensible pour le commun des mortels (tel que moi), chercher "$2116" dans le log, jusqu'à ce que vous trouviez une ligne qui, en plus de contenir "$2116", contient une valeur approchante de "7C00" au niveau du "A:".

Attention, selon les jeux, il n'y aura pas obligatoirement le "$" devant le 2116, donc dans le doute, rechercher juste ":2116]". Pareil pour la valeur approchante recherchée, selon les jeux, elle pourrait se trouver dans le registre X, voir dans le registre Y. A vous de vous adapter en fonction du jeu.

Le premier résultat sur lequel vous devriez tomber en recherchant "$2116" est :

$00/8165 8D 16 21 STA $2116 [$00:2116] A:7E4E X:0000 Y:0011 P:enVmxdIzc

On voit que la valeur de "A" est de "7E4E", ce qui correspond bien à une valeur approche de "7C00".

Le but va donc d'être de vérifier si on est tombé sur la bonne ligne (celle qui correspond au cadre que l'on veut modifier) Evidemment, vous vous doutez bien que si j'ai mis cette ligne en exemple, c'est que c'est la bonne. Cependant, si le test ne s'avérait pas concluant, il suffirait de continuer à rechercher d'autres lignes similaires à celles-ci (avec "$2116" et une valeur de "A" approchante de "7C00" jusqu'à trouver la bonne.

Pour savoir à quoi correspond ce "7E4E" en VRAM, comme tout à l'heure, on multiplie par deux, ce qui donne "FC9C".

On va donc voir ce qu'il y a en VRAM à l'adresse "FC9C" et on tombe alors sur "BA20".

Cela ne fonctionne pas tout le temps, mais il possible de modifier la valeur directement dans l'éditeur hexadécimal de l'émulateur pour voir ce que ça donne dans le jeu.

On remplace donc "BA20" par "0000" et on regarde ce que ça donne dans le jeu en cliquant sur la fenêtre du jeu pour la réactiver.

Vous remarquez que cela fait disparaître le coin haut-gauche du cadre que l'on veut déplacer/modifier, nous sommes donc tombés sur la bonne ligne lors du "trace".

On sait donc que le cadre en question est écrit en "7E4E" (via le registre 2116 [$2116]) Si on veut décaler ce cadre à gauche ou à droite, il faut donc modifier cette valeur. Il faut donc savoir où sort ce "7E4E".

La valeur "7E4E" se trouvant dans le registre A (A:7E4E), il doit donc logiquement y avoir une instruction "LDA" juste avant le "STA $2116".

En remontant à la ligne du dessous, on trouve :

$00/8162 BD 01 01 LDA $0101,x[$00:0101] A:0011 X:0000 Y:0011 P:enVmxdIzc

Cela signifie que le "7E4E" se trouve en "$00:0101"

Nous sommes sur une lorom, donc toute adresse (les quatre chiffres à droite des deux-points, ici le "0101") inférieure à 8000 est en ram

Ce n'est donc pas encore la bonne adresse, il faut trouver à quel moment le jeu écrit en "00:0101".

Pour cela, nos allons rechercher ":0101]" en faisant une recherche vers le haut (option disponible dans le bloc-notes de Windows, par exemple) par rapport à la ligne que j'ai cité précédemment, ce qui nous donne :

$04/D18B 9D 01 01 STA $0101,x[$01:0101] A:7E4E X:0000 Y:0000 P:envmXdizc

N'oubliez pas qu'on cherche toujours d'où sort le "7E4E", il faut donc qu'il y est "A:7E4E" dans votre ligne si la recherche vous en propose plusieurs (ici, la ligne contenant "A:7E4E" est la première proposée par la recherche)

On remonte ensuite un peu dans le log, pour essayer de trouver d'où sort le "7E4E".

On peut tout simplement regarder le contenu du registre A ("A:") et voir à quel moment il passe à "7E4E", ce qui nous donne :

$04/D185 AD FB 04 LDA $04FB [$01:04FB] A:0000 X:0000 Y:0000 P:envmXdiZc
$04/D188 18 CLC A:7E4E X:0000 Y:0000 P:envmXdizc

On voit que juste avant le "STA" de la ligne précédente, il y a un "LDA $04FB [$01:04FB]" avec le contenu du registre A qui est vide (A:0000) et à la ligne suivante, on voit que le contenu du registre passe à "7E4E" (A:7E4E).

Cependant, on se rend compte que le "$01:04FB" est encore une adresse en RAM ("04FB < 8000"), on va donc remonter encore en cherchant ":04FB]", ce qui nous donne :

$05/EA94 8D FB 04 STA $04FB [$05:04FB] A:7E4E X:0006 Y:0001 P:envmXdizc

Et juste à la ligne au-dessus, on peut trouver :

$05/EA91 A9 4E 7E LDA #$7E4E A:7F7D X:0006 Y:0001 P:envmXdizc

Cette instruction charge (LDA) la valeur (#$) 7E4E dans le registre A

Le "7E4E" se trouve donc à l'adresse "$05/EA91", qu'il faut convertir en hexadécimal.

Sauf qu'en "$05/EA91, c'est l'adresse de l'instruction.

La valeur se trouve juste après, on la voit sur la ligne du log que je remets ici :

$05/EA91 A9 4E 7E LDA #$7E4E A:7F7D X:0006 Y:0001 P:envmXdizc

Après l'adresse de l'instruction ($05/EA91), on trouve les octets qui correspondent à cette instruction : "A94E7E".

Le "A9" correspond au "LDA", et après, quelle coincidence, "4E7E".

La SNES est en little endian donc il faut inverser les deux valeurs, ce qui donne "7E4E".

Tout se vérifie donc, c'est parfait ! L'adresse que l'on recherchait pour déplacer le cadre est donc "$05EA91"

Nous verrons un peu plus tard dans cette explication comment écrire un script asm compilable avec xkas afin de modifier facilement cette valeur.

Maintenant qu'on peut déplacer le cadre (le cadre étant aligné à droite, nous désirons donc le déplacer donc à l'extrême gauche), il va maintenant falloir l'agrandir pour pouvoir utiliser toute la surface affichable de l'écran.


Seconde partie : Agrandir le cadre

Nous allons repartir sur notre "$2116" :

$00/8165 8D 16 21 STA $2116 [$00:2116] A:7E4E X:0000 Y:0011 P:enVmxdIzc

Comme il a été dit plus haut, le jeu utilise généralement 2 méthodes pour transférer les données en VRAM, transfert DMA ou écriture directe.

Le transfert est activé par le registre "$420B" (c'est une constante valable pour tous les jeux SNES)

L'écriture directe, quant à elle, est activée par l'écriture dans le registre "$2118/$2119" (il y a visiblement peu de jeux utilisant le registre 2118, surtout de très vieux jeux)

Nous allons donc rechercher une écriture dans le registre 2118 ($2118) qui apparaît après la ligne (donc en dessous) citée précédemment, ce qui nous donne comme premier résultat :

$00/8181 8D 18 21 STA $2118 [$00:2118] A:20BA X:0000 Y:0011 P:envmxdIzc

Ici, le jeu écrit le registre A en VRAM. "A" vaut "20BA" en little endian donc "BA20" (on inverse les valeurs).

Si vous vous rappellez, nous avons tout à l'heure modifié la valeur "BA20" (par "0000") et cela faisait disparaître le coin haut-gauche du cadre que l'on désirait déplacer.

Si on continue de regarder le log, on voit qu'il écrit plusieurs fois de suite dans le registre 2118 ($2118) On peut voir beaucoup de "20BB" et un peu (moins) de "20BC" chargé dans le registre A ("A:20BB" et "A:20BC").

Ce que nous désirons faire, c'est augmenter le nombre de "20BB", vu que "20BA" est le coin haut-gauche, "20BC" le coin haut-droit du cadre, et donc "20BB", les morceaux de tiles composant la ligne horizontale qui relie les deux.

Nous allons donc commencer par chercher d'où sort ce "20BB" et comment fait le jeu pour savoir combien en afficher.

On recherche donc une écriture dans le registre 2118 (x2118) avec "20BB" chargé dans le registre A ("A:20BB"), ce qui nous donne :

$00/8181 8D 18 21 STA $2118 [$00:2118] A:20BB X:0002 Y:0010 P:envmxdIzc

On trouve, juste au-dessus, la ligne suivante :

$00/817E BD 03 01 LDA $0103,x[$00:0105] A:20BA X:0002 Y:0010 P:envmxdIzc

On voit que le jeu récupère le "20BB" de "$00:0105", qui est une adresse en RAM ("0105 < 8000").

Il faut donc rechercher vers le haut ":0105]", ce qui nous donne comme premier résultat :

$04/D1D9 99 03 01 STA $0103,y[$01:0105] A:7EBB X:00BB Y:0002 P:eNvMXdizc

Si on veut savoir d'où sort le "BB", on regarde juste au-dessus :

$04/D1D3 BD 41 97 LDA $9741,x[$01:9741] A:7E20 X:0000 Y:0002 P:envMXdizc
$04/D1D9 99 03 01 STA $0103,y[$01:0105] A:7EBB X:00BB Y:0002 P:eNvMXdizc

On peut donc voir que "$01:9741" est l'adresse de l'octet "BB" en ROM, même si ça importe peu.

Analysons maintenant un peu le code autour de notre instruction :

$04/D1D9 99 03 01 STA $0103,y[$01:0105] A:7EBB X:00BB Y:0002 P:eNvMXdizc
$04/D1DC AD FD 04 LDA $04FD [$01:04FD] A:7EBB X:00BB Y:0002 P:eNvMXdizc
$04/D1DF 99 04 01 STA $0104,y[$01:0106] A:7E20 X:00BB Y:0002 P:envMXdizc
$04/D1E2 C8 INY A:7E20 X:00BB Y:0002 P:envMXdizc
$04/D1E3 C8 INY A:7E20 X:00BB Y:0003 P:envMXdizc
$04/D1E4 C6 01 DEC $01 [$00:1801] A:7E20 X:00BB Y:0004 P:envMXdizc
$04/D1E6 D0 F0 BNE $F0 [$D1D8] A:7E20 X:00BB Y:0004 P:envMXdizc
$04/D1D8 8A TXA A:7E20 X:00BB Y:0004 P:envMXdizc
$04/D1D9 99 03 01 STA $0103,y[$01:0107] A:7EBB X:00BB Y:0004 P:eNvMXdizc
$04/D1DC AD FD 04 LDA $04FD [$01:04FD] A:7EBB X:00BB Y:0004 P:eNvMXdizc
$04/D1DF 99 04 01 STA $0104,y[$01:0108] A:7E20 X:00BB Y:0004 P:envMXdizc

On remarque qu'au début et à la fin, les instructions se ressemblent, et que le jeu écrit les mêmes valeurs.

$04/D1D9 99 03 01 STA $0103,y[$01:0105] A:7EBB X:00BB Y:0002 P:eNvMXdizc

Le jeu écrit "BB" en "0105".

$04/D1D9 99 03 01 STA $0103,y[$01:0107] A:7EBB X:00BB Y:0004 P:eNvMXdizc

Le jeu écrit "BB" en "0107".

On peut voir qu'il boucle sur le "BB" pour l'écrire plusieurs fois.

Maintenant, il faut regarder comment le jeu sait combien de fois écrire "BB".

Il y a 2 instructions "INY". "INY" signifie "incrémente le registre Y de 1" (donc ici de "2" vu qu'il y a deux fois l'instruction).

Ce registre sert ici :

$04/D1D9 99 03 01 STA $0103,y[$01:0105] A:7EBB X:00BB Y:0002 P:eNvMXdizc

Le jeu va écrire en "0103" + la valeur dans Y. ("0103" + "2" ("Y:0002") = "0105")

C'est pour cela qu'on a plus loin la même instruction mais avec une adresse différente :

04/D1D9 99 03 01 STA $0103,y[$01:0107] A:7EBB X:00BB Y:0004 P:eNvMXdizc

Il fait "0103" + "4" ("Y:0004") = "0107"

Cependant, ce n'est pas ça qui va nous donner la largeur du cadre en question.

On continue avec ce morceau de log :

$04/D1E4 C6 01 DEC $01 [$00:1801] A:7E20 X:00BB Y:0004 P:envMXdizc
$04/D1E6 D0 F0 BNE $F0 [$D1D8] A:7E20 X:00BB Y:0004 P:envMXdizc

Ces 2 lignes sont intéressantes.

Le jeu décrémente un compteur et retourne à une instruction précédente tant que le compteur n'est pas à "0", et il écrit à nouveau "BB".

Il y a donc de grandes chances que ce soit la largeur du cadre que l'on recherchait.

Le compteur se trouve en $00:1801 donc on fait une recherche vers le haut de ":1801]"

$04/D1B4 85 01 STA $01 [$00:1801] A:7E0F X:0000 Y:0004 P:envMXdizC

Là, il écrit "0F" en "1801" (et logiquement, c'est la largeur du cadre).

Maintenant, il faut voir d'où sort ce "0F", juste au-dessus :

$04/D1AF B1 13 LDA ($13),y[$01:B06E] A:7E00 X:0000 Y:0004 P:envMXdizC
$04/D1B1 38 SEC A:7E11 X:0000 Y:0004 P:envMXdizC
$04/D1B2 E9 02 SBC #$02 A:7E11 X:0000 Y:0004 P:envMXdizC

Le "11" vient de l'adresse : "$01:B06E"

Vu que la partie 16 bits (à droite des deux-points) est supérieure à 0x8000 ("B06E > 8000"), c'est une adresse en ROM, ce qui donne onc "0xB06E".

Et à cette adresse dans la rom, on trouve bien un "11". Après, il faut tester pour être sûr que c'est ça, mais évidemment, je peux déjà vous confirmer que c'est bien ça :)

Après, il faut savoir qu'il y a un octet pour la partie haute de la ligne horizontale du cadre et un autre octet pour la partie basse.

Dans la rom, en "0x0B06A", on voit un autre "11", on pourrait le vérifier manuellement mais on peut en déduire que c'est l'autre octet et c'est bien le cas (ca se voit assez facilement, dans l'éditeur hexadécimal, on voit "1109 00 0911", donc une symétrie dans les valeurs, tout comme l'est le cadre).

Si vous préférez la version "trace", voici la ligne en question :

$04/D16F B1 13 LDA ($13),y[$01:B06A] A:00FF X:0000 Y:0000 P:envMXdiZc.

Les adresses recherchées sont donc "$01B06A" et "$01B06E".

Maintenant, il y a deux solutions, soit vous modifiez les valeurs à la main avec un éditeur hexadécimal (ce n'est pas la meilleure des méthodes à mon sens car une simple bourde plus tard sur votre rom, et vous devrez refaire toutes les modifications à la main), soit écrire un script ASM compilable avec xkas, et qui permet d'appliquer les modifications automatiquement, par l'intermédaire d'un fichier .bat par exemple.

Je rappelle les données que nous avons trouvé :

Ces trois valeurs suffisent à déplacer le cadre, et à l'agrandir autant que desiré (dans la limite de l'affichage du jeu).

Voici donc le script ASM en question :

//----------------------------------------------------------------------------------------------------------------------
// CaptainTsubsaIV-ActionsTirsSpeciaux.asm - Script for xkas v0.14
// Description : Gestion du cadre des actions d'attaque
// Jeu : Captain Tsubasa IV (SNES)
// Version : 1.0 11/01/2012
// Auteur : Hiei-
//----------------------------------------------------------------------------------------------------------------------
// Execution : xkas -o
//----------------------------------------------------------------------------------------------------------------------

arch snes.cpu; lorom


//----------------------------------------------------------------------------------------------------------------------
print "Gestion de l'ecran des actions de tirs speciaux"


//----------------------------------------------------------------------------------------------------------------------
// Définition des variables

define size_frame_tirs_speciaux 11 // Nombre de colonne du cadre (11 -> ?)
define pos_frame_tirs_speciaux 7E4E // Position en VRAM de la map du cadre des tirs speciaux (7E4E -> ?)


//----------------------------------------------------------------------------------------------------------------------

print "Agrandissement du cadre des tirs speciaux"

org $01B06A
db ${size_frame_tirs_speciaux}

org $01B06E
db ${size_frame_tirs_speciaux}


print "Deplacement de la map en VRAM du cadre des tirs speciaux"

org $05EA91
LDA #${pos_frame_tirs_speciaux}

//----------------------------------------------------------------------------------------------------------------------

Il suffit de le copier-coller dans un fichier texte, de l'enregistrer en script.asm et voilà. Maintenant, je vais expliquer ce qui est à modifier si vous désirez tester sur un autre cadre.

Tout d'abord, il faut définir les variables, ça permet de pouvoir modifier les valeurs ensuite sans toucher au code proprement dit.

define size_frame_tirs_speciaux 11 // Nombre de colonne du cadre (11 -> ?)
define pos_frame_tirs_speciaux 7E4E // Position en VRAM de la map du cadre des tirs speciaux (7E4E -> ?)

Ici, on a définit deux variables, une variable appelé "size_frame_tirs_speciaux" (vous pouvez l'appeller comme vous voulez, tant que vous utilisez le même nom dans le code asm), qui définie la valeur du nombre de colonnes du cadre, ici "11". Si on mettait "20", ça agrandirait évidemment le cadre en conséquence.

Tout ce qui est précédé par des // sont des commentaires qui ne seront pas lus par "xkas", le compilateur. Le "(11 -> ?)" est donc juste là en tant que pense-bête, pour que vous puissez mettre la valeur originale, et votre nouvelle valeur, et pouvoir donc facilement savoir ce que vous avez agrandi ou pas et de combien de colonnes.

La variable suivante, "pos_frame_tirs_speciaux", définie l'emplaçement du cadre. Si je mets "7E50", le cadre va être décalé vers la droite. Si je mets, "7E4C", le cadre va être décalé vers la gauche.

Dans notre cas, il faut donc le décaler le plus à gauche possible de l'écran, puis élargir le nombre de colonnes pour que le cadre prenne tout l'écran (variable "size_frame_tirs_speciaux").

Ensuite, il ne reste plus qu'à entrer/modifier les adresses trouvées précédemment.

Pour l'agrandissement du cadre, il s'agit de ce code :

org $01B06A
db ${size_frame_tirs_speciaux}

org $01B06E
db ${size_frame_tirs_speciaux}

Et pour déplacer le cadre en lui-même, il s'agit de ce code :

org $05EA91
LDA #${pos_frame_tirs_speciaux}

Vous remarquez qu'on retrouve les noms de variables définies précédemmentt.

Evidemment, nous aurions pu ne pas définir de variables et mettre directement :

org $01B06A
db $11

org $01B06E
db $11

Et pour le déplacement du cadre :

org $05EA91
LDA #$7E4E

Attention, notez bien que "db" est pour un octet, si vous mettez une valeur de deux octets, il faut utiliser "dw", pour trois octets c'est "dl", pour 4 octets "dd". Tout ceci est spécifique à xkas, le script presenté ici est compatible avec xkas v14.

Mais c'est plus pratique et plus propre de définir des variables et ensuite ne plus avoir à retoucher au code ensuite.

Pour finir, on télécharge "xkas" et on utilise la ligne de commande suivante :

xkas -o NOM_DE_LA_ROM.SMC NOM_DU_SCRIPT.ASM

Et vos modifications seront réinsérées dans la rom :)

Voilà ce que ça donne après avoir modifié les valeurs citées dans le script ASM au-dessus :

Plus tard, nous verrons sûrement comment "réparer" le curseur de subrillance des choix (c'est un des rares menus qui pose ce problème, si vous décalez le cadre tout à gauche et que vous l'agrandissez, il faut également déplacer ce curseur), et comment décaler directement du texte de la même façon.

Voilà pour ce tutoriel, un grand merci à Bahabulle pour ses explications sans lesquelles je n'aurai pas pu écrire ce tutoriel.


Ecrit par Hiei- (scripts-elysion@hiei-tf.fr) le 11 Janvier 2012 pour la T.R.A.F. (http://traf.romhack.org).
Validé par BahaBulle le 11 Janvier 2012.