JSRF-Decompilation/ghidra/ghidra_scripts/ClassFixup.java
KeybadeBlox 98d88cc212 Support earlier Java releases
Existing code was written for Java 25, which is more recent than many
people have on hand.  The modified scripts appear to run well on Java
21.
2026-03-20 13:20:51 -04:00

106 lines
3.8 KiB
Java

// Creates classes out of namespaces with matching structs, and if they have a
// vtable, sets the calling convention of the contained function typdefs to
// __thiscall.
//
// For vtables to be found, they must be defined as structs with names ending
// in "Vtbl" and be pointed to by the first member of a class struct.
//
// @category Data Types
import ghidra.app.script.GhidraScript;
import ghidra.app.util.NamespaceUtils;
import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.data.FunctionDefinition;
import ghidra.program.model.data.Pointer;
import ghidra.program.model.data.Structure;
import ghidra.program.model.listing.GhidraClass;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.Symbol;
public class ClassFixup extends GhidraScript {
@Override
public void run() throws Exception {
fixInNamespace(currentProgram.getGlobalNamespace());
}
private void fixInNamespace(final Namespace parent) throws Exception {
for (final Symbol s : currentProgram.getSymbolTable()
.getChildren(parent.getSymbol()))
if (s.getObject() instanceof Namespace ns) switch (ns.getType()) {
case Namespace.Type.NAMESPACE:
if (shouldBeClass(ns)) {
println("Converting \"" + ns.getName(true) + "\" to class...");
NamespaceUtils.convertNamespaceToClass(ns);
// Re-fetch namespace to get its new class version
ns = getNamespace(parent, ns.getName());
} else {
fixInNamespace(ns);
break;
}
case Namespace.Type.CLASS: // fallthrough
// Any ns that makes it here should be a class
// (can't bind via pattern match because of the
// fallthrough)
final GhidraClass cls = (GhidraClass)ns;
// Fix up methods if first member is a vtable
if (
DataTypeUtilities.findExistingClassStruct(
currentProgram.getDataTypeManager(),
cls
) instanceof Structure struct &&
struct.getDataTypeAt(0) instanceof DataTypeComponent memberType &&
memberType.getDataType() instanceof Pointer ptrType &&
ptrType .getDataType() instanceof Structure vtblT_maybe &&
vtblT_maybe.getName().endsWith("Vtbl")
) fixMethods(vtblT_maybe);
break;
default: continue;
}
}
private boolean shouldBeClass(final Namespace ns) throws Exception {
/* Determine if a namespace should be converted to a class
The heuristic is whether Ghidra can find a struct that it would be
linked with if it was a class. The process to do so is admittedly a
bit byzantine.
*/
// Change name so the dummy class won't have a conflict
final String name = ns.getName();
ns.getSymbol()
.setName("__" + ns.getName(), ns.getSymbol().getSource());
// Create a dummy class to check for a matching struct without
// converting the namespace (as classes can't be reverted)
final GhidraClass cls = createClass(ns.getParentNamespace(), name);
// Record whether Ghidra could find a matching struct
final boolean ret = DataTypeUtilities.findExistingClassStruct(
currentProgram.getDataTypeManager(),
cls
) != null;
// Clean up
cls.getSymbol().delete();
ns.getSymbol().setName(name, ns.getSymbol().getSource());
return ret;
}
private void fixMethods(final Structure vtbl) throws Exception {
/* Set all the calling conventions in a vtable to __thiscall */
for (final DataTypeComponent member : vtbl.getComponents())
if (
member.getDataType() instanceof Pointer ptr &&
ptr .getDataType() instanceof FunctionDefinition method &&
!method.getCallingConventionName().equals("__thiscall")
) {
println("Fixing calling convention of \"" + method.getDataTypePath().toString() + "\"...");
method.setCallingConvention("__thiscall");
}
}
}