
Saludos, camaradas. No he oído hablar del GHIDRA
aún no de código GHIDRA
, probablemente solo un ingeniero inverso sordo / ciego / tonto / sin Internet. Sus capacidades listas para usar son increíbles: descompiladores para todos los procesadores compatibles, simple adición de nuevas arquitecturas (con descompilación activa inmediata debido a la conversión competente a IR), un montón de scripts que simplifican la vida, la posibilidad de Undo
/ Redo
... Y esto es solo una parte muy pequeña de todas las características proporcionadas. Decir que me impresionó es no decir casi nada.
Entonces, en este artículo, me gustaría contarles cómo escribí mi primer módulo para GHIDRA
, un cargador de ron para juegos para Sega Mega Drive / Genesis
. Para escribirlo necesitaba ... ¡solo un par de horas! Vamos
¿Pero qué hay de la IDA?Pasé algunos días entendiendo el proceso de escribir descargadores para la IDA
. Entonces parecía que era la versión 6.5
, pero en aquellos días había muchos problemas con la documentación del SDK.
Preparamos el entorno de desarrollo.
GHIDRA
desarrolladores de GHIDRA
pensaron en casi todo ( Ilfak , ¿dónde has estado antes?). Y, para simplificar la implementación de la nueva funcionalidad, desarrollaron un complemento para Eclipse
: GhidraDev
, que en realidad " ayuda " a escribir código. El complemento está integrado en el entorno de desarrollo y le permite crear plantillas de proyecto para scripts, cargadores, módulos de procesador y extensiones para ellos, así como exportar módulos (según tengo entendido, esto es algún tipo de exportación de datos de un proyecto) con unos pocos clics.
Para instalar el complemento, descargue Eclipse
para Java
, haga clic en Help
-> Install New Software...
, luego haga clic en el botón Add
y abra el cuadro de diálogo de selección de archivo con el complemento usando el botón Archive...
El archivo con GhidraDev
se encuentra en el $(GHIDRA)/Extensions/Eclipse/GhidraDev
. Selecciónelo, haga clic en el botón Add
.

En la lista que aparece, ponga un Ghidra
a Ghidra
, haga clic en Next >
, acepte los acuerdos, haga clic en Install Anyway
(porque el complemento no tiene una firma) y reinicie Eclipse
.

En total, aparecerá un nuevo elemento de GhidraDev
en el GhidraDev
IDE para la creación y distribución conveniente de sus proyectos (por supuesto, también puede crear a través del asistente habitual para nuevos proyectos de Eclipse
). Además, tenemos la oportunidad de depurar el complemento o script desarrollado.

¿Y dónde está el depurador de aplicaciones?Lo que realmente enfurece la situación con GHIDRA
son los jodidos artículos de publicidad ronca que contienen casi el mismo material, lo que, además, no es cierto. Un ejemplo? Sí, por favor:
La versión actual de la herramienta es 9.0. y la herramienta tiene opciones para incluir funcionalidades adicionales como Criptoanálisis, interacción con OllyDbg, el depurador de Ghidra.
¿Y dónde está todo esto? No!
El segundo punto: apertura. De hecho, está casi allí, pero es prácticamente inexistente. La GHIDRA
contiene códigos fuente para componentes que se escribieron en Java
, pero si Gradle
scripts de Gradle
, puede ver que hay dependencias en un montón de proyectos externos de los secretos. laboratorios Repositorios de la NSA
.
Al momento de escribir, no hay SLEIGH
descompilador y SLEIGH
(esta es una utilidad para compilar descripciones de módulos de procesador y conversiones a IR).
Bueno, bueno, estoy distraído por algo.
Entonces, creemos un nuevo proyecto en Eclipse
.
Crear un proyecto de cargador
GhidraDev
-> New
-> Ghidra Module Project...
Indicamos el nombre del proyecto (tenemos en cuenta que las palabras del tipo Loader
se pegarán a los nombres de los archivos, y para no obtener algo como sega_loaderLoader.java
, los sega_loaderLoader.java
en consecuencia).

Haga clic en Next >
. Aquí ponemos daws delante de las categorías que necesitamos. En mi caso, esto es solo un Loader
. Haga clic en Next >
.

Aquí indicamos la ruta al directorio con
. Haga clic en Next >
.

GHIDRA
permite escribir scripts en python (a través de Jython
). Escribiré en Java
, así que no pongo un daw. Presiono Finish
.

Escribir un código
El árbol del proyecto vacío se ve impresionante:

Todos los archivos con código java
están en la rama /src/main/java
:

getName ()
Para comenzar, elija un nombre para el gestor de arranque. El método getName()
devuelve:
@Override public String getName() { return "Sega Mega Drive / Genesis Loader"; }
findSupportedLoadSpecs ()
El método findSupportedLoadSpecs()
decide (en base a los datos contenidos en el archivo binario) qué módulo de procesador debe usarse para el desmontaje (así como en el IDA
). En la terminología de GHIDRA
esto se llama Compiler Language
. Incluye: procesador, endianness, bitness y compilador (si se conoce).
Este método devuelve una lista de arquitecturas e idiomas compatibles. Si los datos están en el formato incorrecto, simplemente devolvemos una lista vacía.
Entonces, en el caso de Sega Mega Drive
, la palabra " SEGA
" suele estar presente en el desplazamiento 0x100
encabezado (esto no es un requisito previo, pero se cumple en el 99% de los casos). Debe verificar si esta línea está en el archivo importado. Para hacer esto, el ByteProvider provider
se ByteProvider provider
findSupportedLoadSpecs()
, con la que trabajaremos con el archivo.
Creamos un objeto BinaryReader
, para la conveniencia de leer datos de un archivo:
BinaryReader reader = new BinaryReader(provider, false);
El argumento false
en este caso indica el uso de Big Endian
al leer. Ahora leamos la línea. Para hacer esto, use el readAsciiString(offset, size)
del objeto reader
:
reader.readAsciiString(0x100, 4).equals(new String("SEGA"))
Si equals()
devuelve true
, entonces estamos tratando con ron Segov, y en la lista List<LoadSpec> loadSpecs = new ArrayList<>();
será posible agregar Motorola m68k
. Para hacer esto, cree un nuevo objeto de tipo LoadSpec
, LoadSpec
constructor acepte un objeto de cargador (en este caso, this
), ImageBase
, en el que se cargará la ROM, un objeto de tipo LanguageCompilerSpecPair
y una bandera: este LoadSpec
entre los demás en la lista (sí, en la lista puede haber más de una LoadSpec
).
El formato del constructor para LanguageCompilerSpecPair
siguiente:
- El primer argumento es
languageID
, una cadena de la forma " ProcessorName: Endianness: Bits: ExactCpu ". En mi caso, debería ser la línea " 68000: BE: 32: MC68020 " (desafortunadamente, el MC68000
no está incluido en la entrega, pero esto no es un problema). ExactCpu
puede ser default
- El segundo argumento,
compilerSpecID
, para encontrar lo que necesita especificar aquí, se puede encontrar en el directorio con las descripciones del procesador
( $(GHIDRA)/Ghidra/Processors/68000/data/languages
) en el archivo 68000.opinion
. Vemos que aquí solo se indica el default
. En realidad, lo indicamos
Como resultado, tenemos el siguiente código (como puede ver, nada complicado hasta ahora):
@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; }
La diferencia entre IDA y GHIDRA en términos de módulosHay una diferencia, y todavía es muy fuerte. En GHIDRA
puede escribir un proyecto que comprenda diferentes arquitecturas, diferentes formatos de datos, sea un cargador, un módulo de procesador, la expansión de la funcionalidad del descompilador y otras ventajas.
En IDA
, este es un proyecto separado para cada tipo de complemento.
¿Cuánto más conveniente? En mi opinión, en GHIDRA
, ¡a veces!
carga ()
En este método, crearemos segmentos, procesaremos datos de entrada, crearemos código y etiquetas previamente conocidas. Para hacer esto, los siguientes objetos se envían para ayudarnos en la entrada:
ByteProvider provider
: ya lo conocemos. Trabajando con datos de archivos binariosLoadSpec loadSpec
: especificación de la arquitectura que fue seleccionada durante la fase de importación del archivo usando el método findSupportedLoadSpecs
. Es necesario si, por ejemplo, podemos trabajar con varios formatos de datos en un módulo. ConvenienteList<Option> options
: lista de opciones (incluida la personalizada). Todavía no he aprendido a trabajar con ellos.Program program
: el objeto principal que proporciona acceso a toda la funcionalidad necesaria: listado, espacio de direcciones, segmentos, etiquetas, creación de matrices, etc.TaskMonitor monitor
y TaskMonitor monitor
: rara vez tenemos que trabajar directamente con ellos (por lo general, simplemente pasando estos objetos a métodos ya preparados)MessageLog log
: registrador en sí
Entonces, para empezar, GHIDRA
algunos objetos que simplificarán nuestro trabajo con GHIDRA
entidades de GHIDRA
y los datos existentes. Por supuesto, definitivamente necesitaremos BinaryReader
:
BinaryReader reader = new BinaryReader(provider, false);
Siguiente Será muy útil para nosotros y simplificará casi todo el objeto de la clase FlatProgramAPI
(verá más adelante qué puede hacer con él):
FlatProgramAPI fpa = new FlatProgramAPI(program, monitor);
Rumbo
Para empezar, determinaremos cuál es el título de un ron Sega ordinario. En los primeros 0x100
bytes hay una tabla de 64 punteros DWORD a vectores, por ejemplo: Reset
, Trap
, DivideByZero
, VBLANK
y otros.
Luego viene la estructura con el nombre de los romaníes, las regiones, las direcciones del principio y el final de los bloques ROM
y RAM
, la casilla de verificación (el campo se marca a petición de los desarrolladores, y no el prefijo) y otra información.
java
clases de java
para trabajar con estas estructuras, así como para implementar los tipos de datos que se agregarán a la lista de estructuras.
VectoresTabla
Creamos una nueva clase VectorsTable
y, atención, indicamos que implementa la interfaz StructConverter
. En esta clase almacenaremos las direcciones de los vectores (para uso futuro) y sus nombres.

Declaramos una lista de nombres de vectores y su 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" };
Creamos una clase separada para almacenar la dirección y el nombre del vector:
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; } }
La lista de vectores se almacenará en la matriz de vectors
:
private VectorFunc[] vectors;
El constructor de VectorsTable
aceptará:
FlatProgramAPI fpa
para convertir direcciones long
al tipo de datos de Address
de Hydra (de hecho, este tipo de datos complementa el valor numérico simple de la dirección al vincularla a otro chip: el espacio de direcciones)BinaryReader reader
- yardas de lectura
El objeto toAddr()
tiene un método toAddr()
, y el reader
tiene setPointerIndex()
y readNextUnsignedInt()
. En principio, no se requiere nada más. Obtenemos el 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]); } }
El método toDataType()
, que debemos anular para implementar la estructura, debe devolver un objeto Structure
en el que se deben declarar los nombres de los campos de estructura, sus tamaños y comentarios en cada campo (puede 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; }
Bueno, implementemos métodos para obtener cada uno de los vectores, o la lista completa (un montón de código repetitivo):
Otros 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]; }
Haremos lo mismo y crearemos una clase GameHeader
que implemente la interfaz StructConverter
.
Estructura de encabezado de ron de juegoIniciar desplazamiento | Fin de desplazamiento | Descripción |
---|
$ 100 | $ 10F | Nombre de la consola (generalmente 'SEGA MEGA DRIVE' o 'SEGA GENESIS') |
$ 110 | $ 11F | Fecha de lanzamiento (generalmente '© XXXX AAAA.MMM' donde XXXX es el código de la compañía, AAAA es el año y MMM - mes) |
$ 120 | $ 14F | Nombre nacional |
$ 150 | $ 17F | Nombre internacional |
$ 180 | $ 18D | Versión ('XX YYYYYYYYYYYY' donde XX es el tipo de juego y YY el código del juego) |
$ 18E | $ 18F | Suma de comprobación |
$ 190 | $ 19F | Soporte de E / S |
$ 1A0 | $ 1A3 | Inicio ROM |
$ 1A4 | $ 1A7 | ROM final |
$ 1A8 | $ 1AB | Inicio de RAM (generalmente $ 00FF0000) |
$ 1AC | $ 1AF | Fin de RAM (generalmente $ 00FFFFFF) |
$ 1B0 | $ 1B2 | 'RA' y $ F8 habilita SRAM |
$ 1B3 | ---- | sin usar ($ 20) |
$ 1B4 | $ 1B7 | Inicio de SRAM (predeterminado $ 00200000) |
$ 1B8 | $ 1BB | Fin de SRAM (predeterminado $ 0020FFFF) |
$ 1BC | $ 1FF | Notas (sin usar) |
Configuramos los campos, verificamos una longitud suficiente de los datos de entrada, usamos los dos nuevos métodos para readNextByteArray()
, readNextUnsignedShort()
del objeto reader
para leer datos y crear una estructura. El código resultante es el siguiente:
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; } }
Crear objetos para el encabezado:
vectors = new VectorsTable(fpa, reader); header = new GameHeader(fpa, reader);
Segmentos
Sega tiene un mapa bien conocido de regiones de memoria, que, quizás, no daré aquí en forma de tabla, pero solo daré el código que se usa para crear los segmentos.
Entonces, el objeto de la clase FlatProgramAPI
tiene un método createMemoryBlock()
, con el cual es conveniente crear regiones de memoria. En la entrada, toma los siguientes argumentos:
name
: nombre de la regiónaddress
: dirección de inicio de la regiónstream
: un objeto de tipo InputStream
que será la base de los datos en la región de memoria. Si especifica null
, se creará una región no inicializada (por ejemplo, para 68K RAM
o Z80 RAM
necesitaremos solo esosize
: tamaño de la región creadaisOverlay
: acepta true
o false
, e indica que la región de memoria está superpuesta. Donde se necesita además de ejecutables
En la salida, createMemoryBlock()
devuelve un objeto de tipo MemoryBlock
, al que opcionalmente puede establecer banderas de derechos de acceso ( Read
, Write
, Execute
).
Como resultado, obtenemos una función de la siguiente 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)); } }
Aquí también llamamos al método de error
estático de la clase Msg
para mostrar un mensaje de error.
Un segmento que contiene ron de juego puede tener un tamaño máximo de 0x3FFFFF
(todo lo demás ya pertenecerá a otras regiones). Vamos a crearlo:
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
. .