I’m writing an NES emulator in Swift!

The NES uses a 6502 processor, which has 3 normal registers (a, x, and y) and defines its own instruction set of 56 instructions. That makes for a lot of little tiny implementations! Addition (ADC) and subtraction (SBC) are probably the most complex ones, with the latter taking me some two hours to figure out simply because it’s the first time since 2004(?) that I’ve had to think about binary math and two’s complements. Luckily (or annoyingly depending on your point of view) many of the instructions are nearly identical, especially the six “transfer” instructions, which just copy values between different registers.

While the obvious thing might have been to implement each instruction as a small function on my CPU class, I wanted to break each out into its own file to avoid making it a multi-thousand-line mess. Having each instruction implemented as its own struct also gives me the flexibility to turn each into a little state machine if I decide to make my emulator clock-accurate in the future. For the time being, each instruction fully executes on its first clock tick, and then the CPU just spins for the correct number of ticks before executing the next instruction. So my current implementation is duration-accurate, but not clock-accurate.

The Instruction protocol that each instruction conforms to just defines an execute method that takes the CPU as an argument. Since the instruction naturally needs to be able to fully access the CPU, one downside of this arrangement is that none of the CPU’s state can be declared private. The NES class is the only other code with a reference to the CPU, but it does expose it as public so that I can create some debugging UI with access to its registers. Maybe I can mitigate that with some module boundaries. We’ll see.

Because I have no idea what I’m doing (and because I hope the source can be a bit of a portfolio piece to show to potential employers), I wrote unit tests for each instruction and addressing mode.

One mistake I made is forgetting that of course some instructions write to memory! Since most of the instructions just read values and store results in registers, I had the CPU’s tick function use the current addressing mode to fetch the appropriate value and make it available to the instruction via a property on CPU before calling the instruction’s execute method. When I got to read-modify-write instructions like ASL, I let the instruction return a value from execute and then the tick function would use the addressing mode to write it back to where it came from.

But wait! There’s another complication! JMP actually needs to read two bytes from memory. (Addresses are 2 bytes long, but memory reads and writes only operate on a byte at a time.) This means I need to store the address from the addressing mode so that the instruction can read the extra byte from the following address.

I think sometime down the road instead of having the CPU’s tick method try to be smart and do these things, I’ll just pass the addressing mode into the instruction’s execute method and let it read and write exactly what it needs. Whoops!

I finally finished implementing all of the instructions and I’m on to the PPU, starting with its memory-mapped registers that are exposed on the CPU’s bus. Reading and writing these registers cause side effects within the PPU! More interesting times ahead! Stay tuned for more.

If you’re interested in following along you can find the code on Github.


Writing an NES emulator in Swift

A project I don’t suspect I’ll complete 🫣

I bought myself an Anbernic RG35XX Plus late last year and I’ve been enjoying going back and experiencing some of the video game history I missed when I was a kid. (My parents allowed computer use, but no game consoles!) I’ve primarily been playing old Nintendo stuff – some NES games like Super Mario Bros and a fan translation of the original Japanese Fire Emblem game, some SNES stuff (I played Chrono Trigger for the very first time this year, and wow is that a great game!), Pokemon Blue on Game Boy, and so on.

I’ve had a very (very) rough idea of how some of these systems worked, having seen videos about background vs sprite planes on NES and mode 7 on SNES. I even made a basic ROM hack of Dr Mario for my wife that replaced the “Dr Mario” graphic with “Dr Jason” and gave Mario myself a beard instead of a moustache.

On a lark a few days ago, I decided to try my hand at writing an NES emulator. I’ve never implemented anything approaching a virtual machine, and I thought it would be an interesting challenge to do it in Swift, given that most emulators tend to be written in C or C++. I found a good (albeit not comprehensive) series of videos on YouTube of someone building a basic emulator in C++, which gave me an overview of the system and a basis to get started, and I went off to the races!

Things will eventually get very complicated, but for now I just set up a bus with a CPU and some RAM on it, and I’ve been implementing all of the CPU instructions.

It’s obvious in retrospect, but I’d never considered that many instructions have more than one matching opcode! Each specifies a different way of reading or writing the data that the instruction operates on. There are several of these addressing modes, from using absolute raw addresses (Absolute) to indexing into a table of pointers and following the pointer to another memory location ((Indirect,X)). Each is relatively straightforward but some have easy-to-miss requirements like taking an extra clock cycle if an indexed address is in a different memory page than the base address.

I’m a little over half-way through all of the instructions, and looking forward to attaching more devices to the bus later like the cartridge, before tackling more difficult things like implementing the PPU, the chip that displays things on the screen. Interesting times ahead!

The code (such as it is) is on Github if you’re interested in following along.


micro

I doubt I’m going to be posting any more Twitter-like short posts here anymore. You can find me on my Mastodon server: @json@micro.sadlerjw.com.

I was on the mastodon.technology Mastodon server for the last several months, but due to the admin’s personal situation he had to shut down the server, so I decided to try setting up my one single-user instance. (It actually turned out to be a tiny multi-person instance when my friend Mike asked to join.)

(I’m @json@micro.sadlerjw.com. Give me a follow!)

I tried to get everything set up using Docker Compose based on the docker-compose.yml file in the Mastodon repo. It was pretty straightforward, but I ran into a few gotchas, which I figured I’d document here:

  1. The Mastodon services expect the Postgres user to be a member of a group named with the same name. (If your DB user is mastodon, then its group should also be mastodon.
  2. The Mastodon web/API server will silently refuse any non-HTTPS connections. If you’re running a reverse proxy in front of it which terminates the SSL connection, you need to include the X-Forwarded-Proto header. This was really hard to figure out, since the Mastodon server didn’t so much as log the connection attempt.
  3. The Mastodon web/API server will reject any connection not using the hostname it was configured to run under. It does log that it rejected the connection, but not the reason.
  4. You may want your Mastodon handle (eg @json@micro.sadlerjw.com) to use a domain that’s different from the one the server runs on. For instance, my server is at https://micro.sadlerjw.com. If I had known how, I would have configured my handle to be @json, without the “micro”. You can do this using the LOCAL_DOMAIN and WEB_DOMAIN environment variables. You can find details in the documentation. You can’t change this after you start using your instance!
  5. SendGrid has a free tier if you send less than 100 emails per day. This post on the Mastodon Discourse forums helped me get it set up.

There are definitely a few minor things that don’t seem to work properly, but I’m not sure if it’s either “federation ain’t perfect,” or something that’s my fault, or a problem with the software. For instance, I can’t seem to add a post with video uploaded from my iPhone. Your mileage may vary.

Hope this can help someone! At a minimum, this has been a fun experiment. I definitely wouldn’t recommend administrating a server for a large community though…that sounds like a lot of work both in terms of setting policies and moderation, and also in terms of keeping the services up and running and responding quickly. But stand up a little server for your friends, it might be fun!


Psychonauts 2

★★★★☆

I finished playing Psychonauts 2 the other night. What a great game. Cute and funny writing, incredibly clever and varied gameplay, a solid (if heavily foreshadowed) story, and great voice acting (including Elijah Wood)!

The game has decent character diversity, featuring a disabled character, a gay couple, and characters dealing with all sorts of mental difficulties. The game focuses on the founders of The Psychonauts, who experienced a traumatic event together long ago, and has the player character, Raz, jumping into their brains to help them work through their responses to that event. Its treatment is light-hearted but not trivializing (all of the characters are treated with a lot of empathy), and the physical manifestations of these characters’ minds is absolutely stunning in their creativity. The level design is off the charts.

In spite of the game’s inclusiveness, it has some blind spots. Its use of Eastern European stereotypes throughout is uncomfortable, and the treatment of the inside of an East Asian character’s mind in one of the later levels is more than a little problematic. If you make a joke about how smelly a fish market is, you’ve made a joke about fishes; but if you make a joke about how dirty a fish market is in this context, as Phsychonauts 2 does, you’ve made a joke about the sanitariness of an East Asian culture. It reminds me a bit of the term “wet markets” and the demonization of those Chinese markets in the early stages of the pandemic. The level only has a few missteps like this, but better to have avoided them altogether.

The story was very emotionally engaging in the first 80% of the game, but unfortunately the ending fell a little bit flat for me, not living up to what I think could have been something really, really special. After the final battle, it just sort of…ended.

In spite of some cultural missteps, the rest of the game is truly wonderful. If you feel like giving it a try and you have Xbox Game Pass, it’s available there!