Esse repositório era uma coleção de ferramentas para a engenharia reversa dos jogos da geração 1 e 2 Pokémon nos primeiros dias de desmontagem. Os scripts aqui variam de ferramentas de construção de repositório a programas de complexidade variável para extrair dados da ROM. Nenhuma das ferramentas aqui é mantida mais e foi escrita no Python2, e algumas não correm, porque a porta para Python3 foi feita bastante preguiçosamente. As duas principais ferramentas de interesse, GBZ80DISASM.PY e GFX.PY não são mais usadas e têm ferramentas modernas e atualizadas (consulte MGBDIS para obter um substituto para gbz80disasm.py e os vários programas em PokecRystal/Tools para um substituto para gfx.py ).
Como resultado, esse repositório foi arquivado e está disponível apenas para fins históricos. Você está sozinho se optar por usar algum script aqui como base para ferramentas. Apenas algumas das ferramentas aqui foram divulgadas sob uma licença de código aberto, se isso for preocupante para você.
pokemontools é um módulo Python que fornece vários componentes de engenharia reversa para vários jogos de Pokémon. Isso inclui:
Para instalar esta biblioteca Python em site-packages :
pip install --upgrade pokemontools
E para o trabalho de desenvolvimento local:
python setup.py develop
E, claro, instalação local:
python setup.py install
Execute os testes com:
nosetests-2.7
Pode haver muita saída spam. Solte um pouco do spam com:
nosetests-2.7 tests.integration.tests --nocapture --nologcapture
crystal.py analisa a ROM e fornece classes convenientes para despejar o ASM legível pelo homem com o método global to_asm() . Este ASM pode então ser compilado de volta à ROM original. Atualmente, ele analisa os cabeçalhos do mapa: cabeçalhos de mapa "segundo", cabeçalhos de eventos de mapa, cabeçalhos de scripts do mapa, gatilhos de mapa, mapa "retornos de chamada", mapa blockdata, gatilhos XY, distorções, eventos de pessoas, textos e scripts.
NOTA: Ao longo desses exemplos, é possível usar reload(crystal) em vez de import pokemontools.crystal . Depois que o módulo é carregado pela primeira vez, ele deve ser recarregado se o arquivo mudar e as atualizações forem desejadas.
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 () Após a execução dessas linhas, cp extras/output.txt main.asm e execute git diff main.asm para confirmar que ocorreram alterações no main.asm . Para testar se o ASM recém -inserido recém -se ou não compila na mesma ROM, use: make clean && make . Isso vai reclamar muito alto se algo estiver quebrado.
Os testes de unidade cobrem a maioria das classes.
python tests.py Aqui está uma demonstração de como investigar um script específico, começando com apenas um endereço para um script conhecido (0x58043). Nesse caso, o script chama o comando 2writetext para mostrar alguma caixa de diálogo. Essa caixa de diálogo será mostrada no final do exemplo.
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 ()A saída final será o seguinte texto.
db $ 0 , "Hm? That' s a # - " , $ 4f
db "DEX, isn' t it?" , $ 55
; ... No entanto, não é assim que esse objeto TextScript apareceu no ASM final. Para ver como ele apareceria no main.asm uma vez inserido, você executaria print crystal.to_asm(text) para obter o seguinte.
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 Outra abordagem é analisar a ROM inteira e verificar um script em um endereço específico. Isso tem a vantagem de que o objeto de script tenha as variáveis map_group e map_id definidas.
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 Embora o acima não mostre isso, acontece que o script em 0x58043 é referenciado no MapEventHeader como um evento de pessoa.
print map_header . second_map_header . event_header . to_asm ()Isso mostrará uma estrutura aproximadamente como:
person_event $ 3c , 19 , 15 , $ 7 , $ 0 , 255 , 255 , $ 0 , 0 , UnknownScript_0x58043 , $ 0703Dentro disso:
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 textEsse último texto em 0x197186 parecerá:
"""
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!
...
"""