Port Sonic 2 Final Sound Driver to Sonic 1
From Sonic Retro
(Original guide by Kramlat)
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.
Contents
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
|Port Sonic 2 Final Sound Driver to Sonic 1]]