diff --git a/README.md b/README.md
index 36d4bcc..f350050 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,14 @@
This is a (WIP) simple extension to open .rpx and .rpl files with Ghidra.
+# Dependencies
+
+This loader uses the Gekko/Broadway processor definitions for Ghidra if found - it is recommended that this should be installed prior to using the loader.
+
+https://github.com/aldelaro5/ghidra-gekko-broadway-lang
+
+The loader will fallback to the default PowerPC processor if the Gekko/Broadway language is not found, but do not expect good results if the program uses any paired single instructions.
+
# Usage
Install the extension using the `Install Extensions` option inside Ghidra or extract the .zip manually into `[GHIDRA_ROOT]\Ghidra\Extensions`. Make sure to restart the program after installing.
diff --git a/data/languages/RPX.opinion b/data/languages/RPX.opinion
index e7bdf04..5ec29b7 100644
--- a/data/languages/RPX.opinion
+++ b/data/languages/RPX.opinion
@@ -1,5 +1,6 @@
-
-
-
+
+
+
+
diff --git a/src/main/java/cafeloader/Cafe_ElfExtension.java b/src/main/java/cafeloader/Cafe_ElfExtension.java
new file mode 100644
index 0000000..404bfb4
--- /dev/null
+++ b/src/main/java/cafeloader/Cafe_ElfExtension.java
@@ -0,0 +1,324 @@
+package cafeloader;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.format.elf.*;
+import ghidra.app.util.bin.format.elf.extend.*;
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.mem.Memory;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.program.model.data.*;
+import ghidra.program.model.lang.Register;
+import ghidra.util.*;
+import ghidra.util.exception.*;
+import ghidra.util.task.TaskMonitor;
+
+public class Cafe_ElfExtension extends ElfExtension {
+ /*
+ * Note that these values are not the REAL CafeOS section header types, we
+ * transform them to this in the RpxConversion stage because ElfSectionHeaderType
+ * expects an int > 0 and unfortunately the real CafeOS SHT values are invalid for this.
+ */
+ public static final ElfSectionHeaderType SHT_RPL_EXPORTS = new ElfSectionHeaderType(0x0CAFE001,
+ "SHT_RPL_EXPORTS", "Section contains RPL exports");
+ public static final ElfSectionHeaderType SHT_RPL_IMPORTS = new ElfSectionHeaderType(0x0CAFE002,
+ "SHT_RPL_IMPORTS", "Section contains RPL imports");
+ public static final ElfSectionHeaderType SHT_RPL_CRCS = new ElfSectionHeaderType(0x0CAFE003,
+ "SHT_RPL_CRCS", "Section contains RPL crcs");
+ public static final ElfSectionHeaderType SHT_RPL_FILEINFO = new ElfSectionHeaderType(0x0CAFE004,
+ "SHT_RPL_FILEINFO", "Section contains RPL file info");
+
+ public static final int RPL_FILEINFO_V3 = 0xCAFE0300;
+ public static final int RPL_FILEINFO_V4_1 = 0xCAFE0401;
+ public static final int RPL_FILEINFO_V4_2 = 0xCAFE0402;
+
+ @Override
+ public boolean canHandle(ElfHeader elf) {
+ return elf instanceof RplHeader;
+ }
+
+ @Override
+ public boolean canHandle(ElfLoadHelper elfLoadHelper) {
+ return canHandle(elfLoadHelper.getElfHeader());
+ }
+
+ @Override
+ public String getDataTypeSuffix() {
+ return "_PPC";
+ }
+
+ /*
+ * Process elf symbols which are in SHT_RPL_IMPORTS sections to be external imports.
+ */
+ @Override
+ public Address evaluateElfSymbol(ElfLoadHelper elfLoadHelper, ElfSymbol elfSymbol,
+ Address address, boolean isExternal) {
+ ElfHeader elf = elfLoadHelper.getElfHeader();
+ ElfSectionHeader section = elf.getSections()[elfSymbol.getSectionHeaderIndex()];
+
+ if (section.getType() != SHT_RPL_IMPORTS.value) {
+ return address;
+ }
+
+ String name = elfSymbol.getNameAsString();
+ if (name == null) {
+ return address;
+ }
+
+ if (!elfSymbol.isFunction() && !elfSymbol.isObject()) {
+ return address;
+ }
+
+ String sectionName = section.getNameAsString();
+ if (!sectionName.startsWith(".fimport_") && !sectionName.startsWith(".dimport_")) {
+ return address;
+ }
+
+ String rplName = sectionName.split("import_")[1];
+ if (!rplName.endsWith(".rpl")) {
+ rplName += ".rpl";
+ }
+
+ try {
+ Program program = elfLoadHelper.getProgram();
+ elfLoadHelper.setElfSymbolAddress(elfSymbol, address);
+ elfLoadHelper.createSymbol(address, name, true, elfSymbol.isAbsolute(), null);
+
+ if (elfSymbol.isFunction()) {
+ program.getExternalManager().addExtFunction(rplName, name, address,
+ SourceType.IMPORTED);
+ } else if (elfSymbol.isObject()) {
+ program.getExternalManager().addExtLocation(rplName, name, address,
+ SourceType.IMPORTED);
+ }
+
+ return null;
+ } catch (InvalidInputException e) {
+ } catch (DuplicateNameException e) {
+ }
+
+ return address;
+ }
+
+ @Override
+ public Boolean isSectionWritable(ElfSectionHeader section) {
+ // For some reason .rpl files have .rodata marked with W flag,
+ // forcing it to read only will help improve decompiler output.
+ String name = section.getNameAsString();
+ if (name != null && name.contentEquals(".rodata")) {
+ return false;
+ }
+
+ // Force .dimport section to writeable so compiler does not inline
+ // the value... even though its external...
+ // TODO: Maybe there is a better way to define .dimport/.fimport
+ // sections as not real loaded in memory sections so that the
+ // compiler does not inline it's values?
+ if (name != null && name.startsWith(".dimport")) {
+ return true;
+ }
+
+ return (section.getFlags() & ElfSectionHeaderConstants.SHF_WRITE) != 0;
+ }
+
+ @Override
+ public void processElf(ElfLoadHelper elfLoadHelper, TaskMonitor monitor)
+ throws CancelledException {
+ ElfHeader elf = elfLoadHelper.getElfHeader();
+ for (ElfSectionHeader sectionHeader : elf.getSections()) {
+ int headertype = sectionHeader.getType();
+ if (headertype == SHT_RPL_CRCS.value) {
+ processRplCrcs(elfLoadHelper, sectionHeader);
+ } else if (headertype == SHT_RPL_FILEINFO.value) {
+ processRplFileInfo(elfLoadHelper, sectionHeader);
+ } else if (headertype == SHT_RPL_IMPORTS.value) {
+ processRplImports(elfLoadHelper, sectionHeader);
+ } else if (headertype == SHT_RPL_EXPORTS.value) {
+ processRplExports(elfLoadHelper, sectionHeader);
+ }
+ }
+ }
+
+ private void processRplImports(ElfLoadHelper elfLoadHelper, ElfSectionHeader sectionHeader) {
+ // Clear the section data otherwise analysis will identify strings in it.
+ Address sectionAddress = elfLoadHelper.findLoadAddress(sectionHeader, 0);
+ int sectionSize = (int) sectionHeader.getSize();
+ elfLoadHelper.createUndefinedData(sectionAddress, sectionSize);
+
+ byte[] zeroes = new byte[sectionSize];
+ try {
+ elfLoadHelper.getProgram().getMemory().setBytes(sectionAddress, zeroes);
+ } catch (MemoryAccessException e) {
+ }
+ }
+
+ private void processRplExports(ElfLoadHelper elfLoadHelper, ElfSectionHeader sectionHeader) {
+ String sectionName = sectionHeader.getNameAsString();
+ if (sectionName.contentEquals(".dexports")) {
+ // Create symbols for data exports
+ BinaryReader reader = elfLoadHelper.getElfHeader().getReader();
+ reader.setPointerIndex(sectionHeader.getOffset());
+
+ try {
+ int count = reader.readNextInt();
+ /* int signature = */ reader.readNextInt();
+ for (int i = 0; i < count; ++i) {
+ int value = reader.readNextInt();
+ int nameOffset = reader.readNextInt();
+ /* boolean isTlsExport = (nameOffset & 0x80000000) != 0; */
+ String name = reader.readAsciiString(sectionHeader.getOffset() + (nameOffset & 0x7FFFFFFF));
+ elfLoadHelper.createSymbol(elfLoadHelper.getDefaultAddress(value), name, true, false, null);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (InvalidInputException e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Clear the section data otherwise analysis will identify strings in it.
+ Address sectionAddress = elfLoadHelper.findLoadAddress(sectionHeader, 0);
+ int sectionSize = (int) sectionHeader.getSize();
+ elfLoadHelper.createUndefinedData(sectionAddress, sectionSize);
+
+ byte[] zeroes = new byte[sectionSize];
+ try {
+ elfLoadHelper.getProgram().getMemory().setBytes(sectionAddress, zeroes);
+ } catch (MemoryAccessException e) {
+ }
+ }
+
+ private void processRplCrcs(ElfLoadHelper elfLoadHelper, ElfSectionHeader sectionHeader) {
+ Address address = elfLoadHelper.findLoadAddress(sectionHeader, 0);
+ if (address == null) {
+ return;
+ }
+
+ try {
+ for (long i = 0; i < sectionHeader.getSize(); i += 4) {
+ elfLoadHelper.createData(address.add(i), DWordDataType.dataType);
+ }
+ } catch (AddressOutOfBoundsException e) {
+ }
+ }
+
+ private void processRplFileInfo(ElfLoadHelper elfLoadHelper, ElfSectionHeader sectionHeader) {
+ Address fileInfoAddr = elfLoadHelper.findLoadAddress(sectionHeader, 0);
+ if (fileInfoAddr == null) {
+ return;
+ }
+
+ int version = RPL_FILEINFO_V3;
+ Program program = elfLoadHelper.getProgram();
+ Memory memory = program.getMemory();
+ try {
+ version = memory.getInt(fileInfoAddr);
+ } catch (MemoryAccessException e) {
+ Msg.warn(this, "Failed to read RplFileInfo version");
+ }
+
+ Structure fileInfo =
+ new StructureDataType(new CategoryPath("/ELF"), "Elf32_RplFileInfo", 0);
+
+ int filenameOffset = 0;
+ int sdaBase = 0;
+ int sda2Base = 0;
+ if (version >= RPL_FILEINFO_V3) {
+ fileInfo.add(DWordDataType.dataType, "version", null);
+ fileInfo.add(DWordDataType.dataType, "textSize", null);
+ fileInfo.add(DWordDataType.dataType, "textAlign", null);
+ fileInfo.add(DWordDataType.dataType, "dataSize", null);
+ fileInfo.add(DWordDataType.dataType, "dataAlign", null);
+ fileInfo.add(DWordDataType.dataType, "loadSize", null);
+ fileInfo.add(DWordDataType.dataType, "loadAlign", null);
+ fileInfo.add(DWordDataType.dataType, "tempSize", null);
+ fileInfo.add(DWordDataType.dataType, "trampAdjust", null);
+ fileInfo.add(DWordDataType.dataType, "sdaBase", null);
+ fileInfo.add(DWordDataType.dataType, "sda2Base", null);
+ fileInfo.add(DWordDataType.dataType, "stackSize", null);
+ fileInfo.add(DWordDataType.dataType, "filenameOffset", null);
+
+ try {
+ sdaBase = memory.getInt(fileInfoAddr.add(0x24));
+ sda2Base = memory.getInt(fileInfoAddr.add(0x28));
+ filenameOffset = memory.getInt(fileInfoAddr.add(0x30));
+ } catch (MemoryAccessException e) {
+ Msg.warn(this, "Failed to read filenameOffset");
+ }
+ }
+
+ Address minAddress = program.getAddressFactory().getDefaultAddressSpace().getMinAddress();
+ Address maxAddress = program.getAddressFactory().getDefaultAddressSpace().getMaxAddress();
+ if (sdaBase != 0 && minAddress != null && maxAddress != null) {
+ Register r13 = elfLoadHelper.getProgram().getRegister("r13");
+ try {
+ program.getProgramContext().setValue(r13, minAddress, maxAddress, BigInteger.valueOf(sdaBase));
+ } catch (ContextChangeException e) {
+ Msg.warn(this, "Error setting r13 to sdabase: " + e);
+ }
+ }
+
+ if (sda2Base != 0 && minAddress != null && maxAddress != null) {
+ Register r2 = elfLoadHelper.getProgram().getRegister("r2");
+ try {
+ program.getProgramContext().setValue(r2, minAddress, maxAddress, BigInteger.valueOf(sda2Base));
+ } catch (ContextChangeException e) {
+ Msg.warn(this, "Error setting r2 to sda2base: " + e);
+ }
+ }
+
+ int tagOffset = 0;
+ if (version >= RPL_FILEINFO_V4_1) {
+ fileInfo.add(DWordDataType.dataType, "flags", null);
+ fileInfo.add(DWordDataType.dataType, "heapSize", null);
+ fileInfo.add(DWordDataType.dataType, "tagOffset", null);
+
+ try {
+ tagOffset = memory.getInt(fileInfoAddr.add(0x3C));
+ } catch (MemoryAccessException e) {
+ Msg.warn(this, "Failed to read tagOffset");
+ }
+ }
+
+ if (version >= RPL_FILEINFO_V4_2) {
+ fileInfo.add(DWordDataType.dataType, "minVersion", null);
+ fileInfo.add(DWordDataType.dataType, "compressionLevel", null);
+ fileInfo.add(DWordDataType.dataType, "trampAddition", null);
+ fileInfo.add(DWordDataType.dataType, "fileInfoPad", null);
+ fileInfo.add(DWordDataType.dataType, "cafeSdkVersion", null);
+ fileInfo.add(DWordDataType.dataType, "cafeSdkRevision", null);
+ fileInfo.add(WordDataType.dataType, "tlsModuleIndex", null);
+ fileInfo.add(WordDataType.dataType, "tlsAlignShift", null);
+ fileInfo.add(DWordDataType.dataType, "runtimeFileInfoSize", null);
+ }
+ elfLoadHelper.createData(fileInfoAddr, fileInfo);
+
+ // Mark filename as a string
+ if (filenameOffset != 0) {
+ try {
+ elfLoadHelper.createData(fileInfoAddr.add(filenameOffset), TerminatedStringDataType.dataType);
+ } catch (AddressOutOfBoundsException e) {
+ }
+ }
+
+ // Mark tags as strings
+ if (tagOffset != 0) {
+ try {
+ Address tagAddress = fileInfoAddr.add(tagOffset);
+ while (true) {
+ Data d = elfLoadHelper.createData(tagAddress, TerminatedStringDataType.dataType);
+ int length = d.getLength();
+ if (length == 0) {
+ break;
+ }
+ tagAddress = tagAddress.add(length);
+ }
+ } catch (AddressOutOfBoundsException e) {
+ }
+ }
+ }
+}
diff --git a/src/main/java/cafeloader/Cafe_ElfRelocationConstants.java b/src/main/java/cafeloader/Cafe_ElfRelocationConstants.java
new file mode 100644
index 0000000..7d484d7
--- /dev/null
+++ b/src/main/java/cafeloader/Cafe_ElfRelocationConstants.java
@@ -0,0 +1,35 @@
+package cafeloader;
+
+public class Cafe_ElfRelocationConstants {
+ public static final int R_PPC_NONE = 0;
+ public static final int R_PPC_ADDR32 = 1;
+ public static final int R_PPC_ADDR16_LO = 4;
+ public static final int R_PPC_ADDR16_HI = 5;
+ public static final int R_PPC_ADDR16_HA = 6;
+ public static final int R_PPC_REL24 = 10;
+ public static final int R_PPC_REL14 = 11;
+ public static final int R_PPC_DTPMOD32 = 68;
+ public static final int R_PPC_DTPREL32 = 78;
+ public static final int R_PPC_EMB_SDA21 = 109;
+ public static final int R_PPC_EMB_RELSDA = 116;
+ public static final int R_PPC_DIAB_SDA21_LO = 180;
+ public static final int R_PPC_DIAB_SDA21_HI = 181;
+ public static final int R_PPC_DIAB_SDA21_HA = 182;
+ public static final int R_PPC_DIAB_RELSDA_LO = 183;
+ public static final int R_PPC_DIAB_RELSDA_HI = 184;
+ public static final int R_PPC_DIAB_RELSDA_HA = 185;
+ public static final int R_PPC_GHS_REL16_HA = 251;
+ public static final int R_PPC_GHS_REL16_HI = 252;
+ public static final int R_PPC_GHS_REL16_LO = 253;
+
+ // Masks for manipulating Power PC relocation targets
+ public static final int PPC_WORD32 = 0xFFFFFFFF;
+ public static final int PPC_WORD30 = 0xFFFFFFFC;
+ public static final int PPC_LOW24 = 0x03FFFFFC;
+ public static final int PPC_LOW14 = 0x0020FFFC;
+ public static final int PPC_HALF16 = 0xFFFF;
+
+ private Cafe_ElfRelocationConstants() {
+ // no construct
+ }
+}
diff --git a/src/main/java/cafeloader/Cafe_ElfRelocationHandler.java b/src/main/java/cafeloader/Cafe_ElfRelocationHandler.java
new file mode 100644
index 0000000..9fb2d29
--- /dev/null
+++ b/src/main/java/cafeloader/Cafe_ElfRelocationHandler.java
@@ -0,0 +1,149 @@
+package cafeloader;
+
+import ghidra.app.util.bin.format.elf.*;
+import ghidra.app.util.bin.format.elf.relocation.ElfRelocationContext;
+import ghidra.app.util.bin.format.elf.relocation.ElfRelocationHandler;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.Memory;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.util.Msg;
+import ghidra.util.exception.InvalidInputException;
+import ghidra.util.exception.NotFoundException;
+import ghidra.program.model.symbol.ExternalLocation;
+import ghidra.program.model.symbol.RefType;
+import ghidra.program.model.symbol.SourceType;
+
+public class Cafe_ElfRelocationHandler extends ElfRelocationHandler {
+ @Override
+ public boolean canRelocate(ElfHeader elf) {
+ return elf instanceof RplHeader;
+ }
+
+ @Override
+ public void relocate(ElfRelocationContext elfRelocationContext, ElfRelocation relocation,
+ Address relocationAddress) throws MemoryAccessException, NotFoundException {
+ int type = relocation.getType();
+ if (type == Cafe_ElfRelocationConstants.R_PPC_NONE) {
+ return;
+ }
+
+ ElfHeader elf = elfRelocationContext.getElfHeader();
+ Program program = elfRelocationContext.getProgram();
+ Memory memory = program.getMemory();
+ int symbolIndex = relocation.getSymbolIndex();
+ int addend = (int) relocation.getAddend();
+ int offset = (int) relocationAddress.getOffset();
+ ElfSymbol sym = elfRelocationContext.getSymbol(symbolIndex);
+ int symbolValue = (int) elfRelocationContext.getSymbolValue(sym);
+ int oldValue = memory.getInt(relocationAddress);
+ int newValue = 0;
+
+ /*
+ * If the symbol is in a SHT_RPL_IMPORTS section then we must add a memory
+ * reference because it will be too far away for the actual relocation to
+ * be valid itself.
+ */
+ ElfSectionHeader symbolSection = elf.getSections()[sym.getSectionHeaderIndex()];
+ if (symbolSection.getType() == Cafe_ElfExtension.SHT_RPL_IMPORTS.value) {
+ String symbolSectionName = symbolSection.getNameAsString();
+ boolean isDataImport = false;
+ if (symbolSectionName.startsWith(".dimport_")) {
+ program.getReferenceManager().addMemoryReference(relocationAddress,
+ elfRelocationContext.getSymbolAddress(sym), RefType.DATA, SourceType.IMPORTED, 0);
+ isDataImport = true;
+ } else if (symbolSectionName.startsWith(".fimport_")) {
+ program.getReferenceManager().addMemoryReference(relocationAddress,
+ elfRelocationContext.getSymbolAddress(sym), RefType.UNCONDITIONAL_CALL, SourceType.IMPORTED, 0);
+ }
+
+ String rplName = symbolSectionName.split("import_")[1];
+ if (!rplName.endsWith(".rpl")) {
+ rplName += ".rpl";
+ }
+
+ ExternalLocation location = program.getExternalManager().getUniqueExternalLocation(rplName, sym.getNameAsString());
+ if (location != null) {
+ try {
+ if (isDataImport) {
+ program.getReferenceManager().addExternalReference(relocationAddress, 1,
+ location, SourceType.IMPORTED, RefType.DATA);
+ } else {
+ program.getReferenceManager().addExternalReference(relocationAddress, 1,
+ location, SourceType.IMPORTED, RefType.UNCONDITIONAL_CALL);
+ }
+ } catch (InvalidInputException e) {
+ Msg.warn(this, "addExternalReference failed with " + e);
+ }
+ } else {
+ Msg.warn(this, "Failed to find location for " + sym.getNameAsString());
+ }
+ }
+
+ switch (type) {
+ case Cafe_ElfRelocationConstants.R_PPC_ADDR32:
+ newValue = symbolValue + addend;
+ memory.setInt(relocationAddress, newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_ADDR16_LO:
+ newValue = symbolValue + addend;
+ memory.setShort(relocationAddress, (short) newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_ADDR16_HI:
+ newValue = (symbolValue + addend) >> 16;
+ memory.setShort(relocationAddress, (short) newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_ADDR16_HA:
+ newValue = (symbolValue + addend + 0x8000) >> 16;
+ memory.setShort(relocationAddress, (short) newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_REL24:
+ newValue = (symbolValue + addend - offset) >> 2;
+ newValue = ((newValue << 2) & Cafe_ElfRelocationConstants.PPC_LOW24);
+ newValue = (oldValue & ~Cafe_ElfRelocationConstants.PPC_LOW24) | newValue;
+ memory.setInt(relocationAddress, newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_REL14:
+ newValue = (symbolValue + addend - offset) >> 2;
+ newValue = (oldValue & ~Cafe_ElfRelocationConstants.PPC_LOW14) |
+ ((newValue << 2) & Cafe_ElfRelocationConstants.PPC_LOW14);
+ memory.setInt(relocationAddress, newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_GHS_REL16_HA:
+ newValue = (symbolValue + addend - offset + 0x8000) >> 16;
+ memory.setShort(relocationAddress, (short) newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_GHS_REL16_HI:
+ newValue = (symbolValue + addend - offset) >> 16;
+ memory.setShort(relocationAddress, (short) newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_GHS_REL16_LO:
+ newValue = (symbolValue + addend - offset);
+ memory.setShort(relocationAddress, (short) newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_DTPREL32:
+ newValue = symbolValue + addend;
+ memory.setInt(relocationAddress, newValue);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_DTPMOD32:
+ // TODO: Do we need a tlsModuleIndex?
+ // *virt_cast(target) = tlsModuleIndex;
+ memory.setInt(relocationAddress, 0);
+ break;
+ case Cafe_ElfRelocationConstants.R_PPC_EMB_SDA21:
+ // TODO: SDA relocations require sda / sda2 base from SHT_RPL_FILEINFO section
+ case Cafe_ElfRelocationConstants.R_PPC_EMB_RELSDA:
+ case Cafe_ElfRelocationConstants.R_PPC_DIAB_SDA21_LO:
+ case Cafe_ElfRelocationConstants.R_PPC_DIAB_SDA21_HI:
+ case Cafe_ElfRelocationConstants.R_PPC_DIAB_SDA21_HA:
+ case Cafe_ElfRelocationConstants.R_PPC_DIAB_RELSDA_LO:
+ case Cafe_ElfRelocationConstants.R_PPC_DIAB_RELSDA_HI:
+ case Cafe_ElfRelocationConstants.R_PPC_DIAB_RELSDA_HA:
+ default:
+ String symbolName = sym.getNameAsString();
+ markAsUnhandled(program, relocationAddress, type, symbolIndex, symbolName,
+ elfRelocationContext.getLog());
+ break;
+ }
+ }
+}
diff --git a/src/main/java/cafeloader/RplConverter.java b/src/main/java/cafeloader/RplConverter.java
new file mode 100644
index 0000000..38f11b8
--- /dev/null
+++ b/src/main/java/cafeloader/RplConverter.java
@@ -0,0 +1,127 @@
+package cafeloader;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+import generic.continues.RethrowContinuesFactory;
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader;
+import ghidra.app.util.bin.format.elf.ElfConstants;
+import ghidra.app.util.bin.format.elf.ElfException;
+import ghidra.app.util.bin.format.elf.ElfSectionHeader;
+import ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants;
+import ghidra.app.util.bin.format.elf.RplSectionHeader;
+import ghidra.util.task.TaskMonitor;
+import ghidra.util.*;
+
+public class RplConverter {
+ public static final int SHF_RPL_ZLIB = 0x08000000;
+
+ public static final int SHT_RPL_EXPORTS = 0x80000001;
+ public static final int SHT_RPL_IMPORTS = 0x80000002;
+ public static final int SHT_RPL_CRCS = 0x80000003;
+ public static final int SHT_RPL_FILEINFO = 0x80000004;
+
+ public static final byte ELFOSABI_CAFE = (byte) 0xCA;
+ public static final byte ELFOSABI_VERSION_CAFE = (byte) 0xFE;
+
+ public static byte[] convertRpl(ByteProvider byteProvider, TaskMonitor monitor)
+ throws ElfException, IOException, DataFormatException {
+ // Read elf header
+ RplHeader elfHeader = RplHeader.createRplHeader(RethrowContinuesFactory.INSTANCE, byteProvider);
+ BinaryReader reader = elfHeader.getReader();
+
+ // Write elf header
+ ByteArrayOutputStream out = new ByteArrayOutputStream((int) byteProvider.length());
+ DataConverter dc = BigEndianDataConverter.INSTANCE;
+ out.write(ElfConstants.MAGIC_BYTES);
+ out.write(ElfConstants.ELF_CLASS_32);
+ out.write(ElfConstants.ELF_DATA_BE);
+ out.write(ElfConstants.EV_CURRENT);
+ out.write(ELFOSABI_CAFE);
+ out.write(ELFOSABI_VERSION_CAFE);
+ out.write(new byte[7]); // ident padding
+ out.write(dc.getBytes(elfHeader.e_type()));
+ out.write(dc.getBytes(elfHeader.e_machine()));
+ out.write(dc.getBytes(elfHeader.e_version()));
+ out.write(dc.getBytes((int) elfHeader.e_entry()));
+ out.write(dc.getBytes((int) 0)); // phoff
+ out.write(dc.getBytes((int) 0x40)); // shoff
+ out.write(dc.getBytes(elfHeader.e_flags()));
+ out.write(dc.getBytes(elfHeader.e_ehsize()));
+ out.write(dc.getBytes((short) 0)); // phentsize
+ out.write(dc.getBytes((short) 0)); // phnum
+ out.write(dc.getBytes(elfHeader.e_shentsize()));
+ out.write(dc.getBytes(elfHeader.e_shnum()));
+ out.write(dc.getBytes(elfHeader.e_shstrndx()));
+ out.write(new byte[0x40 - 0x34]); // padding until section headers
+
+ // Read sections
+ long sectionDataOffset = elfHeader.e_shoff() + (elfHeader.e_shnum() * elfHeader.e_shentsize());
+ ByteArrayOutputStream sectionData = new ByteArrayOutputStream();
+
+ for (int i = 0; i < elfHeader.e_shnum(); ++i) {
+ long index = elfHeader.e_shoff() + (i * elfHeader.e_shentsize());
+ reader.setPointerIndex(index);
+ ElfSectionHeader sectionHeader = RplSectionHeader.createElfSectionHeader((FactoryBundledWithBinaryReader) reader, elfHeader);
+ long size = sectionHeader.getSize();
+ reader.setPointerIndex(sectionHeader.getOffset());
+
+ // Read & write section data
+ if (sectionHeader.getType() != ElfSectionHeaderConstants.SHT_NOBITS) {
+ if ((sectionHeader.getFlags() & SHF_RPL_ZLIB) == SHF_RPL_ZLIB) {
+ size = reader.readNextInt();
+ byte[] inflatedData = new byte[(int) size];
+ byte[] deflatedData = reader.readNextByteArray((int) sectionHeader.getSize() - 4);
+ Inflater inflater = new Inflater();
+ inflater.setInput(deflatedData);
+ inflater.inflate(inflatedData);
+ inflater.end();
+ sectionData.write(inflatedData);
+ } else if (size > 0) {
+ byte[] inflatedData = reader.readNextByteArray((int) size);
+ sectionData.write(inflatedData);
+ }
+ }
+
+ // Ghidra reads section type as a signed integer which breaks the
+ // rpl section types, so we translate them to a different value.
+ int sectionType = sectionHeader.getType();
+ if (sectionType == SHT_RPL_EXPORTS) {
+ sectionType = Cafe_ElfExtension.SHT_RPL_EXPORTS.value;
+ } else if (sectionType == SHT_RPL_IMPORTS) {
+ sectionType = Cafe_ElfExtension.SHT_RPL_IMPORTS.value;
+ } else if (sectionType == SHT_RPL_FILEINFO) {
+ sectionType = Cafe_ElfExtension.SHT_RPL_FILEINFO.value;
+ } else if (sectionType == SHT_RPL_CRCS) {
+ sectionType = Cafe_ElfExtension.SHT_RPL_CRCS.value;
+ }
+
+ // Write section header
+ out.write(dc.getBytes(sectionHeader.getName()));
+ out.write(dc.getBytes(sectionType));
+ out.write(dc.getBytes((int) sectionHeader.getFlags()));
+ out.write(dc.getBytes((int) sectionHeader.getAddress()));
+
+ if (sectionHeader.getType() != ElfSectionHeaderConstants.SHT_NOBITS && size > 0) {
+ out.write(dc.getBytes((int) sectionDataOffset));
+ out.write(dc.getBytes((int) size));
+ sectionDataOffset += size;
+ } else {
+ out.write(dc.getBytes((int) sectionHeader.getOffset()));
+ out.write(dc.getBytes((int) sectionHeader.getSize()));
+ }
+
+ out.write(dc.getBytes(sectionHeader.getLink()));
+ out.write(dc.getBytes(sectionHeader.getInfo()));
+ out.write(dc.getBytes((int) sectionHeader.getAddressAlignment()));
+ out.write(dc.getBytes((int) sectionHeader.getEntrySize()));
+ }
+
+ out.write(sectionData.toByteArray());
+ return out.toByteArray();
+ }
+}
diff --git a/src/main/java/cafeloader/RplHeader.java b/src/main/java/cafeloader/RplHeader.java
new file mode 100644
index 0000000..0c998ed
--- /dev/null
+++ b/src/main/java/cafeloader/RplHeader.java
@@ -0,0 +1,33 @@
+package cafeloader;
+
+import java.lang.Throwable;
+
+import generic.continues.GenericFactory;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.format.elf.ElfException;
+import ghidra.app.util.bin.format.elf.ElfHeader;
+
+public class RplHeader extends ElfHeader
+{
+ public static RplHeader createRplHeader(GenericFactory factory, ByteProvider provider)
+ throws ElfException {
+ RplHeader elfHeader = (RplHeader) factory.create(RplHeader.class);
+ elfHeader.initElfHeader(factory, provider);
+ return elfHeader;
+ }
+
+ @Override
+ public short e_machine() {
+ // Hack to force use of Cafe_ElfRelocationHandler and Cafe_ElfExtension instead of PowerPC_*
+ StackTraceElement[] trace = new Throwable().getStackTrace();
+ if (trace.length >= 6 &&
+ ((trace[6].getClassName() == "ghidra.app.util.bin.format.elf.relocation.PowerPC_ElfRelocationHandler" &&
+ trace[6].getMethodName() == "canRelocate") ||
+ (trace[6].getClassName() == "ghidra.app.util.bin.format.elf.extend.PowerPC_ElfExtension" &&
+ trace[6].getMethodName() == "canHandle"))) {
+ return 0;
+ }
+
+ return super.e_machine();
+ }
+}
diff --git a/src/main/java/de/mas/ghidra/utils/Utils.java b/src/main/java/de/mas/ghidra/utils/Utils.java
deleted file mode 100644
index 1e6d4de..0000000
--- a/src/main/java/de/mas/ghidra/utils/Utils.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package de.mas.ghidra.utils;
-
-import java.nio.ByteBuffer;
-
-import ghidra.util.task.TaskMonitor;
-
-public class Utils {
- private Utils() {
- }
-
- /**
- * Grows a ByteBuffer if needed.
- *
- * @param buffer the original buffer
- * @param size the needed size.
- * @return A byte buffer with the expected size. If the buffer was big enough,
- * the original buffer will be returned, otherwise a new one will be
- * created.
- */
- public static ByteBuffer checkAndGrowByteBuffer(ByteBuffer buffer, long size) {
- // This probably the worst way to do this.
- if (buffer.remaining() < size) {
- ByteBuffer newBuffer = ByteBuffer.allocate((int) (buffer.capacity() + size - buffer.remaining()));
- newBuffer.put(buffer.array());
- return newBuffer;
- }
- return buffer;
- }
-
- public static String stringFromStringTable(byte[] stringTable, int index) {
- ByteBuffer buf = ByteBuffer.wrap(stringTable);
-
- int pos = index;
-
- StringBuilder result = new StringBuilder();
-
- for (byte b; (b = buf.get(pos)) != 0; pos++) {
- result.append((char) b);
- }
-
- return result.toString();
- }
-
- public static void logWrapper(String msg, TaskMonitor monitor) {
- if (monitor != null) {
- monitor.setMessage(msg);
- }
- System.out.println(msg);
- }
-
-}
diff --git a/src/main/java/de/mas/ghidra/wiiu/RPXUtils.java b/src/main/java/de/mas/ghidra/wiiu/RPXUtils.java
deleted file mode 100644
index d073b69..0000000
--- a/src/main/java/de/mas/ghidra/wiiu/RPXUtils.java
+++ /dev/null
@@ -1,189 +0,0 @@
-package de.mas.ghidra.wiiu;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.zip.DataFormatException;
-import java.util.zip.Inflater;
-
-import de.mas.ghidra.utils.Utils;
-import generic.continues.RethrowContinuesFactory;
-import ghidra.app.util.bin.ByteProvider;
-import ghidra.app.util.bin.format.elf.ElfConstants;
-import ghidra.app.util.bin.format.elf.ElfException;
-import ghidra.app.util.bin.format.elf.ElfHeader;
-import ghidra.app.util.bin.format.elf.ElfSectionHeader;
-import ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants;
-import ghidra.app.util.bin.format.elf.ElfSymbol;
-import ghidra.util.exception.CancelledException;
-import ghidra.util.task.TaskMonitor;
-
-public class RPXUtils {
- private static byte[] RPX_MAGIC = new byte[] { 0x7F, 0x45, 0x4C, 0x46, 0x01, 0x02, 0x01, (byte) 0xCA, (byte) 0xFE };
- public static final int SHF_RPL_ZLIB = 0x08000000;
- public static final int SHT_NOBITS = 0x00000008;
-
- public static final int SHT_RPL_EXPORTS = 0x80000001;
- public static final int SHT_RPL_IMPORTS = 0x80000002;
- public static final int SHT_RPL_CRCS = 0x80000003;
- public static final int SHT_RPL_FILEINFO = 0x80000004;
-
- public static byte[] convertRPX(ByteProvider bProvider, TaskMonitor monitor)
- throws ElfException, IOException, CancelledException, DataFormatException {
- ElfHeader elfFile = ElfHeader.createElfHeader(RethrowContinuesFactory.INSTANCE, bProvider);
- elfFile.parse();
-
- ByteBuffer buffer = ByteBuffer.allocate(0);
-
- long shdr_elf_offset = elfFile.e_ehsize() & 0xFFFFFFFF;
- long shdr_data_elf_offset = shdr_elf_offset + elfFile.e_shnum() * elfFile.e_shentsize();
-
- // Let's get / decompress the section header string table at first.
- ElfSectionHeader sh_str_sh = elfFile.getSections()[elfFile.e_shstrndx()];
- byte[] sh_str_sh_data = new byte[0];
- if (sh_str_sh.getOffset() != 0) {
- if ((sh_str_sh.getFlags() & SHT_NOBITS) != SHT_NOBITS) {
- sh_str_sh_data = sh_str_sh.getData();
- if ((sh_str_sh.getFlags() & SHF_RPL_ZLIB) == SHF_RPL_ZLIB) {
- long section_size_inflated = ByteBuffer.wrap(Arrays.copyOf(sh_str_sh_data, 4)).getInt()
- & 0xFFFFFFFF;
- Inflater inflater = new Inflater();
- inflater.setInput(sh_str_sh_data, 4, (int) sh_str_sh.getSize() - 4); // the first byte is the size
-
- byte[] decompressed = new byte[(int) section_size_inflated];
-
- inflater.inflate(decompressed);
- inflater.end();
-
- sh_str_sh_data = decompressed;
- }
- }
- }
-
- for (ElfSectionHeader h : elfFile.getSections()) {
- monitor.checkCanceled();
- long curSize = h.getSize();
- long flags = h.getFlags();
- long offset = h.getOffset();
- String sectionName = Utils.stringFromStringTable(sh_str_sh_data, h.getName());
-
- if (offset != 0) {
- if ((flags & SHT_NOBITS) != SHT_NOBITS) {
- byte[] data = h.getData();
- if (h.getType() == SHT_RPL_CRCS || h.getType() == SHT_RPL_FILEINFO) {
- data = new byte[0];
- curSize = 0;
- } else {
- if ((flags & SHF_RPL_ZLIB) == SHF_RPL_ZLIB) {
- Utils.logWrapper(
- "Decompressing section " + Utils.stringFromStringTable(sh_str_sh_data, h.getName()),
- monitor);
- long section_size_inflated = ByteBuffer.wrap(Arrays.copyOf(data, 4)).getInt() & 0xFFFFFFFF;
- Inflater inflater = new Inflater();
- inflater.setInput(data, 4, (int) h.getSize() - 4); // the first byte is the size
-
- byte[] decompressed = new byte[(int) section_size_inflated];
-
- inflater.inflate(decompressed);
-
- inflater.end();
-
- // Is this alignment really necessary?
- curSize = (section_size_inflated + 0x03) & ~0x3;
- flags &= ~SHF_RPL_ZLIB;
- data = decompressed;
- }
- }
-
- long newEnd = shdr_data_elf_offset + curSize;
-
- buffer = Utils.checkAndGrowByteBuffer(buffer, newEnd);
- buffer.position((int) shdr_data_elf_offset);
- buffer.put(data);
- offset = shdr_data_elf_offset;
- shdr_data_elf_offset += curSize;
- }
- }
-
- if (h.getType() == ElfSectionHeaderConstants.SHT_SYMTAB
- || h.getType() == ElfSectionHeaderConstants.SHT_DYNSYM) {
- Utils.logWrapper("Fix imports for section " + sectionName + " (" + h.getTypeAsString() + ")", monitor);
- int symbolCount = (int) ((int) (curSize) / h.getEntrySize());
- long entryPos = 0;
- for (int i = 0; i < symbolCount; i++) {
- monitor.checkCanceled();
- long entry_offset = (int) (offset + entryPos);
-
- int sectionIndex = buffer.getShort((int) entry_offset + 14) & 0xFFFF;
- ElfSectionHeader curSection = elfFile.getSections()[sectionIndex];
- int type = curSection.getType();
-
- if (type == SHT_RPL_IMPORTS) {
- String symbolSectionName = Utils.stringFromStringTable(sh_str_sh_data, curSection.getName());
- buffer.position((int) (entry_offset + 4));
- // Set Value to a custom symbol address
- buffer.position((int) (entry_offset + 12));
-
- // Force FUNC type so the name will be used in the decompiler.
- byte symbolType = ElfSymbol.STT_FUNC;
- // But change to OBJECT for data imports
- if (symbolSectionName.startsWith(".d")) {
- symbolType = ElfSymbol.STT_OBJECT;
- }
- // Change type to LOCAL so it won't be in the export list.
- buffer.put((byte) ((ElfSymbol.STB_LOCAL << 4) | symbolType)); // 12
- }
- entryPos += h.getEntrySize();
- }
- }
-
- buffer = Utils.checkAndGrowByteBuffer(buffer, shdr_elf_offset + 0x28);
-
- Utils.logWrapper("Converting section " + sectionName + " (" + h.getTypeAsString() + ")", monitor);
-
- buffer.position((int) shdr_elf_offset);
- buffer.putInt(h.getName());
- if (h.getType() == SHT_RPL_CRCS || h.getType() == SHT_RPL_FILEINFO) {
- buffer.putInt(ElfSectionHeaderConstants.SHT_NULL);
- } else {
- buffer.putInt(h.getType());
- }
- buffer.putInt((int) flags);
-
- buffer.putInt((int) h.getAddress());
- buffer.putInt((int) offset);
- buffer.putInt((int) curSize);
- buffer.putInt(h.getLink());
- buffer.putInt(h.getInfo());
-
- buffer.putInt((int) h.getAddressAlignment());
- buffer.putInt((int) h.getEntrySize());
-
- shdr_elf_offset += 0x28;
- }
-
- Utils.logWrapper("Create new ELF header", monitor);
-
- buffer = Utils.checkAndGrowByteBuffer(buffer, 36);
-
- buffer.position(0);
- buffer.put(RPX_MAGIC);
- buffer.position(0x10);
- buffer.putShort(ElfConstants.ET_EXEC); // e.e_type());
- buffer.putShort(elfFile.e_machine());
- buffer.putInt(elfFile.e_version());
- buffer.putInt((int) elfFile.e_entry());
- buffer.putInt((int) elfFile.e_phoff());
- buffer.putInt(elfFile.e_ehsize()); // e.e_shoff());
- buffer.putInt(elfFile.e_flags());
- buffer.putShort(elfFile.e_ehsize());
- buffer.putShort(elfFile.e_phentsize());
- buffer.putShort(elfFile.e_phnum());
- buffer.putShort(elfFile.e_shentsize());
- buffer.putShort(elfFile.e_shnum());
- buffer.putShort(elfFile.e_shstrndx());
-
- return buffer.array();
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/ghidra/app/util/bin/format/elf/RplSectionHeader.java b/src/main/java/ghidra/app/util/bin/format/elf/RplSectionHeader.java
new file mode 100644
index 0000000..e3654ee
--- /dev/null
+++ b/src/main/java/ghidra/app/util/bin/format/elf/RplSectionHeader.java
@@ -0,0 +1,12 @@
+package ghidra.app.util.bin.format.elf;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader;
+
+public class RplSectionHeader extends ElfSectionHeader {
+ public static ElfSectionHeader createElfSectionHeader(FactoryBundledWithBinaryReader reader,
+ ElfHeader header) throws IOException {
+ return ElfSectionHeader.createElfSectionHeader(reader, header);
+ }
+}
diff --git a/src/main/java/ghidra/app/util/opinion/CafeLoader.java b/src/main/java/ghidra/app/util/opinion/CafeLoader.java
new file mode 100644
index 0000000..0f032ad
--- /dev/null
+++ b/src/main/java/ghidra/app/util/opinion/CafeLoader.java
@@ -0,0 +1,87 @@
+package ghidra.app.util.opinion;
+
+import cafeloader.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.zip.DataFormatException;
+
+import generic.continues.GenericFactory;
+import ghidra.app.util.Option;
+import ghidra.app.util.bin.ByteArrayProvider;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.format.elf.ElfException;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.app.util.importer.MessageLogContinuesFactory;
+import ghidra.program.model.lang.LanguageCompilerSpecPair;
+import ghidra.program.model.listing.Program;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+public class CafeLoader extends ElfLoader {
+ @Override
+ public Collection findSupportedLoadSpecs(ByteProvider provider) throws IOException {
+ byte[] header = new byte[] { 0x7F, 0x45, 0x4C, 0x46, 0x01, 0x02, 0x01, (byte) 0xCA, (byte) 0xFE };
+ List loadSpecs = new ArrayList<>();
+
+ if (Arrays.equals(provider.readBytes(0, header.length), header)) {
+ List results = QueryOpinionService.query(getName(), "wiiu", null);
+ boolean hasGekkoProcessor = false;
+
+ for (QueryResult result : results) {
+ if (result.pair.languageID.getIdAsString().contains("Gekko")) {
+ hasGekkoProcessor = true;
+ }
+ }
+
+ for (QueryResult result : results) {
+ if (result.pair.languageID.getIdAsString().contains("Gekko")) {
+ loadSpecs.add(new LoadSpec(this, 0, new QueryResult(result.pair, true)));
+ } else {
+ loadSpecs.add(new LoadSpec(this, 0, new QueryResult(result.pair, hasGekkoProcessor ? false : result.preferred)));
+ }
+ }
+
+ if (loadSpecs.isEmpty()) {
+ loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair("PowerPC:BE:32:default", "default"), true));
+ }
+ }
+
+ return loadSpecs;
+ }
+
+ @Override
+ public String getName() {
+ return "Wii U / CafeOS Binary (RPX/RPL)";
+ }
+
+ @Override
+ public LoaderTier getTier() {
+ return LoaderTier.SPECIALIZED_TARGET_LOADER;
+ }
+
+ @Override
+ public int getTierPriority() {
+ return 0;
+ }
+
+ @Override
+ public void load(ByteProvider provider, LoadSpec loadSpec, List