Backporting S2 Bosses to S1?

Discussion in 'Discussion & Q&A' started by That-One-Mega-Man, Nov 27, 2023.

  1. That-One-Mega-Man

    That-One-Mega-Man Doofus Maximus Member

    Joined:
    Jun 18, 2023
    Messages:
    32
    Location:
    Right behind you
    i would have posted in basic questions and answers, but from what i know, this is nowhere near basic
    i'm not sure if its possible for EVERY boss, but perhaps a large majority of bosses from s2 could be backported to s1?
    i have little knowledge of assembly, but i might see what i can do by attempting a port of the Drill Tank from either the Final or Nick Arcade builds
    if anyone else knows the ins and outs of this stuff it'd be nice
     
  2. That-One-Mega-Man

    That-One-Mega-Man Doofus Maximus Member

    Joined:
    Jun 18, 2023
    Messages:
    32
    Location:
    Right behind you
    update on this; i'm attempting to port over the Drill Tank
    i would like some help with this task, though- it's not easy as you could probably infer
    any tips?
    edit; the code i have rn, terribly ported to s1 from s2
    Code:
    Obj10:                        ; to-do; pray that this works when i'm done porting all this, find proper s1 variables for the s2 stuff
        moveq    #0,d0
    ;    move.b    routine(a0),d0
        move.w    off_2EF26(pc,d0.w),d1
        jmp    off_2EF26(pc,d1.w)
    ; ===========================================================================
    off_2EF26:
        dc.w loc_2EF36-off_2EF26
        dc.w loc_2F262-off_2EF26; 1
        dc.w loc_2F54E-off_2EF26; 2
        dc.w loc_2F5F6-off_2EF26; 3
        dc.w loc_2F664-off_2EF26; 4
        dc.w loc_2F7F4-off_2EF26; 5
        dc.w loc_2F52A-off_2EF26; 6
        dc.w loc_2F8DA-off_2EF26; 7
    ; ===========================================================================
    loc_2EF36:
    ;    move.l    #Obj56_MapUnc_2FAF8,mappings(a0)
    ;    move.w    #$23A0,art_tile(a0)
    ;    ori.b    #4,render_flags(a0)
    ;    move.b    #$81,subtype(a0)
    ;    move.w    #$29D0,x_pos(a0)
    ;    move.w    #$426,y_pos(a0)
    ;    move.b    #$20,width_pixels(a0)
    ;    move.b    #$14,y_radius(a0)
    ;    move.b    #4,priority(a0)
    ;    move.b    #$F,collision_flags(a0)
    ;    move.b    #8,collision_property(a0)
    ;    addq.b    #2,(a0)
    ;    move.w    x_pos(a0),objoff_30(a0)
    ;    move.w    y_pos(a0),objoff_38(a0)
    ;    bsr.w    JmpTo61_Adjust2PArtPointer
    ;    jsr    (SingleObjLoad2).l
    ;    bne.w    loc_2EFE4
    ;    move.b    #$10,(a1) ; load obj56 (EHZ boss)
    ;    move.l    a0,objoff_34(a1)
    ;    move.l    a1,objoff_34(a0)
    ;    move.l    #Obj56_MapUnc_2FAF8,mappings(a1)
    ;    move.w    #$3A0,art_tile(a1)
    ;    move.b    #4,render_flags(a1)
    ;    move.b    #$20,width_pixels(a1)
    ;    move.b    #4,priority(a1)
    ;    move.l    x_pos(a0),x_pos(a1)
    ;    move.l    y_pos(a0),y_pos(a1)
    ;    move.b    #$E,routine(a1)
    ;    move.b    #1,anim(a1)
    ;    move.b    render_flags(a0),render_flags(a1)
    return_2F096:
            rts
         
    loc_2F262:
        moveq    #0,d0
        move.b    (a0),d0
        move.w    off_2F270(pc,d0.w),d1
        jmp    off_2F270(pc,d1.w)
     
    loc_2F54E:
        moveq    #0,d0
        move.b    (a0),d0
        move.w    off_2F55C(pc,d0.w),d1
        jmp    off_2F55C(pc,d1.w)
     
    loc_2F5F6:
    ;    tst.b    routine_secondary(a0)
    ;    bne.s    loc_2F626
    ;    bne.s    loc_2F626
    ;    cmpi.w    #$28F0,(Camera_Min_X_pos).w
    ;    bcs.w    JmpTo35_DisplaySprite
    ;    cmpi.w    #$29D0,x_pos(a0)
    ;    ble.s    loc_2F618
    ;    subi.w    #1,x_pos(a0)
    ;    bra.w    JmpTo35_DisplaySprite
    loc_2F664:
        moveq    #0,d0
    ;    move.b    routine_secondary(a0),d0
        move.w    off_2F672(pc,d0.w),d1
        jmp    off_2F672(pc,d1.w)
     
    loc_2F626:
    ;    movea.l    objoff_34(a0),a1 ; a1=object
    ;    btst    #1,objoff_2D(a1)
    ;    beq.w    JmpTo35_DisplaySprite
    ;    btst    #2,objoff_2D(a1)
    ;    bne.w    JmpTo35_DisplaySprite
    ;    move.w    x_pos(a1),x_pos(a0)
    ;    move.w    y_pos(a1),y_pos(a0)
    ;    addi.w    #8,y_pos(a0)
    ;    move.b    status(a1),status(a0)
    ;    bmi.w    JmpTo35_DisplaySprite
    ;    move.b    render_flags(a1),render_flags(a0)
    ;    bra.w    JmpTo35_DisplaySprite
     
    loc_2F7F4:
    ;    tst.b    routine_secondary(a0)
    ;    bne.s    loc_2F824
    ;    cmpi.w    #$28F0,(Camera_Min_X_pos).w
    ;    bcs.w    JmpTo35_DisplaySprite
    ;    cmpi.w    #$299A,x_pos(a0)
    ;    ble.s    loc_2F816
    ;    subi.w    #1,x_pos(a0)
    ;    bra.w    JmpTo35_DisplaySprite  
     
    loc_2F52A:
    ;    subi.w    #1,y_pos(a0)
    ;    subi.w    #1,objoff_2A(a0)
    ;    bpl.w    JmpTo35_DisplaySprite
    ;    move.b    #4,routine(a0)
    ;    lea    (off_2F936).l,a1
    ;    bsr.w    JmpTo17_AnimateSprite
    ;    bra.w    JmpTo35_DisplaySprite
     
    loc_2F8DA:
    ;    movea.l    objoff_34(a0),a1 ; a1=object
    ;    move.l    x_pos(a1),x_pos(a0)
    ;    move.l    y_pos(a1),y_pos(a0)
    ;    move.b    status(a1),status(a0)
    ;    move.b    render_flags(a1),render_flags(a0)
    ;    move.b    objoff_3E(a1),d0
        cmpi.b    #$1F,d0
    ;    bne.s    loc_2F906
    ;    move.b    #2,anim(a0)
     
    off_2F270:
    ;    dc.w loc_2F27C-off_2F270
    ;    dc.w loc_2F2A8-off_2F270; 1
    ;    dc.w loc_2F304-off_2F270; 2
    ;    dc.w loc_2F336-off_2F270; 3
    ;    dc.w loc_2F374-off_2F270; 4
    ;    dc.w loc_2F38A-off_2F270; 5
     
    off_2F55C:
    ;    dc.w loc_2F560-off_2F55C
        ;dc.w loc_2F5C6-off_2F55C; 1
    loc_2F618:
    ;    move.w    #$29D0,x_pos(a0)
    ;    addq.b    #2,routine_secondary(a0)
    ;    bra.w    JmpTo35_DisplaySprite
     
    off_2F672:
    ;    dc.w loc_2F67C-off_2F672
    ;    dc.w loc_2F714-off_2F672; 1
    ;    dc.w loc_2F746-off_2F672; 2
    ;    dc.w loc_2F7A6-off_2F672; 3
    ;    dc.w loc_2F7D2-off_2F672; 4
     
    Last edited: Nov 28, 2023
  3. vladikcomper

    vladikcomper Well-Known Member Member

    Joined:
    Dec 2, 2009
    Messages:
    415
    S2 and S1 engines are 90% identical when it comes to object programming.

    Just compare object structures between the games:
    Now, some technical differences come from the disassemblies, because those games were disassembled by different people at different times, so the same constants and memory equates have different naming conventions, which makes porting slightly less straight forward.

    From your very example, when you set boss position, the version from Sonic 2 disassembly does this:
    Code:
        move.w    #$29D0,x_pos(a0)
        move.w    #$426,y_pos(a0)
    
    The equivalent code for Sonic 1 Github disassembly would be:
    Code:
        move.w    #$29D0,obX(a0)
        move.w    #$426,obY(a0)
    
    Or in case you're working with Sonic 1 Hivebrain's disassembly:
    Code:
        move.w    #$29D0,8(a0)
        move.w    #$426,$C(a0)
    
    All these examples compile to the same machine code, which, if you really want to know is:
    Code:
    317C 29D0 0008
    317C 0426 000C
    
    So the game engines are mostly compatible even on binary level.

    So how do you compile Sonic 2's disassembly code in Sonic 1 disassembly? You start by porting that disassembly's constants/equates. You may manually go over each Sonic 2's constant and replace it with Sonic 1's equivalent, but it could be easier to just copy-paste them to your disassembly as alternatives.

    Just grab this file from S2disasm: https://github.com/sonicretro/s2disasm/blob/master/s2.constants.asm
    And copy-paste most of the stuff from "Object Status Table offsets" section:
    Code:
    ; ---------------------------------------------------------------------------
    ; Object Status Table offsets (for everything between Object_RAM and Primary_Collision)
    ; ---------------------------------------------------------------------------
    ; universally followed object conventions:
    id =             0 ; object ID (if you change this, change insn1op and insn2op in s2.macrosetup.asm, if you still use them)
    render_flags =         1 ; bitfield ; bit 7 = onscreen flag, bit 0 = x mirror, bit 1 = y mirror, bit 2 = coordinate system, bit 6 = render subobjects
    art_tile =         2 ; and 3 ; start of sprite's art
    mappings =         4 ; and 5 and 6 and 7
    x_pos =             8 ; and 9 ... some objects use $A and $B as well when extra precision is required (see ObjectMove) ... for screen-space objects this is called x_pixel instead
    x_sub =            $A ; and $B
    y_pos =            $C ; and $D ... some objects use $E and $F as well when extra precision is required ... screen-space objects use y_pixel instead
    y_sub =            $E ; and $F
    priority =       $18 ; 0 = front
    width_pixels =       $19
    mapping_frame =       $1A
    ; ---------------------------------------------------------------------------
    ; conventions followed by most objects:
    x_vel =           $10 ; and $11 ; horizontal velocity
    y_vel =           $12 ; and $13 ; vertical velocity
    y_radius =       $16 ; collision height / 2
    x_radius =       $17 ; collision width / 2
    anim_frame =       $1B
    anim =           $1C
    prev_anim =       $1D
    anim_frame_duration =   $1E
    status =       $22 ; note: exact meaning depends on the object... for Sonic/Tails: bit 0: left-facing. bit 1: in-air. bit 2: spinning. bit 3: on-object. bit 4: roll-jumping. bit 5: pushing. bit 6: underwater.
    routine =       $24
    routine_secondary =   $25
    angle =           $26 ; angle about the z axis (360 degrees = 256)
    ; ---------------------------------------------------------------------------
    ; conventions followed by many objects but NOT Sonic/Tails:
    collision_flags =   $20
    collision_property =   $21
    respawn_index =       $23
    subtype =       $28
    ; <...>
    
    This will magically make 90% of your ported code to at least compile (this doesn't mean it's fully ported yet though).

    You should then take care of differently named memory equates like (Camera_Min_X_pos).w and this is where you can't get away with a copy-paste, because memory layouts are different between the games.

    For this, you have to manually go over the file I linked above I find equivalent variables in Sonic 1 disassembly (90% of them are guaranteed to exist, because Sonic 2 is built on top of Sonic 1 and the engine is technically the same, except with some upgrades).

    Hope this serves as a good starting point, but I should warn you that porting objects, while technically simple, requires some level of understanding of assembly language and game's structures. You have to know what you're doing, otherwise, you won't figure out even the simplest objects. You cannot just comment out all lines that produce errors like you did and hope at leasts something works, it won't. But you need to understand why, and then you start to understand how to pull it off.
     
    giovanni.gen, ProjectFM, Hame and 2 others like this.
  4. That-One-Mega-Man

    That-One-Mega-Man Doofus Maximus Member

    Joined:
    Jun 18, 2023
    Messages:
    32
    Location:
    Right behind you
    the commenting was a temporary thing until i figured it out
    and no- i do not (fully) know what i am doing, but i might as well give it my best shot with this info in mind