Compare commits

..

6 commits

Author SHA1 Message Date
KeybadeBlox
522bf8be7f Fix static init/exit section names in objects.csv 2026-02-18 20:42:45 -05:00
KeybadeBlox
0b10a02ad7 Fix whitespace in Ghidra export script output
Functions with no arguments had a trailing tab.
2026-02-18 20:41:51 -05:00
KeybadeBlox
e0313fa0ba Add class fixup Ghidra script 2026-02-18 20:24:43 -05:00
KeybadeBlox
adc30bb531 Move Std.hpp into CRT directory
It's also been renamed to stddef.h since all it contained was the
definition of NULL, which lives in that header in a standard C library.
2026-02-18 19:22:13 -05:00
KeybadeBlox
3c4f0e72b8 Miscellaneous Ghidra script tweaks
No behavioural changes.
2026-02-18 18:18:24 -05:00
KeybadeBlox
c020c2e247 jsrf.h scalar deleting destructors return void *
They previously returned a pointer to the class type, but this isn't
accurate to the signature indicated by MSVC's name mangling.
2026-02-18 18:15:25 -05:00
15 changed files with 502 additions and 396 deletions

View file

@ -38,11 +38,11 @@ src/JSRF/Jet2.exe: $(OBJ) $(LIB)
# Header files used for each object
src/JSRF/Jet2.obj: src/JSRF/Core.hpp src/Std.hpp src/XDK/D3D.h\
src/JSRF/Jet2.obj: src/JSRF/Core.hpp src/XDK/CRT/stddef.h src/XDK/D3D.h\
src/XDK/Win32.h
src/JSRF/Core.obj: src/JSRF/Core.hpp src/Smilebit/MMatrix.hpp src/Std.hpp\
src/XDK/D3D.h src/XDK/Win32.h
src/JSRF/Core.obj: src/JSRF/Core.hpp src/Smilebit/MMatrix.hpp\
src/XDK/CRT/stddef.h src/XDK/D3D.h src/XDK/Win32.h
src/JSRF/GameData.obj: src/JSRF/GameData.hpp

View file

@ -4,6 +4,7 @@ Game and GameObj classes that form the foundation of the JSRF game code.
#pragma bss_seg(".data")
#include "../XDK/CRT/stddef.h"
#include "Core.hpp"
// Declarations for symbols not yet defined in their own source files

View file

@ -6,7 +6,7 @@ Game and GameObj classes that form the foundation of the JSRF game code.
#define CORE_HPP
#include "../Smilebit/MMatrix.hpp"
#include "../Std.hpp"
#include "../XDK/CRT/stddef.h"
#include "../XDK/D3D.h"
#include "../XDK/Win32.h"

View file

@ -4,6 +4,7 @@ Main function.
#pragma bss_seg(".data")
#include "../XDK/CRT/stddef.h"
#include "Core.hpp"

View file

@ -6,7 +6,7 @@ Smilebit's stack-based matrix math library.
#pragma bss_seg (".data" )
#include "../Std.hpp"
#include "../XDK/CRT/stddef.h"
#include "../XDK/Win32.h"
#include "MMatrix.hpp"

View file

@ -1,12 +0,0 @@
/* JSRF Decompilation: Std.hpp
C(++) standard library definitions. Implemented in the repository instead of
linking to an outside stdlib to ensure consistency (this may not actually be
possible to accomplish, but we'll go for it for now).
*/
#ifndef STD_HPP
#define STD_HPP
#define NULL 0
#endif

View file

@ -0,0 +1,8 @@
/* JSRF Decompilation: XDK/CRT/stddef.h */
#ifndef STDDEF_H
#define STDDEF_H
#define NULL 0
#endif

View file

@ -0,0 +1,106 @@
// Creates classes out 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");
}
}
}

View file

@ -16,7 +16,7 @@ import java.util.Arrays;
import java.util.Optional;
public class EnhancedExport extends GhidraScript{
public class EnhancedExport extends GhidraScript {
@Override
public void run() throws Exception {
final FileWriter out = new FileWriter(askFile("Specify output file", "OK"));
@ -70,14 +70,14 @@ public class EnhancedExport extends GhidraScript{
(f.isInline() ? "inline" : "notinline") + "\t" +
Optional.ofNullable(f.getCallFixup())
.orElse("nofixup") + "\t" +
f.getName(true) + "\t" +
f.getName(true) +
String.join(
"\t",
"",
Arrays.stream(f.getSignature(true)
.getArguments())
.map(arg ->
arg.getDataType().getDisplayName() + "\t" +
arg.getName()
"\t" + arg.getDataType().getDisplayName() +
"\t" + arg.getName()
).toArray(String[]::new)
) +
(f.hasVarArgs() ? "\t..." : "") + "\n"

View file

@ -4,6 +4,7 @@
import ghidra.app.script.GhidraScript;
import ghidra.app.services.DataTypeQueryService;
import ghidra.app.util.NamespaceUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.DataType;
@ -27,7 +28,7 @@ import java.util.List;
import java.util.Optional;
public class EnhancedImport extends GhidraScript{
public class EnhancedImport extends GhidraScript {
@Override
public void run() throws Exception {
final FileReader in = new FileReader(askFile("Select input file", "OK"));
@ -86,16 +87,17 @@ public class EnhancedImport extends GhidraScript{
/* Creates namespaces from the given name, returning the deepest one
Returns null if the qualified name is in the global namespace.
*/
final String[] parts = qualifiedName.split("::");
if (parts.length < 2) return null;
final String[] names = Arrays.copyOfRange(parts, 0, parts.length - 1);
Namespace ns = null;
for (final String name : names) ns = createNamespace(ns, name);
return ns;
return qualifiedName.contains("::") ?
NamespaceUtils.createNamespaceHierarchy(
qualifiedName.substring( // Cut off symbol name
0,
qualifiedName.length() - "::".length() -
unqualified(qualifiedName).length()
),
null,
currentProgram,
SourceType.USER_DEFINED
) : null;
}
private Optional<DataType> makeType(final String type) throws Exception {

View file

@ -73,7 +73,7 @@ import java.util.stream.IntStream;
import java.util.zip.CRC32;
public class MSVC7Mangle extends GhidraScript{
public class MSVC7Mangle extends GhidraScript {
@Override
public void run() throws Exception {
// Get selected ranges from arguments if invoked headless

View file

@ -5,7 +5,7 @@
function fn_ptr(cls, signature, ret, fname, args) {
# Convert the given method signature to a function pointer
if ($1 ~ /^~/) # Special case for virtual destructor
return "\t\t" cls " * __attribute__((thiscall)) "\
return "\t\t void * __attribute__((thiscall)) "\
"(*scalar_deleting_destructor)("\
cls " *, "\
"BOOL"\

View file

@ -9,8 +9,8 @@ printf '%s\n' '// Automatically generated mass header file for Ghidra' > jsrf.h
# all the headers here by hand in an order that functions properly
HEADERS="
Std.hpp
XDK/Win32.hpp
XDK/D3D.hpp
XDK/Win32.h
XDK/D3D.h
Smilebit/MMatrix.hpp
JSRF/Core.hpp
JSRF/GameData.hpp

View file

@ -1,4 +1,4 @@
Object,Delink?,.text,.text$XC*1,.text$XC*2,.text$x,D3D,DSOUND,MMATRIX,XGRPH,XPP,.rdata,.rdata$x,.data$CRT,.data,DOLBY
Object,Delink?,.text,.text$yc,.text$yd,.text$x,D3D,DSOUND,MMATRIX,XGRPH,XPP,.rdata,.rdata$x,.data$CRT,.data,DOLBY
JSRF/Core.obj,true,0x00011000-0x00013FEB,,,0x00186BA0-0x00186C14,,,,,,0x001C4390-0x001C44F9,0x001E4D20-0x001E4DAB,,0x001EB880-0x001EB933,
JSRF/GameData.obj,true,0x00039B50-0x0003B937,0x0018AD60-0x0018AD75,0x0018C9A0-0x0018C9AA,,,,,,,0x001CA16C-0x001CA3DB,,0x001EB790-0x001EB793,0x001EFC88-0x001F7047,
JSRF/Jet2.obj,true,0x0006F9E0-0x0006FA6F,,,0x00187710-0x00187724,,,,,,,0x001E620C-0x001E622F,,0x0022FCE0-0x0022FCE3,

1 Object Delink? .text .text$XC*1 .text$yc .text$XC*2 .text$yd .text$x D3D DSOUND MMATRIX XGRPH XPP .rdata .rdata$x .data$CRT .data DOLBY
2 JSRF/Core.obj true 0x00011000-0x00013FEB 0x00186BA0-0x00186C14 0x001C4390-0x001C44F9 0x001E4D20-0x001E4DAB 0x001EB880-0x001EB933
3 JSRF/GameData.obj true 0x00039B50-0x0003B937 0x0018AD60-0x0018AD75 0x0018C9A0-0x0018C9AA 0x001CA16C-0x001CA3DB 0x001EB790-0x001EB793 0x001EFC88-0x001F7047
4 JSRF/Jet2.obj true 0x0006F9E0-0x0006FA6F 0x00187710-0x00187724 0x001E620C-0x001E622F 0x0022FCE0-0x0022FCE3

File diff suppressed because it is too large Load diff