Ce référentiel était une collection d'outils pour l'ingénierie inverse des jeux Pokémon de génération 1 et 2 au début du démontage. Les scripts vont ici des outils de construction du référentiel aux programmes de complexité variable pour extraire les données ROM. Aucun des outils ici n'est maintenu et n'a été écrit en Python2, et certains ne fonctionnent pas du tout parce que le port vers Python3 a été fait assez paresseusement. Les deux principaux outils d'intérêt, gbz80disasm.py et gfx.py ne sont plus utilisés et ont des outils modernes et mis à jour (voir MGBDIS pour un remplacement de gbz80disasm.py et les divers programmes en pokecrystal / outils pour un remplacement pour gfx.py ).
En conséquence, ce référentiel a été archivé et n'est disponible qu'à des fins historiques. Vous êtes seul si vous choisissez d'utiliser des scripts ici comme base d'outillage. Seuls quelques-uns des outils ici ont été publiés sous une licence open source si cela vous préoccupe.
pokemontools est un module Python qui fournit divers composants d'ingénierie inverse pour divers jeux Pokémon. Cela comprend:
Pour installer cette bibliothèque Python dans site-packages :
pip install --upgrade pokemontools
Et pour les travaux de développement locaux:
python setup.py develop
Et bien sûr l'installation locale:
python setup.py install
Exécutez les tests avec:
nosetests-2.7
Il pourrait y avoir beaucoup de résultats spam. Déposez un peu de spam avec:
nosetests-2.7 tests.integration.tests --nocapture --nologcapture
crystal.py analyse la ROM et fournit des classes pratiques pour vider ASM lisible par l'homme avec la méthode globale to_asm() . Cet ASM peut ensuite être compilé dans la ROM d'origine. Actuellement, il analyse les en-têtes de carte, les en-têtes de carte "deuxième", les en-têtes d'événements de carte, les en-têtes de script de carte, les déclencheurs de cartes, les "rappels", les blocs de blocs de cartes, les déclencheurs XY, les chaînes, les événements de personnes, les textes et les scripts.
Remarque: Tout au long de ces exemples, il est possible d'utiliser reload(crystal) au lieu d' import pokemontools.crystal . Une fois le module chargé une première fois, il doit être rechargé si le fichier modifie et que les mises à jour sont souhaitées.
import pokemontools . crystal as crystal
# parse the ROM
crystal . run_main ()
# create a new dump
asm = crystal . Asm ()
# insert the first 10 maps
x = 10
asm . insert_with_dependencies ( crystal . all_map_headers [: x ])
# dump to extras/output.txt
asm . dump () Après avoir exécuté ces lignes, cp extras/output.txt main.asm et exécutez git diff main.asm pour confirmer que les modifications de main.asm se sont produites. Pour tester si l'ASM nouvellement inséré se compile ou non dans la même ROM, utilisez: make clean && make . Cela se plaindra très fort si quelque chose est cassé.
Les tests unitaires couvrent la plupart des classes.
python tests.py Voici une démo sur la façon d'étudier un script particulier, en commençant par une adresse à un script connu (0x58043). Dans ce cas, le script appelle la commande 2writetext pour afficher une boîte de dialogue. Cette boîte de dialogue sera affichée à la fin de l'exemple.
import pokemontools . crystal as crystal
# parse the script at 0x58043 from the map event header at 0x584c3
# from the second map header at 0x958b8
# from the map header at 0x941ae
# for "Ruins of Alph Outside" (map_group=3 map_id=0x16)
script = Script ( 0x58043 )
# show the script
print script . to_asm ()
# what labels does it point to in the to_asm output?
# these must be present in the final asm file for rgbasm to compile the file
objdeps = script . get_dependencies ()
print str ( objdeps )
# the individual commands that make up the script
commands = script . commands
print str ( commands )
# the 3rd command is 2writetext and points to a text
thirdcommand = script . commands [ 2 ]
print thirdcommand
# <crystal.2writetextCommand instance at 0x8ad4c0c>
# look at the command parameters
params = thirdcommand . params
print params
# {0: <crystal.RawTextPointerLabelParam instance at 0x8ad4b0c>}
# 2writetext always has a single parameter
definition_param_count = len ( getattr ( crystal , "2writetextCommand" ). param_types . keys ())
current_param_count = len ( params . keys ())
assert definition_param_count == current_param_count , "this should never " +
"happen: instance of a command has more parameters than the " +
"definition of the command allows"
# get the first parameter (the text pointer)
param = params [ 0 ]
print param
# <crystal.RawTextPointerLabelParam instance at 0x8ad4b0c>
# RawTextPointerLabelParam instances point to their text
text = param . text
print text
# <crystal.TextScript instance at 0x8ad47ec>
# now investigate this text appearing in this script in "Ruins of Alph Outside"
print text . to_asm ()La sortie finale sera le texte suivant.
db $ 0 , "Hm? That' s a # - " , $ 4f
db "DEX, isn' t it?" , $ 55
; ... Cependant, ce n'est pas ainsi que cet objet TextScript apparaît dans le dernier ASM. Pour voir comment il apparaîtrait dans main.asm une fois inséré, vous exécuteriez print crystal.to_asm(text) pour obtenir ce qui suit.
UnknownText_0x580c7: ; 0x580c7
db $ 0 , "Hm? That' s a # - " , $ 4f
db "DEX, isn' t it?" , $ 55
db "May I see it?" , $ 51
db "There are so many" , $ 4f
db "kinds of #MON." , $ 51
db "Hm? What' s this?" , $ 51
db "What is this" , $ 4f
db "#MON?" , $ 51
db "It looks like the" , $ 4f
db "strange writing on" , $ 51
db "the walls of the" , $ 4f
db "RUINS." , $ 51
db "If those drawings" , $ 4f
db "are really #-" , $ 55
db "MON, there should" , $ 55
db "be many more." , $ 51
db "I know! Let me up-" , $ 4f
db "grade your #-" , $ 55
db "DEX. Follow me." , $ 57
; 0x581e5 Une autre approche consiste à analyser toute la ROM, puis à vérifier un script à une adresse particulière. Cela a l'avantage que l'objet de script aura l'ensemble des variables map_group et map_id .
import pokemontools . crystal as crystal
# parse the ROM
crystal . run_main ()
# get the parsed script
script = crystal . script_parse_table [ 0x58043 ]
# read its attributes to figure out map group / map id
map_group = script . map_group
map_id = script . map_id
# MapHeader is not given all the info yet
# in the mean time "map_names" contains some metadata
map_dict = crystal . map_names [ map_group ][ map_id ]
map_header = map_dict [ "header_new" ]
print map_dict [ "name" ]
# Ruins of Alph Outside Bien que ce qui précède ne le montre pas, il s'avère que le script à 0x58043 est référencé dans le MapEventHeader en tant qu'évéteur de personne.
print map_header . second_map_header . event_header . to_asm ()Cela montrera à peu près une structure comme:
person_event $ 3c , 19 , 15 , $ 7 , $ 0 , 255 , 255 , $ 0 , 0 , UnknownScript_0x58043 , $ 0703Dans ceci:
MapEventHeader_0x584c3: ; 0x584c3
; filler
db 0 , 0
; warps
db 11
warp_def $ 11 , $ 2 , 1 , GROUP_RUINS_OF_ALPH_HO_OH_CHAMBER , MAP_RUINS_OF_ALPH_HO_OH_CHAMBER
warp_def $ 7 , $ e , 1 , GROUP_RUINS_OF_ALPH_KABUTO_CHAMBER , MAP_RUINS_OF_ALPH_KABUTO_CHAMBER
warp_def $ 1d , $ 2 , 1 , GROUP_RUINS_OF_ALPH_OMANYTE_CHAMBER , MAP_RUINS_OF_ALPH_OMANYTE_CHAMBER
warp_def $ 21 , $ 10 , 1 , GROUP_RUINS_OF_ALPH_AERODACTYL_CHAMBER , MAP_RUINS_OF_ALPH_AERODACTYL_CHAMBER
warp_def $ d , $ a , 1 , GROUP_RUINS_OF_ALPH_INNER_CHAMBER , MAP_RUINS_OF_ALPH_INNER_CHAMBER
warp_def $ b , $ 11 , 1 , GROUP_RUINS_OF_ALPH_RESEARCH_CENTER , MAP_RUINS_OF_ALPH_RESEARCH_CENTER
warp_def $ 13 , $ 6 , 1 , GROUP_UNION_CAVE_B1F , MAP_UNION_CAVE_B1F
warp_def $ 1b , $ 6 , 2 , GROUP_UNION_CAVE_B1F , MAP_UNION_CAVE_B1F
warp_def $ 5 , $ 7 , 3 , GROUP_ROUTE_36_RUINS_OF_ALPH_GATE , MAP_ROUTE_36_RUINS_OF_ALPH_GATE
warp_def $ 14 , $ d , 1 , GROUP_ROUTE_32_RUINS_OF_ALPH_GATE , MAP_ROUTE_32_RUINS_OF_ALPH_GATE
warp_def $ 15 , $ d , 2 , GROUP_ROUTE_32_RUINS_OF_ALPH_GATE , MAP_ROUTE_32_RUINS_OF_ALPH_GATE
; xy triggers
db 2
xy_trigger 1 , $ e , $ b , $ 0 , UnknownScript_0x58031 , $ 0 , $ 0
xy_trigger 1 , $ f , $ a , $ 0 , UnknownScript_0x5803a , $ 0 , $ 0
; signposts
db 3
signpost 8 , 16 , $ 0 , UnknownScript_0x580b1
signpost 16 , 12 , $ 0 , UnknownScript_0x580b4
signpost 12 , 18 , $ 0 , UnknownScript_0x580b7
; people-events
db 5
person_event $ 27 , 24 , 8 , $ 6 , $ 0 , 255 , 255 , $ 2 , 1 , Trainer_0x58089 , $ ffff
person_event $ 3c , 19 , 15 , $ 7 , $ 0 , 255 , 255 , $ 0 , 0 , UnknownScript_0x58043 , $ 0703
person_event $ 3a , 21 , 17 , $ 3 , $ 0 , 255 , 255 , $ a0 , 0 , UnknownScript_0x58061 , $ 078e
person_event $ 27 , 15 , 18 , $ 2 , $ 11 , 255 , 255 , $ b0 , 0 , UnknownScript_0x58076 , $ 078f
person_event $ 27 , 12 , 16 , $ 7 , $ 0 , 255 , 255 , $ 80 , 0 , UnknownScript_0x5807e , $ 078f
; 0x58560 import pokemontools . crystal as crystal
# load the bytes
crystal . load_rom ()
# get a sequence of bytes
crystal . rom_interval ( 0x112116 , 10 )
# ['0x48', '0x54', '0x54', '0x50', '0x2f', '0x31', '0x2e', '0x30', '0xd', '0xa']
crystal . rom_interval ( 0x112116 , 10 , strings = False )
# [72, 84, 84, 80, 47, 49, 46, 48, 13, 10]
# get bytes until a certain byte
crystal . rom_until ( 0x112116 , 0x50 , strings = False )
# ['0x48', '0x54', '0x54']
# [72, 84, 84]
# or just look at the encoded characters directly
crystal . rom [ 0x112116 : 0x112116 + 10 ]
# 'HTTP/1.0rn'
# look at a text at 0x197186
text = crystal . parse_text_at2 ( 0x197186 , 601 , debug = False )
print textCe dernier texte à 0x197186 ressemblera à:
"""
OAK: Aha! So
you're !
I'm OAK! A #MON
researcher.
I was just visit-
ing my old friend
MR.#MON.
I heard you were
running an errand
for PROF.ELM, so I
waited here.
Oh! What's this?
A rare #MON!
...
"""