Permalink
Browse files

GameDB: Add patches for King's Field IV PAL/NTSC-U.

Fixes central tower level loading.
Patches by Wgarvin.
  • Loading branch information...
lightningterror committed Nov 1, 2018
1 parent fd75085 commit 6052614233b30dbd3d994ed60cb4739fbb59f1e4
Showing with 10 additions and 0 deletions.
  1. +10 −0 bin/GameIndex.dbf
View
@@ -8622,6 +8622,11 @@ Serial = SLES-50920
Name = King's Field IV
Region = PAL-M5
Compat = 5
[patches = 401F4726]
comment=Patch by Wgarvin
// Fixes central tower level loading.
patch=1,EE,001BCC4C,word,46150036
[/patches]
---------------------------------------------
Serial = SLES-50921
Name = Way of the Samurai
@@ -35788,6 +35793,11 @@ Serial = SLUS-20318
Name = King's Field IV
Region = NTSC-U
Compat = 5
[patches = 36E02E91]
comment=Patch by Wgarvin
// Fixes central tower level loading.
patch=1,EE,001BE42C,word,46150036
[/patches]
---------------------------------------------
Serial = SLUS-20319
Name = Romance of the Three Kingdoms VII

2 comments on commit 6052614

@wgarvin0

This comment has been minimized.

wgarvin0 replied Nov 2, 2018

I was asked to add a comment explaining this patch. (sorry for wall of text)

Kings Field 4 has a gameworld made up of interconnected areas which are dynamically loaded (no load screens once you are in the game). The dynamic loading uses pairs of triggers: first a "loading trigger" telling it to load the data for that area, and then an "activation trigger" which causes it to be connected to the world. The player must pass through--or step on--both of these, or the new area won't be loaded and instead of a doorway or tunnel, the player will just see an empty black rectangle.

The game keeps an array of static objects for the active area(s) (at 0x146C740 in US version) and there is a routine that determines which of those objects the player is standing on (or perhaps passing through?) and writes its index to a halfword at 0x4141D2. In the room where the bug occurs, the object index 0x0002 is used for most of the room, but there is a "loading trigger" at the base of the staircase with index 0x0019 and an "activation trigger" at the top of the staircase with index 0x0018. The data structures about the objects seem to include info about which area to load or activate; if the game never sees you "stand on" the proper trigger object, the necessary loading or activating does not occur. (But when it does occur, you can see a loading-related flag at 0x38CD20 or 0x38CD80 change between 1 and 0, and the "loading state machine" variable at 0x38D620 will cycle through a range of values: 0 means nothing loaded, 1..4 means unloading old stuff or loading the new stuff, 5 means "ready to activate", 6..8 are used during activation and then it resets to 0).

Anyway, the routine to determine which object you are standing on (which starts at 0x1BDDC0 in US version) seems to iterate over the entire list of static objects, culling (skipping) them for various reasons, including what appears to be an AABB test against the player position. (Actually there are two lists it iterates over, but only the one at 0x146C740 contains the objects relevant to repro this bug). If an object passes all of its tests, its object index is written to the halfword at 0x4141D2.

Suppose the player is standing on the loading trigger at the bottom of the stairs, so it should detect object 0x0019. It iterates through all the objects, and the first object to pass all of the checks without getting culled is 0x0002 (the nearby room floor), so it gets chosen as the "best thing so far" and written to 0x4141D2. It keeps iterating over additional objects and culling them, until it gets to 0x0019 (the "loading trigger" object) and this is where the bug occurs: this object fails the AABB test, seemingly because it is at exactly the same height as the earlier object, and the test was written with a "less than" comparison, so it will only accept an object with a more negative Y coord (in the game's coordinate system, +Y axis points downward). Since the object 0x0019 gets culled, the halfword at 0x4141D2 still says 0x0002 and the loading of the area doesn't occur. I think at the top of the stairs, the activation trigger 0x0018 gets culled in the same way.

Now in EE/FPU Rounding modes "Chop/Zero" or "Normal", the Y coordinate of the floor in this room is exactly +325.00 and so is the top of the loading trigger. The top of the stairs (and Y coord of the top of the activation trigger) are both at exactly -350.00. But if your EE/FPU Rounding mode is "Negative", the player position is calculated badly (I guess due to fp rounding as transforms were applied to it?) and so the Y coordinate varies slightly everywhere in the room, but is around +328.5 to +327.6, which happens to be a few centimetres "below" the proper Y coordinate of the trigger, which is why it got triggered anyway (working around the bug). The "activation" trigger at the top of the stairs is more finnicky and it seems that even in "Negative" mode you have to step on the left half of the top stair or it might fail to activate, but I think this is because of the wonky calculation of player Y.

I'm not sure why this bug didn't manifest on actual PS2 hardware, but I am assuming that the math applying transforms to the player position ends up having a slightly different rounding behavior on actual PS2 than it does on IEEE-754-compliant x86 chips, which presumably results in the player being a very tiny amount "lower" (more +Y) on a PS2 than they are in pcsx2, at least for this particular room. By watching the Y position and 0x4141D2 variable while moving my player around in other areas, I got the impression that many (nearly all?) of the game's trigger objects were slightly raised up above the surrounding floors by a tiny amount by the developers, which suggests to me that maybe the level designers had encountered cases where a trigger with the identical Y coordinate to the nearby floor didn't work when they tested it, so they decided to raise them all up slightly to avoid the issue. But for whatever reason, the trigger objects 0x0019 and 0x0018 on the staircase just before the buggy door didn't get that treatment.

The code patch changes that float comparison from a c.lt.s instruction, to c.le.s ("less than or equal") so that objects appearing later in the list will not be culled by this test if they have the exact same top Y coord. (The order appears to be statically assigned in each area; this list only deals with static things like level geometry, not dynamic things like enemies). In the room with the two buggy triggers, the effect of the code patch is to allow the objects 0x0018 and 0x0019 to override the general room object 0x0002 even though their Y coords are identical.

How I found the instruction to patch:

There was a known but unsatisfying workaround for the bug (setting EE/FPU Rounding mode to "Negative") that I used to help figure out which triggers were not 'triggering' and their general positions in the gameworld. Using CheatEngine on the pcsx2 process, I identified some global vars that store the player position (at 0x413F80) and some other variables that got changed when a "loading" or "activation" trigger was properly triggered (such as the 96-byte structs at 0x38CD20 and 0x38CD80), and used breakpoints on those to find some code that was being run in response to the trigger, and worked backwards from there in the debugger looking for the code that was actually deciding that it had been 'triggered'. This led to the halfword at 0x4141D2 where the object index gets written (with CheatEngine, I watched it being set to various values as I stepped on and off the trigger objects, while running in various EE/FPU Rounding modes). I put a write breakpoint on that halfword, which led me to the routine at 0x1BDDC0 that determines which object you are standing on. I stepped through that routine a bunch of times under various conditions until I had identified the c.lt.s instruction at 0x1BE42C that was causing the "loading" trigger (object 0x0019) to be erroneously culled. (if you put an execute breakpoint at 0x1BDFA8, you can see it iterate through the list.. s4 = object ptr, s5 = object index) The two operands of the c.le.s had bit-for-bit identical values in them, which was also bit-for-bit identical to the player Y position while standing on this trigger--or on the nearby room floor. I tried patching it with a c.le.s and it seemed to work, so I made a .pnach and spent a while testing it more extensively. When I was pretty sure it was OK, I posted it to the GameFaqs thread. But I still a bit worried that this code patch might break some other game area (e.g. suppose there had been a room with the same problem of the triggers being at exactly the same Y as the floor, except in the opposite order in the list?) so I did a complete playthrough of the U.S. version of the game with this patch. I followed a gamefaqs walkthrough by seorin that I am pretty sure covers 100% of the locations in the game. Everything loaded fine and no strange problems were encountered.

On the European version, the addresses are different but the general story is the same. The halfword with the object index gets written at 0x413992, the routine that figures out which object you're standing on is at 0x1BC5E0 and the c.lt.s instruction that needs patching is at 0x1BCC4C. On the European version I have only tested this fix in the immediate areas around where the bug occurs, but as far as I know all versions of this game have identical level design--I think the only game differences are related to PAL/NTSC framerate, localised text, etc. To skip past the first half of the game, I warped to the end of the Forge by writing
a 19 to the word at 0x38CEDC and then a 1 to the word at 0x38CED8.

Global variable addresses that I found useful while investigating this bug:
US 38CD20 --> Euro 38C560 : byte // "Current Location0 Entrance"
US 38CD80 --> Euro 38C5C0 : byte // "Current Location1 Entrance"
US 38D620 --> Euro 38CE60 : int // Loading State-Machine State
US 413ED0 --> Euro 413690 : float[6] // Gravity settings
US 413F80 --> Euro 413740 : float[3] // Player XYZ position -5656.00 0.00 -10716.00
US 4141D2 --> Euro 413992 : ushort // Object Index of the object you are currently standing on

If anyone wants to poke around in this game, here are all the global variables, etc. that I found:
Global variables for the US version: https://pastebin.com/raw/cjXmeJ4R
Global variables for the European version: https://pastebin.com/raw/uRa98htE
How to warp instantly to most areas of the game: https://pastebin.com/raw/VUFuX0L7

@willkuer

This comment has been minimized.

Contributor

willkuer replied Nov 2, 2018

Amazing summary! Not sure I will ever needs this info but I guess this might be interesting in the future upon reevaluating gamehacks and for modders. At least it was an interesting read for me. Thanks for sharing.

Please sign in to comment.