Sonic the Hedgehog Triple Trouble
From Sonic Retro
|$10000||Unc. Art-Sonic (340 tiles)|
|$29000||Unc. Art-Monitors (98 tiles)|
|$7C300||Unc. Art-Fang (348 tiles)|
The original research by Doc Eggfan can be found here
|$3B71C||Sega Screen 1 (32)|
|$3B79C||Title screen palette (64)|
|$3B8DC||Sega Screen 2 (32) (SPRITE)|
|$3BA5C||Intro 2 (32)|
|$3BBDC||Intro 1 (32)|
|$3BBFC||Intro 2 (Robotnik) (32)|
|$3B7DC||Character Select (Sonic Select) (32)|
|$3B7FC||Character Select (Tails Select) (32)|
|$3B81C||Character Select (Sonic Not select) (32)|
|$3B83C||Character Select (Tails Not Select) (32)|
|$3B4A3||Title Card (Great Turquoise) (8)|
|$3BDDC||Title Card 2 (32) (SPRITE)|
|$3BABC||Great Turquoise (32)|
|$3BE3C||Great Turquoise (water) (32)|
|$3BE5C||Great Turquoise water sprites (32)|
|$3B99C||Great Turquoise Boss sprite (32)|
|$589B||Great Turquoise Boss sprite (2)|
|$3B89C||End of act results (64)|
|$3BBBC||Special Stage 1 (32)|
|$3BA9C||Special Stage Sprite 1 (32)|
|$5CA37||Chaos Emerald 1 (6)|
|$3BC5C||Special Stage 2 Sonic (32)|
|$3BC9C||Special Stage 2 Tails (32)|
|$3BCDC||Special Stage 2 sprite (32)|
|$9EA||Special Stage 2 BG (32 - every 2nd word)|
|$5CA3D||Chaos Emerald 2 (6)|
|$3B4AB||Title Card (Sunset Park) (8)|
|$3BADC||Sunset Park (32) ***Unknown Rotating Palette (0F00)***|
|$3B8FC||Sunset Park Sprite (32)|
|$3885C||Continue/Game Over (32)|
|$3BD9C||Special Stage 3 (32)|
|$5CA43||Chaos Emerald 3 (6)|
|$3BC7C||Special Stage 4 Sonic (32)|
|$3BCBC||Special Stage 4 Tails (32)|
|$3BCFC||Special Stage 4 Sprite (32)|
|$5CA49||Chaos Emerald 4 (6)|
|$3B4B3||Title Card (Meta Junglira) (8)|
|$3BAFC||Meta Junglira (32)|
|$3B91C||Meta Junglira Sprite (32)|
|$3B9DC||Meta Junglira Boss Sprite (32)|
|$589F||Meta Junglira Boss (2)|
|$3BA7C||Meta Junglira Knuckles Sprite (32)|
|$3BDBC||Special Stage 5 (32)|
|$5CA4F||Chaos Emerald 5 (6)|
|$3B4BB||Title Card (Robotnik Winter) (8)|
|$3BB1C||Robotnik Winter (32)|
|$3B93C||Robotnik Winter Sprite (32)|
|$3B9FC||Robotnik Winter Boss Sprite (32)|
|$58A1||Robotnik Winter Boss Sprite (2)|
|$3B4C3||Title Card (Tidal Plant) (8)|
|$3BB3C||Tidal Plant (32)|
|$3B95C||Tidal Plant Sprite (32)|
|$3BEBC||Tidal Plant Water (32)|
|$3BEDC||Tidal Plant Water Sprite (32)|
|$3BC1C||Tidal Plant Act 3 (32)|
|$3BA1C||Tidal Plant Act 3 Sprite (32)|
|$3B4CB||Title Card (Atomic Destroyer) (8)|
|$3BB5C||Atomic Destroyer Act 1 (32) *3 Unknown Rotating Palettes (DD0F, FF0D, 6202)|
|$3B97C||Atomic Destroyer Sprite (32)|
|$3BB7C||Atomic Destroyer Act 2 (32)|
|$3BB9C||Atomic Destroyer Act 3 (32)|
|$3BA3C||Atomic Destroyer Metal Sonic Sprite (32)|
|$3BC3C||Atomic Destroyer Fang Sprite (32)|
|$58A5||Atomic Destroyer Robotnik Boss Sprite (2)|
|$3BD5C||End Sequence 1 (64)|
|$3F007||End Sequence BG 1 (32 - Every second word)|
The original research by Rolken can be found here
The first point of interest in the ROM is the 8x8 tile methodology. The set of tile addresses is an index hardcoded in the ROM ($6B678), at which a set of seven bytes for each level give:
Tile Bank ?? ?? LS Tile Address MS Tile Address ?? ??
Skip to that tile address, and you get
?? ?? LS Number of Tiles MS Number of Tiles LS Guide Address (offset from Tile Address) MS Guide Address (offset from Tile Address) henceforth - Tile Data
For some reason Sonic Triple Trouble checks the game mode byte and if it sees Time Attack mode it overrides the usual zone/act tile selection and instead forces the address equivalent to level 29, whereas the Time Attack mode level itself is level 30. I can't see why they made that special case behaviour instead of just correcting the address for level 30, but they did.
You need to work with both the tile data and the "guide" data at the same time - the "guide" is a set of 2-bit flags, 4 per byte, from LS to MS, that indicate what to do with the tile data, as follows:
0 - The tile is blank and uses no data 1 - The tile is a direct uncompressed copy of 20 bytes 2 - The tile is compressed 3 - The tile is compressed and an xor scheme should be applied to it
The compression is of variable length, and as such there's no way to jump to a specific tile in the ROM; they have to be decompressed in order. The compression is relatively simple and consists of a 20-bit bitmask which indicates whether to load a byte of data or just push in 00. Here's an example, in which * indicates a compressed 0. Note that it processes bitmask word pairs "backwards", as with addresses.
5 D E 7 5 6 7 5 D57E6557 FF**FF**|C0**C03F|**3F3FC0|3FC0FF**|FC**FF**|**3FFF**|3FC0FF**|0F**FF**
The set of palette IDs are at $3BF1C, and this is just a straight list of two bytes per level, the background palette and the object palette. The palettes themselves are at $3B6FC, and adding the palette ID * 20 (the size of a palette) will take you to the one you're looking for.
The index of map data can be found at $434F. Add 2*zone bytes to that and you'll find the zone index; go to that address, add 2*act and you get the address of your level's map data. Here's what a complete set of STT.
[block][ map ] wdth TH1 - 1100 8012 C080 A800 58FF 9804 0000 0800 3014 3802 334D TH2 - 1100 8012 B489 A800 58FF 9804 0000 0800 3014 3802 334D TH3 - 1100 801A 49B0 4000 C0FF C001 0000 0800 3007 3802 034E SP1 - 1180 9B12 5A92 A800 58FF 9804 0000 0800 3014 3802 334D SP2 - 1180 9B12 159C 8000 80FF 8003 0000 0800 300F 3803 634D SP3 - 1180 9B12 159C 8000 80FF 8003 0000 0800 300F 3803 634D MJ1 - 1B00 8012 AAA5 8000 80FF 8003 0000 0800 300F 3803 634D MJ2 - 1B00 8012 45AF 8000 80FF 8003 0000 0800 300F 3803 634D MJ3 - 1B00 8014 80B2 4000 C0FF C001 0000 0800 3007 3803 034E RW1 - 1400 8014 A5B5 8000 80FF 8003 0000 0800 300F 3803 634D RW2 - 1400 8015 0080 8000 80FF 8003 0000 0800 300F 3803 634D RW3 - 1400 8012 23B9 6000 A0FF A002 0000 0800 300B 3804 A34D TP1 - 14E0 9915 6686 8000 80FF 8003 0000 0800 300F 3803 634D TP2 - 14E0 9915 3490 3000 D0FF 5001 0000 0800 3005 3809 934E TP3 - 14E0 9915 0B9A 4000 C0FF C001 0000 0800 3007 3801 034E AD1 - 1B00 9C15 109B 8000 80FF 8003 0000 0800 300F 3803 634D AD2 - 1B00 9C15 7AA5 6000 A0FF A002 0000 0800 300B 3804 A34D AD3 - 1B00 9C15 E3AF A800 58FF 9804 0000 0800 3014 3802 334D SS1 - 1940 8315 68B5 A800 58FF 9804 0000 0800 3014 3802 334D SS3 - 1940 8315 3BB9 3000 D0FF 5001 0000 0800 3005 3809 934E SS5 - 1940 831A ABB2 8000 80FF 8003 0000 0800 300F 3803 634D Intro-1100 8012 0080 4000 C0FF C001 0000 0800 3007 3800 034E End - 1100 8016 2AB1 A800 58FF 9804 0000 0800 3014 3802 334D
The block/map addresses are of the form
Bank LS Address MS Address
Note that Sunset Park 2-3 have the same map, and if you've played the game, you'll recall that there's not actually a transition between them unless you die.
Hit up that block address and you find a 200 byte collection of 2 byte offsets from the "main" block address. These 100 blocks correspond to the block numbers that will be used in the map; the first block is 00, second 01, etc. Some of the blocks will be identical offsets, which I assume is for the purpose of tagging them with additional effects such as breakability or hidden springage. At the offset is a collection of 20 bytes which form the actual block; each 2-byte segment defines a tile according to the SMS's specification found in the document I mentioned at the beginning, including the tile ID, palette, v/h flipping and bg/fg. The tile order is the same as printed English, left to right, top to bottom.
Sonic Triple Trouble map compression is a complicated; I just found the routine in the code and basically copied it word for word. It takes in a eight bit bitmask whenever its current bitmask runs dry; for each bit it tests, if that bit is 0 it copies in a byte of data, and if 1 that indicates that there is compressed data. The compression scheme works by copying data from a prior point in the map that has been already loaded. It reads in two bytes for directions: The MS half of the second byte + 3 indicates how many bytes to copy, and is then papered over with F so that the two bytes together can define the offset from the current position (which will always be backwards and less than 1000 because of the F and two's complement). If the two bytes are 0000, then the map is done.
Here's an example, from the first level of Sonic Triple Trouble:
F1FE FFFF FFFF FFFF FEFE 0D05
So F1 is the bitmask control byte, which put backwards in binary according to how the game uses it would be 10001111. OK, so we're loading in one byte, doing three compressed sequences, then loading in four more.
So take in that FE. Now for the compression, we read in FFFF. That third F, the MS half of the second byte, indicates we're doing F+3 or 12 bytes of copying. It gets papered over with an F (no effect in this instance), so our offset is FFFF, or back one byte.
What this means is that it will copy the FE over one byte, then the "read from" address will move over into that FE that was just copied and the "copy to" address to the next empty space, and so repeating the process another 11 times we'll get 11 extra FEs. This behavioral quirk only happens when the offset is FFFF; otherwise you'd be copying in, say, the previous 2 bytes over and over if the offset was FEFF, the previous 3 if FDFF, or just a plain block if it was any offset larger than the number of bytes we're copying.
According to the next two bits in the bitmask, we're doing two more FFFF compression sequences, so that gives us 24 more FEs. The last four bits in the bitmask gives us four straight bytes, so we copy in FEFE0D05, and then we load in another bitmask and start the process again. The result so far is 27 FEs in a row, with 0D05 at the end.
Anyway, if you're interested in this compression scheme you can check out the source compression at the addresses mentioned in the big block of hex above, and the output uncompressed map you can see in Emukon or Mekaw by loading up the appropriate level and pointing the memory viewer to $C001, which is where it starts. I thought it was pretty nifty.
Loop XY motion data
Just like in Sonic 2 & Sonic Chaos, the player can't control Sonic or Tails while they move during loops. The game itself moves them, by using a few hex arrays. Two control the camera, and two more control their actual movement. $39B30 is where the array that controls the player while going forward through the loop is located. $3A010 is where the array that contains the camera's xy motion data while going forward through a loop is located. $3A1DF is where the array that contains the camera's XY motion data while going backwards through a loop is located. If you wanted to edit the movement, keep in mind you need to edit not just the player's movement, but the camera movement as well to get desired results.