Development Blog

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

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: 99
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?
My god, Fisher! That was pure madness. I don't know what to say. The mission's over get out of there you're finished. ReallyMad
User avatar
MattKC
Site Admin
Posts: 161
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: 161
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: 99
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
My god, Fisher! That was pure madness. I don't know what to say. The mission's over get out of there you're finished. ReallyMad
Post Reply