Suikoden II Bug: Recipes Become Unobtainable
This bug is one of the more frustrating problems that players can encounter in
Suikoden II. Recipes are items obtained during story events, from chests, random people, bookshelves, enemy drops, shop rare finds, etc. If the designers decided they could place items in an object, chances are at least one recipe can be found in an instance of it. This bug can make it impossible to find certain recipes. It also makes it possible to obtain duplicate copies of certain recipes.
This bug affects all versions.
Cause
The bug has variously been attributed to getting Recipe #X before Recipe #Y, handing recipes to Hai Yo, and twirling the wrong color cat in a graveyard at the full moon. The reason this bug is so annoying is that five of the recipes are rare drops from enemies, so it is difficult to tell if you are being denied by bad luck, or the bug. Recipe #34 famously becomes Unobtainium unless great care is taken in dealing with recipes.
The underlying cause of the bug is a simple programming error. There is a function that checks the bit flags that indicate a recipe has been given to Hai Yo. This is used when determining if it is appropriate for an enemy to drop a recipe, and is also checked to see if a recipe should appear in Rare Finds in a shop. Code for North American (NTSC/UC) version is shown below, with commentary that illuminates the problem. This code resides in
/CDROM/150_BPRG/BUFF0/BP0_AFT.BIN
RAM:8002DA18 bnez $a1, loc_8002DAB0 <- Check the item type for zero.
RAM:8002DA1C move $v0, $0
RAM:8002DA20 addiu $v0, $a0, 0xFFC8 <-Essentially chop out the bottom of type-zero items by adding -0x38
RAM:8002DA24 andi $v0, 0xFF
RAM:8002DA28 sltiu $v0, 0x10 <- If the result is a value less than 0x10, it's one of the recipes.
RAM:8002DA2C beqz $v0, loc_8002DAAC <- Get out if it's not a recipe.
RAM:8002DA30 andi $v0, $v1, 0xFF <- Clear out the high-order bits of the item index/digit.
RAM:8002DA34 addiu $a1, $v0, 0xFFE1 <- Add -0x1F (-31)...this gives the recipe #, but it's wrong. It should be -32.
RAM:8002DA38 bgez $a1, loc_8002DA44 <- This should always be true, given previous logic.
RAM:8002DA3C move $v1, $a1
RAM:8002DA40 addiu $v1, $v0, 0xFFE8
RAM:8002DA44
RAM:8002DA44 loc_8002DA44:
RAM:8002DA44 sra $s2, $v1, 3 <- Get the byte offset.
RAM:8002DA48 move $v1, $s2 <- Save the byte offset.
RAM:8002DA4C sll $v0, $v1, 3 <- This line and...
RAM:8002DA50 subu $s2, $a1, $v0 <- This line establish the bit number within the byte.
RAM:8002DA54 move $s3, $v1 <- Save the bit number.
RAM:8002DA58 la $s1, sub_800D4CBC <- The next few lines are all debug prints.
RAM:8002DA60 la $a0, aBitValDNoDShif
RAM:8002DA68 move $a2, $s2
RAM:8002DA6C move $a3, $s3
RAM:8002DA70 lui $s0, 0x8004
RAM:8002DA74 jalr $s1
RAM:8002DA78 sw $s1, off_8003AEF0
RAM:8002DA7C sw $s1, off_8003AEF0
RAM:8002DA80 addu $s0, $s2, $s4 <-Adds the bit number to the flag address. Should be byte number.
RAM:8002DA84 lui $a0, 0x8003 <-More debug print screen crap coming up.
RAM:8002DA88 lbu $a1, 0x1E6($s0) <-Load the flag byte for printing.
RAM:8002DA8C jalr $s1
RAM:8002DA90 la $a0, aFoodFlg8b
RAM:8002DA94 lbu $v1, 0x1E6($s0) <-Load flag byte so bit within can be checked, this address will be wrong.
RAM:8002DA98 nop
RAM:8002DA9C srav $v1, $s3 <-Shift the flags by the byte number. Should be bit number, but even that's wrong.
RAM:8002DAA0 andi $v1, 1 <-Clear all bits except the flag bit we want.
RAM:8002DAA4 bnez $v1, loc_8002DAB0 <-Go to exit if found, return the 1 set below.
RAM:8002DAA8 li $v0, 1
RAM:8002DAAC
RAM:8002DAAC loc_8002DAAC:
RAM:8002DAAC
RAM:8002DAAC move $v0, $0 <-Otherwise, return not found (i.e., you can get the recipe in battle).
The code determines what byte the flag is in, so it can load it, then it determines what bit number within the byte will represent that recipe. If the bit is 1, then you already handed it over to Hai Yo. The problem is, they determine both numbers in a way that is incorrect, and then they use the incorrect numbers wrong by treating the bit number as the byte offset, and the byte offset as the bit number. When they initially calculate the numbers, it's done in such a way that one bit per byte seems not to exist (the last one), and the byte number comes up one off for many recipes. They compute the bit number, and forget that you don't find "bit 1" of byte 4 by right-shifting the value of byte 4 by 1, because that actually puts the second bit into the first position. It's a cavalcade of really dumb mistakes, and it's a miracle that any recipes drop at all after you give a few to Hai Yo.
This routine will determine that Recipe #34 is handed into Hai Yo if the third byte's 5th bit is set. This actually indicates Recipe #21, which is the one the Land Sharks in the Two River Sewers drop. The exact same routine is duplicated for Recipes #1-24, which are a different item type. For whatever reason, the flags are
set properly, but checking them is a mess.
Fix
The fix is fairly small, if a bit confusing. The code must be modified so that it subtracts 32 in initially determining the recipe's position, and it must be modified so that it uses the byte index and bit shift values for their intended purposes.
GameShark Codes
Fix Recipe Bug 1 (25-40)
(North American Version)
D002DA20 FFC8
8002DA34 FFE0
D002DA20 FFC8
8002DA82 0274
D002DA20 FFC8
8002DA9E 0243
Fix Recipe Bug 2 (1-24)
(North American Version)
D002DA20 FFC8
8002D998 FFD5
D002DA20 FFC8
8002D9E6 0274
D002DA20 FFC8
8002DA02 0242
Patch
See the Patch Files page for your version.