[R/S/E] Faire ses propres DLC à l'aide des e-cards

Ici, vous pouvez poster les glitches et autres contributions que vous pensez avoir découvert ou réalisé dans les jeux, avec de préférence une photo le cas échéant. S'il est validé par notre équipe, il sera ajouté au site web !
Répondre
Avatar du membre
Youpileouf
Nouveau glitchologue
Messages : 12
Enregistré le : ven. 22 août 2025 08:13

[R/S/E] Faire ses propres DLC à l'aide des e-cards

Message par Youpileouf » dim. 19 oct. 2025 21:50

Avant de s'attaquer à ce tutoriel, je recommande vivement de lire [R/S/E] Anatomie d'une e-card puis [R/S/E] Customiser ses e-cards, e-card-Hacking. Ces deux tutoriels vous apporteront des connaissances indispensables pour comprendre et être capable d'exécuter ce que nous verrons ici.

1 INTRODUCTION.
Toujours avec moi ?
Les simples petites modifications de dresseurs chez papy combat ou de propriétés d’une baie ne vous impressionnent plus ? Pire… Elles vous ont donné envie de rêver grand ? De rêver de plus ?
Et si…
Et si avec la puissance d’un morceau de carton et d’une diode infrarouge il était possible d’injecter des véritables DLC dans notre bonne vieille cartouche ? Et si un monde où il serait possible de faire une mise à jour de sa cartouche 20 ans après sa sortie était à portée de main ?
Accrochez-vous…
Aujourd’hui, nous montons de quelques degrés de difficulté supplémentaires mais la récompense sera grande : une forme d’ACE jamais vue auparavant, à la limite du romhacking, s’offrira à ceux qui maîtriseront l’art que je m’apprête à vous enseigner.

2 PRINCIPE MIS EN OEUVRE ET QUOI AVOIR SUR PC.
Pour être capable de vous expliquer comment réaliser des e-cards contenant des scripts, je vais devoir rentrer plus en détail dans l'explication de ce qu'il se passe dans votre Game Boy et comment notre ordinateur arrive à générer des informations compréhensibles par notre bonne vieille console et sa cartouche.

Ce qu'il faut bien comprendre pour commencer, c'est que notre console et sa cartouche ne parlent pas notre langage d'humain ni même des langages de programmation "grand public" tels que C++, java ou encore python. Ce que notre console utilise, c'est un jeu d'instructions Thumb et/ou ARM pour processeur ARM. Concrètement il va être impossible de lui envoyer des lignes de codes du type :

Code : Tout sélectionner

for(int pos=0; pos < numOfElements; pos++) {
    int val = t*pos;
    val = Math.min(val, n);
    array[pos] = val;
}
Ce qui peut être compris et interprété par notre bonne vieille console, ce sont des instructions Thumb et ARM compilées qui vont ressembler à ça si on les ouvre avec un éditeur héxadécimal :

Code : Tout sélectionner

01 00 00 00 02 FF 00 FF 00 00 00 04 00 80 03 00
00 10 57 A4 00 00 1E 00 00 02 37 02 00 02 06 1A
30 01 CD 01 00 02 37 02 00 02 05 30 00 00 02 02
B8 30 00 00 02 23 45 C0 01 02 BE 8D 01 00 02 0E
02 00 00 02 1E 20 01 49 01 4A 10 47 54 C0 01 02
D5 25 15 08 FF B4 09 48 09 49 09 78 64 22 51 43
40 18 08 49 19 22 0B DF 00 20 FF BC 70 47 C0 46
EC 44 02 02 44 47 02 02 E0 75 03 02 1C 00 00 00
24 00 00 00 20 00
En clair, si nous voulons communiquer avec notre console, nous allons devoir lui parler un langage qu'elle comprend... Et malheureusement pour nous, il n'est pas franchement compréhensible pour nous!
Et quoi? Tout est fichu?

Heureusement non!
Vous vous souvenez des fameux dossiers à récupérer et à utiliser dans [R/S/E] Customiser ses e-cards, e-card-Hacking? Et bien ces derniers vont en quelque sorte nous servir d’interprète et nous permettre de remplacer des nombres hexadécimaux par des mots pour nous permettre de faciliter la communication avec notre Game Boy Advance.

Retournons déjà sur notre github préféré (https://github.com/Youpileouf/pokecarde_RubisSaphir_FR) pour creuser un peu ce principe.

2.1 Le dossier constants, le grand dictionnaire :
Lorsque nous regardons un peu plus en détails ce que nous y trouvons, nous allons tomber sur un dossier constants. Ici nous avons de véritables petits dictionnaires de traduction. Par exemple le fichier pokemon.asm dans lequel nous établissons qu'à chaque fois que nous écrirons BULBIZARRE dans notre code, notre programme devra écrire 001.

Code : Tout sélectionner

DEF BULBIZARRE EQU $001
DEF HERBIZARRE EQU $002
DEF FLORIZARRE EQU $003
DEF SALAMECHE  EQU $004
DEF REPTINCEL  EQU $005
DEF DRACAUFEU  EQU $006
De même le fichier scriptcommands.asm explique à notre programme que lorsque nous écrirons goto_if $XX $YYYYYYYY, notre programme écrira à la place 06XXYYYYYYYY.

Code : Tout sélectionner

MACRO goto_if
	db $06
	db \1
	dd \2
	ENDM
L'ensemble de ces fichiers est là pour établir une base de communication entre nous et la console et nous permettre de remplacer le maximum de valeurs héxadécimales pour des vrais mots, plus simples à manier pour nous autres mortels.

2.2 Le dossier scripts, le grand calculateur :
Maintenant que nous avons une base de mots en commun avec notre jeu nous allons pouvoir y aller? Et bien non, pas tout à fait...
Il n'est pas toujours possible de mettre en face un mot et une séquence hexadécimale donnée. Par exemple, le jeu raffole de deux principes qui vont nous donner des cheveux blancs :
  • Les checksums (ou somme de contrôle),
  • Le chiffrement.
Pour s'assurer que rien n'a été bidouillé dans le données, le jeu utilise régulièrement des checksums. Il faut voir cela comme des sortes d'additions géantes des nombres qui composent un morceau de données et qui donnent des genre de mots de passe. Le jeu va faire l'addition et vérifier qu'il tombe bien sur la bonne valeur. Si la valeur n'est pas la bonne : il va se bloquer et refuser d'utiliser le code car il n'a pas la bonne signature. De la même manière, pour éviter les bidouillages de pokémons, le jeu va chiffrer les données de vos chers monstres.

Le rapport avec le schmilblick?

Et bien dans le dossier scripts nous avons stockés des... scripts(!) qui vont nous permettre de calculer ces checksums, de prendre les morceaux données déchiffrées et de les chiffrer, en bref de faire ce que notre dictionnaire ne sait pas faire, c'est à dire faire des calculs.

2.3 Le dossier common, le grand communiste :
Dans ce dossier se trouve des jeux d'instructions pour processeur ARM qui seront utilisés dans plusieurs types de cartes.
Je ne m'étendrais pas trop sur le sujet car cela n'aura pas énormément d'importance pour la suite.

2.4 Le dossier bin, le grand compilateur :
C'est ici que sont stockés les programmes support qui vont nous aider à fabriquer nos e-cards.

2.5 Les autres dossiers, les instructions pour fabriquer chaque type de e-cards :
Un dossier par type de e-card. Chacun de ces dossiers va contenir les instructions de fabrication de la carte. C'est dans ces dossiers que nous allons réellement écrire notre code qui sera compris et transformé en jeu d'instructions ARM (grâce aux dictionnaires, calculateurs, communiste et compilateurs détaillés plus haut).

2.6 Quoi avoir sur son PC pour pouvoir bosser :
Pour travailler, il va vous falloir les choses suivantes :
  • Un PC
  • Windows Subsystem for Linux installé et prêt à fonctionner (comme expliqué ici :[R/S/E] Customiser ses e-cards, e-card-Hacking
  • Un projet de désassemblage de e-cards (comme par exemple le mien, pensé pour Pokémon Rubis et Saphir FR ou celui pensé pour Pokémon Emeraude FR)
  • Un émulateurs et des roms de pokémon + e-Reader pour tester vos créations!
3 EASY MODE : RUBIS/SAPHIR :
Pfiouuuuu… Après cette introduction épique et cet effeuillage de dossier github, et si nous arrêtions les blablas et commencions réellement à coder ?
Ce tutoriel aura pour but de vous expliquer la structure d’une e-Card de type Script et comment créer les vôtres. Ce type de e-Card permet de charger dans une partie spéciale de la cartouche appelée RamScript un petit programme pouvant peser jusqu’à 995 bytes (ou beaucoup plus mais ne nous emballons pas trop vite).

3.1 Composition de la e-Card :
Comme les autres e-Cards, cette e-Card se décompense en plusieurs sous-parties que nous allons parcourir rapidement pour certaines, en détails pour d’autres :
  • eCard.asm : correspond à la partie qui sera lue, affichée et exécutée par la Game Boy Advance munie du e-Reader. Ce fichier sera très peu modifié voir pas du tout. Les seuls éléments qui pourraient être amenés à nécessiter des adaptations sont les instructions affichées sur l’écran de la Game Boy Advance avec son e-Reader :

Code : Tout sélectionner

Instructions2:
	db "Appuyez sur le bouton A de la Game\n"
	db "Boy Advance contenant Pokémon\n"
	db "Emeraude pour débuter l'envoi de\n"
	db "l'évenement.\0"
DeliveryInProcess:
	db "Evenement en cours d'envoi...\0"
TicketDelivered:
	db "Evenement envoyé !\n"
	db "\n"
	db "Appuyez sur A pour renvoyer.\n"
	db "Appuyez sur B pour annuler.\0"
  • prologue.asm : correspond comme son nom l’indique au prologue envoyé par l’e-Reader à la cartouche pokémon comme clé de sécurité. Si ce message n’est pas reçu par la cartouche, le transfert de données n’aura pas lieu. En conclusion, on n’y touche pas !
  • script.asm : correspond à la partie qui se sera lue, affichée et exécutée par la Game Boy Advance munie du jeu Pokémon. Ici il y a beaucoup de choses à dire et ce fichier deviendra notre terrain de jeu dans la suite de ce tutoriel !
  • Makefile : contient l’ensemble des instructions pour que notre PC (via la commande make sous WSL, voir [R/S/E] Customiser ses e-cards, e-card-Hacking) puisse construire le fichier .raw de l’e-Card. Là encore : on ne touche pas !
3.2 Structure interne de script.asm
Comme expliqué plus haut, il s’agit ici de tout ce qui va être chargé dans votre jeu. Pour l’étude de ce fichier, je me baserai sur le code du Passe Eon.

3.2.1 1er segment : déclaration des types de données transmises :
La première partie du code qui est envoyé à la cartouche correspond à un paquet de d'identification. Il a pour rôle de permettre à la cartouche de comprendre ce qu'elle va recevoir.

3.2.1.1 Evènement Mystère :
Le début de notre envoi de données est une simple identification, qui est d’ailleurs commune à tous les types de e-cards et qui déclare que ce qui suit est un évènement mystère :

Code : Tout sélectionner

Mystery_Event
db CHECKSUM_CRC
dd 0 ; checksum placeholder
GBAPTR DataStart
GBAPTR DataEnd
On définit le type de données (évènement mystère donc) puis on déclare une checksum et on donne sa valeur. La checksum permettra de vérifier l’intégrité des données. Enfin on donne deux pointeurs qui permettent de définir la taille et la localisation des données qui font parties de cet évènement mystère.
Si script.asm était un humain, il dirait « Je suis un évènement mystère, je possède une checksum, ma checksum est XXXX mon début est à DataStart et ma fin est DataEnd. »
Ces lignes ne seront jamais à modifier.
Si on s’intéresse un instant à la suite du code, on trouve tout de suite après DataStart : qui correspond donc à l’emplacement pointé par GBAPTR DataStart et à la fin du fichier DataEnd : qui correspond à l’emplacement pointé par GBAPTR DataEnd.

3.2.1.2 Type d’évènement mystère :
Maintenant que la cartouche sait qu’elle est en train de traiter un évènement mystère, elle doit savoir de quel type d’évènement mystère il s’agit. Pour les plus studieux d’entre vous, vous vous rappellerez qu’il existe ces types d’évènements :
  • Message
  • Chargement de statut
  • Pré-script
  • Script
  • Baie
  • Ruban
  • Pokédex National
  • Mot rare
  • Objet via échange de données (record mixing)
  • Pokémon offert
  • Dresseur
  • Réglage d’horloge interne
  • Dresseur du Dôme
Une e-Card de script contient au minimum un pré-script et un script. Elle peut également contenir objet via échange de données (record mixing), mais je déconseille d’utiliser cette fonctionnalité pour vos évènements.
Etudions un exemple : le Passe Eon.

Code : Tout sélectionner

db IN_GAME_SCRIPT
db 8,1 ; Arène de Clémentiville
db 1 ; Norman
GBAPTR NormanScriptStart
GBAPTR NormanScriptEnd
db MIX_RECORDS_ITEM
db 1
db 30
dw PASSE_EON
db PRELOAD_SCRIPT
GBAPTR PreloadScriptStart
db END_OF_CHUNKS
Si script.asm était un humain, il dirait « Je contiens un Script, qui se déclenche en parlant à Norman dans l’arène de Clémentiville. Mon script commence à NormanScriptStart et se termine à NormanScriptEnd. Je contiens aussi un objet via record mixing que j’accepte de partager 48 fois (30 en hexadécimal = 48 en décimal). Cet objet est le Passe Eon. Enfin je contiens un pré-script qui commence à PreloadScriptStart. »

3.2.2 Le pré-script :
Maintenant que notre script.asm a décliné son identité, nous allons sauter à la fin du ficher pour analyser la partie qui sera lue en premier par le jeu : le pré-script.
Mais au fait, c’est quoi un pré-script ?
Un pré-script, c’est un petit programme qui va vérifier si le script devrait être envoyé ou non. Dans le cas du Passe Eon, le pré-script doit s’assurer de plusieurs choses avant d’accepter que vous méritez de recevoir le script qu’il protège :
  • Est-ce que le Passe Eon n’est pas déjà dans le sac ?
  • Est-ce que le Passe Eon n’est pas déjà dans le PC ?
  • Est-ce que le Lati@s a déjà été rencontré ?
  • Y-a-t-il de la place dans le sac pour le Passe Eon ?
Pour trouver où se trouve concrètement le pré-script, il suffit de chercher son pointeur…
Mais si! Vous vous souvenez, « GBAPTR PreloadScriptStart » ?! Et bien le pointeur s’appelle PreloadScriptStart.
Si on le cherche, on va trouver en fin de fichier ce segment :

Code : Tout sélectionner

PreloadScriptStart:
	setvirtualaddress PreloadScriptStart
	checkitem PASSE_EON, 1
	compare LASTRESULT, 1
	virtualgotoif 1, .ineligible
	checkpcitem PASSE_EON, 1
	compare LASTRESULT, 1
	virtualgotoif 1, .ineligible
	checkflag $00CE
	virtualgotoif 1, .ineligible
	checkitemroom PASSE_EON, 1
	compare LASTRESULT, 0
	virtualgotoif 1, .no_room
	virtualloadpointer GoSeeYourFather
	setbyte 2
	end
.ineligible
	virtualloadpointer MayBeplayedOnlyOnce
	setbyte 3
	end
.no_room
	virtualloadpointer BagsKeyItemsPocketFull
	setbyte 3
	end
MayBeplayedOnlyOnce:
	Text_FR "Cet EVENEMENT n’est pas répétable.@"
BagsKeyItemsPocketFull:
	Text_FR "La POCHE OBJ.RARES de\n"
	Text_FR "ton SAC est pleine.@"
Je ne vais pas faire dans ce chapitre une explication en détail de toutes les commandes. Ce qu’il faut retenir c’est que ce pré-script possède un seul début et 3 fins que l’on peut identifier grâce à la commande « end ». Avant chacune des fins possibles, le pré-script donne son verdict sur si le script peut être envoyé ou non grâce à la commande « setbyte » :
  • setbyte 2 -> OK je donne mon accord pour l’envoi du script,
  • setbyte 3 -> NOK, les conditions ne sont pas réunies pour que j’autorise l’envoi du script.
Le pré-script le plus petit possible ressemble à quelque chose comme cela :

Code : Tout sélectionner

PreloadScriptStart:
	setvirtualaddress PreloadScriptStart
	virtualloadpointer MessageDeTransfertScript
	setbyte 2
	end
MessageDeTransfertScript:
	Text_FR "Nouveau DLC disponible ! Va parler\n"
	Text_FR "à XXXX route YYYY.@"
En langage d’humain cela donnerait : « Je suis le pré-script, mon adresse de commencement est PreloadScriptStart, j’affiche le texte de chargement contenu dans MessageDeTransfertScript, je donne mon accord pour transférer le script, j’ai terminé. ».

3.2.3 Le script
Le pré-script a terminé son cinéma ? Bien !!! On va pouvoir attaquer les choses sérieuses !
Le script !
Et c’est quoi au fait un script ? Et bien… Un script… Cela peut être… Beaucoup de chose !
Un message qui s’affiche quand on parle à un panneau : script.
Un dresseur qui vous défi en combat : script.
Un combat de pokémon sauvage : script.
Un changement de carte : vous l’avez compris… C’est aussi un script.
Le script est vraiment la cheville ouvrière des jeux pokémons qui fait en sorte que des évènements se déroulent en jeu. Pour en apprendre plus sur le sujet, je vous conseille fortement de vous intéresser aux tutos de romhacking qui expliquent comment fonctionnent ces petites merveilles.
A noter : la méthode de déclenchement du script ne se trouve pas dans le script lui-même mais plus haut dans le fichier .asm, dans la séquence de définition du type d’évènement mystère distribué par la e-card. Dans ce paragraphe, nous allons nous concentrer sur la structure de ces fameux scripts. Un script dans le cadre des e-cards respectera cette structure minimale :

Code : Tout sélectionner

ScriptStart:
	setvirtualaddress ScriptStart
	end
ScriptEnd:
En langage humain : « je suis le début du script, mon adresse de commencement est ScriptStart , j’ai fini, je suis la fin du script. ».
Je vous l’accorde… Notre script fait un peu pitié en l’état et ne fait… Rien du tout ! Je vous sens sur votre faim et je le comprends. Jetons donc un coup d’œil au script du Passe Eon :

Code : Tout sélectionner

NormanScriptStart:
	setvirtualaddress NormanScriptStart
	checkitem PASSE_EON, 1
	compare LASTRESULT, 1
	virtualgotoif 1, .delete_script
	checkpcitem PASSE_EON, 1
	compare LASTRESULT, 1
	virtualgotoif 1, .delete_script
	checkflag $00CE ; FLAG_ENCOUNTERED_LATIAS_OR_LATIOS
	virtualgotoif 1, .delete_script
	lock
	faceplayer
	virtualmsgbox GoodToSeeYou
	waitmsg
	waitkeypress
	checkitemroom PASSE_EON, 1
	compare LASTRESULT, 0
	virtualgotoif 1, NoRoomToGive
	copyvarifnotzero $8000, PASSE_EON
	copyvarifnotzero $8001, 1
	callstd 0
	setflag $08B3 ; FLAG_ENABLE_SHIP_SOUTHERN_ISLAND
	virtualmsgbox AppearsToBeAFerryTicket
	waitmsg
	waitkeypress
	release
.delete_script
	killscript
NoRoomToGive:
	virtualmsgbox KeyItemsPocketIsFull
	waitmsg
	waitkeypress
	release
	end
GoodToSeeYou:
	Text_FR "PAPA: \v1! Content de te voir! Nous\n"
	Text_FR "avons reçu une lettre pour toi, \v1.@"
AppearsToBeAFerryTicket:
	Text_FR "PAPA: Ca ressemble a un passe de ferry\n"
	Text_FR "mais je n’en ai jamais vu de pareil.\p"
	Text_FR "Tu devrais visiter NENUCRIQUE\n"
	Text_FR "et te renseigner là-bas.@"
KeyItemsPocketIsFull:
	Text_FR "PAPA: \v1, la POCHE\n"
	Text_FR "OBJ.RARES de ton sac est pleine.\p"
	Text_FR "Déplace quelques OBJ.RARES dans ton\n"
	Text_FR "ton PC et reviens me voir.@"
NormanScriptEnd:
Voilà un script un peu plus charnu ! Mais que fait-il ?
Et bien plusieurs choses :
  • Il s’initialise
  • Il commence par regarder si le Passe Eon ne se trouve ni dans le sac, ni dans le PC et s’assure que le Lati@s de l’île du sud n’a pas été déjà rencontré. Si l’une de ses trois conditions est validée, le couperet tombe, il s’auto-détruit. C’est une protection supplémentaire au cas où le pré-script n’a pas bien fait son travail.
  • Une fois ce contrôle passé, il verrouille le joueur en place et lance les boîtes de dialogue entre lui et Norman.
  • Si le joueur n’a plus de place dans son sac, Norman demandera au joueur de faire de la place.
  • Sinon, le Passe Eon est inséré dans le sac du joueur, le flag qui débloque le dialogue avec Mr Marco devant le bateau de Nénucrique est activé et Norman propose au joueur d’aller mener l’enquête là-bas.
Et le reste ? Les dialogues avec Mr Marco, le voyage en bateau, l’île du sud, le combat avec Lati@s ? Et bien dans le cas du Passe Eon, ces autres scripts étaient déjà stockés dans la cartouche de base et n’attendaient que ce petit déblocage pour pouvoir se déclencher.

3.3 Dictionnaire de langage script pokémon
Je ne vous ai pas encore perdu encore ?
Arf, vous êtes des coriaces.
Vous avez l’impression d’avoir beaucoup appris et en même temps trop peu ? Certes vous comprenez mieux comment est structuré une e-card contenant un script mais il vous manque les mots pour raconter vos propres histoires ?
Ici je vais essayer de vous rassasiez en vous fournissant l’ensemble des instructions qui peuvent être utilisées dans un script. Ca risque de faire beaucoup ? Ohhhh que oui !
Alors plutôt que d’allonger ce post inutilement, je vais vous glisser quelques liens qui deviendront vos bibles :

VotreDossier/constants/scriptcommands.asm
C’est l’ensemble des commandes comprises par votre ordinateur quand il tentera de fabriquer votre e-card à partir du fichier script.asm que vous lui aurez rédigé.

https://github.com/pret/pokeruby/blob/m ... /event.inc
Ici vous retrouverez l’ensemble des fonctions basiques comprises par pokémon Rubis et Saphir ainsi que de précieux commentaires sur comment elles fonctionnent.
Attention /!\
  • Les fonctions reprises ici sont les fonctions comprises par les jeux mais pas nécessairement incluses dans le package de création de e-card.
  • Pour vérifier si c’est le cas, ouvrez /constants/scriptcommands.asm et vérifiez que la fonction en question s’y trouve bien. Si ce n’est pas le cas, il vous faudra l’ajouter manuellement et sauvegarder. Parfois, la dénomination de la fonction n’est pas exactement la même entre event.inc et scriptcommands.asm. Ce qui fait foi dans ce cas de figure est le byte d’identifiant unique de la fonction.

Code : Tout sélectionner

setbyte: MACRO
	db $0E, \1
	ENDM
Et

Code : Tout sélectionner

@ Sets some status related to Mystery Event.
.macro setmysteryeventstatus value:req
	.byte 0x0e
	.byte \value
	.endm
Sont la même fonction puisqu’elles portent toutes les deux l’identifiant unique 0E.

https://github.com/pret/pokeruby/blob/1 ... ls.inc#L11
Ici se retrouvent un certain nombre de fonctions avancées regroupées dans une grande catégorie : les fonctions spéciales. Il existe deux manières d’appeler une fonction spéciale :
  • special $XXXX
  • specialvar $YYYY, $XXXX
Pour ceux qui suivent, special et specialvar sont en réalité des fonctions classiques que l’on retrouve dans event.inc sous les identifiants $25 et $26. Ces deux fonctions attendent un identifiant $XXXX qui fait référence à un second set de fonctions listé dans specials.inc. Pour le cas de specialvar, $YYYY permet de désigner où doit être stocké le résultat de la fonction.
Pour vulgariser les choses, comme les identifiants des fonctions sont stockées sur un byte unique, il ne peut exister que 0xFF=256 fonctions différentes. Comme le jeu avait besoin de plus de fonctions, deux fonctions « tiroirs » ont été ajoutées pour pouvoir stocker un plus grand nombre d’entre elles.
Les fonctions spéciales sont données dans l’ordre de leur numéro d’identifiant ainsi SrcSpecial_HealPlayerParty à la ligne 11 de specials.inc sera special $0000 ; SetCableClubWarp sera special $0001 et ainsi de suite. Pour aller plus vite, l’identifiant d’une fonction speciale sera [(Ligne de la fonction)-11] convertie en hexadécimale.

4 NORMAL MODE : FONCTIONS CUSTOMS :
Youpi... J'ai fait quatre fois le tour de toutes fonctions ET les fonctions spéciales et je n'arrive pas à trouver les bons outils pour faire ce que voudrais... Tu ne pourrais pas-

Hum? Alors on prend goût à tout ce pouvoir, toute cette liberté de création et soudain on se retrouve un peu à l'étroit dans cet univers qui nous semblait infini juste avant? Et bien rassurez vous : il existe des solutions, mais il va falloir s'enfoncer un peu plus profondément dans le trou de lapin. Ne t'en fait pas Alice, je t'emmène aux pays des merveilles.

4.1 Callnative : la fonction qui murmurait aux oreilles des fonctions
Avez vous déjà entendu parlé de notre lord and savior callnative?
Mais siiii : callnative (à noter que certains américains que je ne nommerai pas l'appellent callasm) :

Code : Tout sélectionner

MACRO callnative;	Calls the native C function stored at func.
	db $23
	dd \1
	ENDM
Cette petite merveille de fonction a deux gigantesques qualités : elle fait parti des fonctions utilisables dans les cadre des scripts et elle est capable d'appeler une fonction qui n'est pas référencée dans l'annuaires des fonctions de script.
En d'autres termes, callnative est une sorte de special sous stéroïde qui est capable de renvoyer à n'importe quelle fonction déjà programmée dans le jeu!

Voilà de quoi ouvrir bien bien grand l'horizon des possibles, vou ne pensez pas?!

C'est vrai. Mais pour cela, il va falloir travailler un peu.

4.1.1 Trouver notre fonction miracle
Premièrement, il va vous falloir trouver la fonction qui fait ce que vous vouliez faire. Je n'ai pas de grande formule magique pour cela... Il va falloir fouiller la décomp (anglaise) de pokémon rubis à la recherche de votre perle rare (je remets le lien ici pour les plus fainéants d'entre vous https://github.com/pret/pokeruby). Ce que je vous conseille, c'est d'avoir une approche en deux angles :
  • Comment pourrait bien s'appeler ma fonction magique?
  • Quelle partie du jeu pourrait utiliser ma fonction magique?
Avec ces deux approches et grâce à l'outil de recherche de github (qui est accessible uniquement pour ceux qui possèdent un compte, me semble-t-il), vous devriez être capable de trouver votre perle rare.

Pour la suite de ce chapitre et pour vous montrer comment utiliser une fonction en dehors des fonctions de script, nous allons faire comme si nous souhaitions utiliser ZeroMonData.

4.1.2 Trouver l'adresse de notre fonction miracle
A force de recherches, vous avez enfin trouvé la fonction de vos rêves qui sera capable de réaliser ce que vous vouliez faire! C'est génial, non?
Et bien c'est un grand pas en avant, mais cela ne règle pas tout : maintenant il va falloir l'appeler, c'est à dire donner son adresse exacte.
Ici nous allons voir comment s'y prendre pour trouver notre fonction dans tout le code de pokémon. Je vais partir du principe que vous codez pour Pokémon Rubis FR car cela représente le cas de figure le plus compliqué car nous ne possédons pas à l'heure actuelle de décompilation correcte du jeu qui nous permettrait d'un coup d'oeil de retrouver notre fonction disparue. Dans ce cas de figure, nous allons commencer par trouver la fonction dans le jeu US puis faire une recherche de correspondance dans notre jeu français. La fonction devrait être très similaire en terme de bytes (avec quelques variations dues aux traductions de textes) mais ne se situera pas au même endroit dans la ROM, justement à cause des textes, qui ne prennent pas la même place dans toutes les langues et décalent le code.

Pour nos recherches, nous allons avoir besoin de plusieurs choses :
  • Une ROM de Pokémon Rubis US
  • Une ROM de Pokémon Rubis FR
  • Un logiciel d'édition hexadécimale (j'utilise HxD Hex Editor)
La première étape va consister à trouver quelle est la version de votre ROM américaine. Pokémon Ruby est sortie en trois versions pour les américains : 1.0, 1.1 et 1.2.
Pour savoir quel est la version de votre ROM, ouvrez le fichier .gba avec votre éditeur hexadécimal et observez les premières lignes de code :
RubyVersionHeader.jpg
RubyVersionHeader.jpg (241.09 Kio) Vu 9443 fois
La valeur se situant en 0xBC vous donnera la version de votre ROM : 00 pour le v1.0, 01 pour la v1.1 et 02 pour la v1.2.

Maintenant que vous connaissez la version de votre ROM, ouvrez le fichier .sym correspondant ici https://github.com/pret/pokeruby/tree/symbols.

Pour le reste de mon exemple je prendrais la version v1.0.
Je vais donc chercher dans pokeruby.sym (il faudra cliquer sur "view raw" pour accéder au fichier après avoir cliqué sur pokeruby.sum) ma fonction ZeroMonData.
Pour cela, rien de fou : un petit CTRL+F fera l'affaire.
AddrZeroMonData.jpg
AddrZeroMonData.jpg (40.12 Kio) Vu 9443 fois
Hop! Le coupable est trouvé!
Nous apprenons ici que ZeroMonData se situe à l'adresse 0x0803a6d8 et à une taille de 0x7E (beau bébé)!
Il ne reste plus qu'à aller là-bas et capturer.
C'est le parfait moment pour expliquer un petit détail sur l'adressage de nos jeux préférés. Pour bien comprendre les choses, il faut savoir que chaque puce et morceau de mémoire de notre cartouche (et de notre Game Boy)possède une adresse et que le système d'adressage commence par pointer vers la puce mémoire avant de donner l'emplacement à l'intérieur de la puce. Un peu comme un système d'adresse avec des rues et des numéros de maison.
Ainsi les deux premiers chiffres de notre adresse correspondent à la rue, ou en l'occurence à la puce dans laquelle se trouve les informations :
  • 02 pour l'EWRAM (Externe au CPU)
  • 03 pour l'IWRAM (Interne au CPU)
  • 04 Entrées/Sorties de la GBA (=les pressions de boutons)
  • 05 pour la RAM de la Colour Palette
  • 06 pour la VRAM (video)
  • 07 pour l'OAM RAM (stockage des sprites)
  • 08 pour la mémoire ROM de la cartouche
  • 0E pour la mémoire RAM de la cartouche
Fort de ce savoir, nous apprenons donc que ZeroMonData est stocké dans la ROM car il commence par 08. Nous allons donc chercher dans la ROM (le fichier .gba) le bon numéro de rue. Ce numéro étant 0x03a6d8.

Muni de notre éditeur de code, nous faisons CTRL+G (atteindre) pour nous éviter un long long long scrolling et nous tapons notre numéro 03a6d8 ; le 08 est inutile ici puisque nous sommes déjà dans la bonne rue. Nous cliquons sur "OK" et nous nous laissons guider.
ZeroMonDataRubyUS.jpg
ZeroMonDataRubyUS.jpg (136.92 Kio) Vu 9443 fois
Coucou toi!

Maintenant que nous avons notre suspect dans le jeu US, allons le retrouver dans le jeu FR.

Nous ouvrons donc notre ROM de Pokémon Rubis FR avec notre éditeur hexadécimal et nous faisons une recherche (CTRL+F) en prenant bien garde d'être dans l'onglet de recherche "Valeurs hexadécimales" et en retrant une dizaine de bytes consécutifs de notre fonction anglaise puis en cherchant dans "toutes les directions". Ici nous avons de la chance, notre grande disparue se laisser trouver facilement car ses 16 premiers bytes sont exactement les mêmes dans les deux versions du jeu et ne donnent qu'un seul résultat! Parfois la chasse sera plus difficile et il faudra faire plusieurs recherches avec des morceaux de la fonction pour essayer de l'identifier.
ZeroMonDataRubyFR.jpg
ZeroMonDataRubyFR.jpg (125.57 Kio) Vu 9443 fois
Comme le montre le résultat de notre recherche, ZeroMonData dans notre cartouche française se trouve au numéro de rue 03A8AC. Son adresse complète est donc 0x0803A8AC.

Oufffff! On touche à la fin de notre périple!

Pour appeler la fonction nous n'avons plus qu'à écrire :

Code : Tout sélectionner

callnative $0803A8AD ; appelle la fonction ZeroMonData 
Euh... $0803A8AD? Pourquoi pas $0803A8AC? Et bien... Je ne vais pas rentrer dans les grands détails mais notre CPU de console peut exécuter des instructions en Thumb et en ARM et la manière de lui indiquer dans laquelle des deux langues nous souhaitons travailler est de lui donner des adresses paires ou impaires :
  • Paire : ARM (des adresses qui se finissent par 0, 2, 4, 6, 8, A, C ou E)
  • Impaire : Thumb (des adresses qui se finissent par 1, 3, 5, 7, 9, B, D ou F)
Et les fonctions que nous appelons dans notre script sont écrites en Thumb, nous devons donc ajouter +1 à notre adresse de base.

4.2 Fabriquer sa propre fonction à partir de rien du tout
Youpi... Tu vas me détester mais je voudrais faire un truc et j'ai retourné tout le jeu, les fonctions de script, les fonctions spéciales et même toutes les autres fonctions du jeu et pas moyen de faire ce que je veux... Tu aurais pas une solution pour moi?

4.2.1 Un problème rampant, l'espace disponible (not forshadowing)
Je pourrais bien avoir quelque chose pour toi, mais je te préviens, un grand pouvoir implique de grandes responsabilités.
Le pouvoir que je m'apprète à t'enseigner va te coûter cher... Très cher. Puisque tu ne trouves pas de moyen de coder ce que tu veux avec les outils de la cartouche, je vais t'apprendre à discuter directement avec le CPU de la Game Boy Advance.

Ohhhhh.... Ce ne sera pas simple, non. Cela va te coûter cher et amputer à ton enveloppe de 995 bytes de taille de script une bonne quantité de place. Mais avec ce pouvoir... Tu vas pouvoir TOUT faire.

Bon, voilà, j'ai épuisé mon potentiel de drama, je vais poursuivre sur un ton plus normal.
Cette introduction étant faite, je veux insister sur un sujet qui va devenir de plus en plus important au fur et à mesure que vous allez complexifier et allonger vos scripts : la place disponible. La place pour un script n'est pas infini et plus nous allons vers le custom, plus le coût en espace de stockage va devenir important. Pour cela il n'y a qu'à regarder la place que vont prendre nos instructions :
  • Une simple instruction pour script consomme 1 byte à appeler (plus le nombre de bytes des potentiels paramètres de fonction)
  • Une instruction speciale consomme 3 bytes à appeler, 1 byte pour special puis 2 autres bytes pour le code de la fonction spéciale (plus le nombre de bytes des potentiels paramètres de fonction)
  • Une fonction appelée via callnative va nous coûter 5 bytes à appeler, 1 byte pour callnative puis 4 bytes pour l'adresse de la fonction
  • Ici, pour arriver à nos fin, il faudra écrire les instructions thumb puis faire un callnative pour aller lire les instructions que nous avons rédigé. En d'autres termes, si nous voulons coder nous même en thumb notre fonction clône de ZeroMonData, cela va nous coûter 0x7E bytes soit 126 bytes juste pour l'écrire puis 5 bytes supplémentaire à chaque fois que nous voudrons l'appeler. Pour un usage unique, cela représente 136 bytes soit 13% de tout l'espace disponible!
Ce petit disclaimer est là pour bien vous faire comprendre que ce qui va suivre a un coût mémoire très important et que sauf cas particulier, il est vraiment préférable de trouver des fonctions déjà stockées dans la ROM plutôt que de coder les siennes en Thumb.


4.2.2 Ecrire sa fonction custom en Thumb (not forshadowing at all)
Vous avez cherché très très fort et rien ne fait exactement ce que vous voulez?
Dans ces conditions, il n'y a plus aucune autre solution que de discuter directement avec le CPU de la Game Boy Advance pour lui faire faire ce que votre cartouche refuse.

Ici, je vais prendre un exemple tout à fait au hasard pour vous détailler comment rédiger votre propre fonction 100% custom.
Imaginons que je sois un peu fou et que je veuille, à tout hasard, stocker des données dans une zone mémoire qui n'est jamais jamais utilisée. Puisque la zone mémoire n'est pas utilisée (le secteur 30 de la save par exemple), aucune fonction existante ne le permet. Et puisque nous en sommes à rêver, et si les données que je voulais stocker provenaient d'un endroit mal protégé et dont l'espace n'est pas limité à 995 bytes comme par exemple le pré-script?

Cet exemple tout à fait fortuit est parfait : il va nous permettre d'étudier comment arriver à nos fins même quand le jeu n'a pas le bon outil pour nous.

Pour réussir ce tour de force, il va nous falloir un peu plus de matos et de connaissances :
  • Etre capable de coder en thumb (désolé, ce n'est pas un tuto pour apprendre à faire cela:/)
  • Etre capable de naviguer dans la décompilation de pokémon Ruby et dans sa liste de symboles pour trouver des fonctions, des espaces mémoires, des variables et tout ce dont vous aurez besoin pour votre code.
  • Si vous ne codez pas pour Pokémon Ruby US, vous devrez être capable de retrouver les adressages FR grâce à la méthode montrée dans le chapitre précédent. Pour les équivalences RAM, save et autres, les recherches d'équivalence se feront plutôt sur émulateur via les menus debugg d'inspection de mémoire
  • arm-none-eabi-as.exe qui sera notre compiler de Thumb
4.2.2.1 Coder et compiler sa fonction :
Bon, ici, ceux qui savent savent et ceux qui ne savent pas vont devoir apprendre!
Pour que tout reste bien propre et organisé dans vos dossiers de dev, je vous conseille de créer un nouveau dossier "Thumb" à l'intérieur de votre dossier de script d'e-card qui contient la makefile, le prologue.asm, la eCard.asm, le script.asm, le sprite.4bpp et le sprite.pal : VotreDossier/e-Card_Custom/thumb.
A l'intérieur de ce nouveau dossier nous allons :
  • Copier/Coller arm-none-eabi-as.exe
  • créer le fichier FonctionsCustom.asm, qui nous servira plus tard à stocker le code thumb de notre fonction sous forme de bytes comme une annexe à nos dictionnaires. Ce n'est pas obligatoire mais cela rendra votre fichier script.asm bien plus propre en évitant de le saturer de longues chaînes hexadécimales.
  • Nous n'allons pas non plus oublier notre but premier, coder en thumb, et allons par conséquent créer un fichier pour notre fonction. Le mien s'appelera WriteASM.txt car je vais coder une fonction qui va me permettre d'écrire du langage assembleur.
A l'intérieur de mon fichier, je code :

Code : Tout sélectionner

    mov     r0, #30	@destination aka Flash memory section 30 (0x1E)
    ldr     r1, PreloadASMStart	@origine
    ldr     r2, TryWriteSector	@Ecrit l'origine dans la destination
    bx      r2

.align
PreloadASMStart:
    .long   0x0200005C @ pour Rubis FR, l'adresse est la même dans RUBY US v1.0
TryWriteSector:
    .long   0x081258C9 @ pour Rubis FR, correspond à 0x08125441 dans RUBY US v1.0
    
    
Sans oublier de mettre des commentaires pour m'y retrouver et comprendre ce que j'ai essayé de faire si je rouvre mon fichier plus tard.

Le code étant rédigé, il va me rester à le compiler, pour cela, je lance Windows Powershell et je tape :

Code : Tout sélectionner

& "VotreDossier\e-Card_Custom\thumb\arm-none-eabi-as.exe" "VotreDossier\e-Card_Custom\thumb\WriteASM.txt" -o "VotreDossier\e-Card_Custom\thumb\WriteASM.arm" -mcpu=arm7tdmi -mthumb
Et hop! Voilà mon code de compilé dans un fichier tout beau qui s'appelle WriteASM.arm.

4.2.2.2 Inclure sa fonction dans l'architecture de notre e-card :
Maintenant que nous avons notre nouvelle fonction toute shiny, il est plus que temps de l'inclure à notre code. Pour cela, nous allons commencer par ouvrir WriteASM.arm avec notre éditeur hexadécimal pour en extraire le jeu d'instructions Thumb qu'il renferme et qui a été encapsulé autour de données dont nous n'aurons pas besoin.

Le code commence en 0x34 et se termine juste avant ce set de caractères :

Code : Tout sélectionner

A . . . . aeabi . . . . . . . ARM7TDMI
En l'occurrence voici notre fonction dans sa forme la plus dénudée : une simple séquence de bytes.
WriteASM.jpg
WriteASM.jpg (243.69 Kio) Vu 9435 fois
Nous allons maintenant ouvrir FonctionsCustom.asm et créer une macro qui va nous éviter de polluer le fichier script.asm de longues chaines de valeurs hexadécimales. Nous pourrons à la place juste appeler notre macro qui va servir d'annexe aux dictionnaires déjà existants.

Code : Tout sélectionner

MACRO WriteASM ; Rubis Français (16 bytes)
	db $1E
	db $20
	db $01
	db $49
	db $01
	db $4A
	db $10
	db $47
	db $5C
	db $00
	db $00
	db $02
	db $C9
	db $58
	db $12
	db $08
	ENDM
On sauvegarde, on ferme le fichier FonctionsCustom.asm. et on retourne dans le dossier VotreDossier\e-Card_Custom\ pour ouvrir script.asm.
Au début du fichier, on ajoute notre annexe de dictionnaire :

Code : Tout sélectionner

INCLUDE "../e-Card_Custom/thumb/FonctionsCustom.asm"
Il ne va plus qu'à vous rester d'inclure la séquence de bytes de votre fonction dans votre code script pour qu'elle soit "sauvegardée" au sein de ce dernier. Puis quand vous voudrez l'appeler, il vous suffira de faire un callnative suivi de l'adresse (impaire) de localisation de votre fonction! Par exemple :

Code : Tout sélectionner

PreloadScriptStart:
	setvirtualaddress PreloadScriptStart

	callnative $02000045 ; Demande d'exécuter la fonction WriteASM
    
	virtualloadpointer MessageDeTransfertScript
    
	setbyte 2
    
	dw $0000
    
	end
    
	WriteASM ; lieu de stockage de la fonction, après un end pour l'empêcher d'être lue accidentellement par le jeu
5 HARD MODE : MORE POAWARRR :
Hey Youpi, tu es sûr que tu ne nous caches rien?
Ton dernier chapitre avait des airs de... Tremplin pour autre chose?...
D'ailleurs je commence à me sentir à l'étroit avec mes 995 bytes d'espace pour stocker mes scripts. Tu n'aurais pas quelque chose pour moi?

La gourmandise est un vilain défaut~ Tu ne te satisfaits pas de tout ce qui est déjà possible? Et bien tu sais quoi... J'ai peut-être une solution pour toi. Nous allons considérer cela comme une sorte de récompense pour avoir tenue jusqu'ici dans ce tuto à la longueur et à la densité particulièrement importante.

Dans ce chapitre, nous allons voir comment augmenter considérablement la taille de nos script et gagner 4096 bytes supplémentaires d'espace de stockage en nous transformant en hackerman. Vous êtes prêt? En avant!

5.1 identifications de failles et mise en place d'exploit.
Toute notre histoire va commencer par une recherche de vulnérabilité dans la structure des e-cards. Si nous voulons être capable d'envoyer de plus grandes quantités de données vers notre jeu, il va falloir commencer par trouver, un moyen de les stocker dans notre e-card.

Regardons cela de plus prêt en commencant par le commencement.
Les e-cards possèdent deux formats de dotecodes (les genres de QR à scanner), un court et un long qui sont capables de respectivement stocker 1400 et 2200 bytes et le logiciel contenu dans le e-Reader est capable d'assembler jusqu'à 8 de ces dotecodes en un seul programme. En d'autre terme, la limite théorique maximum pour une communication de l'e-Reader vers notre jeu est de 17600 bytes. La question étant... Il est certes possible d'inclure ces données dans la e-card mais comment la formatée pour qu'elle soit reconnue par notre jeu?

Pour que cette donnée soit lue par notre jeu, il va falloir la déclarer comme un type (ou plusieurs) d'évènement(s) mystère(s). Creusons un peu nos options :
  • Script : ici la taille maximale acceptée est de 1004 bytes (tatata tu avais pas dit 995? oui, mais là je compte les checksums qui font gagner un peu d'espace.
  • Baie : ici la taille maximale acceptée est de 530 bytes
  • Ruban : ici la taille maximale acceptée est de 12 bytes
  • Objet via échange de données (record mixing) : ici la taille maximale acceptée est de 12 bytes
  • Dresseur : ici la taille maximale acceptée est de 188 bytes
Rien de tout cela n'est extrêment intéressant pour nous puisque nous nous rendons compte que notre plus grosse allocation d'espace est déjà pour le script. Qui plus est, tout stockage de données détourné dans un emplacement mémoire qui n'est pas prévu à cet effet risque de faire plnater le jeu. Imaginons un instant que nous tentions de stocker du script sur l'emplacement de la baie énigma. Le jeu n'a aucun moyen de savoir que notre baie énigma ne doit pas être interprêtée comme une baie énigma. Il va donc essayer de le faire, lorsqu'on ouvre la poche à baies du sac, par exemple, et planter.

Et les autres types de données d'e-card? Oui, c'est vrai, je n'ai pas abordé tous les types de données d'ecard, simplement parce qu'un certain nombre de ces types ne "sauvegarde" rien en jeu. Par exemple, le pokémon offert n'a pas d'emplacement mémoire dédié, au lieu de cela, un petit programme vérifie qu'il y a de la place dans l'équipe du joueur et y insère directement le nouveau pokémon.

Il n'y a donc aucune solution?

Réfléchissons un peu à notre candidat parfait et dressons en un portrait robot. Il nous faudrait idéalement quelque chose qui n'ait pas de limites de taille, qui soit relativement permissif... Et qui nous permette de stocker des données dans un lieu qui ne risquerait pas d'être lu par hasard par notre jeu au cours d'une partie normale...

Un instant... Et si... Et si nous abusions du pré-script?

Le pré-script coche finalement pas mal de cases pour nous!
Il est compris et interprêté par notre jeu, il ne possède pas de limite de taille et surtout, il n'a pas vocation à venir être stocké quelque part dans la sauvegarde de notre jeu! Mieux encore! Il est capable d'exécuter des commandes de script pour décider de si le script devrait ou non être transmis.

Et si...

Et si nous abusions de son fonctionnement? Et si, au lieu de lui faire dire :
« Je suis le pré-script, mon adresse de commencement est PreloadScriptStart, j’affiche le texte de chargement contenu dans MessageDeTransfertScript, je donne mon accord pour transférer le script, j’ai terminé. »
Nous lui faisions dire :
« Je suis le pré-script, mon adresse de commencement est PreloadScriptStart, oh, btw stocke cette quantité absurde de données dans un coin qui ne va déranger personne, j’affiche le texte de chargement contenu dans MessageDeTransfertScript, je donne mon accord pour transférer le script, j’ai terminé. »
Nous pourrions certainement augmenter sensiblement la taille notre script!

Et bien essayons justement de faire cela!

5.2 Où stocker la data?
Nous avons un début de plan!
La grande question maitenant est : où allons nous bien pouvoir stocker les données?

Pour que notre solution soit absolument propre, il faudrait trouver des endroits libres ET persistants. Parce que si à chaque fois que nous éteignons notre console nous console nous perdons notre script, cela n'a pas franchement d'intérêt.
Libre et persistant donc... La mémoire ROM n'est pas une option, car elle n'est accessible qu'en lecture, pas en écriture... La plupart de la RAM est quant à elle reset à chaque reboot... Toute la RAM? Et bien non! Un petit village d'irréductible RAM lutte encore et toujours contre l'envahisseur à grand renfort de potion magique!

La sauvegarde mesdames et messieurs!

La sauvegarde du jeu est notre candidate parfaite : elle est modifiable et résistante aux resets (sauf quand on ne sauvegarde pas, mais là, c'est le but, justement de ne pas résister au reset).

La sauvegarde donc...
Le seul problème, c'est que la plupart de son contenu déjà utilisé pour stocker des choses.

Intéressons nous un peu à la structure de la sauvegarde. Cette dernière est structurée en différents secteurs numérotés de 0 à 31 et faisant chacun 4096 bytes (3968 bytes de données puis 128 bytes de footer data):
  • Secteurs 0 à 13 pour la sauvegarde A
  • Secteurs 14 à 27 pour la sauvegarde B
  • Secteurs 28 et 29 pour les données du Panthéon
  • Secteur 30 : vide
  • Secteur 31 : vide
A noter que le secteur 29 n'est pas entièrement utilisé pour les données du Panthéon et nous laisse gentiment 1936 bytes d'espace.
Voilà qui est magnifique! Nous avons plein plein plein d'espace?
Et bien oui! puisqu'en théorie nous pourrions stocker 995 bytes dans le script, 1936 bytes dans les données du Panthéon et deux fois 3968 bytes dans les secteurs 30 et 31! Le e-Reader peut donc envoyer au maximum 17600 bytes et le jeu peut en stocker au maximum 10 867 bytes!

Je vais simplement ici nous brider un peu afin de mettre en place une base de solution qui fonctionnera aussi pour pokémon Emeraude, aussi nous utiliserons "uniquement" le secteur 30, le secteur 31 ayant un usage dans pokémon Emeraude (Battle records).

5.3 Comment stocker la data et la lire?
Tout ca commence à sentir sacrément bon, non? Nous avons notre astuce pour injecter des données en abusant du pré-script, nous avons un endroit où stocker ces données... Il ne reste plus qu'à trouver les astuces pour réaliser des lectures et écritures de tout ce beau monde.

En termes d'outils, nous avons même tout ce qu'il nous faut avec nos fonctions customs créées à partir de rien du tout! Et comme je suis un petit coquin, j'ai même détaillé le processus pour écrire des données dans le secteur 30 avec mon chapitre précédent.

On regarde notre code custom plus en détail maintenant que nous avons compris ce que nous essayons de faire?

Code : Tout sélectionner

    mov     r0, #30	@destination aka Flash memory section 30 (0x1E)
    ldr     r1, PreloadASMStart		@origine
    ldr     r2, TryWriteSector		@Ecrit l'origine dans la destination
    bx      r2

.align
PreloadASMStart:
    .long   0x0200005C 			@ pour Rubis FR, l'adresse est la même dans RUBY US v1.0
TryWriteSector:
    .long   0x081258C9 			@ pour Rubis FR, correspond à 0x08125441 dans RUBY US v1.0
    
    
Ce code va s'appuyer sur une fonction existante dans le jeu, la fonction en oeuvre dans le processus de sauvegarde de la cartouche qui s'appelle TryWriteSector. Ici nous allons juste jouer avec en lui demandant de s'exécuter pour écrire un secteur qu'elle n'écrit pas d'ordinaire.

Nous définissons donc le secteur à écrire, le 30 puis l'origine des données à sauvegarder, ici par facilité nous allons donner l'adresse du tout début du pré-script et enfin l'instruction nous lançons l'instruction de copie.

Il ne reste plus qu'à faire pareil avec la lecture?

En piste!

Code : Tout sélectionner

    movs r0, #30			@origine aka Flash memory section 30 (0x1E)
    ldr r1, WRAMLocation 				@endroit où charger la data à lire
    ldr r3, DoReadFlashWholeSection 	@charge l'origine dans la WRAM pour être lue depuis cette dernière
    bx r3
    
.align
WRAMLocation:
    .long 0x0201B201				@pour Rubis FR, l'adresse est la même dans RUBY US v1.0
DoReadFlashWholeSection:
    .long 0x08126081  				@ pour Rubis FR, correspond à 0x08125BF9 ans RUBY US V1.0
    
    
Et zoup! nous avons nos outils pour stocker puis lire nos données... Il ne nous reste plus qu'à assembler tout cela?

5.4 Tout mettre en musique
Nous avons créé nos outils...
Et bien... Il n'y a plus qu'à?

Commençons par remettre à jour notre annexe au dictionnaire qui contient nos fonctions customs :

Code : Tout sélectionner

MACRO WriteASM ; Rubis Français (16 bytes)
	db $1E
	db $20
	db $01
	db $49
	db $01
	db $4A
	db $10
	db $47
	db $5C
	db $00
	db $00
	db $02
	db $C9
	db $58
	db $12
	db $08
	ENDM
MACRO ReadASM ; Rubis Français
	db $1E
	db $20
	db $01
	db $49
	db $01
	db $4B
	db $18
	db $47
	db $01
	db $B2
	db $01
	db $02
	db $81
	db $60
	db $12
	db $08
	ENDM
Puis reprenons la structure de notre fichier script.asm :
[note de l'auteur : à contrôler, je n'ai jamais utilisé ces astuces sur Rubis/Saphir]

Code : Tout sélectionner

INCLUDE "../macros.asm"
INCLUDE "../constants/items.asm"
INCLUDE "../constants/scriptcommands.asm"
INCLUDE "../MonScript/thumb/FonctionsCustom.asm"

	Mystery_Event

	db CHECKSUM_CRC
	dd 0 ; checksum placeholder
	GBAPTR DataStart
	GBAPTR DataEnd

DataStart:
	db IN_GAME_SCRIPT
	db $00,$12 ; 					Route 103 = 1200
	db 1   ; 					TopDresseur à côté de la grotte
	GBAPTR ScriptStart
	GBAPTR ScriptEnd

	db PRELOAD_SCRIPT
	GBAPTR PreloadScriptStart

	db END_OF_CHUNKS

PreloadScriptStart:
	setvirtualaddress PreloadScriptStart
	callnative $02000045; 				Exécute WriteASM
	virtualloadpointer MessageDeTransfertScript
	setbyte 2
	dw $0000
	end
	WriteASM;					Code thumb dans /thumb/FonctionsCustom.asm
	;						WriteASM lit les 4096 bytes de code qui se
	;						trouvent sous lui et les copie dans le secteur
	;						30 de la save (complètement inutilisé dans les
	;						cartouches d'Emeraude non japonnaise)

VraiScriptStartStockeDansSecteurTrente:
	setvirtualaddress VraiScriptStartStockeDansSecteurTrente
	
	;Le vrai script est à rédiger ici
	;Grâce à l'astuce du WriteASM puis du ReadASM,
	;le code du script ne se situe plus dans les
	;zones mémoires du Ramscript.
	;Comme le code n'est plus stocké dans le 
	;RamScript, il n'est plus soumis à la limite
	;de taille de ce dernier. La limite passe de
	;995 bytes à 4096 bytes.

	end

MessageDeTransfertScript:
	Text_FR "Nouveau DLC disponible ! Va parler\n"
	Text_FR "à XXXX route YYYY.@"

ScriptStart:
	callnative $02XXXXXX ;				Exécute ReadASM
	goto $0x0201B201 ; 				pointe vers le code qui vient d'être lu 	
	ReadASM ;						Stocke ReadASM



ScriptEnd:

DataEnd:
	EOF
Prenons un peu de temps pour comprendre ce que nous avons fait?
En avant!
Premièrement, nous pouvons voir que notre pré-script est revenu au début du code et non à la fin. Pourquoi? Et bien parce que nous allons "cacher" tout notre code à l'intérieur de ce dernier et nous voulons donc que notre "vrai" code se trouve après lui et non au avant pour se faire copier.

Tout de suite après avoir défini le début de notre préscript, nous appelons la fonction stockée en 0x02000045, puis nous affichons le message de confirmation de transfert, nous envoyons le code "ok pour transmission du script" et nous terminons notre pré-script.

Mais est-ce qu'on le termine vraiment? Et bien pas tout à fait et c'est là que toute notre petite magie rentre en jeu. La fonction stockée en 0x02000045, quelle est-elle? C'est là que toute l'ingéniosité se trouve. Grâce aux décompilations du jeu, nous savons très exactement où va se positionner notre pré-script dans la mémoire RAM, puisque nous savons cela, en prenant en compte la place que va prendre nos premières instructions de pré-script en mémoire, nous somme capable de prédire l'endroit où va être stocké la séquence de bytes de notre fonction WriteASM : en 0x02000045. C'est ainsi que nous allons être capable de l'appeler.

Avant de clotûrer notre pré-script, nous avons donc glissé la commande "exécute WriteASM". Et que fait-elle dans le contexte de notre script? et bien elle va copier les 4096 bytes commençant à l'adresse d'origine (0x0200005C) dans le secteur30 de la mémoire. Vous le voyez venir? Où se situe 0x0200005C? Et bien c'est tout juste après la séquence de bytes de notre fonction WriteASM, tout pile au VraiScriptStartStockeDansSecteurTrente: !!!

Nous avons gagné! Grâce à cette astuce, nous venons de copier une quantité monstrueuse de données depuis une zone tampon éphémère de la mémoire directement dans la sauvegarde du jeu!

Cela étant fait et comme notre pré-script à terminé de travailler, il donne son accord pour transmettre le script et cloturer la séquence.

D'ailleurs, notre script, parlons en.
Il est tout petit, parce qu'il n'a pas besoin d'être plus grand.
Sur le même principe il va demander d'exécuter la fonction en 0x02XXXXXX (à tester et donner la vraie adresse, je me suis concentré sur pokémon Emeraude et n'ai pas encore fait les correspondances pour Rubis/Saphir), puis continuer d'exécuter les commandes en sautant à l'adresse 0x0201B201.
Nous nous sommes arrangés ici (sur le même principe que WriteASM) pour que notre séquence de byte de la fonction ReadASM tombe comme par magie en 0x02XXXXXX et la fonction WriteASM va chercher les données du secteur 30 de la sauvegarde pour venir les placer dans une zone de la RAM libre : 0x0201B201. En d'autre termes, nous venons rechercher les données rendues persistantes grâce à leur stockage dans la zone de la sauvegarde du jeu, pour les remettre dans une zone mémoire RAM depuis laquelle elles pourront être éxécutés comme du langage script!

A chaque fois que nous allons éteindre notre console ou soft-reseter notre jeu, la RAM va s'effacer, mais ce n'est pas grâve car notre script possède une copie cachée bien au chaud dans la savegarde même du jeu!

6 EXTREME MODE : POKEMON EMERAUDE :
Oui mais moi j'aimerai faire des trucs sur Emeraude, la version finale d'Hoenn.

Oh... Ainsi tu veux explorer pokémon Emeraude? Et bien accroche toi, c'est ici que je vais te montrer comment faire sauter les verrous de ce jeu. Avant de commencer mes explications, je veux insister sur un point : le contenu des e-card n'a pas été prévu pour fonctionner sur pokémon Emeraude. Si nous sommes capable de faire des choses sur ce jeu, c'est uniquement parce qu'il est basé sur les cartouches de pokémon Rubis/Saphir, pensées pour être compatibles e-Reader bien que pas utilisé en Europe. Heureusement pour nous, pokémon Emeraude a gardé la majorité de ses fonctionnalités qui ont été simplement retiré des menus normalement accessibles par le jeu.

6.1 Les obstacles à surmonter
Le premier obstacle à surmonter pour être capable de profiter des e-cards sur pokémon Emeraude, c'est d'être capable d'activer la fonction de communication avec le e-Reader. Cette fonction était normalement accessible via le menu principal du jeu en sélectionnant EVENEMENTS MYSTERE, juste après l'écran titre, dans pokémon Rubis/Saphir. Ce protocole de communication a été remplacé dans pokémon Emeraude par CADEAU MYSTERE, un protocole différent qui tire cette fois parti de la communication sans-fil via l'adaptateur sans-fil. Notre soucis : CADEAU MYSTERE n'inclus pas les protocoles de communication avec un e-Reader et nous empêche par conséquence d'injecter notre code via une e-card.

Le deuxième obstacle à surmonter a un nom qui a terrorisé pendant des générations les amoureux d'action replay et d'ACE : l'ASLR. Oui mais Fred, c'est quoi l'ASLR? Et bien c'est très simple Jamy : l'ASLR c'est l'Adress Space Layout Randomization, une technologie mise en place à partir de pokémon Rouge Feu et Vert Feuille qui fait en sorte de déplacer de manière aléatoire les données sensibles du jeu stockées dans la RAM afin de rendre la lecture et l'écriture par des programmes tiers de ces dernières beaucoup plus compliquées. Vous vous souvenez des fameux codes d'anti-DMA (anti Dynamic Memory Allocation) que vous étiez obligés de rentrer avant de pouvoir vraiment commencer à jouer avec vos autres codes de triche? Et bien ils servaient justement à contourner cette mesure de sécurité. Notre grand malheur avec pokémon Emeraude est que comme les scripts de e-Card n'ont jamais été prévus pour fonctionner dans cette version du jeu, l'endroit de stockage de ces derniers se trouve en plein dans les espaces d'ASLR. Qu'est-ce que cela signifie concrètement? Et bien cela signifie que tout le code stocké dans le RAMscript peut être déplacé de manière aléatoire, rendant les scripts extrêment instable et faisant sauter le code d'une segment à un autre de manière aléatoire et peu prévisible. Le script change d'endroit dans la mémoire en pleine exécution provoquant crash et comportements non souhaités.

Enfin le troisième obstacle, plus anecdotique, est que le jeu a été refondu et que par conséquence, la quasi intégralité de nos adressages ont changé : toutes les fonctions que vous aviez identifiés dans la ROM, tous les lieux de stockage provisoire dans la RAM, toutes les adresses de Flags sont différents de ceux de pokémon Rubis/Saphir et il vous faudra reconstituer votre petite bibliothèque d'adresses. Fort heureusement, il existe une décompilation de pokémon Emerald (USA) et grâce à cette dernière et sa branche symbols nous allons pouvoir retrouver nos petits. C'est désagréable, mais nous ne repartons pas totalement de 0.
Modifié en dernier par Youpileouf le jeu. 13 nov. 2025 09:45, modifié 4 fois.

Avatar du membre
Youpileouf
Nouveau glitchologue
Messages : 12
Enregistré le : ven. 22 août 2025 08:13

Re: [R/S/E] Faire ses propres DLC à l'aide des e-cards

Message par Youpileouf » dim. 9 nov. 2025 12:33

6.2 Restaurer EVENEMENTS MYSTERE
La première chose à mettre en place sera la restitution du protocole de communication entre pokémon Emeraude et le e-Reader. Je vais essayer d'expliquer succintement ici comment je suis arrivé à ce résultat mais si votre but est de pouvoir simplement injecter votre script, il existe une solution clé en main via exploit disponible ici avec un tuto de mise place ici. Vous pouvez simplement suivre les instructions et passer au chapitre suivant.

Pour ceux qui sont restés, nous allons pouvoir investiguer un petit peu sur le fameux protocole de lancement de la communication avec le e-Reader. Pour cela, armons-nous d'un émulateur et d'une ROM de pokémon RUBY US ansi que de la décomp de pokémon RUBY US. En lançant le menu EVENEMENTS MYSTERE, nous nous rendons compte assez rapidement qu'il déclenche la fonction 0x08146930 g 0000007c CB2_InitMysteryEventMenu (RUBY USA), fonction qui existe également dans le code de pokémon Emerald USA en 0x08178974 g 000000cc CB2_InitMysteryEventMenu (Emerald USA). Munie de notre fonction de pokémon Emerald USA, nous allons pouvoir chercher où elle se trouve dans pokémon Emeraude FR via la même technique que montrée dans le paragraphe 4.1.2 et enfin trouver la coquine en 0x0817858C (Emeraude FR).

Un petit test via l'éditeur de RAM de l'émulateur en mode debbug et nous arrivons la plus part du temps à lancer la communication avec le e-Reader tout en subissant des corruptions de la carte. On se rapproche... Mais quelque chose ne va pas encore tout à fait. Pas mal de tirage de cheveux plus tard, une solution simple nous apparait : il suffit de sauvegarder la partie avant de lancer l'exécution de CB2_InitMysteryEventMenu pour éviter les corruptions de carte.

Nous sommes donc reparti : on trouve dans la liste des symboles de pokémon Emerald US la fonction responsable de la sauvegarde : 0809ff80 g 00000018 SaveGame puis on cherche sa jumelle française : 0x0809ff94. Il nous faudra donc un moyen de lancer successivement la fonction SaveGame puis la fonction CB2_InitMysteryEventMenu.

En creusant le sujet, nous tombons sur la fonction CADEAU MYSTERE qui nous permet d'injecter un minuscule script en jeu, à peine assez grand pour ce que nous voulons faire et très limité car ne pouvant être déclenché qu'en parlant au postier au 1er étage du centre pokémon.

Après beaucoup d'itérations, nous finissons par réussir à modder une distribution du ticket aurore pour remplacer la distribution de l'objet par un CADEAU MYSTERE custom qui exécute un tout petit script de sauvegarde + lancement de CB2_InitMysteryEventMenu enrobé d'un peu de dialogue pour donner un sentiment d'interaction legit en parlant au postier.

Et voilà.
Nous venons d'abattre le premier mur qui nous séparait de nos scripts customs. Un inconvénient majeur demeurre néanmoins : l'écriture dans l'espace RAMscript d'un script d'e-Card écrase notre set-up... Il faudra donc refaire notre set-up entre chaque utilisation de e-card de type script. Heureusement pour nous, les autres e-Cards qui utilisent des emplacements mémoires différents ne suppriment pas notre set-up et permettent de faire plusieurs scans d'affilé.

6.3 ASLR ou pas ASLR?
Vous vous souvenez du méchant ASLR qui va déplacer dans la mémoire votre script et ruiner l'exécution de ce dernier? Et bien je dois vous dire quelque chose : l'ASLR ne s'active pas toujours et la plupart des scripts que vous ferez et qui ne demandent pas d'ouverture de menu, qui se contentent d'être des dialogues et quelques manipulations de flags et d'objets devraient fonctionner sans soucis (à condition d'avoir remis à jour vos adresses de Flags et de fonctions). C'est ainsi que j'ai été capable de redéployer l'event du ticket Eon et même la réactivation de la grotte Métamo.

Mon conseil? Si votre script n'est pas trop gros, je vous conseille de le tester sans plus de complication pour regarder s'il marche. Vous verez très vite si l'une des instructions provoque un cycle d'ASLR : votre script va soit s'interrompre brutalement, soit repartir de 0 soit sauter sans raison vers un autre endroit de ce dernier et dans les cas les plus avancés, commencer à afficher des choses glitchy à l'écran. Si rien de tout cela n'arrive après deux ou trois essais : vous êtes bon! Sinon... Et bien j'ai une mauvaise nouvelle pour vous... Il va falloir continuer dans ce tuto pour trouver vos solutions.

6.4 Contourner l'ASLR
Votre script a fait des choses bizarres? Il y a de fortes chances qu'une des instructions de votre séquence provoque un cycle d'ASLR et fasse planter vos magnifiques plans, mais pas de panique, Youpi à tout ce qu'il vous faut.

Comme nous l'avons vu avec pokémon Ruby et Saphir, il est possible d'exploiter les pré-script pour sauvegarder nos scripts en dehors de la zone prévue à cet effet et c'est exactement ce que nous allons faire ici. Nous allons faire en sorte d'enregister l'écrasante majorité de votre script dans le secteur 30 de votre sauvegarde puis de le charger dans un emplacement RAM qui n'est pas soumis à l'ASLR afin de pouvoir l'éxécuter sereinement.

6.5 Tout adapter à Emeraude
Comme je l'ai dit plus haut, pokémon Emeraude est un tout autre jeu que pokémon Rubis et Saphir et bien qu'il s'appuit sur la même base, l'ensemble de ses adresses sont modifiés. Aussi, pour avoir une chance de faire fonctionner notre e-card, il va falloir remettre à jour tout notre dossier de desassemblage. Heureusement pour vous, ce travail, je l'ai déjà fait et vous n'aurez donc qu'à partir de ma base accessible ici.

6.5.1 WriteASM et ReadASM
Comme je l'ai expliqué, tout est similaire mais un peu différent de pokémon Rubis et Saphir et nous allons devoir retrouver nos fonctions TryWriteSector et DoReadFlashWholeSection (qui entre temps s'est vue renommée ReadFlashSector) ainsi que les différentes adresses dans la RAM que nous avions utilisé (WRAMLocation et PreloadASMStart).

Rien d'insurmontable heureusement, après quelques retouches, nos fonctions WriteASM et ReadASM ressemblent désormais à cela :

Code : Tout sélectionner

    mov     r0, #30		@Destination aka Flash memory section 30 (0x1E)
    ldr     r1, PreloadASMStart @Origine
    ldr     r2, TryWriteSector	@Ecrit l'origine à la destination
    bx      r2

.align
PreloadASMStart:
    .long   0x0201C054 @Adresse Emeraude Fr
TryWriteSector:
    .long   0x081525D5 @Adresse Emeraude Fr
Et cela :

Code : Tout sélectionner

    mov r0, #30		@Origine aka Flash memory section 30 (0x1E)
    ldr r1, ASMLocation @Destination
    ldr r3, ReadFlashSector	@Lit l'origine dans la RAM de destination
    bx r3
.align
ASMLocation:
    .long 0x0203ABBC @Adresse Emeraude Fr (nous avons pris soin de choisir une zone de la RAM qui n'est pas impactée par l'ASLR)
ReadFlashSector:
    .long 0x08152E19 @Adresse Emeraude Fr
Une fois compilées grâce à arm-none-eabi.exe nous obtenons nos deux fonctions sous forme de séquence de bytes que nous pouvons réinsérer dans notre fichier FonctionsCustom.asm.

Code : Tout sélectionner

MACRO WriteASM ; Emeraude Français (16 bytes)
	db $1E
	db $20
	db $01
	db $49
	db $01
	db $4A
	db $10
	db $47
	db $54
	db $C0
	db $01
	db $02
	db $D5
	db $25
	db $15
	db $08
	ENDM

MACRO ReadASM ; Emeraude Français (16 bytes)
	db $1E
	db $20
	db $01
	db $49
	db $01
	db $4B
	db $18
	db $47
	db $BC
	db $AB
	db $03
	db $02
	db $19
	db $2E
	db $15
	db $08
	ENDM
6.5.2 ASLR et lancer ReadASM
L'une des dernière choses qu'il va nous rester à solutionner est la suivante : comment exécuter un callnative $XXXXXXXX pour appeler ReadASM? La question peut paraitre idiote mais pour pouvoir se jailbreaker de l'espace du RAMscript soumis à l'ASLR, nous sommes obligé d'exécuter du code à l'intérieur de ce lieu pour sauter ailleurs en mémoire.

Si je reprends mon analogie des rues et des adresses, notre code sera stocké dans l'avenue des cyprès, un endroit sympa et dans lequel il est facile de se repérer et dont les numéros de rues ne changent pas, mais pour y aller, nous sommes obligés de démarrer depuis une rue folle où les numéros de maison changent sans cesse.

La solution à ce casse-tête? Et bien nous allons faire en sorte d'utiliser le minimum d'instructions possible dans notre zone d'addresses changeantes et surtout, nous allons faire en sorte que ces instructions s'exécutent très vite et qu'elles ne déclenchent pas de nouveau cycle ASLR.
Notre script sera donc réduit à sa forme minimale et fera en sorte de stocker la fonction ReadASM en dehors de lui même afin de ne pas perdre sa trace en cas de changement d'adressage à cause de l'ASLR

Code : Tout sélectionner

ScriptStart:
	
	writebytetoaddr $1E, $02024744 ;		Stocke ReadASM dans gEnemyParty[0]
	writebytetoaddr $20, $02024745 ;		 
	writebytetoaddr $01, $02024746 ;		ReadASM lit les données du secteur 30
	writebytetoaddr $49, $02024747 ;		de la save et les copie dans l'EWRAM
	writebytetoaddr $01, $02024748 ;		à partir de 0x0203ABBC.
	writebytetoaddr $4B, $02024749 ;		
	writebytetoaddr $18, $0202474A ;		
	writebytetoaddr $47, $0202474B
	writebytetoaddr $BC, $0202474C
	writebytetoaddr $AB, $0202474D
	writebytetoaddr $03, $0202474E
	writebytetoaddr $02, $0202474F
	writebytetoaddr $19, $02024750
	writebytetoaddr $2E, $02024751
	writebytetoaddr $15, $02024752
	writebytetoaddr $08, $02024753

	callnative $02024745 ;				Exécute ReadASM

	goto $0203ABBC ; 				pointe vers le code qui vient d'être chargé dans l'EWRAM

ScriptEnd:
6.5.3 Tout mettre en musique
Voyons un peu ce que donne notre fichier script.asm complet :

Code : Tout sélectionner

INCLUDE "../macros.asm"
INCLUDE "../constants/items.asm"
INCLUDE "../constants/scriptcommands.asm"
INCLUDE "../eCard Injection Script/thumb/FonctionsCustom.asm"

	Mystery_Event

	db CHECKSUM_CRC
	dd 0 ; checksum placeholder
	GBAPTR DataStart
	GBAPTR DataEnd

DataStart:
	db IN_GAME_SCRIPT
	db $00,$12 ; 					Route 103 = 1200
	db 1   ; 					TopDresseur à côté de la grotte
	GBAPTR ScriptStart
	GBAPTR ScriptEnd

	db PRELOAD_SCRIPT
	GBAPTR PreloadScriptStart

	db END_OF_CHUNKS

PreloadScriptStart:
	setvirtualaddress PreloadScriptStart
	callnative $0201C045; 				Exécute WriteASM
	virtualloadpointer MessageDeTransfertScript
	setbyte 2
	dw $0000
	end
	WriteASM;					Code thumb dans /thumb/FonctionsCustom.asm
	;						WriteASM lit les 4096 bytes de code qui se
	;						trouvent sous lui et les copie dans le secteur
	;						30 de la save (complètement inutilisé dans les
	;						cartouches d'Emeraude non japonnaise)

VraiScriptStartHorsASLR:
	setvirtualaddress VraiScriptStartHorsASLR
	
	;Le vrai script est à rédiger ici
	;Grâce à l'astuce du WriteASM puis du ReadASM,
	;le code du script ne se situe plus dans les
	;zones mémoires soumises à l'ASLR.
	;Comme le code n'est plus stocké dans le 
	;RamScript, il n'est plus soumis à la limite
	;de taille de ce dernier. La limite passe de
	;995 bytes à 4096 bytes.

	end

MessageDeTransfertScript:
	Text_FR "Nouveau DLC disponible ! Va parler\n"
	Text_FR "à XXXX route YYYY.@"

ScriptStart:
	
	writebytetoaddr $1E, $02024744 ;		Stocke ReadASM dans gEnemyParty[0]
	writebytetoaddr $20, $02024745 ;		 
	writebytetoaddr $01, $02024746 ;		ReadASM lit les données du secteur 30
	writebytetoaddr $49, $02024747 ;		de la save et les copie dans l'EWRAM
	writebytetoaddr $01, $02024748 ;		à partir de 0x0203ABBC.
	writebytetoaddr $4B, $02024749 ;		
	writebytetoaddr $18, $0202474A ;		
	writebytetoaddr $47, $0202474B
	writebytetoaddr $BC, $0202474C
	writebytetoaddr $AB, $0202474D
	writebytetoaddr $03, $0202474E
	writebytetoaddr $02, $0202474F
	writebytetoaddr $19, $02024750
	writebytetoaddr $2E, $02024751
	writebytetoaddr $15, $02024752
	writebytetoaddr $08, $02024753

	callnative $02024745 ;				Exécute ReadASM

	goto $0203ABBC ; 				pointe vers le code qui vient d'être lu 

ScriptEnd:

DataEnd:
	EOF
De la même manière que Pokémon Rubis et Saphir, au lancement du pré-script, nous allons exécuter WriteASM (en tenant compte des nouvelles adresses dans la RAM) puis envoyer le message de validation du transfert de script et enfin donner l'accord de transmission du script.

Ici encore, WriteASM aura copié toutes les données qui se situent après lui dans le secteur 30 de sauvegarde, ce qui nous permettra d'y stocker notre vrai script en dehors de l'espace ASLR nommé VraiScriptStartHorsASLR.

Quant à notre script, il fera le strict minimum au moment de son exécution soit : écrire byte par byte la fonction ReadASM en dehors de l'espace ASLR grâce à la fonction writebytetoaddr, coûteuse mais nous permettant de jailbreaker en sécurité. puis l'exécutera et sautera vers l'avenue des Cyprès pour poursuivre l'exécution normale de notre vrai script. ReadASM sera écrit dans gEnemyParty, un endroit de la RAM normalement dédié au stockage des équipes adverses pendant les combats. Comme nous n'avons pas besoin de garder la fonction en mémoire RAM longtemps et que le lancement du script ne se fera jamais en plein milieu d'un combat, nous pouvons nous permettre de ranger ici nos bytes.

Et voilà! Nous avons réussi l'impossible : faire fonctionner les e-cards de script sur pokémon Emeraude, débloquant ainsi la possibilité de réaliser nos propres DLC!

Qu'allez-vous bien pouvoir créer?
Je suis impatient de le découvrir.

Répondre