Programming in assembly for a Commodore 64 wasn’t ever on my bucket list, or even my Trello list. But I got drawn into it anway by reading ‘Programming the Atari ST 20 years later’ which made assembly and old computers sound fun. But after beating up my brain trying to follow the Atari ST bitplane-based graphics, I
wanted needed something simpler. Enter the c64! I could go on about the specs, the thousands of games, the active demoscene, this amazingly great tutorial site, or that the entire programmers guide is a single txt file. People much better at writing and explaining have already talked forever about all of those things. Instead I’ll just write about how to implement smooth scrolling on it, because who doesn’t want that?
I’m going to go ahead and assume you know (or can figure out) 6502 assembly, buffering, interrupts and raster timings. In any case, heres a incredibly brief description of the graphics configuration: I’m working in character mode, so the screen area is 40x25 characters, and the RAM for the screen data is a relocatable block of 1000 bytes. In addition, there is a fixed 1000 bytes of color RAM to provide a color for each character. There are many different graphics configurations, but in this simple case, we’re assuming character-mode, single color-per-character graphics. The VIC-II is the graphics chip in the C64. Programs can set interrupts on the VIC-II based on the current raster line, so we can run code, for example, after the last line has been drawn each frame to update game logic etc.
Default screen memory is at $0400, color RAM is at $d800. The following simple code will put some characters on the screen. This is nothing you wouldn’t find in any hello world tutorial, but it shows just how simple it is to start coding for the C64.
Jumping ahead now, taking that simple example and expanding on it, lets imagine we now have drawn a screen like this:
How do we scroll the background across the screen? The simplest approach is to move each byte in each line of screen RAM to the left. Then, at the right edge of the screen, we draw the new column. This results in jerky movement though, as the screen moves a character (8 pixels) per frame.
To avoid this, the VIC-II chip has two hardware scroll registers, one horizontal, one vertical. They allow the screen to be offset by up to 7 pixels in each direction. Heres how that works:
- Start by setting the scroll register to 7 (assuming we want to scroll to the left)
- Each frame, you decrement the scroll register by 1
- The screen contents move 1 pixel to the left
- Once it falls below 0, shift the entire screen RAM contents over by 1 byte
- Reset the scroll register to 7
- Now the screen contents have again appeared to move to the left by 1 pixel
- Rinse and repeat.
Here is an implementation:
We’re now copying 1000 bytes of screen RAM, 1000 bytes of color RAM, plus adding the new columns and dealing with the hardware scroll register. And we need to do it all before the VIC-II starts drawing the first line of the next frame. You think the 6502 CPU can do all this?
Nope! Not a chance! The flickering and tearing you see there is caused by screen/color RAM being updated while the screen is being drawn, and hardware scroll register being updated in the middle of the frame.
Alright, we need to get a bit smarter at this. Currently, we do nothing for 7 frames except twiddle the scroll register, waiting for it to fall below 0. To use this time, we need to implement double buffering. We’ll reserve another 1000 byte block in RAM to be our back buffer. This allows us to shift screen data at any time, because we are writing to the back buffer, not the visible screen.
Unfortunately, this still isn’t good enough, because it can take longer than the vblank to shift the screen ram, which means there is no time left over for game logic. We have to split it out further, and shift portions of the screen on different vblank intervals. In the example below, we copy screen when the scroll value is 4 and then 2.
The interesting thing to note here, is that we can shift portions of screen RAM across multiple vblanks thanks to our back buffer, but we can’t do the same with color RAM. Color RAM is fixed and cannot be buffered or relocated in memory. Unfortunately, we still don’t have enough time to shift 1000 bytes of color RAM, and run music and game logic in a single vblank. What can happen is as we’re shifting color RAM (and adding new colors at the right edge of each row), the VIC-II is also rasterizing the screen, pulling values from the same color RAM that we’re writing to. When the shifted colors are different, the screen tears and flickers as colors are updated.
The final piece of the puzzle, then, is how to deal with this color RAM. From reading a bunch of forum posts it sounds like there are several ways to handle it, but here is what I’ve implement and works nicely:
- Add a new interrupt a few lines below the top of the screen. When the interrupt fires, and xscroll==0, we know that next frame we need updated color RAM.
- In that case, shift the top half of color RAM now. Because we start the copy a couple of lines below the top of the screen, we follow behind the raster beam down the screen, shifting color entries which have already been used on this frame.
- On the vblank interrupt, after swapping screen buffers etc, we start shifting the lower half of color RAM. As long as we’re careful, we can shift it all before the VIC-II starts rasterizing the lower half of the next frame!
And there you have it. If anyone has a different implementation of this, I’d sure be interested to see it! :) Now, onto actually making a playable game…
Source code for the version above
TL;DR - Dumped a 17 year old debugging symbol file for a game called Carmageddon. Take a look at the symbols folder to see the output.
In the Carmageddon Splat Pack folder, there is a file called ‘DETHRSC.SYM’, last modified 13th November 1997.
It has sat there, un-noticed and un-loved for the last 17 years, ignored by the internet. Having made a remake of the Carmageddon engine, and being generally curious about random binary files, I tried to figure out the file format. Immediately by looking at it in a hex editor, it was obviously a debugging symbol file, the question was which type of symbol file? Of course, there are many symbol files with a .sym extension, and after some trial and error, it turned out to be a Watcom symbol file. I grabbed a copy of OpenWatcom and fired up the debugger, wd. It could read the symbols, but I never found any Carmageddon executable that matched up with it. It seems likely it was left there by mistake from a debug build.
Using wd to look at the symbols 1 by 1 in a little DOS window quickly becomes tiring, so then I wanted to dump the symbols out. For that, I needed the source code for the Watcom tools, and a working Open Watcom development environment.
(…Fast forward a while getting the environment up and finding where the code for handling symbol files lives…)
In the Watcom world, symbol file support is provided by various DIPs. To use a DIP dll requires the calling program to implement various client-side methods to allow the DIP to alloc memory etc, and then to provide callback functions for the DIP to call when walking the symbol list. It’s all pretty complex, but luckily there are a couple of utilities which illustrate generally how it should be done. I based jsonsymdump off dipdump (which is advertised as dumping symbol files to text format, but crashes on DETHRSRC.SYM). I did the minimal amount of work in C required to generate a valid json file, then wrote a node.js script to take that json file and generate some semi-valid-ish c files.
So what do we have after all that work?
You can browse the output in the symbols folder in the carmageddon1-symbol-dump repo.
We now know all the methods and properties that were implemented in at least some build of the Carmageddon engine. It’s not going to allow anyone to reverse-engineer the engine without a matching executable, and we don’t know how any of the methods were actually implemented. But it is interesting to see how things were named and organized, and by reading the method names, one can make some good guesses at what is happening at a high level in the game engine.
- Files underneath the DETHRACE directory are Carmageddon.
- Files underneath the BRSRC13 directory are BRender, a 3d graphics engine which Carmageddon used.
Some of the symbol names are funny, some might offend people, and some make no sense unless you are one of the original developers! A few examples:
How to build and run yourself (if anyone ever wants to…)
- Follow the README in jsonsymdump to build the C binary
- Run the node script:
npm install mkdirp
I’ve just posted a new video showing the latest OpenNFS1 dev version. Compared to the original video I made years ago when I made v1.1, this version is much faster, with a better sense of speed and more faithful to the original.
It’s been at least 4 years since I did any work on my remake of the original need for speed game. Back then it got picked up by a few gamedev sites and I was contacted by EA Canada asking if I was interested in interviewing with them! (A completely different story to Square Enix and my Carmageddon remake!). Looking at my code after so long is pretty cringe-worthy - I cut a lot of corners before the release, the code is messy but it is still sort of fun to play. With the end of XNA, and being between projects, I decided to do a quick port to MonoGame and move the project to GitHub.
I figured it would be a few days of work and I’d be done with the port, as MonoGame implements almost all of XNA 4. Unfortunately, I had compiled against XNA 3, so first I had to update to XNA 4 and deal with all the breaking changes. Once that was done, it was running in MonoGame pretty quickly. As all developers love to do, I started ripping, replacing and refactoring. The problem now was when I wrote the code I had built up a really good mental model of the Need for Speed track structure and how it was represented in code and in the data files. It’s quite complex, with many parts, represented by classes with names like TrackNode, TerrainSegment, TerrainRow, TerrainStrip etc. The code didn’t do a good job of explaining how these objects fitted together. I used to just know that a TerrainSegment always had 4 TerrainRows, and each row had 10 TerrainStrips, but coming back to it took a quite a while to get back in the groove.
A few weeks of late nights later, a load of new features, bug fixing, and more digging around the original data files with a hex editor, OpenNFS1 is much cleaner and more faithful to the original. A couple of things that made the biggest look and feel difference was changing the field of view and aspect ratio to match screenshots of the original. The values I originally used were way off, and resulted in a lack of feeling of speed. I’ve added some simple AI to race against, here are a couple of screenshots with 100 racers! The original game used some really fun optimizations to have 8 cars simulated at once, which I am also using, and 100 cars is actually pretty computationally simple because of it.
I’m going to write a few posts about how the tracks and scenery and physics are organized in the original game (as far as I can guess from understanding the data files) because some of the ideas could work well for mobile games today. Also, if I don’t write it down this time, I’ll forget and hopefully it will be interesting for someone else too.