Dirty Game Development TricksBy Game Developer Magazine Staff
In this article from the final issue (June/July) of Game Developer magazine, game developers share their hacks, tricks, and war stories about getting the game out the door however they had to. (The complete issue is available as a free download here.)
The article is being highlighted as one of Gamasutra's best stories of 2013.
Crouching RSX, hidden texture assets
Joe Valenzuela, Insomniac Games
This trick was on the PS3: We on the Insomniac engine team had some textures that we wanted distributed with our engine/tools release. These were things like noise textures and source input for full-screen filter effects. For some unimportant reasons, we didn't want to distribute these as actual asset files, so instead we converted them to binary arrays and compiled them into the executable. There was one downside, though -- we wanted these to be in a different chunk of memory (RSX visible), so we would end up copying them out and just wasting the memory for the source.
The PS3 toolchain had a link feature to put certain sections in RSX memory, but it requires using 1MB pages, and in our case it would have wasted 700k. Instead, we added a new data section in the executable that aliased the bss (the "bss alias" or "balias" section). We had something like 3MB of bss in our final builds, so there was more than enough space to hide some texture assets. We ran some code as early as we could in the crt initialization to initialize the destination memory, copy the assets out, and then re-initialize the bss to 0.
Believe it or not, it worked! There was a little tweaking to accommodate hidden bss use and toolchain updates, but overall it was pretty straightforward.
This is not the bug you're looking for
Brett Douville, LucasArts
In early 2002, we were readying Star Wars: Jedi Starfighter for submission to Sony. One niggling TCR bug remained, which was that the controller's analog stick functionality would shut off while we were loading our post-mission cutscenes, causing the red light in the center of the controller to go off as well. This bug had shown up when we updated to a library version required by Sony, and the programmer who had originally written both the movie-loading code and the IOP logic for the controller itself had left LucasArts some months before.
In the hopes of narrowing down the problem quickly in code I didn't understand, I inserted seven screen clears in different colors in the code I determined to be the likely source of the problem, hoping that I could at least narrow it down to one or two sections of code by checking what color the screen was when the analog controls turned off. But when I then tried to reproduce the bug, it had disappeared.
It's an old programming adage that if you don't understand the cause, you can't be said to have fixed the bug. But in this case, we were two or three days away from our intended submission date and missing it would have been a big deal. So I changed all the screen clears to black, marked the bug fixed, and called it a day. We shipped on time, with no TCR showstoppers.
Jonathan Garrett, Insomniac Games
Ratchet and Clank: Up Your Arsenal was an online title that shipped without the ability to patch either code or data. Which was unfortunate.
The game downloads and displays an End User License Agreement each time it's launched. This is an ascii string stored in a static buffer. This buffer is filled from the server without checking that the size is within the buffer's capacity.
We exploited this fact to cause the EULA download to overflow the static buffer far enough to also overwrite a known global variable. This variable happened to be the function callback handler for a specific network packet. Once this handler was installed, we could send the network packet to cause a jump to the address in the overwritten global. The address was a pointer to some payload code that was stored earlier in the EULA data.
Valuable data existed between the real end of the EULA buffer and the overwritten global, so the first job of the payload code was to restore this trashed data. Once that was done things were back to normal and the actual patching work could be done.
One complication is that the EULA text is copied with strcpy. And strcpy ends when it finds a 0 byte (which is usually the end of the string). Our string contained code which often contains 0 bytes. So we mutated the compiled code such that it contained no zero bytes and had a carefully crafted piece of bootstrap asm to un-mutate it.
By the end, the hack looked like this:
1. Send oversized EULA
2. Overflow EULA buffer, miscellaneous data, callback handler pointer
3. Send packet to trigger handler
4. Game jumps to bootstrap code pointed to by handler
5. Bootstrap decodes payload data
6. Payload downloads and restores stomped miscellaneous data
7. Patch executes
Takeaways: Include patching code in your shipped game, and don't use unbounded strcpy.
Jamming the cartridge
Michael A. Carr-Robb-John, Monolith Productions
In 1993 I was finishing off Desert Strike; I was doing the conversion from the 16-bit Mega Drive / Genesis to its humble little brother, the 8-bit Master System. The game was exceeding the desired cartridge size by about 12K, and going to the next size cartridge was out of the question. Today, 12K sounds incredibly small, but back then it was a major deal. During development, I had budgeted and planned all the sound and graphic resources and they were within their limits. The only section I hadn't been so strict on was the code. In those days, games were written in assembly language -- in this specific case, Z80 assembly -- so I had only one option left. I spent a week going through and finding redundant code and rewriting things to use a smaller memory footprint (usually at the cost of being more processor-intensive.)
By the time I had finished, the game fit onto a cartridge with just 98 bytes! The game was burned onto ROM and tested for a few days by the chaps in QA before being submitted to Sega for certification. Unfortunately it didn't pass on the first time, and the required fixes pretty quickly used up those 98 bytes. I think when we did publish, there were only 6 bytes free!
The Dalton Allocator
Jonathan Adamczewski, Insomniac Games
Toward the end of one project, we discovered that after playing through the game for many hours, movies would not trigger when they were supposed to. Fragmentation within one of our memory allocation heaps had made it impossible to reliably allocate the large blocks of memory that were needed for full-screen movie playback.
We needed to find a way to be sure that we could always get the memory we needed. However, it was too late in the project and too risky to consider defragmentation of the heap, we didn't have enough spare memory available to set aside exclusively for movie playback, and taking space away from other systems at that stage was impractical.
It was clear that when movies were playing, there were many other systems sitting idle, so we gave some consideration to "borrowing" memory from them. However, these other potential sources were either too small, or else also suffered from the same kind of fragmentation problems that we were already seeing. There was plenty of free space available for the GPU, but for various reasons we could not use that for the movie-playback buffers. So we needed another source of space.
While looking into another problem, a particular pattern of memory allocations within our main game-asset heap stood out: When the game started up, a number of assets were loaded from disk, and once in memory were never modified. Some of those assets were very large. None of them were needed while a movie was playing.
This gave us an idea: What if we were to copy the contents of one of the larger asset files to somewhere else in memory? The GPU's memory would work just fine as a place to temporarily stash the asset, and then we could temporarily reuse the space in the asset heap for movie playback, copying the asset data back from GPU memory when the movie finished.
So that's what we did. We chose the largest set of animation clips used for one of the game's heroes (named Dalton). The animation system was switched off, animation clips copied into the GPU's memory space, and the memory handed to the movie-playback system. At the end of the movie, the animation data was copied back to where it belonged, the animation system restarted, and the game proceeded as if nothing horrible had happened. The implementation ended up being fairly straightforward, though the process does span a number of frames to ensure that all the systems involved are safely synchronized with each other through each step of the process.
The memory for the movie system has since been described as coming from "the Dalton Allocator" after the character whose animation clip memory was usurped.
(I've since discovered that there's some history of this kind of thing at the studio -- of both stashing random things in GPU memory, and failing to set aside enough space earlier in the project...)
Michael Carr-Robb-John, Monolith Productions
Anyone that has ever written a game on a console knows about the certification headache. For the most part, certification is simply common sense and good practices, but there are one or two "requirements" that just seem to be nothing more than a way to cause developers problems. One such requirement that we've had to deal with is that from the time the user chooses to run your game, you must be displaying the first presentation screen within four seconds. If you have a large executable, it can take at least two or three seconds just for it to load before you get control, and you still have to load a whole bunch of visuals and sounds in order to present the main menu.
My game was taking 26 seconds from the time the user selected the game to the time it displayed the first presentation screen, so I could already feel that headache starting. My first job was to isolate what was required to display the menus, and put off loading specific global data until it was actually needed. Surprisingly, this had a bigger impact than I was expecting; it knocked off over nine seconds. Many tweaks later, I had managed to knock it down to around 10 seconds, but I just couldn't get it to load any faster. While discussing the problem with another engineer (best problem-solving method I have ever found), the solution dawned on us.
The specific console also has another requirement that two specific screens are displayed by your game before progressing into the menu system. What is also of interest is that the user must be prevented from skipping the screens for a set amount of time. Let's see now -- if we loaded only those two screens, and did most of the menu loading while the player is watching those screens... voila -- 5.5 seconds to load and display.
Unfortunately, that still was not enough to satisfy the letter of the requirement, so we eventually had to ask for a waiver, which was granted (probably because we were so close).
Edward J. Douglas, Flying Helmet Games
I was running cinematics on a long-running racing series. Our scenes were a mix of straightforward startgrids and fancier action sequences. As we iterated on further sequels, our ambitions with the scenes got greater and greater, but our technology iterated at a slower pace.
You see, the cars would be animated by a QA "stunt driver" to get the base motion, then hacked up in a 3D animation program to adjust the timing and placement of the action, and re-exported to our in-game cinematic tool where playback would simulate all the physics and engine behavior. Lots of gameplay data was captured, including gas and brake information from the controller and represented in metadata in the 3D file, then reproduced in-game. The idea was that this would drive the car audio system as well.
The problem came a few sequels down, where our scenes were very complicated, with a mix of hand-animated car action and recorded capture. The old tricks of using the engine metadata to drive simple audio rev samples for our cars wouldn't work anymore -- the data just wasn't there! The audio team couldn't post-process the audio like a movie, because any scene could have any car in it, depending on player's choice and modifications, so the audio needed to be procedural. But by this time, our games had won numerous awards for audio, especially for the car engine sounds, so we were determined to make it work.
Coming on to beta, things were looking dicey, but a combination of ingenuity and madness between our cinematics, audio, and AI engineering teams found the solution. The gas and brake metadata was represented by a float scale on a cube in the 3D scene. If an artist went in and "drew" curves in a keyframe editor like 3DS Max, they could draw in the car engine sounds they wanted. A few members of the audio team rushed to learn 3DS Max, and by using their intuition of how rev patterns should look, they drew in the animation, exported all the scenes, and squeezed it all in in time. It sounded great, but after this last-minute hack, we knew we'd need something more robust if we'd continue with the same tech-base for the next sequel.
...Or so I thought. I left the studio after that game, and a few years later I met an audio guy who had joined that team after I left. It wasn't long before I realized they never upgraded the tech, and he was the "engine rev painter" guy for the latest sequel.
...And one for good luck
I had a list of background textures, and I coded the game to show each one as they scrolled across the screen. One of my background images was being skipped and I couldn't figure out why, even after spending a lot of time debugging. Deadline was five days away, so I just stuck an extra reference to the texture in the background list. Ta-da! No more "skipped" background. :)
When I used to work for a very big company, one of the employees figured out that the best way to advance his career was to write negative performance reviews for as many co-workers as possible. This resulted in him receiving a higher annual staff ranking, which in turn led to larger bonuses and stock grants. It eventually becomes difficult, he told me, because you need to make sure to only review people with different managers, so nobody can catch on to your ruse. My trick for avoiding this cycle was to quit and go work someplace much smaller and awesome.
Honorable mention: Nice save
Chris Pruett, Robot Invader
[Editor's note: This isn't, strictly speaking, a dirty game dev trick -- but we figured it's a handy way to use job skills for real-world problems. Also, it's a sweet story.]
My wife doesn't play a lot of video games, but one series she's been hooked on since childhood is Dragon Quest. A few years ago, she started playing Dragon Quest VII on my aging PlayStation. After putting about 80 hours into it (which, as I understand it, is about three-quarters of the way through the main quest), she discovered, to her horror, that her save file had become corrupted. It appeared in the continue menu, but was grayed-out and could not be selected. She was devastated. She was angry. She swore never to play games again.
I found a used DexDrive, a device for reading and writing PS1 memory cards with a PC, on eBay for $15. I didn't tell my wife that I was trying to fix her save -- I didn't want to get her hopes up and I didn't actually think it would be possible. Presumably the data was irrecoverably damaged, a lost cause. On the other hand, I thought, it couldn't hurt to try.
With the DexDrive, I was able to dump the broken save to a PC and examine it in a hex editor. I ended up printing it out and marking the hex up with a highlighter; though PS1 saves came in 8kb blocks, printing 8kb as 16-columns of hexadecimal data comes out to a lot of pages. Working with an unofficial spec written by the author of a PS1 emulator, I located the main chucks of the data: the header, the icon image, and lastly, the save data itself. Unfortunately, decoding the raw game state data proved challenging; after a few days I decided that it was going to require a lot more work than I had planned.
Instead, I concentrated on the header portion of the data. Because of the placement of the icon (which is at a consistent offset from the top of the file and easy to identify as pixel data in hex), I could tell exactly where the header started and stopped. If the continue menu could tell that the save was busted, maybe it was just the header that was broken. I tested this theory by copying the header data from some other save I got off the internet, and pasting it over the header section of my wife's broken save. Then I saved this data back to the memory card and loaded it up.
Miraculously, it worked. The continue menu showed stats from the other save, but once loaded her game was completely restored. Between ordering the DexDrive and patching the save, the whole process had taken me about three weeks. That night I booted the game up and showed her the continue menu with the strangely named save. She loaded it up and was very surprised to find her progress, her characters, her stats, and her items all as she left them. She was pretty excited, but before we could talk about it she was off to complete another dungeon.
Return to the full version of this article
Copyright © UBM Tech, All rights reserved