Suikoden II Bug: Castle Farm Bug
Throughout the game, the player can collect farm animals that can be given to Yuzu in the castle garden. Likewise, plant seeds can be collected and turned in to the gardener, Tony. When these items are handed in, they should be shown wandering the screen, in the case of animals, or growing in the garden, the next time the player returns to the screen. This is what happens as long as you turn in only one of a particular type of item. If, for instance, two Cabbage Seeds are turned in together, only one new set of cabbage plants will be shown.
Affected Versions
All versions are affected by this bug.
Cause
There are several copies of each item type that can be found throughout the game. The caretakers have some of them by default (Tony starts with a Cabbage and Potato Seed), but in general there are three available to find. When you turn in a seed, a count is updated, to track how many have been handed in. After the count is updated, the bit flags for the item are set based on the count. The problem is that when the count is 3, it only sets the bit for the third copy of the item. If you've handed in all three at once, that means it will immediately set the flag for the third set, leaving the other two unset. It would have been simple to set all the flags for a particular item, had Konami not shared space for seeds and animals, and handled the flags as an array of bytes.
Here's how the data breaks down:
Item Counts
|
Type of Item
|
Address
|
Cabbage
|
0x8006AA2B
|
Potato
|
0x8006AA2C
|
Spinach
|
0x8006AA2D
|
Tomato
|
0x8006AA2E
|
Chick
|
0x8006AA2F
|
Pig
|
0x8006AA30
|
Sheep
|
0x8006AA31
|
Cow
|
0x8006AA31
|
Bit Flags
|
|
Position 0
|
Position 1
|
Position 2
|
Position 3
|
Position 4
|
Position 5
|
Position 6
|
Position 7
|
0x8006A91C
|
Cabbage
|
Cabbage
|
Cabbage
|
Potato
|
Potato
|
Potato
|
Spinach
|
Spinach
|
0x8006A91D
|
Spinach
|
Tomato
|
Tomato
|
Tomato
|
Chick
|
Chick
|
Chick
|
Pig
|
0x8006A91E
|
Pig
|
Pig
|
Sheep
|
Sheep
|
Sheep
|
Sheep
|
Cow
|
Cow
|
0x8006A91F
|
Cow
|
None
|
None
|
None
|
None
|
None
|
None
|
None
|
The code below is taken from the routine that accepts seeds. It's slightly simpler, owing to the fact that there are only three of each kind of item it has to deal with. Still, it has to span bytes and handle the data in somewhat ugly ways. The snippet is
after the counting of seeds in the party's inventory. There is nothing wrong with the way the count is done. All the relevant code is in
/CDROM/110_ARK/VK19.BIN.
RAM:8015E198 lbu $a0, 0x1A2B($v1)
RAM:8015E19C li $v0, 1
RAM:8015E1A0 bne $a0, $v0, loc_8015E1B4
RAM:8015E1A4 nop
RAM:8015E1A8 lbu $v0, 0x191C($v1)
RAM:8015E1AC j loc_8015E1E4
RAM:8015E1B0 ori $v0, 1
RAM:8015E1B4 # ---------------------------------------------------------------------------
RAM:8015E1B4
RAM:8015E1B4 loc_8015E1B4: # CODE XREF: TakeSeeds+134j
RAM:8015E1B4 li $v0, 2
RAM:8015E1B8 bne $a0, $v0, loc_8015E1CC
RAM:8015E1BC nop
RAM:8015E1C0 lbu $v0, 0x191C($v1)
RAM:8015E1C4 j loc_8015E1E4
RAM:8015E1C8 ori $v0, 2
RAM:8015E1CC # ---------------------------------------------------------------------------
RAM:8015E1CC
RAM:8015E1CC loc_8015E1CC: # CODE XREF: TakeSeeds+14Cj
RAM:8015E1CC li $v0, 3
RAM:8015E1D0 bne $a0, $v0, loc_8015E1E8
RAM:8015E1D4 nop
RAM:8015E1D8 lbu $v0, 0x191C($v1)
RAM:8015E1DC nop
RAM:8015E1E0 ori $v0, 4
RAM:8015E1E4
RAM:8015E1E4 loc_8015E1E4: # CODE XREF: TakeSeeds+140j
RAM:8015E1E4 # TakeSeeds+158j
RAM:8015E1E4 sb $v0, 0x191C($v1)
RAM:8015E1E8
RAM:8015E1E8 loc_8015E1E8: # CODE XREF: TakeSeeds+164j
RAM:8015E1E8 ori $v1, $s7, 0x8000
RAM:8015E1EC lbu $a0, 0x1A2C($v1)
RAM:8015E1F0 li $v0, 1
RAM:8015E1F4 bne $a0, $v0, loc_8015E208
RAM:8015E1F8 nop
RAM:8015E1FC lbu $v0, 0x191C($v1)
RAM:8015E200 j loc_8015E238
RAM:8015E204 ori $v0, 8
RAM:8015E208 # ---------------------------------------------------------------------------
RAM:8015E208
RAM:8015E208 loc_8015E208: # CODE XREF: TakeSeeds+188j
RAM:8015E208 li $v0, 2
RAM:8015E20C bne $a0, $v0, loc_8015E220
RAM:8015E210 nop
RAM:8015E214 lbu $v0, 0x191C($v1)
RAM:8015E218 j loc_8015E238
RAM:8015E21C ori $v0, 0x10
RAM:8015E220 # ---------------------------------------------------------------------------
RAM:8015E220
RAM:8015E220 loc_8015E220: # CODE XREF: TakeSeeds+1A0j
RAM:8015E220 li $v0, 3
RAM:8015E224 bne $a0, $v0, loc_8015E23C
RAM:8015E228 nop
RAM:8015E22C lbu $v0, 0x191C($v1)
RAM:8015E230 nop
RAM:8015E234 ori $v0, 0x20
RAM:8015E238
RAM:8015E238 loc_8015E238: # CODE XREF: TakeSeeds+194j
RAM:8015E238 # TakeSeeds+1ACj
RAM:8015E238 sb $v0, 0x191C($v1)
RAM:8015E23C
RAM:8015E23C loc_8015E23C: # CODE XREF: TakeSeeds+1B8j
RAM:8015E23C ori $v1, $s7, 0x8000
RAM:8015E240 lbu $a0, 0x1A2D($v1)
RAM:8015E244 li $v0, 1
RAM:8015E248 bne $a0, $v0, loc_8015E264
RAM:8015E24C li $v0, 2
RAM:8015E250 lbu $v0, 0x191C($v1)
RAM:8015E254 nop
RAM:8015E258 ori $v0, 0x40
RAM:8015E25C j loc_8015E29C
RAM:8015E260 sb $v0, 0x191C($v1)
RAM:8015E264 # ---------------------------------------------------------------------------
RAM:8015E264
RAM:8015E264 loc_8015E264: # CODE XREF: TakeSeeds+1DCj
RAM:8015E264 bne $a0, $v0, loc_8015E280
RAM:8015E268 li $v0, 3
RAM:8015E26C lbu $v0, 0x191C($v1)
RAM:8015E270 nop
RAM:8015E274 ori $v0, 0x80
RAM:8015E278 j loc_8015E298
RAM:8015E27C sb $v0, 0x191C($v1)
RAM:8015E280 # ---------------------------------------------------------------------------
RAM:8015E280
RAM:8015E280 loc_8015E280: # CODE XREF: TakeSeeds:loc_8015E264j
RAM:8015E280 bne $a0, $v0, loc_8015E298
RAM:8015E284 nop
RAM:8015E288 lbu $v0, 0x191D($v1)
RAM:8015E28C nop
RAM:8015E290 ori $v0, 1
RAM:8015E294 sb $v0, 0x191D($v1)
RAM:8015E298
RAM:8015E298 loc_8015E298: # CODE XREF: TakeSeeds+20Cj
RAM:8015E298 # TakeSeeds:loc_8015E280j
RAM:8015E298 ori $v1, $s7, 0x8000
RAM:8015E29C
RAM:8015E29C loc_8015E29C: # CODE XREF: TakeSeeds+1F0j
RAM:8015E29C lbu $a0, 0x1A2E($v1)
RAM:8015E2A0 li $v0, 1
RAM:8015E2A4 bne $a0, $v0, loc_8015E2B8
RAM:8015E2A8 nop
RAM:8015E2AC lbu $v0, 0x191D($v1)
RAM:8015E2B0 j loc_8015E2E8
RAM:8015E2B4 ori $v0, 2
RAM:8015E2B8 # ---------------------------------------------------------------------------
RAM:8015E2B8
RAM:8015E2B8 loc_8015E2B8: # CODE XREF: TakeSeeds+238j
RAM:8015E2B8 li $v0, 2
RAM:8015E2BC bne $a0, $v0, loc_8015E2D0
RAM:8015E2C0 nop
RAM:8015E2C4 lbu $v0, 0x191D($v1)
RAM:8015E2C8 j loc_8015E2E8
RAM:8015E2CC ori $v0, 4
RAM:8015E2D0 # ---------------------------------------------------------------------------
RAM:8015E2D0
RAM:8015E2D0 loc_8015E2D0: # CODE XREF: TakeSeeds+250j
RAM:8015E2D0 li $v0, 3
RAM:8015E2D4 bne $a0, $v0, loc_8015E2F0
RAM:8015E2D8 li $v0, 1
RAM:8015E2DC lbu $v0, 0x191D($v1)
RAM:8015E2E0 nop
RAM:8015E2E4 ori $v0, 8
RAM:8015E2E8
RAM:8015E2E8 loc_8015E2E8: # CODE XREF: TakeSeeds+244j
RAM:8015E2E8 # TakeSeeds+25Cj
RAM:8015E2E8 sb $v0, 0x191D($v1)
RAM:8015E2EC li $v0, 1
Fix
There are so many different and better ways this could have been handled. It seems like the developer was trying to develop an easily expanded routine, as if he didn't know what the final number of these items would be. Even assuming that, this is bad. It seems like it was coded by someone unaware of how to work with data at the level of bits, which has been sort of a theme with Suikoden II.
There really isn't an easy way to modify the existing routine to work correctly. So it had to be rewritten. This is actually pretty easy to do. The existing routine is huge and bloated, and can be replaced with a routine about a third of the size. Code just needs to be inserted after the inventory count, to replace the setting of flags.
.openfile VK19.BIN, 0x8015DC50
.headersize 0
.org 0x8015E198
; file location will be 0x540
; start with cabbage.
cabbage:
lbu v0, 0x1A2B(v1) ; cabbage count
lw s4, 0x191C(v1) ; flags (seeds and stock)
li s6, 7 ; mask base
li s5, 3 ; max count (this will keep)
subu v0, s5, v0 ; shift amount
srlv v0, s6, v0 ; 0x7 >> (3 - cabbage count)
or s4, s4, v0 ; flags |= mask
potato:
lbu v0, 0x1A2C(v1) ; count
li s6, 7 ; load delay
subu v0, s5, v0
srlv v0, s6, v0
sll v0, v0, 3 ; finish mask
or s4, s4, v0
spinach:
lbu v0, 0x1A2D(v1) ; count
li s6, 7 ; load delay
subu v0, s5, v0
srlv v0, s6, v0
sll v0, v0, 6 ; finish mask
or s4, s4, v0
tomato:
lbu v0, 0x1A2E(v1) ; count
li s6, 7 ; load delay
subu v0, s5, v0
srlv v0, s6, v0
sll v0, v0, 9 ; finish mask
or s4, s4, v0
store:
sw s4, 0x191C(v1)
beq zero, zero, 0x8015E2EC ; skip the rest
nop
.close
The above takes the counts, and uses them to construct an appropriate mask that will be used to set the required bits in the game's data. It sets
all the bits indicated by the counts, each time the routine is called. In this way, you can turn in none or all the seeds at one time, and the flags will be retained or set as needed. It also treats the entire 32-bit value allocated to the farm flags (both seeds and livestock) as a single variable. Doing this, the code doesn't need to worry about moving ahead bytes. It just has to set the right bits in the data. Since the only modification done to the flags is a bitwise OR, it can never unset anything by accident.
GameShark Codes
A code for this fix would be so long that it could not be entered. It is easier to use existing codes to set the flag, after the bug has been encountered.
Patch
Find your version on the
Patch Files page.