Every enemy in the game has space for three "drops", items that they may drop after being killed. These drops all have a probability from 1/255 to 254/255—it should be 255/255, but that's another bug. Enemies that drop a Recipe item, are only allowed to drop it once, but it will always be possible for the Recipe drop to trigger. It isn't removed or replaced; receipt of the item is simply prevented after the fact by checking to see if it has been dropped previously. Apparently to save space in the Playstation's precious RAM, Konami decided not to flag the occurrence of a drop, and instead opted to search for the item everywhere a player might put it. That includes the party's bag, storage, and Hai Yo's "cookbook".
The searches through various inventories actually work correctly. The bug mostly manifests when Hai Yo has recipes. However, the error in the check can also cause data unrelated to recipes to be examined in some cases. Luckily for Konami, this mostly occurs for recipes given as part of an event, like a Cook-off, or found in towns, chests, or shops. Since the after-battle module is not used in those cases, the bug has no opportunity to ruin it for you.
Picture it like this: You need to check whether there is mail for a particular unit in a post office. The office has five buildings, numbered 0-4, and each building has 8 mailboxes numbered 0-7. When a box contains mail, a display on the front will be lit. For simplicity's sake, people use numbers 1-40 to address these boxes. So to everyone else, mail is in P.O. Box 1, but to you and your co-workers it's in building 0, box 0. And P.O. Box 40 would be building 4, box 7. When a customer calls in, and gives his P.O. Box number, you subtract 1 from it, and divide by 8. The quotient is the building number, and the remainder is the box number
Simple enough, right? This is essentially what the after-battle module should do, in order to check whether or not Hai Yo has a particular recipe. The only difference is that it needs to go to a byte instead of a building, and look at a bit instead of a box. Unfortunately, the numbers are computed wrong. To start with, Konami determines the recipe number from the item digit, and never subtracts 1 from it as above. Even if everything else was correct, they would examine position (4,2) for Recipe #34, which would be the flag for Recipe #35. Every check would be one bit (box) off, and the last recipe would be checked against postion (5,0), which is data that is not related to Hai Yo's cookbook. The error is further compounded when they flip the position values. So for Recipe #34, they incorrectly determine (4,2), and then check (2,4) for good measure. Those of you who are good at math probably already know that that positon corresponds to Recipe #21. The table below shows what byte and bit are checked for each Recipe, and what the flag at that position actually indicates. Thanks to the swapping of parameters, 3 bytes unrelated to Hai Yo's cookbook (bytes 5-7) can be examined. These appear to be event flags for other purposes.
In reality, the bit number should ascend 0-7, and the byte number 0-4, increasing by 1 each time the bit number maxes out. So Recipe #9 should be byte 1, bit 0; Recipe #17 should be byte 2, bit 0, and so on.
|Acquired||Check Recipe Flag At|
|(Event) Hai Yo joins||1||0||Recipe #9|
|(Event) Cook-off 1||2||0||Recipe #17|
|(Speak) Huan in Muse||3||0||Recipe #25|
|(Event) Hai Yo joins||4||0||Recipe #33|
|(Speak) Cook in Coronet Inn||5||0||Unknown Data|
|(Event) Hai Yo joins||6||0||Unknown Data|
(BBQ Meat Bun)
|(Event) Hai Yo joins||7||0||Unknown Data|
|(Event) Cook-off 2||0||1||Recipe #2|
|(Search) Kuskus bookshelf||1||1||Recipe #10|
|(Event) Hai Yo joins||2||1||Recipe #18|
|(Rare Find) Kuskus Item shop||3||1||Recipe #26|
|(Drop) Eagle Man, Kobold Forest||4||1||Recipe #34|
|(Speak) Cook in Kobold Village Chief's tent||5||1||Unknown Data|
|(Speak) Cook in Radat Tavern||6||1||Unknown Data|
(Fried Fish Balls)
|(Event) Hai Yo joins||7||1||Unknown Data|
|(Search) Lakewest, Shelf in Taki's house||0||2||Recipe #3|
|(Event) Cook-off 3||1||2||Recipe #11|
|(Rare Find) Radat Item Shop||2||2||Recipe #19|
|(Event) Cook-off 4||3||2||Recipe #27|
|(Speak) Cook in Greenhill Inn||4||2||Recipe #35|
|(Drop) Land Sharks in Two River Sewers||5||2||Unknown Data|
|(Event) Cook-off 6||6||2||Unknown Data|
|(Rare Find) Two River Item Shop||7||2||Unknown Data|
|(Event) Cook-off 5||0||3||Recipe #4|
|(Rare Find) Greenhill Item Shop||1||3||Recipe #12|
|(Rare Find) Highway Village Item Shop||2||3||Recipe #20|
|(Event) Cook-off 8||3||3||Recipe #28|
|(Rare Find) Gregminster Item shop||4||3||Recipe #36|
|(Drop) Zombie Slug in Matilda Path||5||3||Unknown Data|
|(Event) Cook-off 7||6||3||Unknown Data|
|(Rare Find) Kobold Village Item Shop||7||3||Unknown Data|
|(Speak) Kent's mother in Highway Village||0||4||Recipe #5|
|(Event) Cook-off 10||1||4||Recipe #13|
|(Drop) DoReMi Elves in Greenhill Forest Path||2||4||Recipe #21|
|(Rare Find) Muse Item shop||3||4||Recipe #29|
|(Drop) Highland Soldiers in Rockaxe||4||4||Recipe #37|
|(Search) Gorudo's room in Rockaxe||5||4||Unknown Data|
|(Event) Cook-off 11||6||4||Unknown Data|
|(Speak) Gremio in Gregminster||7||4||Unknown Data|
|(Event) Cook-off 12||0||5||Recipe #6|
Dropped recipes are indicated in red. They are the only items affected by the bug in the after-battle code. The remaining recipes have been included to obviate the pattern. It is possible that there are other bugs, that might affect recipes acquired through Rare Finds, etc, but no reports of such an issue are known at the time of writing.
As you can see, Recipe #34 becomes impossible to find if Recipe #21 has been handed in; Recipe #12 becomes impossible to find if Recipe #34 has been handed in; and Recipe #36 becomes impossible to find when Recipe #37 has been handed in. Recipe #36 is an interesting case, because it is the last dropped recipe available. If you are following a guide, and avoided handing in any dropped recipes until after you got #36, it becomes possible to get duplicate copies of it when you go back to Rockaxe to claim Recipe #37. It is also possible to get duplicates of the other dropped recipes. For example, if you hand in Recipe #34 and don't hand in #21, you will be able to get #34 again. It will be absent from your bag and storage, and the wrong flag will be checked, so the game will believe you never got it.
Most unfortunately, Recipe #21 and Recipe #29 are checked against data that serves another purpose. It is possible, though it seems unlikely, that those recipes can become inaccessible for other reasons. It is unknown what purpose those variables serve. Luckily, in watching the data throughout play, it appears that byte remains zero for much of the game, and the bits in question remain unset at least until after Muse is liberated. That means you can get these recipes without issue, even long after they first become available. It also means you can get duplicates of them for much, if not all, of the game.
Fix Recipe Bug 2 (1-24)
(North American Version)
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, item is one of the recipes. RAM:8002DA2C beqz $v0, loc_8002DAAC <- Get out if it is 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 is 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. (Laymen: shifting right 3 = integer division by 8) 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 (remainder). RAM:8002DA54 move $s3, $v1 <- Save the byte 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 is 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).