├── Module.manifest
├── ghidra_scripts
├── README.txt
└── src
│ └── Test6502.java
├── src
├── main
│ ├── resources
│ │ └── images
│ │ │ └── README.txt
│ ├── java
│ │ └── ik
│ │ │ └── ghidranesrom
│ │ │ ├── util
│ │ │ ├── MemoryBlockType.java
│ │ │ ├── BrandedAddress.java
│ │ │ ├── AddressSpaceUtil.java
│ │ │ ├── Constants.java
│ │ │ └── MemoryBlockDescription.java
│ │ │ ├── loader
│ │ │ ├── exception
│ │ │ │ ├── InvalidNesRomHeaderException.java
│ │ │ │ ├── NesRomEofException.java
│ │ │ │ ├── UnimplementedNesMapperException.java
│ │ │ │ └── NesRomException.java
│ │ │ ├── mapper
│ │ │ │ ├── NesMapper.java
│ │ │ │ └── NromMapper.java
│ │ │ ├── NesRom.java
│ │ │ ├── NesRomHeader.java
│ │ │ ├── GhidraNesLoaderHelper.java
│ │ │ └── GhidraNesLoader.java
│ │ │ ├── wrappers
│ │ │ ├── SymbolTagStorage.java
│ │ │ ├── OperandObjectWrapper.java
│ │ │ ├── BrandedSymbolWrapper.java
│ │ │ ├── InstructionWrapper.java
│ │ │ ├── ProgramWrapper.java
│ │ │ ├── LoopWrapper.java
│ │ │ └── SymbolWrapper.java
│ │ │ └── analyzer
│ │ │ ├── InMemorySymbolTagStorage.java
│ │ │ ├── Worker.java
│ │ │ └── GhidraNesAnalyzer.java
│ └── help
│ │ └── help
│ │ ├── topics
│ │ └── ghidranes
│ │ │ └── help.html
│ │ ├── TOC_Source.xml
│ │ └── shared
│ │ └── Frontpage.css
├── test
│ └── java
│ │ └── README.test.txt
└── script
│ ├── sh
│ ├── find_java.sh
│ ├── run_docker.sh
│ └── inside_docker_build.sh
│ ├── java
│ └── IkScript.java
│ └── generate_script.py
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .github
├── screenshots
│ └── ghidra-nes.png
└── workflows
│ └── gradle.yml
├── lib
└── README.txt
├── data
├── languages
│ ├── nes-rom.opinion
│ ├── 6502NES.ldefs
│ ├── 6502NES.pspec
│ ├── 6502NES.cspec
│ └── 6502NES.slaspec
├── README.txt
└── build.xml
├── os
├── linux64
│ └── README.txt
├── osx64
│ └── README.txt
└── win64
│ └── README.txt
├── .gitignore
├── README.md
├── gradlew.bat
└── gradlew
/Module.manifest:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ghidra_scripts/README.txt:
--------------------------------------------------------------------------------
1 | Java source directory to hold module-specific Ghidra scripts.
2 |
--------------------------------------------------------------------------------
/src/main/resources/images/README.txt:
--------------------------------------------------------------------------------
1 | The "src/resources/images" directory is intended to hold all image/icon files used by
2 | this module.
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilyakharlamov/Ghidra-Nes-Rom-Decompiler-Plugin/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.github/screenshots/ghidra-nes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilyakharlamov/Ghidra-Nes-Rom-Decompiler-Plugin/HEAD/.github/screenshots/ghidra-nes.png
--------------------------------------------------------------------------------
/src/test/java/README.test.txt:
--------------------------------------------------------------------------------
1 | The "test" directory is intended to hold unit test cases. The package structure within
2 | this folder should correspond to that found in the "src" folder.
3 |
--------------------------------------------------------------------------------
/lib/README.txt:
--------------------------------------------------------------------------------
1 | The "lib" directory is intended to hold Jar files which this module
2 | is dependent upon. This directory may be eliminated from a specific
3 | module if no other Jar files are needed.
4 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/util/MemoryBlockType.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.util;
2 |
3 | public enum MemoryBlockType {
4 | INITIALIZED,
5 | UNINITIALIZED,
6 | BIT_MAPPED,
7 | BYTE_MAPPED,
8 | }
9 |
--------------------------------------------------------------------------------
/src/script/sh/find_java.sh:
--------------------------------------------------------------------------------
1 | set -Eeu
2 |
3 | find "$GHIDRA_INSTALL_DIR" -type f -name '*.jar' | while read -r jarfpath
4 | do
5 | unzip -l "$jarfpath" | awk "NR==1{J=\$0} /$1/{print J;print}"
6 | done
7 |
--------------------------------------------------------------------------------
/data/languages/nes-rom.opinion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/os/linux64/README.txt:
--------------------------------------------------------------------------------
1 | The "os/linux64" directory is intended to hold Linux native binaries
2 | which this module is dependent upon. This directory may be eliminated for a specific
3 | module if native binaries are not provided for the corresponding platform.
4 |
--------------------------------------------------------------------------------
/os/osx64/README.txt:
--------------------------------------------------------------------------------
1 | The "os/osx64" directory is intended to hold macOS (OS X) native binaries
2 | which this module is dependent upon. This directory may be eliminated for a specific
3 | module if native binaries are not provided for the corresponding platform.
4 |
--------------------------------------------------------------------------------
/os/win64/README.txt:
--------------------------------------------------------------------------------
1 | The "os/win64" directory is intended to hold MS Windows native binaries (.exe)
2 | which this module is dependent upon. This directory may be eliminated for a specific
3 | module if native binaries are not provided for the corresponding platform.
4 |
--------------------------------------------------------------------------------
/src/script/sh/run_docker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | docker run -it \
3 | --mount type=bind,source=$(pwd),target=/mnt \
4 | --mount type=bind,source="$(echo ~/.ghidra/)",target=/tilda_dot_ghidra \
5 | --mount type=bind,source=/Applications,target=/Applications \
6 | ghidra:run /bin/bash
7 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/exception/InvalidNesRomHeaderException.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.loader.exception;
2 |
3 | public class InvalidNesRomHeaderException extends NesRomException {
4 | public InvalidNesRomHeaderException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/exception/NesRomEofException.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.loader.exception;
2 |
3 | public class NesRomEofException extends NesRomException {
4 | public NesRomEofException() {
5 | super("Encountered unexpected EOF when reading NES ROM");
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/exception/UnimplementedNesMapperException.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.loader.exception;
2 |
3 | public class UnimplementedNesMapperException extends NesRomException {
4 | public UnimplementedNesMapperException(int mapperNum) {
5 | super("Tried to load ROM with unimplemented mapper " + mapperNum);
6 | }
7 | }
--------------------------------------------------------------------------------
/src/script/sh/inside_docker_build.sh:
--------------------------------------------------------------------------------
1 | find build -type f -name '*.zip' | xargs -I ^ rm '^'
2 | export GHIDRA_INSTALL_DIR=/Applications/ghidra_10.0.3_PUBLIC/
3 | gradle assemble
4 | find build -type f -name '*.zip' | xargs -I ^ unzip -c ^ mnt/extension.properties
5 | find build -type f -name '*.zip' | xargs -I ^ cp ^ "$GHIDRA_INSTALL_DIR/Extensions/Ghidra/"
6 | cp build/libs/mnt.jar /tilda_dot_ghidra/.ghidra_10.0.3_PUBLIC/Extensions/mnt/lib/mnt.jar
7 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/wrappers/SymbolTagStorage.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.wrappers;
2 |
3 | import com.google.common.collect.ImmutableSet;
4 | import ghidra.program.model.address.Address;
5 | import ghidra.program.model.symbol.Symbol;
6 |
7 | import java.util.Set;
8 |
9 | public interface SymbolTagStorage {
10 | void add(Symbol symbol, String name);
11 |
12 | ImmutableSet getAll(Symbol symbol);
13 |
14 | ImmutableSet keys();
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/util/BrandedAddress.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.util;
2 |
3 | public class BrandedAddress {
4 | private final int address;
5 | private final int size;
6 | private final String name;
7 |
8 | public BrandedAddress(int address, int size, String name) {
9 | this.address = address;
10 | this.size = size;
11 | this.name = name;
12 | }
13 |
14 | public String getName() {
15 | return name;
16 | }
17 |
18 | public long getSize() {
19 | return size;
20 | }
21 |
22 | public int getAddr() {
23 | return address;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/exception/NesRomException.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.loader.exception;
2 |
3 | public class NesRomException extends Exception {
4 | public NesRomException() {
5 | super();
6 | }
7 |
8 | public NesRomException(String message) {
9 | super(message);
10 | }
11 |
12 | public NesRomException(Throwable cause) {
13 | super(cause);
14 | }
15 |
16 | public NesRomException(String message, Throwable cause) {
17 | super(message, cause);
18 | }
19 |
20 | public NesRomException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
21 | super(message, cause, enableSuppression, writableStackTrace);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/data/languages/6502NES.ldefs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 | 6502 Microcontroller Family with (Disassembly Improvements)
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/util/AddressSpaceUtil.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.util;
2 |
3 | import ghidra.program.model.address.Address;
4 | import ghidra.program.model.address.AddressSpace;
5 | import ghidra.program.model.mem.Memory;
6 | import ghidra.program.model.mem.MemoryAccessException;
7 |
8 | /**
9 | *
10 | */
11 | public class AddressSpaceUtil {
12 | public static Address getLittleEndianAddress(final AddressSpace addressSpace, final Memory memory, final Address irqAddress) throws MemoryAccessException {
13 | byte irqLo = memory.getByte(irqAddress);
14 | byte irqHi = memory.getByte(irqAddress.add(1));
15 | long irq = (Byte.toUnsignedLong(irqHi) << 8) | Byte.toUnsignedLong(irqLo);
16 | return addressSpace.getAddress(irq);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/wrappers/OperandObjectWrapper.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.wrappers;
2 |
3 | import ghidra.program.model.address.GenericAddress;
4 | import ghidra.program.model.listing.CodeUnit;
5 | import ghidra.program.model.listing.Program;
6 |
7 | public class OperandObjectWrapper {
8 |
9 | private final GenericAddress address;
10 | private final Program program;
11 |
12 | public OperandObjectWrapper(GenericAddress addr, Program program) {
13 | this.address = addr;
14 | this.program = program;
15 | }
16 |
17 | public CodeUnit get() {
18 | return program.getListing().getCodeUnitAt(address);
19 | }
20 |
21 | public String getLabel() {
22 | return program.getListing().getCodeUnitAt(address).getLabel();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/wrappers/BrandedSymbolWrapper.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.wrappers;
2 |
3 | import ghidra.program.model.symbol.Reference;
4 | import ghidra.program.model.symbol.Symbol;
5 | import ik.ghidranesrom.util.BrandedAddress;
6 |
7 | import java.util.Arrays;
8 | import java.util.stream.Stream;
9 |
10 | public class BrandedSymbolWrapper {
11 | private final BrandedAddress addr;
12 | private final Symbol symbol;
13 |
14 | BrandedSymbolWrapper(BrandedAddress addr, Symbol symbol) {
15 | this.addr = addr;
16 | this.symbol = symbol;
17 | }
18 |
19 | public String getName() {
20 | return addr.getName();
21 | }
22 |
23 | public Stream streamReferences() {
24 | return Arrays.stream(symbol.getReferences());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/data/languages/6502NES.pspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/data/README.txt:
--------------------------------------------------------------------------------
1 | The "data" directory is intended to hold data files that will be used by this module and will
2 | not end up in the .jar file, but will be present in the zip or tar file. Typically, data
3 | files are placed here rather than in the resources directory if the user may need to edit them.
4 |
5 | An optional data/languages directory can exist for the purpose of containing various Sleigh language
6 | specification files and importer opinion files.
7 |
8 | The data/build.xml is used for building the contents of the data/languages directory.
9 |
10 | The skel language definition has been commented-out within the skel.ldefs file so that the
11 | skeleton language does not show-up within Ghidra.
12 |
13 | See the Sleigh language documentation (docs/languages/sleigh.htm or sleigh.pdf) for details
14 | on Sleigh language specification syntax.
15 |
16 |
--------------------------------------------------------------------------------
/data/languages/6502NES.cspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/main/help/help/topics/ghidranes/help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
12 | Skeleton Help File for a Module
13 |
14 |
15 |
16 |
17 |
Skeleton Help File for a Module
18 |
19 |
This is a simple skeleton help topic. For a better description of what should and should not
20 | go in here, see the "sample" Ghidra extension in the Extensions/Ghidra directory, or see your
21 | favorite help topic. In general, language modules do not have their own help topics.
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/mapper/NesMapper.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.loader.mapper;
2 |
3 | import ghidra.framework.store.LockException;
4 | import ghidra.program.model.address.AddressOverflowException;
5 | import ghidra.program.model.listing.Program;
6 | import ghidra.program.model.mem.MemoryConflictException;
7 | import ghidra.util.exception.CancelledException;
8 | import ghidra.util.exception.DuplicateNameException;
9 | import ghidra.util.task.TaskMonitor;
10 | import ik.ghidranesrom.loader.NesRom;
11 | import ik.ghidranesrom.loader.exception.UnimplementedNesMapperException;
12 |
13 | public abstract class NesMapper {
14 | public abstract void updateMemoryMapForRom(NesRom rom, Program program, TaskMonitor monitor) throws LockException, MemoryConflictException, AddressOverflowException, CancelledException, DuplicateNameException;
15 |
16 | public static NesMapper getMapper(int mapperNum) throws UnimplementedNesMapperException {
17 | switch (mapperNum) {
18 | case 0:
19 | return new NromMapper();
20 | default:
21 | throw new UnimplementedNesMapperException(mapperNum);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/NesRom.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.loader;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | import ik.ghidranesrom.loader.exception.NesRomEofException;
7 |
8 | public class NesRom {
9 | public NesRomHeader header;
10 | byte[] trainerBytes;
11 | public byte[] prgRom;
12 | byte[] chrRom;
13 |
14 | public NesRom(NesRomHeader romHeader, InputStream bytes) throws NesRomEofException, IOException {
15 | if (romHeader.hasTrainer) {
16 | trainerBytes = bytes.readNBytes(512);
17 | if (trainerBytes.length < 512) {
18 | throw new NesRomEofException();
19 | }
20 | }
21 | else {
22 | trainerBytes = new byte[0];
23 | }
24 |
25 | byte[] prgRomBytes = bytes.readNBytes(romHeader.prgRomSizeBytes);
26 | if (prgRomBytes.length < romHeader.prgRomSizeBytes) {
27 | throw new NesRomEofException();
28 | }
29 |
30 | byte[] chrRomBytes = bytes.readNBytes(romHeader.chrRomSizeBytes);
31 | if (chrRomBytes.length < romHeader.chrRomSizeBytes) {
32 | throw new NesRomEofException();
33 | }
34 |
35 | this.header = romHeader;
36 | this.prgRom = prgRomBytes;
37 | this.chrRom = chrRomBytes;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .metadata
3 | bin/
4 | tmp/
5 | .idea
6 | *.tmp
7 | *.bak
8 | *.swp
9 | *.iml
10 | *.swp
11 | *~.nib
12 | local.properties
13 | .settings/
14 | .loadpath
15 | .recommenders
16 |
17 | ghidra_scripts
18 |
19 | # Unnecessary Eclipse configuration
20 | .antProperties.xml
21 | .classpath
22 | .project
23 | .gradle
24 | build
25 | dist
26 |
27 | # External tool builders
28 | .externalToolBuilders/
29 |
30 | # Locally stored "Eclipse launch configurations"
31 | *.launch
32 |
33 | # PyDev specific (Python IDE for Eclipse)
34 | *.pydevproject
35 |
36 | # CDT-specific (C/C++ Development Tooling)
37 | .cproject
38 |
39 | # CDT- autotools
40 | .autotools
41 |
42 | # Java annotation processor (APT)
43 | .factorypath
44 |
45 | # PDT-specific (PHP Development Tools)
46 | .buildpath
47 |
48 | # sbteclipse plugin
49 | .target
50 |
51 | # Tern plugin
52 | .tern-project
53 |
54 | # TeXlipse plugin
55 | .texlipse
56 |
57 | # STS (Spring Tool Suite)
58 | .springBeans
59 |
60 | # Code Recommenders
61 | .recommenders/
62 |
63 | # Annotation Processing
64 | .apt_generated/
65 |
66 | # Scala IDE specific (Scala & Java development for Eclipse)
67 | .cache-main
68 | .scala_dependencies
69 | .worksheet
70 |
71 |
--------------------------------------------------------------------------------
/src/script/java/IkScript.java:
--------------------------------------------------------------------------------
1 | //TODO test your ideas fast here
2 | //@author Ilya Kharlamov
3 | //@category _NEW_
4 | //@keybinding
5 | //@menupath Tools.NES
6 | //@toolbar
7 |
8 | import ghidra.app.script.GhidraScript;
9 | import ik.ghidranesrom.analyzer.InMemorySymbolTagStorage;
10 | import ik.ghidranesrom.analyzer.Worker;
11 | import ik.ghidranesrom.wrappers.ProgramWrapper;
12 |
13 |
14 | public class IkScript extends GhidraScript {
15 |
16 | public void run() throws Exception {
17 | println("==============================================================================================================");
18 | // currentProgram
19 | // currentAddress
20 | // currentLocation
21 | // currentSelection
22 | // currentHighlight
23 | printf("currentSelection: %s\n", currentSelection);
24 | InMemorySymbolTagStorage symbolTagStorage = new InMemorySymbolTagStorage();
25 | Worker worker = new Worker(new ProgramWrapper(currentProgram), symbolTagStorage);
26 | worker.analyzeBrandedLabels();
27 | worker.analyzeLoops();
28 | //StreamSupport.stream(programWrapper.getLabels().spliterator(), false);
29 |
30 |
31 | // AutoAnalysisManager.
32 | //AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(currentProgram);
33 | //ConsoleService consoleService = mgr.getAnalysisTool().getService(ConsoleService.class);
34 | //consoleService.print("FU");
35 |
36 | println("END");
37 | }
38 |
39 | }
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/wrappers/InstructionWrapper.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.wrappers;
2 |
3 | import ghidra.program.model.address.GenericAddress;
4 | import ghidra.program.model.listing.Instruction;
5 |
6 | import java.util.Arrays;
7 |
8 | public class InstructionWrapper {
9 | private final Instruction instruction;
10 |
11 | InstructionWrapper(Instruction instruction) {
12 | this.instruction = instruction;
13 | }
14 |
15 | @Override
16 | public String toString() {
17 | return this.instruction.getAddress().toString();
18 | }
19 |
20 | public Boolean isInput() {
21 | return (this.instruction.getMnemonicString().equals("LDA")
22 | || this.instruction.getMnemonicString().equals("LDX")
23 | || this.instruction.getMnemonicString()
24 | .equals("LDY")) && Arrays.stream(this.instruction.getInputObjects()).map(
25 | Object::getClass).filter(x -> x.equals(GenericAddress.class)).count() > 0;
26 | }
27 |
28 | public OperandObjectWrapper getSource() {
29 | for (Object obj : this.instruction.getInputObjects()) {
30 | if (obj instanceof GenericAddress) {
31 | return new OperandObjectWrapper((GenericAddress) obj, this.instruction.getProgram());
32 | }
33 | }
34 | throw new RuntimeException(String.format("No Generic address for LD* found: %s at ", this.instruction,
35 | this.instruction.getAddress()
36 | ));
37 | }
38 |
39 | public Boolean isBranch() {
40 | return instruction.getFlowType().isConditional();
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/util/Constants.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.util;
2 |
3 | import com.google.common.collect.ImmutableList;
4 |
5 | import java.util.Map;
6 | import java.util.function.Function;
7 | import java.util.stream.Collectors;
8 |
9 | public class Constants {
10 | public static ImmutableList brandedAddresses = ImmutableList.builder()
11 | .add(new BrandedAddress(0x2000, 1, "PPUCTRL"))
12 | .add(new BrandedAddress(0x2001, 1, "PPUMASK"))
13 | .add(new BrandedAddress(0x2002, 1, "PPUSTATUS"))
14 | .add(new BrandedAddress(0x2003, 1, "OAMADDR"))
15 | .add(new BrandedAddress(0x2004, 1, "OAMDATA"))
16 | .add(new BrandedAddress(0x2005, 1, "PPUSCROLL"))
17 | .add(new BrandedAddress(0x2006, 1, "PPUADDR"))
18 | .add(new BrandedAddress(0x2007, 1, "PPUDATA"))
19 | .add(new BrandedAddress(0x4000, 4, "APU_SND_SQUARE1_REG"))
20 | .add(new BrandedAddress(0x4004, 4, "APU_SND_SQUARE2_REG"))
21 | .add(new BrandedAddress(0x4008, 4, "APU_SND_TRIANGLE_REG"))
22 | .add(new BrandedAddress(0x400c, 2, "APU_NOISE_REG"))
23 | .add(new BrandedAddress(0x400e, 1, "APU_NOISE_REG_FREQUENCY_2"))
24 | .add(new BrandedAddress(0x400f, 1, "APU_NOISE_REG_FREQUENCY_AND_TIME_3"))
25 | .add(new BrandedAddress(0x4010, 4, "APU_DELTA_REG"))
26 | .add(new BrandedAddress(0x4014, 1, "OAMDMA"))
27 | .add(new BrandedAddress(0x4015, 1, "APU_MASTERCTRL_REG"))
28 | .add(new BrandedAddress(0x4016, 1, "JOYPAD_PORT1"))
29 | .add(new BrandedAddress(0x4017, 1, "JOYPAD_PORT2"))
30 | .build();
31 |
32 | public static Map brandedAddressImmutableMap =
33 | brandedAddresses.stream().collect(Collectors.toMap(x -> Long.valueOf(x.getAddr()), Function.identity()));
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/analyzer/InMemorySymbolTagStorage.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.analyzer;
2 |
3 | import com.google.common.collect.ImmutableSet;
4 | import ghidra.program.model.address.Address;
5 | import ghidra.program.model.symbol.Symbol;
6 | import ghidra.util.Msg;
7 | import ik.ghidranesrom.wrappers.SymbolTagStorage;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 | import java.util.Set;
12 |
13 | public class InMemorySymbolTagStorage implements SymbolTagStorage {
14 | private Map> map;
15 |
16 | public InMemorySymbolTagStorage() {
17 | this.map = new HashMap<>();
18 | }
19 |
20 | @Override
21 | public void add(Symbol symbol, String name) {
22 | Msg.info(getClass().getSimpleName(), String.format("adding to symbol: %s tag: %s ...", symbol, name));
23 | ImmutableSet existingValue = map.getOrDefault(symbol.getAddress(), ImmutableSet.of());
24 | Msg.info(getClass().getSimpleName(), String.format(
25 | "old value: %s",
26 | existingValue
27 | ));
28 | this.map.put(
29 | symbol.getAddress(),
30 | ImmutableSet.builder().addAll(existingValue).add(name).build()
31 | );
32 | Msg.info(getClass().getSimpleName(), String.format(
33 | "new value: %s",
34 | this.map.getOrDefault(symbol.getAddress(), ImmutableSet.of())
35 | ));
36 | }
37 |
38 | @Override
39 | public ImmutableSet getAll(Symbol symbol) {
40 | ImmutableSet val = map.getOrDefault(symbol.getAddress(), ImmutableSet.of());
41 | Msg.info(getClass().getSimpleName(), String.format("getAll: for symbol %s val:%s, total keys:%s", symbol, val,
42 | ImmutableSet.copyOf(map.keySet())
43 | ));
44 | return val;
45 | }
46 |
47 | @Override
48 | public ImmutableSet keys() {
49 | return ImmutableSet.copyOf(this.map.keySet());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/wrappers/ProgramWrapper.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.wrappers;
2 |
3 | import ghidra.program.model.address.Address;
4 | import ghidra.program.model.listing.Program;
5 | import ghidra.program.model.symbol.Symbol;
6 | import ghidra.program.model.symbol.SymbolType;
7 | import ghidra.program.model.symbol.SymbolUtilities;
8 |
9 | import java.util.Arrays;
10 | import java.util.stream.Collectors;
11 | import java.util.stream.Stream;
12 | import java.util.stream.StreamSupport;
13 |
14 | public class ProgramWrapper {
15 | private final Program program;
16 |
17 | public ProgramWrapper(Program program) {
18 | this.program = program;
19 | }
20 |
21 | public Stream getLabels() {
22 | return StreamSupport.stream(program.getSymbolTable().getAllSymbols(true).spliterator(), false)
23 | .filter(x -> x.getSymbolType().equals(SymbolType.LABEL));
24 | }
25 |
26 | public Iterable getLoops() {
27 | return StreamSupport.stream(program.getSymbolTable().getAllSymbols(true).spliterator(), false)
28 | .filter(x -> x.getSymbolType().equals(SymbolType.LABEL))
29 | .filter(x -> x.getReferenceCount() == 1)
30 | .filter(x -> SymbolUtilities.getDynamicName(x.getProgram(), x.getAddress()).startsWith("LAB_"))
31 | .filter(x -> x.getReferences()[0].getFromAddress().compareTo(x.getAddress()) > 0)
32 | .map(LoopWrapper::new)
33 | .collect(Collectors.toList());
34 | }
35 |
36 | public Stream getFirstLabelAbove(Address address) {
37 | return StreamSupport.stream(program.getListing().getInstructions(address, false).spliterator(), false)
38 | .dropWhile(l -> !program.getSymbolTable().hasSymbol(l.getAddress()))
39 | .findFirst().stream()
40 | .flatMap(inst -> Arrays.stream(program.getSymbolTable().getSymbols(inst.getAddress())))
41 | ;
42 |
43 | }
44 |
45 | public Stream streamSymbolsAt(Address address) {
46 | return Arrays.stream(this.program.getSymbolTable().getSymbols(address));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/mapper/NromMapper.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.loader.mapper;
2 |
3 | import java.util.Arrays;
4 |
5 | import ghidra.framework.store.LockException;
6 | import ghidra.program.model.address.AddressOverflowException;
7 | import ghidra.program.model.listing.Program;
8 | import ghidra.program.model.mem.MemoryConflictException;
9 | import ghidra.util.exception.CancelledException;
10 | import ghidra.util.exception.DuplicateNameException;
11 | import ghidra.util.task.TaskMonitor;
12 | import ik.ghidranesrom.loader.NesRom;
13 | import ik.ghidranesrom.util.MemoryBlockDescription;
14 |
15 | public class NromMapper extends NesMapper {
16 | @Override
17 | public void updateMemoryMapForRom(NesRom rom, Program program, TaskMonitor monitor) throws LockException, MemoryConflictException, AddressOverflowException, CancelledException, DuplicateNameException {
18 | // TODO: Do we always want to include work RAM?
19 | int workRamPermissions =
20 | MemoryBlockDescription.READ | MemoryBlockDescription.WRITE | MemoryBlockDescription.EXECUTE;
21 | MemoryBlockDescription.uninitialized(0x6000, 0x2000, "WORK_RAM", workRamPermissions, false)
22 | .create(program);
23 |
24 | // TODO: Do we need all this? It appears no NROM games have anything besides 16K or 32K, so we will only ever need to create one mirror
25 | for (int romMirror = 0; romMirror * rom.prgRom.length < 0x8000; romMirror++) {
26 | int romMirrorOffsetStart = romMirror * rom.prgRom.length;
27 | int romMirrorLength = Math.min(rom.prgRom.length, 0x8000);
28 |
29 | int romMirrorStart = romMirrorOffsetStart + 0x8000;
30 | int romPermissions =
31 | MemoryBlockDescription.READ | MemoryBlockDescription.EXECUTE;
32 |
33 | if (romMirror == 0) {
34 | byte[] romBytes = Arrays.copyOfRange(rom.prgRom, 0, romMirrorLength);
35 | MemoryBlockDescription.initialized(romMirrorStart, romMirrorLength, "PRG_ROM", romPermissions, romBytes, false, monitor)
36 | .create(program);
37 | }
38 | else {
39 | MemoryBlockDescription.byteMapped(romMirrorStart, romMirrorLength, "PRG_ROM_MIRROR_" + romMirror, romPermissions, 0x8000)
40 | .create(program);
41 | }
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Gradle
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3 |
4 | name: Ghidra Nes Rom extension build
5 |
6 | env:
7 | ghidra-url: https://ghidra-sre.org/ghidra_9.2.1_PUBLIC_20201215.zip
8 | ghidra-zip-filename: ghidra_9.2.1_PUBLIC.zip
9 | ghidra-directory: ghidra_9.2.1_PUBLIC
10 |
11 | on:
12 | push:
13 | branches: [ master ]
14 | pull_request:
15 | branches: [ master ]
16 | release:
17 | types: [ created ]
18 |
19 | jobs:
20 | build:
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - uses: actions/checkout@v2
25 |
26 | - name: Download Ghidra release
27 | uses: carlosperate/download-file-action@v1.0.3
28 | id: download-ghidra
29 | with:
30 | file-url: ${{ env.ghidra-url }}
31 | file-name: ${{ env.ghidra-zip-filename }}
32 |
33 | - name: Unzip Ghidra
34 | uses: TonyBogdanov/zip@1.0
35 | with:
36 | args: unzip -qq ${{ steps.download-ghidra.outputs.file-path }} -d .
37 | - uses: actions/setup-java@v1
38 | with:
39 | java-version: '11.0.2'
40 | - name: Grant execute permission for gradlew
41 | run: chmod +x gradlew
42 | - name: Build extension
43 | uses: eskatos/gradle-command-action@v1
44 | with:
45 | gradle-version: '6.8.2'
46 | # build-root-directory: ${{ github.workspace }}/build
47 | arguments: '-Pghidra.dir=${{ github.workspace }}/${{ env.ghidra-directory }} assemble'
48 | - name: list what we have
49 | run: find .
50 | - name: make sure zip is in dist
51 | run: ls build/distributions/*.zip
52 | - name: Upload built extension
53 | uses: actions/upload-artifact@v2
54 | with:
55 | name: extension
56 | path: build/distributions/*.zip
57 |
58 | release:
59 | runs-on: ubuntu-latest
60 | if: ${{ github.event_name == 'release' }}
61 | needs: build
62 |
63 | steps:
64 | - name: Download built extension
65 | uses: actions/download-artifact@v2
66 | with:
67 | name: extension
68 | - name: Upload extension to release
69 | uses: skx/github-action-publish-binaries@master
70 | env:
71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 | with:
73 | args: '*.zip'
74 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/wrappers/LoopWrapper.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.wrappers;
2 |
3 | import ghidra.program.model.address.Address;
4 | import ghidra.program.model.address.AddressSet;
5 | import ghidra.program.model.listing.InstructionIterator;
6 | import ghidra.program.model.symbol.SourceType;
7 | import ghidra.program.model.symbol.Symbol;
8 | import ghidra.util.exception.DuplicateNameException;
9 | import ghidra.util.exception.InvalidInputException;
10 |
11 | import java.util.List;
12 | import java.util.stream.Collectors;
13 | import java.util.stream.StreamSupport;
14 |
15 | public class LoopWrapper {
16 | private final Symbol symbol;
17 |
18 | LoopWrapper(Symbol symbol) {
19 | if (symbol.getReferences().length != 1) {
20 | throw new RuntimeException("Number of references is not 1");
21 | }
22 | this.symbol = symbol;
23 | }
24 |
25 | public String getName() {
26 | return this.symbol.getName();
27 | }
28 |
29 | public List getInstructions() {
30 | AddressSet range = symbol.getProgram().getAddressFactory().getAddressSet(symbol.getAddress(), symbol.getReferences()[0].getFromAddress());
31 | InstructionIterator instructionIterator = symbol.getProgram().getListing().getInstructions(range, true);
32 | return StreamSupport.stream(instructionIterator.spliterator(), false).map(x -> new InstructionWrapper(x)).collect(Collectors.toList());
33 | }
34 |
35 | public List getInstructionsInput() {
36 | AddressSet range = symbol.getProgram().getAddressFactory().getAddressSet(symbol.getAddress(), symbol.getReferences()[0].getFromAddress());
37 | InstructionIterator instructionIterator = symbol.getProgram().getListing().getInstructions(range, true);
38 | return StreamSupport.stream(instructionIterator.spliterator(), false)
39 | .map(x -> new InstructionWrapper(x))
40 | .filter(x -> x.isInput())
41 | .collect(Collectors.toList());
42 | }
43 |
44 | public Address getAddress() {
45 | return this.symbol.getAddress();
46 | }
47 |
48 | public void renameTo(String s) {
49 | try {
50 | this.symbol.setName(s, SourceType.ANALYSIS);
51 | } catch (DuplicateNameException e) {
52 | e.printStackTrace();
53 | } catch (InvalidInputException e) {
54 | e.printStackTrace();
55 | }
56 | }
57 |
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/ghidra_scripts/src/Test6502.java:
--------------------------------------------------------------------------------
1 | //TODO test your ideas fast here
2 | //@author Ilya Kharlamov
3 | //@category _NEW_
4 | //@keybinding
5 | //@menupath Tools.NES
6 | //@toolbar
7 |
8 | import ghidra.app.script.GhidraScript;
9 | import ghidra.app.services.Analyzer;
10 | import ghidra.program.model.address.Address;
11 | import ghidra.program.model.listing.Instruction;
12 | import ghidra.program.model.mem.MemoryAccessException;
13 | import ghidra.program.model.symbol.RefType;
14 | import ghidra.program.model.symbol.Reference;
15 | import ghidra.program.model.symbol.SourceType;
16 | import ghidra.util.NumericUtilities;
17 | import ik.ghidranesrom.analyzer.Worker;
18 | import ik.ghidranesrom.wrappers.ProgramWrapper;
19 |
20 | public class Test6502 extends GhidraScript {
21 | public void run() throws Exception {
22 | ProgramWrapper programWrapper = new ProgramWrapper(currentProgram);
23 | Worker analyzer = new Worker(programWrapper);
24 | }
25 |
26 | private void extracted(Instruction instr) throws MemoryAccessException {
27 | for (int i = 0; i < instr.getNumOperands(); i++) {
28 | printf(" operandRefType:%s\n", instr.getOperandRefType(i));
29 | printf(" mnemonic:%s\n", instr.getMnemonicString());
30 | printf(" bytes:%s\n", NumericUtilities.convertBytesToString(instr.getBytes()));
31 | printf(" opObject:%s\n", instr.getOpObjects(i));
32 | printf(" reftype:%s\n", instr.getOperandRefType(i));
33 | }
34 | }
35 |
36 | private void fix9d(Instruction instr) throws MemoryAccessException {
37 | if (instr.getByte(0) == (byte) 0x9d) {
38 | printf("ROTTEN instr: %s at %s\n", instr, instr.getAddress());
39 | for (Reference x : instr.getReferencesFrom()) {
40 | if (x.getSource().equals(SourceType.ANALYSIS)) {
41 | printf("ROTTEN REFERENCE: %s\n", x);
42 | instr.getProgram().getReferenceManager().delete(x);
43 | }
44 | }
45 | int addr_int = (instr.getByte(2) << 8) | instr.getByte(1);
46 | printf("myhex: 0x%08X\n", addr_int);
47 | Address addr = instr.getAddress().getAddressSpace().getAddress(addr_int);
48 | printf("myadr: %s\n", addr);
49 | println(NumericUtilities.convertBytesToString(new byte[]{instr.getByte(1), instr.getByte(2)}));
50 | instr.getProgram().getReferenceManager()
51 | .setPrimary(instr.getProgram().getReferenceManager().addMemoryReference(
52 | instr.getAddress(),
53 | addr, RefType.DATA, SourceType.ANALYSIS, 0
54 | ), true);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/analyzer/Worker.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.analyzer;
2 |
3 | import ghidra.util.Msg;
4 | import ik.ghidranesrom.wrappers.ProgramWrapper;
5 | import ik.ghidranesrom.wrappers.SymbolTagStorage;
6 | import ik.ghidranesrom.wrappers.SymbolWrapper;
7 |
8 | import java.util.stream.Stream;
9 |
10 | public class Worker {
11 | private final ProgramWrapper programWrapper;
12 | private final SymbolTagStorage symbolTagStorage;
13 |
14 | public Worker(ProgramWrapper programWrapper, SymbolTagStorage symbolTagStorage) {
15 | this.programWrapper = programWrapper;
16 | this.symbolTagStorage = symbolTagStorage;
17 | }
18 |
19 | public void analyzeBrandedLabels() {
20 | programWrapper.getLabels()
21 | .map(symbol -> new SymbolWrapper(symbol, symbolTagStorage))
22 | .flatMap(SymbolWrapper::streamBranded)
23 | .peek((symbolWrapper) -> {
24 | Msg.info("branded", symbolWrapper.getName());
25 | })
26 | .forEach(brandedSymbolWrapper -> {
27 | brandedSymbolWrapper.streamReferences().forEach(ref -> {
28 | programWrapper.getFirstLabelAbove(ref.getFromAddress())
29 | .map(x -> new SymbolWrapper(x, symbolTagStorage))
30 | .forEach(l -> {
31 | if (l.getName().equals("reset")) {
32 | return;
33 | }
34 | if (l.getName().equals("irq")) {
35 | return;
36 | }
37 | if (l.getName().equals("vblank")) {
38 | return;
39 | }
40 | Msg.debug("label", l.getName());
41 | l.addTag("LAB");
42 | l.addTag(brandedSymbolWrapper.getName());
43 | });
44 | });
45 | });
46 | }
47 |
48 | public void analyzeLoops() {
49 | programWrapper.getLabels()
50 | .map(symbol -> new SymbolWrapper(symbol, symbolTagStorage))
51 | .filter(SymbolWrapper::isLoop).forEach(x -> x.addTag("LOOP"));
52 | }
53 |
54 | public void renameLabels() {
55 | symbolTagStorage.keys().stream()
56 | .flatMap(x -> programWrapper.streamSymbolsAt(x))
57 | .map(x -> new SymbolWrapper(x, symbolTagStorage))
58 | .forEach(SymbolWrapper::renameFromTags);
59 |
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NES / Famicom Decompiler Plugin for Ghidra
2 |
3 | A Ghidra extension to support disassembling and analyzing NES (Nintendo Entertainment System) and Famicom ROMs.
4 |
5 | 
6 |
7 | ## Features
8 |
9 | - Import NES ROMs in the iNES format. The following mappers are supported:
10 | - NROM (mapper 0)
11 | - Add labels and memory blocks in disassembly, making it easier to jump around a disassembled ROM!
12 |
13 | ## Installation
14 |
15 | 1. Install a Compatible version of Java and Ghidra (Java 11+, tested with Ghidra 10.0.3).
16 | 2. Download the latest [GhidraNesRom release](https://github.com/ilyakharlamov/GhidraNesRom/releases).
17 | 3. Go to "File" > "Install Extensions...". Click "+" in the top-right corner and choose the GhidraNes Zip. Click "OK" to install the extension.
18 | 4. Restart Ghidra.
19 |
20 | ## Usage
21 |
22 | 1. In Ghidra, create a new project by following the wizard under "File" > "New Project...".
23 | 2. Drag-and-drop an iNES `.nes` ROM onto the project. Set the format to "NES ROM" and click "OK".
24 | 3. Double-click the ROM in the project to open Ghidra's CodeBrowser.
25 | 4. Analyze the file when prompted (or go to "Analysis" > "Auto Analyze..."). Leave the settings as default and click "Analyze".
26 | 5. Done, the game will be disassembled! On the left-hand side, under "Symbol Tree" > "Functions", open `reset` to jump to the reset vector (where execution starts), or `vblank` to jump to the NMI vector (where execution goes during VBlank).
27 |
28 | ## Developing
29 |
30 | 1. Install Java and Ghidra.
31 | 2. install Eclipse.
32 | 3. Install the GhidraDev Eclipse plugin. Instructions can be found in your Ghidra install directory, under `Extensions/Eclipse/GhidraDev/GhidraDev_README.html`.
33 | 4. In Eclipse, open the GhidraNes repo by going to "File" > "Open Projects from File System...".
34 | 5. Open "GhidraDev" > "Link Ghidra...". Add your Ghidra installation, click "Next >", then select the "GhidraNes" as the Java project. Click "Finish".
35 | 6. Run the project in Eclipse to start Ghidra and the GhidraNes extension.
36 |
37 | ### Building a release from Gradle
38 | 1. Install gradle
39 | 2. `export GHIDRA_INSTALL_DIR=/path/to/ghidra`
40 | 3. `gradle assemble`
41 |
42 | ### Building a release from Eclipse
43 |
44 | **NOTE:** Ensure the GhidraNes Eclipse project is set up with the _earliest_ version of Java that should be targeted. Using a later version of Java can cause compatibility issues!
45 |
46 | 1. Install Gradle (with [SDKMAN](https://sdkman.io/), this can be done with `sdk install gradle`).
47 | 2. In Eclipse, open "GhidraDev" > "Export" > "Ghidra Module Extension...". Choose "GhidraNes" as the project, click "Next >", then choose "Local installation directory:" and browse to your Gradle installation dir (with SDKMAN, this will be at `~/.sdkman/candidates/gradle/$GRADLE_VERSION`). Click "Finish".
48 | 3. The built zip file will be saved in the `dist/` directory.
49 |
--------------------------------------------------------------------------------
/src/main/help/help/TOC_Source.xml:
--------------------------------------------------------------------------------
1 |
2 |
49 |
50 |
51 |
52 |
57 |
58 |
--------------------------------------------------------------------------------
/data/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/src/main/help/help/shared/Frontpage.css:
--------------------------------------------------------------------------------
1 | /* ###
2 | * IP: GHIDRA
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /*
17 | WARNING!
18 | This file is copied to all help directories. If you change this file, you must copy it
19 | to each src/main/help/help/shared directory.
20 |
21 |
22 | Java Help Note: JavaHelp does not accept sizes (like in 'margin-top') in anything but
23 | px (pixel) or with no type marking.
24 |
25 | */
26 |
27 | body { margin-bottom: 50px; margin-left: 10px; margin-right: 10px; margin-top: 10px; } /* some padding to improve readability */
28 | li { font-family:times new roman; font-size:14pt; }
29 | h1 { color:#000080; font-family:times new roman; font-size:36pt; font-style:italic; font-weight:bold; text-align:center; }
30 | h2 { margin: 10px; margin-top: 20px; color:#984c4c; font-family:times new roman; font-size:18pt; font-weight:bold; }
31 | h3 { margin-left: 10px; margin-top: 20px; color:#0000ff; font-family:times new roman; font-size:14pt; font-weight:bold; }
32 | h4 { margin-left: 10px; margin-top: 20px; font-family:times new roman; font-size:14pt; font-style:italic; }
33 |
34 | /*
35 | P tag code. Most of the help files nest P tags inside of blockquote tags (the was the
36 | way it had been done in the beginning). The net effect is that the text is indented. In
37 | modern HTML we would use CSS to do this. We need to support the Ghidra P tags, nested in
38 | blockquote tags, as well as naked P tags. The following two lines accomplish this. Note
39 | that the 'blockquote p' definition will inherit from the first 'p' definition.
40 | */
41 | p { margin-left: 40px; font-family:times new roman; font-size:14pt; }
42 | blockquote p { margin-left: 10px; }
43 |
44 | p.providedbyplugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
45 | p.ProvidedByPlugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
46 | p.relatedtopic { color:#800080; margin-left: 10px; font-size:14pt; }
47 | p.RelatedTopic { color:#800080; margin-left: 10px; font-size:14pt; }
48 |
49 | /*
50 | We wish for a tables to have space between it and the preceding element, so that text
51 | is not too close to the top of the table. Also, nest the table a bit so that it is clear
52 | the table relates to the preceding text.
53 | */
54 | table { margin-left: 20px; margin-top: 10px; width: 80%;}
55 | td { font-family:times new roman; font-size:14pt; vertical-align: top; }
56 | th { font-family:times new roman; font-size:14pt; font-weight:bold; background-color: #EDF3FE; }
57 |
58 | code { color: black; font-family: courier new; font-size: 14pt; }
59 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/NesRomHeader.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.loader;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | import ik.ghidranesrom.loader.exception.InvalidNesRomHeaderException;
7 | import ik.ghidranesrom.loader.exception.NesRomEofException;
8 |
9 | public class NesRomHeader {
10 | protected int prgRomSizeBytes;
11 | protected int chrRomSizeBytes;
12 | protected int prgRamSizeBytes;
13 | protected boolean hasPersistence;
14 | protected boolean hasTrainer;
15 | protected int mapper;
16 |
17 | public NesRomHeader(InputStream bytes) throws NesRomEofException, InvalidNesRomHeaderException, IOException {
18 | byte[] magicBytes = bytes.readNBytes(4);
19 | if (magicBytes.length < 4) {
20 | throw new NesRomEofException();
21 | }
22 | if (magicBytes[0] != 'N' || magicBytes[1] != 'E' && magicBytes[2] != 'S' && magicBytes[3] != 0x1A) {
23 | throw new InvalidNesRomHeaderException("Input is not a valid NES ROM");
24 | }
25 |
26 | int prgRomSizeField = bytes.read();
27 | if (prgRomSizeField < 0) {
28 | throw new NesRomEofException();
29 | }
30 |
31 | int chrRomSizeField = bytes.read();
32 | if (chrRomSizeField < 0) {
33 | throw new NesRomEofException();
34 | }
35 |
36 | int flags6 = bytes.read();
37 | if (flags6 < 0) {
38 | throw new NesRomEofException();
39 | }
40 |
41 | int flags7 = bytes.read();
42 | if (flags7 < 0) {
43 | throw new NesRomEofException();
44 | }
45 |
46 | int prgRamSizeField = bytes.read();
47 | if (prgRamSizeField < 0) {
48 | throw new NesRomEofException();
49 | }
50 |
51 | int flags9 = bytes.read();
52 | if (flags9 < 0) {
53 | throw new NesRomEofException();
54 | }
55 |
56 | int flags10 = bytes.read();
57 | if (flags10 < 0) {
58 | throw new NesRomEofException();
59 | }
60 |
61 | byte[] padding = bytes.readNBytes(5);
62 | if (padding.length < 5) {
63 | throw new NesRomEofException();
64 | }
65 |
66 | // boolean flagMirrorBit = (flags6 & 0b0000_0001) != 0;
67 | boolean flagPersistentBit = (flags6 & 0b0000_0010) != 0;
68 | boolean flagTrainerBit = (flags6 & 0b0000_0100) != 0;
69 | // boolean flagFourScreenVramBit = (flags6 & 0b0000_1000) != 0;
70 | int flagMapperLo = (flags6 & 0b1111_0000) >> 4;
71 |
72 | // boolean flagVsUnisystem = (flags7 & 0b0000_0001) != 0;
73 | // boolean flagPlaychoice10 = (flags7 & 0b0000_0010) != 0;
74 | int flagRomFormat = (flags7 & 0b0000_1100) >> 2;
75 | int flagMapperHi = (flags7 & 0b1111_0000) >> 4;
76 |
77 | // Fields in flag 9 are ignored
78 |
79 | // int flagTvSystem = (flags10 & 0b0000_0011);
80 | boolean flagPrgRamBit = (flags10 & 0b0001_0000) != 0;
81 | // boolean busConflictBit = (flags10 & 0b0010_0000) != 0;
82 |
83 | this.prgRomSizeBytes = prgRomSizeField * 16_384;
84 | this.chrRomSizeBytes = chrRomSizeField * 8_192;
85 | if (flagPrgRamBit) {
86 | if (prgRamSizeField == 0) {
87 | // When a ROM has the PRG RAM bit set but has a PRG RAM
88 | // size of 0, then a fallback size of 8KB is used
89 | this.prgRamSizeBytes = 8_192;
90 | }
91 | else {
92 | this.prgRamSizeBytes = prgRamSizeField * 8_192;
93 | }
94 | }
95 | else {
96 | this.prgRamSizeBytes = 0;
97 | }
98 |
99 | this.hasPersistence = flagPersistentBit;
100 | this.hasTrainer = flagTrainerBit;
101 |
102 | this.mapper = flagMapperLo | (flagMapperHi << 4);
103 |
104 | if (flagRomFormat == 2) {
105 | // TODO: Handle NES 2.0 format
106 | }
107 | }
108 |
109 | int getPrgRomSizeBytes() {
110 | return this.prgRamSizeBytes;
111 | }
112 |
113 | int getChrRomSizeBytes() {
114 | return this.chrRomSizeBytes;
115 | }
116 |
117 | int getPrgRamSizeBytes() {
118 | return this.prgRamSizeBytes;
119 | }
120 |
121 | boolean getHasPersistence() {
122 | return this.hasPersistence;
123 | }
124 |
125 | boolean getHasTrainer() {
126 | return this.hasTrainer;
127 | }
128 |
129 | int getMapper() {
130 | return this.mapper;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/script/generate_script.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import re
4 | import sys
5 | import logging
6 |
7 |
8 | class JavaSourceCode(object):
9 | def __init__(self, fpath):
10 | assert os.path.isfile(fpath), fpath
11 |
12 |
13 | class Inliner(object):
14 | def __init__(self, class_resolve_dir_path):
15 | assert os.path.isdir(class_resolve_dir_path), class_resolve_dir_path
16 | self.__class_resolve_dir_path = class_resolve_dir_path
17 |
18 | def resolve_class_source_code(self, package_name):
19 | assert type(package_name) == str, package_name
20 | assert re.compile("([a-zA-Z_$][a-zA-Z\d_$]*\.)*[a-zA-Z_$][a-zA-Z\d_$]*").match(package_name), package_name
21 | subpath = package_name.replace(".", "/")
22 | class_fpath = os.path.join(self.__class_resolve_dir_path, subpath+".java")
23 | return self.resolve_class_source_code_fpath(class_fpath)
24 |
25 | def resolve_class_source_code_fpath(self, class_fpath):
26 | assert os.path.isfile(class_fpath), class_fpath
27 | precomments = []
28 | imports = []
29 | lines = []
30 | with open(class_fpath) as fin:
31 | for line in fin:
32 | if line.startswith("package"):
33 | continue
34 | if line.startswith("import"):
35 | imports.append(line)
36 | continue
37 | if line.startswith("//") and not imports:
38 | precomments.append(line)
39 | continue
40 | lines.append(line)
41 | return (precomments, imports, lines)
42 |
43 | def import_to_java_fully_qualified_class_name(self, line):
44 | return line[line.find("import ")+len("import "):line.rfind(";")]
45 |
46 | def replacefile(self, fpath_from, fpath_to):
47 | already = set()
48 | with open(fpath_to, "w") as fout:
49 | (precomment_lines, import_lines, content_lines) = self.resolve_class_source_code_fpath(fpath_from)
50 | import_lines += ["import ik.ghidranesrom.wrappers.LoopWrapper;\n"]
51 | import_lines += ["import ik.ghidranesrom.wrappers.BrandedSymbolWrapper;\n"]
52 | import_lines += ["import ik.ghidranesrom.wrappers.InstructionWrapper;\n"]
53 | import_lines += ["import ik.ghidranesrom.wrappers.OperandObjectWrapper;\n"]
54 | for import_line in import_lines:
55 | if import_line.startswith("import ik.ghidranesrom") and import_line not in already:
56 | (pre, imp, cont) = self.resolve_class_source_code(self.import_to_java_fully_qualified_class_name(import_line))
57 | import_lines += imp
58 | #import_lines += ["import ik.ghidranesrom.%s;" % re.compile("public class (\w+)\s*{").search(s).group(1) for s in cont if "public class" in cont]
59 | content_lines += map(lambda line:line.replace("public class", "class").replace("public interface", "interface"), cont)
60 | already.add(import_line)
61 | for precomment_line in precomment_lines:
62 | fout.write(precomment_line)
63 | for import_line in import_lines:
64 | if "import ik.ghidranesrom" in import_line:
65 | continue
66 | fout.write(import_line)
67 | for content_line in content_lines:
68 | fout.write(content_line)
69 |
70 |
71 | def find_src_dir():
72 | current_dir = os.path.dirname(__file__)
73 | while os.path.basename(current_dir) != "src":
74 | current_dir = os.path.dirname(current_dir)
75 | logging.info("current_dir: %s", current_dir)
76 | return current_dir
77 |
78 |
79 | if __name__ == "__main__":
80 | script_src_dir = find_src_dir()
81 | script_source_dir = os.path.join(script_src_dir, "script", "java")
82 | script_resolve_dir = os.path.join(script_src_dir, "main", "java")
83 | script_target_dir = os.path.join(script_src_dir, "../", "ghidra_scripts", "src")
84 | assert os.path.isdir(script_target_dir), ("%s does not exist" % script_target_dir)
85 | for fname in os.listdir(script_source_dir):
86 | if not fname.endswith(".java"):
87 | continue
88 | fpath = os.path.join(script_source_dir, fname)
89 | target_fpath = os.path.join(script_target_dir, fname)
90 | inliner = Inliner(class_resolve_dir_path=script_resolve_dir)
91 | inliner.replacefile(fpath, target_fpath)
92 | print("file_written", target_fpath)
93 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/wrappers/SymbolWrapper.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.wrappers;
2 |
3 | import com.google.common.base.Joiner;
4 | import com.google.common.collect.ImmutableList;
5 | import com.google.common.collect.ImmutableSet;
6 | import ghidra.program.model.listing.Instruction;
7 | import ghidra.program.model.symbol.SourceType;
8 | import ghidra.program.model.symbol.Symbol;
9 | import ghidra.program.model.symbol.SymbolType;
10 | import ghidra.util.Msg;
11 | import ghidra.util.exception.DuplicateNameException;
12 | import ghidra.util.exception.InvalidInputException;
13 | import ik.ghidranesrom.util.Constants;
14 |
15 | import java.util.Arrays;
16 | import java.util.stream.Collectors;
17 | import java.util.stream.Stream;
18 |
19 | public class SymbolWrapper {
20 | private final Symbol symbol;
21 | private final SymbolTagStorage symbolTagStorage;
22 | private ImmutableList orderedLabels = ImmutableList.builder().add("LOOP").build();
23 |
24 | public SymbolWrapper(Symbol symbol, SymbolTagStorage symbolTagStorage) {
25 | this.symbol = symbol;
26 | this.symbolTagStorage = symbolTagStorage;
27 | }
28 |
29 | public Stream streamBranded() {
30 | return Constants.brandedAddresses.stream().filter(b -> b.getAddr() == symbol.getAddress().getOffset())
31 | .map(b -> new BrandedSymbolWrapper(b, symbol));
32 | }
33 |
34 |
35 | public String getName() {
36 | return symbol.getName();
37 | }
38 |
39 | public void addTag(String name) {
40 | Msg.debug(getClass().getSimpleName(), String.format("adding tag %s ...", name));
41 | addStorageTag(name);
42 | }
43 |
44 | private void addStorageTag(String name) {
45 | symbolTagStorage.add(symbol, name);
46 | }
47 |
48 | public void renameFromTags() {
49 | ImmutableList tags = getOrderedStorageTags();
50 | Msg.info(getClass().getSimpleName(), String.format("rename from tags:%s", tags));
51 | try {
52 | this.symbol.setName(
53 | Joiner.on("_").join(tags).replaceAll("\\W+", "_"),
54 | SourceType.ANALYSIS
55 | );
56 | } catch (DuplicateNameException e) {
57 | e.printStackTrace();
58 | } catch (InvalidInputException e) {
59 | e.printStackTrace();
60 | }
61 | }
62 |
63 | private ImmutableList getOrderedStorageTags() {
64 | ImmutableList.Builder itemsBuilder = ImmutableList.builder();
65 | ImmutableSet storageTags= ImmutableSet.copyOf(symbolTagStorage.getAll(symbol));
66 | Msg.info("storageTags:", storageTags);
67 | itemsBuilder.addAll(orderedLabels.stream().filter(storageTags::contains).collect(Collectors.toList()));
68 | if (storageTags.contains("LOOP") && storageTags.contains("LAB")) {
69 | storageTags = ImmutableSet.copyOf(storageTags.stream().filter(x -> !"LAB".equals(x)).collect(Collectors.toSet()));
70 | }
71 | itemsBuilder.addAll(storageTags.stream().filter(x->!ImmutableSet.copyOf(orderedLabels).contains(x)).collect(Collectors.toList()));
72 | itemsBuilder.add(this.symbol.getAddress().toString());
73 | ImmutableList tags = itemsBuilder.build();
74 | return tags;
75 | }
76 |
77 | public boolean isLoop() {
78 | Symbol x = symbol;
79 | boolean isConditional = false;
80 | Msg.info("SymbolType: %s\n", x.getSymbolType());
81 | boolean hasAnyLoop = !Arrays.stream(x.getReferences()).filter(ref -> ref.getFromAddress().compareTo(x.getAddress()) > 0).findAny().isEmpty();
82 | Msg.info("hasAnyLoop: %s\n", hasAnyLoop);
83 | boolean isLabel = x.getSymbolType().equals(SymbolType.LABEL);
84 | if (isLabel && hasAnyLoop && x.getProgram().getListing()
85 | .getInstructionAt(x.getReferences()[0].getFromAddress()) != null) {
86 | Msg.info("isLoop", x);
87 | Instruction instr = x.getProgram().getListing()
88 | .getInstructionAt(x.getReferences()[0].getFromAddress());
89 | Msg.info("instr", instr);
90 | Msg.info("flowType", instr.getFlowType());
91 | Msg.info("isConditional", instr.getFlowType().isConditional());
92 | if (instr.getFlowType().isConditional()) {
93 | isConditional = true;
94 | }
95 | }
96 | return isLabel && hasAnyLoop && isConditional;
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/GhidraNesLoaderHelper.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.loader;
2 |
3 | import ghidra.app.util.importer.MessageLog;
4 | import ghidra.program.model.address.Address;
5 | import ghidra.program.model.address.AddressSpace;
6 | import ghidra.program.model.listing.Program;
7 | import ghidra.program.model.mem.Memory;
8 | import ghidra.program.model.mem.MemoryAccessException;
9 | import ghidra.program.model.mem.MemoryBlock;
10 | import ghidra.program.model.symbol.SourceType;
11 | import ghidra.program.model.symbol.Symbol;
12 | import ghidra.program.model.symbol.SymbolTable;
13 | import ghidra.util.exception.InvalidInputException;
14 | import ghidra.util.task.TaskMonitor;
15 | import ik.ghidranesrom.util.BrandedAddress;
16 | import ik.ghidranesrom.util.Constants;
17 |
18 | import static ik.ghidranesrom.util.AddressSpaceUtil.getLittleEndianAddress;
19 |
20 | public class GhidraNesLoaderHelper {
21 | private final Program program;
22 | private final TaskMonitor monitor;
23 | private final MessageLog log;
24 |
25 | public GhidraNesLoaderHelper(Program program, TaskMonitor monitor, MessageLog log) {
26 | this.program = program;
27 | this.monitor = monitor;
28 | this.log = log;
29 | }
30 |
31 | void makeSym(
32 | BrandedAddress brandedAddress
33 | ) {
34 | try {
35 | Address addr = program.getAddressFactory().getDefaultAddressSpace().getAddress(brandedAddress.getAddr());
36 | MemoryBlock block = program.getMemory().createInitializedBlock(
37 | brandedAddress.getName(),
38 | addr,
39 | brandedAddress.getSize(),
40 | (byte) 0x00,
41 | monitor,
42 | false
43 | );
44 | block.setRead(true);
45 | block.setWrite(true);
46 | block.setExecute(false);
47 | program.getSymbolTable()
48 | .createLabel(addr, brandedAddress.getName(), SourceType.IMPORTED);
49 | } catch (Exception e) {
50 | log.appendException(e);
51 | }
52 | }
53 |
54 | private static void createPinnedLabel(
55 | final SymbolTable symbolTable,
56 | final Address address,
57 | final String label
58 | ) throws InvalidInputException {
59 | Symbol nmiSymbol = symbolTable.createLabel(address, label, SourceType.IMPORTED);
60 | nmiSymbol.setPinned(true);
61 | nmiSymbol.setPrimary();
62 | }
63 |
64 | public void makeSyms() {
65 | Constants.brandedAddresses.stream().forEach(this::makeSym);
66 | }
67 |
68 | public void markAddresses() throws MemoryAccessException, InvalidInputException {
69 | AddressSpace addressSpace = program.getAddressFactory().getDefaultAddressSpace();
70 | SymbolTable symbolTable = program.getSymbolTable();
71 | Memory memory = program.getMemory();
72 |
73 | Address nmiAddress = addressSpace.getAddress(0xFFFA);
74 | createPinnedLabel(symbolTable, nmiAddress, "NMI");
75 | symbolTable.addExternalEntryPoint(nmiAddress);
76 |
77 | Address resAddress = addressSpace.getAddress(0xFFFC);
78 | createPinnedLabel(symbolTable, resAddress, "RES");
79 | symbolTable.addExternalEntryPoint(resAddress);
80 |
81 | Address irqAddress = addressSpace.getAddress(0xFFFE);
82 | createPinnedLabel(symbolTable, irqAddress, "IRQ");
83 | symbolTable.addExternalEntryPoint(irqAddress);
84 |
85 | // RES should have the highest precedence, followed by NMI, followed by IRQ. We set them
86 | // as primary in reverse order because the last `.setPrimary()` call has precedence
87 | Address resTargetAddress = getLittleEndianAddress(addressSpace, memory, resAddress);
88 | Symbol resTargetSymbol =
89 | symbolTable.createLabel(resTargetAddress, "reset", SourceType.IMPORTED);
90 | symbolTable.addExternalEntryPoint(resTargetAddress);
91 | resTargetSymbol.setPrimary();
92 |
93 | Address nmiTargetAddress = getLittleEndianAddress(addressSpace, memory, nmiAddress);
94 | Symbol nmiTargetSymbol =
95 | symbolTable.createLabel(nmiTargetAddress, "vblank", SourceType.IMPORTED);
96 | symbolTable.addExternalEntryPoint(nmiTargetAddress);
97 | nmiTargetSymbol.setPrimary();
98 |
99 | Address irqTargetAddress = getLittleEndianAddress(addressSpace, memory, irqAddress);
100 | Symbol irqTargetSymbol =
101 | symbolTable.createLabel(irqTargetAddress, "irq", SourceType.IMPORTED);
102 | symbolTable.addExternalEntryPoint(irqTargetAddress);
103 | irqTargetSymbol.setPrimary();
104 | }
105 |
106 | public void smartRename() {
107 | System.out.format("IK smart rename\n");
108 |
109 | }
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/util/MemoryBlockDescription.java:
--------------------------------------------------------------------------------
1 | package ik.ghidranesrom.util;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.InputStream;
5 |
6 | import ghidra.framework.store.LockException;
7 | import ghidra.program.model.address.Address;
8 | import ghidra.program.model.address.AddressOverflowException;
9 | import ghidra.program.model.address.AddressSpace;
10 | import ghidra.program.model.listing.Program;
11 | import ghidra.program.model.mem.Memory;
12 | import ghidra.program.model.mem.MemoryBlock;
13 | import ghidra.program.model.mem.MemoryConflictException;
14 | import ghidra.util.exception.CancelledException;
15 | import ghidra.util.exception.DuplicateNameException;
16 | import ghidra.util.task.TaskMonitor;
17 |
18 | public class MemoryBlockDescription {
19 | public static int READ = 1 << 0;
20 | public static int WRITE = 1 << 1;
21 | public static int EXECUTE = 1 << 2;
22 | public static int VOLATILE = 1 << 3;
23 |
24 | long start;
25 | long length;
26 | String name;
27 | MemoryBlockType type;
28 | int permissions;
29 | byte[] data;
30 | long mappedTo;
31 | boolean overlay;
32 | TaskMonitor monitor;
33 |
34 | MemoryBlockDescription() {
35 |
36 | }
37 |
38 | public void create(Memory memory, AddressSpace addressSpace) throws LockException, MemoryConflictException, AddressOverflowException, CancelledException, DuplicateNameException {
39 | Address startAddress = addressSpace.getAddress(start);
40 | InputStream dataStream = new ByteArrayInputStream(data);
41 |
42 | Address mappedAddress;
43 |
44 | switch(type) {
45 | case INITIALIZED:
46 | memory.createInitializedBlock(name, startAddress, dataStream, length, monitor, overlay);
47 | break;
48 | case UNINITIALIZED:
49 | memory.createUninitializedBlock(name, startAddress, length, overlay);
50 | break;
51 | case BIT_MAPPED:
52 | mappedAddress = addressSpace.getAddress(mappedTo);
53 | memory.createBitMappedBlock(name, startAddress, mappedAddress, length, overlay);
54 | break;
55 | case BYTE_MAPPED:
56 | mappedAddress = addressSpace.getAddress(mappedTo);
57 | memory.createByteMappedBlock(name, startAddress, mappedAddress, length, overlay);
58 | break;
59 | }
60 |
61 | MemoryBlock block = memory.getBlock(name);
62 | block.setRead((permissions & READ) != 0);
63 | block.setWrite((permissions & WRITE) != 0);
64 | block.setExecute((permissions & EXECUTE) != 0);
65 | block.setVolatile((permissions & VOLATILE) != 0);
66 | }
67 |
68 | public void create(Program program) throws LockException, MemoryConflictException, AddressOverflowException, CancelledException, DuplicateNameException {
69 | create(program.getMemory(), program.getAddressFactory().getDefaultAddressSpace());
70 | }
71 |
72 | public static MemoryBlockDescription initialized(
73 | long start,
74 | long length,
75 | String name,
76 | int permissions,
77 | byte[] data,
78 | boolean overlay,
79 | TaskMonitor monitor
80 | ) {
81 | MemoryBlockDescription block = new MemoryBlockDescription();
82 | block.start = start;
83 | block.length = length;
84 | block.name = name;
85 | block.permissions = permissions;
86 | block.type = MemoryBlockType.INITIALIZED;
87 | block.data = data;
88 | block.overlay = overlay;
89 | block.monitor = monitor;
90 |
91 | return block;
92 | }
93 |
94 | public static MemoryBlockDescription uninitialized(
95 | long start,
96 | long length,
97 | String name,
98 | int permissions,
99 | boolean overlay
100 | ) {
101 | MemoryBlockDescription block = new MemoryBlockDescription();
102 | block.start = start;
103 | block.length = length;
104 | block.name = name;
105 | block.permissions = permissions;
106 | block.type = MemoryBlockType.UNINITIALIZED;
107 | block.data = new byte[0];
108 | block.overlay = overlay;
109 |
110 | return block;
111 | }
112 |
113 | public static MemoryBlockDescription bitMapped(
114 | long start,
115 | long length,
116 | String name,
117 | int permissions,
118 | long mappedTo
119 | ) {
120 | MemoryBlockDescription block = new MemoryBlockDescription();
121 | block.start = start;
122 | block.length = length;
123 | block.name = name;
124 | block.permissions = permissions;
125 | block.type = MemoryBlockType.BIT_MAPPED;
126 | block.data = new byte[0];
127 | block.mappedTo = mappedTo;
128 | block.overlay = false;
129 |
130 | return block;
131 | }
132 |
133 | public static MemoryBlockDescription byteMapped(
134 | long start,
135 | long length,
136 | String name,
137 | int permissions,
138 | long mappedTo
139 | ) {
140 | MemoryBlockDescription block = new MemoryBlockDescription();
141 | block.start = start;
142 | block.length = length;
143 | block.name = name;
144 | block.permissions = permissions;
145 | block.type = MemoryBlockType.BYTE_MAPPED;
146 | block.data = new byte[0];
147 | block.mappedTo = mappedTo;
148 | block.overlay = false;
149 |
150 | return block;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/analyzer/GhidraNesAnalyzer.java:
--------------------------------------------------------------------------------
1 | /* ###
2 | * IP: GHIDRA
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ik.ghidranesrom.analyzer;
17 |
18 | import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
19 | import ghidra.app.services.AbstractAnalyzer;
20 | import ghidra.app.services.AnalysisPriority;
21 | import ghidra.app.services.AnalyzerType;
22 | import ghidra.app.services.ConsoleService;
23 | import ghidra.app.util.importer.MessageLog;
24 | import ghidra.framework.options.Options;
25 | import ghidra.program.model.address.AddressSetView;
26 | import ghidra.program.model.listing.Program;
27 | import ghidra.util.Msg;
28 | import ghidra.util.exception.CancelledException;
29 | import ghidra.util.task.TaskMonitor;
30 | import ik.ghidranesrom.wrappers.ProgramWrapper;
31 |
32 |
33 | /**
34 | * TODO: Provide class-level documentation that describes what this analyzer does.
35 | */
36 | public class GhidraNesAnalyzer extends AbstractAnalyzer {
37 | public static final String OPTION_NAME_GOES_HERE = "Option name goes here";
38 | private ConsoleService consoleService;
39 |
40 | public GhidraNesAnalyzer() {
41 |
42 | // TODO: Name the analyzer and give it a description.
43 |
44 | super("GhidraNesRom analyzer", "Rename labels", AnalyzerType.INSTRUCTION_ANALYZER);
45 | setSupportsOneTimeAnalysis(true);
46 | }
47 |
48 | @Override
49 | public boolean getDefaultEnablement(Program program) {
50 |
51 | // TODO: Return true if analyzer should be enabled by default
52 |
53 | return true;
54 | }
55 |
56 | @Override
57 | public boolean canAnalyze(Program program) {
58 | // TODO: Examine 'program' to determine of this analyzer should analyze it.
59 | return program.getLanguage().getProcessor().toString().startsWith("6502");
60 | }
61 |
62 | @Override
63 | public void registerOptions(Options options, Program program) {
64 |
65 | // TODO: If this analyzer has custom options, register them here
66 |
67 | options.registerOption(OPTION_NAME_GOES_HERE, false, null,
68 | "Option description goes here"
69 | );
70 | }
71 |
72 | @Override
73 | public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
74 | throws CancelledException {
75 | println("added");
76 | AutoAnalysisManager analysisManager = AutoAnalysisManager.getAnalysisManager(program);
77 | ConsoleService consoleService = analysisManager.getAnalysisTool().getService(ConsoleService.class);
78 | // TODO: Perform analysis when things get added to the 'program'. Return true if the
79 | // analysis succeeded.
80 |
81 | ProgramWrapper programWrapper = new ProgramWrapper(program);
82 | Worker analyzer = new Worker(programWrapper, new InMemorySymbolTagStorage());
83 | analyzer.analyzeBrandedLabels();
84 | analyzer.analyzeLoops();
85 | analyzer.renameLabels();
86 | return false;
87 | }
88 |
89 | @Override
90 | public void analysisEnded(Program program) {
91 | println("analysys ended");
92 | }
93 |
94 | private void println(String s) {
95 | Msg.info(this.getClass().getSimpleName(), s);
96 | }
97 |
98 | private void print(Program program, String s) {
99 | AutoAnalysisManager analysisManager = AutoAnalysisManager.getAnalysisManager(program);
100 | consoleService = analysisManager.getAnalysisTool().getService(ConsoleService.class);
101 | consoleService.print(s);
102 | }
103 |
104 | @Override
105 | protected void setPriority(AnalysisPriority priority) {
106 | super.setPriority(priority);
107 | }
108 |
109 | @Override
110 | protected void setDefaultEnablement(boolean b) {
111 | super.setDefaultEnablement(b);
112 | }
113 |
114 | @Override
115 | protected void setSupportsOneTimeAnalysis() {
116 | super.setSupportsOneTimeAnalysis();
117 | }
118 |
119 | @Override
120 | protected void setSupportsOneTimeAnalysis(boolean supportsOneTimeAnalysis) {
121 | super.setSupportsOneTimeAnalysis(supportsOneTimeAnalysis);
122 | }
123 |
124 | @Override
125 | protected void setPrototype() {
126 | super.setPrototype();
127 | }
128 |
129 | @Override
130 | public boolean removed(
131 | Program program,
132 | AddressSetView set,
133 | TaskMonitor monitor,
134 | MessageLog log
135 | ) throws CancelledException {
136 | return super.removed(program, set, monitor, log);
137 | }
138 |
139 | @Override
140 | public void optionsChanged(Options options, Program program) {
141 | super.optionsChanged(options, program);
142 | }
143 | }
144 |
145 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/src/main/java/ik/ghidranesrom/loader/GhidraNesLoader.java:
--------------------------------------------------------------------------------
1 | /* ###
2 | * IP: GHIDRA
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ik.ghidranesrom.loader;
17 |
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.util.*;
21 |
22 | import ghidra.app.util.Option;
23 | import ghidra.app.util.bin.ByteProvider;
24 | import ghidra.app.util.importer.MessageLog;
25 | import ghidra.app.util.opinion.AbstractLibrarySupportLoader;
26 | import ghidra.app.util.opinion.LoadSpec;
27 | import ghidra.framework.model.DomainObject;
28 | import ghidra.framework.store.LockException;
29 | import ghidra.program.model.address.AddressOutOfBoundsException;
30 | import ghidra.program.model.address.AddressOverflowException;
31 | import ghidra.program.model.lang.Language;
32 | import ghidra.program.model.lang.LanguageCompilerSpecPair;
33 | import ghidra.program.model.listing.Program;
34 | import ghidra.program.model.mem.MemoryAccessException;
35 | import ghidra.program.model.mem.MemoryConflictException;
36 | import ghidra.util.exception.CancelledException;
37 | import ghidra.util.exception.DuplicateNameException;
38 | import ghidra.util.exception.InvalidInputException;
39 | import ghidra.util.task.TaskMonitor;
40 | import ik.ghidranesrom.loader.exception.InvalidNesRomHeaderException;
41 | import ik.ghidranesrom.loader.exception.NesRomEofException;
42 | import ik.ghidranesrom.loader.exception.NesRomException;
43 | import ik.ghidranesrom.loader.exception.UnimplementedNesMapperException;
44 | import ik.ghidranesrom.loader.mapper.NesMapper;
45 | import ik.ghidranesrom.util.MemoryBlockDescription;
46 |
47 | /**
48 | * TODO: Provide class-level documentation that describes what this loader does.
49 | */
50 | public class GhidraNesLoader extends AbstractLibrarySupportLoader {
51 |
52 | @Override
53 | public String getName() {
54 | return "NES ROM";
55 | }
56 |
57 | @Override
58 | public Collection findSupportedLoadSpecs(ByteProvider provider) throws IOException {
59 | List loadSpecs = new ArrayList<>();
60 |
61 | InputStream bytes = provider.getInputStream(0);
62 |
63 | try {
64 | // Try to parse the ROM header (will throw an exception if parsing fails)
65 | new NesRomHeader(bytes);
66 |
67 | // If successful, add the load spec
68 | LanguageCompilerSpecPair languageCompilerSpecPair =
69 | new LanguageCompilerSpecPair("6502:LE:16:default", "default");
70 | LoadSpec loadSpec = new LoadSpec(this, 0, languageCompilerSpecPair, true);
71 | loadSpecs.add(loadSpec);
72 | } catch (NesRomException e) {
73 | // If parsing failed, do not add the load spec
74 | }
75 |
76 | return loadSpecs;
77 | }
78 |
79 | @Override
80 | protected void load(
81 | ByteProvider provider, LoadSpec loadSpec, List