Actions

SCHG How-to

Port Sonic 3k's rings manager to Sonic 2

From Sonic Retro

Revision as of 14:35, 26 April 2008 by SOTI (talk | contribs)

(Original guide by shobiz)

Introduction

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.

Instructions

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: <asm>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</asm>

Then, after the Ring_Positions equate, add <asm>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 )</asm>

Finally, after the Level_started_flag equate, add <asm>Ring_start_addr_RAM = ramaddr( $FFFFF712 ) Ring_start_addr_RAM_P2 = ramaddr( $FFFFF714 )</asm>

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. 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, change byte_12C52 to:

<asm>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:</asm> Also, since we've added one more entry to this table, we'll need to modify the loop count on this line in loc_12ABA: <asm>moveq #$11,d2</asm> 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: <asm>moveq #(((byte_12C52_end-byte_12C52)/6)-1),d2</asm> 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

add

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

Conclusion

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, and it continues all the way up to $EDFF. The free RAM can obviously be used for whatever you like - the most obvious uses I can think of right now are changing a ported Sonic 1 sound driver to use this RAM area to avoid having to mess around with underwater palette stuff, or setting up an object collision response list like in S3K to save some cycles in TouchResponse.