Skip to content

Commit 1d9fe00

Browse files
committed
Attempt to recover from invalid spell items
Gothic encounters an unrecoverable error when unarchiving oCNpcInventory and facing an equipped spell item (scroll or rune) that no longer exists in the game scripts. This is due to an extra archive-value that will not be skipped when continuing to the next item. While this error is native to Gothic, it is more likely to happen if a patch introduces a spell item, that item is equipped and the patch is removed. While corrupt binary archives are unrecoverable, and the game will crash with certainty, this commit introduces an attempt to recover, by advancing the buffer beyond a potential extra archive-value and tries to read the item object again. Note that this will guarantee to solve the above issue, but that issue only.
1 parent 60ab04b commit 1d9fe00

7 files changed

+79
-0
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ BIN_BASE := core \
141141
hook_archiveWorldWriteLogicalNpc \
142142
hook_oCSpawnManager__Archive \
143143
hook_npcReference \
144+
hook_recoverInvalidItem \
144145
hook_fastexit \
145146
hook_CGameManager_destructor \
146147
hook_libExit \

src/exec/misc.asm

+45
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,51 @@ removeNpcInstRef:
243243
jmp g1g2(0x68C0F6,0x6BC7DD,0x6D0445,0x72E625) + 5
244244

245245

246+
global recoverInvalidItem
247+
recoverInvalidItem:
248+
resetStackoffset g1g2(0x94,0x94,0x100,0x100)
249+
test esi, esi ; Check for invalid item in archive
250+
jnz .back
251+
252+
sub esp, 0x10 ; At this point the error already happened:
253+
push char_shortKey ; Attempt to recover by reading shortKey
254+
lea eax, [esp+0x4] ; This advances the buffer to try again
255+
push eax
256+
call DWORD [ds_lstrcpyA] ; Construct 'shortKeyX' on stack
257+
addStack 2*4
258+
push 0xA
259+
lea eax, [esp+0x4+0x8] ; Stack-offset + len('shortKey')
260+
push eax
261+
mov ecx, [esp+stackoffset-g1g2(0x80,0x80,0xEC,0xEC)] ; Index X
262+
push ecx
263+
call _itoa ; 'shortKey' + 'X', 0
264+
add esp, 0xC
265+
266+
mov eax, [g1g2(ebp,ebp,ebx,ebx)] ; Attempt to advance buffer
267+
lea ecx, [esp+stackoffset-g1g2(0x78,0x78,0xE0,0xE0)] ; Key-variable used as sink
268+
push ecx ; Arg1 key
269+
mov edx, esp ; Arg0 'shortKeyX'
270+
mov ecx, g1g2(ebp,ebp,ebx,ebx) ; This zCArchive*
271+
call [eax+0x60] ; zCArchive->ReadInt
272+
addStack 4
273+
add esp, 0x10 ; Free 'shortKeyX' from stack
274+
275+
mov edx, [esp+stackoffset-g1g2(0x68,0x68,0xBC,0xBC)] ; Get existing 'itemX' and try again
276+
mov esi, [g1g2(ebp,ebp,ebx,ebx)]
277+
push 0x0
278+
mov ecx, g1g2(ebp,ebp,ebx,ebx)
279+
call [esi+0xAC] ; zCArchive->ReadObject
280+
addStack 4
281+
mov esi, eax
282+
verifyStackoffset g1g2(0x94,0x94,0x100,0x100)
283+
284+
.back:
285+
%if GOTHIC_BASE_VERSION == 1 || GOTHIC_BASE_VERSION == 112
286+
mov DWORD [esp+stackoffset-0x78], 0xFFFFFFFF
287+
%endif
288+
jmp g1g2(0x66DC47,0x69B420,0x6AFBB9,0x70D6D9) + 6
289+
290+
246291
global ninja_injectInfo
247292
ninja_injectInfo:
248293
resetStackoffset ; 0xBC

src/hook_recoverInvalidItem.asm

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
; Hook oCNpcInventory::Unarchive to recover invalid items to continue with the remaining ones
2+
; When a spell item (rune or scroll) in the inventory does no longer exist in the game, loading results in a crash
3+
; This hook tries to recover that crash
4+
5+
%include "inc/macros.inc"
6+
%include "inc/symbols.inc"
7+
8+
%ifidn __OUTPUT_FORMAT__, bin
9+
org g1g2(0x66DC47,0x69B420,0x6AFBB9,0x70D6D9)
10+
%endif
11+
12+
bits 32
13+
14+
15+
section .text align=1 ; Prevent auto-alignment
16+
17+
jmp recoverInvalidItem
18+
nop
19+
20+
%if GOTHIC_BASE_VERSION == 1 || GOTHIC_BASE_VERSION == 112
21+
times 2 nop
22+
%endif
23+
24+
; Overwrites
25+
; %if GOTHIC_BASE_VERSION == 1 || GOTHIC_BASE_VERSION == 112
26+
; mov [esp+0x90-0x74], 0xFFFFFFFF
27+
; %elif GOTHIC_BASE_VERSION == 130 || GOTHIC_BASE_VERSION == 2
28+
; jz g1g2(,,0x6AFDA0,0x70D8C0)
29+
; %endif

src/inc/engine_g1.inc

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
%define char_timer 0x8510CC ; 'timer', 0
140140
%define char_noOfEntries 0x8510E4 ; 'NoOfEntries', 0
141141
%define char_spawningEnabled 0x8510BC ; 'spawningEnabled', 0
142+
%define char_shortKey 0x84CF50 ; 'shortKey', 0
142143

143144
%define char_zStartupWindowed 0x846978 ; 'zStartupWindowed', 0
144145
%define char_settings 0x82BD98 ; 'SETTINGS', 0

src/inc/engine_g112.inc

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
%define char_timer 0x897418 ; 'timer', 0
140140
%define char_noOfEntries 0x897430 ; 'NoOfEntries', 0
141141
%define char_spawningEnabled 0x897408 ; 'spawningEnabled', 0
142+
%define char_shortKey 0x892FCC ; 'shortKey', 0
142143

143144
%define char_zStartupWindowed 0x88C634 ; 'zStartupWindowed', 0
144145

src/inc/engine_g130.inc

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
%define char_noOfEntries 0x8A7008 ; 'NoOfEntries', 0
141141
%define char_spawningEnabled 0x8A6FE0 ; 'spawningEnabled', 0
142142
%define char_spawnFlags 0x8A6FD4 ; 'spawnFlags', 0
143+
%define char_shortKey 0x8A33D8 ; 'shortKey', 0
143144
%define char_zStartupWindowed 0x89C760 ; 'zStartupWindowed', 0
144145

145146
%define char_meatbug_mds 0x8A47D0 ; 'Meatbug.mds', 0

src/inc/engine_g2.inc

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
%define char_noOfEntries 0x8BAE60 ; 'NoOfEntries', 0
141141
%define char_spawningEnabled 0x8BAE38 ; 'spawningEnabled', 0
142142
%define char_spawnFlags 0x8BAE2C ; 'spawnFlags', 0
143+
%define char_shortKey 0x8B7200 ; 'shortKey', 0
143144
%define char_zStartupWindowed 0x8AA930 ; 'zStartupWindowed', 0
144145
%define char_settings 0x8948A0 ; 'SETTINGS', 0
145146
%define char_meatbug_mds 0x8B85F8 ; 'Meatbug.mds', 0

0 commit comments

Comments
 (0)