Actions

SCHG How-to

Extend the Level Select in Sonic 2

From Sonic Retro

Revision as of 16:17, 26 January 2014 by Clownacy (talk | contribs) (Created page with "{{GuideBy|Clownacy}} As you may have seen with S3K or ColinC10's Sonic 1 and 2, the S2 Level Select can become really cramped, really fast. Wouldn't it be great if you could ...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

(Original guide by Clownacy)

As you may have seen with S3K or ColinC10's Sonic 1 and 2, the S2 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 hacks 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 (HINT: look here).

This guide will focus on the HG 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 supports numerous pages.

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. 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. 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 (there are no pages after it), 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 (there will be a following page), 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 (3-1).

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 <asm> lea (Chunk_Table+$8C0).l,a1</asm>

to <asm> lea (PageX_RAM+$8C0).l,a1</asm>

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: <asm>Page1_RAM = Chunk_Table Page2_RAM = Page1_RAM+$8C0 Page3_RAM = Page2_RAM+$8C0</asm>

You see, a decompressed full-screen Level Select mappings file always takes up 2240 bytes. This is 8C0 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. <asm>; 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"</asm>

With that done, go to MenuScreen_LevelSelect and replace the two "(Chunk_Table).l"s with "(Page1_RAM).l". After that you should see this (slighted formatted by myself): <asm>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</asm>

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 <asm>bsr.w JmpTo_PlaneMapToVRAM</asm>

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

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


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

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

               offsetTableEntry.w LevelSelect_Order    ; 0
               offsetTableEntry.w LevelSelect_Order2   ; 1
               offsetTableEntry.w LevelSelect_Order3   ; 2</asm>

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

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


LevSel_IconTable

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


LevSel_MarkTable

Do not give this one an index. Again, not much else to say.

Dynamic data

LevelSelect_Order

Now we make use of those tables!

Starting with LevelSelect_Order, go to LevelSelect_PressStart, you should see this: <asm>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</asm>

Replace <asm> move.w (Level_select_zone).w,d0

       add.w   d0,d0
       move.w  LevelSelect_Order(pc,d0.w),d0</asm>

With <asm> moveq #0,d0

       move.b  (LevSel_Page).w,d0
       add.b   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</asm>


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

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

With <asm> move.b (LevSel_Page).w,d0

       add.b   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</asm>

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

...</asm>

Replace <asm> move.w (Level_select_zone).w,d0

       lea     (LevSel_IconTable).l,a3</asm>

With <asm> moveq #0,d0

       move.b  (LevSel_Page).w,d0
       add.b   d0,d0
       move.w  LevSel_IconTableIndex(pc,d0.w),d0
       lea     LevSel_IconTableIndex(pc,d0.w),a3
       move.w  (Level_select_zone).w,d0</asm>

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

...</asm>

Replace <asm> lea (Chunk_Table).l,a4

       lea     (LevSel_MarkTable).l,a5</asm>

With <asm> moveq #0,d0

       move.b  (LevSel_Page).w,d0
       add.b   d0,d0
       move.w  LevelSelect_MarkFieldsSubIndex(pc,d0.w),d0
       lea     LevelSelect_MarkFieldsSubIndex(pc,d0.w),a0
       jsr     (a0)    ; dynamic call!</asm>

And above the LevelSelect_MarkFields label, add this: <asm>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</asm>

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 EVEN it. Here's mine: <asm>LevSel_LimitTable:

       dc.b    $16
       dc.b    $17
       dc.b    $16
       even</asm>

Under LevSelControls, replace <asm> subq.w #1,d0  ; decrease by 1

       bcc.s   +       ; >= 0?
       moveq   #$15,d0 ; set to $15</asm>

With <asm> bcc.s +  ; >= 0?

       moveq   #0,d0
       move.b  (LevSel_Page).w,d3
       move.b  LevSel_LimitTable(pc,d3.w),d0</asm>

And <asm> cmpi.w #$16,d0

       blo.s   +       ; smaller than $16?
       moveq   #0,d0   ; if not, set to 0</asm>

With <asm>

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

Now go to LevSelControls_CheckLR and replace <asm> cmpi.w #$15,(Level_select_zone).w  ; are we in the sound test?</asm>

With <asm>

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

Then go to LevelSelect_MarkFields (at the very bottom, right above the LevelSelect_DrawSoundNumber label) and replace <asm> cmpi.w #$15,(Level_select_zone).w</asm>

With <asm> 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?</asm>

Using the new pages

Now to make another choice. Do you want the 'NEXT' and 'PREV' to be activated using the start button, 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 the start button.

Go to LevelSelect_PressStart and after <asm> bmi.w LevelSelect_Return  ; sound test</asm>

Paste this: <asm> cmpi.w #$3000,d0  ; was the selection Next Page?

       bne.w   ++
       clr.w   (Level_select_zone).w
       add.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.w   ++
       clr.w   (Level_select_zone).w
       sub.b   #1,(LevSel_Page).w
       bpl.s   +
       move.b  #MaxPageNum,(LevSel_Page).w

+

       bra.w   LevelSelect_LoadPage

+</asm>


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 <asm> bmi.w LevelSelect_Return  ; sound test</asm>

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


Now go to LevelSelect_Main, find this: <asm> andi.b #button_start_mask,d0

       bne.s   LevelSelect_PressStart  ; yes
       bra.w   LevelSelect_Main        ; no</asm>

Replace it with this: <asm> 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</asm>

Go to LevelSelect_Return, and above the label, paste this: <asm>LevelSelect_PressABC:

       moveq   #0,d0
       move.b  (LevSel_Page).w,d0
       add.b   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.w   ++
       clr.w   (Level_select_zone).w
       add.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.w   ++
       clr.w   (Level_select_zone).w
       sub.b   #1,(LevSel_Page).w
       bpl.s   +
       move.b  #MaxPageNum,(LevSel_Page).w

+

       bra.w   LevelSelect_LoadPage

+

       bra.w   LevelSelect_Main</asm>

Then go to LevelSelect_StartZone, and above the label, paste this: <asm>LevelSelect_LoadPage:

       moveq   #0,d0
       move.b  (LevSel_Page).w,d0
       add.b   d0,d0
       move.w  LevelSelect_LoadPageSubIndex(pc,d0.w),d0
       lea     LevelSelect_LoadPageSubIndex(pc,d0.w),a0
       jsr     (a0)    ; dynamic call!
       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
       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</asm>

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) Using uncompressed mappings

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

Go to MenuScreen_LevelSelect and remove this: <asm> 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</asm>

And replace the nearby reference to Page1_RAM with MapEng_LevSel.

Next, remove the other pages' mapping decompression, they should be below either the "bsr.w JmpTo_PlaneMapToVRAM" or above LevelSelect_Main. You know, this thing: <asm> 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</asm>

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.) <asm>LevelSelect_LoadPageSub1:

       lea     (MapEng_LevSel).l,a1
       rts

LevelSelect_LoadPageSub2:

       lea     (MapEng_LevSel2).l,a1
       rts

LevelSelect_LoadPageSub3:

       lea     (MapEng_LevSel3).l,a1
       rts</asm>


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


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: <asm> move.w LevelSelect_OrderIndex(pc,d0.w),d0

       lea     LevelSelect_OrderIndex(pc,d0.w),a0</asm>


Rearrange them to follow this format: <asm> 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</asm>

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