
Saudações, camaradas. Eu não ouvi falar do GHIDRA
, ainda não aberto, provavelmente apenas um engenheiro reverso surdo / cego / mudo / sem Internet. Seus recursos prontos são surpreendentes: descompiladores para todos os processadores suportados, adição simples de novas arquiteturas (com descompilação ativa imediata devido à conversão competente em IR), um monte de scripts que simplificam a vida, a possibilidade de Undo
/ Redo
... E essa é apenas uma parte muito pequena de todos os recursos fornecidos. Dizer que fiquei impressionado é dizer quase nada.
Portanto, neste artigo, gostaria de contar como escrevi meu primeiro módulo para o GHIDRA
- um carregador de rum para jogos do Sega Mega Drive / Genesis
. Para escrever, eu precisava ... apenas algumas horas! Vamos lá
Mas e a IDA?Passei alguns dias entendendo o processo de criação de downloaders para a IDA
. Aparentemente, era a versão 6.5
, mas naquela época havia muitos problemas com a documentação do SDK.
Preparamos o ambiente de desenvolvimento
GHIDRA
desenvolvedores da GHIDRA
pensaram em quase tudo ( Ilfak , onde você esteve antes?). E, apenas para simplificar a implementação da nova funcionalidade, eles desenvolveram um plug-in para o Eclipse
- GhidraDev
, que na verdade " ajuda " a escrever código. O plug-in é integrado ao ambiente de desenvolvimento e permite criar modelos de projeto para scripts, carregadores, módulos de processador e extensões para eles, bem como módulos de exportação (como eu entendo, isso é algum tipo de exportação de dados de um projeto) com apenas alguns cliques.
Para instalar o plug-in, faça o download do Eclipse
para Java
, clique em Help
-> Install New Software...
, clique no botão Add
e abra a caixa de diálogo de seleção de arquivamento com o plug-in usando o botão Archive...
O arquivo GhidraDev
com GhidraDev
está localizado no $(GHIDRA)/Extensions/Eclipse/GhidraDev
. Selecione, clique no botão Add
.

Na lista exibida, Ghidra
uma Ghidra
no Ghidra
, clique em Next >
, aceite os contratos, clique em Install Anyway
(porque o plug-in não possui uma assinatura) e reinicie o Eclipse
.

No total, um novo item do GhidraDev
aparecerá no GhidraDev
IDE para criação e distribuição convenientes de seus projetos (é claro, você também pode criar através do assistente usual para novos projetos Eclipse
). Além disso, temos a oportunidade de depurar o plug-in ou script desenvolvido.

E onde está o depurador de aplicativos?O que realmente enfurece a situação com a GHIDRA
são os artigos roucos e GHIDRA
contêm quase o mesmo material, o que, além disso, não é verdade. Um exemplo? Sim por favor:
A versão atual da ferramenta é 9.0. e a ferramenta possui opções para incluir funcionalidades adicionais, como análise de criptografia, interação com o OllyDbg, o depurador Ghidra.
E onde está tudo isso? Não!
O segundo ponto: abertura. De fato, está quase lá, mas é praticamente inexistente. A GHIDRA
contém códigos-fonte para componentes que foram escritos em Java
, mas se você observar os scripts Gradle
, poderá ver que existem dependências em vários projetos externos dos secretos laboratórios Repositórios da NSA
.
No momento da redação deste documento, não havia SLEIGH
decompilador e SLEIGH
(este é um utilitário para compilar descrições de módulos de processador e conversões para IR).
Bem, oh bem, estou distraído com alguma coisa.
Então, vamos criar um novo projeto no Eclipse
.
Crie um projeto loader
GhidraDev
-> New
-> Ghidra Module Project...
Indicamos o nome do projeto (levamos em conta que as palavras do tipo Loader
serão coladas aos nomes dos arquivos e, para não obter algo como sega_loaderLoader.java
, os sega_loaderLoader.java
acordo).

Clique em Next >
. Aqui, colocamos daws na frente das categorias que precisamos. No meu caso, este é apenas um Loader
. Clique em Next >
.

Aqui, indicamos o caminho para o diretório com o
. Clique em Next >
.

GHIDRA
permite escrever scripts em python (via Jython
). Vou escrever em Java
, por isso não coloco um daw. Eu pressiono Finish
.

Escrevendo um código
A árvore vazia do projeto parece impressionante:

Todos os arquivos com código java
estão no ramo /src/main/java
:

getName ()
Para começar, vamos escolher um nome para o gerenciador de inicialização. O método getName()
retorna:
@Override public String getName() { return "Sega Mega Drive / Genesis Loader"; }
findSupportedLoadSpecs ()
O método findSupportedLoadSpecs()
decide (com base nos dados contidos no arquivo binário) qual módulo do processador deve ser usado para desmontagem (assim como no IDA
). Na terminologia GHIDRA
isso é chamado de Compiler Language
. Inclui: processador, endianness, bitness e compilador (se conhecido).
Este método retorna uma lista de arquiteturas e idiomas suportados. Se os dados estiverem no formato errado, simplesmente retornamos uma lista vazia.
Portanto, no caso do Sega Mega Drive
, a palavra " SEGA
" costuma estar presente no deslocamento 0x100
cabeçalho (isso não é um pré-requisito, mas é cumprido em 99% dos casos). Você precisa verificar se esta linha está no arquivo importado. Para fazer isso, o ByteProvider provider
é ByteProvider provider
findSupportedLoadSpecs()
, com a qual trabalharemos com o arquivo.
Criamos um objeto BinaryReader
, para a conveniência de ler dados de um arquivo:
BinaryReader reader = new BinaryReader(provider, false);
O argumento false
neste caso indica o uso de Big Endian
ao ler. Agora vamos ler a linha. Para fazer isso, use o readAsciiString(offset, size)
do objeto reader
:
reader.readAsciiString(0x100, 4).equals(new String("SEGA"))
Se equals()
retorna true
, então estamos lidando com o rum de Segov e na lista List<LoadSpec> loadSpecs = new ArrayList<>();
será possível adicionar o Motorola m68k
. Para fazer isso, crie um novo objeto do tipo LoadSpec
, cujo construtor aceita um objeto loader (neste caso, this
), ImageBase
, no qual a ROM será carregada, um objeto do tipo LanguageCompilerSpecPair
e um sinalizador - esse LoadSpec
entre os outros na lista (sim, na lista pode haver mais de um LoadSpec
).
O formato do construtor para LanguageCompilerSpecPair
o seguinte:
- O primeiro argumento é
languageID
, uma sequência no formato " ProcessorName: Endianness: Bits: ExactCpu ". No meu caso, deve ser a linha " 68000: BE: 32: MC68020 " (infelizmente, exatamente o MC68000
não está incluído na entrega, mas isso não é um problema). ExactCpu
pode ser o default
- O segundo argumento,
compilerSpecID
, para encontrar o que você precisa especificar aqui, pode ser encontrado no diretório com as descrições do processador
( $(GHIDRA)/Ghidra/Processors/68000/data/languages
) no arquivo 68000.opinion
. Vemos que apenas o default
é indicado aqui. Na verdade, nós indicamos isso
Como resultado, temos o seguinte código (como você pode ver, nada complicado até agora):
@Override public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException { List<LoadSpec> loadSpecs = new ArrayList<>(); BinaryReader reader = new BinaryReader(provider, false); if (reader.readAsciiString(0x100, 4).equals(new String("SEGA"))) { loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair("68000:BE:32:MC68020", "default"), true)); } return loadSpecs; }
A diferença entre IDA e GHIDRA em termos de módulosHá uma diferença, e ainda é muito forte. No GHIDRA
você pode escrever um projeto que entenderá diferentes arquiteturas, diferentes formatos de dados, seja um carregador, um módulo de processador, expansão da funcionalidade do descompilador e outros itens.
Na IDA
, esse é um projeto separado para cada tipo de complemento.
Quanto mais conveniente? Na minha opinião, na GHIDRA
- às vezes!
load ()
Nesse método, criaremos segmentos, processaremos dados de entrada, criaremos códigos e rótulos conhecidos anteriormente. Para fazer isso, os seguintes objetos são enviados para nos ajudar na entrada:
ByteProvider provider
: já o conhecemos. Trabalhando com dados de arquivos bináriosLoadSpec loadSpec
: especificação da arquitetura que foi selecionada durante a fase de importação do arquivo usando o método findSupportedLoadSpecs
. É necessário que, por exemplo, possamos trabalhar com vários formatos de dados em um módulo. ConvenienteList<Option> options
: lista de opções (incluindo personalizadas). Ainda não aprendi a trabalhar com elesProgram program
: o principal objeto que fornece acesso a todas as funcionalidades necessárias: listagem, espaço de endereçamento, segmentos, etiquetas, criação de matrizes, etc.MemoryConflictHandler handler
e o TaskMonitor monitor
: raramente precisamos trabalhar diretamente com eles (geralmente, apenas passando esses objetos para métodos prontos)MessageLog log
: próprio logger
Portanto, para iniciantes, vamos criar alguns objetos que simplificarão nosso trabalho com entidades GHIDRA
e dados existentes. Obviamente, precisaremos definitivamente do BinaryReader
:
BinaryReader reader = new BinaryReader(provider, false);
Próximo. Será muito útil para nós e simplificará quase todo o objeto da classe FlatProgramAPI
(você verá mais tarde o que pode fazer com ele):
FlatProgramAPI fpa = new FlatProgramAPI(program, monitor);
Rum cabeçalho
Para começar, determinaremos qual é o título de um rum comum da Sega. Nos primeiros 0x100
bytes, há uma tabela de 64 ponteiros DWORD para vetores, por exemplo: Reset
, Trap
, DivideByZero
, VBLANK
e outros.
Em seguida, vem a estrutura com o nome dos Roma, regiões, endereços do início e final dos blocos ROM
e RAM
, a caixa de seleção (o campo é verificado a pedido dos desenvolvedores, e não o prefixo) e outras informações.
Vamos criar classes java
para trabalhar com essas estruturas, bem como implementar os tipos de dados que serão adicionados à lista de estruturas.
VetoresTabela
Criamos uma nova classe VectorsTable
e, atenção, indicamos que ela implementa a interface StructConverter
. Nesta classe, armazenaremos os endereços dos vetores (para uso futuro) e seus nomes.

Declaramos uma lista de nomes de vetores e seu número:
private static final int VECTORS_SIZE = 0x100; private static final int VECTORS_COUNT = VECTORS_SIZE / 4; private static final String[] VECTOR_NAMES = { "SSP", "Reset", "BusErr", "AdrErr", "InvOpCode", "DivBy0", "Check", "TrapV", "GPF", "Trace", "Reserv0", "Reserv1", "Reserv2", "Reserv3", "Reserv4", "BadInt", "Reserv10", "Reserv11", "Reserv12", "Reserv13", "Reserv14", "Reserv15", "Reserv16", "Reserv17", "BadIRQ", "IRQ1", "EXT", "IRQ3", "HBLANK", "IRQ5", "VBLANK", "IRQ7", "Trap0", "Trap1", "Trap2", "Trap3", "Trap4", "Trap5", "Trap6", "Trap7", "Trap8", "Trap9", "Trap10", "Trap11", "Trap12", "Trap13","Trap14", "Trap15", "Reserv30", "Reserv31", "Reserv32", "Reserv33", "Reserv34", "Reserv35", "Reserv36", "Reserv37", "Reserv38", "Reserv39", "Reserv3A", "Reserv3B", "Reserv3C", "Reserv3D", "Reserv3E", "Reserv3F" };
Criamos uma classe separada para armazenar o endereço e o nome do vetor:
package sega; import ghidra.program.model.address.Address; public class VectorFunc { private Address address; private String name; public VectorFunc(Address address, String name) { this.address = address; this.name = name; } public Address getAddress() { return address; } public String getName() { return name; } }
A lista de vetores será armazenada na matriz de vectors
:
private VectorFunc[] vectors;
O construtor para VectorsTable
aceitará:
FlatProgramAPI fpa
para converter endereços long
no tipo de dados Address
do Hydra (na verdade, esse tipo de dado complementa o valor numérico simples do endereço, vinculando-o a outro chip - o espaço de endereço)BinaryReader reader
- estaleiros de leitura
O objeto fpa
possui um método toAddr()
e o reader
setPointerIndex()
e readNextUnsignedInt()
. Em princípio, nada mais é necessário. Nós obtemos o código:
public VectorsTable(FlatProgramAPI fpa, BinaryReader reader) throws IOException { if (reader.length() < VECTORS_COUNT) { return; } reader.setPointerIndex(0); vectors = new VectorFunc[VECTORS_COUNT]; for (int i = 0; i < VECTORS_COUNT; ++i) { vectors[i] = new VectorFunc(fpa.toAddr(reader.readNextUnsignedInt()), VECTOR_NAMES[i]); } }
O método toDataType()
, que precisamos substituir para implementar a estrutura, deve retornar um objeto Structure
no qual os nomes dos campos da estrutura, seus tamanhos e comentários em cada campo devem ser declarados (você pode usar null
):
@Override public DataType toDataType() { Structure s = new StructureDataType("VectorsTable", 0); for (int i = 0; i < VECTORS_COUNT; ++i) { s.add(POINTER, 4, VECTOR_NAMES[i], null); } return s; }
Bem, vamos implementar métodos para obter cada um dos vetores ou a lista inteira (um monte de código padrão):
Outros métodos public VectorFunc[] getVectors() { return vectors; } public VectorFunc getSSP() { if (vectors.length < 1) { return null; } return vectors[0]; } public VectorFunc getReset() { if (vectors.length < 2) { return null; } return vectors[1]; } public VectorFunc getBusErr() { if (vectors.length < 3) { return null; } return vectors[2]; } public VectorFunc getAdrErr() { if (vectors.length < 4) { return null; } return vectors[3]; } public VectorFunc getInvOpCode() { if (vectors.length < 5) { return null; } return vectors[4]; } public VectorFunc getDivBy0() { if (vectors.length < 6) { return null; } return vectors[5]; } public VectorFunc getCheck() { if (vectors.length < 7) { return null; } return vectors[6]; } public VectorFunc getTrapV() { if (vectors.length < 8) { return null; } return vectors[7]; } public VectorFunc getGPF() { if (vectors.length < 9) { return null; } return vectors[8]; } public VectorFunc getTrace() { if (vectors.length < 10) { return null; } return vectors[9]; } public VectorFunc getReserv0() { if (vectors.length < 11) { return null; } return vectors[10]; } public VectorFunc getReserv1() { if (vectors.length < 12) { return null; } return vectors[11]; } public VectorFunc getReserv2() { if (vectors.length < 13) { return null; } return vectors[12]; } public VectorFunc getReserv3() { if (vectors.length < 14) { return null; } return vectors[13]; } public VectorFunc getReserv4() { if (vectors.length < 15) { return null; } return vectors[14]; } public VectorFunc getBadInt() { if (vectors.length < 16) { return null; } return vectors[15]; } public VectorFunc getReserv10() { if (vectors.length < 17) { return null; } return vectors[16]; } public VectorFunc getReserv11() { if (vectors.length < 18) { return null; } return vectors[17]; } public VectorFunc getReserv12() { if (vectors.length < 19) { return null; } return vectors[18]; } public VectorFunc getReserv13() { if (vectors.length < 20) { return null; } return vectors[19]; } public VectorFunc getReserv14() { if (vectors.length < 21) { return null; } return vectors[20]; } public VectorFunc getReserv15() { if (vectors.length < 22) { return null; } return vectors[21]; } public VectorFunc getReserv16() { if (vectors.length < 23) { return null; } return vectors[22]; } public VectorFunc getReserv17() { if (vectors.length < 24) { return null; } return vectors[23]; } public VectorFunc getBadIRQ() { if (vectors.length < 25) { return null; } return vectors[24]; } public VectorFunc getIRQ1() { if (vectors.length < 26) { return null; } return vectors[25]; } public VectorFunc getEXT() { if (vectors.length < 27) { return null; } return vectors[26]; } public VectorFunc getIRQ3() { if (vectors.length < 28) { return null; } return vectors[27]; } public VectorFunc getHBLANK() { if (vectors.length < 29) { return null; } return vectors[28]; } public VectorFunc getIRQ5() { if (vectors.length < 30) { return null; } return vectors[29]; } public VectorFunc getVBLANK() { if (vectors.length < 31) { return null; } return vectors[30]; } public VectorFunc getIRQ7() { if (vectors.length < 32) { return null; } return vectors[31]; } public VectorFunc getTrap0() { if (vectors.length < 33) { return null; } return vectors[32]; } public VectorFunc getTrap1() { if (vectors.length < 34) { return null; } return vectors[33]; } public VectorFunc getTrap2() { if (vectors.length < 35) { return null; } return vectors[34]; } public VectorFunc getTrap3() { if (vectors.length < 36) { return null; } return vectors[35]; } public VectorFunc getTrap4() { if (vectors.length < 37) { return null; } return vectors[36]; } public VectorFunc getTrap5() { if (vectors.length < 38) { return null; } return vectors[37]; } public VectorFunc getTrap6() { if (vectors.length < 39) { return null; } return vectors[38]; } public VectorFunc getTrap7() { if (vectors.length < 40) { return null; } return vectors[39]; } public VectorFunc getTrap8() { if (vectors.length < 41) { return null; } return vectors[40]; } public VectorFunc getTrap9() { if (vectors.length < 42) { return null; } return vectors[41]; } public VectorFunc getTrap10() { if (vectors.length < 43) { return null; } return vectors[42]; } public VectorFunc getTrap11() { if (vectors.length < 44) { return null; } return vectors[43]; } public VectorFunc getTrap12() { if (vectors.length < 45) { return null; } return vectors[44]; } public VectorFunc getTrap13() { if (vectors.length < 46) { return null; } return vectors[45]; } public VectorFunc getTrap14() { if (vectors.length < 47) { return null; } return vectors[46]; } public VectorFunc getTrap15() { if (vectors.length < 48) { return null; } return vectors[47]; } public VectorFunc getReserv30() { if (vectors.length < 49) { return null; } return vectors[48]; } public VectorFunc getReserv31() { if (vectors.length < 50) { return null; } return vectors[49]; } public VectorFunc getReserv32() { if (vectors.length < 51) { return null; } return vectors[50]; } public VectorFunc getReserv33() { if (vectors.length < 52) { return null; } return vectors[51]; } public VectorFunc getReserv34() { if (vectors.length < 53) { return null; } return vectors[52]; } public VectorFunc getReserv35() { if (vectors.length < 54) { return null; } return vectors[53]; } public VectorFunc getReserv36() { if (vectors.length < 55) { return null; } return vectors[54]; } public VectorFunc getReserv37() { if (vectors.length < 56) { return null; } return vectors[55]; } public VectorFunc getReserv38() { if (vectors.length < 57) { return null; } return vectors[56]; } public VectorFunc getReserv39() { if (vectors.length < 58) { return null; } return vectors[57]; } public VectorFunc getReserv3A() { if (vectors.length < 59) { return null; } return vectors[58]; } public VectorFunc getReserv3B() { if (vectors.length < 60) { return null; } return vectors[59]; } public VectorFunc getReserv3C() { if (vectors.length < 61) { return null; } return vectors[60]; } public VectorFunc getReserv3D() { if (vectors.length < 62) { return null; } return vectors[61]; } public VectorFunc getReserv3E() { if (vectors.length < 63) { return null; } return vectors[62]; } public VectorFunc getReserv3F() { if (vectors.length < 64) { return null; } return vectors[63]; }
Faremos o mesmo e criaremos uma classe GameHeader
que implementa a interface StructConverter
.
Estrutura de cabeçalho de rum de jogoIniciar deslocamento | Deslocamento final | Descrição do produto |
---|
$ 100 | $ 10F | Nome do console (geralmente 'SEGA MEGA DRIVE' ou 'SEGA GENESIS') |
$ 110 | $ 11F | Data de lançamento (geralmente '© XXXX YYYY.MMM', em que XXXX é a empresa, YYYY é o ano e MMM - mês) |
$ 120 | $ 14F | Nome nacional |
$ 150 | $ 17F | Nome internacional |
$ 180 | $ 18D | Versão ('XX AAAAAAAAAAA', onde XX é o tipo de jogo e AA o código do jogo) |
$ 18E | $ 18F | Soma de verificação |
$ 190 | $ 19F | Suporte de E / S |
$ 1A0 | $ 1A3 | Início da ROM |
$ 1A4 | $ 1A7 | Final da ROM |
$ 1A8 | $ 1AB | Início da RAM (geralmente $ 00FF0000) |
$ 1AC | $ 1AF | Final da RAM (geralmente $ 00FFFFFF) |
US $ 1 bilhão | $ 1B2 | 'RA' e $ F8 habilitam SRAM |
$ 1B3 | ---- | não utilizado (US $ 20) |
$ 1B4 | $ 1B7 | Início da SRAM (padrão $ 00200000) |
$ 1B8 | US $ 1 bilhão | Fim da SRAM (padrão $ 0020FFFF) |
$ 1BC | $ 1FF | Notas (não utilizadas) |
Configuramos os campos, verificamos um comprimento suficiente dos dados de entrada, usamos dois métodos para readNextByteArray()
, readNextUnsignedShort()
do objeto reader
para ler dados e criar uma estrutura. O código resultante é o seguinte:
Gameheader package sega; import java.io.IOException; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.StructConverter; import ghidra.program.flatapi.FlatProgramAPI; import ghidra.program.model.address.Address; import ghidra.program.model.data.DataType; import ghidra.program.model.data.Structure; import ghidra.program.model.data.StructureDataType; public class GameHeader implements StructConverter { private byte[] consoleName = null; private byte[] releaseDate = null; private byte[] domesticName = null; private byte[] internationalName = null; private byte[] version = null; private short checksum = 0; private byte[] ioSupport = null; private Address romStart = null, romEnd = null; private Address ramStart = null, ramEnd = null; private byte[] sramCode = null; private byte unused = 0; private Address sramStart = null, sramEnd = null; private byte[] notes = null; FlatProgramAPI fpa; public GameHeader(FlatProgramAPI fpa, BinaryReader reader) throws IOException { this.fpa = fpa; if (reader.length() < 0x200) { return; } reader.setPointerIndex(0x100); consoleName = reader.readNextByteArray(0x10); releaseDate = reader.readNextByteArray(0x10); domesticName = reader.readNextByteArray(0x30); internationalName = reader.readNextByteArray(0x30); version = reader.readNextByteArray(0x0E); checksum = (short) reader.readNextUnsignedShort(); ioSupport = reader.readNextByteArray(0x10); romStart = fpa.toAddr(reader.readNextUnsignedInt()); romEnd = fpa.toAddr(reader.readNextUnsignedInt()); ramStart = fpa.toAddr(reader.readNextUnsignedInt()); ramEnd = fpa.toAddr(reader.readNextUnsignedInt()); sramCode = reader.readNextByteArray(0x03); unused = reader.readNextByte(); sramStart = fpa.toAddr(reader.readNextUnsignedInt()); sramEnd = fpa.toAddr(reader.readNextUnsignedInt()); notes = reader.readNextByteArray(0x44); } @Override public DataType toDataType() { Structure s = new StructureDataType("GameHeader", 0); s.add(STRING, 0x10, "ConsoleName", null); s.add(STRING, 0x10, "ReleaseDate", null); s.add(STRING, 0x30, "DomesticName", null); s.add(STRING, 0x30, "InternationalName", null); s.add(STRING, 0x0E, "Version", null); s.add(WORD, 0x02, "Checksum", null); s.add(STRING, 0x10, "IoSupport", null); s.add(POINTER, 0x04, "RomStart", null); s.add(POINTER, 0x04, "RomEnd", null); s.add(POINTER, 0x04, "RamStart", null); s.add(POINTER, 0x04, "RamEnd", null); s.add(STRING, 0x03, "SramCode", null); s.add(BYTE, 0x01, "Unused", null); s.add(POINTER, 0x04, "SramStart", null); s.add(POINTER, 0x04, "SramEnd", null); s.add(STRING, 0x44, "Notes", null); return s; } public byte[] getConsoleName() { return consoleName; } public byte[] getReleaseDate() { return releaseDate; } public byte[] getDomesticName() { return domesticName; } public byte[] getInternationalName() { return internationalName; } public byte[] getVersion() { return version; } public short getChecksum() { return checksum; } public byte[] getIoSupport() { return ioSupport; } public Address getRomStart() { return romStart; } public Address getRomEnd() { return romEnd; } public Address getRamStart() { return ramStart; } public Address getRamEnd() { return ramEnd; } public byte[] getSramCode() { return sramCode; } public byte getUnused() { return unused; } public Address getSramStart() { return sramStart; } public Address getSramEnd() { return sramEnd; } public boolean hasSRAM() { if (sramCode == null) { return false; } return sramCode[0] == 'R' && sramCode[1] == 'A' && sramCode[2] == 0xF8; } public byte[] getNotes() { return notes; } }
Crie objetos para o cabeçalho:
vectors = new VectorsTable(fpa, reader); header = new GameHeader(fpa, reader);
Segmentos
A Sega possui um mapa bem conhecido de regiões de memória, que talvez eu não cite aqui na forma de tabela, mas darei apenas o código usado para criar os segmentos.
Portanto, o objeto da classe FlatProgramAPI
possui um método createMemoryBlock()
, com o qual é conveniente criar regiões de memória. Na entrada, são utilizados os seguintes argumentos:
name
: nome da regiãoaddress
: address
inicial da regiãostream
: um objeto do tipo InputStream
que será a base para os dados na região da memória. Se você especificar null
, uma região não inicializada será criada (por exemplo, para 68K RAM
ou Z80 RAM
, precisaremos exatamente dissosize
: tamanho da região criadaisOverlay
: aceita true
ou false
e indica que a região da memória está sobreposta. Não sei onde é necessário, exceto arquivos executáveis
Na saída, createMemoryBlock()
retorna um objeto do tipo MemoryBlock
, no qual você pode definir opcionalmente sinalizadores de direitos de acesso ( Read
, Write
, Execute
).
Como resultado, obtemos uma função da seguinte forma:
private void createSegment(FlatProgramAPI fpa, InputStream stream, String name, Address address, long size, boolean read, boolean write, boolean execute) { MemoryBlock block = null; try { block = fpa.createMemoryBlock(name, address, stream, size, false); block.setRead(read); block.setWrite(read); block.setExecute(execute); } catch (Exception e) { Msg.error(this, String.format("Error creating %s segment", name)); } }
Aqui também chamamos o método de error
estático da classe Msg
para exibir uma mensagem de erro.
Um segmento que contém rum de jogo pode ter um tamanho máximo de 0x3FFFFF
(todo o resto já pertencerá a outras regiões). Vamos criá-lo:
InputStream romStream = provider.getInputStream(0); createSegment(fpa, romStream, "ROM", fpa.toAddr(0x000000), Math.min(romStream.available(), 0x3FFFFF), true, false, true);
InputStream
, 0.
, ( SegaCD
Sega32X
). OptionDialog
. , showYesNoDialogWithNoAsDefaultButton()
YES
NO
- NO
.
:
if (OptionDialog.YES_OPTION == OptionDialog.showYesNoDialogWithNoAsDefaultButton(null, "Question", "Create Sega CD segment?")) { if (romStream.available() > 0x3FFFFF) { InputStream epaStream = provider.getInputStream(0x400000); createSegment(fpa, epaStream, "EPA", fpa.toAddr(0x400000), 0x400000, true, true, false); } else { createSegment(fpa, null, "EPA", fpa.toAddr(0x400000), 0x400000, true, true, false); } } if (OptionDialog.YES_OPTION == OptionDialog.showYesNoDialogWithNoAsDefaultButton(null, "Question", "Create Sega 32X segment?")) { createSegment(fpa, null, "32X", fpa.toAddr(0x800000), 0x200000, true, true, false); }
:
createSegment(fpa, null, "Z80", fpa.toAddr(0xA00000), 0x10000, true, true, false); createSegment(fpa, null, "SYS1", fpa.toAddr(0xA10000), 16 * 2, true, true, false); createSegment(fpa, null, "SYS2", fpa.toAddr(0xA11000), 2, true, true, false); createSegment(fpa, null, "Z802", fpa.toAddr(0xA11100), 2, true, true, false); createSegment(fpa, null, "Z803", fpa.toAddr(0xA11200), 2, true, true, false); createSegment(fpa, null, "FDC", fpa.toAddr(0xA12000), 0x100, true, true, false); createSegment(fpa, null, "TIME", fpa.toAddr(0xA13000), 0x100, true, true, false); createSegment(fpa, null, "TMSS", fpa.toAddr(0xA14000), 4, true, true, false); createSegment(fpa, null, "VDP", fpa.toAddr(0xC00000), 2 * 9, true, true, false); createSegment(fpa, null, "RAM", fpa.toAddr(0xFF0000), 0x10000, true, true, true); if (header.hasSRAM()) { Address sramStart = header.getSramStart(); Address sramEnd = header.getSramEnd(); if (sramStart.getOffset() >= 0x200000 && sramEnd.getOffset() <= 0x20FFFF && sramStart.getOffset() < sramEnd.getOffset()) { createSegment(fpa, null, "SRAM", sramStart, sramEnd.getOffset() - sramStart.getOffset() + 1, true, true, false); } }
,
CreateArrayCmd
. , :
address
: ,numElements
:dataType
:elementSize
:
applyTo(program)
, .
, , BYTE
, WORD
, DWORD
. , FlatProgramAPI
createByte()
, createWord()
, createDword()
..
, , (, VDP
). , :
Program
getSymbolTable()
, , ..createLabel()
, , . , , SourceType.IMPORTED
, :
private void createNamedByteArray(FlatProgramAPI fpa, Program program, Address address, String name, int numElements) { if (numElements > 1) { CreateArrayCmd arrayCmd = new CreateArrayCmd(address, numElements, ByteDataType.dataType, ByteDataType.dataType.getLength()); arrayCmd.applyTo(program); } else { try { fpa.createByte(address); } catch (Exception e) { Msg.error(this, "Cannot create byte. " + e.getMessage()); } } try { program.getSymbolTable().createLabel(address, name, SourceType.IMPORTED); } catch (InvalidInputException e) { Msg.error(this, String.format("%s : Error creating array %s", getName(), name)); } } private void createNamedWordArray(FlatProgramAPI fpa, Program program, Address address, String name, int numElements) { if (numElements > 1) { CreateArrayCmd arrayCmd = new CreateArrayCmd(address, numElements, WordDataType.dataType, WordDataType.dataType.getLength()); arrayCmd.applyTo(program); } else { try { fpa.createWord(address); } catch (Exception e) { Msg.error(this, "Cannot create word. " + e.getMessage()); } } try { program.getSymbolTable().createLabel(address, name, SourceType.IMPORTED); } catch (InvalidInputException e) { Msg.error(this, String.format("%s : Error creating array %s", getName(), name)); } } private void createNamedDwordArray(FlatProgramAPI fpa, Program program, Address address, String name, int numElements) { if (numElements > 1) { CreateArrayCmd arrayCmd = new CreateArrayCmd(address, numElements, DWordDataType.dataType, DWordDataType.dataType.getLength()); arrayCmd.applyTo(program); } else { try { fpa.createDWord(address); } catch (Exception e) { Msg.error(this, "Cannot create dword. " + e.getMessage()); } } try { program.getSymbolTable().createLabel(address, name, SourceType.IMPORTED); } catch (InvalidInputException e) { Msg.error(this, String.format("%s : Error creating array %s", getName(), name)); } }
createNamedDwordArray(fpa, program, fpa.toAddr(0xA04000), "Z80_YM2612", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10000), "IO_PCBVER", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10002), "IO_CT1_DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10004), "IO_CT2_DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10006), "IO_EXT_DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10008), "IO_CT1_CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1000A), "IO_CT2_CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1000C), "IO_EXT_CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1000E), "IO_CT1_RX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10010), "IO_CT1_TX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10012), "IO_CT1_SMODE", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10014), "IO_CT2_RX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10016), "IO_CT2_TX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10018), "IO_CT2_SMODE", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1001A), "IO_EXT_RX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1001C), "IO_EXT_TX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1001E), "IO_EXT_SMODE", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA11000), "IO_RAMMODE", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA11100), "IO_Z80BUS", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA11200), "IO_Z80RES", 1); createNamedByteArray(fpa, program, fpa.toAddr(0xA12000), "IO_FDC", 0x100); createNamedByteArray(fpa, program, fpa.toAddr(0xA13000), "IO_TIME", 0x100); createNamedDwordArray(fpa, program, fpa.toAddr(0xA14000), "IO_TMSS", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00000), "VDP_DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00002), "VDP__DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00004), "VDP_CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00006), "VDP__CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00008), "VDP_CNTR", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC0000A), "VDP__CNTR", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC0000C), "VDP___CNTR", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC0000E), "VDP____CNTR", 1); createNamedByteArray(fpa, program, fpa.toAddr(0xC00011), "VDP_PSG", 1);
createData()
DataUtilities
. :
program
: Program
address
: ,dataType
:dataLength
: . -1
stackPointers
: true
, - . false
clearDataMode
: , (, )
: .. (, ), . FlatProgramAPI
createFunction()
, .
:
private void markVectorsTable(Program program, FlatProgramAPI fpa) { try { DataUtilities.createData(program, fpa.toAddr(0), vectors.toDataType(), -1, false, ClearDataMode.CLEAR_ALL_UNDEFINED_CONFLICT_DATA); for (VectorFunc func : vectors.getVectors()) { fpa.createFunction(func.getAddress(), func.getName()); } } catch (CodeUnitInsertionException e) { Msg.error(this, "Vectors mark conflict at 0x000000"); } } private void markHeader(Program program, FlatProgramAPI fpa) { try { DataUtilities.createData(program, fpa.toAddr(0x100), header.toDataType(), -1, false, ClearDataMode.CLEAR_ALL_UNDEFINED_CONFLICT_DATA); } catch (CodeUnitInsertionException e) { Msg.error(this, "Vectors mark conflict at 0x000100"); } }
load()
load()
setMessage()
TaskMonitor
, .
monitor.setMessage(String.format("%s : Start loading", getName()));
, :
@Override protected void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options, Program program, MemoryConflictHandler handler, TaskMonitor monitor, MessageLog log) throws CancelledException, IOException { monitor.setMessage(String.format("%s : Start loading", getName())); BinaryReader reader = new BinaryReader(provider, false); FlatProgramAPI fpa = new FlatProgramAPI(program, monitor); vectors = new VectorsTable(fpa, reader); header = new GameHeader(fpa, reader); createSegments(fpa, provider, program, monitor); markVectorsTable(program, fpa); markHeader(program, fpa); monitor.setMessage(String.format("%s : Loading done", getName())); }
getDefaultOptions validateOptions
,
Run
-> Debug As
-> 1 Ghidra
. .

GHIDRA
, - . Eclipse
extension.properties
, :
description=Loader for Sega Mega Drive / Genesis ROMs author=Dr. MefistO createdOn=20.03.2019
GhidraDev
-> Export
-> Ghidra Module Extension...
:



dist
zip- (- ghidra_9.0_PUBLIC_20190320_Sega.zip
) GHIDRA
.
.
, File
-> Install Extensions...
, , . ...


github- , .
: IDA
GHIDRA
. .