Design a site like this with WordPress.com
Get started

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.

Advertisement

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!

Yup, still making that assembler.

I think that I started working on this assembler in early February, and I haven’t really stopped since, so that puts me about a month and a half into working on this thing non-stop. I’ve been so busy with it, in fact, that I haven’t even had time to keep this blog updated on its progress.

But what is there to talk about… it still can’t assemble any of the Sonic disassemblies; it can only do tiny custom files that I write myself to test each feature as it’s implemented. Like this one, for example:

	; Amazing test
  
; A line with just a colonless label
AmazingTest 

; A line with just an instruction
	move.w d0,d1 ; Absolutely NOT add.w d1,d0

; A line with a colonless label and an instruction
Lbl	add.w d1,d0 ; Absolutely NOT move.w d0,d1

; A line with a coloned label and an instruction
FunkyLabel:	move.w #$2700,sr ; A literal and the status register

; A line with just a coloned label
Label:
	move.w #0,ccr ; A literal and the condition code register
  
  
  ; A lot of blank lines to test that empty statements are properly supported
  
  
  ; A line with an address operand
	move.w d0,($00000000).w
	;adda.w d0,a1 ; Not implemented yet


	; sr instructions
	move.w	d0,sr
	move.w	($FFFF8000).w,sr

	; usp instructions
	move.l	a0,usp
	move.l	usp,a0

	; Testing the various effective address modes
	move.l d0,d0
	move.l a0,d0
	move.l (a0),d0
	move.l (a0)+,d0
	move.l -(a0),d0
	move.l 20(a0),d0
	move.l (a0,d0.w),d0
	move.l 20(a0,d0.w),d0
	move.l (a0,a1.w),d0
	move.l 20(a0,a1.w),d0
	move.l 20(pc),d0
	move.l (pc,d0.w),d0
	move.l 20(pc,d0.w),d0
	move.l (pc,a0.w),d0
	move.l 20(pc,a0.w),d0

	; Testing ori
	ori.w	#$FFFF,sr
	ori.b	#$FF,ccr
	ori.l	#$FFFFFFFF,d0

	; Testing andi
	andi.w	#$FFFF,sr
	andi.b	#$FF,ccr
	andi.l	#$FFFFFFFF,d0

	subi.w	#1,d0
	addi.w	#1,d0

	; Testing eori
	eori.w	#$FFFF,sr
	eori.b	#$FF,ccr
	eori.l	#$FFFFFFFF,d0

	; Testing cmpi
	cmpi.w	#1,d0
	cmpi.w	#1,(a0)
	cmpi.w	#1,(a0)+
	cmpi.w	#1,-(a0)
	cmpi.w	#1,$10(a0)
	cmpi.w	#1,(a0,d0.w)
	cmpi.w	#1,DummyDummyDummy(pc)
	cmpi.w	#1,DummyDummyDummy(pc,d0.w)
DummyDummyDummy:
	; Testing both modes of BTST/BCHG/BCLR/BSET
	btst.l	#0,d0
	btst.b	#0,(a0)
	bchg.l	#0,d0
	bchg.b	#0,(a0)
	bclr.l	#0,d0
	bclr.b	#0,(a0)
	bset.l	#0,d0
	bset.b	#0,(a0)
	btst.l	d0,d0
	btst.b	d0,(a0)
	bchg.l	d0,d0
	bchg.b	d0,(a0)
	bclr.l	d0,d0
	bclr.b	d0,(a0)
	bset.l	d0,d0
	bset.b	d0,(a0)
	bset.b	d0,#$AA ; What a wacky instruction...

	movep.l	d0,0(a0)
	movep.w	10(a0),d0

	movea.l	#$FFFF,a0
	movea.w	d0,a0

	negx.w	d0
	clr.w	d0
	neg.w	d0
	not.w	d0

	tst.w	d0

	ext.w	d0
	ext.l	d1

	nbcd	($FFFF8000).w

	pea.l	($FFFFFFFF).w

	illegal

	tas.b	d0

	trap	#4

	link	a0,#12
	unlk	a0

	reset
	nop
	stop	#$2700
	rte
	rts
	trapv
	rtr

	jsr (a0)
	jmp (0).w

	movem.w	a1/d0-d2/d4-a1/d3,-(sp)
	movem.w	(sp)+,a1/d0-d2/d4-a1/d3
	movem.w	d0-a7,-(sp)

	lea	10(a0),a0

	chk.w	#12,d0

	divu.w	#2,d0
	divs.w	#2,d0
	mulu.w	#2,d0
	muls.w	#2,d0

	addq.w	#1,d0
	addq.l	#8,d0
	subq.w	#1,d0
	subq.l	#8,d0

	st.b	($FFFF81D0).w
	sf.b	($FFFF81D0).w
	shi.b	($FFFF81D0).w
	sls.b	($FFFF81D0).w
	scc.b	($FFFF81D0).w
	scs.b	($FFFF81D0).w
	sne.b	($FFFF81D0).w
	seq.b	($FFFF81D0).w
	svc.b	($FFFF81D0).w
	svs.b	($FFFF81D0).w
	spl.b	($FFFF81D0).w
	smi.b	($FFFF81D0).w
	sge.b	($FFFF81D0).w
	slt.b	($FFFF81D0).w
	sgt.b	($FFFF81D0).w
	sle.b	($FFFF81D0).w

	dbf	d0,0

	bra.s	$180
	bra.w	0
	bsr.s	$180
	bsr.w	0
	bhi.s	$180
	bls.s	$180
	bcc.s	$180
	bcs.s	$180
	bne.s	$180
	beq.s	$180
	bvc.s	$180
	bvs.s	$180
	bpl.s	$180
	bmi.s	$180
	bge.s	$180
	blt.s	$180
	bgt.s	$180
	ble.s	$180
	bhi.w	0
	bls.w	0
	bcc.w	0
	bcs.w	0
	bne.w	0
	beq.w	0
	bvc.w	0
	bvs.w	0
	bpl.w	0
	bmi.w	0
	bge.w	0
	blt.w	0
	bgt.w	0
	ble.w	0

	moveq	#0,d0
	moveq	#$FFFFFFFF,d1
	moveq	#$FFFFFF80,d2

	sbcd	d0,d1
	sbcd	-(a0),-(a1)
	abcd	d0,d1
	abcd	-(a0),-(a1)
	subx.w	d0,d1
	subx.w	-(a0),-(a1)
	addx.w	d0,d1
	addx.w	-(a0),-(a1)

	or.w	d0,d0
	sub.w	d0,d0
	eor.w	d0,d0
	and.w	d0,d0
	add.w	d0,d0

	suba.w	d0,a0
	cmpa.w	d0,a0
	adda.w	d0,a0

	cmpm.w	(a0)+,(a1)+

	cmp.w	(0).w,d0

	exg	d0,d1
	exg	a0,a1
	exg	d0,a0
	exg	a0,d0

	asl.w	#1,d0
	asl.w	d0,d0
	asr.w	#1,d0
	asr.w	d0,d0
	lsl.w	#1,d0
	lsl.w	d0,d0
	lsr.w	#1,d0
	lsr.w	d0,d0
	roxl.w	#1,d0
	roxl.w	d0,d0
	roxr.w	#1,d0
	roxr.w	d0,d0
	rol.w	#1,d0
	rol.w	d0,d0
	ror.w	#1,d0
	ror.w	d0,d0

	asl.w	(a0)
	asr.w	(a0)
	lsl.w	(a0)
	lsr.w	(a0)
	roxl.w	(a0)
	roxr.w	(a0)
	rol.w	(a0)
	ror.w	(a0)

	; Testing the symbol table
TestLabel:
	bra.s	TestLabel


	; Testing fix-ups
	bra.w	DeclareAfterUseLabel
DeclareAfterUseLabel:

	; Some actual regular code
CoolFunction:
	moveq	#0,d2
	tst.w	d1
	beq.s	CoolFunction_Exit

CoolFunction_Loop:
	add.w	d0,d2
	dbf	d1,CoolFunction_Loop

CoolFunction_Exit:
	rts

	move.w	#%0101010101010101,d0

	; Testing out arithmetic in literals
	move.w	#1+1,d0       ; 2
	move.w	#1+(2*3),d0   ; 7
	move.w	#(2*3)+1,d0   ; 7
	move.w	#-1,d0        ; $FFFF
	move.w	#1+2-3*4/5,d0 ; 3
	move.w	#1--1,d0      ; 2

	; The Macro Assembler AS would fail to assemble this line (it would mistake the `(1*2)` for an absolute address)
	move.w	(1*2)+1(a0,d0.w),d0

	dc.b	0,1,2,3,4,5
	dc.w	0,1,2,3,4,5
	dc.l	0,1,2,3,4,5

Object:
	moveq	#0,d0
	move.b	2(a0),d0
	add.w	d0,d0
	move.w	@Offsets(pc,d0.w),d0
	jmp	@Offsets(pc,d0.w)

@Offsets:
	dc.w	@Offset1-Object@Offsets
	dc.w	Object@Offset2-@Offsets

@Offset1:
	rts

@Offset2:
	rts

	bra.s	*
	bra.s	1*(**1) ; Amazingly, this actually works as intended

	dc.l	*,*,*

	; Test operators
	dc.b	1<<1, 4>>1, 3&2, 7^5, 0|2, 6%4, 1==1, 1<>0, 1>0, 0<1, 1&&1, 1||1, 0+1, 2-1 , 1*1 , 1/1

	; Testing REPT
Delta:	rept 8
	dc.b	*-Delta
	endr

	; Testing case-insensitivity
	MOVE.B	D0,D0
	mOvE.b	(A0,d0.W),(a1,D1.l)

  ; More blank lines to test support for trailing blank statements




Well, okay, I *say* that they’re tiny…

As you can see, the assembler supports every type of 68k instruction, and even some high-level constructs like REPT directives. I’m working towards adding support for macros, but those are proving to be a real headache. I’ve implemented labels, a symbol table, and arithmetic expressions, just like I said I would in the last blog post. I’ve also added fancy error-reporting, for effective debugging – here’s an example of one:

Semantic error!
On line 2 of '[SOME REPT]'...
On line 333 of 'misc.asm'...
On line 1 of 'shim.asm'...
'ADDA' instruction cannot be this size - allowed sizes are...
ADDA.W
ADDA.L
        adda.b  d0,a0

It can definitely be improved, but for a rough prototype, I think it’s really coming together. What I like most is that it tracks the nesting of the erroneous statement, all the way through ‘include’ statements and even REPT blocks or macro declarations.

Until yesterday or so, this assembler would use Flex and Bison to produce a parse tree for the entire source file. A lecturer at my university pointed out that it’s unnecessary to produce a parse tree for anything more than a single statement, so I spent a few hours refactoring the assembler to parse only a single statement at a time. I definitely imagine that this saves quite a bit of RAM, but most noteworthy is that it makes supporting macros possible without an entire separate preprocessing step.

One major refactor that I’m thinking of carrying-out is to replace the fix-up system with a second assembler pass. These are both solutions to the problem of instructions using labels that don’t yet exist at that point in the source file. The idea is that the assembler reaches the end of the source file, and then goes back to assemble the instructions that couldn’t be done previously.

The current system manually takes note of each instruction that couldn’t be assembled due to then-undefined labels, and stores various bits of information about them (more notably, assembler state) in a linked list; when the end of the source file is reached, the assembler then iterates over this linked list, reverting some of the assembler state to how it was at the time the instruction was first processed, and then attempting again to assemble it (or, rather, ‘fix it up’).

This process is quite a pain, because duplicating internal assembler state is a mess of string duplication and backing up *just enough* internal state for the instruction to correctly assemble, even when done out-of-order. As an alternative, I’m thinking of just making it so that, when the assembler reaches the end of the file, it goes back to the start and assembles the whole file all over again. That way, I don’t need to keep track of what the assembler state was at the time an instruction failed, because that state will naturally be recreated by the assembler re-assembling the file in a step-by-step recreation of how it did the first time. This essentially trades code complexity and memory usage for performance, saving the former and costing the latter.

I think that, in the future, when I add support for build-time variables, I will need to ditch fix-ups anyway, since otherwise I’d have to back up portions of the symbol table too, in addition to the other backed-up state, which is a horrifically-complex task that I would much rather avoid.

I think that the remaining features that I need to implement before my assembler can assemble Sonic 1 are as follows…

  • Macros
  • Character literals
  • Constants
  • Variables
  • ‘equ’ directives
  • ‘org’ directives
  • ‘fatal’ and ‘inform’ directives
  • ‘dcb’ directives
  • ‘incbin’ directives

Having them all written out like this really makes it look like the end is finally in sight: assembling Sonic 1 would be the first major milestone for this project, and it would give this assembler an actual use, instead of it just being a tech-demo.

I have about a month and ten days before my honour’s project is due for submission; I hope that I can complete this and get it polished-up by then. Considering what I’ve gotten done in the last month and a half, I think that I’ll be just fine.

Writing a Motorola 68000 assembler

For my university honours project, I chose to create an assembler for the Motorola 68000. It’s not even close to being completed, but I’m bored right now and writing about it seems like a fun idea.

I’ve written a partial 68k assembler before, for my smps2asm2bin project. This mini-assembler was capable of processing dc.b and dc.w directives, as well as some hardcoded macros. It featured a working symbol table as well as managing fix-ups for instructions that used then-undefined symbols. This partial assembler was entirely custom, written in pure C.

This new assembler takes a different approach: while at university, I studied the creation of compilers, and how the process of parsing can be broken down into three steps: lexical, syntactic, and semantic analysis. I wanted my new assembler to follow this structure, so I’ve made use of the Flex and Bison tools in order to achieve this. Unlike more-modern software like ANTLR, Flex and Bison produce standard C89 code, which fits my assembler nicely.

At the moment, the assembler is capable of producing machine code for about 3/4 of the 68k’s instructions. However, it lacks a symbol table and support for arithmetic operations in instruction operands. It also lacks advanced features like macros. Still, I’m happy with the progress that’s been made so far.

Working on the assembler has taught me some strange things about the 68k assembly language: there are things that I never knew, despite speaking this language since late 2012 – almost 10 years ago. For instance, it’s possible for the index register in an indirect address register operand to be another address register (so ‘clr.b (a0,a1.w)’ is a valid instruction). Additionally, a literal operand can serve as the second operand of ‘btst’ and ‘link’ instructions, when it is usually only ever used as the first operand (‘btst d0,#$55’ and ‘link a0,#8’ are examples of this usage).

While my all-C mini-assembler never got to support the entire 68k assembly syntax, I do find Flex and Bison to be very powerful tools that make supporting the intricacies of the language quite easy, rendering many issues that I expected to encounter – were I to expand my mini-assembler – a non-issue in my new assembler. These tools require that the syntax be conceptualised in a very peculiar way, but I found that I clicked with it rather quickly. Essentially, you have to break the language down into ever-shrinking components. For example, a program is a series of statements; a statement can be a macro invocation or an instruction; an instruction is composed of an opcode with an optional size, optionally followed by a number of operands; each operand can be a register, an absolute address, or a literal; a register can be a data register, or an address register. By breaking the syntax down like this, one can use Bison to produce code that can parse the syntax, without needing to write it manually.

One awkward part of writing this assembler is that I will get so far into it, before realising that I need to rework it to support an edge-case that I had failed to consider. One example of this is when I needed to add support for a whole extra type of operand specifically for the MOVEM instruction, which allows you to specify multiple registers in a single operand, whereas normally instructions only let you specify a single one. This kind of problem has recently struck again, as, for the branch instructions, I need to add support for an operand type that expresses an address without a size specifier.

Soon enough, this assembler should be able to assemble all of the 68k’s instructions to their proper machine code, albeit without support for labels, macros, and other high-level features. Once I reach this stage, I should really produce an exhaustive test suite, which ensures that no instruction can be used with operands that it is incompatible with, and that correct machine code is produced. After that, I can focus on adding a symbol table so that the assembler can then support labels. After that, I want to add support for basic arithmetic in literals, so that expressions like ‘move.w #4*(2+2+2),d0’ work.

I suppose that’s enough for today. Hopefully, I’ll have some more stuff to talk about soon.

Mega Drive emulator update: debug menus

It looks like I wanted to get one more thing done before the project enters hibernation again.

While not exactly an accuracy-focussed emulator, I do fancy the idea of making clownmdemu into a useful developer tool. After all, I do make hacks and homebrew for the Mega Drive occasionally.

For now, it features a bunch of video-debugging windows which visualise some of the VDP’s internal processes. There’s also a PSG debugger, which shows each channel’s frequency and volume attenuation level. Eventually I’ll add more, for things like the CPUs and the FM chip.

Previously, my debugging emulator of choice was Regen, but that thing’s gotten pretty long-in-the-tooth, having last been updated in 2009. Since then, many things in it have been found to be inaccurate. In addition, it has a particularly glaring bug where any save data that it creates is accidentally byte-swapped, effectively rendering it unusable without manual correction. Once clownmdemu is fleshed-out enough, however, I can totally see it becoming my new main emulator.

I’m finding Dear ImGui to be very straightforward and fun to develop with (albeit with some annoying exceptions here and there, unfortunately). It certainly beats Qt for me: it’s so much simpler and less invasive. Having a huge demo window whose code you can just peak at to see how to do things is very handy too, and is way more preferable to trawling though page after page of incoherent documentation and Stack Overflow posts. Yeah, I know, I’m probably just bad at Qt: there’s probably a reason that projects like Dolphin, Yuzu, and DuckStation use it.

I think I’ve done enough GUI work for now though: the next thing that I work on will probably be FM emulation. Who knows how long that will take to be functional, though.

Progress on my Mega Drive emulator – clownmdemu

My emulator has recently seen some work, but development seems to be on the verge of slowing down again, so I think now is a good time to post a write-up here.

So, what’s new in clownmdemu?

Holy moly, an actual GUI

Yes, your eyes aren’t deceiving you: I’ve actually made a GUI. I know – I’m shocked too.

This GUI is implemented using Dear ImGui. I would have used Qt for a more native look-and-feel, but I think it’s a little bloated for my needs, and it’s also a very invasive piece of software, requiring that I effectively completely rewrite my frontend to be based on Qt instead of SDL2 (the two can’t co-exist). Dear ImGui, however, is very light and minimal, with a design philosophy that allows it to be layered atop of whatever windowing and renderer libraries I please. This allows me to keep the existing SDL2 base, and merely expand the frontend with GUI elements.

Dear ImGui isn’t entirely vanilla: it sports some customisations to make it extra pretty. In particular, the font has been changed from Proggy Clean to Karla Regular, and the font rasteriser has been swapped from the default stb_truetype rasteriser to FreeType. Another customisation is that Dear ImGui scales according to the display’s DPI. In fact, the whole frontend does. This is notable because SDL2 apparently doesn’t actually support DPI scaling on Windows, so I have to do it manually.

Finally, Dear ImGui features some code modifications. One change was the elimination of an OpenGL-specific function call in Dear ImGui’s SDL2 backend, improving support for its non-OpenGL renderers. Another modification was to improve the font quality by applying gamma-correction to the bitmaps. These two changes are currently pending Pull Requests on Dear ImGui’s repo.

Hopefully the GUI will be further expanded in the future: I’d like to add a controller rebinding menu at some point. For now though, this is enough to make the emulator usable without the command line or excessive hotkey usage.

PSG audio emulation

I’ve been dreading doing audio emulation since I started this emulator, but PSG honestly isn’t that bad. Granted, that’s mostly because the hardware for it has been thoroughly dissected, and an amazing write-up that explains how it works can be found on SMS Power!. Fun fact: the Mega Drive’s PSG can produce frequencies of up to around 100kHz.

At first, I wrote a PSG emulator that generated audio at the sample rate of the emulator itself (usually 48kHz), but this had nearest-neighbour aliasing issues and it also didn’t perform any kind of low-pass filtering. The result was some slightly unpleasant audio that sounded quite harsh.

In my second attempt, I wrote a PSG emulator that generated audio at the PSG’s native sample rate (roughly 200kHz). This was great and all, but required resampling to the emulator’s native sample rate. At first I was content to leave this task to SDL2, which is capable of accepting audio that isn’t in the format needed by the underlying audio playback library, but I had doubts over the quality of its resampler (it appeared to introduce some distortion in situations where low-pass filtering would be required). Additionally, SDL2 is locked to only one sample rate for the lifetime of its ‘audio device’, making supporting the slightly-different sample rate on PAL systems impossible outside of a compile-time flag or forcefully destroying and reinitialising SDL2’s entire audio system whenever the user switches console region.

To address this, I wrote my own resampler. I’d written linear interpolators before, but this time I wanted something higher quality. For this, I began researching windowed-sinc resampling, particularly the type that uses a Lanczos window. This is something that I’d researched before for CSE2 (maybe I’ll make a blog post on that someday…), but never had a solid grasp on. In fact, the more I studied windowed-sinc resampling, the more I realised that CSE2’s implementation was broken: it completely failed to perform low-pass filtering when downsampling.

So what’s so special about windowed-sinc resampling? Well, while linear interpolation simply plays ‘connect the dots’ with the audio samples, windowed-sinc resampling attempts to recreate the original waveforms from which the samples were produced, and then sample them again at a new sample rate (hence “resampling”). As an added bonus, windowed-sinc resamplers perform low-pass filtering for free (at least when done correctly). The result is a very high-quality resampling, which eliminates aliasing while also producing smooth natural-looking waveforms. The only downside to a windowed-sinc resampler is the complexity of implementing it, which also leads to it being a bit of a performance sinkhole. Still, after some optimisation, it currently appears to be roughly on-par with stb_vorbis (a popular Ogg Vorbis decoder) when it comes to average CPU usage per sample.

Personally, I’m pretty proud of this resampler, so I split it off from the frontend and turned it into a public-domain single-header-file library that’s compatible with C89 and C++98. It can be found here.

Other changes

There were also some miscellaneous changes that don’t need much explanation: PAL and Japanese modes can be toggled in the frontend at runtime (just like using a region switch on a real modded Mega Drive), the framebuffer is now scaled with a degree of smoothing at non-integer multiples, and controllers are now supported.

Conclusion

Aside from the lack of input rebinding and persistent configuration, this emulator is quite usable now. If those two issues were addressed, FM and Z80 emulation were added, and some of the missing 68k instructions were implemented, then this emulator would practically be ready for an initial release. We’ll just have to wait and see where this project goes next.

Bugger, I missed the anniversary

To tell you the truth, I thought that I started this blog at the end of December, not the start. Oh well.

So… one year on, and I think things have gone pretty well: not counting the post before this one, which was written after the anniversary, I put out 24 articles, which statistically works out to two posts for each month (haha, take that Dolphin and your “monthly” progress reports!).

Writing these posts has been a lot of fun, and I feel like I’ve been able to talk about a nice range of subjects, be it the development histories of some of my projects, my experience with various Linux components, or even just rambling about my laptop’s buggy BIOS.

I haven’t been able to dedicate much time to my hobbies lately, which is why the posts have been getting a little sporadic, but soon I’ll be working on a Motorola 68000 assembler, so that should give me a lot to talk about.

As for other things to come in the future, I may finally wrap-up my series of posts about clownaudio, since I think it’s approaching the point where there’s really not much left to go over. Additionally, I’ll likely get back to working on my Mega Drive emulator at some point. I’m getting a new laptop soon (assuming that the delivery doesn’t get cancelled for whatever reason), so maybe I’ll write some posts about getting it set up. I’m also thinking of switching from Xfce to KDE and Wayland, so that could be the subject of another post.

But yeah, this has been a fun first year. I can only imagine what hijinks I’ll get up to in the next.