don't click here

Converting Sonic 2 Title Cards to an act-sensitive edition

Discussion in 'Engineering & Reverse Engineering' started by silent.creature, Oct 15, 2016.

  1. silent.creature

    silent.creature

    Silent Creature Member
    18
    0
    0
    Brazil
    Studying both to work and to get back to some stopped hobbies...
    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):
    1. Obj34_PrepareTitleCardData:
    2.     bsr.w   Obj34_PrepareDecompressionRoutine
    3.    
    4.     moveq   #0,d0
    5.     move.b  (Current_Zone).w,d0
    6.     add.b   d0,d0
    7.     add.b   (Current_Act).w,d0
    8.    
    9.     move.b  Off_TitleCardLetters(pc,d0.w),d0
    10.     lea TitleCardLetters(pc),a0
    11.     lea (a0,d0.w),a0
    12.     move.l  #vdpComm(tiles_to_bytes(ArtTile_LevelName),VRAM,WRITE),d0
    13.     rts
    14.  
    15. ;==================
    16. Off_TitleCardLetters:
    17.     (1)
    18.    
    19.     (2)
    20.  
    21. ;==================
    22. Obj34_PrepareDecompressionRoutine:
    23.         move.l  #vdpComm(tiles_to_bytes(ArtTile_ArtNem_TitleCard),VRAM,WRITE),(VDP_control_port).l
    24.     lea (ArtNem_TitleCard).l,a0
    25.     jsrto   (NemDec).l, JmpTo2_NemDec
    26.     lea (Level_Layout).w,a4
    27.    
    28.     moveq   #0,d6
    29.     move.b  (Current_Zone).w,d6
    30.     add.b   d6,d6
    31.     add.b   (Current_Act).w,d6
    32.    
    33.     add.b   d6,d6
    34.     add.b   d6,d6
    35.    
    36.     movea.l TitleCard_Art_Handler(pc,d6.w),a0
    37.     jmpto   (NemDecToRAM).l, JmpTo_NemDecToRAM
    38.  
    39. ;==================
    40. TitleCard_Art_Handler:
    41.     (3)
    42.  
    43. ; ===========================================================================
    44. ; This macro declares data for an object. The data includes:
    45.     [...]
    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):
    1. Off_TitleCardLetters:
    2.     (1)
    3.  
    4.      ; temporarily remap characters to title card letter format
    5.  ; Characters are encoded as Aa, Bb, Cc, etc. through a macro
    6.  charset 'A',0  ; can't have an embedded 0 in a string
    7.  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"
    8.  
    9.     [...]
    10.  
    11. TitleCardLetters_DEZ:
    12.     titleLetters    "DEATH EGG"
    13.  
    14.  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):
    1. Off_TitleCardLetters:
    2.     dc.w    TitleCardLetters_EHZ-Off_TitleCardLetters, TitleCardLetters_EHZ-Off_TitleCardLetters    ; 00 EHZ1/EHZ2
    3.     dc.w    TitleCardLetters_EHZ-Off_TitleCardLetters, TitleCardLetters_EHZ-Off_TitleCardLetters    ; 01 ----/----
    4.  
    5.     [...]
    6.  
    7.     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):
    1. TitleCard_Art_Handler:
    2.     dc.l    ArtNem_TitleCard, ArtNem_TitleCard  ; 00 EHZ1/EHZ2
    3.     dc.l    ArtNem_TitleCard, ArtNem_TitleCard  ; 01 ----/----
    4.  
    5.     [...]
    6.  
    7.     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):
    1. ; Pointer declaration
    2. const_nzon = $10 ; number of zones (this may exist)
    3. const_acts = const_nzon+const_nzon+1 ; (doubling it and adding 1, so we have all the game acts)
    4. const_text = const_acts+1 ; (the text immediately after the zone labels is "ZONE")
    5. const_actn = const_text+1 ; (immediately come after that number "1")
    6. const_botm = const_actn+3 ; (after the numbers, the yellow bar)
    7. const_redp = const_botm+1 ; (and, finally, the red stripe)
    8.  
    9. 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):
    1. Obj34_TitleCardData:
    2.     titlecardobjdata  8,          0, $80, $1B, $240, $120, $B8  ; zone name
    3.     titlecardobjdata $A, const_text, $40, $1C,  $28, $148, $D0  ; "ZONE"
    4.     titlecardobjdata $C, const_actn, $18, $1C,  $68, $188, $D0  ; act number
    5.     titlecardobjdata  2,          0,   0,   0,    0,    0,   0  ; blue background
    6.     titlecardobjdata  4, const_botm, $48,   8, $2A8, $168,$120  ; bottom yellow part
    7.     titlecardobjdata  6, const_redp,   8, $15,  $80,  $F0, $F0  ; left red part
    8. 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):
    1. Obj34_ZoneName:     ; the name of the zone, coming in
    2.     jsr Obj34_Wait(pc)
    3.     move.b  (Current_Zone).w,mapping_frame(a0)
    4.     [...]
    Change the move.b line to:

    Code (Text):
    1.     moveq   #0,d6
    2.     move.b  (Current_Zone).w,d6
    3.     add.b   d6,d6
    4.     add.b   (Current_Act).w,d6
    5.     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):
    1. Obj34_ActNumber:    ; the act number, coming in
    2.     jsr Obj34_Wait(pc)
    3.     move.b  (Current_Zone).w,d0 ; get the current zone
    4.     cmpi.b  #sky_chase_zone,d0  ; is it Sky Chase?
    5.     beq.s   BranchTo9_DeleteObject  ; if yes, branch
    6.     cmpi.b  #wing_fortress_zone,d0  ; is it Wing Fortress?
    7.     beq.s   BranchTo9_DeleteObject  ; if yes, branch
    8.     cmpi.b  #death_egg_zone,d0  ; is it Death Egg Zone?
    9.     beq.s   BranchTo9_DeleteObject  ; if yes, branch
    10.     move.b  (Current_Act).w,d1  ; get the current act
    11.     addi.b  #$12,d1         ; add $12 to it (this is the index of the "1" frame in the mappings)
    12.     cmpi.b  #metropolis_zone_2,d0   ; are we in Metropolis Zone Act 3?
    13.     bne.s   +           ; if not, branch
    14.     moveq   #$14,d1         ; use the "3" frame instead
    15. +
    16.     move.b  d1,mapping_frame(a0)    ; set the mapping frame
    Change this whole thing to

    Code (Text):
    1. Obj34_ActNumber:    ; the act number, coming in
    2.     jsr Obj34_Wait(pc)
    3.    
    4.     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):
    1. Animal_PLCTable: zoneOrderedTable 1,1
    2.  
    3.     [...]
    4.  
    5.     zoneTableEnd
    6.  
    7.     dc.b PLCID_SczAnimals   ; level slot $11 (non-existent), not part of main table
    8.     even
    9.  
    10. Obj34_CardCheck:   
    11.  
    12.     moveq   #0,d0
    13.     move.b  (Current_Zone).w,d0
    14.     add.b   d0,d0
    15.     add.b   (Current_Act).w,d0
    16.  
    17.     ; Set a word-size offset
    18.     add.w   d0,d0
    19.     move.w  Card_Display_Index(pc,d0.w),d0
    20.     jmp Card_Display_Index(pc,d0.w)
    21.    
    22. Card_Display_Index:
    23.     (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):
    1. Card_Have_Null:
    2.     bra.w   DeleteObject   
    3.  
    4. Card_Have_Act1:
    5.     moveq   #0,d1
    6.     addi.b  #const_actn+0,d1
    7.     bra.s   Card_Display_Frame
    8.    
    9. Card_Have_Act2:
    10.     moveq   #0,d1
    11.     addi.b  #const_actn+1,d1
    12.     bra.s   Card_Display_Frame
    13.    
    14. Card_Have_Act3:
    15.     moveq   #0,d1
    16.     addi.b  #const_actn+2,d1
    17.     ;bra.s  Card_Display_Frame
    18.    
    19. Card_Display_Frame:
    20.     move.b  d1,mapping_frame(a0)
    21.    
    22.     rts
    Now we can build the response table accordingly. So, this table will be like:

    Code (Text):
    1. Card_Display_Index:
    2.     dc.w    Card_Have_Act1-Card_Display_Index, Card_Have_Act2-Card_Display_Index    ; 00 EHZ1/EHZ2
    3.    
    4.     [...]
    5.    
    6.     dc.w    Card_Have_Null-Card_Display_Index, Card_Have_Null-Card_Display_Index    ; 10 SCZ /SCZ
    7.  
    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):
    1. Obj34_MapUnc_147BA:
    2.    
    3.     [...]
    4.    
    5. word_14C32:
    6.     dc.w 7
    7.    
    8.     dc.w $9003, $85D4, $82EA, 0
    9.     dc.w $B003, $85D4, $82EA, 0
    10.     dc.w $D003, $85D4, $82EA, 0
    11.     dc.w $F003, $85D4, $82EA, 0
    12.     dc.w $1003, $85D4, $82EA, 0
    13.     dc.w $3003, $85D4, $82EA, 0
    14.     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):
    1.     word_147E8 > EHZ
    2.     word_14842 > MZ/MTZ (I prefer MZ, but that doesn't matter)
    3.     word_14894 > HTZ
    4.     word_148CE > HPZ
    5.     word_14930 > OOZ
    6.     word_14972 > MCZ
    7.     word_149C4 > CNZ
    8.     word_14A1E > CPZ
    9.     word_14A88 > ARZ
    10.     word_14AE2 > SCZ
    11.     word_14B24 > WFZ
    12.     word_14B86 > DEZ
    13.     word_14BC8 > "ZONE"
    14.     word_14BEA > "1"
    15.     word_14BF4 > "2"
    16.     word_14BFE > "3"
    17.     word_14C08 > yellow bar written "SONIC THE HEDGEHOG"
    18.     word_14C32 > left red stripe
    And the source table we need for Obj34_MapUnc_147BA is built like:

    Code (Text):
    1. Obj34_MapUnc_147BA:
    2.     dc.w    TC_EHZ-Obj34_MapUnc_147BA,     TC_EHZ-Obj34_MapUnc_147BA    ; 00 EHZ1/EHZ2
    3.     dc.w    TC_EHZ-Obj34_MapUnc_147BA,     TC_EHZ-Obj34_MapUnc_147BA    ; 01 ----/----
    4.  
    5.     [...]
    6.  
    7.     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):
    1. LoadTitleCard:
    2.     bsr.s   LoadTitleCard0
    3.     moveq   #0,d0
    4.     move.b  (Current_Zone).w,d0
    5.     move.b  Off_TitleCardLetters(pc,d0.w),d0
    6.     lea TitleCardLetters(pc),a0
    7.     lea (a0,d0.w),a0
    8.     move.l  #vdpComm(tiles_to_bytes(ArtTile_LevelName),VRAM,WRITE),d0
    Just change it to:

    Code (Text):
    1. LoadTitleCard:
    2.     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).