An Old Problem Meets Its Timely Demise

The Legend of Zelda: The Wind Waker is one of the most popular Gamecube games, if not Nintendo games, in existence. Its mixture of an open world, sharp dungeons, and an inventive art style turned heads more than ten years ago when it was released. Dolphin has had its share of problems with Wind Waker, but none could be so frustrating as its mishandling of the heat distortion.

The Issues

This issue actually crops up in two ways throughout Wind Waker. The more common way is that all of the flame effects result in doubling rather than distortion. The "copy" ends up down and to the right quite a bit, making it awkward at best.

Fire Double Image Example

More troublesome is the way extreme heat in Dragon Roost Island is rendered. It results in severe screen tearing and detracts from the strong atmosphere presented by the game.

Dragon Roost Heat Example

Desperate Measures

For years, it appeared this issue would never be fixed. No one knew what was wrong, and no one was focusing on fixing it for the longest time. Users, unlike developers, often care about the end product more than the why. That allows them to develop sometimes genius patches, codes, and work-arounds that developers would never condone or sometimes even consider! Using the free camera, action replay codes (shown below) and even texture replacement, users of Dolphin found their way to at least make the game tolerable.


The Payoff

As of 4.0-593, this issue is no more! The best way to explain how it was fixed would be to take the words from the one who fixed. delroth left a very entertaining and well thought out commit message that really dives into the error, why it existed, and how it was fixed.

The following is an excerpt from delroth's commit message

Let's talk a bit about this bug a bit. [The Wind Waker Heat Distortion Issue] is the 12th oldest remaining glitch in Dolphin. It was a lot of fun to debug and it kept me busy for a while :)

Shoutout to Nintendo for, without which this could have taken a lot longer.

What goes where...

Basic debugging using apitrace shows that the heat effect is rendered in an interesting way:
  • An EFB copy texture is created, using the hardware scaler to divide the texture resolution by two and that way create the blur effect.
  • This texture is then warped using indirect texturing: a deformation map is used to "move" the texture coordinates used to sample the framebuffer copy.

Pixel shader:

Interestingly, when looking at apitrace, the deformation texture was only 4x4 pixels... weird. It also does not have any feature that you would expect from a deformation map. Seeing how the heat effect glitches, this deformation texture being wrong looks like a good candidate for the problem. Let's see how it's loaded!

By NOPing random calls to GXSetTevIndirect, we find a call that when removed breaks the effect completely. The parameters used for this call come from the results of methods of JPAExTexShapeArc objects. 3 different objects go through this code path, by breaking each one we can notice that the one "controlling" the heat effect is the one at 0x81575b98.

Hard at work

Following the path of this object a bit more, we can see that it has a method called "getIndTexId". When this is called, the returned texture ID is used to index a map and get a JPATextureArc object stored at 0x81577bec.

Nice feature of JPATextureArc: they have a getName method. For this object, it returns "AK_kagerouInd01". We can probably use that to see how this texture should look like, by loading it "manually" from the Wind Waker DVD. Unfortunately I don't know how to do that. Fortunately [@Abahbob]( "@abahbob") got me the texture I wanted in less than 10min after I asked him on Twitter. AK_kagerouInd01 is a 32x32 texture that really looks like a deformation map: . Fun fact: "kagerou" means "heat haze" in JP.

So apparently we're not using the right texture object when rendering! The GXTexObj that maps to the JPATextureArc is at offset 0x81577bf0 and points to data at 0x80ed0460, but we're loading texture data from 0x0039d860 instead.

I started to suspect the BP write that loads the texture parameters "did not work" somehow. Logged that and yes: nothing gets loaded to texture stage 1! ... but it turns out this is normal, the deformation map is loaded to texture stage 5 (hardcoded in the DOL). Wait, why is the TextureCache trying to load from texture stage 1 then?!

Because someone sucked at hex.

Broken Code

436     u32 getTexCoord(int i) { return (hex>>(6*i+3))&3; }
437     u32 getTexMap(int i) { return (hex>>(6*i))&3; }

Working Code

436     u32 getTexCoord(int i) { return (hex>>(6*i+3))&7; }
437     u32 getTexMap(int i) { return (hex>>(6*i))&7; }

Oh yeah, this also fixes videos in some EA games.

Você pode continuar a discussão no tópico do fórum deste artigo.

Próximo post

Post anterior

Posts similares