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" endif 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:
byte_12C52: 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 byte_12C52_end:
Or for Github, change teleport_swap_table to this:
teleport_swap_table: 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 teleport_swap_table_end:
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:
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:
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 cls
REM // run the rings conversion program cd level/rings rings.exe 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 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.