Skip to content

Conversation

@dpiers
Copy link

@dpiers dpiers commented Dec 1, 2025

Summary

Fixes #1278 - OpenJO x64: Crash when loading saved game

Root Cause

The watchTarget field (gentity_t*) in gNPC_t was being serialized as int32_t in sg_export() but was not included in the savefields_gNPC pointer conversion table. This caused:

  1. On save: 64-bit pointer truncated to 32-bit garbage
  2. On load: Truncated value interpreted as an invalid pointer
  3. On use: Crash or hang when NPC_BSCinematic() calls CalcEntitySpot(NPCInfo->watchTarget, ...)

Investigation

Using the crash dump and save file from issue #1278:

  • Windows x64: Crashes with EXCEPTION_ACCESS_VIOLATION at 0x426e6fa8
  • macOS ARM64: Hangs in infinite loop - process sampling showed:
    NPC_BSCinematic() → CalcEntitySpot() → ViewHeightFix()
    
    The game was stuck calling CalcEntitySpot(NPCInfo->watchTarget, SPOT_HEAD_LEAN, viewSpot) with a corrupted pointer at line 252 of NPC_behavior.cpp.

Fix

Add the missing watchTarget entry to savefields_gNPC in codeJK2/game/g_savegame.cpp:

{strNPCOFS(watchTarget),        F_GENTITY},

This ensures the pointer is properly converted to an entity index when saving and restored to a valid pointer when loading. Also - improve game save parameter validation to return null for invalid entity IDs to maintain save compatibility.

Notes

  • The Jedi Academy codebase (code/game/g_savegame.cpp) correctly includes watchTarget in its savefields_gNPC table - this was an oversight specific to the JK2 codebase.
  • Save files created with the unfixed 64-bit build are corrupted and cannot be recovered, as the original entity index was lost when overwritten with a truncated pointer.
  • This bug was latent for years because 32-bit builds don't truncate pointers, so the raw pointer value would still work after reload.

Add missing watchTarget pointer field to savefields_gNPC table.

The watchTarget field (gentity_t*) in gNPC_t was being serialized as
int32_t in sg_export() but was not included in the savefields_gNPC
conversion table. This caused:

- On save: 64-bit pointer truncated to 32-bit
- On load: Truncated value used as invalid pointer
- On use: Crash (Windows x64) or infinite loop (macOS ARM64) when
  NPC_BSCinematic() calls CalcEntitySpot(NPCInfo->watchTarget, ...)

The Jedi Academy codebase (code/game/g_savegame.cpp) correctly includes
watchTarget in its savefields_gNPC table, but the JK2 codebase was
missing this entry.

Fixes JACoders#1278
@dpiers dpiers force-pushed the fix/jk2-savegame-watchTarget-crash branch from 24d83f0 to 785cdf6 Compare December 3, 2025 03:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenJO x64: Crash when loading saved game

1 participant