Project Sand: Sonic Aftermath

I was digging through some old Sonic hacks of mine when I stumbled across these ancient relics from 2014 and 2016.

Remember that Project Sand/Sonic Aftermath hack that I have a few old videos about? Here’s the one level of it that was worked on before the project died: Sand Zone from Cave Story.

There’s not much that I can tell you about it: this one test level was made before I came up with any ideas for gimmicks and the sort. That purple mess in the middle of the stage is supposed to be spikes, but its graphics are overwritten with the level tiles.

I believe that the music is a straight port from Cave Story: the Organya music was converted to XM with the org2xm tool, then that was converted to SMPS with xm2smps, and then that was converted from binary to ASM with SMPS2ASM, allowing it to be installed in my hack’s custom sound driver. This hack was either rocking my Sonic 2 Clone Driver v2, or Flamewing’s Flamedriver. After all this time, I can’t remember which.

This footage is of an earlier build of my hack than the one seen in the other videos. The reason for this is that the hack was remade from scratch after this build, and this level was never reintroduced afterwards.

Here’s a cutscene that was one of the last things that was worked on before the project died.

This was meant to be the opening cutscene to Knuckles’ story: the hack was meant to be a ‘what if’ scenario where Sonic 2 ends differently, leading to a different series of events in what would be Sonic 3. This cutscene depicts the Death Egg landing in Angel Island’s volcano directly after the events of Sonic 2, instead of the lake like it normally does. Following this, Knuckles would have gone to investigate it. At the time, I didn’t have Knuckles ported into the game, so I used Sonic as a placeholder.

Between this cutscene, Sand Zone, and the custom title screen, this is all there ever was to Project Sand/Sonic Aftermath.

clownmdemu and clownassembler released

Sorry for the drought of blog posts lately: I’ve been busy with work and a lot of other IRL stuff. Still, if there’s one thing I can give an update on, it’s that two of my projects have finally seen a release: clownmdemu and clownassembler have been released on Sonic Retro, Sonic Stuff Research Group, and Mega Drive Developers Collective.

Here are links to the various release threads.

clownassembler was released less than an hour ago, so there’s not much that I can comment on feedback-wise. However, clownmdemu was released at the end of June, giving it plenty of time to receive feedback.

Surprisingly, people were quite enthusiastic and welcoming of the emulator despite its unfinished state. If anything, people seemed more bothered by the hardcoded key bindings than its inability to boot certain games. I can only hope that clownassembler gets an equally warm reception, considering that it is similarly unfinished.

clownmdemu – Z80 Support

With the addition of FM support, my Mega Drive emulator came much closer to being able to provide a complete experience for certain games such as Sonic 1. Unfortunately, there was still one major missing feature: drums, voice clips, and sometime even all audio entirely, were inaudible. What gives? Sonic 1 plays most of its audio, but 2 and 3 don’t play any at all?

The reason that this is the case lies in the architecture of these games: Sonic 1 uses a sound engine that runs on the 68k CPU, while 2 and 3 use one that runs on the Z80 CPU. Up to this point, my emulator has not emulated the Z80 CPU, which is why no sound plays in those two games. Additionally, Sonic 1 uses the Z80 CPU for its drums as well as the famous Sega chant, which is why those are missing as well.

However, this problem is no more: new to clownmdemu is Z80 CPU emulation. It doesn’t implement 100% of the Z80’s feature set, but it’s enough to get at least the Sonic games to output all of their audio. Heck, it’s even enough to get Sonic ROM hacks to play their audio, including the ones that use fancy custom Z80 code:

This ROM hack in particular, Sonic 2 Recreation, uses Z80 code that was written from scratch by ValleyBell and is able to apply a variety of effects to its PCM samples.

Writing another CPU emulator was nice because it gave me a chance to apply what I’ve learnt since writing the 68k CPU emulator. In particular, instead of making each step of execution a giant switch statement, there’s only one switch statement: one for each instruction. This does result in some duplicate code, but the hope is that this is outweighed by avoiding the overhead of going through 6 or so switch statements.

I’ve also used this opportunity to write the machine code decoder in such a way that its job can be replaced with a lookup table. You see, the task of breaking a byte of machine code down into a struct which describes the operation to be performed has been given to a function: this function takes a byte and returns a struct. Because of this modularised design, I can execute a loop on start-up which executes this function for every possible byte, from 0 to 0xFF, and then caches the resulting structs in a big lookup table. Then, during emulation, the machine code to be executed can be used as an index into this table to retrieve its corresponding struct. By doing this, the expensive step of manually decoding the machine code is skipped entirely. According to my tests, this doubles the performance of the Z80 emulator. Though, for RAM-limited platforms, I’ve left a compile-time option to revert back to using the function instead of a lookup table. One day, I would really like to refactor the 68k CPU emulator to bring over these improvements.

The Zilog Z80 itself is a weird thing: it’s an 8-bit CPU with a 4-bit Arithmetic Logic Unit which mimics the instruction set of another CPU (the Intel 8080) while adding extra instructions of its own. The extra instructions were bolted-on in a very ugly way that clashes with the ‘neatness’ of the base 8080 instruction set. It also contains some outright hacks that aren’t intuitive to emulate at all: for instance, there’s a certain mode that you can make the Z80 enter where the output of an instruction can be written to both a register and memory at the same time. It essentially hijacks the operand getters and setters.

Anyway, with this implemented, Sonic 1 and 2 are now finally fully emulated: there isn’t a single feature in those games that isn’t being emulated (to my knowledge anyway). Sonic 3 still has some strange bugs, and it makes use of unimplemented features such as SRAM and the Window Plane, so that may be what I work on next.

There are still a number of things that I want to add before I give this emulator a proper release: a libretro core, controller rebinding, SRAM, the Window Plane, 68k instruction durations, 68k exceptions, YM2612 LFO, YM2612 SSG-EG, YM2612 Timers… though, maybe I should just release this anyway: it’s not like this emulator will ever play every title perfectly or anything.

As always, you can find the source code to my emulator in the usual place.

Oh, right. I should also list the resources that I used when making this Z80 emulator. First there’s this blog post which gave a general overview of the Z80, and explained why and how the Z80’s instruction set is the way it is. Then there’s this follow-up which explains the Z80’s timings. There’s also this useful table which visualises the various opcodes. Finally, there’s this webpage which explains how to effectively decode Z80 opcodes. These resources were invaluable to me, and hopefully they can be to others too.

clownmdemu – FM Audio Emulation

It finally happened! With university over, I decided to tackle what is perhaps my greatest challenge yet in writing this emulator: emulating the YM2612.

The YM2612 is the Mega Drive’s primary audio chip. Apparently, it is a cost-effective, stripped-down version of the YM2608: while the YM2608 featured FM, SSG, Rhythm, and ADPCM modules, the YM2612 is just a standalone FM module with basic DAC output slapped on it.

For the longest time, the only documentation that was available to emulator developers was the “Genesis Software Manual“, which was a document that Sega made available to developers that described the console’s hardware. Unfortunately, this document went into very little detail about how the sound hardware worked. Still, it was apparently good enough for a number of emulators to be made back in the 1990s and 2000s.

Later, in 2008, Nemesis obtained a copy of the official YM2608 manual. Unfortunately, this document was in Japanese, but he was able to produce a mostly-coherent machine-translation. This document answered many questions that emulator developers had about the YM2612, but it still failed to go into detail when it came to certain subjects that were essential for emulator developers to understand.

When I began implementing my own FM emulation, I decided that I would try to stick to this manual early on, and only seek out additional information once I get stuck. With just this YM2608 document and my own knowledge of the YM2612, I was able to produce an extremely basic emulator that produced one sine wave per channel. These sine waves could have their volume and frequency adjusted. While this wasn’t nearly enough to produce authentic audio, it did at least make it possible to hear music and sounds.

Unfortunately, there wasn’t much more that I could do than this: I knew from the manual that there were three core components of the YM2612 that were essential to how it produced audio: the Operators, the Phase Generator, and the Envelope Generator. However, while the manual described what they are and what input they take, it did not describe what output they create from that input. To give an example, a ‘detune’ value can be supplied that offsets the frequency, but neither the Genesis Software Manual nor the YM2608 manual describe how much it offsets the frequency.

At this point, I decided to find some more information on the YM2612. I remembered that the SMPS devkit which was found a few years ago had a YM2612 manual, but unfortunately it too was in Japanese and with seemingly no translation available. It turns out that the manual for the YM2612’s CMOS equivalent (the YM3438) was also found, but yet again it was in Japanese. This wasn’t a huge loss though, as those didn’t appear to contain anything particularly useful that wasn’t already in the YM2608 manual.

What was useful, however, was a thread on SpritesMind that I’ve had bookmarked for years. It’s 58 pages of discussion and discoveries regarding YM2612 emulation, including some incredible documentation that was produced by Nemesis. In particular, he created three massive write-ups of exactly how the YM2612’s Operators, Phase Generator, and Envelope Generator all work. This information is utterly invaluable, as it provides most of the ‘missing pieces’ to the YM2612’s functionality that the manuals lack. Somehow, with nothing more than a Mega Drive, an oscilloscope, and an extensive array of tests, he was able to even figure out details as nuanced as the exact values of the chip’s internal sine wave lookup table.

It took me about four hours straight to read through that whole thread, but it was worth it! I’ll probably have to read through it again to catch any details that I missed the first time around. If you’re interested in making your own Mega Drive emulator, or are just curious about the YM2612, then I cannot recommend that thread enough.

Unfortunately, Nemesis wasn’t able to complete his documentation of the YM2612, meaning that there was still missing information on three key components: the accumulator, Operator feedback and modulation, and the Low Frequency Oscillator.

For the time being, I passed on implementing the Low Frequency Oscillator because Sonic 1 (the game that I was using to test audio) doesn’t use it. The accumulator, I had already produced my own implementation of through guesswork. This left the Operator feedback and modulation.

So what is Operator feedback and modulation? To understand that, you have to understand how the YM2612 produces sound. So, what the heck: here’s an overview of how the YM2612 works:

The YM2612 has six channels. Each channel is composed of four sine waves, dubbed ‘Operators’. Each Operator has its own Phase Generator and Envelope Generator. The Phase Generator advances the sine wave, and the Envelope Generator produces an ADSR envelope. The Phase Generator manages the frequency of the sine wave, and thus is responsible for producing notes, while the Envelope Generator is responsible for shaping the sine wave into a more complex waveform, and thus creating basic ‘instruments’ or ‘voices’.

However, the Operators allow for even more advanced ‘instruments’ to be made through Operator modulation: rather than output its waveform to the speakers, an Operator can instead feed directly into another Operator, modulating its sine wave in a process known as ‘phase modulation‘.

‘Operator feedback’ is the process of the first operator of a channel feeding into itself, which is a feature unique to the first operator.

In the spirit of emulation development, I decided I’d find out for myself how to implement Operator modulation: with the help of the cycle-accurate Nuked OPN2 YM3438 emulator, I compared the output of my emulator to what a real Mega Drive would sound like, and tweaked my own Operator modulation implementation until it sounded correct.

Despite this, the audio still sounded far from accurate. I was able to track one source of distortion down to an incorrect implementation of the Phase Generator’s multiplier, and while that did fix the channels sounding like whistles, it still left the audio sounding like this:

Clearly the envelope generator was running too fast… and yet its code matched Nemesis’s notes exactly. I spent hours debugging this, creating custom FM instruments to test specific parts of the emulator against Nuked OPN2, but nothing made sense: the envelope generator was absolutely working as intended. I then figured that perhaps I had given the emulator’s frontend the wrong sample rate, and that it was somehow playing the audio back twice as fast.

The truth was a lot dumber: I’d accidentally given the emulated YM2612 a 7x overclock.

The YM2612’s clock is derived from the Motorola 68000’s clock, which is derived from the master clock. The 68000’s clock is the master clock divided by 7, and the YM2612 clock is the 68000’s clock divided by 6. My emulator had the YM2612 clocked at the master clock divided by 6.

Once that was corrected, (and I properly implemented ADSR envelope rate-scaling which I somehow glossed over when reading the YM2608 manual 100 times), I finally had this:

At last: it worked! And it came so suddenly too: one minute everything’s a whistle, and the next it sounds like a real Mega Drive!

And that takes us to where we are now. There are still many things left for me to implement in my YM2612 emulator, such as per-operator frequencies, Timer A and Timer B, the Low-Frequency Oscillator, SSG-EG (a second Envelope Generator), and possibly even the debug registers.

You might be asking yourself how I’m going to replicate all of those when there’s so much missing documentation. Well, the truth is that the YM2612 and YM3438 have actually been completely documented for years now. I just figured that it would be too easy to use that documentation. What is that documentation? Nuked OPN2’s source code.

You see, Nuked OPN2 isn’t just a cycle-accurate emulator: it’s a cycle-accurate emulator that’s based directly on a die-shot of a de-capped YM3438. Essentially, Nuked OPN2 is a conversion of the YM3438’s circuitry to C. With this, there are no mysteries about how the YM3438 works: everything is documented in such a way that you can verify it just by running it. What other form of documentation doesn’t just say ‘dude, trust me’, but ‘here: I’ll prove it’?

While I do plan on using Nemesis’s documentation to implement SSG-EG, Nuked OPN2 can be used to implement any details that aren’t explained elsewhere.

Surprisingly, some parts of my YM2612 emulator happen to function exactly how a real YM2612 does, in contrary to how documentation suggests they should function. For example, the note octave is encoded as a number between 0 and 7, which expresses the following behaviour:

OctaveBehaviour
0Divide note frequency by 2.
1Leave note frequency as-is.
2Multiply frequency by 2.
3Multiple frequency by 4.
4Multiple frequency by 8.
5Multiple frequency by 16.
6Multiple frequency by 32.
7Multiple frequency by 64.

Both the manuals and Nemesis’s notes suggest that this should be implemented as a left-shift by the number of the octave minus 1, with special-case logic for octave 0 that does a right-shift by 1 instead. In my emulator, however, I just left-shift by the number of the octave, and perform a single right-shift afterwards, creating the same result with more-efficient code. According to Nuked OPN2, this is exactly what a real YM3438 does as well.

As much as I’d like to continue rambling about this stuff, there’s only so much to write about, and only so many hours in the day. As usual, you can find clownmdemu’s source code in its Git repo. Hopefully I’ll have more progress on my emulator to talk about soon. In the meantime, I’ll leave you with a video of a fun bug:

The Mega Drive’s Interlaced Video Output

Today, I looked into adding support for the Mega Drive’s interlaced video output to my emulator. It didn’t go how I planned, and I eventually realised that it was worthless to pursue. Still, I think this makes for a fun story.

First, I should probably go over the basics of how old CRT TVs would display. Basically, the screen is split up into 480 lines, but they are not all drawn at once. Rather, the even lines are done first, and the odd lines are done on the next frame, or vice versa. You could think of it as the TV rendering 240 lines at 60FPS or 480 lines at 30FPS.

With that in mind, we can begin to understand the Mega Drive’s interlacing. It has three modes:

  • Mode 0, which is the non-interlaced mode. This mode is plain 240p. It uses a trick to prevent the odd lines from ever being drawn, meaning that two sets of 240 even lines are drawn instead.
  • Mode 1. This mode is similar to mode 0, but it does not prevent odd lines from being drawn. The odd lines will display the exact same graphics as the even lines. The official ‘Genesis Software Manual’ developer document warns that this mode will result in severe vertical blurring.
  • Mode 2. This mode is very interesting: it is like mode 1, except the odd lines will not display the same graphics as the even lines. Basically, the Mega Drive’s vertical resolution will double, being 320×480 or 256×480. However, because only even or odd lines are displayed in a single frame, this means that the image will be downsampled back down to 320×240/256×240 when displayed.

Regardless of the interlace mode, it is always 240 lines that are output in a frame. I wanted to implement this in my emulator, to perfectly replicate the rendering of interlace mode 2, which until now has been rendering at the native internal resolution of 320×480.

However… always rendering a 320×240 image wouldn’t be correct. After all, old TVs have 480 lines, not 240. Since mode 0 disables odd lines, there should theoretically be empty black spaces between each line, creating a ‘scanline’ effect.

To recreate this, I set about making my emulator always render a 320×480 image, and having the emulated console simply skip lines. This is accurate to how a real Mega Drive displays on a real CRT TV. However, having leftover lines from a previous frame mixed in with lines from the current frame produced an ugly ‘comb’ effect:

The effect in Mode 1.

Because of this, and knowing that the lines of a CRT fade when they’re skipped, I decided to simply make the skipped scanlines black.

In Mode 1 and Mode 2, the lines which are black and the lines which are actually drawn alternate every frame, causing the screen to ‘jitter’. It looked pretty cool and authentic to how I remember the two modes looking on a real Mega Drive. I ended up doing some extended playtesting with this, to soak in the nice scanline effect. When I was done, I closed the emulator and- oh dear:

My monitor had severe image persistence:

A recreation, because there’s no way in hell that I’m doing it again.

It turns out that this is a terrible idea: apparently, much like the waveform of a sound, the electricity going through an LCD pixel must go up and down, positive and negative. It alternates every frame, just like how a wave alternates every sample. By rapidly flickering a pixel between colour and no colour every frame, however, the pixel never gets to go negative, always remaining positive, or vice versa. The result is that the pixel becomes a capacitor, storing charge and refusing to release it, causing it to display colour when it shouldn’t.

Thankfully, this appears to be temporary, as the built-up charge will dissipate naturally. Still, this was bloody terrifying: I thought I’d just ruined the display of my fancy new laptop.

Clearly, this interlacing emulation had to go. The last thing I need is a wave of heated bug reports from furious users who think their monitors have been destroyed.

Thinking about it, I realised that there’s no point to emulating the Mega Drive’s interlacing in the first place besides authenticity: in every use-case, interlacing is either an annoying side-effect or an irrelevant technical detail.

  • Sonic 2 uses mode 2 for its split screen multiplayer, taking advantage of the doubled vertical resolution. The interlacing does nothing but halve the game’s vertical resolution and introduce an ugly jitter effect.
  • Mode 2 could be used to display a static 320×480 image, in which case the interlacing wouldn’t be visible at all.
  • Mode 2 could be used for supporting 3D glasses, in which case the interlacing would cause each eye to see 30FPS instead of 60FPS.

As a result of this, interlacing emulation is completely pointless: my emulator has been doing ‘the right thing’ the entire time by just not emulating it. Better yet, my emulator goes above and beyond by rendering mode 2 at its native 320×480 resolution instead of halving its resolution back down to 320×240, meaning that it renders mode 2 better than a real Mega Drive.

It’s… beautiful!

I suppose this is where the story ends. Interlacing has been a strange thing to research, but I’m glad that I now understand more about how the Mega Drive delivers images to the display. I just wish that I didn’t nearly suffer heart failure in the process.

Knuckles in Sonic 2 Disassembled

With my honours project complete, I decided to put my newfound free time into a project that I’ve been meaning to get around to for almost five years: disassembling Knuckles in Sonic 2.

In case you don’t know, Knuckles in Sonic 2 (which I’m just going to call ‘KiS2’ from now on) is a version of Sonic 2 that lets you play as Knuckles instead of Sonic and Tails. Sonic hackers like to port Knuckles from this version back into regular Sonic 2, but, in the process, they effectively undo the huge number of changes that KiS2 made to Sonic 2’s codebase. This ranges from simple alterations for Knuckles, to bugfixes that have gone undiscovered to this very day.

You might be asking yourself why I want to disassemble this game, since a disassembly for it already exists. Well, the reason is that the existing disassembly is completely separate from the Sonic 2 disassembly that also already exists. Not only does this mean that it is horrifically outdated in comparison to the Sonic 2 disassembly, but this also makes it extremely difficult to compare the two games and find differences between them.

Rather than disassembling the game from scratch like the maker(s) of the other disassembly did, my approach is to take the Sonic 2 disassembly, and edit it to match KiS2. This is exactly what I did to create the disassemblies of Sonic 2’s revisions (REV00 and REV02), the game’s Mega Play arcade version, and the version of Sonic 2 found in Sonic Classics/Sonic Compilation.

As of writing, this task is finally done, and I have a modified Sonic 2 disassembly that produces a perfect copy of KiS2. With this disassembly more or less complete, I figured I should explain everything I’ve learnt about KiS2 here:

Changes

Knuckles

Obviously, Knuckles has replaced Sonic. This is actually surprisingly tacked-on: Knuckles is just a lightly-modified Sonic with all of the gliding and wall-climbing behaviour wrapped in a single function call. I suppose this isn’t surprising, but I was under the impression that the whole Knuckles object was copied from an in-development version of Sonic & Knuckles. I think I got that idea from the Sonic 3 Unlocked blog, but I could just be misremembering.

Notably, Knuckles’ graphics are loaded from the Sonic & Knuckles cartridge: the tiles are recoloured at runtime to suit Sonic 2’s palette. The sprite mappings and dynamic tile loading data are also loaded from the Sonic & Knuckles cartridge. Sonic hackers may find this surprising, since Sonic & Knuckles uses a different sprite mapping format to Sonic 2. This leads me into my next point…

Mappings

All of the game’s mappings were converted to Sonic & Knuckles’ format. This strikes me as very odd, as this means that the mappings now have to be included in the KiS2 ROM, instead of being loaded from the Sonic 2 cartridge, wasting space. Maybe it was considered too much effort to go through the whole game and split the mappings? This conversion was universal: even unused mappings were converted. Heck, even unreferenced parts of mappings were converted. This suggests that the mappings were created using assembly macros, and the macro itself was modified to convert the mappings to Sonic & Knuckles’ format.

The difference between Sonic 2’s and Sonic & Knuckles’ sprite mapping format is that Sonic 2’s has extra data for the game’s two player mode, which uses a fancy rendering mode of the Mega Drive’s VDP. This leads me onto yet another point…

Two Player Mode

Two player mode was removed, but not entirely. It appears that the developer(s?) were struggling to fit the game to the size they wanted, so they began removing code related to two player mode, and once they reached their desired size, they stopped. In the end, they scraped by with only 680 bytes to spare.

There are plenty of leftovers from two player mode in the game: the variable used to detect two player mode (dubbed ‘Two_player_mode’ in the disassembly) still exists, and is referenced frequently in the game’s code. For example, the level title card object still makes heavy use of the flag.

Being a Sonic hacker, I’ve removed two player mode from Sonic 2 before, and I’ve done it much more thoroughly than in KiS2. With that in mind, I know how complex removing two player mode is, so it doesn’t surprise me that the developers didn’t go all the way with it.

Lock-On Technology

This won’t be a surprise to most people reading this, but KiS2, despite being a version of Sonic 2, doesn’t have many of Sonic 2’s assets in it. Instead, it copies them from the attached Sonic 2 cartridge. You see, KiS2 isn’t a standalone game: it’s actually a bonus mode in Sonic & Knuckles. Sonic & Knuckles’ cartridge has a cartridge slot on top of it, allowing you to plug other cartridges into it, with KiS2 being the result of plugging in Sonic 2’s cartridge.

The way Sonic 2’s assets were removed from KiS2 is pretty basic: at the end of Sonic 2 is a massive block of assets (including the game’s music, sounds, drum samples, enemy graphics, player graphics, player sprite mappings, level graphics, level layout, level object placements, and more), and it is simply removed in KiS2. Notably, assets that aren’t part of this giant block were not removed, such as the title screen’s ‘1 PLAYER’ and ‘2 PLAYER VS’ text.

As mentioned earlier, some assets are loaded from the Sonic & Knuckles cartridge, such as Knuckles’ assets. However, those aren’t the only things that are loaded from that cartridge: KiS2 features modified level object placements, which reward the player for exploring with Knuckles’ wall-climbing. Strangely, the data for this is in the Sonic & Knuckles portion of the cartridge instead of KiS2. It’s possible that this was done to free-up space in KiS2, with Sonic & Knuckles having room to spare.

Bugfixes

KiS2 contains a surprising number of bugfixes:

Perhaps most notably, KiS2 removed the air speed cap, which appears to be a leftover from Sonic 1. This is significant because it has always been unclear whether the air speed cap was deliberately retained in Sonic 2 as a feature, or leftover as a bug. The air speed cap is responsible for at least two areas in Sonic 2 not working as intended: the red spring that leads to the ‘monkey island’ in Emerald Hill Zone Act 2, and the launcher that flings you over a large gap in the floor in Wing Fortress Zone. In both cases, the speed cap causes the player to undershoot their target if they press left or right on the D-pad while moving through the air. The removal of this speed cap in KiS2 suggests that it was indeed an unintentional leftover all along.

One of the most well-known bugfixes in KiS2 is the correction of a bug that causes the bottom two lines of the screen to appear incorrectly in Emerald Hill Zone. I wonder how this bug was discovered, since televisions were especially prone to overscan hiding the edges of the screen back then.

One type of bugfix that KiS2 contains is taking the player’s character out of their ‘roll-jumping’ state, where their controls are basically locked. Being left in this state at a bad time can result in the game soft-locking, as the player is unable to move their character. Times when KiS2 makes the character exit their roll-jumping state is when they enter a wind-tunnel and when hovering over a propeller in Wing Fortress Zone.

Sonic 2 suffers from a particularly glaring bug, where entering the cheat to gain 15 Continues causes the game to play Oil Ocean Zone’s music forever. The cause is a nonsensical sound ID being submitted to the sound driver. This is corrected in KiS2. This bug was also fixed in the version of Sonic 2 included in Sonic Mega Collection.

The title card appears to have had a bugfix applied to it which prevents odd behaviour if the graphic of the name of the zone goes too far to the left of the screen, causing its X coordinate to drop below 0. This bugfix works by replacing some unsigned conditional branches with signed conditional branches, and only drawing the sprite if it is within 48 pixels of the screen’s left side.

The bumpers in Casino Night Zone have their own layout data. This data needs to be terminated with special byte patterns that prevent the bumper manager from reading beyond them and parsing surrounding code as data. One of these termination patterns is missing from the very start of Act 1’s layout data. In a stroke of good luck, the code before the data happens to resemble the terminating byte pattern, preventing the bumper manager from processing invalid data. In KiS2, however, this is no longer the case. A proper data terminator was added at the start of the data, fixing this problem. Fun fact: this bug appears to have not been fixed in the earliest prototype of KiS2, causing the game to crash if you go to the top left corner of the level.

There are also some modifications to the game’s collision code, which may be an attempt to fix bugs in it. Unfortunately, I haven’t figured out the point of these modifications yet, so I can’t say for sure what bugs, if any, they’re trying to fix. One bug that it appears to be trying to fix is the bug in Sonic 2 where collision with an object from below doesn’t properly push the player out, sometimes resulting in them phasing straight through the object. This fix does not work correctly, however, and cancels-out the player’s inertia when it shouldn’t. You can read more about it here.

One rather funny bug is that if you’re moving at a high speed towards a wall, and then start moving in the other direction at last second, Sonic will impact the wall and then start moving away from it while playing his pushing animation. KiS2 appears to fix this bug as well, preventing Knuckles from entering his pushing animation if he is not facing towards the object that he pushed against.

In Sonic’s movement code, a register that holds his speed is unintentionally partially overwritten before being used later on to decide whether Sonic is moving fast enough to skid or not. This creates an asymmetry in what speed Sonic needs to be in order to skid when attempting to move in the opposite direction. This too is fixed in KiS2. You can read more about this bug here.

Another bug fixed by KiS2 is that, when the player turns Super, a ring is instantly drained. This is due to a counter never being initialised. Now, the game waits a second before draining the first ring, which is consistent with how it drains every ring afterwards.

In Mystic Cave Zone, it’s possible for the player to become ‘detached’ from a hanging vine switch, appearing suspended in the air away from the vine itself. KiS2 addresses this by forcefully updating the player’s coordinates to match the vine every frame.

Speaking of Mystic Cave Zone, the boss of that zone has a nasty bug where, apparently due to a copy-paste error, the wrong address register is used at one point, causing a random byte of memory to be overwritten. Somehow, KiS2’s developers noticed this and fixed it.

And… that’s it. That should be the last of the bugfixes that I’ve found in KiS2. So, what other changes were made in KiS2?

JmpTos

Yep, JmpTos again. They always find an excuse to crop up when I do this kind of thing. For those not in the loop, ‘JmpTo’ is the nickname given to branch extensions that are present through Sonic 2’s codebase. If a branch is too short to reach its destination, it instead branches to a long-range jump instruction in order to reach it. In the first two revisions of Sonic 2 (REV00 and REV01), they appear to have been generated by the assembler. In the third revision – REV02 – they changed significantly, presumably because the developers switched to using a different assembler. They’ve once again changed quite a bit in KiS2.

What’s interesting about the JmpTos in KiS2 is that they appear to be hand-made, as opposed to the obviously-automated JmpTos in Sonic 2 REV00 and REV01. You see, it appears that the developers went through much of the game’s code, ‘tidying’ the JmpTos: rather than being messily mixed into code, as they were in REV02, they were grouped and moved to the end of their respective blocks of code. Additionally, redundant branches to JmpTos were eliminated: in Sonic 2 REV02, it wasn’t uncommon to see unconditional branches that branched to JmpTos, when they could have just been jump instructions that jumped straight to the intended destination – KiS2 removed many, if not all, of these.

Further adding to the idea that REV02 and KiS2’s JmpTos were hand-made is the fact that one of the JmpTos in REV02 (‘JmpTo13_MarkObjGone’) is completely unused. It was removed in KiS2.

Restored Debug Features

Invisible objects, such as plane-switchers and invisible walls, become visible in Debug Mode in KiS2. One object in particular is made visible with code that was previously only in REV00. This suggests that the code may have existed in REV01’s and REV02’s source code in a dummied-out form that was simply un-dummied-out in KiS2. Perhaps these debug features were hidden behind a build-time flag?

Removed Development Code

In Sonic 2, after the ‘loadLevelLayout’ function is some leftover code. The first chunk of code is the level layout loading function from Sonic 1, modified to repeat the background layout. This was used in some of Sonic 2’s prototypes.

After that is a function that converts a level’s chunks from Sonic 1’s 256×256 format to Sonic 2’s 128×128 format, and after that is a function for eliminating duplicate 128×128 chunks. These were likely used to convert Green Hill Zone’s chunks to 128×128 for Sonic 2’s “Nick Arcade” prototype.

After surviving through numerous prototypes, all three revisions of the final Sonic 2, Sonic Classics, and the Mega Play arcade version, this code was finally removed in KiS2. RIP.

Demos

Also known as ‘attract mode’, the game will play some demos if you leave it on the title screen. The developers of KiS2 attempted to preserve compatibility with Sonic 2’s demos, reenabling things like the air speed cap and giving Knuckles Sonic’s jump height when a demo is playing. Unfortunately, the result is not perfect, and the demos still manage to desynchronise at points. The developers went so far in their attempts to keep the demos working that they manually edited the inputs for the Emerald Hill Zone demo.

Other

I could talk about the modified title screen, Wing Fortress Zone cutscene, ending, and logo after the credits, but honestly I can’t think of anything noteworthy about them. Maybe I’ll go over them in a follow-up post, if I can think of anything interesting to say.

Standalone

As an experiment in what is possible with this disassembly, I’ve added an option to build a ‘standalone’ version of KiS2 that doesn’t rely on Sonic 2 or Sonic & Knuckles in order to run. This is similar to the ‘Sonic 3 Complete’ mode of the Sonic & Knuckles disassembly, which produces a version of Sonic 3 & Knuckles that doesn’t rely on Sonic 3. You can find a built ROM of this standalone KiS2 here. The intention of this, in addition to just being a tech demo, is also to make it feasible to produce ROM hacks of KiS2, which is practically impossible whilst it is dependant on two other ROMs.

Conclusion

Personally, I’ve learnt a lot about KiS2 from this disassembly, and I hope others will learn a lot from it too. KiS2 has always been a mysterious black box to me: its many changes and fixes always being out of reach and beyond our understanding, with no easy way to find the new in a sea of old. Every change and every fix was a needle in a haystack… but not anymore. Maybe now we can see a *complete* port of Knuckles to Sonic 2, title screen, ending, compatibility adjustments, and all!

The disassembly can be found here.

Fun fact: I started this disassembly on the 28th of April, and it was completed on the 5th of May. It took me almost five years to get around to doing something that only took a week. Geez.

Sonic Monitor in Microsoft Office

This will never not be weird to me: for some reason, there’s a Sonic monitor lookalike in Microsoft Office. To see it, go to the ‘View’ tab and select the ‘Zoom’ option:

I don’t get it. What came first: Sonic’s monitor or Microsoft Office’s monitor? Who was copying who? Were they even copying each other to begin with?

There seems to be multiple variations of this little sprite: I have an old screenshot from 2015 that shows a version which is much closer to Sonic’s monitor sprite:

Here’s a comparison of the three:

Somebody please tell me that I’m not alone in thinking that these look uncannily similar: they’re both 30×30, they use similar greys, and even the image in the Sonic monitor perfectly matches the size and position of the image in the Office monitor. They’re so similar that I can literally put it into a ROM-hack of Sonic 1 and it works perfectly:

Somebody please help: this has been driving me nuts for the last 7 years.

Everything That I Know About Sonic the Hedgehog’s Source Code

I’ve been meaning to write this for years:

To my understanding, not much is commonly known about the source code of the original Sonic the Hedgehog games. As someone who’s been obsessed with the programming of these games for almost ten years, I believe that I know a lot more than most people do. Unfortunately, my memory is awful, and I’m not going to be a Sonic hacker forever, so I want to preserve this information however I can before it’s lost again.

As of writing, the source code for the ‘classic’ Mega Drive Sonic the Hedgehog games has never been found. We do have an exhaustive list of disassemblies, however those do not capture all information that proper source code would give us. For example, while the logic of code is recovered by these disassemblies, the meaning of it might not be. Sometimes, the logic isn’t all you need to understand code: it may do something that seems pointless or nonsensical, leaving nearby labels and comments to explain it fully. However, a disassembly cannot reproduce the original labels and comments: those are lost. Likewise, a disassembly cannot reproduce code that was never in the ROM to begin with, such as disabled (or “commented-out”) prototype code. Because of this, a disassembly cannot truly replace source code.

However, small snippets of source code have surfaced over the years. The most obvious example I can think of is a fully-intact copy of a single source file (likely “EDIT.ASM”) in Sonic 2’s “Nick Arcade” prototype.

Sonic 2 “Nick Arcade” prototypes’s source file

Inside one of Sonic 2’s prototypes is a fully intact copy of the source file responsible for the game’s “edit mode” (commonly known as “debug mode”):

	addsym
	nolist
	include	"equ.lib"
	include	"macro.lib"
	list

	xref	colichgpat
	xref	ringpat,itempat,butapat,kanipat,hachipat,togepat
	xref	fishpat,fish2pat,mogurapat,shimapat2,jyamapat
	xref	musipat,sjumppat,kamerepat,arumapat,kagebpat,ballpat
	xref	firepat,fblockpat,signalpat,bobinpat,yoganpat,yogan2pat
	xref	usapat,yadopat,boxpat,bryukapat,daipat,break2pat,yogancpat
	xref	batpat,z5daipat,dai2pat,switch2pat,z4daipat
	xref	elevpat,pedalpat,steppat,funpat,sisoopat,hassyapat
	xref	brobopat,unipat,yaripat,udaipat,dai3pat,kazaripat,kassyapat
	xref	awapat,mizupat,boupat,benpat,fetamapat,mawarupat,hagurumapat
	xref	patapat,yukafpat,nokopat,dai4pat,doorpat,yukaepat,fire6pat
	xref	elepat,yukaipat,scolipat,imopat,savepat,bigringpat,btenpat
	xref	actionsub,actwkchk,frameout,playpat,dualmodesub
	xref	flicpat,usagipat,pengpat,azarpat,fbutapat,niwapat,risupat

	xref	kaitenpat,prodaipat,buranko0dpat
	xref	frntlitpat,gempat,wfallpat,pltfrmpat
	xref	takipat,banepat,dai00pat

	xref	redzpat,bfishpat,seahorsepat,horsepat
	xref	stegopat,wasppat,gatorpat,bbatpat,octpat,wfish2pat,snailpat

	xdef	edit

;------------------------------------------------------------------------------
edit:
	moveq	#0,d0
	move.b	editmode,d0
	move.w	edit_move_tbl(pc,d0.w),d1
	jmp	edit_move_tbl(pc,d1.w)
edit_move_tbl:
	dc.w	editinit-edit_move_tbl
	dc.w	editmove-edit_move_tbl
editinit:
	addq.b	#word,editmode
	move.w	scralim_up,editstack
	move.w	scralim_n_down,editstack2
	move.w	#$0000,scralim_up
	move.w	#$0720,scralim_n_down
	andi.w	#$07ff,playerwk+yposi
	andi.w	#$07ff,scra_v_posit
	andi.w	#$03ff,scrb_v_posit
	move.b	#0,patno(a0)
	move.b	#0,mstno(a0)
	cmpi.b	#spgamemd,gmmode
	bne.b	.jump0
*	move.b	#7-1,stageno
*	move.w	#$000,rotspd
*	move.w	#$000,rotdir
	moveq	#7-1,d0
	bra.b	.jump1
.jump0:
	moveq	#0,d0
	move.b	stageno,d0
.jump1:
	lea	edittbl,a2
	add.w	d0,d0
	adda.w	(a2,d0.w),a2
	move.w	(a2)+,d6
	cmp.b	editno,d6
	bhi.b	.jump
	move.b	#0,editno
.jump:
	bsr.w	editpatchg
	move.b	#12,edittimer
	move.b	#1,edittimer+1
editmove:
	moveq	#7-1,d0
	cmpi.b	#spgamemd,gmmode
	beq.b	.jump
	moveq	#0,d0
	move.b	stageno,d0
.jump:
	lea	edittbl,a2
	add.w	d0,d0
	adda.w	(a2,d0.w),a2
	move.w	(a2)+,d6
	bsr.w	editwalk
*	bsr.w	dirsprset
	jmp	actionsub
editwalk:
	moveq	#0,d4
	move.w	#1,d1
	move.b	swdata1+1,d4
	andi.w	#$0f,d4
	bne.b	.jump0
	move.b	swdata1,d0
	andi.w	#$0f,d0
	bne.b	.jump
	move.b	#12,edittimer
	move.b	#$0f,edittimer+1
	bra.w	.lend
.jump:
	subq.b	#1,edittimer
	bne.b	.jump1
	move.b	#1,edittimer
	addq.b	#1,edittimer+1
*	cmpi.b	#255,edittimer+1
	bne.b	.jump0
	move.b	#255,edittimer+1
.jump0:
	move.b	swdata1,d4
.jump1:
	moveq	#0,d1
	move.b	edittimer+1,d1
	addq.w	#1,d1
	swap	d1
	asr.l	#4,d1
	move.l	yposi(a0),d2
	move.l	xposi(a0),d3
	btst.l	#0,d4			*swdata+0
	beq.b	.jump2
	sub.l	d1,d2			*yposi
	bcc.b	.jump2
	moveq	#0,d2
.jump2:
	btst.l	#1,d4			*swdata+0
	beq.b	.jump3
	add.l	d1,d2			*yposi
	cmpi.l	#$7ff0000,d2
	bcs.b	.jump3
	move.l	#$7ff0000,d2
.jump3:
	btst.l	#2,d4			*swdata+0
	beq.b	.jump4
	sub.l	d1,d3			*xposi
	bcc.b	.jump4
	moveq	#0,d3
.jump4:
	btst.l	#3,d4			*swdata+0
	beq.b	.jump5
	add.l	d1,d3			*xposi
.jump5:
	move.l	d2,yposi(a0)
	move.l	d3,xposi(a0)
.lend:
	btst.b	#6,swdata1+0
	beq.b	.jump7
	btst.b	#5,swdata1+1		* c button check
	beq.b	.jump77
	subq.b	#1,editno
	bcc.b	.jump6
	add.b	d6,editno
	bra.b	.jump6
.jump77:
	btst.b	#6,swdata1+1
	beq.b	.jump7
	addq.b	#1,editno
	cmp.b	editno,d6
	bhi.b	.jump6
	move.b	#0,editno
.jump6:
	bra.w	editpatchg
.jump7:
	btst.b	#5,swdata1+1		* c button check
	beq.b	.jump8
	jsr	actwkchk
	bne.b	.worknai		;z=0:ok	z=1:no
	move.w	xposi(a0),xposi(a1)
	move.w	yposi(a0),yposi(a1)
	move.b	patbase(a0),actno(a1)
	move.b	actflg(a0),actflg(a1)
	move.b	actflg(a0),cddat(a1)
	andi.b	#$7f,cddat(a1)
	moveq	#0,d0
	move.b	editno,d0
	lsl.w	#3,d0
	move.b	4(a2,d0.w),userflag(a1)
	rts
.worknai:
.jump8:
	btst.b	#4,swdata1+1		* b button check
	beq.b	.jump9
	moveq	#0,d0
	move.w	d0,editmode
	move.l	#playpat,playerwk+patbase
	move.w	#$0780,playerwk+sproffset
	tst.w	dualmode		; dual mode check
	beq.b	.end
	move.w	#$0780/2,playerwk+sproffset
.end:
	move.b	d0,playerwk+mstno
	move.w	d0,xposi+2(a0)
	move.w	d0,yposi+2(a0)
	move.w	editstack,scralim_up
	move.w	editstack2,scralim_n_down
	cmpi.b	#spgamemd,gmmode
	bne.b	.jump9
*	clr.w	rotdir
*	move.w	#$040,rotspd
*	move.l	#playpat,playerwk+patbase
*	move.w	#$0780,playerwk+sproffset
	move.b	#02,playerwk+mstno
	bset.b	#cd_ball,playerwk+cddat
	bset.b	#cd_jump,playerwk+cddat
.jump9:
	rts
editpatchg:
	moveq	#0,d0
	move.b	editno,d0
	lsl.w	#3,d0
	move.l	0(a2,d0.w),patbase(a0)
	move.w	6(a2,d0.w),sproffset(a0)
	move.b	5(a2,d0.w),patno(a0)
*	move.b	4(a2,d0.w),userflag(a0)
	bsr.w	dualmodesub
	rts

dcblw	macro	\1,\2,\3,\4,\5
	dc.l	(\1)*$1000000+(\2)
	dc.w	(\4)+(\5)*$100
	dc.w	(\3)
	endm

edittbl:
	dc.w	edit1tbl-edittbl
	dc.w	edit2tbl-edittbl
	dc.w	edit3tbl-edittbl
	dc.w	edit4tbl-edittbl
	dc.w	edit5tbl-edittbl
	dc.w	edit6tbl-edittbl
	dc.w	edit7tbl-edittbl
edit1tbl:
	dc.w	14
	dcblw	ring_act,ringpat,$26bc,0,$00		;1:
	dcblw	item_act,itempat,$0680,0,$00		;2:
	dcblw	kani_act,kanipat,$0400,0,$00		;3:
	dcblw	hachi_act,hachipat,$0444,0,$00		;4:
	dcblw	fish_act,fishpat,$0470,0,$00		;5:
	dcblw	toge_act,togepat,$04a0,0,$00		;6:
	dcblw	shima_act,shimapat2,$4000,0,$00		;7:
	dcblw	jyama_act,jyamapat,$66c0,0,$00		;8:
	dcblw	musi_act,musipat,$04e0,0,$00		;9:
	dcblw	sjump_act,sjumppat,$04a8,0,$00		;10:
	dcblw	kamere_act,kamerepat,$249b,0,$00	;11:
	dcblw	kageb_act,kagebpat,$434c,0,$00		;12:
	dcblw	save_act,savepat,$26bc,0,$01		;13:
	dcblw	colichg_act,colichgpat,$26bc,0,$00	;14:

edit2tbl:
edit3tbl:
;zone0d
	dc.w	07
	dcblw	ring_act,ringpat,$26bc,0,$00		;01:
	dcblw	item_act,itempat,$0680,0,$00		;02:
	dcblw	sjump_act,sjumppat,$04a8,0,$00		;03:
	dcblw	colichg_act,colichgpat,$07bc,0,$00	;04:
	dcblw	kaiten_act,kaitenpat,$e000,0,$00	;05:
	dcblw	prodai_act,prodaipat,$e418,0,$00	;06:
	dcblw	buranko_act,buranko0dpat,$2418,0,$08	;07:

edit4tbl:
;zone00
	dc.w	18
	dcblw	ring_act,ringpat,$26bc,0,$00		;1:
	dcblw	item_act,itempat,$0680,0,$00		;2:
	dcblw	save_act,savepat,$047c,0,$01		;3:

	dcblw	colichg_act,colichgpat,$26bc,0,$00	;6:
	dcblw	taki_act,takipat,$23ae,0,$00		;7:
	dcblw	taki_act,takipat,$23ae,3,$02		;7:
	dcblw	shima_act,dai00pat,$4000,0,$01		;8:
	dcblw	shima_act,dai00pat,$4000,1,$0a		;8:
	dcblw	toge_act,togepat,$2434,0,$00		;10:
	dcblw	sisoo_act,sisoopat,$03ce,0,$00		;9:

	dcblw	sjump_act,banepat,$045c,0,$80		;3: banev
	dcblw	sjump_act,banepat,$0470,3,$90		;3: baneh
	dcblw	sjump_act,banepat,$045c,6,$a0		;3: banevr
	dcblw	sjump_act,banepat,$043c,7,$30		;3: bane45
	dcblw	sjump_act,banepat,$043c,10,$40		;3: bane45r

	dcblw	wasp_act,wasppat,$03e6,0,$00		;25   ""    ""
	dcblw	snail_act,snailpat,$0402,0,$00		;25   ""    ""
	dcblw	wfish2_act,wfish2pat,$041c,0,$00	;21   ""    ""



	dcblw	redz_act,redzpat,$0500,0,$00		;20: will change
	dcblw	bfish_act,bfishpat,$2530,0,$00		;21   ""    ""
	dcblw	seahorse_act,horsepat,$2570,0,$00	;22   ""    ""
	dcblw	skyhorse_act,horsepat,$2570,0,$00	;23   ""    ""
	dcblw	stego_act,stegopat,$23c4,0,$00		;24   ""    ""
	dcblw	wasp_act,wasppat,$032c,0,$00		;25   ""    ""
	dcblw	gator_act,gatorpat,$2300,0,$00		;26   ""    ""
	dcblw	bbat_act,bbatpat,$2350,0,$00		;27   ""    ""
	dcblw	oct_act,octpat,$238a,0,$00		;28   ""    ""

edit5tbl:
edit6tbl:
edit7tbl:
;zone08
	dc.w	15
	dcblw	ring_act,ringpat,$26bc,0,$00		;1:
	dcblw	item_act,itempat,$0680,0,$00		;2:

	dcblw	bgspr_act,frntlitpat,$e485,3,$21	;3:
	dcblw	wfall_act,wfallpat,$e415,4,$04		;4:
	dcblw	break_act,pltfrmpat,$4475,0,$00		;5:
	dcblw	colichg_act,colichgpat,$26bc,0,$00	;6:

	dcblw	redz_act,redzpat,$0500,0,$00		;20: will change
	dcblw	bfish_act,bfishpat,$2530,0,$00		;21   ""    ""
	dcblw	seahorse_act,horsepat,$2570,0,$00	;22   ""    ""
	dcblw	skyhorse_act,horsepat,$2570,0,$00	;23   ""    ""
	dcblw	stego_act,stegopat,$23c4,0,$00		;24   ""    ""
	dcblw	wasp_act,wasppat,$032c,0,$00		;25   ""    ""
	dcblw	gator_act,gatorpat,$2300,0,$00		;26   ""    ""
	dcblw	bbat_act,bbatpat,$2350,0,$00		;27   ""    ""
	dcblw	oct_act,octpat,$238a,0,$00		;28   ""    ""

;------------------------------------------------------------------------------
	align
;------------------------------------------------------------------------------
	end


edit2tbl:
	dc.w	25
	dcblw	ring_act,ringpat,$26bc,0,$00		;1:
	dcblw	item_act,itempat,$0680,0,$00		;2:
	dcblw	sjump_act,sjumppat,$0523,0,$00		;3:
	dcblw	fish2_act,fish2pat,$2486,0,$08		;4:
	dcblw	mogura_act,mogurapat,$84a6,2,$00	;5:
	dcblw	yari_act,yaripat,$03cc,0,$00		;6:
	dcblw	yari_act,yaripat,$03cc,3,$02		;7:
	dcblw	box_act,boxpat,$43de,0,$00		;8:
	dcblw	switch2_act,switch2pat,$0513,0,$00	;9:
	dcblw	toge_act,togepat,$051b,0,$00		;10:
	dcblw	dai_act,udaipat,$43bc,0,$04		;11:
	dcblw	dai3_act,dai3pat,$43e6,0,$01		;12:
	dcblw	dai3_act,dai3pat,$43e6,1,$13		;13:
	dcblw	dai3_act,dai3pat,$43e6,0,$05		;14:
	dcblw	kazari_act,kazaripat,$443e,0,$00	;15:
	dcblw	dai3_act,dai3pat,$43e6,2,$27		;16:
	dcblw	dai3_act,dai3pat,$43e6,3,$30		;17:
	dcblw	kassya_act,kassyapat,$03f6,0,$7f	;18:
	dcblw	uni_act,unipat,$0467,0,$00		;19:
	dcblw	awa_act,awapat,$8348,19,$84		;20:
	dcblw	mizu_act,mizupat,$c259,2,$02		;21:
	dcblw	mizu_act,mizupat,$c259,9,$09		;22:
	dcblw	bou_act,boupat,$43de,0,$00		;23:
	dcblw	ben_act,benpat,$4328,0,$02		;24:
	dcblw	save_act,savepat,$26bc,0,$01		;25:

*	dcblw	dai4_act,dai4pat,$41f0,4,$80		;25:
edit3tbl:
	dc.w	18
	dcblw	ring_act,ringpat,$26bc,0,$00		;1:
	dcblw	item_act,itempat,$0680,0,$00		;2:
	dcblw	hachi_act,hachipat,$0444,0,$00		;3:
	dcblw	toge_act,togepat,$051b,0,$00		;4:
	dcblw	sjump_act,sjumppat,$0523,0,$00		;5:
	dcblw	mfire_act,firepat,$0345,0,$00		;6:
	dcblw	fblock_act,fblockpat,$4000,0,$00	;7:
	dcblw	myogan_act,yoganpat,$63a8,0,$00		;8:
	dcblw	yogan2_act,yogan2pat,$63a8,0,$00	;9:
	dcblw	box_act,boxpat,$42b8,0,$00		;10:
	dcblw	yado_act,yadopat,$247b,0,$00		;11:
	dcblw	bryuka_act,bryukapat,$42b8,0,$00	;12:
	dcblw	dai_act,daipat,$02b8,0,$00		;13:
	dcblw	break2_act,break2pat,$62b8,0,$00	;14:
	dcblw	yoganc_act,yogancpat,$8680,0,$00	;15:
	dcblw	bat_act,batpat,$04b8,0,$00		;16:
	dcblw	imo_act,imopat,$24ff,0,$00		;17:
	dcblw	save_act,savepat,$26bc,0,$01		;18:
edit4tbl:
	dc.w	15
	dcblw	ring_act,ringpat,$26bc,0,$00		;1:
	dcblw	item_act,itempat,$0680,0,$00		;2:
	dcblw	elev_act,elevpat,$4000,0,$00		;3:
	dcblw	break2_act,break2pat,$44e0,2,$00	;4:
	dcblw	shima_act,z4daipat,$4000,0,$00		;5:
	dcblw	pedal_act,pedalpat,$4000,0,$00		;6:
	dcblw	step_act,steppat,$4000,0,$00		;7:
	dcblw	fun_act,funpat,$43a0,0,$00		;8:
	dcblw	sisoo_act,sisoopat,$0374,0,$00		;9:
	dcblw	sjump_act,sjumppat,$0523,0,$00		;10:
	dcblw	mfire_act,firepat,$0480,0,$00		;11:
	dcblw	bgspr_act,hassyapat,$44d8,0,$00		;12:
	dcblw	brobo_act,brobopat,$0400,0,$00		;13:
	dcblw	uni_act,unipat,$2429,0,$00		;14:
	dcblw	save_act,savepat,$26bc,0,$01		;15:
edit5tbl:
	dc.w	15
	dcblw	ring_act,ringpat,$26bc,0,$00		;1:
	dcblw	item_act,itempat,$0680,0,$00		;2:
	dcblw	toge_act,togepat,$051b,0,$00		;3:
	dcblw	sjump_act,sjumppat,$0523,0,$00		;4:
	dcblw	aruma_act,arumapat,$04b8,0,$00		;5:
	dcblw	signal_act,signalpat,$0000,0,$00	;6:
	dcblw	bobin_act,bobinpat,$0380,0,$00		;7:
	dcblw	kani_act,kanipat,$0400,0,$00		;8:
	dcblw	hachi_act,hachipat,$0444,0,$00		;9:
	dcblw	yado_act,yadopat,$247b,0,$00		;10:
	dcblw	shima_act,z5daipat,$4000,0,$00		;11:
	dcblw	dai2_act,dai2pat,$4000,0,$00		;12:
	dcblw	switch2_act,switch2pat,$0513,0,$00	;13:
	dcblw	imo_act,imopat,$24ff,0,$00		;14:
	dcblw	save_act,savepat,$26bc,0,$01		;15:
edit6tbl:
	dc.w	29
	dcblw	ring_act,ringpat,$26bc,0,$00		;1:
	dcblw	item_act,itempat,$0680,0,$00		;2:
	dcblw	brobo_act,brobopat,$0400,0,$00		;3:
	dcblw	uni_act,unipat,$0429,0,$00		;4:
	dcblw	imo_act,imopat,$22b0,0,$00		;5:
	dcblw	buranko_act,fetamapat,$4391,2,$07	;6:
	dcblw	haguruma_act,hagurumapat,$c344,00,$e0	;7:
	dcblw	dai_act,daipat,$22c0,2,$28		;8:
	dcblw	switch2_act,switch2pat,$0513,0,$00	;9:
	dcblw	pata_act,patapat,$4492,0,$03		;10:
	dcblw	pata_act,yukafpat,$04df,0,$83		;11:
	dcblw	noko_act,nokopat,$43b5,0,$02		;12:
	dcblw	break2_act,break2pat,$43f5,0,$00	;13:
	dcblw	dai_act,daipat,$4460,3,$39		;14:
	dcblw	dai4_act,dai4pat,$22c0,0,$00		;15:
	dcblw	door_act,doorpat,$42e8,0,$00		;16:
	dcblw	dai4_act,dai4pat,$22c0,1,$13		;17:
	dcblw	noko_act,nokopat,$43b5,0,$01		;18:
	dcblw	dai4_act,dai4pat,$22c0,1,$24		;19:
	dcblw	noko_act,nokopat,$43b5,2,$04		;20:
	dcblw	dai4_act,dai4pat,$22c0,1,$34		;21:
	dcblw	yukae_act,yukaepat,$44c3,0,$00		;22:
	dcblw	fire6_act,fire6pat,$83d9,0,$64		;23:
	dcblw	fire6_act,fire6pat,$83d9,11,$64		;24:
	dcblw	ele_act,elepat,$047e,0,$04		;25:
	dcblw	yukai_act,yukaipat,$42f0,0,$00		;26:
	dcblw	scoli_act,scolipat,$8680,0,$11		;27:
	dcblw	buta_act,butapat,$2302,0,$04		;28:
	dcblw	save_act,savepat,$26bc,0,$01		;29:

*	dcblw	ring_act,playpat,$0780,50,$00		;28:
*	dcblw	usa_act,usapat,$0448,0,$00		;5:
*	dcblw	mawaru_act,mawarupat,$4348,16,$00	;7:

edit7tbl:
	dc.w	02	*13
	dcblw	ring_act,ringpat,$26bc,0,$00		;1:
	dcblw	ring_act,ringpat,$26bc,8,$00		;2:
*	dcblw	bobin_act,bobinpat,$0380,0,$00		;2:
*	dcblw	usagi_act,flicpat,$05a0,0,$0a		;3
*	dcblw	usagi_act,flicpat,$05a0,0,$0b		;4
*	dcblw	usagi_act,flicpat,$05a0,0,$0c		;5
*	dcblw	usagi_act,usagipat,$0553,0,$0d		;6
*	dcblw	usagi_act,usagipat,$0553,0,$0e		;7
*	dcblw	usagi_act,pengpat,$0573,0,$0f		;8
*	dcblw	usagi_act,pengpat,$0573,0,$10		;9
*	dcblw	usagi_act,azarpat,$0585,0,$11		;10
*	dcblw	usagi_act,fbutapat,$0593,0,$12		;11
*	dcblw	usagi_act,niwapat,$0565,0,$13		;12
*	dcblw	usagi_act,risupat,$05b3,0,$14		;13

This source code contains everything: code, comments, labels, and even disabled prototype code that we never knew even existed before. Notably, these labels give the internal names of enemies and objects used throughout the game, while the comments elaborate on the intended level order. The source code snippet even gives some insight into the exact assembler used to build the source code into a ROM.

That’s not the only useful data in this prototype however…

Sonic 2 “Nick Arcade” prototype’s symbol list

Starting at ROM address 0x418A8 is what appears to be a partial copy of the assembler’s symbol table. Oddly, it doesn’t appear to match the ROM, as labels that appear in the above source code are recorded in this symbol table at different addresses to where they are in the actual ROM. It can be assumed, based on this, that this symbol table is leftover from a previous execution of the assembler, rather than the one that produced the ROM that we have.

From roughly 0x418A8 to 0x47B14, the symbol table follows this format:

  • The first four bytes denote the length of the symbol identifier divided by four, and rounded up. That is to say, it denotes the number of longwords needed to hold the identifier. Notably, this length is stored in big-endian format.
  • The following longwords contain the label. Unused bytes are set to 00.
  • The following four bytes contain the value that is assigned to that symbol. This value is also in big-endian.

Here’s an example:

00 00 00 02 65 64 69 74 69 6E 69 74 00 01 AB 12

The first four bytes are 00000002, indicating an identifier that is 2 longwords (or 8 bytes) long.

The next eight bytes are 65646974696E6974. When interpreted as ASCII, it is “editinit”, which is one of the labels seen in the above source code.

The following four bytes are 0001AB12, which is the ROM address that was assigned to this label when the assembler that produced this symbol table was ran. In the ROM we have, however, “editinit” is located at 0001BABE.

Past 0x47B14, the format of the symbol table changes. Unfortunately, the entries don’t seem to specify what each symbol’s value is, instead containing what appears to be three pointers. This format continues to 0x50000, when actual game data resumes.

The symbol table begins again at 0x50A9C, but in another format that seems to just contain identifiers and pointers.

[EDIT: I’m a dummy: apparently you *can* extract meaningful data from the later parts of the symbol table, as someone else was able to and produce this massive list.]

While the later symbols don’t contain any useful information, they are still useful for determining the original names of various bits of code. For example, the label ‘random’ is certainly what we’ve been calling ‘RandomNumber’ in the Sonic 2 disassembly. Additionally, ‘bgmset’ and ‘soundset’ are more than likely ‘PlayMusic’ and ‘PlaySound’. What’s notable about that last one is that it answers the question of whether ‘PlayMusic’ really was intended to play music, since sometimes it’s used to play sounds instead, which caused people to question whether it actually was a dedicated music-playing function, or simply a generic ‘play something‘ function.

One might be wondering why there’s an intact source file and a partial symbol table in the middle of a ROM. Well, the space that this data occupies is what would normally be padding: towards the end of the ROM, the game’s data is spaced-out for some reason, leaving big gaps. My theory is that whatever tool the devs were using to produce the final ROM simply malloc‘d a huge buffer, and pasted data where it was needed, never initialising the unused space. This resulted in the unused space containing garbage data, leftover in memory from other programs.

This isn’t the only time that symbol data appears in a Sonic game:

Sonic & Knuckles Collection’s symbol list

Unfortunately, I can’t comment too much on this due to not having a copy of Sonic & Knuckles Collection on hand. However, this game contains another instance of symbol data, inside its main EXE.

For the most complete set of symbols, however, one need not look further than…

Sonic CD’s unstripped ELF files

Sonic Gems is a compilation of various emulated classic Sonic titles. Its version of Sonic CD, however, is not emulated: rather, it is a port of Sonic CD’s PC port.

One might be wondering how a game written in Motorola 68000 assembly could have ever been ported to anything with a different CPU architecture. The answer to that is that its code was machine-converted from assembly to C. Notably, this process preserved the original labels, and possibly even the original comments.

Within the files of Sonic Gems, you can find a series of ELF files, which parallel the DLL files of Sonic CD’s original PC port. What’s special about these ELF files is that they are unstripped, containing huge amounts of symbol data.

When loaded into something like IDA or Ghidra, this symbol data is taken advantage of while disassembling or decompiling the code. This reverse-engineered code will have its original labels intact, giving a great look into the original source code. Unfortunately, the code you’ll be looking at is the disassembled/decompiled output of PowerPC/MIPS assembly which was generated from C which was converted from 68k assembly. That is to say, it’s hideous and practically unreadable. Still, if you’re able to draw parallels between this code and the original 68k assembly, you effectively have a pre-labelled disassembly.

These ELF files also contain some debug data, such as the paths of various source files. I was able to extract a bunch of them back in 2018:

C:\project\GEMS\application\SonicCD\src\ps2\main\DLLMAIN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\ACTION.C
C:\project\GEMS\application\SonicCD\src\ps2\main\ACTSET.C
C:\project\GEMS\application\SonicCD\src\ps2\main\DAI_K.C
C:\project\GEMS\application\SonicCD\src\ps2\main\DAI_RD1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\DIRCOL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\DUMMY.C
C:\project\GEMS\application\SonicCD\src\ps2\main\EDIT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\EMIE1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\EMIE1CG.C
C:\project\GEMS\application\SonicCD\src\ps2\main\ENEMY.C
C:\project\GEMS\application\SonicCD\src\ps2\main\EQU.C
C:\project\GEMS\application\SonicCD\src\ps2\main\ET1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\ETC.C
C:\project\GEMS\application\SonicCD\src\ps2\main\FCOL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\GAME.C
C:\project\GEMS\application\SonicCD\src\ps2\main\GOAL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\IO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\ITEM.C
C:\project\GEMS\application\SonicCD\src\ps2\main\PLAYER.C
C:\project\GEMS\application\SonicCD\src\ps2\main\PLAYPAT1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\PLAYSP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\PLAYSUB.C
C:\project\GEMS\application\SonicCD\src\ps2\main\PLCHG.C
C:\project\GEMS\application\SonicCD\src\ps2\main\RIDECHK.C
C:\project\GEMS\application\SonicCD\src\ps2\main\RING.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SCORE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SCRCHK.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPRING.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SUICIDE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TAKI.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TBL0.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TBL1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TREE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\ZONE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\ZONETBL1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\ACT11A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\COL1A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV11A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\EDTBL11A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KOWASI1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KUZUR11A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KUZURE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR11A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SHOOT1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SIKAKE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z11ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\BLOCK.C
C:\project\GEMS\application\SonicCD\src\ps2\main\LOADER2.C
C:\project\GEMS\application\SonicCD\src\ps2\main\BMP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\COLI1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z11ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SAVE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\FRIEND1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\MOVIE1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\ACT11B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\COL1B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV11B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\EDTBL11B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KUZUR11B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR11B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z11BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV11C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR11C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z11CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\EDTBL11C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KUZUR11C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\COL1C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV11D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\EDTBL11D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KUZUR11D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z11DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\COL1D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR11D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\ACT12A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV12A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\EDTBL12A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KUZUR12A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\BRANKO1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR12A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z12ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z12ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV12B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\EDTBL12B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KUZUR12B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR12B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z12BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV12C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\EDTBL12C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KUZUR12C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR12C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z12CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV12D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\EDTBL12D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\KUZUR12D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR12D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z12DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\ACT13C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV13C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\EDTBL13C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR13C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z13ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z13CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\BOSS_1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\DEV13D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\SCR13D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R1\Z13DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\BANPA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\BOBIN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\BOBINB.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\COLI3.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\EDTBL31A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\ET3.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\FRIEND3.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\GA3.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\PLAYSP3.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\POCKET.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\TOGEBL3A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\TRAP_R3.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z31ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\MIRACLE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\COL3A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR31A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z31ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV31A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\ACT31A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\MECASNC.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\MSNCCG.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\TENTOU.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\KAMA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\PLPAT6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\MOVIE3.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\ZONETBL3.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\ACT31B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\COL3B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV31B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\EDTBL31B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z31BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR31B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\TOGEBL3B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\COL3C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV31C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\EDTBL31C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z31CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR31C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\TOGEBL3D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\COL3D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV31D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\EDTBL31D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z31DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR31D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV32A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\EDTBL32A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z32ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z32ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR32A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\ACT32A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV32B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\EDTBL32B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z32BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR32B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV32C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\EDTBL32C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z32CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR32C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV32D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\EDTBL32D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z32DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR32D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\ACT33C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV33C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\EDTBL33C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z33CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z33ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SIKAKE33.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\LIGHT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\STOPPER.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\BOSS_3.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\GATE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\ROLLPLAT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR33C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\DEV33D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\Z33DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R3\SCR33D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ACT41A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\COL4A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\COLI4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV41A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\EDTBL41A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ET4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCR41A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCRCHK4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SW4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SWBLK4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z41ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z41ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ZONETBL4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\GAME4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\PLAYSUB4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\AWA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\HARID4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\TOBIRA4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\HARIR4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WALLS.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WTBL41A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\FUNSUI4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WALL1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCREW_A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ESCAL4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\BRUNKO4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\TONBO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\AMENBO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\TAGAMEB4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\YAGO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\FRIEND4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\TEKKYU.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\PLAYPAT4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\KUZURE4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\LRBLK4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\COL4B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCR41B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ACT41B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\EDTBL41B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV41B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\MOVIE4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\RBLK4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z41BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\UDBLK4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WTBL41B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ACT41C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\COL4C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV41C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\EDTBL41C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCREW_C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\TEKKYU4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WTBL41C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\RENKETU4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCR41C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z41CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SWGUN4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\COL4D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCR41D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV41D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z41DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCR42A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV42A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\EDTBL42A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z42ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WTBL42A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\TOBIRAS4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\OSUMIZU.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WALL4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ACT42A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\UKIDAI.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z42ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\TEKKYU1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WALL42.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ACT42B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\EDTBL42B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WTBL42B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV42B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z42BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ACT42C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV42C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\EDTBL42C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WTBL42C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z42CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\BANEIWA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DAID4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCR42C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\KOWASI4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV42D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z42DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\ACT43C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV43C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\EDTBL43C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCR43C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z43ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\BOSS_4.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\BOSS_4_2.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z43CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\WTBL43.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\DEV43D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\SCR43D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R4\Z43DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\ACT51A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\COL5A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\COLI5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV51A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\EDTBL5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\GAME5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\PLAYSP5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR51A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z51ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z51ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\ZONETBL5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\BELTSW5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\MOVIE5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\FRIEND5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\KEMUSI.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SHOOT5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\KUMO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\IWA5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\IWA5WAVE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DENDEN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\HASHI5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\HARI5F.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\HARIR5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\IWA5ROLL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\KOWASI5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\KUZURE5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SASORI.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\HASIRA5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\ET5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\BURANKO5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DAI_RD5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\COL5B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV51B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR51B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z51BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\COL5C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV51C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR51C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z51CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\COL5D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV51D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR51D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z51DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV52A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR52A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z52ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z52ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV52B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR52B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z52BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV52C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR52C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z52CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV52D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR52D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z52DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\ACT53.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV53C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR53C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z53ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z53CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\BOSS_5.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\EDTBL53.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\DEV53D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\SCR53D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R5\Z53DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\ACT61A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\COLI6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\EDTBL61A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SW6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\TOBIRA6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\TRAP_R6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\KDAI6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\BEEM6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\BLOCK6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\COL6A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DAIR6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\EGG6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\ET6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\FRIEND6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\HACHI6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\MINOMUSI.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\MOVIE6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\PISTON6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR61A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SEMI.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SHOOT6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\TOBIDAI6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\UDBLK6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z61ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z61ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV61A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\BATTA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SEESAW6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\PLAYER6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\PLCHG6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\ZONETBL6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\COL6B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR61B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV61B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\EDTBL61B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z61BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\COL6C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV61C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR61C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z61CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\EDTBL61C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\COL6D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV61D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR61D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z61DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\EDTBL62A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z62ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z62ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\TOGEBL6A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV62A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR62A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\ACT62A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV62B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\EDTBL62B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR62B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z62BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\TOGEBL6B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV62C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\EDTBL62C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z62CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\TOGEBL6D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR62C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV62D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\EDTBL62D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR62D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z62DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\ACT63.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV63C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\EDTBL63C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR63C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z63CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z63ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\BOSS_6.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\DEV63D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\SCR63D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R6\Z63DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\ACT71A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\CHGBAN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\CHGWALL7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\COL7A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\COLI7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV71A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\EDTBL7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\PLAYSP7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR71A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z71ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z71ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\ZONETBL7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\WALL7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\TEKKYU7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\TEKKYU7J.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SLIGHT7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\FRIEND7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\BRANKO7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\ET7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\KANABUN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DANGO7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\MOVIE7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\KABASIRA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\HOTARU7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\COL7B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR71B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV71B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z71BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\COL7C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z71CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR71C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV71C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\COL7D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV71D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR71D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z71DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR72A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z72ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV72A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z72ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV72B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR72B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z72BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV72C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR72C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z72CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV72D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR72D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z72DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR73C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\ACT73.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\EDTBL73.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z73ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\GAITOU73.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\WALL73.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\EMIE7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z73CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV73C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\HARI73.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\EMIE7CG.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\BOSS_7.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\SCR73D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\DEV73D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R7\Z73DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\PLPAT8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\ACT81A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\COL8A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\COLI8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV81A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\EDTBL81.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR81A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z81ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z81ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\ZONETBL8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\TRAP_R81.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\KABUTO8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\KOMA8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SW8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\OKUSIESO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\TOBIRA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\KONBEA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCARAB.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\KUZURE8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\NOKOGIRI.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\PROPERA8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DAI8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\HACHI8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\ET8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\FRIEND8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\PISTON.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\HAGURUMA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCRCHK8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\COL8B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR81B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z81BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV81B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\COL8C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR81C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z81CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV81C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\COL8D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR81D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z81DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV81D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\PLAYSP8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\ACT82A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\EDTBL82.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\TRAP_R82.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z82ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z82ATBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\BEAM.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SHUT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV82A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SHOOT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DANGO8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\LEGMECA8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\BIGBOM8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR82A.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\JETTOGE8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\PIPE8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV82B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR82B.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z82BTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV82C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR82C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z82CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV82D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR82D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z82DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\EDTBL83.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR83C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z83ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z83CTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\BOSS_8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV83C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\ACT83.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\EMIE8.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\KONBEA83.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\TRAP_R83.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\HOTA8C.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\DEV83D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\SCR83D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\Z83DTBL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\R8\HOTA8D.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\ACT_S.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\BACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\BMP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\COLI_S.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\COMMON.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\DATA_S1.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\ENS.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\ETC_M.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\ETC_S.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\FADEIN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\GAME.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\KAITEN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\PLS.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\SPM_EQU.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\SPS_EQU.C
C:\project\GEMS\application\SonicCD\src\ps2\main\SPECIAL\SIN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\AVIGOOD\AVIGODDO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\AVIGOOD\AVIGODEN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\AVIOPEN\AVIOPNDO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\AVIOPEN\AVIOPNEN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\BESTTIME\BESENTRY.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\BESTTIME\BESTBMP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\BESTTIME\BESTDO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\BESTTIME\BESTGRID.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\BESTTIME\BESTITEM.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\BESTTIME\BESTPALT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\BESTTIME\BESTSPRT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\COMMON\HMX_OEEACTL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\OPENING\OPNBMP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\OPENING\OPNDO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\OPENING\OPNENTRY.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\OPENING\OPNGRID.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\OPENING\OPNPALT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\ACTM.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\ASCIISET.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\ASCMAP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\CHAMOV.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\DATA_M.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\ETC.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\FLASH.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\KAITEN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\LPLMAIN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\PALET.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\PLM.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\PLS.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\SPM_EQU.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\PLANET\SPS_EQU.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SAVEDATA\SVDBMP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SAVEDATA\SVDDO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SAVEDATA\SVDEDIT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SAVEDATA\SVDENTRY.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SAVEDATA\SVDFILE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SAVEDATA\SVDGRID.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SAVEDATA\SVDMENU.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SAVEDATA\SVDPALT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SAVEDATA\SVDSPRT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SOUNDTST\SNDENTRY.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SOUNDTST\SNDBMP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SOUNDTST\SNDDO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SOUNDTST\SNDGRID.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SOUNDTST\SNDPALT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\SOUNDTST\SNDSPRT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\STAGETST\STGENTRY.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\STAGETST\STGBMP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\STAGETST\STGDO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\STAGETST\STGGRID.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\STAGETST\STGITEM.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\STAGETST\STGPALT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\STAGETST\STGSPRT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\TA\TA.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\TA\FADEIN.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\TA\TACOLOR.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\TA\TAEACTRL.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\ACT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\BM_M.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\CHRSET.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\DATA_SP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\ETC_M.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\FADE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\GAME.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\IO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\SPM.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\THANKS\SPRMOVE.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\VISUALMD\VMDBMP.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\VISUALMD\VMDDO.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\VISUALMD\VMDENTRY.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\VISUALMD\VMDGRID.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\VISUALMD\VMDPALT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\TITLE\VISUALMD\VMDSPRT.C
C:\project\GEMS\application\SonicCD\src\ps2\main\WARP\WARP.C

This file list gives you a look at the layout of Sonic CD’s source code. Notably, you can see ‘EDIT.C’, which is very likely the file that originally contained the code seen in Sonic 2’s “Nick Arcade” prototype.

Sonic Gems isn’t the only game to contain unstripped executables like this. For instance, The Legend of Zelda Collector’s Edition contains an unstripped executable for its Nintendo 64 emulator (or “simulator”, as it calls itself. Yeah, sure, Nintendo).

Yuji Naka’s video

A few years back, Yuji Naka found some old footage of him working on Sonic 1 back in February 1990. In this video, he scrolls through a portion of the game’s source code, giving us yet another sighting of authentic source code:

Sonic hackers looked into this footage, and were able to produce a transcription of the source code, ‘FCOL3.ASM’:

*cd_walk            equ 0*2
*cd_jump            equ 1*2
*cd_up              equ 2*2
*cd_down            equ 3*2
 
updotmax        equ -4
downdotmax      equ 4
jumpdotmax      equ 8
 
;sprvo          equ $1c
;sprho          equ $1d
;sprvs          equ $1e
;sprhs          equ $1f
;------------------------------------------------------------------------
;   foot colition
;
;col:
    move.w  yposi(a0),d4
    move.b  sprvo(a0),d1
    add.b   sprvs(a0),d1
    ext.w   d1
    add.w   d1,d4
    move.w  xposi(a0),d5
    move.b  sprho(a0),d1
    ext.w   d1
    add.w   d1,d5
    move.w  xspeed(a0),scrhithz
?loop:
    move.w  d4,d2
    move.w  d5,d3
    moveq   #0,d0
    move.b  sprhs(a0),d0
    tst.w   xspeed(a0)
    bpl.b   ?jump
*   btst.b  #cd_right,cddot(a0)
*   beq.b   ?jump
    neg.w   d0
?jump:
    add.w   d0,d3
    bsr.w   scdchk
    tst.w   d1
    beq.b   ?end
    bpl.b   ?down
?up:
    cmp.w   #updotmax,d1
    blt.b   ?nomove
    add.w   d1,yposi(a0)
?end:
    rts
?nomove:
    bsr.b   hoseihsub
    bra.b   ?loop
    move.w  #0,xspeed(a0)
    rts
?down:
    move.w  d1,d6
    swap    d6
?down2:
    move.w  d4,d2
    move.w  d6,d3
    moveq   #0,d0
    move.b  sprhs(a0),d0
    tst.w   xspeed(a0)
    bpl.b   ?jump3
*   btst    #cd_left,cddot(a0)
*   bne.s   ?jump3
    neg.w   d0
?jump3:
    add.w   d0,d3
    bsr.w   scdchk
    tst.w   d1
    beq.b   ?end2
    bpl.b   ?down1
?up2:
    cmp.w   #updotmax,d1
    blt.b   ?nomove2

Also visible in the footage was a brief glance of a partial file list:

                        ENEMY.ASM
            FCOL.ASM    FCOL.BAK
FCOL3.ASM   GAME.ASM    INT.ASM
LOGO.ASM    MACRO.LIB   MAIN.ASM
ML.EVT      ML.S28      OBJ¥
S           S.CMD       SCORE.ASM
SOUND¥      TR¥

Some of these filenames may seem familiar, as they survived into Sonic CD. Note that the items ending with the Yen symbol are actually directories, and not files.

This footage is also noteworthy for showing that Sonic 1 was developed on DOS. This is something that I’ll come back to later.

Patent US5411272A

A tiny snippet of source code can be found in a patent that Sega filed around the time of Sonic 2’s release. It contains two tables that are responsible for Emerald Hill Zone’s spiral loops. While it’s not all that insightful, it does give you a look at the original formatting of these tables, and how the data was grouped.

J2ME

The developers of the J2ME version of Sonic 1 appear to have had access to Sonic 1’s source code, or at least its assets: this is evident through how its filenames reflect labels found in the original source code. For instance, ‘scdtblwk.scd’, matches the label ‘scdtblwk’ that is found in Sonic 2’s “Nick Arcade” prototype.

Most notably, however, some of the files in this version appear to be ‘raw’, unprocessed versions of the data found in the ROM of the Mega Drive version. For example, there’s an unused function in Sonic 2 that converts the collision data from a previously-unknown format to the format that is seen in the ROM itself, and it was discovered that the collision files in the J2ME version are in this mysterious format.

There are other file format oddities as well, such as block priority being stored in its own file, instead of being embedded in the chunk data. It’s possible that this too is how the data was originally formatted, before being converted into its final form for inclusion in the ROM.

Assembler

So we have all this information about the source code itself, and the game’s assets, but what about the development environment? Well, for starters, from the footage we saw earlier, we can tell that Sonic 1 was developed on DOS computers.

According to LazloPsylus, Sonic 1 was very evidently assembled with the 2500AD assembler, X68k, based partly on the fact that the syntax of the code snippet seen in Yuji Naka’s footage is unusual, and unlikely to have built with any other assembler.

However, it does not appear that Sonic 2 was assembled with X68k, as the “Nick Arcade” source code snippet uses a different syntax. It’s plausible that Sonic 2 was assembled with SN 68k, also known as asm68k, however the presence of an ‘addsym’ directive, which is not supported by asm68k, calls this into question.

Further complicating matters is something that I discovered while writing this very blog post: the “Nick Arcade” symbol table uses big-endian integers. DOS PCs are x86, and store their integers in little-endian. This suggests that Sonic 2 wasn’t assembled on a DOS PC at all, unlike Sonic 1, and rather that it was assembled on a big-endian platform like a 68k-powered Macintosh. Another indication of this is that the “Nick Arcade” source snippet uses Unix-style line endings (0x0A), instead of DOS-style line endings (0x0D 0x0A).

There’s some evidence to suggest that Sonic 2 used a different assembler between REV01 and REV02: cross-object-file function calls behave differently, ‘dc.b’ directives are automatically padded, some ‘addi’ and ‘subi’ instructions were optimised to ‘addq’ and ‘subq’ instructions, and some ‘lea’ instructions were unoptimised from PC-relative addressing to absolute long addressing. The cross-object-file behaviour of REV00 and REV01 matches that of the “Nick Arcade” prototype, so it can be assumed that Sonic 2 used the same assembler throughout development up until REV02.

Something I just discovered while writing this blog post is that Sonic 1’s machine code is similar to Sonic 2 REV02: in code that is shared between Sonic 1 and Sonic 2, the same ‘addi’ and ‘subi’ instructions are ‘addq’ and ‘subq’ in both Sonic 1 and Sonic 2 REV02. Likewise, the same ‘lea’ instructions are unoptimised. With this in mind, it would appear that Sonic 2 was migrated back to X68k during the development of REV02, and it would continue to use this assembler as it was used to develop Sonic 3, Knuckles in Sonic 2, and the Mega Play arcade version of Sonic 2.

Sonic 2’s source file boundaries

While we don’t have an exact list of Sonic 2’s source files, we do have a way of determining where its original source files began and ended: the assembler used by Sonic 2 before REV02 would resolve cross-object-file function calls in a clunky way, basically proxying them to little ‘JmpTo’ functions. To put it literally, a ‘bsr’ instruction that referenced a label which was outside of the current object file would instead branch to a single-instruction function that was appended to the end of the object file, which would itself jump to the ‘bsr’ instruction’s original destination. Because these ‘JmpTo’ functions are inserted at the end of the object file, they can be used to tell where an object file (and thus a source file) ended.

From this, we can determine things like that the ring object (object 0x25), the scattered ring object (object 0x37), the big Special Stage ring object leftover from Sonic 1, and the Casino Night Zone ring prize object (object 0xDC) were all stored in the same source file.

Sonic 2 “Simon Wai” prototype’s unassembled Kosinski file

Like the “Nick Arcade” prototype before it, Sonic 2’s “Simon Wai” prototype includes another source code file. This one, however, is not entirely complete, but it contains more than enough useful information:

; 圧縮前 $8000  圧縮後 $2c00  圧縮率 34.4%  セル数 1024
	dc.b	$1d,$7f,$00,$ff,$f8,$7e,$04,$01,$fb,$02,$00,$03,$04,$05,$04,$80
	dc.b	$ff,$fa,$ff,$07,$54,$0b,$54,$0a,$50,$0c,$50,$ff,$8f,$0d,$54,$0f
	dc.b	$54,$0e,$50,$10,$50,$11,$52,$19,$52,$18,$ff,$ff,$fc,$1a,$52,$1b
	dc.b	$51,$8a,$51,$8b,$52,$1c,$02,$1e,$02,$1f,$02,$2a,$9f,$ff,$02,$2b
	dc.b	$01,$8c,$01,$8d,$f4,$1d,$02,$2c,$02,$2d,$01,$ff,$f4,$88,$01,$89
	dc.b	$01,$8e,$01,$8f,$02,$1a,$e4,$02,$2e,$02,$46,$c0,$2f,$e0,$ff,$1b
	dc.b	$ec,$fc,$dc,$00,$70,$02,$c4,$d4,$ee,$18,$02,$19,$01,$3f,$f6,$fd
	dc.b	$e4,$e6,$04,$2e,$04,$2d,$04,$9d,$fe,$77,$bd,$00,$be,$f9,$50,$31
	dc.b	$54,$30,$54,$2f,$7f,$f8,$56,$03,$56,$02,$56,$01,$05,$ff,$f0,$52
	dc.b	$1d,$52,$1e,$e1,$9f,$52,$1f,$fc,$50,$3e,$54,$3c,$56,$04,$54,$36
	dc.b	$09,$e0,$c4,$9c,$f8,$ea,$1a,$50,$48,$24,$41,$ba,$a4,$b2,$fc,$fc
	dc.b	$21,$9a,$f2,$80,$f6,$9c,$fc,$1d,$5e,$92,$68,$01,$8a,$01,$8b,$ee
	dc.b	$fc,$68,$f2,$fd,$d5,$fd,$ce,$f7,$f7,$80,$fd,$f0,$f8,$0f,$04,$35
	dc.b	$f0,$f8,$0d,$54,$3f,$54,$39,$7f,$fe,$54,$38,$01,$ff,$02,$00,$06
	dc.b	$00,$60,$1a,$54,$43,$54,$ff,$f3,$42,$55,$fe,$55,$fd,$55,$fc,$55
	dc.b	$fb,$54,$46,$a4,$1c,$ff,$11,$50,$49,$54,$44,$54,$52,$50,$51,$54
	dc.b	$50,$80,$b2,$12,$8e,$8f,$4e,$06,$fc,$f2,$f2,$3c,$90,$fa,$58,$5c
	dc.b	$80,$f8,$20,$60,$f8,$20,$40,$f3,$02,$0a,$02,$2c,$3c,$0b,$70,$40
	dc.b	$f6,$86,$2a,$54,$29,$52,$f4,$62,$f8,$11,$c0,$f1,$2a,$52,$2b,$bc
	dc.b	$f4,$fc,$1a,$62,$f1,$72,$6e,$f2,$f2,$78,$00,$16,$bd,$fe,$00,$17
	dc.b	$fc,$f8,$0c,$18,$00,$19,$fc,$f8,$0c,$e0,$f8,$2e,$54,$06,$54,$04
	dc.b	$50,$fb,$be,$08,$50,$09,$f8,$fe,$50,$12,$50,$13,$fc,$f8,$0b,$00
	dc.b	$14,$00,$15,$f7,$fc,$fc,$f8,$0c,$1e,$00,$1f,$fc,$f8,$0c,$20,$00
	dc.b	$21,$fc,$f4,$50,$f5,$f7,$07,$50,$f6,$50,$f7,$e8,$fe,$50,$f8,$50
	dc.b	$87,$52,$1b,$3c,$1f,$fd,$e0,$fc,$50,$f9,$50,$85,$40,$ae,$f1,$50
	dc.b	$fa,$50,$fb,$07,$e1,$50,$fc,$50,$8b,$f6,$86,$f1,$2a,$1c,$50,$2f
	dc.b	$b2,$8c,$50,$8d,$50,$8e,$24,$f1,$24,$ee,$0a,$24,$9a,$eb,$92,$f3
	dc.b	$7c,$f2,$0a,$f2,$e0,$ec,$af,$9f,$dc,$05,$6d,$ae,$ef,$60,$f2,$1a
	dc.b	$06,$25,$01,$89,$45,$94,$e4,$4e,$ed,$e0,$e8,$2a,$f3,$11,$ae,$e0
	dc.b	$f0,$c0,$28,$02,$29,$38,$f6,$00,$8c,$c0,$fd,$ce,$e4,$a0,$59,$44
	dc.b	$c0,$8d,$ce,$00,$ea,$bc,$e0,$0b,$a8,$d6,$fc,$1b,$dc,$e2,$f8,$f0
	dc.b	$68,$ec,$ea,$bd,$f8,$e5,$20,$f0,$15,$a0,$e8,$09,$20,$f0,$16,$50
	dc.b	$54,$0d,$20,$f5,$1b,$52,$1c,$92,$a0,$dc,$e4,$60,$e5,$a8,$6e,$e2
	dc.b	$80,$6e,$e4,$82,$28,$12,$fc,$4e,$e2,$80,$6e,$e2,$44,$ee,$e7,$fa
	dc.b	$00,$66,$8e,$fe,$56,$25,$51,$8c,$c0,$c2,$e4,$bc,$f0,$fe,$38,$02
	dc.b	$15,$d8,$80,$fc,$06,$f8,$9d,$59,$f0,$12,$02,$11,$02,$10,$80,$fc
	dc.b	$f6,$5a,$30,$fe,$e0,$ee,$13,$80,$2e,$52,$2f,$f9,$6d,$7f,$f5,$05
	dc.b	$f9,$02,$16,$00,$23,$00,$24,$e4,$f3,$f2,$f1,$80,$ea,$50,$2b,$20
	dc.b	$8c,$50,$76,$5a,$f3,$84,$51,$89,$40,$a0,$5c,$58,$60,$6e,$00,$f9
	dc.b	$11,$95,$ae,$f5,$1b,$5a,$f6,$6a,$e3,$6a,$fc,$02,$82,$e8,$10,$96
	dc.b	$ee,$ea,$47,$0f,$ce,$f2,$13,$02,$14,$76,$b2,$f2,$16,$02,$17,$44
	dc.b	$8c,$ee,$e4,$88,$ea,$33,$fa,$d4,$04,$23,$f2,$e4,$e6,$fc,$54,$27
	dc.b	$54,$4f,$e4,$94,$54,$93,$04,$92,$e0,$ea,$e4,$f2,$54,$af,$af,$85
	dc.b	$54,$97,$04,$98,$c6,$fd,$52,$f3,$54,$87,$54,$99,$bc,$fc,$ff,$ff
	dc.b	$00,$e8,$39,$50,$6b,$50,$6c,$50,$6d,$50,$73,$50,$74,$50,$75,$50
	dc.b	$76,$50,$ff,$8f,$77,$50,$78,$50,$79,$50,$15,$50,$7a,$50,$7b,$50
	dc.b	$7c,$5e,$51,$f8,$20,$ee,$12,$00,$13,$fc,$f8,$0c,$00,$e8,$0e,$08
	dc.b	$6e,$e2,$4d,$d2,$fc,$f2,$bc,$d2,$8a,$32,$ee,$f6,$60,$04,$f0,$fa
	dc.b	$22,$fa,$00,$23,$06,$5c,$32,$fc,$80,$f1,$26,$54,$25,$67,$c1,$50
	dc.b	$27,$f6,$58,$20,$80,$f2,$20,$60,$d1,$51,$55,$f1,$6d,$d8,$80,$f3
	dc.b	$cc,$f3,$28,$f1,$80,$f0,$09,$12,$dc,$2d,$a3,$04,$e1,$d8,$ea,$18
	dc.b	$84,$d6,$a0,$d8,$4f,$41,$80,$f4,$02,$25,$01,$24,$ae,$05,$c0,$80
	dc.b	$f4,$88,$23,$f0,$25,$92,$12,$05,$fa,$ab,$02,$d6,$e0,$ff,$92,$f3
	dc.b	$e0,$fc,$8a,$f1,$e0,$86,$a7,$88,$10,$88,$01,$f9,$01,$fa,$80,$5d
	dc.b	$21,$05,$fa,$60,$fc,$50,$28,$80,$fe,$e0,$e9,$fc,$2d,$1b,$94,$84
	dc.b	$bc,$c9,$52,$86,$c0,$ce,$64,$ea,$2a,$7c,$74,$19,$fa,$e9,$76,$d2
	dc.b	$58,$55,$05,$48,$01,$5d,$f9,$5e,$f2,$fc,$f4,$57,$05,$52,$85,$e9
	dc.b	$1b,$05,$49,$f4,$fe,$02,$d5,$ff,$f2,$f4,$f8,$09,$ff,$f8,$32,$32
	dc.b	$d6,$96,$ca,$ff,$d8,$38,$50,$39,$50,$3f,$a2,$49,$a2,$4a,$fc,$54
	dc.b	$ea,$fe,$7f,$af,$02,$0d,$02,$0e,$02,$0f,$06,$0d,$cc,$f8,$0a,$c1
	dc.b	$00,$c2,$bc,$f8,$0c,$d2,$f9,$f0,$f8,$2c,$c0,$e1,$f0,$07,$04,$05
	dc.b	$f1,$07,$51,$bf,$aa,$75,$50,$c1,$50,$c2,$54,$31,$a0,$c6,$ac,$da
	dc.b	$ee,$f5,$46,$f2,$aa,$20,$60,$f3,$e4,$f3,$dc,$de,$d2,$cc,$6e,$de
	dc.b	$d4,$52,$d8,$0c,$b2,$ac,$e2,$e8,$5c,$f3,$d8,$c8,$e2,$82,$b2,$20
	dc.b	$de,$34,$f3,$ec,$1a,$f2,$84,$db,$b8,$84,$6a,$e8,$fe,$c4,$b8,$c0
	dc.b	$b8,$49,$20,$fc,$c0,$b8,$10,$1b,$2d,$e8,$08,$f3,$00,$f1,$51,$fc
	dc.b	$da,$8a,$f2,$bd,$ae,$d7,$97,$01,$73,$06,$99,$fc,$fe,$98,$f6,$e4
	dc.b	$04,$0a,$fc,$f6,$f4,$9b,$7f,$46,$f6,$06,$9f,$02,$e2,$0a,$98,$0a
	dc.b	$f4,$e6,$38,$e9,$fe,$e3,$04,$00,$fa,$f4,$fc,$50,$f5,$93,$43,$e2
	dc.b	$06,$91,$02,$fe,$92,$f8,$f0,$07,$84,$98,$06,$96,$02,$fe,$b0,$f8
	dc.b	$91,$20,$06,$95,$f0,$c4,$fe,$9a,$8d,$87,$f8,$06,$9c,$f0,$e4,$02
	dc.b	$e5,$aa,$08,$fe,$02,$bc,$f8,$fa,$eb,$32,$50,$33,$50,$34,$9f,$ff
	dc.b	$50,$b4,$50,$b5,$50,$b6,$fc,$b7,$50,$b8,$50,$b9,$54,$e1,$fb,$b9
	dc.b	$f0,$fe,$ba,$f0,$bb,$f0,$bc,$68,$d2,$06,$19,$fc,$ff,$f4,$ae,$fc
	dc.b	$ad,$fc,$ac,$fc,$ab,$fc,$aa,$f0,$fc,$a6,$fc,$fb,$8b,$a5,$fc,$a4
	dc.b	$48,$d6,$30,$be,$3c,$a3,$3c,$a2,$38,$d0,$09,$c5,$56,$f0,$a1,$58
	dc.b	$c0,$0b,$f0,$a0,$f0,$f8,$0e,$9f,$40,$b0,$52,$58,$d5,$b5,$6e,$80
	dc.b	$f7,$58,$d5,$80,$f0,$0d,$f1,$72,$cc,$80,$f7,$01,$6d,$c0,$f8,$1e
	dc.b	$58,$45,$fd,$48,$f6,$bc,$fc,$c0,$c0,$ee,$9c,$ea,$18,$a1,$6d,$01
	dc.b	$4b,$f8,$48,$05,$55,$8e,$cc,$a6,$f0,$52,$05,$57,$d1,$a0,$05,$56
	dc.b	$ec,$38,$ea,$88,$a0,$32,$cb,$c0,$fe,$ec,$fe,$c6,$f0,$ec,$fe,$01
	dc.b	$1f,$02,$5d,$01,$ff,$bf,$0f,$01,$10,$02,$45,$02,$30,$02,$31,$c1
	dc.b	$da,$c2,$33,$c2,$32,$ff,$cf,$d2,$e2,$02,$34,$02,$35,$02,$32,$c1
	dc.b	$db,$c2,$3c,$02,$3d,$7f,$00,$f0,$47,$02,$36,$02,$37,$c1,$dc,$e0
	dc.b	$ea,$0e,$ff,$f0,$44,$ce,$c5,$d8,$c1,$d9,$02,$40,$02,$f0,$ff,$41
	dc.b	$f0,$c1,$d4,$c1,$d5,$c1,$d6,$c1,$d7,$02,$3e,$02,$ff,$3f,$3f,$02
	dc.b	$46,$c0,$c1,$c0,$c2,$c2,$3a,$c2,$3b,$02,$38,$02,$39,$01,$02,$d8
	dc.b	$f0,$d0,$c8,$02,$7e,$c0,$c4,$f8,$30,$01,$29,$01,$2a,$02,$44,$92
	dc.b	$d6,$aa,$c0,$a3,$aa,$80,$d3,$8b,$2e,$ca,$d3,$a0,$c9,$9a,$d2,$a4
	dc.b	$cd,$aa,$eb,$80,$cf,$a4,$cd,$66,$d2,$a8,$ce,$80,$d6,$04,$5e,$72
	dc.b	$ce,$60,$d5,$5d,$97,$f6,$54,$61,$54,$60,$e4,$fc,$ea,$52,$1c,$e0
	dc.b	$a9,$63,$54,$ff,$da,$62,$54,$67,$54,$66,$54,$65,$54,$64,$f2,$bc
	dc.b	$e0,$ac,$54,$e0,$a9,$7f,$e1,$51,$fb,$51,$fc,$51,$fd,$51,$fe,$80
	dc.b	$e8,$0f,$fc,$50,$ba,$5b,$50,$50,$bb,$50,$80,$e8,$14,$0c,$80,$e8
	dc.b	$0c,$f0,$80,$e8,$7b,$e0,$d8,$3f,$fd,$ef,$ec,$e9,$9a,$f2,$1e,$54
	dc.b	$9d,$54,$9c,$54,$90,$54,$8f,$92,$b7,$1c,$1f,$d5,$54,$8e,$54,$8d
	dc.b	$50,$50,$e0,$e8,$ba,$b2,$9e,$80,$9a,$aa,$aa,$1d,$9c,$bb,$c4,$da
	dc.b	$7c,$ea,$ba,$b2,$36,$db,$f8,$9d,$00,$be,$aa,$f4,$a0,$c8,$09,$8e
	dc.b	$a6,$0e,$a3,$92,$c6,$c0,$9a,$d4,$04,$24,$00,$c2,$ff,$5a,$32,$f0
	dc.b	$09,$e6,$54,$4f,$54,$4e,$54,$4d,$54,$4c,$54,$d5,$7e,$59,$54,$9c
	dc.b	$c3,$a8,$b1,$00,$ea,$1c,$80,$99,$57,$54,$56,$54,$5f,$11,$55,$60
	dc.b	$ff,$aa,$ea,$80,$9a,$a0,$fe,$06,$bc,$95,$54,$68,$d6,$5c,$d6,$12
	dc.b	$9c,$56,$dc,$e4,$9a,$ea,$84,$c4,$8f,$3f,$5c,$ac,$51,$09,$83,$f2
	dc.b	$62,$02,$63,$01,$88,$05,$7c,$a2,$f6,$51,$00,$91,$09,$24,$f2,$fc
	dc.b	$10,$25,$05,$64,$91,$f0,$96,$b2,$ee,$61,$1d,$92,$c1,$f2,$50,$91
	dc.b	$a8,$c2,$fc,$fe,$f0,$96,$ac,$b0,$f0,$48,$b1,$8d,$64,$e1,$20,$c3
	dc.b	$f0,$00,$9e,$8c,$aa,$92,$95,$78,$f2,$60,$f8,$09,$8e,$93,$92,$f3
	dc.b	$d1,$f5,$72,$90,$09,$1f,$36,$44,$ef,$00,$53,$40,$b1,$fa,$f1,$50
	dc.b	$4a,$ff,$ff,$50,$4b,$50,$4c,$50,$4d,$50,$4e,$50,$4f,$50,$55,$50
	dc.b	$56,$50,$57,$3d,$48,$50,$3d,$f8,$f5,$18,$50,$40,$f6,$fc,$f7,$2a
	dc.b	$a9,$2a,$20,$9a,$b6,$bb,$20,$90,$0a,$e8,$ee,$fe,$e8,$a4,$a4,$ff
	dc.b	$e4,$f4,$c6,$80,$de,$0e,$b5,$50,$46,$50,$47,$50,$48,$50,$3f,$c2
	dc.b	$3a,$50,$3b,$50,$42,$50,$43,$a0,$ff,$51,$5f,$55,$50,$52,$50,$44
	dc.b	$50,$45,$d8,$f1,$3c,$f2,$e4,$c3,$2e,$f8,$09,$a0,$8e,$95,$05,$a0
	dc.b	$fd,$80,$88,$0a,$e0,$ef,$0e,$b2,$c5,$ce,$55,$62,$00,$bb,$5c,$f8
	dc.b	$0a,$60,$f6,$5c,$8a,$3c,$5a,$2b,$2a,$d2,$fe,$01,$12,$a0,$09,$52
	dc.b	$f3,$f0,$5a,$f3,$8e,$fe,$30,$91,$f0,$b6,$f2,$ea,$8a,$32,$c0,$d1
	dc.b	$96,$fd,$2e,$bd,$f0,$b2,$fe,$12,$cb,$2c,$c0,$40,$fd,$0e,$a0,$5c
	dc.b	$8f,$be,$56,$a0,$a6,$4e,$a2,$51,$82,$b8,$09,$88,$a1,$53,$22,$b8
	dc.b	$2d,$c0,$82,$f5,$27,$f0,$f8,$0b,$a6,$b9,$c0,$81,$54,$34,$54,$33
	dc.b	$54,$32,$f0,$ed,$92,$40,$d1,$e0,$ea,$1a,$c0,$82,$54,$37,$c0,$81
	dc.b	$68,$68,$55,$72,$f2,$58,$aa,$81,$41,$00,$e0,$60,$8e,$b0,$0a,$c0
	dc.b	$80,$09,$7a,$b0,$0a,$bf,$28,$80,$c0,$40,$c0,$be,$cc,$a3,$cc,$00
	dc.b	$d8,$0a,$f0,$00,$d8,$0c,$8a,$ba,$f0,$00,$d8,$0c,$f0,$00,$d8,$0a
	dc.b	$88,$fe,$54,$b4,$7f,$fd,$cc,$f4,$f4,$bc,$f4,$bb,$f4,$ba,$f4,$80
	dc.b	$d1,$7c,$d1,$bf,$50,$c0,$f8,$ff,$fa,$aa,$f8,$ab,$f8,$ac,$f8,$ad
	dc.b	$f8,$ae,$d4,$91,$26,$b7,$f8,$b1,$f8,$fb,$dd,$b2,$f8,$b3,$16,$b0
	dc.b	$0b,$38,$af,$38,$b0,$30,$06,$b0,$0c,$38,$a9,$f0,$f8,$0e,$d6,$ed
	dc.b	$a8,$f0,$f8,$0e,$a7,$f0,$f8,$0d,$ff,$f8,$13,$f0,$d5,$f0,$f8,$0e
	dc.b	$d6,$f0,$f8,$0e,$d7,$fd,$ff,$05,$6e,$f2,$f8,$09,$f4,$d8,$f0,$d9
	dc.b	$55,$71,$55,$70,$55,$6f,$50,$dd,$7f,$7f,$f0,$de,$f0,$df,$f0,$e0
	dc.b	$f2,$18,$00,$f1,$72,$51,$73,$50,$ef,$f0,$d8,$f0,$ff,$f1,$f2,$00
	dc.b	$d9,$a2,$fe,$1f,$a1,$88,$7f,$70,$a1,$89,$a2,$2c,$a2,$2d,$a2,$1a
	dc.b	$f4,$f8,$c8,$67,$02,$59,$5d,$ef,$a0,$6c,$f0,$fe,$f2,$5a,$00,$d4
	dc.b	$9b,$f8,$4c,$2d,$00,$2e,$ac,$e8,$0a,$2f,$ff,$6d,$50,$30,$50,$31
	dc.b	$50,$36,$50,$37,$50,$3c,$40,$df,$1e,$40,$d9,$1b,$ad,$6a,$7c,$e7
	dc.b	$54,$e2,$8d,$88,$94,$f8,$d4,$a0,$8c,$92,$8e,$ee,$cb,$8f,$05,$19
	dc.b	$ee,$bf,$2a,$e2,$f4,$80,$88,$0a,$e6,$04,$14,$f2,$e0,$ba,$f6,$ee
	dc.b	$c0,$d3,$94,$f5,$e0,$c0,$d0,$15,$58,$85,$80,$84,$da,$

‘Kosinski’ is the nickname of a compression format used by various Mega Drive games, and this source file contains data compressed in this format. The header, when translated, says…

; Before compression $8000  After compression $2c00  Compression ratio 34.4%  Number of cells 1024

‘Cells’ refers to Mega Drive tile graphics, which are 32 bytes each. It doesn’t make such sense in this context, since it’s not tiles that are being compressed, but rather Aquatic Ruin Zone’s level chunk data.

Anyway, what we can learn from this source file is that Kosinski-compressed data wasn’t included into the ROM as binary data, but rather assembly data for the assembler to process. This is unusual, as every assembler that I’ve worked with supports including binary data directly, without requiring this odd shim.

Regardless, this assembly file also possibly indicates why Kosinski files are always padded to 0x10 bytes: each ‘dc.b’ directive in the source file is 0x10 values long, so perhaps the tool that produced this assembly file was unable to output a ‘dc.b’ that was less than 0x10 values long, and so it would instead output dummy 0 values until the ‘dc.b’ was ‘full’.

Closing

Aaaaaaand… that’s it. I think that’s all I know about Sonic the Hedgehog’s source code. Hopefully this information is of use to someone.

Assembler 4

It’s been a week since the last update, so what’s new? Well, the biggest improvement is that the assembler can now assemble SuperEgg’s Sonic 2 Nick Arcade disassembly without any modification.

In addition, the assembler is now case-insensitive. Symbol case-insensitivity is user-configurable, just like in asm68k.

Also like asm68k, the assembler can now output a “symbol file”. This is useful because symbol files can be used by Vladikcomper’s Advanced Error Handler. However, the error handler itself cannot be assembled with my assembler, because of its use of advanced asm68k features that I have yet to implement.

One particular difference in how I’m developing this assembler now compared to how I was doing it before is that I’m now consulting asm68k’s manual (SATMAN.pdf) for details on unimplemented features, whereas previously I was avoiding official documentation, and just relying on my own knowledge and guesswork. The reason for this didn’t have anything to do with legal paranoia, just that I didn’t want to make things too easy for myself early on. Now that I’m implementing features that I didn’t even know existed, I feel that using the manual is justified.

One area where using the manual has helped is operator precedence: previously, it matched that of C, but operator precedence is actually different in asm68k, so my assembler was recreating it incorrectly.

Speaking of the operators, I’ve been able to implement my own assembler extension: asm68k doesn’t provide any logical operators, only bitwise ones, so I’ve taken the opportunity to add them to my assembler. Now, in addition to ‘&’ and ‘|’, there’s ‘&&’ and ‘||’. There’s also ‘!’ to complement the bitwise unary ‘~’. I’ve also implemented C-style equality and inequality operators: ‘==’ and ‘!=’, to complement the usual ‘=’ and ‘<>’. This makes it possible to use C-style expressions in the assembler, which is a great fit for me. It’s nice that there’s at least one area where my assembler is already superior to asm68k.

I’m currently working towards getting three asm68k-based projects to assemble with my assembler: Aurora Field’s Z80 assembly macros, Vladikcomper’s Advanced Error Handler, and radioshadow’s Dr. Robotnik’s Mean Bean Machine disassembly. Unfortunately, all of them make use of numerous elaborate asm68k features (the first two especially), so despite spending days implementing missing features, I’m still far from getting any of them to assemble.

I’m planning to focus on the Mean Bean Machine disassembly, since that’s the one that’s closest to assembling. Hopefully my assembler will be compatible with it by the time of the next blog post.

Assembler Milestone Reached

It happened sooner than I was expecting, but here it is: my assembler can now assemble the Sonic 1 disassembly.

As I guessed in the last part, there really weren’t many things left to implement before this was possible: it turns out that the Sonic 1 disassembly doesn’t use variables at all, nor does it use ‘org’ or ‘fatal’ directives. It does use string literals in ‘dc’ directives though, as well as ‘even’ and ‘end’. It also uses if/else/endc blocks, which I somehow completely forgot to mention in the previous post.

Once those were implemented, it was just a matter of ironing out a bunch of bugs before my assembler was eventually outputting a binary file that exactly matched the one produced by asm68k. Not only that, but my assembler detected various mistakes and ambiguities in the disassembly’s code, enabling me to improve the disassembly’s readability. You can see these changes here, here, and here.

I’ve got to say, it’s so cool to assemble a ROM of Sonic 1 with my own assembler, and then play it in my own emulator. It’s like I’m creating an entire software ecosystem or something. Soon enough, it won’t just be Sonic 1: I’ll be assembling my own Mega Drive homebrew too! But, before I can do that, I’ll need to make my assembler at least partially-compatible with AS.

Right, AS. The Macroassembler AS. If I want to assemble Sonic 2, Sonic & Knuckles, or my homebrew, I’m going to have to add support for AS’s features. That’s going to be tricky, because AS is a multi-assembler: it can assemble multiple different assembly languages at once. The Sonic 2 and Sonic & Knuckles disassemblies use this for assembling those games’ sound engines, which are written in Zilog Z80 assembly. On top of that, AS has extensive macro facilities (that ‘Macroassembler’ title is well-earned) which I also need to replicate. Overall, AS support won’t be easy, and it definitely won’t be happening soon.

I’d publicly release this assembler, possibly with a fork of the Sonic 1 disassembly that includes a copy of it, but I’m not sure if that’s the wisest thing to do right now: I think I should keep it private until my honours project is over to avoid any issues regarding “plagiarism” (code contributions) and “human testing” (getting feedback from users). The assembler needs some polish anyway. But don’t worry: once my honours project is over and this becomes a hobby project, it’ll be ‘release early, release often‘ all the way!

Create your website with WordPress.com
Get started