Table of Contents

Suikoden II Bug: Characters Joining at Level 99
Affected Characters
Cause
Fix
GameShark Code
Patch

Suikoden II Bug: Characters Joining at Level 99


This is one of the less burdensome bugs in Suikoden II. It is an intermittent bug, and may have never shown up during debugging and play-testing. It occurs when certain characters, whose initial level is set based on another character, join the 108 Stars.

Affected Characters


Any character whose level on joining is set based on another character is prone to this bug. Notably, these characters join at a level derived from the Hero's current level.


In addition, L.C. Chan (Long Chan Chan) will be set based on Wakaba's level.

Cause


The bug is caused by bad management of variable sizes. In the original source, the problem is likely a casting issue, with a 32-bit integer pointer being cast to an 8-bit pointer in a function call. The assembly below shows where the actual error occurs. (This is taken from the PAL-E/European English version of the game.)
TEXT:800A23B0 BuildCharaToLV: # DATA XREF: TEXT:800ECE64o
TEXT:800A23B0
TEXT:800A23B0 var_18 = -0x18
TEXT:800A23B0 var_10 = -0x10
TEXT:800A23B0 var_C = -0xC
TEXT:800A23B0 var_8 = -8
TEXT:800A23B0 var_4 = -4
TEXT:800A23B0
TEXT:800A23B0 addiu $sp, -0x28
TEXT:800A23B4 andi $a1, 0xFF
TEXT:800A23B8 sll $a1, 4
TEXT:800A23BC addu $t0, $a0, $a1
TEXT:800A23C0 move $a0, $0
TEXT:800A23C4 move $a1, $a0
TEXT:800A23C8 sw $ra, 0x28+var_4($sp)
TEXT:800A23CC sw $s2, 0x28+var_8($sp)
TEXT:800A23D0 sw $s1, 0x28+var_C($sp)
TEXT:800A23D4 sw $s0, 0x28+var_10($sp)
TEXT:800A23D8 lw $v0, 0x7C($t0)
TEXT:800A23DC addiu $a3, $sp, 0x28+var_18 <-------
TEXT:800A23E0 lbu $s2, 0($v0)
TEXT:800A23E4 addiu $v0, 1
TEXT:800A23E8 sw $v0, 0x7C($t0)
TEXT:800A23EC lbu $a2, 0($v0)
TEXT:800A23F0 addiu $v1, $v0, 1
TEXT:800A23F4 sw $v1, 0x7C($t0)
TEXT:800A23F8 lbu $s0, 1($v0)
TEXT:800A23FC addiu $v1, $v0, 2
TEXT:800A2400 sw $v1, 0x7C($t0)
TEXT:800A2404 lbu $s1, 2($v0)
TEXT:800A2408 addiu $v0, 3
TEXT:800A240C jal MP_Level_Sub
TEXT:800A2410 sw $v0, 0x7C($t0)
TEXT:800A2414 bnez $s0, loc_800A2428
TEXT:800A2418 nop
TEXT:800A241C lw $v0, 0x28+var_18($sp) <-------
TEXT:800A2420 j loc_800A2440
TEXT:800A2424 addu $v0, $s1, $v0
TEXT:800A2428 # ---------------------------------------------------------------------------
TEXT:800A2428
TEXT:800A2428 loc_800A2428: # CODE XREF: BuildCharaToLV+64j
TEXT:800A2428 lw $v0, 0x28+var_18($sp) <-------
TEXT:800A242C nop
TEXT:800A2430 subu $v0, $s1
TEXT:800A2434 bgtz $v0, loc_800A2440
TEXT:800A2438 nop
TEXT:800A243C li $v0, 1
TEXT:800A2440
TEXT:800A2440 loc_800A2440: # CODE XREF: BuildCharaToLV+70j
TEXT:800A2440 # BuildCharaToLV+84j
TEXT:800A2440 sw $v0, 0x28+var_18($sp)
TEXT:800A2444 li $a0, 3
TEXT:800A2448 lw $a1, 0x28+var_18($sp)
TEXT:800A244C move $a2, $s2
TEXT:800A2450 jal MP_Level_Sub
TEXT:800A2454 move $a3, $0
TEXT:800A2458 lw $ra, 0x28+var_4($sp)
TEXT:800A245C lw $s2, 0x28+var_8($sp)
TEXT:800A2460 lw $s1, 0x28+var_C($sp)
TEXT:800A2464 lw $s0, 0x28+var_10($sp)
TEXT:800A2468 addiu $sp, 0x28
TEXT:800A246C jr $ra
TEXT:800A2470 nop
TEXT:800A2470 # End of function BuildCharaToLV
The lines indicated with arrows are the proximate cause of the bug. The MP_Level_Sub is multi-purpose. It can add levels to a character, retrieve a particular character's current level, etc. In the function above, it is used in both capacities. The first call retrieves a character's current level. In most cases this will be the Hero. When called for this purpose, it expects to be handed a pointer to a signed 8-bit integer that it can store the requested character's level on. Within the function, it actually executes a Store Byte command on this address. The problem is, the BuildCharaToLV subroutine has passed a pointer to an address on the stack, and hasn't taken the trouble to initialize the storage. When it subsequently loads the value given back by MP_Level_Sub, as 32-bits, it will get the byte stored, along with three bytes that will contain whatever values were left in them. When it calls MP_Level_Sub the second time, it passes an argument that could be the desired level, or anything up to 0x7FFFFF00 + the desired level.

Luckily, other functions will prevent the level from going over 99, or otherwise doing something that would let this result in a crash.

Fix


Ideally, the calling function would initialize the space to zero before making the call. It's not terribly practical to edit the routine to do this, as none of the operations preceding the call are easily replaceable. The fix selected was to replace the first two Load Word operations that retrieve the stored level with Load Byte Unsigned operations. The end result is that the value is retrieved as it was stored, operated on, and then stored again as a word, clearing the upper bits of their stale values.

GameShark Code

If you are playing on a console with an original copy, you can use this code to prevent the bug from occurring.

Fix Recruit at Level 99 Bug
(North American Version)
800A237E 93A2
800A238A 93A2

(European English Version)
800A241C 93A2
800A2428 93A2

Patch


See the Patch Files page for your version.