Reburn 3: Development Blog

A place to discuss reverse engineering and modding Burnout 3: Takedown.
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Reburn 3: Development Blog

Post by MattKC »

I wanted a place to gradually document my thoughts and progress without necessarily having enough for its own thread. So here's a development blog! Actually just a forum thread, so I expect it to get thoroughly derailed at some point Clueless

Humble Beginnings (2023-01-12)

As I mentioned in The Game Plan, I'm staying laser focused on building on top of Cxbx and doing whatever I can to get the graphics working properly. Last year, when I first started thinking about this, I actually spoke to a member of the Cxbx development team who had mentioned on Twitter that Burnout 3 was a "lost cause", at least for the foreseeable future. I asked why, and he explained some of the biggest problems facing the game, and Cxbx as a whole.

The Xbox's variant of D3D8 has a unique feature called push buffers. Rather than making a ton of D3D calls that go back and forth between the GPU and CPU, you can instead record all of those actions into a push buffer and send it to the GPU in one go. Theoretically, this cuts out a lot of overhead, especially because push buffers are generated and stored in the NV2A's (Xbox's GPU) native instruction format. Push buffers can be generated at both runtime and compile time.

The problem is, push buffers don't exist on Windows. They can't really exist in the same capacity because, unlike the Xbox, you don't know what GPU any given Windows user is going to have, so you can't generate native instructions for it. If a game uses push buffers for NV2A GPUs, there's not a whole lot of relevance that will have to the average Windows PC.

Okay, well if Cxbx is already injecting a bunch of code to replace Xbox functions, can't you replace the push buffer functions too (at least for the stuff generated at runtime)?

Well, that leads to another big roadblock for Cxbx: the XDK used aggressive optimization, especially for later Xbox games like B3. I guess MS really wanted to squeeze as much performance out of the Xbox as possible, because a ton of Xbox APIs get forcibly inlined into each game's code, including many of the push buffer functions. A lot of later games also use LTCG (link time code generation), where API code linked into the game is even further altered to improve performance.

Both of these make it almost impossible to automatically identify and replace those functions, since the code for them may take on different forms, not just between each game, but between each time the function is used in each game. This is what I was alluding to in The Game Plan about how Cxbx has unique challenges trying to wrap every game in the Xbox library.

The only real way to solve this for every game would be to add a push buffer interpreter - something that interprets NV2A instructions and reproduces the intended results on another platform. This would also be known as an Xbox GPU emulator, which doesn't really fit in with an injection-only/HLE approach.

Since I'm focusing solely on one single game, I'm not bound by trying to find automated ways to do this. Instead of working towards emulating the Xbox's GPU, my plan is instead to rewrite/reinject pieces of the B3 code so that it hopefully no longer uses push buffers at all. Again, I consider this a PC port, not emulation, so if I have the option of reimplementing parts of the Xbox vs shifting away from it, I'll pick the latter where possible. I also think that's going to be the faster approach.

So far I've been making good progress finding the functions responsible for drawing the 3D scenery, and I think I've even found some of the push buffer related functions. It can be hard to tell what code is B3's and what code is D3D8's because they're so aggressively inlined, but I've been able to match some code patterns to Xbox D3D8 code, so I think I'm on the right track.

I'll update this thread as I continue to make progress. Larger discoveries (those that can be neatly contained in one post) will continue to have their own threads, but more cas' posts like this will be put into here.

And I also have to talk about my secret weapon(s) at some point...
User avatar
CrabHead
Posts: 199
Joined: Tue Dec 27, 2022 11:10 pm
Location: Poland
Contact:

Re: Development Blog

Post by CrabHead »

And I also have to talk about my secret weapon(s) at some point...
"The first secret weapon i have is a ICBM Minuteman III, a ballistic missile with a range of 5,500 kilometers. You may have a lot of question right now and i'm not surprised. But i got it from the game you all know and love, LEGO Island. When i was looking for the solution of the turning speed, i noticed a strange string, leading to some website, along with some random numbers that didnt make sense at that time. I went to the website and it's apperantly a control station where you can launch, disarm, set coordinates to the ICBM missiles. With this fascinating discovery, i'm planning to threaten EA to give me their entire Burnout 3 repo (along with other burnout games because why not?). If they don't do it, well, they'll unleash a nuclear war, where NO ONE, will have the source code."

Besides that joke, could the burnout 3 code be used for other burnout games such as Revenge?
KEKW Goin' Flipmode!
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Re: Development Blog

Post by MattKC »

CrabHead wrote: Fri Jan 13, 2023 7:18 pm Besides that joke, could the burnout 3 code be used for other burnout games such as Revenge?
I've never looked at Revenge's code, but I assume it's just a further progression of B3 and therefore the discoveries we make here should translate over.

I'm also curious about B2. It's not as good as B3 IMO, but would still be cool to have a PC port for. I have no idea what the code is like for it either though.
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Re: Development Blog

Post by MattKC »

A Burning Question
Does Burnout 3 actually use push buffers?

That's what I was told, and I had no reason to doubt it, but I've been looking at this code for over a week now and the results are... inconclusive.

I have found references to D3D's internal push buffer pointer (from here on referred to as "m_Pusher"), and a couple references to the functions BeginPush and EndPush, and at first this seemed to confirm what I'd been told - the game code was definitely using push buffers. But as I looked into them more, I noticed something kind of odd.

Firstly, you have to know that D3D on the Xbox doesn't just use push buffers when you ask it to. Microsoft must have really believed in this whole pushing and buffering concept, because the XDK seems to use them everywhere.

Using a leaked copy of the XDK source code, you can see that all kinds of functions write to m_Pusher, and many also call BeginPush and EndPush. It seems that, even when you're not explicitly using push buffers, D3D is still using them internally to send commands to the NV2A.

You also have to know that m_Pusher is not directly available from the D3D API. As the m_ prefix suggests, it's an internal/private variable, making it virtually impossible that Criterion could be accessing it directly on purpose. This means any reference to m_Pusher in Burnout 3's code must be an inlined function from D3D.

Obviously using push buffers explicitly (and reusing them as much as possible) is still going to provide a performance benefit over D3D's internal usage, so the burning question is... is B3 actually doing that? Or can all of the references to m_Pusher, BeginPush, and EndPush be explained by inlined D3D functions?

Well, if you do want to explicitly use push buffers, there are publicly accessible API functions to do so like CreatePushBuffer, BeginPushBuffer, EndPushBuffer, and RunPushBuffer.

...None of these are ever explicitly referenced in Burnout 3's code.

Okay, that does seem a little damning, but remember that if any of those got inlined, they'll be impossible to automatically identify, so this isn't necessarily conclusive.

But I did think it was worth investigating, and looking through B3's references and comparing to XDK code, I was indeed able to start matching up the code in B3 to functions in D3D. The more I did it, the more it seemed like this theory might actually hold - as I gradually picked apart the m_Pusher references and decoded the NV2A instructions being written to it, I found that each one could be explained by a standard function in the D3D API.

It really started to fit, people thought B3 used push buffers because they were mentioned in the code, but those references could just as easily be inlined functions from D3D itself. And Cxbx wouldn't be able to automatically identify and patch those inlined functions, so this code would be stuck trying to write to a push buffer that didn't exist. This could explain the lack of 3D graphics, and would theoretically be as simple to solve as just rewriting this inlined code into function calls that Cxbx would know how to replace.

It felt like I finally had an answer. Not just one that made total sense and explained why Cxbx couldn't automatically patch it, but also one with a fairly straightforward solution. One that would take some time and effort, but wouldn't require a lot of puzzle solving or reverse engineering.

...But then just to experiment, I nopped out all of the code I was looking at - every mention to BeginPush, EndPush, and m_Pusher - tried it on a real Xbox, and... the game displayed fine.

So... huh... none of that inlined code was actually important at all? Nothing I was looking at was responsible for any of the 3D graphics?

It was an extremely promising lead, everything seemed to fit perfectly, but it looks like it was too good to be true. The whole time I was barking up the wrong tree...
User avatar
CrabHead
Posts: 199
Joined: Tue Dec 27, 2022 11:10 pm
Location: Poland
Contact:

Re: Development Blog

Post by CrabHead »

the story has just begun and we have this gigantic plot twist
KEKW Goin' Flipmode!
User avatar
DrScoop
Posts: 7
Joined: Tue Mar 28, 2023 1:04 am

Re: Development Blog

Post by DrScoop »

Did the game run any better than compared to before after removing all the pushes?
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Re: Development Blog

Post by MattKC »

On the Xbox? Nothing appeared to change performance-wise, I assume it's capped at 60 either way. Though it may be a good test for something like xemu where I still can't get above maybe 30 FPS.
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Re: Development Blog

Post by MattKC »

The Mistake


There comes a time where everyone must admit they made a mistake. This is one of those times...

In the last update, I'd concluded that the now-infamous Xbox push buffers did not play a significant role in Cxbx's failure to play Burnout 3. This was based on a handful of observations, the first being that the 3D geometry appeared to primarily be rendered using a standard D3D function called "DrawIndexedVertices". While the docs suggest that any function can be recorded into a push buffer (by running them between calls to BeginPushBuffer and EndPushBuffer), this didn't seem to be what the game was doing.

The second was that I had nopped all the push buffer code - as far as I could tell - tested it on original hardware, and the 3D still seemed to render fine. This felt like conclusive evidence that the push buffers must not be used for anything important, if anything at all.


garageno.png
Current appearance of the garage/car selector in Cxbx


At this point, thinking I'd have to come up with a new lead, I took a small break from the project for a few months. When I came back, I decided I should investigate more about how Cxbx draws vertices, and consult a friend much more knowledgeable about these things to help. But before I did that, I thought I'd better do my "push buffer nop" test again so I can more precisely document my findings, and make sure my evidence was as conclusive as I thought.

What I discovered was that it was actually anything but...


What happened?

Since I took that two-month-long break, I can't remember exactly what I did or what function(s) I nopped, but I can take a guess at what happened. Xbox executables are statically linked, which means all of the Xbox APIs (including Direct3D, DirectSound, DirectInput, etc.) that the game uses are actually baked into the game itself. That means when searching for D3D8 related stuff, like m_Pusher, you have to ignore the D3D8 library code that's been statically linked in, because it's not really relevant to investigating the game's own usage of it.

However, I think I misidentified a lot of functions as not belonging to the game when they actually did, and I was probably too quick to dismiss whatever I did nop as having "no effect". While you could argue that if I couldn't see a difference, it wasn't important enough to worry about, it's probably better to know as much of what's happening as possible.


Okay, so what's really happening?

While my final conclusion was wrong, my other findings do still seem to be correct. There are indeed push buffers being used in the game code, but there's a lot of evidence to suggest the game isn't trying to use them; they're just here due to the XDK's aggressive code inlining. Since Xbox executables are statically linked, this opens the door wide open to inlining and link-time code generation (LTCG). Both of these significantly reduce the overhead of an application calling into other functions by embedding (aka "inlining") the called functions into the caller. While this is definitely beneficial from a performance standpoint, it makes the kind of automated patching/injection that Cxbx tries to do an absolute nightmare. Xbox stuff that ought to get replaced (e.g. push buffers) becomes ingrained into the game code, generally requiring manual intervention (essentially what we're doing) to resolve.

When you think about it, it's definitely plausible that they wouldn't explicitly use push buffers. I've heard B3's primary development platform was the PS2, since that was the harder one to get 60 FPS out of, and as such it's unlikely (though not impossible) that the game contains a lot of Xbox-specific optimizations.

When I redid my nop test, I found at least 8 functions that use push buffer code that are definitely part of the game itself. For reference (mine as much as yours), the functions are at the following addresses:

Code: Select all

0x32020
0x367c0
0x3ba10
0x3bfb0
0x3da90
0x3e520
0x3fee0
0x44170
So I went through again nopping them one by one to see the effects each one had on the game. The results were actually very interesting...


Society if there were no push buffers

3e520.480p.mp4
Disabling 0x3e520

For the most part, what I've found is that push buffers are used for a lot of post-processing effects like bloom and motion blur. Check out that lighting artifact that appeared in the top left corner in the video above.

At first I wasn't sure where it came from, but then realized it's lighting from the garage, which calls the same function too (I didn't nop it there):

garagelight.jpg

Presumably if I'd left the garage at a different time, the artifact would be completely different (or perhaps not visible at all if there was no bloom in the scene). This is why I say I was likely too quick to dismiss my original tests as having no effect; some of these are so subtle it would be easy to assume nothing had changed.

After nopping a bunch of the functions and noting what they did, I eventually stumbled upon this one:

3da90.480p.mp4
Disabling 0x3da90

For anyone familiar with how Burnout 3 plays in Cxbx, this will look immediately familiar. Yep, I think we've found our culprit! While it may not be the only thing breaking 3D, it's definitely causing the most immediate problems, and is therefore our prime suspect.

I haven't yet analyzed this function enough to know exactly what it's doing yet, but as you might expect it contains a ton of inlined push buffer code. This is where my theory that these are actually inlined D3D functions rather than "handwritten" push buffer calls becomes important. If the game was intentionally using these, I'd have to figure out what it's doing and rewrite new code by hand to accomplish the same goals. However since I'm pretty sure they're just D3D functions, all I should actually have to do is identify which one is which, and swap them out with their non-inlined equivalent. At that point, Cxbx/Windows should be able to handle that stuff itself.

That's certainly the next thing on the agenda, however while testing, I realized something I didn't expect. Disabling this function may have broken the 3D in-game, it did not break the 3D in the garage...



Dude, where's my car? Oh, it's right there.

If you don't know, the garage in Burnout 3 is where you select the car you're going to drive whatever particular race you chose with. It appears in the menus as some 2D images over a 3D background where the camera circles around a car in a parking garage environment. As you can see in the screenshot at the very top of the article, the 3D is completely broken in Cxbx. However, despite now replicating with Cxbx did in-game, the garage was still working on hardware.

...Huh, so something else is breaking the garage?

While I was testing each push buffer function, there was one I couldn't quite nop on hardware. For some reason, the game would just freeze when I tried. I assumed I just nopped it wrong (it can be hard to keep track of the stack when doing static analysis) so at first I didn't think much of it, but I later realized it was freezing right when the garage was about to appear.

Interesting, so it sounds like this function is definitely relevant...

Only three of the aforementioned functions are used in the garage: one that handles that lighting/bloom effect, one whose changes were too subtle for me to notice (maybe motion blur?), and this third one that had frozen as soon as I tried entering the garage on hardware. I decided to zero in on that third one and see what I could get out of it.

I was still assuming I just nopped it wrong. Since I could use a real debugger with Cxbx, I decided to nop the function (and all associated stack instructions) there instead. Once I had something I knew worked, I could port that patch to the executable and run it on hardware to see what it actually did.

Instead, something completely different happened...

nopped 3fee0 + reflections.mp4
HOLY SHIT

Suddenly all the 3D appeared! The cars, the garage, the spinning camera, it's all here!

Well, by "all here", I mean the textures are janky as hell, all those push buffer-based effects definitely aren't working, and it only works in the garage; in-game still looks the same as before. But the fact that it's actually rendering something is more than I've ever seen the game do on PC!

(Note: the cars themselves and their reflections actually seem pretty spot on. at least from a glance. It's only the environment textures specifically that seem to be corrupted. I realize the globe texture in the menus is also corrupted, I wonder if that's the same texture issue as the garage environment...)

Okay, so this is actually really interesting. I still don't even know if I just nopped it wrong on hardware or it happens to freeze for some other reason; once again, I haven't investigated the function closely enough to know exactly what it's doing, but my curiosity is absolutely piqued now. I'll have to try it properly.

Once again, this means there are definitely push buffers being used in critical parts of the pipeline here, but to my surprise, removing them (at least this one in the garage) actually makes it work better.

Obviously, my plan is to eventually port even the subtle push buffer code faithfully to PC (I'm not going to just rip them out and say it's good enough), but just seeing this stuff kinda sorta work answers and clarifies so many of my questions. For one thing, the vertices themselves indeed do not require push buffers as I suspected; I mean if it did, it'd be impossible for something to be rendering right now without much more significant rewrites. That call to DrawIndexedVertices was not a red herring after all. It's quite likely in-game works very similarly, just with a few more push buffer things in the pipeline getting in the way. If I can get those to work (or at least untangle them to see if I can get something displaying at all), the game should be theoretically fully playable, if a little graphically garbled.

...Or at least that's my guess.

Regardless, this feels like a ton of really good progress. Not just in the sense of having something draw where it didn't before, but also in how much this has confirmed or disproven. I feel like before I was largely stabbing in the dark, but now I know exactly what code I need to investigate next. I can't wait until the 3D works ingame. Even if the environment textures are still garbled, it will still be Burnout 3 running more or less natively on PC.

We'll see how far I get by the next update!
User avatar
DrScoop
Posts: 7
Joined: Tue Mar 28, 2023 1:04 am

Re: Development Blog

Post by DrScoop »

By natively on PC, does that mean that the game will still only be running on D3D8? Sorry if this is a dumb question.
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Re: Development Blog

Post by MattKC »

DrScoop wrote: Sat Apr 08, 2023 10:38 pm By natively on PC, does that mean that the game will still only be running on D3D8? Sorry if this is a dumb question.
For the time being, yes. Much of this is facillitated by simply connecting the game's Xbox D3D8 calls to equivalent functions in Windows' D3D8 and D3D9.

Of course, a project like this will probably lead to graphical enhancement mods that go beyond D3D8/9. Long-term, I'd certainly like for the game to not be entirely dependent on Windows (especially as a predominantly Linux user). This would at least mean making the graphics compatible with OpenGL or Vulkan too (possibly by swapping out the game's RenderWare code with librw). But we'll see how it goes, right now all I'm focusing on is getting a viable PC port.
User avatar
DrScoop
Posts: 7
Joined: Tue Mar 28, 2023 1:04 am

Re: Development Blog

Post by DrScoop »

I hope asking these questions here isn't flooding up the thread with non-updates too much, but how exactly is it using both D3D8 AND D3D9? This'll probably be my last Q before your next update. Thanks for responding tho!
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Re: Development Blog

Post by MattKC »

DrScoop wrote: Sun Apr 09, 2023 8:40 am I hope asking these questions here isn't flooding up the thread with non-updates too much, but how exactly is it using both D3D8 AND D3D9? This'll probably be my last Q before your next update. Thanks for responding tho!
Nah not at all! Part of the reason this is on a forum is so people can discuss and ask questions.

I think strictly it's using D3D9. The Xbox implemented D3D8, but with some extensions that weren't in the Windows version until 9. But a lot of D3D8 functionality is still present as-is in 9 (it wasn't a complete rework like 7->8 or 11->12 were), so it gets kinda murky. That's why I say it's kind of both D3D8/9.
User avatar
CrabHead
Posts: 199
Joined: Tue Dec 27, 2022 11:10 pm
Location: Poland
Contact:

Re: Development Blog

Post by CrabHead »

So what would be needed to load a race? Garage works, and that is considered a map by game's standards.

As a start you could use that cut c4_v1 test track from the june proto since it doesnt have many elements
KEKW Goin' Flipmode!
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Re: Development Blog

Post by MattKC »

I couldn't exactly say yet, but I have a solid lead. Primarily that push buffer function I disabled that led to identical behavior as Cxbx on hardware.

It seems the garage was technically drawing fine the whole time, there was just a push buffer-heavy function (whose purpose I haven't determined yet) somehow getting in the way. My theory is it's the same in the races - the 3D geometry is in there, but they use even more push buffer-heavy post-processing effects that are causing further problems. Unfortunately it doesn't seem to be as simple as just disabling that function (I already tried), so some more analysis is required.

Honestly the fact that removing code worked at all in the garage was definitely a fluke, and I don't intend for that to be a permanent solution (eventually I'll port them properly to use non-push buffer code). But if we at least have a playable game, even if the textures are glitchy like the garage is right now, that's still a playable game and a good base to build on top of.

I finally managed to set up a debug Xbox so it should be a lot easier and quicker for me to test on hardware from now on. So far all I've been doing is patching XBEs, which means testing was slow and I couldn't make any changes at runtime, but now I should be able to get a lot more information.
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Re: Development Blog

Post by MattKC »

hahahahaha no fucking way
193400.mp4
User avatar
CrabHead
Posts: 199
Joined: Tue Dec 27, 2022 11:10 pm
Location: Poland
Contact:

Re: Development Blog

Post by CrabHead »

HOLY SHIT!

This is interesting, the road textures render correctly but the buildings partially aren't? Particles don't seem to work and the skybox is darker... Weird shenanigans.
Anyway really great work
KEKW Goin' Flipmode!
User avatar
DrScoop
Posts: 7
Joined: Tue Mar 28, 2023 1:04 am

Re: Development Blog

Post by DrScoop »

How about the cars? It looks like its the "reflections" on them are the only parts that are miscolored. There must be a push buffer responsible for making the fake reflections on them
User avatar
DrScoop
Posts: 7
Joined: Tue Mar 28, 2023 1:04 am

Re: Development Blog

Post by DrScoop »

So, I noticed that a lot of the reflections and metallic objects are rendering where the rainbow effects are at currently. Could it be possible that the normal and specular maps of the objects are delegated to a push buffer?
User avatar
MattKC
Site Admin
Posts: 323
Joined: Mon Aug 22, 2022 1:05 am
Contact:

Re: Development Blog

Post by MattKC »

CrabHead wrote: Tue Apr 11, 2023 12:12 pm This is interesting, the road textures render correctly but the buildings partially aren't? Particles don't seem to work and the skybox is darker... Weird shenanigans.
Yeah it's trippy to look at. I suspected there would be some texture corruption based on how the garage looks, but it's fascinating to actually see it.
DrScoop wrote: Tue Apr 11, 2023 1:43 pm So, I noticed that a lot of the reflections and metallic objects are rendering where the rainbow effects are at currently. Could it be possible that the normal and specular maps of the objects are delegated to a push buffer?
That's an interesting theory. I definitely think it's something along those lines; the textures are actually probably fine underneath (similar to the geometry), they're just getting run through some lighting effects or something that happen to be push buffer-based and are hence failing. Same with the particles and other missing things.

Here's an interesting one, check out how the road changes in the tunnels (where there's probably more complex lighting/shadows):

mpv-shot0002.png
Yooo they put Rainbow Road in Burnout 3

I think I might start working on my custom launcher (which will patch the game on the fly before running it through Cxbx) so people can start playing with it themselves. For now, if you're interested in seeing more, I've recorded a full playthrough of the first track:


I have to say, apart from the corrupted graphics, it plays beautifully. The biggest roadblock is that, since getting this working, I've discovered a few crashes (probably because no one has ever gotten this far with this game before in Cxbx). I've resolved all the ones I've found except for one, which occurs - ironically - whenever you crash your car in-game (hence why I'm being a little more careful with the boost in that video above than I otherwise would be). Still, great progress, and if it weren't for that crash, I think the game would technically be playable from start to finish in its current state.
User avatar
TheGoat62
Posts: 16
Joined: Thu Jan 26, 2023 4:38 am

Re: Development Blog

Post by TheGoat62 »

This is amazing! I also noticed that the fog and lens flare are also absent which could connect to the reason why the environment is darker because it has something to do with the push buffer. Also why is the game overscanning? (the motion blur effect being cut off as well the takedown screen)
Post Reply