Actions

SCHG How-to

Port Sonic 2 Final Sound Driver to Sonic 1

From Sonic Retro

Many have proven that the Sonic the Hedgehog sound driver can be put into Sonic the Hedgehog 2, but what about the reverse? Well, the reverse makes a few hacks easier, but some would prefer the Sonic the Hedgehog 3 sound driver for it's rich and diverse effects, including music. But, it is all about personal choice. One advantage with this driver over the Sonic 3 one is that you do not have to worry about where you put things. The driver comes from Xenowhirl's Sonic 2 disasm, and this guide uses the AS version of Hivebrain's disasm of Sonic 1.

Overview

There are a lot of things that need to change before we can use this:

  • Before you get started, if your hack was built off the ASM68k version of Hivebrain's disassembly, you have to port what you were working on to AS.
  • First off, you need insert the macros from the S2 Xenowhirl disassembly; this makes it relatively easier to work with.
  • Next, you will have to update the vertical and horizontal interrupts. Every time the Z80 is stopped, we need to call the sound driver input routine.
  • You also need the sound driver input routine.
  • The play sound routines need to be altered to fit the Sonic 2 sound driver standards, including pausing and unpausing the game.
  • All of the places a sound is played has to be updated to play the correct sounds.
  • The level select and debug need fixing.
  • Sonic 2 build tools need to be put in place.

Importing S2 macros

If the macros would have not been added, they would be expanded instead, and it makes work a lot harder. So, let's insert the macros. First, create a new file called "s2.macrosetup.asm" and put this in it:

	padding off	; we don't want AS padding out dc.b instructions
	listing off	; we don't need to generate anything for a listing file
	supmode on	; we don't need warnings about privileged instructions


paddingSoFar set 0

; 128 = 80h = z80, 32988 = 80DCh = z80unDoC 
notZ80 function cpu,(cpu<>128)&&(cpu<>32988)

; make org safer (impossible to overwrite previously assembled bytes) and count padding
; and also make it work in Z80 code without creating a new segment
org macro address
	if notZ80(MOMCPU)
		if address < *
			if assembleZ80SoundDriver
				error "too much stuff before org $\{address} ($\{(*-address)} bytes)"
			else
				error "too much stuff before org $\{address} ($\{(*-address)} bytes) ... try setting assembleZ80SoundDriver=1 in the asm file"
			endif
		elseif address > *
paddingSoFar	set paddingSoFar + address - *
			!org address
		endif
	else
		if address < $
			error "too much stuff before org 0\{address}h (0\{($-address)}h bytes)"
		else
			while address > $
				db 0
			endm
		endif
	endif
    endm

; define the cnop pseudo-instruction
cnop macro offset,alignment
	if notZ80(MOMCPU)
		org (*-1+(alignment)-((*-1+(-(offset)))#(alignment)))
	else
		org ($-1+(alignment)-(($-1+(-(offset)))#(alignment)))
	endif
    endm

; redefine align in terms of cnop, for the padding counter
align macro alignment
		cnop 0,alignment
	endm

; define the even pseudo-instruction
even macro
	if notZ80(MOMCPU)
		if (*)&1
paddingSoFar		set paddingSoFar+1
			dc.b 0 ;ds.b 1 
		endif
	else
		if ($)&1
			db 0
		endif
	endif
    endm

; make ds work in Z80 code without creating a new segment
ds macro
	if notZ80(MOMCPU)
		!ds.ATTRIBUTE ALLARGS
	else
		rept ALLARGS
			db 0
		endm
	endif
   endm

  if TRUE
; define a trace macro
; lets you easily check what address a location in this disassembly assembles to
; if used in Z80 code, the displayed PC will be relative to the start of Z80 RAM
trace macro optionalMessageWithoutQuotes
    if MOMPASS=1
	if notZ80(MOMCPU)
		if ("ALLARGS"<>"")
			message "#\{tracenum/1.0}: line=\{MOMLINE/1.0} PC=$\{*} msg=ALLARGS"
		else
			message "#\{tracenum/1.0}: line=\{MOMLINE/1.0} PC=$\{*}"
		endif
	else
		if ("ALLARGS"<>"")
			message "#\{tracenum/1.0}: line=\{MOMLINE/1.0} PC=\{$}h msg=ALLARGS"
		else
			message "#\{tracenum/1.0}: line=\{MOMLINE/1.0} PC=\{$}h"
		endif
	endif
tracenum := (tracenum+1)
    endif
   endm
  else
trace macro
	endm
  endif
tracenum := 0

    if zeroOffsetOptimization=0
    ; disable a space optimization in AS so we can build a bit-perfect rom
    ; (the hard way, but it requires no modification of AS itself)

; 1-arg instruction that's self-patching to remove 0-offset optimization
insn1op	 macro oper,x
	  if substr("x",0,2)<>"0("
		!oper	x
	  else
		!oper	1+x
		!org	*-1
		!dc.b	0
	  endif
	 endm

; 2-arg instruction that's self-patching to remove 0-offset optimization
insn2op	 macro oper,x,y
	  if substr("x",0,2)<>"0("
		  if substr("y",0,2)<>"0("
			!oper	x,y
		  else
			!oper	x,1+y
			!org	*-1
			!dc.b	0
		  endif
	  else
		if substr("y",0,1)<>"D"
		  if substr("y",0,2)<>"0("
			!oper	1+x,y
			!org	*-3
			!dc.b	0
			!org	*+2
		  else
			!oper	1+x,1+y
			!org	*-3
			!dc.b	0
			!org	*+1
			!dc.b	0
		  endif
		else
			!oper	1+x,y
			!org	*-1
			!dc.b	0
		endif
	  endif
	 endm

	; instructions that were used with 0(a#) syntax
	; defined to assemble as they originally did
_move	macro
		insn2op move.ATTRIBUTE, ALLARGS
	endm
_add	macro
		insn2op add.ATTRIBUTE, ALLARGS
	endm
_addq	macro
		insn2op addq.ATTRIBUTE, ALLARGS
	endm
_cmp	macro
		insn2op cmp.ATTRIBUTE, ALLARGS
	endm
_cmpi	macro
		insn2op cmpi.ATTRIBUTE, ALLARGS
	endm
_clr	macro
		insn1op clr.ATTRIBUTE, ALLARGS
	endm
_tst	macro
		insn1op tst.ATTRIBUTE, ALLARGS
	endm

	else

	; regular meaning to the assembler; better but unlike original
_move	macro
		!move.ATTRIBUTE ALLARGS
	endm
_add	macro
		!add.ATTRIBUTE ALLARGS
	endm
_addq	macro
		!addq.ATTRIBUTE ALLARGS
	endm
_cmp	macro
		!cmp.ATTRIBUTE ALLARGS
	endm
_cmpi	macro
		!cmpi.ATTRIBUTE ALLARGS
	endm
_clr	macro
		!clr.ATTRIBUTE ALLARGS
	endm
_tst	macro
		!tst.ATTRIBUTE ALLARGS
	endm

    endif

Next, your disassembly would have to be fixed so that we can use these macros, which is rather easy. Open sonic1.asm and find:

	CPU 68000
	padding off	; we don"t want AS padding out dc.b instructions
	listing off	; we don"t need to generate anything for a listing file
	supmode on	; we don"t need warnings about privileged instructions

; /=========================================================================\
; º	This file is generated by The Interactive Disassembler (IDA)	    º
; º	Copyright (c) 2003 by DataRescue sa/nv,	<[email protected]>	    º
; \=========================================================================/
;
; Disassembly created by Hivebrain
; thanks to drx and Stealth

; Processor:	    68000
; Target Assembler: 680x0 Assembler in MRI compatible mode
; This file should be compiled with "as	-M"

; ===========================================================================

StartOfRom:

now replace it with:

	CPU 68000

; >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; ASSEMBLY OPTIONS:
;
padToPowerOfTwo = 1
;	| If 1, pads the end of the rom to the next power of two bytes (for real hardware)
;
skipChecksumCheck = 0
;	| If 1, disables the unnecessary (and slow) bootup checksum calculation
;
zeroOffsetOptimization = 0
;	| If 1, makes a handful of zero-offset instructions smaller
;
assembleZ80SoundDriver = 1
;	| If 1, the Z80 sound driver is assembled with the rest of the rom
;	| If 0, the Z80 sound driver is BINCLUDEd (less flexible)

	include "s2.macrosetup.asm"
; >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equates section - Names for variables.
; ---------------------------------------------------------------------------
; size variables - you'll get an informational error if you need to change these...
; they are all in units of bytes
Size_of_DAC_samples =		$2F00
Size_of_SEGA_sound =		$6174
Size_of_Snd_driver_guess =	$F64 ; approximate post-compressed size of the Z80 sound driver

; ---------------------------------------------------------------------------
; Object Status Table offsets (for everything between Object_RAM and Primary_Collision)
; ---------------------------------------------------------------------------
; universally followed object conventions:
render_flags =		  1 ; bitfield ; bit 7 = onscreen flag, bit 0 = x mirror, bit 1 = y mirror, bit 2 = coordinate system
art_tile =		  2 ; and 3 ; start of sprite's art
mappings =		  4 ; and 5 and 6 and 7
x_pos =			  8 ; and 9 ... some objects use $A and $B as well when extra precision is required (see ObjectMove) ... for screen-space objects this is called x_pixel instead
y_pos =			 $C ; and $D ... some objects use $E and $F as well when extra precision is required ... screen-space objects use y_pixel instead
priority =		$18 ; 0 = front
width_pixels =		$19
mapping_frame =		$1A
; ---------------------------------------------------------------------------
; conventions followed by most objects:
x_vel =			$10 ; and $11 ; horizontal velocity
y_vel =			$12 ; and $13 ; vertical velocity
y_radius =		$16 ; collision width / 2
x_radius =		$17 ; collision height / 2
anim_frame =		$1B
anim =			$1C
next_anim =		$1D
anim_frame_duration =	$1E
status =		$22 ; note: exact meaning depends on the object... for sonic/tails: bit 0: leftfacing. bit 1: inair. bit 2: spinning. bit 3: onobject. bit 4: rolljumping. bit 5: pushing. bit 6: underwater.
routine =		$24
routine_secondary =	$25
angle =			$26 ; angle about the z=0 axis (360 degrees = 256)
; ---------------------------------------------------------------------------
; conventions followed by many objects but NOT sonic/tails:
collision_flags =	$20
collision_property =	$21
respawn_index =		$23
subtype =		$28
; ---------------------------------------------------------------------------
; conventions specific to sonic/tails (Obj01, Obj02, and ObjDB):
; note: $1F, $20, and $21 are unused and available
inertia =		$14 ; and $15 ; directionless representation of speed... not updated in the air
flip_angle =		$27 ; angle about the x=0 axis (360 degrees = 256) (twist/tumble)
air_left =		$28
flip_turned =		$29 ; 0 for normal, 1 to invert flipping (it's a 180 degree rotation about the axis of Sonic's spine, so he stays in the same position but looks turned around)
obj_control =		$2A ; 0 for normal, 1 for hanging or for resting on a flipper, $81 for going through CNZ/OOZ/MTZ tubes or stopped in CNZ cages or stoppers or flying if Tails
status_secondary =	$2B
flips_remaining =	$2C ; number of flip revolutions remaining
flip_speed =		$2D ; number of flip revolutions per frame / 256
move_lock =		$2E ; and $2F ; horizontal control lock, counts down to 0
invulnerable_time =	$30 ; and $31 ; time remaining until you stop blinking
invincibility_time =	$32 ; and $33 ; remaining
speedshoes_time =	$34 ; and $35 ; remaining
next_tilt =		$36 ; angle on ground in front of sprite
tilt =			$37 ; angle on ground
stick_to_convex =	$38 ; 0 for normal, 1 to make Sonic stick to convex surfaces like the rotating discs in Sonic 1 and 3 (unused in Sonic 2 but fully functional)
spindash_flag =		$39 ; 0 for normal, 1 for charging a spindash or forced rolling
spindash_counter =	$3A ; and $3B
jumping =		$3C
interact =		$3D ; RAM address of the last object Sonic stood on, minus $FFFFB000 and divided by $40
layer =			$3E ; collision plane, track switching...
layer_plus =		$3F ; always same as layer+1 ?? used for collision somehow
; ---------------------------------------------------------------------------
; conventions followed by several objects but NOT sonic/tails:
y_pixel =		2+x_pos ; and 3+x_pos ; y coordinate for objects using screen-space coordinate system
x_pixel =		x_pos ; and 1+x_pos ; x coordinate for objects using screen-space coordinate system
parent =		$3E ; and $3F ; address of object that owns or spawned this one, if applicable
; ---------------------------------------------------------------------------
; unknown or inconsistently used offsets that are not applicable to sonic/tails:
; (provided because rearrangement of the above values sometimes requires making space in here too)
objoff_A =		2+x_pos ; note: x_pos can be 4 bytes, but sometimes the last 2 bytes of x_pos are used for other unrelated things
objoff_B =		3+x_pos
objoff_E =		2+y_pos
objoff_F =		3+y_pos
objoff_14 =		$14
objoff_15 =		$15
objoff_1F =		$1F
objoff_27 =		$27
objoff_28 =		$28 ; overlaps subtype, but a few objects use it for other things anyway
 enum               objoff_29=$29,objoff_2A=$2A,objoff_2B=$2B,objoff_2C=$2C,objoff_2D=$2D,objoff_2E=$2E,objoff_2F=$2F
 enum objoff_30=$30,objoff_31=$31,objoff_32=$32,objoff_33=$33,objoff_34=$34,objoff_35=$35,objoff_36=$36,objoff_37=$37
 enum objoff_38=$38,objoff_39=$39,objoff_3A=$3A,objoff_3B=$3B,objoff_3C=$3C,objoff_3D=$3D,objoff_3E=$3E,objoff_3F=$3F
; ---------------------------------------------------------------------------
; property of all objects:
next_object =		$40 ; the size of an object

; ---------------------------------------------------------------------------
; I run the main 68k RAM addresses through this function
; to let them work in both 16-bit and 32-bit addressing modes.
ramaddr function x,-(-x)&$FFFFFFFF

; ---------------------------------------------------------------------------
; RAM variables
RAM_Start =			ramaddr( $FFFF0000 )
Metablock_Table =		ramaddr( $FFFF0000 )
Level_Layout =			ramaddr( $FFFF8000 )
Block_Table =			ramaddr( $FFFF9000 )
Decomp_Buffer =			ramaddr( $FFFFAA00 )
Sprite_Table_Input = 		ramaddr( $FFFFAC00 ) ; in custom format before being converted and stored in Sprite_Table/Sprite_Table_2
Object_RAM =			ramaddr( $FFFFB000 ) ; through $FFFFD5FF
MainCharacter =			ramaddr( $FFFFB000 ) ; first object (usually Sonic except in a Tails Alone game)
Sidekick =			ramaddr( $FFFFB040 ) ; second object (Tails in a Sonic and Tails game)
Tails_Tails =			ramaddr( $FFFFD000 ) ; address of the Tail's Tails object
Sonic_Dust =			ramaddr( $FFFFD100 )
Tails_Dust =			ramaddr( $FFFFD140 )

PNT_Buffer =			ramaddr( $FFFFD000 ) ; in special stage
Primary_Collision =		ramaddr( $FFFFD600 )
Horiz_Scroll_Buf_2 =		ramaddr( $FFFFD700 ) ; in special stage
Secondary_Collision =		ramaddr( $FFFFD900 )
VDP_Command_Buffer =		ramaddr( $FFFFDC00 ) ; stores VDP commands to issue the next time ProcessDMAQueue is called
VDP_Command_Buffer_Slot =	ramaddr( $FFFFDCFC ) ; stores the address of the next open slot for a queued VDP command
Sprite_Table_2 =		ramaddr( $FFFFDD00 ) ; Sprite attribute table buffer for the bottom split screen in 2-player mode
Horiz_Scroll_Buf =		ramaddr( $FFFFE000 )
Sonic_Stat_Record_Buf =		ramaddr( $FFFFE400 )
Sonic_Pos_Record_Buf =		ramaddr( $FFFFE500 )
Tails_Pos_Record_Buf =		ramaddr( $FFFFE600 )
Ring_Positions =		ramaddr( $FFFFE800 )
Camera_RAM =			ramaddr( $FFFFEE00 )
Camera_X_pos =			ramaddr( $FFFFEE00 )
Camera_Y_pos =			ramaddr( $FFFFEE04 )
Camera_Max_Y_pos =		ramaddr( $FFFFEEC6 )
Camera_Min_X_pos =		ramaddr( $FFFFEEC8 )
Camera_Max_X_pos =		ramaddr( $FFFFEECA )
Camera_Min_Y_pos =		ramaddr( $FFFFEECC )
Camera_Max_Y_pos_now =		ramaddr( $FFFFEECE ) ; was "Camera_max_scroll_spd"...
Sonic_Pos_Record_Index =	ramaddr( $FFFFEED2 ) ; into Sonic_Pos_Record_Buf and Sonic_Stat_Record_Buf
Tails_Pos_Record_Index =	ramaddr( $FFFFEED6 ) ; into Tails_Pos_Record_Buf
Camera_Y_pos_bias =		ramaddr( $FFFFEED8 ) ; added to y position for lookup/lookdown, $60 is center
Camera_Y_pos_bias_2P =		ramaddr( $FFFFEEDA ) ; for Tails
Dynamic_Resize_Routine =	ramaddr( $FFFFEEDF )
Tails_Min_X_pos =		ramaddr( $FFFFEEF8 )
Tails_Max_X_pos =		ramaddr( $FFFFEEFA )
Tails_Max_Y_pos =		ramaddr( $FFFFEEFE )

Underwater_palette_2 = 		ramaddr( $FFFFF000 ) ; not sure what it's used for but it's only used when there's water
Underwater_palette = 		ramaddr( $FFFFF080 ) ; main palette for underwater parts of the screen
Underwater_palette_line4 = 	ramaddr( $FFFFF0E0 )

Game_Mode =			ramaddr( $FFFFF600 ) ; 1 byte ; see GameModesArray (master level trigger, Mstr_Lvl_Trigger)
Ctrl_1_Logical =		ramaddr( $FFFFF602 ) ; 2 bytes
Ctrl_1_Held_Logical =		ramaddr( $FFFFF602 ) ; 1 byte
Ctrl_1_Press_Logical =		ramaddr( $FFFFF603 ) ; 1 byte
Ctrl_1 =			ramaddr( $FFFFF604 ) ; 2 bytes
Ctrl_1_Held =			ramaddr( $FFFFF604 ) ; 1 byte ; (pressed and held were switched around before)
Ctrl_1_Press =			ramaddr( $FFFFF605 ) ; 1 byte
Ctrl_2 =			ramaddr( $FFFFF606 ) ; 2 bytes
Ctrl_2_Held =			ramaddr( $FFFFF606 ) ; 1 byte
Ctrl_2_Press =			ramaddr( $FFFFF607 ) ; 1 byte
Demo_Time_left =		ramaddr( $FFFFF614 ) ; 2 bytes

Vscroll_Factor =		ramaddr( $FFFFF616 )
Hint_counter_reserve =		ramaddr( $FFFFF624 ) ; Must contain a VDP command word, preferably a write to register $0A. Executed every V-INT.
Delay_Time =			ramaddr( $FFFFF62A ) ; number of frames to delay the game
RNG_seed =			ramaddr( $FFFFF636 ) ; used for random number generation
Game_paused =			ramaddr( $FFFFF63A )
DMA_data_thunk =		ramaddr( $FFFFF640 ) ; Used as a RAM holder for the final DMA command word. Data will NOT be preserved across V-INTs, so consider this space reserved.

Water_Level_1 =			ramaddr( $FFFFF646 )
Water_Level_2 =			ramaddr( $FFFFF648 )
Water_Level_3 =			ramaddr( $FFFFF64A )
Water_routine =			ramaddr( $FFFFF64D )
Water_move =			ramaddr( $FFFFF64E )
Water_on =			ramaddr( $FFFFF64C ) ; is set based on Water_flag
New_Water_Level =		ramaddr( $FFFFF650 )
Water_change_speed =		ramaddr( $FFFFF652 )
Palette_frame_count =		ramaddr( $FFFFF65E )
Super_Sonic_palette =		ramaddr( $FFFFF65F )
Ctrl_2_Logical =		ramaddr( $FFFFF66A ) ; 2 bytes
Ctrl_2_Held_Logical =		ramaddr( $FFFFF66A ) ; 1 byte
Ctrl_2_Press_Logical =		ramaddr( $FFFFF66B ) ; 1 byte
Sonic_Look_delay_counter = 	ramaddr( $FFFFF66C ) ; 2 bytes
Tails_Look_delay_counter = 	ramaddr( $FFFFF66E ) ; 2 bytes
Super_Sonic_frame_count =	ramaddr( $FFFFF670 )
Plc_Buffer =			ramaddr( $FFFFF680 ) ; Pattern load queue

Misc_Variables =		ramaddr( $FFFFF700 )

; extra variables for the second player (CPU) in 1-player mode
Tails_control_counter =		ramaddr( $FFFFF702 ) ; how long until the CPU takes control
Tails_respawn_counter =		ramaddr( $FFFFF704 )
Tails_CPU_routine =		ramaddr( $FFFFF708 )
Tails_CPU_target_x =		ramaddr( $FFFFF70A )
Tails_CPU_target_y =		ramaddr( $FFFFF70C )
Tails_interact_ID =		ramaddr( $FFFFF70E ) ; object ID of last object stood on

Level_started_flag =		ramaddr( $FFFFF711 )
CNZ_Bumper_routine =		ramaddr( $FFFFF71A )
Dirty_flag =			ramaddr( $FFFFF72C ) ; if whole screen needs to redraw
Water_flag =			ramaddr( $FFFFF730 ) ; if the level has water or oil

Sonic_top_speed =		ramaddr( $FFFFF760 )
Sonic_acceleration =		ramaddr( $FFFFF762 )
Sonic_deceleration =		ramaddr( $FFFFF764 )
Obj_placement_routine =		ramaddr( $FFFFF76C )
Obj_load_addr_0 =		ramaddr( $FFFFF770 )
Obj_load_addr_1 =		ramaddr( $FFFFF774 )
Obj_load_addr_2 =		ramaddr( $FFFFF778 )
Obj_load_addr_3 =		ramaddr( $FFFFF77C )
Demo_button_index =		ramaddr( $FFFFF790 ) ; index into button press demo data, for player 1
Demo_press_counter =		ramaddr( $FFFFF792 ) ; frames remaining until next button press, for player 1
Demo_button_index_2P =		ramaddr( $FFFFF732 ) ; index into button press demo data, for player 2
Demo_press_counter_2P =		ramaddr( $FFFFF734 ) ; frames remaining until next button press, for player 2
Collision_addr =		ramaddr( $FFFFF796 )
Current_Boss_ID =		ramaddr( $FFFFF7AA )
Control_Locked =		ramaddr( $FFFFF7CC )
Chain_Bonus_counter =		ramaddr( $FFFFF7D0 ) ; counts up when you destroy things that give points, resets when you touch the ground
Bonus_Countdown_1 =		ramaddr( $FFFFF7D2 ) ; level results time bonus or special stage sonic ring bonus
Bonus_Countdown_2 =		ramaddr( $FFFFF7D4 ) ; level results ring bonus or special stage tails ring bonus
Update_Bonus_score =		ramaddr( $FFFFF7D6 )
Camera_X_pos_coarse =		ramaddr( $FFFFF7DA ) ; (Camera_X_pos - 128) / 256

Sprite_Table =			ramaddr( $FFFFF800 ) ; Sprite attribute table buffer

Normal_palette =		ramaddr( $FFFFFB00 )
Normal_palette_line2 =		ramaddr( $FFFFFB20 )
Normal_palette_line3 =		ramaddr( $FFFFFB40 )
Normal_palette_line4 =		ramaddr( $FFFFFB60 )
Second_palette =		ramaddr( $FFFFFB80 )
Second_palette_line2 =		ramaddr( $FFFFFBA0 )
Second_palette_line3 =		ramaddr( $FFFFFBC0 )
Second_palette_line4 =		ramaddr( $FFFFFBE0 )

Object_Respawn_Table =		ramaddr( $FFFFFC00 )
System_Stack =			ramaddr( $FFFFFE00 )
Level_Inactive_flag = 		ramaddr( $FFFFFE02 ) ; (2 bytes)
Timer_frames =			ramaddr( $FFFFFE04 ) ; (2 bytes)
Debug_object =			ramaddr( $FFFFFE06 )
Debug_placement_mode =		ramaddr( $FFFFFE08 )
Current_ZoneAndAct =		ramaddr( $FFFFFE10 ) ; 2 bytes
Current_Zone =			ramaddr( $FFFFFE10 ) ; 1 byte
Current_Act =			ramaddr( $FFFFFE11 ) ; 1 byte
Life_count =			ramaddr( $FFFFFE12 )
Current_Special_Stage =		ramaddr( $FFFFFE16 )
Continue_count =		ramaddr( $FFFFFE18 )
Super_Sonic_flag =		ramaddr( $FFFFFE19 )
Time_Over_flag =		ramaddr( $FFFFFE1A )
Extra_life_flags =		ramaddr( $FFFFFE1B )

; If set, the respective HUD element will be updated.
Update_HUD_lives =		ramaddr( $FFFFFE1C )
Update_HUD_rings =		ramaddr( $FFFFFE1D )
Update_HUD_timer =		ramaddr( $FFFFFE1E )
Update_HUD_score =		ramaddr( $FFFFFE1F )

Ring_count =			ramaddr( $FFFFFE20 ) ; 2 bytes
Timer =				ramaddr( $FFFFFE22 ) ; 4 bytes
Timer_minute_word =		ramaddr( $FFFFFE22 ) ; 2 bytes
Timer_minute =			ramaddr( $FFFFFE23 ) ; 1 byte
Timer_second =			ramaddr( $FFFFFE24 ) ; 1 byte
Timer_centisecond =		ramaddr( $FFFFFE25 ) ; 1 byte
Score =				ramaddr( $FFFFFE26 ) ; 4 bytes
Last_star_pole_hit =		ramaddr( $FFFFFE30 ) ; 1 byte -- max activated starpole ID in this act

Saved_Last_star_pole_hit =	ramaddr( $FFFFFE31 )
Saved_x_pos =			ramaddr( $FFFFFE32 )
Saved_y_pos =			ramaddr( $FFFFFE34 )
Saved_Ring_count =		ramaddr( $FFFFFE36 )
Saved_Timer =			ramaddr( $FFFFFE38 )
Saved_art_tile =		ramaddr( $FFFFFE3C )
Saved_layer =			ramaddr( $FFFFFE3E )
Saved_Camera_X_pos =		ramaddr( $FFFFFE40 )
Saved_Camera_Y_pos =		ramaddr( $FFFFFE42 )
Saved_Water_Level =		ramaddr( $FFFFFE50 )
Saved_Water_routine =		ramaddr( $FFFFFE52 )
Saved_Water_move =		ramaddr( $FFFFFE53 )
Saved_Extra_life_flags =	ramaddr( $FFFFFE54 )
Saved_Extra_life_flags_2P =	ramaddr( $FFFFFE55 )
Saved_Camera_Max_Y_pos =	ramaddr( $FFFFFE56 )
Saved_Dynamic_Resize_Routine =	ramaddr( $FFFFFE58 )

Logspike_anim_counter =		ramaddr( $FFFFFEA0 )
Logspike_anim_frame =		ramaddr( $FFFFFEA1 )
Rings_anim_counter =		ramaddr( $FFFFFEA2 )
Rings_anim_frame =		ramaddr( $FFFFFEA3 )
Unknown_anim_counter =		ramaddr( $FFFFFEA4 ) ; I think this was $FFFFFEC4 in the alpha
Unknown_anim_frame =		ramaddr( $FFFFFEA5 )
Ring_spill_anim_counter =	ramaddr( $FFFFFEA6 ) ; scattered rings
Ring_spill_anim_frame =		ramaddr( $FFFFFEA7 )
Ring_spill_anim_accum =		ramaddr( $FFFFFEA8 )

; values for the second player (some of these only apply to 2-player games)
Tails_top_speed =		ramaddr( $FFFFFEC0 ) ; Tails_max_vel
Tails_acceleration =		ramaddr( $FFFFFEC2 )
Tails_deceleration =		ramaddr( $FFFFFEC4 )
Life_count_2P =			ramaddr( $FFFFFEC6 )
Extra_life_flags_2P =		ramaddr( $FFFFFEC7 )
Update_HUD_lives_2P =		ramaddr( $FFFFFEC8 )
Update_HUD_rings_2P =		ramaddr( $FFFFFEC9 )
Update_HUD_timer_2P =		ramaddr( $FFFFFECA )
Update_HUD_score_2P =		ramaddr( $FFFFFECB ) ; mostly unused
Time_Over_flag_2P =		ramaddr( $FFFFFECC )
Ring_count_2P =			ramaddr( $FFFFFED0 )
Timer_2P =			ramaddr( $FFFFFED2 ) ; 4 bytes
Timer_minute_word_2P =		ramaddr( $FFFFFED2 ) ; 2 bytes
Timer_minute_2P =		ramaddr( $FFFFFED3 ) ; 1 byte
Timer_second_2P =		ramaddr( $FFFFFED4 ) ; 1 byte
Timer_centisecond_2P =		ramaddr( $FFFFFED5 ) ; 1 byte
Score_2P =			ramaddr( $FFFFFED6 )
Last_star_pole_hit_2P =		ramaddr( $FFFFFEE0 )

Saved_Last_star_pole_hit_2P =	ramaddr( $FFFFFEE1 )
Saved_x_pos_2P =		ramaddr( $FFFFFEE2 )
Saved_y_pos_2P =		ramaddr( $FFFFFEE4 )
Saved_Ring_count_2P =		ramaddr( $FFFFFEE6 )
Saved_Timer_2P =		ramaddr( $FFFFFEE8 )
Saved_art_tile_2P =		ramaddr( $FFFFFEEC )
Saved_layer_2P =		ramaddr( $FFFFFEEE )

Loser_Time_Left =		ramaddr( $FFFFFEF8 )
Results_Screen_2P =		ramaddr( $FFFFFF10 ) ; 0 = act, 1 = zone, 2 = game, 3 = SS, 4 = SS all
Results_Data_2P =		ramaddr( $FFFFFF20 ) ; $18 bytes
EHZ_Results_2P =		ramaddr( $FFFFFF20 ) ; 6 bytes
MCZ_Results_2P =		ramaddr( $FFFFFF26 ) ; 6 bytes
CNZ_Results_2P =		ramaddr( $FFFFFF2C ) ; 6 bytes
SS_Results_2P =			ramaddr( $FFFFFF32 ) ; 6 bytes
SS_Total_Won =			ramaddr( $FFFFFF38 ) ; 2 bytes (player 1 then player 2)
Perfect_rings_left =		ramaddr( $FFFFFF40 )
Player_mode =			ramaddr( $FFFFFF70 ) ; 0 = Sonic and Tails, 1 = Sonic, 2 = Tails
Player_option =			ramaddr( $FFFFFF72 ) ; 0 = Sonic and Tails, 1 = Sonic, 2 = Tails

Two_player_items =		ramaddr( $FFFFFF74 )
Level_select_zone =		ramaddr( $FFFFFF82 )
Sound_test_sound =		ramaddr( $FFFFFF84 )
Title_screen_option =		ramaddr( $FFFFFF86 )
Current_Zone_2P =		ramaddr( $FFFFFF88 )
Current_Act_2P =		ramaddr( $FFFFFF89 )
Two_player_mode_copy =		ramaddr( $FFFFFF8A )
Options_menu_box =		ramaddr( $FFFFFF8C )
Level_Music =			ramaddr( $FFFFFF90 )
Game_Over_2P =			ramaddr( $FFFFFF98 )
Got_Emerald =			ramaddr( $FFFFFFB0 )
Emerald_count =			ramaddr( $FFFFFFB1 )
Got_Emeralds_array =		ramaddr( $FFFFFFB2 ) ; 7 bytes
Next_Extra_life_score =		ramaddr( $FFFFFFC0 )
Next_Extra_life_score_2P =	ramaddr( $FFFFFFC4 )
Level_Has_Signpost =		ramaddr( $FFFFFFC8 ) ; 1 byte ; 1 = signpost, 0 = boss or nothing
Level_select_flag =		ramaddr( $FFFFFFD0 )
Slow_motion_flag =		ramaddr( $FFFFFFD1 )
Correct_cheat_entries =		ramaddr( $FFFFFFD4 )
Correct_cheat_entries_2 =	ramaddr( $FFFFFFD6 ) ; for 14 continues or 7 emeralds codes
Two_player_mode =		ramaddr( $FFFFFFD8 ) ; flag (0 for main game)

; Values in these variables are passed to the sound driver during V-INT.
; They use a playlist index, not a sound test index.
Music_to_play =			ramaddr( $FFFFFFE0 )
SFX_to_play =			ramaddr( $FFFFFFE1 ) ; normal
SFX_to_play_2 =			ramaddr( $FFFFFFE2 ) ; alternating stereo
Music_to_play_2 =		ramaddr( $FFFFFFE4 ) ; alternate (higher priority?) slot

Demo_mode_flag =		ramaddr( $FFFFFFF0 ) ; 1 if a demo is playing (2 bytes)
Demo_number =			ramaddr( $FFFFFFF2 ) ; which demo will play next (2 bytes)
Graphics_Flags =		ramaddr( $FFFFFFF8 ) ; misc. bitfield
Debug_mode_flag =		ramaddr( $FFFFFFFA ) ; (2 bytes)
Checksum_fourcc =		ramaddr( $FFFFFFFC ) ; (4 bytes)

; ---------------------------------------------------------------------------
; VDP addressses
VDP_data_port =			$C00000 ; (8=r/w, 16=r/w)
VDP_control_port =		$C00004 ; (8=r/w, 16=r/w)

; ---------------------------------------------------------------------------
; Z80 addresses
Z80_RAM =			$A00000 ; start of Z80 RAM
Z80_RAM_End =			$A02000 ; end of non-reserved Z80 RAM
Z80_Version =			$A10001
Z80_Port_1_Data =		$A10002
Z80_Port_1_Control =		$A10008
Z80_Port_2_Control =		$A1000A
Z80_Expansion_Control =		$A1000C
Z80_Bus_Request =		$A11100
Z80_Reset =			$A11200

Security_Addr =			$A14000

; >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; simplifying macros

; tells the VDP to copy a region of 68k memory to VRAM or CRAM or VSRAM
dma68kToVDP macro source,dest,length,type
	lea	(VDP_control_port).l,a5
	move.l	#(($9400|((((length)>>1)&$FF00)>>8))<<16)|($9300|(((length)>>1)&$FF)),(a5)
	move.l	#(($9600|((((source)>>1)&$FF00)>>8))<<16)|($9500|(((source)>>1)&$FF)),(a5)
	move.w	#$9700|(((((source)>>1)&$FF0000)>>16)&$7F),(a5)
	move.w	#((dest)&$3FFF)|((type&1)<<15)|$4000,(a5)
	move.w	#$80|(((dest)&$C000)>>14)|((type&2)<<3),(DMA_data_thunk).w
	move.w	(DMA_data_thunk).w,(a5)
    endm
    ; values for the type argument
    enum VRAM=0,CRAM=1,VSRAM=2

; tells the VDP to fill a region of VRAM with a certain byte
dmaFillVRAM macro byte,addr,length
	lea	(VDP_control_port).l,a5
	move.w	#$8F01,(a5) ; VRAM pointer increment: $0001
	move.l	#(($9400|((((length)-1)&$FF00)>>8))<<16)|($9300|(((length)-1)&$FF)),(a5) ; DMA length ...
	move.w	#$9780,(a5) ; VRAM fill
	move.l	#$40000080|(((addr)&$3FFF)<<16)|(((addr)&$C000)>>14),(a5) ; Start at ...
	move.w	#(byte)<<8,(VDP_data_port).l ; Fill with byte
-	move.w	(a5),d1
	btst	#1,d1
	bne.s	- ; busy loop until the VDP is finished filling...
	move.w	#$8F02,(a5) ; VRAM pointer increment: $0002
    endm

; calculates initial loop counter value for a dbf loop
; that writes n bytes total at 4 bytes per iteration
bytesToLcnt function n,n>>2-1

; fills a region of 68k RAM with 0 (4 bytes at a time)
clearRAM macro addr,length
    if length&3
	fatal "clearRAM len must be divisible by 4, but was length"
    endif
	lea	(addr).w,a1
	moveq	#0,d0
	move.w	#bytesToLcnt(length),d1
-	move.l	d0,(a1)+
	dbf	d1,-
    endm

; tells the Z80 to stop, and waits for it to finish stopping (acquire bus)
stopZ80 macro
	move.w	#$100,(Z80_Bus_Request).l ; stop the Z80
-	btst	#0,(Z80_Bus_Request).l
	bne.s	- ; loop until it says it's stopped
    endm

; tells the Z80 to start again
startZ80 macro
	move.w	#0,(Z80_Bus_Request).l    ; start the Z80
    endm

; function to make a little-endian 16-bit pointer for the Z80 sound driver
z80_ptr function x,(x)<<8&$FF00|(x)>>8&$7F|$80

; macro to declare a little-endian 16-bit pointer for the Z80 sound driver
rom_ptr_z80 macro addr
		dc.w z80_ptr(addr)
	endm

; >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; start of ROM

; /=========================================================================\
; º	This file is generated by The Interactive Disassembler (IDA)	    º
; º	Copyright (c) 2003 by DataRescue sa/nv,	<[email protected]>	    º
; \=========================================================================/
;
; Disassembly created by Hivebrain
; thanks to drx and Stealth

; Processor:	    68000
; Target Assembler: 680x0 Assembler in MRI compatible mode
; This file should be compiled with "as	-M"

; ===========================================================================

StartOfRom:

This will make the task a lot easier from this point forward, as well as make most other Sonic 2 to Sonic 1 ports much easier.

Fixing the Vertical and Horizontal Interrupts

The vertical and horizontal interrupts need to be fixed otherwise the sound driver will not work. First off, loc_B5E needs to go, as it is a reloader for the Sonic 1 driver and will not work with the Sonic 2 driver. Find:

loc_B5E:				; XREF: loc_B88
		jsr	(sub_71B4C).l

and remove that whole routine up there. Next, add the calls for the Sonic 2 sound driver reloader. Find:

loc_B88:				; XREF: loc_B10; off_B6E
		cmpi.b	#$8C,($FFFFF600).w
		beq.s	loc_B9A
		cmpi.b	#$C,($FFFFF600).w
		bne.w	loc_B5E

and change it to:

loc_B88:				; XREF: loc_B10; off_B6E
		cmpi.b	#$8C,($FFFFF600).w
		beq.s	loc_B9A
		cmpi.b	#$C,($FFFFF600).w
		bne.w	loc_B64
		stopZ80			; stop the Z80
		bsr.w	sndDriverInput	; give input to the sound driver
		startZ80		; start the Z80

Now find:

loc_C22:				; XREF: loc_BC8
		move.w	($FFFFF624).w,(a5)
		move.w	#0,($A11100).l
		bra.w	loc_B5E

and change it to:

loc_C22:				; XREF: loc_BC8
		move.w	($FFFFF624).w,(a5)
		bsr.w	sndDriverInput
		move.w	#0,($A11100).l
		bra.w	loc_B64

Now find:

loc_D50:

and add below:

		bsr.w	sndDriverInput

Now find:

loc_DAE:
		btst	#0,($A11100).l	; has Z80 stopped?
		bne.s	loc_DAE		; if not, branch
		bsr.w	ReadJoypads
		lea	($C00004).l,a5
		move.l	#$94009340,(a5)
		move.l	#$96FD9580,(a5)
		move.w	#$977F,(a5)
		move.w	#$C000,(a5)
		move.w	#$80,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$94019340,(a5)
		move.l	#$96FC9500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7800,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$940193C0,(a5)
		move.l	#$96E69500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7C00,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		move.w	#0,($A11100).l
		bsr.w	PalCycle_SS
		tst.b	($FFFFF767).w
		beq.s	loc_E64
		lea	($C00004).l,a5
		move.l	#$94019370,(a5)
		move.l	#$96E49500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7000,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		move.b	#0,($FFFFF767).w

and change it to:

loc_DAE:
		btst	#0,($A11100).l	; has Z80 stopped?
		bne.s	loc_DAE		; if not, branch
		bsr.w	ReadJoypads
		lea	($C00004).l,a5
		move.l	#$94009340,(a5)
		move.l	#$96FD9580,(a5)
		move.w	#$977F,(a5)
		move.w	#$C000,(a5)
		move.w	#$80,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$94019340,(a5)
		move.l	#$96FC9500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7800,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$940193C0,(a5)
		move.l	#$96E69500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7C00,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		bsr.w	sndDriverInput
		move.w	#0,($A11100).l
		bsr.w	PalCycle_SS
		tst.b	($FFFFF767).w
		beq.s	loc_E64
		lea	($C00004).l,a5
		move.l	#$94019370,(a5)
		move.l	#$96E49500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7000,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		move.b	#0,($FFFFF767).w

Now find:

loc_F54:

and add below:

		bsr.w	sndDriverInput

Now find:

loc_FAE:
		btst	#0,($A11100).l	; has Z80 stopped?
		bne.s	loc_FAE		; if not, branch
		bsr.w	ReadJoypads
		lea	($C00004).l,a5
		move.l	#$94009340,(a5)
		move.l	#$96FD9580,(a5)
		move.w	#$977F,(a5)
		move.w	#$C000,(a5)
		move.w	#$80,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$94019340,(a5)
		move.l	#$96FC9500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7800,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$940193C0,(a5)
		move.l	#$96E69500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7C00,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		move.w	#0,($A11100).l	; start	the Z80
		tst.b	($FFFFF767).w
		beq.s	loc_1060
		lea	($C00004).l,a5
		move.l	#$94019370,(a5)
		move.l	#$96E49500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7000,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		move.b	#0,($FFFFF767).w

and change to:

loc_FAE:
		btst	#0,($A11100).l	; has Z80 stopped?
		bne.s	loc_FAE		; if not, branch
		bsr.w	ReadJoypads
		lea	($C00004).l,a5
		move.l	#$94009340,(a5)
		move.l	#$96FD9580,(a5)
		move.w	#$977F,(a5)
		move.w	#$C000,(a5)
		move.w	#$80,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$94019340,(a5)
		move.l	#$96FC9500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7800,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$940193C0,(a5)
		move.l	#$96E69500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7C00,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		bsr.w	sndDriverInput
		move.w	#0,($A11100).l	; start	the Z80
		tst.b	($FFFFF767).w
		beq.s	loc_1060
		lea	($C00004).l,a5
		move.l	#$94019370,(a5)
		move.l	#$96E49500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7000,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		move.b	#0,($FFFFF767).w

Now find:

loc_10D4:				; XREF: sub_106E
		lea	($C00004).l,a5
		move.l	#$94019340,(a5)
		move.l	#$96FC9500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7800,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$940193C0,(a5)
		move.l	#$96E69500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7C00,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		move.w	#0,($A11100).l	; start	the Z80
		rts

and change it to:

loc_10D4:				; XREF: sub_106E
		lea	($C00004).l,a5
		move.l	#$94019340,(a5)
		move.l	#$96FC9500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7800,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		lea	($C00004).l,a5
		move.l	#$940193C0,(a5)
		move.l	#$96E69500,(a5)
		move.w	#$977F,(a5)
		move.w	#$7C00,(a5)
		move.w	#$83,($FFFFF640).w
		move.w	($FFFFF640).w,(a5)
		bsr.w	sndDriverInput
		move.w	#0,($A11100).l	; start	the Z80
		rts

Now find:

Joypad_WaitZ80:
		btst	#0,($A11100).l	; has the Z80 stopped?
		bne.s	Joypad_WaitZ80	; if not, branch
		moveq	#$40,d0
		move.b	d0,($A10009).l	; init port 1 (joypad 1)
		move.b	d0,($A1000B).l	; init port 2 (joypad 2)
		move.b	d0,($A1000D).l	; init port 3 (extra)
		move.w	#0,($A11100).l	; start	the Z80
		rts

and change it to:

Joypad_WaitZ80:
		btst	#0,($A11100).l	; has the Z80 stopped?
		bne.s	Joypad_WaitZ80	; if not, branch
		moveq	#$40,d0
		move.b	d0,($A10009).l	; init port 1 (joypad 1)
		move.b	d0,($A1000B).l	; init port 2 (joypad 2)
		move.b	d0,($A1000D).l	; init port 3 (extra)
    	bsr.w	sndDriverInput
		move.w	#0,($A11100).l	; start	the Z80
		rts	
; End of function JoypadInit

Now the interrupts are ready for the driver.

Installing the new Reloader Routine

Build errors will occur if you do not add the Sonic 2 driver reloader routine, as it does not exist in Sonic 1. find:

; End of function PalToCRAM

and add below:

; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
; Input our music/sound selection to the sound driver.

sndDriverInput:
	lea	(Music_to_play&$00FFFFFF).l,a0
	lea	(Z80_RAM+zComRange).l,a1 ; $A01B80
	cmpi.b	#$80,8(a1)	; If this (zReadyFlag) isn't $80, the driver is processing a previous sound request.
	bne.s	loc_10C4	; So we'll wait until at least the next frame before putting anything in there.
	_move.b	0(a0),d0
	beq.s	loc_10A4
	_clr.b	0(a0)
	bra.s	loc_10AE
; ---------------------------------------------------------------------------

loc_10A4:
	move.b	4(a0),d0	; If there was something in Music_to_play_2, check what that was. Else, just go to the loop.
	beq.s	loc_10C4
	clr.b	4(a0)

loc_10AE:		; Check that the sound is not FE or FF
	move.b	d0,d1	; If it is, we need to put it in $A01B83 as $7F or $80 respectively
	subi.b	#$FE,d1
	bcs.s	loc_10C0
	addi.b	#$7F,d1
	move.b	d1,3(a1)
	bra.s	loc_10C4
; ---------------------------------------------------------------------------

loc_10C0:
	move.b	d0,8(a1)

loc_10C4:
	moveq	#4-1,d1
				; FFE4 (Music_to_play_2) goes to 1B8C (zMusicToPlay),
-	move.b	1(a0,d1.w),d0	; FFE3 goes to 1B8B, (unknown)
	beq.s	+		; FFE2 (SFX_to_play_2) goes to 1B8A (zSFXToPlay2),
	tst.b	9(a1,d1.w)	; FFE1 (SFX_to_play) goes to 1B89 (zSFXToPlay).
	bne.s	+
	clr.b	1(a0,d1.w)
	move.b	d0,9(a1,d1.w)
+
	dbf	d1,-
	rts
; End of function sndDriverInput

Now that the driver's reloader routine in place, we have to make a few other changes so that it can be utilized.

Updating the playback routines

Now, update the playback routines so that the sound driver will actually play music and sounds. The first routine we will update is the loader. This routine is what calls the driver itself so that sound is playable. Find:

GameClrRAM:
		move.l	d7,(a6)+
		dbf	d6,GameClrRAM	; fill RAM ($0000-$FDFF) with $0
		bsr.w	VDPSetupGame
		bsr.w	SoundDriverLoad
		bsr.w	JoypadInit
		move.b	#0,($FFFFF600).w ; set Game Mode to Sega Screen

and change it to:

GameClrRAM:
		move.l	d7,(a6)+
		dbf	d6,GameClrRAM	; fill RAM ($0000-$FDFF) with $0
		bsr.w	VDPSetupGame
		bsr.w	JmpTo_SoundDriverLoad
		bsr.w	JoypadInit
		move.b	#0,($FFFFF600).w ; set Game Mode to Sega Screen

Now find:

SoundDriverLoad:			; XREF: GameClrRAM; TitleScreen
		nop	
		move.w	#$100,($A11100).l ; stop the Z80
		move.w	#$100,($A11200).l ; reset the Z80
		lea	(Kos_Z80).l,a0	; load sound driver
		lea	($A00000).l,a1
		bsr.w	KosDec		; decompress
		move.w	#0,($A11200).l
		nop	
		nop	
		nop	
		nop	
		move.w	#$100,($A11200).l ; reset the Z80
		move.w	#0,($A11100).l	; start	the Z80
		rts	
; End of function SoundDriverLoad

and replace it with:

JmpTo_SoundDriverLoad: 
	nop
	jmp	(SoundDriverLoad).l

Now find:

TitleScreen:				; XREF: GameModeArray
		move.b	#$E4,d0
		bsr.w	PlaySound_Special ; stop music
		bsr.w	ClearPLC
		bsr.w	Pal_FadeFrom
		move	#$2700,sr
		bsr.w	SoundDriverLoad
		lea	($C00004).l,a6
		move.w	#$8004,(a6)
		move.w	#$8230,(a6)
		move.w	#$8407,(a6)
		move.w	#$9001,(a6)
		move.w	#$9200,(a6)
		move.w	#$8B03,(a6)
		move.w	#$8720,(a6)
		clr.b	($FFFFF64E).w
		bsr.w	ClearScreen
		lea	($FFFFD000).w,a1
		moveq	#0,d0
		move.w	#$7FF,d1

and change it to:

TitleScreen:				; XREF: GameModeArray
		move.b	#$FD,d0
		bsr.w	PlaySound ; stop music
		bsr.w	ClearPLC
		bsr.w	Pal_FadeFrom
		move	#$2700,sr
		bsr.w	JmpTo_SoundDriverLoad
		lea	($C00004).l,a6
		move.w	#$8004,(a6)
		move.w	#$8230,(a6)
		move.w	#$8407,(a6)
		move.w	#$9001,(a6)
		move.w	#$9200,(a6)
		move.w	#$8B03,(a6)
		move.w	#$8720,(a6)
		clr.b	($FFFFF64E).w
		bsr.w	ClearScreen
		lea	($FFFFD000).w,a1
		moveq	#0,d0
		move.w	#$7FF,d1

Next, you will work with the actual playback routines themselves. find:

; ---------------------------------------------------------------------------
; Subroutine to	play a sound or	music track
; ---------------------------------------------------------------------------

; ||||||||||||||| S U B	R O U T	I N E |||||||||||||||||||||||||||||||||||||||


PlaySound:
		move.b	d0,($FFFFF00A).w
		rts	
; End of function PlaySound

; ---------------------------------------------------------------------------
; Subroutine to	play a special sound/music (E0-E4)
;
; E0 - Fade out
; E1 - Sega
; E2 - Speed up
; E3 - Normal speed
; E4 - Stop
; ---------------------------------------------------------------------------

; ||||||||||||||| S U B	R O U T	I N E |||||||||||||||||||||||||||||||||||||||


PlaySound_Special:
		move.b	d0,($FFFFF00B).w
		rts	
; End of function PlaySound_Special

; ===========================================================================
; ---------------------------------------------------------------------------
; Unused sound/music subroutine
; ---------------------------------------------------------------------------

PlaySound_Unk:
		move.b	d0,($FFFFF00C).w
		rts

and replace it with:

; ---------------------------------------------------------------------------
; Subroutine to	play a sound or	music track
; ---------------------------------------------------------------------------

; ||||||||||||||| S U B	R O U T	I N E |||||||||||||||||||||||||||||||||||||||


PlayMusic:
	tst.b	(Music_to_play).w
	bne.s	+
	move.b	d0,(Music_to_play).w
	rts
+
	move.b	d0,(Music_to_play_2).w
	rts
; End of function PlayMusic

; ---------------------------------------------------------------------------
; Subroutine to	play a special sound/music (E0-E4)
;
; F9 - Fade out
; FA - Sega
; FB - Speed up
; FC - Normal speed
; FD - Stop
; ---------------------------------------------------------------------------

; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

; sub_1370
PlaySound:
	move.b	d0,(SFX_to_play).w
	rts
; End of function PlaySound


; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
; play a sound in alternating speakers (as in the ring collection sound)
; sub_1376:
PlaySoundStereo:
	move.b	d0,(SFX_to_play_2).w
	rts
; End of function PlaySoundStereo


; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
; play a sound if the source is onscreen
; sub_137C:
PlaySoundLocal:
	tst.b	render_flags(a0)
	bpl.s	+
	move.b	d0,(SFX_to_play).w
+
	rts
; End of function PlaySoundLocal

Now, the pause/unpause code needs a fix too. find:

PauseGame:				; XREF: Level_MainLoop; et al
		nop	
		tst.b	($FFFFFE12).w	; do you have any lives	left?
		beq.s	Unpause		; if not, branch
		tst.w	($FFFFF63A).w	; is game already paused?
		bne.s	loc_13BE	; if yes, branch
		btst	#7,($FFFFF605).w ; is Start button pressed?
		beq.s	Pause_DoNothing	; if not, branch

loc_13BE:
		move.w	#1,($FFFFF63A).w ; freeze time
		move.b	#1,($FFFFF003).w ; pause music

loc_13CA:
		move.b	#$10,($FFFFF62A).w
		bsr.w	DelayProgram
		tst.b	($FFFFFFE1).w	; is slow-motion cheat on?
		beq.s	Pause_ChkStart	; if not, branch
		btst	#6,($FFFFF605).w ; is button A pressed?
		beq.s	Pause_ChkBC	; if not, branch
		move.b	#4,($FFFFF600).w ; set game mode to 4 (title screen)
		nop	
		bra.s	loc_1404
; ===========================================================================

Pause_ChkBC:				; XREF: PauseGame
		btst	#4,($FFFFF604).w ; is button B pressed?
		bne.s	Pause_SlowMo	; if yes, branch
		btst	#5,($FFFFF605).w ; is button C pressed?
		bne.s	Pause_SlowMo	; if yes, branch

Pause_ChkStart:				; XREF: PauseGame
		btst	#7,($FFFFF605).w ; is Start button pressed?
		beq.s	loc_13CA	; if not, branch

loc_1404:				; XREF: PauseGame
		move.b	#$80,($FFFFF003).w

Unpause:				; XREF: PauseGame
		move.w	#0,($FFFFF63A).w ; unpause the game

Pause_DoNothing:			; XREF: PauseGame
		rts	
; ===========================================================================

Pause_SlowMo:				; XREF: PauseGame
		move.w	#1,($FFFFF63A).w
		move.b	#$80,($FFFFF003).w
		rts	
; End of function PauseGame

and replace it with:

PauseGame:
	nop
	tst.b	(Life_count).w	; do you have any lives left?
	beq.w	Unpause		; if not, branch
	tst.w	(Game_paused).w	; is game already paused?
	bne.s	+		; if yes, branch
	move.b	(Ctrl_1_Press).w,d0 ; is Start button pressed?
	or.b	(Ctrl_2_Press).w,d0 ; (either player)
	andi.b	#$80,d0
	beq.s	Pause_DoNothing	; if not, branch
+
	move.w	#1,(Game_paused).w	; freeze time
	move.b	#-2,(Music_to_play).w	; pause music

loc_13B2:
	move.b	#$10,(Delay_Time).w
	bsr.w	DelayProgram
	tst.b	(Slow_motion_flag).w	; is slow-motion cheat on?
	beq.s	Pause_ChkStart		; if not, branch
	btst	#6,(Ctrl_1_Press).w	; is button A pressed?
	beq.s	Pause_ChkBC		; if not, branch
	move.b	#4,(Game_Mode).w	; => TitleScreen
	nop
	bra.s	loc_13F2
; ===========================================================================
; loc_13D4:
Pause_ChkBC:
	btst	#4,(Ctrl_1_Held).w	; is button B pressed?
	bne.s	Pause_SlowMo		; if yes, branch
	btst	#5,(Ctrl_1_Press).w	; is button C pressed?
	bne.s	Pause_SlowMo		; if yes, branch
; loc_13E4:
Pause_ChkStart:
	move.b	(Ctrl_1_Press).w,d0	; is Start button pressed?
	or.b	(Ctrl_2_Press).w,d0	; (either player)
	andi.b	#$80,d0
	beq.s	loc_13B2	; if not, branch

loc_13F2:
	move.b	#-1,(Music_to_play).w
; loc_13F8:
Unpause:
	move.w	#0,(Game_paused).w
; return_13FE:
Pause_DoNothing:
	rts
; ===========================================================================
; loc_1400:
Pause_SlowMo:
	move.w	#1,(Game_paused).w
	move.b	#-1,(Music_to_play).w
	rts
; End of function PauseGame

Now, you will just have to make some replacements in the game code that utiizes our new routines so that playback will work as we want it to (playsound_special needs to be changed to playsound or playsoundsetreo and playsound needs to be changed to playmusic.) That and music fixes will be in the next section.

Misc. Symbol and music fixes

If you try to build it now, you will get build errors about a missing PlaySound_Special (The old Sonic 1 sound effect routine.) This is not a problem; by the time you finish this section, that one will be corrected. Let's fix them all. First find:

SegaScreen:				; XREF: GameModeArray
		move.b	#$E4,d0
		bsr.w	PlaySound_Special ; stop music

and replace it with:

SegaScreen:				; XREF: GameModeArray
		move.b	#$FD,d0
		bsr.w	PlaySound ; stop music

Now find:

		move.b	#$E1,d0
		bsr.w	PlaySound_Special ; play "SEGA"	sound

and replace it with:

		move.b	#$FA,d0
		bsr.w	PlaySound ; play "SEGA"	sound

Now find:

TitleScreen:				; XREF: GameModeArray
		move.b	#$E4,d0
		bsr.w	PlaySound_Special ; stop music

and replace it with:

TitleScreen:				; XREF: GameModeArray
		move.b	#$FD,d0
		bsr.w	PlaySound ; stop music

Now find:

		moveq	#1,d0		; load title screen pallet
		bsr.w	PalLoad1
		move.b	#$8A,d0		; play title screen music
		bsr.w	PlaySound_Special

and replace it with:

		moveq	#1,d0		; load title screen pallet
		bsr.w	PalLoad1
		move.b	#$99,d0		; play title screen music
		bsr.w	PlayMusic

Now find:

Title_PlayRing:
		move.b	#1,(a0,d1.w)	; activate cheat
		move.b	#$B5,d0		; play ring sound when code is entered
		bsr.w	PlaySound_Special
		bra.s	Title_CountC

and replace it with:

Title_PlayRing:
		move.b	#1,(a0,d1.w)	; activate cheat
		move.b	#$B5,d0		; play ring sound when code is entered
		bsr.w	PlaySoundStereo
		bra.s	Title_CountC

Now find:

LevSel_PlaySnd:
		bsr.w	PlaySound_Special
		bra.s	LevelSelect

and replace it with:

LevSel_PlaySnd:
		bsr.w	PlaySound
		bra.s	LevelSelect

Now find:

LevSel_Credits:				; XREF: LevelSelect
		move.b	#$1C,($FFFFF600).w ; set screen	mode to	$1C (Credits)
		move.b	#$91,d0
		bsr.w	PlaySound_Special ; play credits music

and replace it with:

LevSel_Credits:				; XREF: LevelSelect
		move.b	#$1C,($FFFFF600).w ; set screen	mode to	$1C (Credits)
		move.b	#$9E,d0
		bsr.w	PlayMusic ; play credits music

Now find:

		move.b	#$E0,d0
		bsr.w	PlaySound_Special ; fade out music
		rts	
; ===========================================================================
; ---------------------------------------------------------------------------
; Level	select - level pointers
; ---------------------------------------------------------------------------

and replace it with:

		move.b	#$F9,d0
		bsr.w	PlaySound ; fade out music
		rts	
; ===========================================================================
; ---------------------------------------------------------------------------
; Level	select - level pointers
; ---------------------------------------------------------------------------

Now find:

loc_33E4:				; XREF: Demo
		andi.b	#$80,($FFFFF605).w ; is	Start button pressed?
		bne.w	Title_ChkLevSel	; if yes, branch
		tst.w	($FFFFF614).w
		bne.w	loc_33B6
		move.b	#$E0,d0
		bsr.w	PlaySound_Special ; fade out music

and replace it with:

loc_33E4:				; XREF: Demo
		andi.b	#$80,($FFFFF605).w ; is	Start button pressed?
		bne.w	Title_ChkLevSel	; if yes, branch
		tst.w	($FFFFF614).w
		bne.w	loc_33B6
		move.b	#$F9,d0
		bsr.w	PlaySound ; fade out music

Now find:

Level:					; XREF: GameModeArray
		bset	#7,($FFFFF600).w ; add $80 to screen mode (for pre level sequence)
		tst.w	($FFFFFFF0).w
		bmi.s	loc_37B6
		move.b	#$E0,d0
		bsr.w	PlaySound_Special ; fade out music

and replace it with:

Level:					; XREF: GameModeArray
		bset	#7,($FFFFF600).w ; add $80 to screen mode (for pre level sequence)
		tst.w	($FFFFFFF0).w
		bmi.s	loc_37B6
		move.b	#$F9,d0
		bsr.w	PlaySound ; fade out music

Now find:

Level_PlayBgm:
		lea	(MusicList).l,a1 ; load	music playlist
		move.b	(a1,d0.w),d0	; add d0 to a1
		bsr.w	PlaySound	; play music
		move.b	#$34,($FFFFD080).w ; load title	card object

and replace it with:

Level_PlayBgm:
		lea	(MusicList).l,a1 ; load	music playlist
		move.b	(a1,d0.w),d0	; add d0 to a1
		bsr.w	PlayMusic	; play music
		move.b	#$34,($FFFFD080).w ; load title	card object

Now find:

		move.w	#$B7,d0
		bsr.w	PlaySound_Special ; play sound $B7 (rumbling)

loc_3D54:

and replace it with:

		move.w	#$B7,d0
		bsr.w	PlaySound ; play sound $B7 (rumbling)

loc_3D54:

Now find:

		move.w	#$D0,d0
		jsr	(PlaySound_Special).l ;	play rushing water sound

loc_3E90:

and replace it with:

		move.w	#$F0,d0
		jsr	(PlaySound).l ;	play rushing water sound

loc_3E90:

Now find:

		move.w	#$D0,d0
		jsr	(PlaySound_Special).l ;	play water sound

locret_3FBE:
		rts	
; End of function LZWaterSlides

and replace it with:

		move.w	#$F0,d0
		jsr	(PlaySound).l ;	play water sound

locret_3FBE:
		rts	
; End of function LZWaterSlides

Now find:

SpecialStage:				; XREF: GameModeArray
		move.w	#$CA,d0
		bsr.w	PlaySound_Special ; play special stage entry sound

and replace it with:

SpecialStage:				; XREF: GameModeArray
		move.w	#$CA,d0
		bsr.w	PlaySound ; play special stage entry sound

Now find:

		move.w	#$40,($FFFFF782).w ; set stage rotation	speed
		move.w	#$89,d0
		bsr.w	PlaySound	; play special stage BG	music

and change it to:

		move.w	#$40,($FFFFF782).w ; set stage rotation	speed
		move.w	#$92,d0
		bsr.w	PlayMusic	; play special stage BG	music

Now find:

		move.w	#$8E,d0
		jsr	(PlaySound_Special).l ;	play end-of-level music
		lea	($FFFFD000).w,a1
		moveq	#0,d0
		move.w	#$7FF,d1

SS_EndClrObjRam:

and change it to:

		move.w	#$9A,d0
		jsr	(PlayMusic).l ;	play end-of-level music
		lea	($FFFFD000).w,a1
		moveq	#0,d0
		move.w	#$7FF,d1

SS_EndClrObjRam:

Now find:

		move.w	#$CA,d0
		bsr.w	PlaySound_Special ; play special stage exit sound
		bsr.w	Pal_MakeFlash
		rts	
; ===========================================================================

SS_ToSegaScreen:

and change it to:

		move.w	#$CA,d0
		bsr.w	PlaySound ; play special stage exit sound
		bsr.w	Pal_MakeFlash
		rts	
; ===========================================================================

SS_ToSegaScreen:

Now find:

Cont_ClrObjRam:
		move.l	d0,(a1)+
		dbf	d1,Cont_ClrObjRam ; clear object RAM

		move.l	#$70000002,($C00004).l
		lea	(Nem_TitleCard).l,a0 ; load title card patterns
		bsr.w	NemDec
		move.l	#$60000002,($C00004).l
		lea	(Nem_ContSonic).l,a0 ; load Sonic patterns
		bsr.w	NemDec
		move.l	#$6A200002,($C00004).l
		lea	(Nem_MiniSonic).l,a0 ; load continue screen patterns
		bsr.w	NemDec
		moveq	#10,d1
		jsr	(ContScrCounter).l	; run countdown	(start from 10)
		moveq	#$12,d0
		bsr.w	PalLoad1	; load continue	screen pallet
		move.b	#$90,d0
		bsr.w	PlaySound	; play continue	music

and change it to:

Cont_ClrObjRam:
		move.l	d0,(a1)+
		dbf	d1,Cont_ClrObjRam ; clear object RAM

		move.l	#$70000002,($C00004).l
		lea	(Nem_TitleCard).l,a0 ; load title card patterns
		bsr.w	NemDec
		move.l	#$60000002,($C00004).l
		lea	(Nem_ContSonic).l,a0 ; load Sonic patterns
		bsr.w	NemDec
		move.l	#$6A200002,($C00004).l
		lea	(Nem_MiniSonic).l,a0 ; load continue screen patterns
		bsr.w	NemDec
		moveq	#10,d1
		jsr	(ContScrCounter).l	; run countdown	(start from 10)
		moveq	#$12,d0
		bsr.w	PalLoad1	; load continue	screen pallet
		move.b	#$9C,d0
		bsr.w	PlayMusic	; play continue	music

Now find:

		move.b	#$E0,d0
		bsr.w	PlaySound_Special ; fade out music

Obj81_Run:				; XREF: Obj81_Index

and replace it with:

		move.b	#$F9,d0
		bsr.w	PlaySound ; fade out music

Obj81_Run:				; XREF: Obj81_Index

Now find:

EndingSequence:				; XREF: GameModeArray
		move.b	#$E4,d0
		bsr.w	PlaySound_Special ; stop music
		bsr.w	Pal_FadeFrom
		lea	($FFFFD000).w,a1
		moveq	#0,d0
		move.w	#$7FF,d1

and change it to:

EndingSequence:				; XREF: GameModeArray
		move.b	#$FD,d0
		bsr.w	PlaySound ; stop music
		bsr.w	Pal_FadeFrom
		lea	($FFFFD000).w,a1
		moveq	#0,d0
		move.w	#$7FF,d1

Now find:

		move.w	#$8B,d0
		bsr.w	PlaySound	; play ending sequence music
		btst	#6,($FFFFF604).w ; is button A pressed?
		beq.s	End_LoadSonic	; if not, branch
		move.b	#1,($FFFFFFFA).w ; enable debug	mode

End_LoadSonic:

and change it to:

		move.w	#$95,d0
		bsr.w	PlayMusic	; play ending sequence music
		btst	#6,($FFFFF604).w ; is button A pressed?
		beq.s	End_LoadSonic	; if not, branch
		move.b	#1,($FFFFFFFA).w ; enable debug	mode

End_LoadSonic:

Now find:

loc_6ED0:
		move.w	#$8C,d0
		bsr.w	PlaySound	; play boss music

and replace it with:

loc_6ED0:
		move.w	#$93,d0
		bsr.w	PlayMusic	; play boss music

Now find:

		move.w	#$B7,d0
		bsr.w	PlaySound_Special ; play rumbling sound

loc_6F28:

and replace it with:

		move.w	#$B7,d0
		bsr.w	PlaySound ; play rumbling sound

loc_6F28:

Now find:

loc_6F4A:
		move.w	#$8C,d0
		bsr.w	PlaySound	; play boss music

and replace it with:

loc_6F4A:
		move.w	#$93,d0
		bsr.w	PlayMusic	; play boss music


SCHG How-To Guide: Sonic the Hedgehog (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix the SEGA Sound | Display the Press Start Button Text | Fix the Level Select Menu | Fix the Hidden Points Bug | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix the Walk-Jump Bug | Correct Drowning Bugs | Fix the Death Boundary Bug | Fix the Camera Follow Bug | Fix Song Restoration Bugs | Fix the HUD Blinking | Fix the Level Select Graphics Bug
Changing Design Choices
Change Spike Behavior | Fix Special Stage Jumping Physics | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Remove the Speed Cap | Port the REV01 Background Effects | Port Sonic 2's Level Art Loader | Retain Rings Between Acts | Add Sonic 2 (Simon Wai Prototype) Level Select | Improve ObjectMove Subroutines
Adding Features
Add Spin Dash ( Part 1 / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor
Sound Features
Expand Music Index From $94 to $9F | Extend Music Slots | Play Different Songs Per Act | Expand Music Index to Start at $00 | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver
Extending the Game
Load Chunks From ROM | Add Extra Characters | Make an Alternative Title Screen | Use Dynamic Tilesets | Make GHZ Load Alternate Art | Add a New Zone | Set Up the Goggle Monitor | Add New Moves | Add a Dynamic Collision System | Dynamic Special Stage Walls System | Extend Sprite Mappings and Art Limit | Enigma Credits
Miscellaneous
Convert the Hivebrain 2005 Disassembly to ASM68K
Split Disassembly Guides
Set Up a Split Disassembly | Basic Level Editing | Basic Art Editing | Basic ASM Editing (Spin Dash)
SCHG How-To Guide: Sonic the Hedgehog 2 (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix Super Sonic Bugs | Use Correct Height When Roll Jumping | Fix Jump Height Bug When Exiting Water | Fix Spin Dash Code and Add Spin Dash Speeds | Fix Screen Boundary Spin Dash Bug | Correct Drowning Bugs | Fix Camera Y Position for Tails | Fix Tails Subanimation Error | Fix Tails' Respawn Speeds | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix Rexon Crash | Fix Monitor Collision Bug | Fix EHZ Deformation Bug | Correct CPZ Boss Attack Behavior | Fix Bug in ARZ Boss Arrow's Platform Behavior | Fix ARZ Boss Walking on Air Glitch | Fix ARZ Boss Sprite Behavior | Fix Multiple CNZ Boss Bugs | Fix HTZ Background Scrolling Mountains | Fix OOZ Launcher Speed Up Glitch | Fix DEZ Giant Mech Collision Glitch | Fix Boss Deconstruction Behavior | Fix Speed Bugs
Design Choices
Remove the Air Speed Cap | Disable Floor Collision While Dying | Modify Super Sonic Transformation Methods & Behavior | Enable/Disable Tails in Certain Levels | Collide with Water After Being Hurt | Retain Rings When Returning at a Star Post | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Insert LZ Water Ripple Effect | Restore Lost CPZ Boss Feature | Prevent SCZ Tornado Spin Dash Death | Improve ObjectMove Subroutines | Port S3K Rings Manager | Port S3K Object Manager | Port S3K Priority Manager | Edit Level Order with ASM‎ | Alter Ring Requirements in Special Stages | Make Special Stage Characters Use Normal DPLCs | Speed Up Ring Loss Process
Adding Features
Create Insta-kill and High Jump Monitors | Create Clone and Special Stage Monitors | Port Knuckles
Sound Features
Port Sonic 1 Sound Driver | Port Sonic 2 Clone Driver | Port Sonic 3 Sound Driver | Expand the Music Index to Start at $00 (Sonic 2 Clone Driver Version)
Extending the Game
Extend the Level Index Past $10 | Extend the Level Select | Extend Water Tables | Add Extra Characters | Free Up 2 Universal SSTs