
Grüße, Genossen. Ich habe noch nichts von dem noch nicht Open-Source- GHIDRA
, wahrscheinlich nur einem taub / blind / dumm / kein Internet-Reverse Engineer. Die sofort einsatzbereiten Funktionen sind erstaunlich: Dekompilierer für alle unterstützten Prozessoren, einfaches Hinzufügen neuer Architekturen (mit sofortiger aktiver Dekompilierung aufgrund kompetenter Konvertierung in IR), eine Reihe von Skripten, die das Leben vereinfachen, die Möglichkeit des Undo
/ Redo
... Und dies ist nur ein sehr kleiner Teil aller bereitgestellten Funktionen. Zu sagen, dass ich beeindruckt war, bedeutet fast nichts zu sagen.
In diesem Artikel möchte ich Ihnen erzählen, wie ich mein erstes Modul für GHIDRA
- einen GHIDRA
für Spiele für Sega Mega Drive / Genesis
. Um es zu schreiben, brauchte ich ... nur ein paar Stunden! Lass uns gehen.
Aber was ist mit der IDA?Ich habe einige Tage damit verbracht, den Prozess des Schreibens von Downloadern für die IDA
verstehen. Dann war es anscheinend Version 6.5
, aber damals gab es viele Probleme mit der SDK-Dokumentation.
Wir bereiten die Entwicklungsumgebung vor
GHIDRA
Entwickler haben fast alles durchdacht ( Ilfak , wo waren Sie vorher?). Um die Implementierung der neuen Funktionalität zu vereinfachen, haben sie ein Plug-In für Eclipse
- GhidraDev
, das tatsächlich beim Schreiben von Code " hilft ". Das Plugin ist in die Entwicklungsumgebung integriert und ermöglicht es Ihnen, Projektvorlagen für Skripte, Loader, Prozessormodule und Erweiterungen für diese sowie Exportmodule (so wie ich es verstehe, ist dies eine Art Export von Daten aus einem Projekt) mit wenigen Klicks zu erstellen.
Um das Plugin zu installieren, laden Sie Eclipse
für Java
herunter, klicken Sie auf Help
-> Install New Software...
, klicken Sie auf die Schaltfläche Add
und öffnen Sie den Archivauswahldialog mit dem Plugin über die Schaltfläche Archive...
Das Archiv mit GhidraDev
befindet sich im $(GHIDRA)/Extensions/Eclipse/GhidraDev
. Wählen Sie es aus und klicken Sie auf die Schaltfläche Add
.

Legen Sie in der angezeigten Liste eine Morgendämmerung für Ghidra
, klicken Sie auf Next >
, stimmen Sie den Vereinbarungen zu, klicken Install Anyway
auf Trotzdem Install Anyway
(da das Plugin keine Signatur hat) und starten Sie Eclipse
.

Insgesamt wird im IDE- GhidraDev
ein neues GhidraDev
Element GhidraDev
, mit dem Sie Ihre Projekte bequem erstellen und verteilen können (natürlich können Sie es auch über den üblichen Assistenten für neue Eclipse
Projekte erstellen). Darüber hinaus haben wir die Möglichkeit, das entwickelte Plug-In oder Skript zu debuggen.

Und wo ist der Anwendungsdebugger?Was die Situation bei GHIDRA
wirklich wütend GHIDRA
sind die verdammt heiseren Hype-Artikel, die fast das gleiche Material enthalten, was im Übrigen nicht stimmt. Ein Beispiel? Ja, bitte:
Die aktuelle Version des Tools ist 9.0. Das Tool bietet Optionen für zusätzliche Funktionen wie Kryptoanalyse und Interaktion mit OllyDbg, dem Ghidra-Debugger.
Und wo ist das alles? Nein!
Der zweite Punkt: Offenheit. In der Tat ist es fast da, aber es ist praktisch nicht vorhanden. Die GHIDRA
enthält Quellcodes für Komponenten, die in Java
wurden. Wenn Sie sich jedoch Gradle
Skripte ansehen, können Sie feststellen, dass Abhängigkeiten von einer Reihe externer Projekte von geheimen bestehen Laboratorien NSA
Repositories.
Zum Zeitpunkt des Schreibens gibt es keine Dekompiler- und SLEIGH
(dies ist ein Dienstprogramm zum Kompilieren von Beschreibungen von Prozessormodulen und Konvertierungen in IR).
Na ja, ich bin von etwas abgelenkt.
Erstellen wir also ein neues Projekt in Eclipse
.
Erstellen Sie ein Loader-Projekt
GhidraDev
-> New
-> Ghidra Module Project...
GhidraDev
Ghidra Module Project...
Wir geben den Namen des Projekts an (wir berücksichtigen, dass Wörter vom Typ Loader
an die Dateinamen geklebt werden, und um so etwas wie sega_loaderLoader.java
nicht zu erhalten, sega_loaderLoader.java
wir sie entsprechend).

Klicken Sie auf Next >
. Hier stellen wir die Morgendämmerung vor die Kategorien, die wir brauchen. In meinem Fall ist dies nur ein Loader
. Klicken Sie auf Next >
.

Hier geben wir den Pfad zum Verzeichnis mit
. Klicken Sie auf Next >
.

GHIDRA
können GHIDRA
Skripte in Python schreiben (über Jython
). Ich werde in Java
schreiben, damit ich keine Morgenröte mache. Ich drücke auf Finish
.

Einen Code schreiben
Der leere Projektbaum sieht beeindruckend aus:

Alle Dateien mit java
Code befinden sich im Zweig /src/main/java
:

getName ()
Wählen Sie zunächst einen Namen für den Bootloader. Die Methode getName()
gibt getName()
zurück:
@Override public String getName() { return "Sega Mega Drive / Genesis Loader"; }
findSupportedLoadSpecs ()
Die Methode findSupportedLoadSpecs()
entscheidet (basierend auf den in der Binärdatei enthaltenen Daten), welches Prozessormodul für die Demontage (sowie in der IDA
) verwendet werden soll. In der GHIDRA
Terminologie GHIDRA
dies als Compiler Language
. Es enthält: Prozessor, Endianness, Bitness und Compiler (falls bekannt).
Diese Methode gibt eine Liste der unterstützten Architekturen und Sprachen zurück. Wenn die Daten im falschen Format vorliegen, geben wir einfach eine leere Liste zurück.
Im Fall von Sega Mega Drive
ist das Wort " SEGA
" am häufigsten am Offset 0x100
Headers vorhanden (dies ist keine Voraussetzung, wird aber in 99% der Fälle erfüllt). Sie müssen überprüfen, ob diese Zeile in der importierten Datei enthalten ist. Zu diesem ByteProvider provider
wird der ByteProvider provider
findSupportedLoadSpecs()
, mit der wir mit der Datei arbeiten.
Wir erstellen ein BinaryReader
Objekt, um das Lesen von Daten aus einer Datei zu BinaryReader
:
BinaryReader reader = new BinaryReader(provider, false);
Das false
Argument in diesem Fall weist auf die Verwendung von Big Endian
beim Lesen hin. Nun lesen wir die Zeile. Verwenden Sie dazu die Methode readAsciiString(offset, size)
des reader
Objekts:
reader.readAsciiString(0x100, 4).equals(new String("SEGA"))
Wenn equals()
true
zurückgibt, handelt es sich um Segov rum und in der Liste List<LoadSpec> loadSpecs = new ArrayList<>();
Es wird möglich sein, Motorola m68k
hinzuzufügen. Erstellen Sie dazu ein neues Objekt vom Typ LoadSpec
, dessen Konstruktor ein Loader-Objekt (in diesem Fall this
), ImageBase
, in das das ROM geladen wird, ein Objekt vom Typ LanguageCompilerSpecPair
und ein Flag LoadSpec
dieses LoadSpec
unter den anderen in der Liste LoadSpec
(ja, in der Liste) LoadSpec
Es kann mehr als eine LoadSpec
.
Das Konstruktorformat für LanguageCompilerSpecPair
wie folgt:
- Das erste Argument ist
languageID
, eine Zeichenfolge der Form " ProcessorName: Endianness: Bits: ExactCpu ". In meinem Fall sollte es die Zeile " 68000: BE: 32: MC68020 " sein (leider ist genau der MC68000
nicht im Lieferumfang enthalten, aber dies ist kein solches Problem). ExactCpu
möglicherweise default
- Das zweite Argument,
compilerSpecID
, um zu finden, was Sie hier angeben müssen, befindet sich im Verzeichnis mit den
Prozessorbeschreibungen ( $(GHIDRA)/Ghidra/Processors/68000/data/languages
) in der Datei 68000.opinion
. Wir sehen, dass hier nur die default
angegeben ist. Eigentlich geben wir es an
Als Ergebnis haben wir den folgenden Code (wie Sie sehen können, bisher nichts Kompliziertes):
@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; }
Der Unterschied zwischen IDA und GHIDRA in Bezug auf ModuleEs gibt einen Unterschied und es ist immer noch sehr stark. In GHIDRA
Sie ein Projekt schreiben, das unterschiedliche Architekturen, unterschiedliche Datenformate, einen Loader, ein Prozessormodul, eine Erweiterung der Decompiler-Funktionalität und andere Extras versteht.
In der IDA
ist dies ein separates Projekt für jeden Add-On-Typ.
Wie viel bequemer? Meiner Meinung nach bei GHIDRA
- manchmal!
load ()
Bei dieser Methode erstellen wir Segmente, verarbeiten Eingabedaten, erstellen Code und zuvor bekannte Beschriftungen. Dazu werden folgende Objekte eingereicht, um uns am Eingang zu helfen:
ByteProvider provider
: Wir kennen ihn bereits. Arbeiten mit BinärdateidatenLoadSpec loadSpec
: Angabe der Architektur, die während der findSupportedLoadSpecs
der Datei mit der Methode findSupportedLoadSpecs
. Es ist notwendig, wenn wir beispielsweise mit mehreren Datenformaten in einem Modul arbeiten können. BequemList<Option> options
: Liste der Optionen (einschließlich benutzerdefinierter Optionen). Ich habe noch nicht gelernt, mit ihnen zu arbeitenProgram program
: Das Hauptobjekt, das Zugriff auf alle erforderlichen Funktionen bietet: Auflistung, Adressraum, Segmente, Beschriftungen, Erstellen von Arrays usw.MemoryConflictHandler handler
und TaskMonitor monitor
: Wir müssen selten direkt mit ihnen arbeiten (normalerweise übergeben wir diese Objekte nur an vorgefertigte Methoden).MessageLog log
: Logger selbst
Lassen Sie uns zunächst einige Objekte erstellen, die unsere Arbeit mit GHIDRA
Entitäten und vorhandenen Daten vereinfachen. Natürlich brauchen wir auf jeden Fall BinaryReader
:
BinaryReader reader = new BinaryReader(provider, false);
Weiter. Es wird für uns sehr nützlich sein und fast das gesamte Objekt der FlatProgramAPI
Klasse vereinfachen (Sie werden später sehen, was Sie damit tun können):
FlatProgramAPI fpa = new FlatProgramAPI(program, monitor);
Rum Überschrift
Zunächst werden wir bestimmen, wie die Überschrift eines gewöhnlichen Sega-Rums lautet. In den ersten 0x100
Bytes gibt es eine Tabelle mit 64 DWORD-Zeigern auf Vektoren, zum Beispiel: Reset
, Trap
, DivideByZero
, VBLANK
und andere.
Als nächstes folgt die Struktur mit dem Namen der Roma, Regionen, Adressen des Anfangs und Endes der ROM
und RAM
Blöcke, dem Kontrollkästchen (das Feld wird auf Anfrage der Entwickler und nicht dem Präfix aktiviert) und anderen Informationen.
Erstellen wir java
Klassen, um mit diesen Strukturen zu arbeiten und die Datentypen zu implementieren, die der Liste der Strukturen hinzugefügt werden.
VectorsTable
Wir erstellen eine neue Klasse VectorsTable
und weisen darauf hin, dass sie die StructConverter
Schnittstelle implementiert. In dieser Klasse speichern wir die Adressen von Vektoren (für die zukünftige Verwendung) und deren Namen.

Wir deklarieren eine Liste von Vektornamen und deren Nummer:
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" };
Wir erstellen eine separate Klasse zum Speichern der Adresse und des Vektornamens:
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; } }
Die Liste der Vektoren wird im Vektorarray gespeichert:
private VectorFunc[] vectors;
Der Konstruktor für VectorsTable
akzeptiert:
FlatProgramAPI fpa
zum Konvertieren long
Adressen in den Address
von Hydra (tatsächlich ergänzt dieser Datentyp den einfachen numerischen Wert der Adresse, indem er sie an einen anderen Chip bindet - den Adressraum).BinaryReader reader
- Lesehöfe
Das fpa
Objekt hat eine toAddr()
-Methode, und der reader
hat setPointerIndex()
und readNextUnsignedInt()
. Grundsätzlich ist nichts mehr erforderlich. Wir bekommen den Code:
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]); } }
Die toDataType()
-Methode, die wir überschreiben müssen, um die Struktur zu implementieren, sollte ein Structure
, in dem die Namen der Strukturfelder, ihre Größen und Kommentare zu jedem Feld deklariert werden sollten (Sie können 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; }
Lassen Sie uns Methoden implementieren, um jeden der Vektoren oder die gesamte Liste (eine Reihe von Boilerplate-Code) abzurufen:
Andere Methoden 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]; }
Wir werden dasselbe tun und eine GameHeader
Klasse erstellen, die die StructConverter
Schnittstelle implementiert.
Game Rum Header StrukturOffset starten | Endversatz | Beschreibung |
---|
100 $ | $ 10F | Konsolenname (normalerweise 'SEGA MEGA DRIVE' oder 'SEGA GENESIS') |
$ 110 | $ 11F | Erscheinungsdatum (normalerweise '© XXXX YYYY.MMM', wobei XXXX der Buchungskreis ist, YYYY das Jahr und MMM - Monat) |
120 $ | $ 14F | Inländischer Name |
$ 150 | $ 17F | Internationaler Name |
$ 180 | $ 18D | Version ('XX JJJJJJJJJJ' 'wobei XX der Spieltyp und JJ der Spielcode ist) |
$ 18E | $ 18F | Prüfsumme |
190 $ | $ 19F | E / A-Unterstützung |
$ 1A0 | $ 1A3 | ROM-Start |
$ 1A4 | $ 1A7 | ROM-Ende |
$ 1A8 | $ 1AB | RAM-Start (normalerweise $ 00FF0000) |
$ 1AC | $ 1AF | RAM-Ende (normalerweise $ 00FFFFFF) |
$ 1B0 | $ 1B2 | 'RA' und $ F8 aktivieren SRAM |
$ 1B3 | ---- | unbenutzt ($ 20) |
$ 1B4 | $ 1B7 | SRAM-Start (Standard $ 00200000) |
$ 1B8 | $ 1BB | SRAM-Ende (Standard $ 0020FFFF) |
$ 1BC | $ 1FF | Notizen (unbenutzt) |
Wir richten die Felder ein, prüfen, ob die Eingabedaten ausreichend lang sind, verwenden die beiden neuen Methoden readNextByteArray()
, readNextUnsignedShort()
des reader
Objekts, um Daten zu lesen, und erstellen eine Struktur. Der resultierende Code lautet wie folgt:
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; } }
Erstellen Sie Objekte für den Header:
vectors = new VectorsTable(fpa, reader); header = new GameHeader(fpa, reader);
Segmente
Sega hat eine bekannte Karte von Speicherbereichen, die ich hier vielleicht nicht in Form einer Tabelle angeben werde, sondern nur den Code, der zum Erstellen der Segmente verwendet wird.
Das Objekt der FlatProgramAPI
Klasse verfügt also über eine createMemoryBlock()
-Methode, mit der bequem Speicherbereiche erstellt werden können. Bei der Eingabe werden die folgenden Argumente verwendet:
name
: Name der Regionaddress
: Startadresse der Regionstream
: Ein Objekt vom Typ InputStream
, das die Basis für die Daten im Speicherbereich bildet. Wenn Sie null
angeben, wird ein nicht initialisierter Bereich erstellt (für 68K RAM
oder Z80 RAM
benötigen wir genau dassize
: Größe der erstellten RegionisOverlay
: isOverlay
true
oder false
und zeigt an, dass der Speicherbereich überlagert ist. Wo es neben ausführbaren Dateien benötigt wird
In der Ausgabe gibt createMemoryBlock()
ein Objekt vom Typ MemoryBlock
, für das Sie optional Zugriffsrechte-Flags ( Read
, Write
, Execute
) setzen können.
Als Ergebnis erhalten wir eine Funktion der folgenden Form:
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)); } }
Hier haben wir zusätzlich die statische error
der Msg
Klasse aufgerufen, um eine Fehlermeldung anzuzeigen.
Ein Segment mit 0x3FFFFF
kann eine maximale Größe von 0x3FFFFF
(alles andere gehört bereits zu anderen Regionen). Lassen Sie es uns erstellen:
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
. .