Actions

SCHG How-to

Port Sonic 2 Final Sound Driver to Sonic 1

From Sonic Retro

Revision as of 11:42, 8 October 2014 by Kazblox (talk | contribs) (Corrected weird terminology such as "disasm2005" and "disasm2007")

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 I would prefer the Sonic the Hedgehog 3 sound driver for its rich and diverse and effects and music. I guess 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:

  • First off, we need to insert the macros from the S2 Xenowhirl disassembly; this makes it relatively easier to work with.
  • Next, we will have to update the vertical and horizontal interrupts. Every time the Z80 is stopped, we need to call the sound driver input routine.
  • We also need the sound driver input routine.
  • We need to alter the play sound routines: Sonic 2 sound routines are needed instead of Sonic 1 ones (pause and unpause included.)
  • All the places a sound is played need 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 we did not add the macros, we would have to expand them instead and make our work a lot harder. So, let's insert the macros. First, create a new file called "s2.macrosetup.asm" and put this in it: <asm>

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

</asm> Next, we need to get the Hivebrain disassembly fixed so that we can use these macros, which is rather easy. Open sonic1.asm and find: <asm> 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: </asm> now replace it with: <asm> 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: </asm> This will make our 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: <asm> loc_B5E: ; XREF: loc_B88 jsr (sub_71B4C).l

</asm> and remove that whole routine up there. Next, we will have to add calls for the Sonic 2 sound driver reloader. Find: <asm> 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 </asm>

and change it to: <asm> 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 </asm>

Now find: <asm> loc_C22: ; XREF: loc_BC8 move.w ($FFFFF624).w,(a5) move.w #0,($A11100).l bra.w loc_B5E </asm> and change it to: <asm> loc_C22: ; XREF: loc_BC8 move.w ($FFFFF624).w,(a5) bsr.w sndDriverInput move.w #0,($A11100).l bra.w loc_B64 </asm>

Now find: <asm> loc_D50: </asm> and add below: <asm> bsr.w sndDriverInput </asm> Now find: <asm> 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

</asm> and change it to: <asm> 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 </asm> Now find: <asm> loc_F54: </asm> and add below: <asm> bsr.w sndDriverInput </asm> Now find: <asm> 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 </asm> and change to: <asm> 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 </asm> Now find: <asm> 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 </asm> and change it to: <asm> 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 </asm> Now find: <asm> 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 </asm> and change it to: <asm> 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

</asm>

Now the interrupts are ready for the driver.

Installing the new Reloader Routine

We will have build errors if we do not fix this next one: the Sonic 2 driver reloader routine does not exist in Sonic 1. It's time that we add it to our hack. find: <asm>

End of function PalToCRAM

</asm> and add below: <asm>

||||||||||||||| 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

</asm>

Now that we have 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 we will 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 we can have sound. Find: <asm> 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 </asm> and change it to: <asm> 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 </asm> Now find: <asm> 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

</asm> and replace it with: <asm> JmpTo_SoundDriverLoad: nop jmp (SoundDriverLoad).l </asm> Now find: <asm> 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 </asm> and change it to: <asm> 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 </asm> Next we will work with the actual playback routines themselves. find: <asm>

---------------------------------------------------------------------------
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 </asm> and replace it with: <asm>

---------------------------------------------------------------------------
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

</asm> Next we fix the pause/unpause code. find: <asm> 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

</asm> and replace it with: <asm> 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

</asm> Now we 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: <asm> SegaScreen: ; XREF: GameModeArray move.b #$E4,d0 bsr.w PlaySound_Special ; stop music </asm> and replace it with: <asm> SegaScreen: ; XREF: GameModeArray move.b #$FD,d0 bsr.w PlaySound ; stop music </asm> Now find: <asm> move.b #$E1,d0 bsr.w PlaySound_Special ; play "SEGA" sound </asm> and replace it with: <asm> move.b #$FA,d0 bsr.w PlaySound ; play "SEGA" sound </asm> Now find: <asm> TitleScreen: ; XREF: GameModeArray move.b #$E4,d0 bsr.w PlaySound_Special ; stop music </asm> and replace it with: <asm> TitleScreen: ; XREF: GameModeArray move.b #$FD,d0 bsr.w PlaySound ; stop music </asm> Now find: <asm> moveq #1,d0 ; load title screen pallet bsr.w PalLoad1 move.b #$8A,d0 ; play title screen music bsr.w PlaySound_Special </asm> and replace it with: <asm> moveq #1,d0 ; load title screen pallet bsr.w PalLoad1 move.b #$99,d0 ; play title screen music bsr.w PlayMusic </asm> Now find: <asm> 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 </asm> and replace it with: <asm> 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 </asm> Now find: <asm> LevSel_PlaySnd: bsr.w PlaySound_Special bra.s LevelSelect </asm> and replace it with: <asm> LevSel_PlaySnd: bsr.w PlaySound bra.s LevelSelect </asm> Now find: <asm> 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 </asm> and replace it with: <asm> 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 </asm> Now find: <asm> move.b #$E0,d0 bsr.w PlaySound_Special ; fade out music rts

===========================================================================
---------------------------------------------------------------------------
Level select - level pointers
---------------------------------------------------------------------------

</asm> and replace it with: <asm> move.b #$F9,d0 bsr.w PlaySound ; fade out music rts

===========================================================================
---------------------------------------------------------------------------
Level select - level pointers
---------------------------------------------------------------------------

</asm> Now find: <asm> 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 </asm> and replace it with: <asm> 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 </asm> Now find: <asm> 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 </asm> and replace it with: <asm> 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 </asm> Now find: <asm> 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 </asm> and replace it with: <asm> 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 </asm> Now find: <asm> move.w #$B7,d0 bsr.w PlaySound_Special ; play sound $B7 (rumbling)

loc_3D54: </asm> and replace it with: <asm> move.w #$B7,d0 bsr.w PlaySound ; play sound $B7 (rumbling)

loc_3D54: </asm> Now find: <asm> move.w #$D0,d0 jsr (PlaySound_Special).l ; play rushing water sound

loc_3E90: </asm> and replace it with: <asm> move.w #$F0,d0 jsr (PlaySound).l ; play rushing water sound

loc_3E90: </asm> Now find: <asm> move.w #$D0,d0 jsr (PlaySound_Special).l ; play water sound

locret_3FBE: rts

End of function LZWaterSlides

</asm> and replace it with: <asm> move.w #$F0,d0 jsr (PlaySound).l ; play water sound

locret_3FBE: rts

End of function LZWaterSlides

</asm> Now find: <asm> SpecialStage: ; XREF: GameModeArray move.w #$CA,d0 bsr.w PlaySound_Special ; play special stage entry sound </asm> and replace it with: <asm> SpecialStage: ; XREF: GameModeArray move.w #$CA,d0 bsr.w PlaySound ; play special stage entry sound </asm> Now find: <asm> move.w #$40,($FFFFF782).w ; set stage rotation speed move.w #$89,d0 bsr.w PlaySound ; play special stage BG music </asm> and change it to: <asm> move.w #$40,($FFFFF782).w ; set stage rotation speed move.w #$92,d0 bsr.w PlayMusic ; play special stage BG music </asm> Now find: <asm> 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: </asm> and change it to: <asm> 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: </asm> Now find: <asm> move.w #$CA,d0 bsr.w PlaySound_Special ; play special stage exit sound bsr.w Pal_MakeFlash rts

===========================================================================

SS_ToSegaScreen: </asm> and change it to: <asm> move.w #$CA,d0 bsr.w PlaySound ; play special stage exit sound bsr.w Pal_MakeFlash rts

===========================================================================

SS_ToSegaScreen: </asm> Now find: <asm> 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 </asm> and change it to: <asm> 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 </asm> Now find: <asm> move.b #$E0,d0 bsr.w PlaySound_Special ; fade out music

Obj81_Run: ; XREF: Obj81_Index </asm> and replace it with: <asm> move.b #$F9,d0 bsr.w PlaySound ; fade out music

Obj81_Run: ; XREF: Obj81_Index </asm> Now find: <asm> 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 </asm> and change it to: <asm> 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 </asm> Now find: <asm> 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: </asm> and change it to: <asm> 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: </asm> Now find: <asm> loc_6ED0: move.w #$8C,d0 bsr.w PlaySound ; play boss music </asm> and replace it with: <asm> loc_6ED0: move.w #$93,d0 bsr.w PlayMusic ; play boss music </asm> Now find: <asm> move.w #$B7,d0 bsr.w PlaySound_Special ; play rumbling sound

loc_6F28: </asm> and replace it with: <asm> move.w #$B7,d0 bsr.w PlaySound ; play rumbling sound

loc_6F28: </asm> Now find: <asm> loc_6F4A: move.w #$8C,d0 bsr.w PlaySound ; play boss music </asm> and replace it with: <asm> loc_6F4A: move.w #$93,d0 bsr.w PlayMusic ; play boss music </asm>


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 | Fix a remember sprite related bug
Changing Design Choices
Change Spike Behavior | Collide with Water After Being Hurt | 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 | Port Sonic 2 Level Select
Adding Features
Add Spin Dash ( Part 1 / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor | Add Super Sonic | Add the Air Roll
Sound Features
Expand the Sound Index | Play Different Songs Per Act | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver | Port Flamewing's Sonic 3 & Knuckles Sound Driver | Change The SEGA Sound
Extending the Game
Load Chunks From ROM | Add Extra Characters | Make an Alternative Title Screen | Use Dynamic Tilesets | Make GHZ Load Alternate Art | Make Ending 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 | Use Dynamic Palettes
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 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 | Fix 14 Continues Cheat | Fix Debug Mode Crash | Fix 99+ Lives | Fix Sonic 2's Sega Screen
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 | Change spike behaviour in Sonic 2
Adding Features
Create Insta-kill and High Jump Monitors | Create Clone and Special Stage Monitors | Port Knuckles
Sound Features
Expand Music Index to Start at $00 | Port Sonic 1 Sound Driver | Port Sonic 2 Clone Driver | Port Sonic 3 Sound Driver | Port Flamewing's Sonic 3 & Knuckles Sound Driver | Expand the Music Index to Start at $00 (Sonic 2 Clone Driver Version) | Play Different Songs Per Act
Extending the Game
Extend the Level Index Past $10 | Extend the Level Select | Extend Water Tables | Add Extra Characters | Free Up 2 Universal SSTs