The array which holds tile, block and chunk data for each level is called the "level load block". Here's what it looks like in the current disassembly:
; 1st PLC palette 2nd 8x8 data 2nd 16x16 data 2nd 128x128 data ; 2nd PLC 1st 8x8 data 1st 16x16 data 1st 128x128 data LevelLoadBlock: levartptrs $B, $B, $A, AIZ1_8x8_Primary_KosM, AIZ1_8x8_Secondary_KosM, AIZ1_16x16_Primary_Kos, AIZ1_16x16_Secondary_Kos, AIZ1_128x128_Kos, AIZ1_128x128_Kos ; ANGEL ISLAND ZONE ACT 1 levartptrs $C, $C, $B, AIZ2_8x8_Primary_KosM, AIZ2_8x8_Secondary_KosM, AIZ2_16x16_Primary_Kos, AIZ2_16x16_Secondary_Kos, AIZ2_128x128_Kos, AIZ2_128x128_Kos ; ANGEL ISLAND ZONE ACT 2 levartptrs $E, $F, $C, HCZ_8x8_Primary_KosM, HCZ1_8x8_Secondary_KosM, HCZ_16x16_Primary_Kos, HCZ1_16x16_Secondary_Kos, HCZ_128x128_Primary_Kos, HCZ1_128x128_Secondary_Kos ; HYDROCITY ZONE ACT 1 levartptrs $10, $11, $D, HCZ_8x8_Primary_KosM, HCZ2_8x8_Secondary_KosM, HCZ_16x16_Primary_Kos, HCZ2_16x16_Secondary_Kos, HCZ_128x128_Primary_Kos, HCZ2_128x128_Secondary_Kos ; HYDROCITY ZONE ACT 2 ...
Yikes, it's a bit packed there, isn't it. levartptrs is a macro, and here's the definition:
; macro for declaring a "main level load block" (MLLB) levartptrs macro plc1,plc2,palette,art1,art2,map16x161,map16x162,map128x1281,map128x1282 dc.l (plc1<<24)|art1 dc.l (plc2<<24)|art2 dc.l (palette<<24)|map16x161 dc.l (palette<<24)|map16x162 dc.l map128x1281 dc.l map128x1282 endmOkay, so each entry is made up of six longwords, each a pointer to some kind of level data. From the top, two pointers to 8x8 tile data, then two pointers to 16x16 block mappings, and finally two pointers to 128x128 chunk mappings.
Additionally, since the Mega Drive's ROM address space only goes up to 0x400000, the top byte in a longword pointer is never set, so we can sneak in some extra pointers: two PLC pointers and a palette pointer (which appears twice but only the first one is used).
Here's what it looks like unpacked:
... dc.l HCZ_8x8_Primary_KosM+$E000000 dc.l HCZ1_8x8_Secondary_KosM+$F000000 dc.l HCZ_16x16_Primary_Kos+$C000000 dc.l HCZ1_16x16_Secondary_Kos+$C000000 dc.l HCZ_128x128_Primary_Kos dc.l HCZ1_128x128_Secondary_Kos ...The reasoning for two of each level data comes from Sonic 3's act system. The second act of each Zone tends to shoot off and do its own thing, often requiring a new set of tiles, blocks and chunks. Almost inevitably though, some elements end up being present in both acts; they're the same Zone, after all.
Rather than duplicating the common bits in both act 1 and act 2's data, a more space-efficient solution is to split them into their own "primary" data, and then each act adds on its own "secondary" data as needed. A similar trick was used in Sonic 2's Hill Top Zone because geez, that stage did everything first.
The three byte pointers are indexes to arrays elsewhere in the ROM. The palette byte points to an array called, well:
PalPoint: ... dc.l Pal_HCZ1 dc.w $FC20 dc.w $17 ...Each entry is made up of one longword and two words. The longword is a ROM pointer to the actual color data, the first word is a RAM pointer to the destination address, and the last word is the number of longwords to copy minus 1. Recall that Mega Drive colors are 16 bits each: each longword holds two colors, making $18 longwords hold $30 colors, which completely fills out three palette lines.
The PLC bytes point to the PLC offset array, which is itself a pointer array that points to individual PLCs:
Offs_PLC: ... dc.w PLC_0E-Offs_PLC ; HCZ 1 PLC 1 dc.w PLC_0F-Offs_PLC ; HCZ 1 PLC 2 ...Here's what a PLC looks like:
PLC_0E: plrlistheader plreq $45C, ArtNem_Bubbles plreq $3CA, ArtNem_HCZMisc plreq $426, ArtNem_HCZButton plreq $37A, ArtNem_HCZWaterRush plreq $42E, ArtNem_HCZWaveSplash plreq $43E, ArtNem_HCZSpikeBall PLC_0E_End PLC_0F: plrlistheader plreq $44C, ArtNem_HCZDragonfly PLC_0F_EndOh great, more macros. Can I just take a minute to ask, does anybody actually like these? I'm not even going to bother with the macro definition; it's gibberish for all I can tell. You'll just have to believe me when I say it unpacks to this:
PLC_0E: dc.w 5 dc.l ArtNem_Bubbles dc.w $8B80 dc.l ArtNem_HCZMisc dc.w $7940 dc.l ArtNem_HCZButton dc.w $84C0 dc.l ArtNem_HCZWaterRush dc.w $6F40 dc.l ArtNem_HCZWaveSplash dc.w $85C0 dc.l ArtNem_HCZSpikeBall dc.w $87C0 PLC_0F: dc.w 0 dc.l ArtNem_HCZDragonfly dc.w $8980PLCs, or Pattern Load Cues, are essentially pairs of pointers: the first pointer, a longword, is a pointer to graphic data in ROM; the second pointer, a word, is the destination address in VRAM. The first word in each PLC is the number of entries minus 1.
Sonic 3 loads most of its sprite graphics through PLCs. They allow graphics to be arranged and reused between levels, allowing elements to be shared between both acts of a Zone, or used globally such as spikes and springs.
Next time, we'll look at one way PLCs can go very, very wrong.
"Can I just take a minute to ask, does anybody actually like these?"
ReplyDeleteYes.
Speaking as the guy that added those macros in the first place - yeah.
ReplyDeleteNo. They suck monkey ass
DeleteThe links in this post all seem to go to the blogger domain and not to S3unlocked pages. Not sure what happened.
ReplyDeleteOops. The WYSIWYG editor mangles all relative links like that when you switch to it. Fixed.
Delete