Well, after more than 5 years away, my real life finally allowed me to get back to my project (Sonic 2: Innovative). Since I lost the original code, I started again with the Git disassembly and I think I should share what I achieved now, that I wanted to achieve long time ago... First, I need to apologize, because at that time (2010/2011), I started a mess on the SCHG-How To article "Extended level index past $10 in Sonic 2". I wanted to update some procedures kram wrote since I thought I could make them work at the time, but parts of them I accidentally deleted. Hopefully I got help to restore partially its content and kram came up with a revision, even updating it to the Git disassembly. And, for now, I wanted to post a guide for making the game detect the title cards in an act-sensitive way instead of a zone-sensitive way. I found it useful, at least, for me, but it will not avoid me from extending the game properly, since I want, in my project, to keep intact the original zones (especially because of the built-in 2-player mode). Ok, now to action. First of all, I recommend you to keep all the routines and tables associated with the title card object (Obj34) near its definition, and I'll tell how it should work while you read. We must know that the main routines controlling title card data are LoadTitleCard, Obj34_ZoneName and Obj34_ActNumber; the main tables containing proper data are Off_TitleCardLetters, Obj34_TitleCardData and Obj34_MapUnc_147BA. We'll create some additional data to make this attempt to work properly. Step 1: Prepare the card reading: EDIT: I've mistaken the art. Now the procedure will affect the correct art. Before the table Obj34_TitleCardData, and even before the macro there defined (titlecardobjdata), input two procedures, intercalated by two tables. The first of them will load the letters for each act, and the second will properly load the art needed. They are based on the original LoadTitleCard and LoadTitleCard0 routines: Code (Text): Obj34_PrepareTitleCardData: bsr.w Obj34_PrepareDecompressionRoutine moveq #0,d0 move.b (Current_Zone).w,d0 add.b d0,d0 add.b (Current_Act).w,d0 move.b Off_TitleCardLetters(pc,d0.w),d0 lea TitleCardLetters(pc),a0 lea (a0,d0.w),a0 move.l #vdpComm(tiles_to_bytes(ArtTile_LevelName),VRAM,WRITE),d0 rts ;================== Off_TitleCardLetters: (1) (2) ;================== Obj34_PrepareDecompressionRoutine: move.l #vdpComm(tiles_to_bytes(ArtTile_ArtNem_TitleCard),VRAM,WRITE),(VDP_control_port).l lea (ArtNem_TitleCard).l,a0 jsrto (NemDec).l, JmpTo2_NemDec lea (Level_Layout).w,a4 moveq #0,d6 move.b (Current_Zone).w,d6 add.b d6,d6 add.b (Current_Act).w,d6 add.b d6,d6 add.b d6,d6 movea.l TitleCard_Art_Handler(pc,d6.w),a0 jmpto (NemDecToRAM).l, JmpTo_NemDecToRAM ;================== TitleCard_Art_Handler: (3) ; =========================================================================== ; This macro declares data for an object. The data includes: [...] Now, in the space reserved with (1), you will build a table referring to the existing title card letters, so, move them to the space reserved in (2). Now it will become Code (Text): Off_TitleCardLetters: (1) ; temporarily remap characters to title card letter format ; Characters are encoded as Aa, Bb, Cc, etc. through a macro charset 'A',0 ; can't have an embedded 0 in a string charset 'B',"\4\8\xC\4\x10\x14\x18\x1C\x1E\x22\x26\x2A\4\4\x30\x34\x38\x3C\x40\x44\x48\x4C\x52\x56\4" [...] TitleCardLetters_DEZ: titleLetters "DEATH EGG" charset ; revert character set The table that must be built in space (1) is a modified version of the table Off_TitleCardLetters. So, delete the original data and build another one in space (1) as follows: Code (Text): Off_TitleCardLetters: dc.w TitleCardLetters_EHZ-Off_TitleCardLetters, TitleCardLetters_EHZ-Off_TitleCardLetters ; 00 EHZ1/EHZ2 dc.w TitleCardLetters_EHZ-Off_TitleCardLetters, TitleCardLetters_EHZ-Off_TitleCardLetters ; 01 ----/---- [...] dc.w TitleCardLetters_SCZ-Off_TitleCardLetters, TitleCardLetters_SCZ-Off_TitleCardLetters ; 10 SCZ /SCZ In the space (3), in addition to the routine I provided, I turned the game able to receive art for each act. This way, for the original behaviour, the table must be built as: Code (Text): TitleCard_Art_Handler: dc.l ArtNem_TitleCard, ArtNem_TitleCard ; 00 EHZ1/EHZ2 dc.l ArtNem_TitleCard, ArtNem_TitleCard ; 01 ----/---- [...] dc.l ArtNem_TitleCard, ArtNem_TitleCard ; 10 SCZ /SCZ I want to note this is not a relational index, but an offset index. Step 2: fixing title card main configuration Now, we need to fix the table Obj34_TitleCardData, so the game can properly process what texts are from zones, and what texts are reserved. To do this, we should define some indexes before it: Code (Text): ; Pointer declaration const_nzon = $10 ; number of zones (this may exist) const_acts = const_nzon+const_nzon+1 ; (doubling it and adding 1, so we have all the game acts) const_text = const_acts+1 ; (the text immediately after the zone labels is "ZONE") const_actn = const_text+1 ; (immediately come after that number "1") const_botm = const_actn+3 ; (after the numbers, the yellow bar) const_redp = const_botm+1 ; (and, finally, the red stripe) Obj34_TitleCardData: Maybe the names are strange, but you can rename them as you feel comfortable provided you change the body of what follows. Now, we fix properly the table, and it becomes Code (Text): Obj34_TitleCardData: titlecardobjdata 8, 0, $80, $1B, $240, $120, $B8 ; zone name titlecardobjdata $A, const_text, $40, $1C, $28, $148, $D0 ; "ZONE" titlecardobjdata $C, const_actn, $18, $1C, $68, $188, $D0 ; act number titlecardobjdata 2, 0, 0, 0, 0, 0, 0 ; blue background titlecardobjdata 4, const_botm, $48, 8, $2A8, $168,$120 ; bottom yellow part titlecardobjdata 6, const_redp, 8, $15, $80, $F0, $F0 ; left red part Obj34_TitleCardData_End: Let's continue. If you build and try to run, you'll get or a crash or a strange repeating mapping. Step 3: fix inner routines to load names and system reserved data Next to fix is the routine Obj34_ZoneName. It needs to process the name as we already have configured it, instead of its original zone-sensitive way. There is its start: Code (Text): Obj34_ZoneName: ; the name of the zone, coming in jsr Obj34_Wait(pc) move.b (Current_Zone).w,mapping_frame(a0) [...] Change the move.b line to: Code (Text): moveq #0,d6 move.b (Current_Zone).w,d6 add.b d6,d6 add.b (Current_Act).w,d6 move.b d6,mapping_frame(a0) What we've done is teaching the game how to process our tables. But, we still need to fix the remaining pointers (ZONE, ...). We will, also, make another table, which allows us to make these fixes more easily. Get to the routine Obj34_ActNumber. It will be like this: Code (Text): Obj34_ActNumber: ; the act number, coming in jsr Obj34_Wait(pc) move.b (Current_Zone).w,d0 ; get the current zone cmpi.b #sky_chase_zone,d0 ; is it Sky Chase? beq.s BranchTo9_DeleteObject ; if yes, branch cmpi.b #wing_fortress_zone,d0 ; is it Wing Fortress? beq.s BranchTo9_DeleteObject ; if yes, branch cmpi.b #death_egg_zone,d0 ; is it Death Egg Zone? beq.s BranchTo9_DeleteObject ; if yes, branch move.b (Current_Act).w,d1 ; get the current act addi.b #$12,d1 ; add $12 to it (this is the index of the "1" frame in the mappings) cmpi.b #metropolis_zone_2,d0 ; are we in Metropolis Zone Act 3? bne.s + ; if not, branch moveq #$14,d1 ; use the "3" frame instead + move.b d1,mapping_frame(a0) ; set the mapping frame Change this whole thing to Code (Text): Obj34_ActNumber: ; the act number, coming in jsr Obj34_Wait(pc) jsr Obj34_CardCheck And we will create the subroutine Obj34_CardCheck after all the definitions of Obj34, namely, after the table Animal_PLCTable. So, there, our routine will fit properly, as: Code (Text): Animal_PLCTable: zoneOrderedTable 1,1 [...] zoneTableEnd dc.b PLCID_SczAnimals ; level slot $11 (non-existent), not part of main table even Obj34_CardCheck: moveq #0,d0 move.b (Current_Zone).w,d0 add.b d0,d0 add.b (Current_Act).w,d0 ; Set a word-size offset add.w d0,d0 move.w Card_Display_Index(pc,d0.w),d0 jmp Card_Display_Index(pc,d0.w) Card_Display_Index: (4) Ok. This table, Card_Display_Index is a response table. There the subroutine will know if you are placing "Act 1", "Act 2", "Act 3", or nothing. After this reserved space, let's put our responses as routines: Code (Text): Card_Have_Null: bra.w DeleteObject Card_Have_Act1: moveq #0,d1 addi.b #const_actn+0,d1 bra.s Card_Display_Frame Card_Have_Act2: moveq #0,d1 addi.b #const_actn+1,d1 bra.s Card_Display_Frame Card_Have_Act3: moveq #0,d1 addi.b #const_actn+2,d1 ;bra.s Card_Display_Frame Card_Display_Frame: move.b d1,mapping_frame(a0) rts Now we can build the response table accordingly. So, this table will be like: Code (Text): Card_Display_Index: dc.w Card_Have_Act1-Card_Display_Index, Card_Have_Act2-Card_Display_Index ; 00 EHZ1/EHZ2 [...] dc.w Card_Have_Null-Card_Display_Index, Card_Have_Null-Card_Display_Index ; 10 SCZ /SCZ Step 4: converting the mappings table Then, we need to relocate and properly convert the table Obj34_MapUnc_147BA and its associated data. You can move all the data Code (Text): Obj34_MapUnc_147BA: [...] word_14C32: dc.w 7 dc.w $9003, $85D4, $82EA, 0 dc.w $B003, $85D4, $82EA, 0 dc.w $D003, $85D4, $82EA, 0 dc.w $F003, $85D4, $82EA, 0 dc.w $1003, $85D4, $82EA, 0 dc.w $3003, $85D4, $82EA, 0 dc.w $5003, $85D4, $82EA, 0 to immediately after the routine Card_Display_Frame. To build the table, I used new label names according to the zone names. This is the equivalence: Code (Text): word_147E8 > EHZ word_14842 > MZ/MTZ (I prefer MZ, but that doesn't matter) word_14894 > HTZ word_148CE > HPZ word_14930 > OOZ word_14972 > MCZ word_149C4 > CNZ word_14A1E > CPZ word_14A88 > ARZ word_14AE2 > SCZ word_14B24 > WFZ word_14B86 > DEZ word_14BC8 > "ZONE" word_14BEA > "1" word_14BF4 > "2" word_14BFE > "3" word_14C08 > yellow bar written "SONIC THE HEDGEHOG" word_14C32 > left red stripe And the source table we need for Obj34_MapUnc_147BA is built like: Code (Text): Obj34_MapUnc_147BA: dc.w TC_EHZ-Obj34_MapUnc_147BA, TC_EHZ-Obj34_MapUnc_147BA ; 00 EHZ1/EHZ2 dc.w TC_EHZ-Obj34_MapUnc_147BA, TC_EHZ-Obj34_MapUnc_147BA ; 01 ----/---- [...] dc.w TC_SCZ-Obj34_MapUnc_147BA, TC_SCZ-Obj34_MapUnc_147BA ; 10 SCZ /SCZ Step 5: allow the game to properly load the data And, finally, we should tell the game to load the parameters we defined. Go to LoadTitleCard. There will be its original routine yet, as: Code (Text): LoadTitleCard: bsr.s LoadTitleCard0 moveq #0,d0 move.b (Current_Zone).w,d0 move.b Off_TitleCardLetters(pc,d0.w),d0 lea TitleCardLetters(pc),a0 lea (a0,d0.w),a0 move.l #vdpComm(tiles_to_bytes(ArtTile_LevelName),VRAM,WRITE),d0 Just change it to: Code (Text): LoadTitleCard: jsr Obj34_Load_Title_Card Note If you run into errors while building, try to fix the branches to BranchTo9_DeleteObject in the middle of Obj34 (instead of .s, fix to .w).