don't click here

Quackshot Terrain Collision Overlay Script

Discussion in 'Engineering & Reverse Engineering' started by Cokie, Jul 19, 2023.

  1. Cokie


    C. Okie Member
    I reversed engineered a small amount of Quackshot to find the Subroutine that solid terrain the player collides against such as walls and floor. Upon finding it, I wrote a lua overlay script to overlay the solid terrain.

    The Overlay Script Running

    The Lua Overlay Script

    The subroutine is:

    Code (Text):
    1. ROM:0000B9F8 Terrain_Collision_Tlle:                 ; CODE XREF: sub_B9C2+24↑p
    2. ROM:0000B9F8                                         ; sub_11790+A↓p
    3. ROM:0000B9F8                                         ; DATA XREF: ...
    4. ROM:0000B9F8                 move.w  d2,d4           ; d4 = d2 = X
    5. ROM:0000B9FA                 move.w  d1,d5           ; d5 = d1 = Y
    6. ROM:0000B9FC                 movea.l (SomeMapping_1).w,a2
    7. ROM:0000BA00                 asr.w   #8,d4           ; X integer division 256 signed and Y integer division 256 signed
    8. ROM:0000BA02                 asr.w   #8,d5
    9. ROM:0000BA04                 asl.w   #5,d5
    10. ROM:0000BA06                 add.w   d5,d4
    11. ROM:0000BA08                 move.b  (a2,d4.w),d4
    12. ROM:0000BA0C                 movea.l (SomeMapping_2).w,a2
    13. ROM:0000BA10                 lsl.w   #8,d4
    14. ROM:0000BA12                 move.w  d2,d5
    15. ROM:0000BA14                 lsr.w   #4,d5
    16. ROM:0000BA16                 andi.w  #$F,d5
    17. ROM:0000BA1A                 add.w   d4,d5
    18. ROM:0000BA1C                 move.w  d1,d4
    19. ROM:0000BA1E                 andi.w  #$F0,d4
    20. ROM:0000BA22                 add.w   d4,d5
    21. ROM:0000BA24                 moveq   #0,d4
    22. ROM:0000BA26                 move.b  (a2,d5.w),d4
    23. ROM:0000BA2A                 rts
    24. ROM:0000BA2A ; End of function Terrain_Collision_Tlle
    As for how I found it , here's the notes of my described process. NOTE THERE MAY BE SOME MISTAKES

    In Quackshot my method was to jump and see when the player's y no longer
    making him jump when he hits the floor and see if something triggers
    that when he hits the floor. I want some information first such as player's
    x and y at least and have them named to make it easier to understand.
    I wasn't sure if I needed x but found it just in case.
    I found the players x and y variables address in RAM and named them.
    I made equates for displacements that reference any variable I am interested
    in in order to make it easier to read. Say $14,(a0) is players Y;
    then I will manually add the equate to any xref to the
    players Y that using the displacement. To find the xref since I
    hade a incomplete disassembly, I wrote a bizhawk lua script that recorded
    the PC of any instruction that read / wrote to the particular ram variable
    and then displayed a list of them.

    Now I need the player to jump to see if once the player
    lands on the ground from jumping if a different instruction
    writes to the player's y and see what triggers that. I ran the
    IDA debugger and your gens_68k emulator loading the game.
    I set a write breakpoint on players Y. I wrote a lua script that makes the
    jump button pressed. I find the same instruction writes to the players Y
    when he lands, so I need to switch my game plan. I find the player's Y velocity
    by looking at the code above the write to player y to see what is being added to
    it. I name the player's Y Velocity and make a displacement equates running the
    bizhawk script to find all xref in code and manually naming the references that
    use displacements. So now I am interested in Y_Vel. I set a write breakpoint
    on y_vel and make player jump using my script and see what code is called.

    When player is standing on ground this code is called to clear y_vel:

    Code (Text):
    1. ROM:00005CD8 tst.b   (byte_FFEE45).w                 ; IF BYTE FFEE45 == 0 goto Byte_FFEE45_Is_Zero else continue
    2. ROM:00005CDC beq.s   Byte_FFEE45_Is_Zero
    3. ROM:00005CDE clr.b   (byte_FFEE45).w
    4. ROM:00005CE2 clr.l   Y_Vel(a0)                       ; clear y velocity
    Upon the player initially jumping , this is called writing to y vel:

    Code (Text):
    1. ROM:0000708A loc_708A:                               ; CODE XREF: ROM:00007082↑j
    2. ROM:0000708A move.l  d0,Y_Vel(a0)
    We can not worry about this, though you may want to mark it in your disassembly
    as when he first jumps

    While he is jumping , this is called

    Code (Text):
    1. ROM:00005DE4 sub_5DE4:                               ; CODE XREF: ROM:000053D4↑p
    2. ROM:00005DE4                                         ; ROM:00005478↑p ...
    3. ROM:00005DE4 move.l  (a1),d0                         ; NOTE THAT BASED ON THE CONDITION, WHAT IS SET CAN BE OTHER THINGS INCLUDING X VELOCIY.
    4. ROM:00005DE6 cmp.l   d1,d0
    5. ROM:00005DE8 bgt.s   loc_5DF2
    6. ROM:00005DEA add.l   d2,d0
    7. ROM:00005DEC cmp.l   d1,d0                           ; if d0 >= d1 then branch 5dfc else continue
    8. ROM:00005DEC                                         ; todo find out what is going on here in that it
    9. ROM:00005DEC                                         ; determines outcome of setting y  velocity.
    10. ROM:00005DEC                                         ; see jump notes
    11. ROM:00005DEE bge.s   Set_Y_Velocity_To_D1
    12. ROM:00005DF0 bra.s   Set_Y_Velocity_To_D0
    13. ROM:00005DF2 ; ---------------------------------------------------------------------------
    14. ROM:00005DF2
    15. ROM:00005DF2 loc_5DF2:                               ; CODE XREF: sub_5DE4+4↑j
    16. ROM:00005DF2 sub.l   d2,d0
    17. ROM:00005DF4 cmp.l   d1,d0
    18. ROM:00005DF6 ble.s   Set_Y_Velocity_To_D1
    19. ROM:00005DF8
    20. ROM:00005DF8 Set_Y_Velocity_To_D0:                   ; CODE XREF: sub_5DE4+C↑j
    21. ROM:00005DF8 move.l  d0,(a1)                         ; how is d0 determined ?  - it is assigned to player y velocity
    22. ROM:00005DFA rts
    23. ROM:00005DFC ; ---------------------------------------------------------------------------
    24. ROM:00005DFC
    25. ROM:00005DFC Set_Y_Velocity_To_D1:                   ; CODE XREF: sub_5DE4+A↑j
    26. ROM:00005DFC                                         ; sub_5DE4+12↑j
    27. ROM:00005DFC move.l  d1,(a1)                         ; how is d1 determined ?  - it is assigned to player y velocity
    28. ROM:00005DFE rts
    ROM:00005DF8 move.l d0,(a1) is called most the time while jumping except for one frame when player is at the top of his jump. Here at the top, ROM:00005DFC move.l d1,(a1) is called. You may want to investigate this code.

    When the player descends form jump and is about to his the ground again, this is called,
    writing to y vel. It is the same code as the first one

    Code (Text):
    1. ROM:00005CD8 tst.b   (byte_FFEE45).w                 ; IF BYTE FFEE45 == 0 goto loc_5cec else continue
    2. ROM:00005CDC beq.s   Byte_FFEE45_Is_Zero
    3. ROM:00005CDE clr.b   (byte_FFEE45).w
    4. ROM:00005CE2 clr.l   Y_Vel(a0)                       ; clear y velocity
    5. ROM:00005CE6 clr.l   $38(a0)
    6. ROM:00005CEA rts
    Now I want to know a little bit about the program flow. I want to see why
    ROM:00005CE2 clr.l Y_Vel(a0) isn't called when you are in mid jump and
    why ROM:00005DF8 move.l d0,(a1) and ROM:00005DFC move.l d1,(a1) aren't called
    when you land. I made a break point just before ROM:00005CE2 clr.l Y_Vel(a0)
    at ROM:00005CD8 tst.b (byte_FFEE45).w , and realize it gets called whether
    you are standing on ground or jumping. So I realize byte_FFEE45 determines
    is y velocity is cleared to zero . In other words,
    when Byte_FFEE45_Is_Zero is not equal to zero then the y velocity
    is cleared to zero. I set a write breakpoint on byte_FFEE45. Most of
    the time in the game, byte_FFEE45 is written back and forth, each frame
    by these two instructions.

    ROM:0000724C st (byte_FFEE45).w

    ROM:00005CDE clr.b (byte_FFEE45).w

    I cause the player to jump again using my lua script and see these
    two instructions aren't called while the player is jumping in the air. When
    he starts to land , ROM:0000724C st (byte_FFEE45).w is called.

    I back trace to see what caused this instruction to be called again once
    the player is about to land.

    Code (Text):
    1. ROM:0000718E sub_718E:                               ; CODE XREF: interest_sub_5CC8+C↑p
    2. ROM:0000718E                                         ; DATA XREF: interest_sub_5CC8+C↑o
    3. ROM:0000718E tst.w   Y_Vel(a0)
    4. ROM:00007192 bmi.w   YVel_Negative
    5. ROM:00007196 move.w  #0,d1
    6. ROM:0000719A moveq   #0,d2
    7. ROM:0000719C jsr     sub_B9C2(pc)
    8. ROM:000071A0 lea     unk_71EA(pc),a1
    9. ROM:000071A4 add.w   d7,d7
    10. ROM:000071A6 move.w  (a1,d7.w),d7
    11. ROM:000071AA jsr     (a1,d7.w)
    From this I looked at sub_B9C2: I added some more variable names of RAM / ROM
    and labels as you probably can tell by now :) - You may want to add the Level_MaX
    X and Y RAM Variables to your disassembly.

    Code (Text):
    1. ROM:0000B9C2 sub_B9C2:                               ; CODE XREF: sub_718E+E↑p
    2. ROM:0000B9C2                                         ; sub_718E+F0↑p ...
    3. ROM:0000B9C2 add.w   Y_Pos(a0),d1                    ; Add Y_Pos to d1, many calls to sub_b9C2 CANT ASSUME ALL HAVE D1 = 0 WHEN CALL. Todo what is YPos + d1 and what is d1 right before this instruction and can it be several values.
    4. ROM:0000B9C6 ble.s   set_d7_to_zero                  ; branch if <= 0 - Note ble is sign comparison
    5. ROM:0000B9C8 move.w  (Level_Max_Y).w,d4
    6. ROM:0000B9CC addi.w  #$D0,d4                         ; #$D0 is 208 which is 224 minus 32 . where 224 is the Sega window height
    7. ROM:0000B9D0 cmp.w   d4,d1
    8. ROM:0000B9D2 bcc.s   set_d7_to_zero                  ; if d1 + YPos >= Level_Max_Y + 208  branch - todo find out purpose  note unsigned comparison
    9. ROM:0000B9D4 add.w   X_Pos(a0),d2                    ; Add X_Pos to d2, many calls to sub_b9C2 CANT ASSUME ALL HAVE D2 = 0 WHEN CALL. Todo what is XPos + d2 and what is d2 right before this instruction and can it be several values.
    10. ROM:0000B9D8 bmi.s   set_d7_to_zero                  ; branch if minus - signed comparison - why is this using bmi and ble is used above ?
    11. ROM:0000B9DA move.w  (Level_Max_X).w,d4
    12. ROM:0000B9DE addi.w  #$140,d4                        ; #$140 is 320 screen width
    13. ROM:0000B9E2 cmp.w   d4,d2
    14. ROM:0000B9E4 bcc.s   set_d7_to_zero                  ; if d2 + xPos >= Level_Max_X + 320 branch - todo find out purpose note unsigned comparison
    15. ROM:0000B9E6 bsr.s   Terrain_Collision_Tlle
    16. ROM:0000B9E8 moveq   #0,d7
    17. ROM:0000B9EA lea     (SomeMapping_3).w,a2
    18. ROM:0000B9EE move.b  (a2,d4.w),d7
    19. ROM:0000B9F2 rts
    At this point, I determined that the checks of the Level X and Y Max could be ignored. I look at the subroutine called at ROM B9E6 , which I later named Terrain_Collision_Tlle. By the Way ,
    I believe SomeMapping_3 is Collison mapping you and you may want to add it :)
    Double check as I just found this stuff and haven't verified.

    So Terrain_Collision_Tlle d1 = y d2 = x. SomeMapping_1 may be the
    256X256 pixel block mapping . I am not sure though. It is a pointer to the mapping address.
    SomeMapping_2 I believe is a pointer to the 8x8 tile mapping address. 0xFF0000 I think, but
    not sure if it changes. May want to verify.

    Code (Text):
    1. ROM:0000B9F8 ; Parameters
    2. ROM:0000B9F8 ; d1 = Y
    3. ROM:0000B9F8 ; d2 = X
    4. ROM:0000B9F8
    5. ROM:0000B9F8 Terrain_Collision_Tlle:                 ; CODE XREF: sub_B9C2+24↑p
    6. ROM:0000B9F8                                         ; sub_11790+A↓p
    7. ROM:0000B9F8                                         ; DATA XREF: ...
    8. ROM:0000B9F8 move.w  d2,d4                           ; d4 = d2 = X
    9. ROM:0000B9FA move.w  d1,d5                           ; d5 = d1 = Y
    10. ROM:0000B9FC movea.l (SomeMapping_1).w,a2
    11. ROM:0000BA00 asr.w   #8,d4                           ; X integer division 256 signed and Y integer division 256 signed
    12. ROM:0000BA02 asr.w   #8,d5
    13. ROM:0000BA04 asl.w   #5,d5
    14. ROM:0000BA06 add.w   d5,d4
    15. ROM:0000BA08 move.b  (a2,d4.w),d4
    16. ROM:0000BA0C movea.l (SomeMapping_2).w,a2
    17. ROM:0000BA10 lsl.w   #8,d4
    18. ROM:0000BA12 move.w  d2,d5
    19. ROM:0000BA14 lsr.w   #4,d5
    20. ROM:0000BA16 andi.w  #$F,d5
    21. ROM:0000BA1A add.w   d4,d5
    22. ROM:0000BA1C move.w  d1,d4
    23. ROM:0000BA1E andi.w  #$F0,d4
    24. ROM:0000BA22 add.w   d4,d5
    25. ROM:0000BA24 moveq   #0,d4
    26. ROM:0000BA26 move.b  (a2,d5.w),d4
    27. ROM:0000BA2A rts
    28. ROM:0000BA2A ; End of function Terrain_Collision_Tlle
    So it looks like it first finds the 256X256 pixel block the player
    is in and then using that finds the 8x8 pixel tile that player is in. I could be
    wrong. I am not sure if it is interleaved.[/MEDIA]
    Last edited: Nov 17, 2023
  2. JeeWee


    Hi Cokie!

    I dig up this topic several month after its original posting, because I stumbled upon your work on Quackshot on Youtube.
    I've been speedrunning this game for the past year, I recently took the world record, and I was looking into better understanding the core engine of the game, in order to make sense of some (useful) glitches, and maybe replicate them in other places, or find new ones.

    I'm fairly certain a lot of very interesting stuff could be found about the game, mainly by analyzing the different revisions. There's apparently only a few minor changes between rev.00 and rev.01, but it seems like the collisions were changed, indicating that some glitches could occur in rev.00 (there's one known spot where it's the case).

    What's interesting, glitches in specific spots of the game could lead to pretty big sequence breaking of the run, which currently doesn't feature entire level skips (only partial skips).

    I'd be very interested in showing you some known glitches in order to further our understanding of them, would you be down to talk about it?

    Also, i saw that you developed another LUA script to freely control donald's position, but it's currently not on github, would you be willing to share it?

    Thanks :)
  3. Cokie


    C. Okie Member
    Sorry for late reply. Appreciate yours and everyone's interest. I'd be happy to collab JeeWee. I am around , it may not be all the time, but I come around here and there lol . Here is a early version I just redid of a script to move player . Message me how you would like it to change. It is messy at point currently and save states will probably cause unconsidered results

    Please contact me feel free. Id like to learn about this and help in any way I can. :)