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 # 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/XDK/Win32.h
src/JSRF/Core.obj: src/JSRF/Core.hpp src/Smilebit/MMatrix.hpp src/Std.hpp\ src/JSRF/Core.obj: src/JSRF/Core.hpp src/Smilebit/MMatrix.hpp\
src/XDK/D3D.h src/XDK/Win32.h src/XDK/CRT/stddef.h src/XDK/D3D.h src/XDK/Win32.h
src/JSRF/GameData.obj: src/JSRF/GameData.hpp 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") #pragma bss_seg(".data")
#include "../XDK/CRT/stddef.h"
#include "Core.hpp" #include "Core.hpp"
// Declarations for symbols not yet defined in their own source files // 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 #define CORE_HPP
#include "../Smilebit/MMatrix.hpp" #include "../Smilebit/MMatrix.hpp"
#include "../Std.hpp" #include "../XDK/CRT/stddef.h"
#include "../XDK/D3D.h" #include "../XDK/D3D.h"
#include "../XDK/Win32.h" #include "../XDK/Win32.h"

View file

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

View file

@ -6,7 +6,7 @@ Smilebit's stack-based matrix math library.
#pragma bss_seg (".data" ) #pragma bss_seg (".data" )
#include "../Std.hpp" #include "../XDK/CRT/stddef.h"
#include "../XDK/Win32.h" #include "../XDK/Win32.h"
#include "MMatrix.hpp" #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; import java.util.Optional;
public class EnhancedExport extends GhidraScript{ public class EnhancedExport extends GhidraScript {
@Override @Override
public void run() throws Exception { public void run() throws Exception {
final FileWriter out = new FileWriter(askFile("Specify output file", "OK")); final FileWriter out = new FileWriter(askFile("Specify output file", "OK"));
@ -70,14 +70,14 @@ public class EnhancedExport extends GhidraScript{
(f.isInline() ? "inline" : "notinline") + "\t" + (f.isInline() ? "inline" : "notinline") + "\t" +
Optional.ofNullable(f.getCallFixup()) Optional.ofNullable(f.getCallFixup())
.orElse("nofixup") + "\t" + .orElse("nofixup") + "\t" +
f.getName(true) + "\t" + f.getName(true) +
String.join( String.join(
"\t", "",
Arrays.stream(f.getSignature(true) Arrays.stream(f.getSignature(true)
.getArguments()) .getArguments())
.map(arg -> .map(arg ->
arg.getDataType().getDisplayName() + "\t" + "\t" + arg.getDataType().getDisplayName() +
arg.getName() "\t" + arg.getName()
).toArray(String[]::new) ).toArray(String[]::new)
) + ) +
(f.hasVarArgs() ? "\t..." : "") + "\n" (f.hasVarArgs() ? "\t..." : "") + "\n"

View file

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

View file

@ -5,7 +5,7 @@
function fn_ptr(cls, signature, ret, fname, args) { function fn_ptr(cls, signature, ret, fname, args) {
# Convert the given method signature to a function pointer # Convert the given method signature to a function pointer
if ($1 ~ /^~/) # Special case for virtual destructor if ($1 ~ /^~/) # Special case for virtual destructor
return "\t\t" cls " * __attribute__((thiscall)) "\ return "\t\t void * __attribute__((thiscall)) "\
"(*scalar_deleting_destructor)("\ "(*scalar_deleting_destructor)("\
cls " *, "\ cls " *, "\
"BOOL"\ "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 # all the headers here by hand in an order that functions properly
HEADERS=" HEADERS="
Std.hpp Std.hpp
XDK/Win32.hpp XDK/Win32.h
XDK/D3D.hpp XDK/D3D.h
Smilebit/MMatrix.hpp Smilebit/MMatrix.hpp
JSRF/Core.hpp JSRF/Core.hpp
JSRF/GameData.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/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/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, 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

View file

@ -667,12 +667,12 @@
0x0014a83e func undefined unknown notinline nofixup someAllocater_MAYBE undefined4 size/4_MAYBE undefined4 param_2 0x0014a83e func undefined unknown notinline nofixup someAllocater_MAYBE undefined4 size/4_MAYBE undefined4 param_2
0x0014a85b func undefined __stdcall notinline nofixup someDeallocator_MAYBE undefined * param_1 0x0014a85b func undefined __stdcall notinline nofixup someDeallocator_MAYBE undefined * param_1
0x0014a8a1 func PLARGE_INTEGER __stdcall notinline nofixup makeTimeout undefined4 out uint milliseconds 0x0014a8a1 func PLARGE_INTEGER __stdcall notinline nofixup makeTimeout undefined4 out uint milliseconds
0x0014a8d0 func undefined __stdcall notinline nofixup _XapiValidateDiskPartition OBJECT_STRING * param_1 0x0014a8d0 func undefined __stdcall notinline nofixup extern_"C"::XapiValidateDiskPartition OBJECT_STRING * param_1
0x0014ad4e func NTSTATUS __stdcall notinline nofixup _XapiSetupPerTitleDriveLetters DWORD TitleID LPWSTR TitleName 0x0014ad4e func NTSTATUS __stdcall notinline nofixup extern_"C"::XapiSetupPerTitleDriveLetters DWORD TitleID LPWSTR TitleName
0x0014ada3 func undefined unknown notinline nofixup _XapiBootToDash undefined4 dwReason undefined4 dwParameter1 undefined4 dwParameter2 0x0014ada3 func void __stdcall notinline nofixup extern_"C"::XapiBootToDash DWORD dwReason DWORD dwParameter1 DWORD dwParameter2
0x0014ae08 func void __stdcall notinline nofixup _XapiInitProcess 0x0014ae08 func void __stdcall notinline nofixup extern_"C"::XapiInitProcess
0x0014b3b1 func BOOL __stdcall notinline nofixup XAPILIB::InternalRemoveDirectoryRecursive_MAYBE LPCSTR lpPathName 0x0014b3b1 func BOOL __stdcall notinline nofixup XAPILIB::InternalRemoveDirectoryRecursive_MAYBE LPCSTR lpPathName
0x0014b44b func undefined unknown notinline nofixup _XapiInitAutoPowerDown 0x0014b44b func void __stdcall notinline nofixup extern_"C"::XapiInitAutoPowerDown
0x0014b4b5 func void __stdcall notinline nofixup __cinit 0x0014b4b5 func void __stdcall notinline nofixup __cinit
0x0014b50d func void __stdcall notinline nofixup __rtinit 0x0014b50d func void __stdcall notinline nofixup __rtinit
0x0014b6c4 func HANDLE __stdcall notinline nofixup XAPILIB::XGetSectionHandle LPCSTR pSectionName 0x0014b6c4 func HANDLE __stdcall notinline nofixup XAPILIB::XGetSectionHandle LPCSTR pSectionName
@ -896,7 +896,7 @@
0x0018ad30 func undefined unknown notinline nofixup initUnknownStatic05 0x0018ad30 func undefined unknown notinline nofixup initUnknownStatic05
0x0018ad40 func undefined unknown notinline nofixup initUnknownStatic06 0x0018ad40 func undefined unknown notinline nofixup initUnknownStatic06
0x0018ad50 func undefined unknown notinline nofixup initUnknownStatic07 0x0018ad50 func undefined unknown notinline nofixup initUnknownStatic07
0x0018ad60 func void __stdcall notinline nofixup initGameData 0x0018ad60 func void __cdecl notinline nofixup initGameData
0x0018ad80 func undefined unknown notinline nofixup initUnknownStatic09 0x0018ad80 func undefined unknown notinline nofixup initUnknownStatic09
0x0018ada0 func undefined unknown notinline nofixup initUnknownStatic10 0x0018ada0 func undefined unknown notinline nofixup initUnknownStatic10
0x0018adb0 func undefined unknown notinline nofixup initUnknownStatic11 0x0018adb0 func undefined unknown notinline nofixup initUnknownStatic11
@ -1205,13 +1205,13 @@
0x00228598 data char *[5] tagSaveImagePaths 0x00228598 data char *[5] tagSaveImagePaths
0x0022db98 data undefined * CRI::wxci_vtable 0x0022db98 data undefined * CRI::wxci_vtable
0x0022dc00 data undefined * CRI::mfci_vtable 0x0022dc00 data undefined * CRI::mfci_vtable
0x0022dfe4 data OBJECT_STRING _DDrive 0x0022dfe4 data OBJECT_STRING extern_"C"::DDrive
0x0022dfec data OBJECT_STRING _CDDevice 0x0022dfec data OBJECT_STRING extern_"C"::CDDevice
0x0022dff4 data OBJECT_STRING _MainVol 0x0022dff4 data OBJECT_STRING extern_"C"::MainVol
0x0022dffc data OBJECT_STRING _TDrive 0x0022dffc data OBJECT_STRING extern_"C"::TDrive
0x0022e004 data OBJECT_STRING _TitleData 0x0022e004 data OBJECT_STRING extern_"C"::TitleData
0x0022e00c data OBJECT_STRING __UDrive 0x0022e00c data OBJECT_STRING extern_"C"::UDrive
0x0022e014 data OBJECT_STRING _UserData 0x0022e014 data OBJECT_STRING extern_"C"::UserData
0x0022e58c data uint g_iRng 0x0022e58c data uint g_iRng
0x0022e6ac data float g_someMin 0x0022e6ac data float g_someMin
0x0022e6b0 data float g_someMax 0x0022e6b0 data float g_someMax
@ -1289,4 +1289,4 @@
0x00273780 data undefined1[40][336] CRI::null_ARRAY_ARRAY_00273780 0x00273780 data undefined1[40][336] CRI::null_ARRAY_ARRAY_00273780
0x00276c00 data char[256] CRI::wxci_buf 0x00276c00 data char[256] CRI::wxci_buf
0x0027b1c0 data undefined1[16][164] mwRnaInstances 0x0027b1c0 data undefined1[16][164] mwRnaInstances
0x0027dcd4 data undefined4 _XapiProcessHeap 0x0027dcd4 data undefined4 extern_"C"::XapiProcessHeap

Can't render this file because it has a wrong number of fields in line 3.