SCHG How-to

Port Sonic 3k's rings manager to Sonic 2

From Sonic Retro

(Original guide by shobiz)

(Updated for Github by Pokepunch)

Sonic 3k's ring management system differs drastically from Sonic 2's in one major way: Levels in Sonic 3k don't use groups of rings, they only have individual rings, and as a result Sonic 3k can read ring placement data directly from ROM instead of having to expand it inside RAM, allowing more rings in less RAM space. Setting up a similar system in Sonic 2 also allows more rings in levels and frees up RAM space which can be used for other purposes.


The first step is downloading this zip file, which contains the rings conversion program (compiled with Visual C++ 6.0), the program source code (written in C), and a text file containing the names of all the rings files. Extract this zip inside yourdisassemblypath/level/rings. The purpose of the rings conversion program is to take a Sonic 2-style rings data file, expand all ring groups and sort the file according to X position. rings.txt contains the names of all files to be converted - you can add more files to this list if you need to. The source code for the program is included so that it can be compiled for other platforms.

After you've done this, open up your disassembly. The first step is adding some equates. At the very top of the RAM equates section, add this:

Max_Rings = 511 ; default. maximum number possible is 759
    if Max_Rings > 759
    fatal "Maximum number of rings possible is 759"

Rings_Space = (Max_Rings+1)*2

Then, after the Ring_Positions equate, add

Ring_start_addr_ROM =		ramaddr( Ring_Positions+Rings_Space )
Ring_end_addr_ROM =			ramaddr( Ring_Positions+Rings_Space+4 )
Ring_start_addr_ROM_P2 =	ramaddr( Ring_Positions+Rings_Space+8 )
Ring_end_addr_ROM_P2 =		ramaddr( Ring_Positions+Rings_Space+12 )
Ring_free_RAM_start =		ramaddr( Ring_Positions+Rings_Space+16 )

Finally, after the Level_started_flag equate, for Xenowhirl add

Ring_start_addr_RAM =        ramaddr( $FFFFF712 )
Ring_start_addr_RAM_P2 =    ramaddr( $FFFFF714 )

On GitHub replace the values from Ring_start_addr up to and including Ring_end_addr_P2 with:

Ring_start_addr_RAM:	ds.w	1
Ring_start_addr_RAM_P2:	ds.w	1
						ds.w	2	; 2 words freed here

The next step is to go to all the ring incbins (right after Off_Rings). Change all these incbins from something like "level/rings/EHZ_1.bin" to "level/rings/EHZ_1_INDIVIDUAL.bin". If your editor supports regular expression search and replace, this is as simple as searching for "level/rings/(.+)\.bin" (including the quotes) and replacing it with "level/rings/\1_INDIVIDUAL.bin" (once again including the quotes). If it doesn't, get a better text editor =P

After you've done this, the rings manager needs to be changed completely to read ring positions from ROM and ring statuses from RAM. The basic idea behind all the changes is to use two separate pointers, one for RAM and one for ROM. There are too many changes to explain individually though, so just replace everything from:

RingsManager to off_1736A (inclusive) with the contents of this file for Xenowhirl


RingsManager to MapUnc_Rings (inclusive) with the contents of this file for Github.

By default, the rings manager includes S3K-type ring attraction, but for it to work properly you're going to have to either port the attracted ring yourself or use this ported object.

The penultimate change is fixing a bug with the teleport monitors. The teleport monitor stores a list of all RAM locations needing to be swapped at byte_12C52. This list of RAM variables references the old ring manager variables, so when you use the teleport monitor the rings manager will screw up quite badly (badly enough to cause Kega to have a Windows crash :o). To fix this on Xenowhirl, change byte_12C52 to:

	dc.w $B008, $B048, $1B
	dc.w $F76E, $F78C, 0
	dc.w $FC00, $F78E, 0
	dc.w $F770, $F778, 3
	dc.w $F760, $FEC0, 2
	dc.w Ring_start_addr_RAM, Ring_start_addr_RAM_P2, 0
	dc.w Ring_start_addr_ROM, Ring_start_addr_ROM_P2, 3
	dc.w $F71C, $F724, 3
	dc.w $EE00, $EE20, $F
	dc.w $F7DA, $F7DC, 0
	dc.w $EEC8, $EEF8, 3
	dc.w $EED0, $EED4, 1
	dc.w $EED8, $EEDA, 0
	dc.w $EE40, $EE48, 3
	dc.w $EE50, $EE58, 3
	dc.w $EE60, $EE80, $F
	dc.w $EEA0, $EEA8, 3
	dc.w $EEB0, $EEB8, 1
	dc.w $E500, $E600, $7F

Or for Github, change teleport_swap_table to this:

	dc.w MainCharacter+x_pos,	Sidekick+x_pos,			$1B
	dc.w Camera_X_pos_last,		Camera_X_pos_last_P2,		  0
	dc.w Obj_respawn_index,		Obj_respawn_index_P2,		  0
	dc.w Obj_load_addr_right,	Obj_load_addr_2,		  3
	dc.w Sonic_top_speed,		Tails_top_speed,		  2
	dc.w Ring_start_addr_RAM,		Ring_start_addr_RAM_P2,		  0
	dc.w Ring_start_addr_ROM,		Ring_start_addr_ROM_P2,		  3
	dc.w CNZ_Visible_bumpers_start,	CNZ_Visible_bumpers_start_P2,  3
	dc.w Camera_X_pos,		Camera_X_pos_P2,		 $F
	dc.w Camera_X_pos_coarse,	Camera_X_pos_coarse_P2,		  0
	dc.w Camera_Min_X_pos,		Tails_Min_X_pos,		  3
	dc.w Horiz_scroll_delay_val,	Horiz_scroll_delay_val_P2,	  1
	dc.w Camera_Y_pos_bias,		Camera_Y_pos_bias_P2,		  0
	dc.w Horiz_block_crossed_flag,	Horiz_block_crossed_flag_P2,	  3
	dc.w Scroll_flags,		Scroll_flags_P2,		  3
	dc.w Camera_RAM_copy,		Camera_P2_copy,			 $F
	dc.w Scroll_flags_copy,		Scroll_flags_copy_P2,		  3
	dc.w Camera_X_pos_diff,		Camera_X_pos_diff_P2,		  1
	dc.w Sonic_Pos_Record_Buf,	Tails_Pos_Record_Buf,		$7F

Also for Xenowhirl, since we've added one more entry to this table, we'll need to modify the loop count on this line in loc_12ABA:

moveq	#$11,d2

The $11 needs to be changed to $12 since one more entry has been added, but while we're at it we might as well enable dynamic calculation of the loop count so that we don't have to change it manually in case this list needs additional modifications in the future, so change this line to:

moveq	#(((byte_12C52_end-byte_12C52)/6)-1),d2

This change has already been made on the GitHub version. After these two changes have been made, the rings manager should continue working even after a teleport occurs.

Finally, open up build.bat in the main disassembly folder, and after these lines:

REM // clear the output window


REM // run the rings conversion program
cd level/rings
cd ../..
Whenever the rings conversion program runs, it outputs a "beginning conversion", what errors (if any) were encountered during conversion, and at the end, the total number of successful and unsuccessful conversions. I've tested this out pretty thoroughly and it seems to work perfectly, but any problems encountered should be posted in
Sonic Retro
the Retro topic
. The only thing I've noticed so far is that 2P mode randomly lags sometimes, probably because it's slower to read ring data from ROM than from RAM.


The main advantage of doing this is that quite a bit of RAM is freed up ($1F0 bytes under default settings, which are 511 rings max). If you want more free RAM or more rings, you can change the Max_Rings equate, and everything else will be adjusted automatically. The first byte of the free RAM space obtained this way is at Ring_free_RAM_start, it continues all the way up to $EDFF, and it can obviously be used for whatever you like.

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