Actions

SCHG How-to

Extend the Level Select in Sonic 2

From Sonic Retro

(Original guide by Clownacy)

As you may have seen with Sonic 3 & Knuckles or ColinC10's Sonic 1 and 2, the Sonic 2 Level Select can become really cramped, really fast. Wouldn't it be great if you could somehow extend the menu? To do this, several changes will have to be made to the Level Select's code. This does not require a total rewrite, so your own modifications can coexist with this one. I recommend that you are familiar with the Level Select's many tables, as you will be creating your own (look here).

This guide will target the Git disassembly.

The result will be your average S2 Level Select with multiple "pages", you toggle between the pages with the 'NEXT' and 'PREV' options. The switch is instantaneous (no fading, no loading screen) and support for more than just two pages is available.

Making your mappings

First, open a plane map editor (or a hex editor) and edit your Level Select.bin to feature a 'NEXT' option and, optionally, a 'PREV' option. The 'PREV' will simply loop from the first page to the last page. You can also remove the zeroes from the Sound Test selection, as they are redundant and are overwritten almost immediately. Doing so will save you a few bytes of ROM space. For reference, this is my example:

ExtendedLevSel Example1.png

Take note of the location of your 'NEXT'. Mine is placed after Oil Ocean, and before Metropolis.


Now we shall create our second page, so start listing some new zones! Also, Sound Test must be present on all pages and it must be the last entry. Use the stock Level Select as a template: The area allocated to the emblem must be left untouched, as should the Sound Test.

If this new page is your last, in order, then you might not want to list a 'NEXT' option unless you want page-looping. If it is, for example, the second out of three, in order, then you must list both 'NEXT' and 'PREV'.

To give you an idea of what I mean, here is another example (second page out of three):

ExtendedLevSel Example2.png

Once done, save as Level Select2.bin. The next page will be 3, then 4, etc.

Repeat the process for each new page you want.

Setting up constants

Before we touch any ASM, this is a good time to define the 1 byte large "LevSel_Page" RAM address constant. You should be able to do that on your own.

Now, wherever you feel it's appropriate, define the "MaxPageNum" constant. This constant will be used in calculations and checks regarding the maximum number of pages in your Level Select, such as those involved in page looping. Remember, however, that zero counts as a number, so, if you have three pages, the constant must be defined as 2 (0,1,2).

In the next section, Mappings decompression, we'll be setting up the decompression of the mappings you made earlier. To make this easier, we'll define some constants, otherwise changing the decompression locations would require changing several lines, doing this requires only the one.

Again, where appropriate, define the following constants:

  • Page1_RAM
  • Page2_RAM
  • Page3_RAM


The number of constants need to match the number of pages you plan to have. Adjust accordingly.

By default, the first page occupies Chunk_Table, while the icons' mappings occupy Chunk_Table+$8C0, we're going to rearrange this a bit to fit with our new pages.

Go to MenuScreen_LevelSelect and change this...

	lea	(Chunk_Table+$8C0).l,a1

...into this:

	lea	(PageX_RAM+$8C0).l,a1

With X being the number of your last page. Go to LevelSelect_DrawIcon and do the same thing.

Now we'll make our constants. Do so in this manner:

Page1_RAM =			Chunk_Table
Page2_RAM =			Page1_RAM+$8C0
Page3_RAM =			Page2_RAM+$8C0

You see, a decompressed full-screen Level Select mappings file always takes up 2240 bytes. This is 0x8C0 in hex. Doing this makes sure that no space is wasted, allowing more pages without encountering space issues.

Mappings decompression

Inside your s2.asm, go to MapEng_LevSel and, beneath it, add entries for your new mappings.

; level select page 1 screen mappings (Enigma compressed)
; byte_9ADE:
MapEng_LevSel:		BINCLUDE "mappings/misc/Level Select.bin"

; level select page 2 screen mappings (Enigma compressed)

MapEng_LevSel2:		BINCLUDE "mappings/misc/Level Select2.bin"

; level select page 3 screen mappings (Enigma compressed)

MapEng_LevSel3:		BINCLUDE "mappings/misc/Level Select3.bin"


With that done, go to MenuScreen_LevelSelect and add these two lines below the label:

	clr.b	(LevSel_Page).w
	clr.w	(Level_select_zone).w

Then replace the two "(Chunk_Table).l"s with "(Page1_RAM).l". After that you should see this (slighted formatted by myself):

MenuScreen_LevelSelect:
	lea	(Page1_RAM).l,a1
	lea	(MapEng_LevSel).l,a0	; 2 bytes per 8x8 tile, compressed
	move.w	#make_art_tile(ArtTile_VRAM_Start,0,0),d0
	bsr.w	EniDec

	lea	(Page1_RAM).l,a1
	move.l	#vdpComm(VRAM_Plane_A_Name_Table,VRAM,WRITE),d0
	moveq	#$27,d1
	moveq	#$1B,d2	; 40x28 = whole screen
	bsr.w	JmpTo_PlaneMapToVRAM	; display patterns

The upper section of code decompresses the Enigma-compressed mappings to a RAM address, the second reads the decompressed data and applies the visuals. Using this, we will load our new mappings. Here we have a choice: do we make the new mappings decompress pre-fade in or post-fade in? Pre-fade will increase the duration of the loading screen (time the screen stays black), but loading it post-fade will run the risk of someone with speedy fingers loading the page before its mappings have fully decompressed.

Pre-Fade

Copy the upper section of the code found above and paste it after

	lea	(MapEng_LevSel).l,a0	; 2 bytes per 8x8 tile, compressed
	lea	(Page1_RAM).l,a1
	move.w	#make_art_tile(ArtTile_VRAM_Start,0,0),d0
	bsr.w	EniDec

Post-Fade

Copy the upper section of the code found above and paste it before the LevelSelect_Main label


Then make some adjustments to make it load your second page by changing the "MapEng_LevSel" to "MapEng_LevSel2", and adding to the "Page1_RAM". Do so again for any other pages you have, using MapEng_LevSelX and using other available RAM spaces. The result should look like this:

	lea	(Page2_RAM).l,a1
	lea	(MapEng_LevSel2).l,a0	; 2 bytes per 8x8 tile, compressed
	move.w	#make_art_tile(ArtTile_VRAM_Start,0,0),d0
	bsr.w	EniDec
        
	lea	(Page3_RAM).l,a1
	lea	(MapEng_LevSel3).l,a0	; 2 bytes per 8x8 tile, compressed
	move.w	#make_art_tile(ArtTile_VRAM_Start,0,0),d0
	bsr.w	EniDec

Table expansion

LevelSelect_Order

Go to LevelSelect_Order. Remember where you placed your 'NEXT'? That's going to come into play now. Insert "dc.w $3000" into the table to match where the 'NEXT' is on your page 1 mappings. If you're following my example, it should look like this:

LevelSelect_Order:
	dc.w	emerald_hill_zone_act_1
	dc.w	emerald_hill_zone_act_2		; 1
	dc.w	chemical_plant_zone_act_1	; 2
	dc.w	chemical_plant_zone_act_2	; 3
	dc.w	aquatic_ruin_zone_act_1		; 4
	dc.w	aquatic_ruin_zone_act_2		; 5
	dc.w	casino_night_zone_act_1		; 6
	dc.w	casino_night_zone_act_2		; 7
	dc.w	hill_top_zone_act_1		; 8
	dc.w	hill_top_zone_act_2		; 9
	dc.w	mystic_cave_zone_act_1		; 10
	dc.w	mystic_cave_zone_act_2		; 11
	dc.w	oil_ocean_zone_act_1		; 12
	dc.w	oil_ocean_zone_act_2		; 13
	dc.w	$3000				; 14 - next page
	dc.w	metropolis_zone_act_1		; 15
	dc.w	metropolis_zone_act_2		; 16
	dc.w	metropolis_zone_act_3		; 17
	dc.w	sky_chase_zone_act_1		; 18
	dc.w	wing_fortress_zone_act_1	; 19
	dc.w	death_egg_zone_act_1		; 20
	dc.w	$4000				; 21 - special stage
	dc.w	$FFFF				; 22 - sound test


We will now create our new pages' tables. In the place of 'PREV' you must place "dc.w $5000". My second page's table looks like this:

LevelSelect_Order2:
	dc.w	emerald_hill_zone_act_1
	dc.w	emerald_hill_zone_act_2		; 1
	dc.w	death_egg_zone_act_1		; 2
	dc.w	chemical_plant_zone_act_2	; 3
	dc.w	aquatic_ruin_zone_act_1		; 4
	dc.w	aquatic_ruin_zone_act_2		; 5
	dc.w	casino_night_zone_act_1		; 6
	dc.w	casino_night_zone_act_2		; 7
	dc.w	hill_top_zone_act_1		; 8
	dc.w	hill_top_zone_act_2		; 9
	dc.w	mystic_cave_zone_act_1		; 10
	dc.w	mystic_cave_zone_act_2		; 11
	dc.w	oil_ocean_zone_act_1		; 12
	dc.w	oil_ocean_zone_act_2		; 13
	dc.w	$3000				; 14 - next page
	dc.w	$5000				; 15 - prev page
	dc.w	metropolis_zone_act_1		; 16
	dc.w	metropolis_zone_act_2		; 17
	dc.w	metropolis_zone_act_3           ; 18
	dc.w	sky_chase_zone_act_1		; 19
	dc.w	wing_fortress_zone_act_1	; 20
	dc.w	death_egg_zone_act_1		; 21
	dc.w	$4000				; 22 - special stage
	dc.w	$FFFF				; 23 - sound test

Once all of your tables are complete, we will bundle them all together with an index. Above LevelSelect_Order, paste this:

LevelSelect_OrderIndex: offsetTable
		offsetTableEntry.w LevelSelect_Order    ; 0
		offsetTableEntry.w LevelSelect_Order2   ; 1
		offsetTableEntry.w LevelSelect_Order3   ; 2

For each page/table you have, make an entry in the offset table. They must be in order!

We'll be following this template for several other tables.


LevelSelect_SwitchTable

Do as you did with LevelSelect_Order: a table for each page, add an index (same format as the one used above, uses word-sized offsets) Note, if you placed your 'NEXT' before the last entry of the table, then all references to entries after it will be shifted by it. So this...

LevelSelect_SwitchTable:
	dc.b $E
	dc.b $F		; 1
	dc.b $11	; 2
	dc.b $11	; 3
	dc.b $12	; 4
	dc.b $12	; 5
	dc.b $13	; 6
	dc.b $13	; 7
	dc.b $14	; 8
	dc.b $14	; 9
	dc.b $15	; 10
	dc.b $15	; 11
	dc.b $C		; 12
	dc.b $D		; 13
	dc.b 0		; 14
	dc.b 1		; 15
	dc.b 1		; 16
	dc.b 2		; 17
	dc.b 4		; 18
	dc.b 6		; 19
	dc.b 8		; 20
	dc.b $A		; 21

...becomes this:

LevelSelect_SwitchTable:
	dc.b $F
	dc.b $10	; 1
	dc.b $12	; 2
	dc.b $12	; 3
	dc.b $13	; 4
	dc.b $13	; 5
	dc.b $14	; 6
	dc.b $14	; 7
	dc.b $15	; 8
	dc.b $15	; 9
	dc.b $16	; 10
	dc.b $16	; 11
	dc.b $C		; 12
	dc.b $D		; 13
	dc.b $E		; 14 NEXT
	dc.b 0		; 15
	dc.b 1		; 16
	dc.b 1		; 17
	dc.b 2		; 18
	dc.b 4		; 19
	dc.b 6		; 20
	dc.b 8		; 21
	dc.b $A		; 22


LevSel_IconTable

Not much to say here, I just use the Sound Test icon for 'NEXT' and 'PREV'. Though you can make your own.

Here's a list of the all the icons.

	dc.b   0,0	;0	EHZ
	dc.b   7,7	;2	CPZ
	dc.b   8,8	;4	ARZ
	dc.b   6,6	;6	CNZ
	dc.b   2,2	;8	HTZ
	dc.b   5,5	;$A	MCZ
	dc.b   4,4	;$C	OOZ
	dc.b   1,1,1	;$E	MTZ
	dc.b   9	;$11	SCZ
	dc.b  $A	;$12	WFZ
	dc.b  $B	;$13	DEZ
	dc.b  $C	;$14	Special Stage
	dc.b  $E	;$15	Sound Test
	dc.b  3	;$16	HPZ

LevSel_MarkTable

Do not give this one an index. You should know what to do by this point.

Dynamic data

LevelSelect_Order

Now we make use of those tables!

Starting with LevelSelect_Order, go to LevelSelect_PressStart, you should see this:

LevelSelect_PressStart:
	move.w	(Level_select_zone).w,d0
	add.w	d0,d0
	move.w	LevelSelect_Order(pc,d0.w),d0
	bmi.w	LevelSelect_Return	; sound test
	cmpi.w	#$4000,d0
	bne.s	LevelSelect_StartZone

Replace this...

	move.w	(Level_select_zone).w,d0
	add.w	d0,d0
	move.w	LevelSelect_Order(pc,d0.w),d0

...with this:

	moveq	#0,d0
	move.b	(LevSel_Page).w,d0
	add.w	d0,d0
	move.w	LevelSelect_OrderIndex(pc,d0.w),d0
	lea	LevelSelect_OrderIndex(pc,d0.w),a0
	move.w	(Level_select_zone).w,d0
	add.w	d0,d0
	movea.w	(a0,d0.w),d0


LevelSelect_PressStart will now dynamically load LevelSelect_Order/2/3 depending on what page you're on.


LevelSelect_SwitchTable

Go to LevSelControls_SwitchSide, you should see this:

LevSelControls_SwitchSide:				; not in soundtest, not up/down pressed
	move.b	(Ctrl_1_Press).w,d1
	andi.b	#button_left_mask|button_right_mask,d1
	beq.s	+					; no direction key pressed
	move.w	(Level_select_zone).w,d0		; left or right pressed
	move.b	LevelSelect_SwitchTable(pc,d0.w),d0	; set selected zone according to table
	move.w	d0,(Level_select_zone).w
+
	rts

Replace this...

	move.w  (Level_select_zone).w,d0		; left or right pressed
	move.b  LevelSelect_SwitchTable(pc,d0.w),d0	; set selected zone according to table

...with this:

	move.b	(LevSel_Page).w,d0
	add.w	d0,d0
	move.w	LevelSelect_SwitchTableIndex(pc,d0.w),d0
	lea	LevelSelect_SwitchTableIndex(pc,d0.w),a0
	move.w	(Level_select_zone).w,d0		; left or right pressed
	move.b	(a0,d0.w),d0				; set selected zone according to table

LevSelControls_SwitchSide will now dynamically load LevelSelect_SwitchTable/2/3 depending on what page you're on.


LevSel_IconTable

Go to LevelSelect_DrawIcon, you should see this:

LevelSelect_DrawIcon:
	move.w	(Level_select_zone).w,d0
	lea	(LevSel_IconTable).l,a3
	lea	(a3,d0.w),a3
	lea	(Chunk_Table+$8C0).l,a1
	moveq	#0,d0
	move.b	(a3),d0
	lsl.w	#3,d0
	move.w	d0,d1
	add.w	d0,d0
	add.w	d1,d0
...

Replace this...

	move.w	(Level_select_zone).w,d0
	lea	(LevSel_IconTable).l,a3

...with this:

	moveq	#0,d0
	move.b	(LevSel_Page).w,d0
	add.w	d0,d0
	move.w	LevSel_IconTableIndex(pc,d0.w),d0
	lea	LevSel_IconTableIndex(pc,d0.w),a3
	move.w	(Level_select_zone).w,d0

LevelSelect_DrawIcon will now dynamically load LevSel_IconTable/2/3 depending on what page you're on.


LevSel_MarkTable

This will be quite a bit different from the other three.

Go to LevelSelect_MarkFields, you should find this:

LevelSelect_MarkFields:
	lea	(Chunk_Table).l,a4
	lea	(LevSel_MarkTable).l,a5
	lea	(VDP_data_port).l,a6
	moveq	#0,d0
	move.w	(Level_select_zone).w,d0
...

Replace this...

	lea	(Chunk_Table).l,a4
	lea	(LevSel_MarkTable).l,a5

...with this:

	moveq	#0,d0
	move.b	(LevSel_Page).w,d0
	add.w	d0,d0
	move.w	LevelSelect_MarkFieldsSubIndex(pc,d0.w),d0
	jsr	LevelSelect_MarkFieldsSubIndex(pc,d0.w)

And above the LevelSelect_MarkFields label, add this:

LevelSelect_MarkFieldsSubIndex: offsetTable
	offsetTableEntry.w LevelSelect_MarkFieldsSub1	; 0
	offsetTableEntry.w LevelSelect_MarkFieldsSub2	; 1
	offsetTableEntry.w LevelSelect_MarkFieldsSub3	; 2

LevelSelect_MarkFieldsSub1:
	lea	(Page1_RAM).l,a4
	lea	(LevSel_MarkTable).l,a5
	rts

LevelSelect_MarkFieldsSub2:
	lea	(Page2_RAM).l,a4
	lea	(LevSel_MarkTable2).l,a5
	rts

LevelSelect_MarkFieldsSub3:
	lea	(Page3_RAM).l,a4
	lea	(LevSel_MarkTable3).l,a5
	rts

For each mapping, an entry, mapping decompression RAM address, and MarkTable is added. Since I have three pages, three 'subs' entries exist. Adjust accordingly.

Fixing the Sound Test

Now for some hardcoded values to become a little less hardcoded. The hardcoded Sound Test functions!

Note where your Sound Test is (in the context of Level_select_zone). Count how many entries down LevelSelect_Order/2/3 "dc.w $FFFF ; sound test" is in hex, and there's your value. In vanilla S2, Sound Test is $15, but on your new pages, this is likely to have changed.

Find these values for each page, and compile a small table, all in bytes, label it "LevSel_LimitTable" and place it above LevSelControls. Don't forget to add an 'even at the end. Here's mine:

LevSel_LimitTable:
	dc.b	$16
	dc.b	$17
	dc.b	$16
	even

Under LevSelControls, replace this...

	subq.w	#1,d0	; decrease by 1
	bcc.s	+	; >= 0?
	moveq	#$15,d0	; set to $15

...with this:

	subq.w	#1,d0	; decrease by 1
	bcc.s	+	; >= 0?
	moveq	#0,d0
	move.b	(LevSel_Page).w,d3
	move.b	LevSel_LimitTable(pc,d3.w),d0

And replace this...

	cmpi.w	#$16,d0
	blo.s	+	; smaller than $16?
	moveq	#0,d0	; if not, set to 0

...with this:

	moveq	#0,d2
	move.b	(LevSel_Page).w,d3
	move.b	LevSel_LimitTable(pc,d3.w),d2

	cmp.w	d2,d0
	bls.s	+	; smaller than $18?
	moveq	#0,d0	; if not, set to 0

Now go to LevSelControls_CheckLR and replace this...

	cmpi.w	#$15,(Level_select_zone).w	; are we in the sound test?

...with this:

	moveq	#0,d0
	move.b	(LevSel_Page).w,d3
	move.b	LevSel_LimitTable(pc,d3.w),d0
	move.w	(Level_select_zone).w,d1
	cmp.w	d0,d1	; are we in the sound test?

Then go to LevelSelect_MarkFields (at the very bottom, right above the LevelSelect_DrawSoundNumber label) and replace this...

	cmpi.w	#$15,(Level_select_zone).w

...with this:

	moveq	#0,d0
	move.b	(LevSel_Page).w,d1
	lea	(LevSel_LimitTable).l,a0
	move.b	(a0,d1.w),d0
	move.w	(Level_select_zone).w,d1
	cmp.w	d0,d1	; are we in the sound test?

Using the new pages

Now to make another choice. Do you want the 'NEXT' and 'PREV' to be activated using Start, like several other elements, or A/B/C, like the Sound Test?


Using Start

Now to make 'NEXT' and 'PREV' work. Like the others, they'll be activated using Start.

Go to LevelSelect_PressStart and after this...

	bmi.w	LevelSelect_Return	; sound test

...paste this:

	cmpi.w	#$3000,d0	; was the selection Next Page?
	bne.s	++
	addq.b	#1,(LevSel_Page).w
	cmpi.b	#MaxPageNum+1,(LevSel_Page).w
	bne.s	+
	clr.b	(LevSel_Page).w
+
	bra.w	LevelSelect_LoadPage
+
	cmpi.w	#$5000,d0	; was the selection Prev Page?
	bne.s	++
	subq.b	#1,(LevSel_Page).w
	bpl.s	+
	move.b	#MaxPageNum,(LevSel_Page).w
+
	bra.w	LevelSelect_LoadPage
+


Using A/B/C

Now to make 'NEXT' and 'PREV' work. In this section, we'll make it so you activate them using the A/B/C buttons, similar to the Sound Test.

Go to LevelSelect_PressStart and after this...

	bmi.w	LevelSelect_Return	; sound test

...paste this:

	cmpi.w	#$3000,d0	; was the selection Next Page?
	beq.w	LevelSelect_Main
	cmpi.w	#$5000,d0	; was the selection Prev Page?
	beq.w	LevelSelect_Main


Now go to LevelSelect_Main, find this:

	andi.b	#button_start_mask,d0
	bne.s	LevelSelect_PressStart	; yes
	bra.w	LevelSelect_Main	; no

Replace it with this:

	andi.b	#button_start_mask,d0
	bne.s	LevelSelect_PressStart	; yes
	move.b	(Ctrl_1_Press).w,d0
	or.b	(Ctrl_2_Press).w,d0
	andi.b	#button_A_mask|button_B_mask|button_C_mask,d0
	bne.w	LevelSelect_PressABC	; yes
	bra.w	LevelSelect_Main	; no

Go to LevelSelect_Return, and above the label, paste this:

LevelSelect_PressABC:
	moveq	#0,d0
	move.b	(LevSel_Page).w,d0
	add.w	d0,d0
	lea	LevelSelect_OrderIndex(pc),a0
	move.w	(a0,d0.w),d0
	lea	(a0,d0.w),a0
	move.w	(Level_select_zone).w,d0
	add.w	d0,d0
	movea.w	(a0,d0.w),d0
	cmpi.w	#$3000,d0	; was the selection Next Page?
	bne.s	++
	addq.b	#1,(LevSel_Page).w
	cmpi.b	#MaxPageNum+1,(LevSel_Page).w
	bne.s	+
	clr.b	(LevSel_Page).w
+
	bra.w	LevelSelect_LoadPage
+
	cmpi.w	#$5000,d0	; was the selection Prev Page?
	bne.s	++
	subq.b	#1,(LevSel_Page).w
	bpl.s	+
	move.b	#MaxPageNum,(LevSel_Page).w
+
	bra.w	LevelSelect_LoadPage
+
	bra.w	LevelSelect_Main

Then go to LevelSelect_StartZone, and above the label, paste this:

LevelSelect_LoadPage:
	moveq	#0,d0
	move.b	(LevSel_Page).w,d0
	add.w	d0,d0
	move.w	LevelSelect_LoadPageSubIndex(pc,d0.w),d0
	jsr	LevelSelect_LoadPageSubIndex(pc,d0.w)

	move.l	#vdpComm(VRAM_Plane_A_Name_Table,VRAM,WRITE),d0
	moveq	#$27,d1
	moveq	#$1B,d2 ; 40x28 = whole screen
	bsr.w	JmpTo_PlaneMapToVRAM	; display patterns

	clr.w	(Level_select_zone).w

	moveq	#0,d3
	bsr.w	LevelSelect_DrawSoundNumber

	bra.w	LevelSelect_Main

LevelSelect_LoadPageSubIndex:	offsetTable
	offsetTableEntry.w LevelSelect_LoadPageSub1	; 0
	offsetTableEntry.w LevelSelect_LoadPageSub2	; 1
	offsetTableEntry.w LevelSelect_LoadPageSub3	; 2

LevelSelect_LoadPageSub1:
	lea	(Page1_RAM).l,a1
	rts

LevelSelect_LoadPageSub2:
	lea	(Page2_RAM).l,a1
	rts

LevelSelect_LoadPageSub3:
	lea	(Page3_RAM).l,a1
	rts

Each LoadPageSub contains a constant defined back in Setting up constants. As with the other indexes, there is one entry per page. Adjust accordingly.

(Optional) Memorising LevSel_Page

If you want the page you are on to be memorised, à la Level_Select_Zone, so that when you return to the Level Select after leaving it, you're placed back where you were when you left, you'll have to do the following. This one's a little messy, as if you want that behaviour, you cannot have your page mappings decompress post-fade. Make this so before continuing.

Go to MenuScreen_LevelSelect and replace this...

	lea	(Page1_RAM).l,a1
	move.l	#vdpComm(VRAM_Plane_A_Name_Table,VRAM,WRITE),d0
	moveq	#$27,d1
	moveq	#$1B,d2 ; 40x28 = whole screen
	bsr.w	JmpTo_PlaneMapToVRAM	; display patterns

...with this:

	moveq	#0,d0
	move.b	(LevSel_Page).w,d0
	add.w	d0,d0
	move.w	LevelSelect_LoadPageSubIndex(pc,d0.w),d0
	jsr	LevelSelect_LoadPageSubIndex(pc,d0.w)

	move.l	#vdpComm(VRAM_Plane_A_Name_Table,VRAM,WRITE),d0
	moveq	#$27,d1
	moveq	#$1B,d2 ; 40x28 = whole screen
	bsr.w	JmpTo_PlaneMapToVRAM	; display patterns

Also, remove these two lines found at the top of MenuScreen_LevelSelect:

	clr.b	(LevSel_Page).w
	clr.w	(Level_select_zone).w

(Optional) Using uncompressed mappings

Only follow this if you want uncompressed mappings for whatever reason.

Go to MenuScreen_LevelSelect and remove this:

	lea	(Page1_RAM).l,a1
	lea	(MapEng_LevSel).l,a0	; 2 bytes per 8x8 tile, compressed
	move.w	#make_art_tile(ArtTile_VRAM_Start,0,0),d0
	bsr.w	EniDec

Do the same for the other pages'.

Replace the nearby reference to Page1_RAM with MapEng_LevSel, note that you will have deleted that reference if you chose to memorise LevSel_Page.

Next, head to LevelSelect_LoadPageSub1 and LevelSelect_MarkFieldsSub and begin replacing the references to PageX_RAM with the in-ROM equivalents (MapEng_LevSel, MapEng_LevSel2, etc.)

LevelSelect_LoadPageSub1:
	lea	(MapEng_LevSel).l,a1
	rts

LevelSelect_LoadPageSub2:
	lea	(MapEng_LevSel2).l,a1
	rts

LevelSelect_LoadPageSub3:
	lea	(MapEng_LevSel3).l,a1
	rts


LevelSelect_MarkFieldsSub1:
	lea	(MapEng_LevSel).l,a4
	lea	(LevSel_MarkTable).l,a5
	rts

LevelSelect_MarkFieldsSub2:
	lea	(MapEng_LevSel2).l,a4
	lea	(LevSel_MarkTable2).l,a5
	rts

LevelSelect_MarkFieldsSub3:
	lea	(MapEng_LevSel3).l,a4
	lea	(LevSel_MarkTable3).l,a5
	rts


Doing this removes the decompression to RAM and instead loads directly from ROM. Decompressed plane mappings are quite a bit larger than their Enigma-compressed counterparts. My first page's mappings went to 2240 bytes (the usual size for full-screen mappings) from 340.

Conclusion

If you encounter any of these errors...

> > >s2.asm(11686): error: addressing mode not allowed on 68000

> > > move.b LevelSelect_OrderIndex(pc,d0.w),d0


...regarding lines such as these:

	move.w	LevelSelect_OrderIndex(pc,d0.w),d0
	lea	LevelSelect_OrderIndex(pc,d0.w),a0


Rearrange them to follow this format:

	lea	LevelSelect_OrderIndex(pc),a0	; if this still doesn't work, use (LevelSelect_OrderIndex).l,a0 instead
	move.w	(a0,d0.w),d0
	lea	(a0,d0.w),a0

After fixing some 'branch out of range' errors, you should be good to go. Save and build.

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

|Extend the Level Select in Sonic 2]]