Skip to content

SDKtoGhidra: apply the XML's calling convention when importing functions - #116

Closed
jatassi wants to merge 1 commit into
Spore-Community:masterfrom
jatassi:fix/importer-calling-convention
Closed

SDKtoGhidra: apply the XML's calling convention when importing functions#116
jatassi wants to merge 1 commit into
Spore-Community:masterfrom
jatassi:fix/importer-calling-convention

Conversation

@jatassi

@jatassi jatassi commented Sep 6, 2026

Copy link
Copy Markdown

Problem

ImportSporeSDK.java resolves each function's calling convention from the function's existing convention (getCallingConvention(Function, FunctionSignature, CompilerSpec) returns function.getCallingConventionName()). After Ghidra's auto-analysis that is always unknown, so the CONVENTION attribute the XML carries on 3,662 thiscall and 203 stdcall definitions is never used. The this-in-ECX branch in createParameters therefore never runs: this is placed at stack offset 4 and every stack argument of a __thiscall function is shifted by four bytes.

Example, Editors::EditorModel::SetColor before the fix:

void Editors__EditorModel__SetColor(EditorModel *this,int index,ColorRGB color)
{
  int *piVar1;
  int in_ECX;
  piVar1 = (int *)(in_ECX + 0xa4 + (int)this * 0xc);
  *piVar1 = index;
  ...

The real this is in_ECX, the parameter named this is the real index, and index is the first word of color.

A second, related problem: processFunctionDef stores the convention through the deprecated FunctionDefinition.setGenericCallingConvention, which in Ghidra 11+ stores the bare enum name (thiscall). No compiler spec recognises that name, so every definition reports hasUnknownCallingConventionName() == true, and the decompiler treats calls through __vftable slots (whose members are pointers to these definitions) as unknown-convention calls.

Fix

  • getCallingConvention(String) now maps the XML's cdecl/fastcall/stdcall/thiscall/vectorcall/unknown to the CompilerSpec.CALLING_CONVENTION_* names, and processFunctionDef stores that name with FunctionDefinition.setCallingConvention.
  • getCallingConvention(Function, FunctionSignature, CompilerSpec) returns the definition's convention when the program's compiler spec knows it, and the function's existing convention otherwise.
  • Custom storage is kept as before. With dynamic storage the typed this is lost, because the functions live in Ghidra's global namespace with :: in their names and Ghidra only derives an auto-this inside a class namespace.
  • The error message for an unknown CONVENTION value now prints the offending text instead of null.

Tested

Ghidra 12.1.3, JDK 21, SporeGhidra_march2017.xml against the GOG SporeApp.exe:

  • Editors::EditorModel::SetColor decompiles to this->mColors[index].r = color.r; ....
  • App::Property::GetValueInt32 reads this-> in its body.
  • Math::RandomNumberGenerator::RandomFloat (returns float) still returns in st0.
  • Imported function count is unchanged; imported functions with a signature now report __thiscall or __stdcall instead of unknown.

Found while building an annotated project for the OpenSpore effort (https://github.com/jatassi/OpenSpore).

ImportSporeSDK.java read each function's calling convention from the
function's existing convention, which is always "unknown" after Ghidra's
auto-analysis, and ignored the CONVENTION attribute the XML carries on
3,662 thiscall and 203 stdcall definitions. The this-in-ECX branch in
createParameters therefore never ran: "this" landed at stack offset 4
and every stack argument of a __thiscall function was shifted by four
bytes. Editors::EditorModel::SetColor labelled its real "index" argument
as "this", and the decompiler read the real "this" as in_ECX.

Two changes:

- processFunctionDef stores the convention through
  FunctionDefinition.setCallingConvention with Ghidra's own names
  (__thiscall, __stdcall, ...). The deprecated
  setGenericCallingConvention stored the bare enum name ("thiscall"),
  which no compiler spec recognises, so every definition read as an
  unknown convention. This also fixes calls through vftable slots, which
  the decompiler resolves from the definition's convention.
- setSignature passes the definition's convention into updateFunction
  when the compiler spec knows it, and keeps the existing one otherwise.
  Custom storage is kept, because the functions live in Ghidra's global
  namespace with "::" in their names and dynamic storage would lose the
  typed "this".

Tested against Ghidra 12.1.3 with SporeGhidra_march2017.xml: SetColor
decompiles to "this->mColors[index].r = color.r", App::Property::
GetValueInt32 reads "this->", and float-returning functions still return
in st0.
@Rosalie241

Copy link
Copy Markdown
Member

Found while building an annotated project for the OpenSpore effort (https://github.com/jatassi/OpenSpore).

That link doesn't work but I presume it's also mostly LLM generated?

@Rosalie241

Copy link
Copy Markdown
Member

After some internal discussion with the other maintainers, we've decided we will not accept LLM generated patches.

@Rosalie241 Rosalie241 closed this Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants