├── 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 | 25 | 26 | 27 | 28 | 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 | ![NES game disassembly](.github/screenshots/ghidra-nes.png) 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