don't click here

Basic Questions & Answers thread

Discussion in 'Engineering & Reverse Engineering' started by Tweaker, May 29, 2008.

  1. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,245
    1,415
    93
    your mom
    Okay, so here's what's going on:
    1. The platform waits for the button to be pressed. When it is, the platform (subtype 7/Obj52_Type07) then changes its subtype to 4/Obj52_Type02.
    2. As per the new subtype, it waits for Sonic to stand on it. When it happens, it increments its subtype to 5/Obj52_Type05.
    3. Obj52_Type05 calls ObjHitWallRight, which OVERWRITES a1 as it gets the distance from the wall towards the right (it calls FindWall).
    4. As a result, when it returns to Obj52_StandOn, it'll enter MvSonicOnPtfm2 with a corrupted a1 value, and will modify the chunk data that referenced in ObjHitWallRight, instead of Sonic's object variables.
    The same problem happens when the subtype gets set to 3/Obj52_Type03. To fix this, when calling Obj52_Move (in both Obj52_Platform and Obj52_StandOn), make sure to restore a1 afterwards, so that PlatformObject and MvSonicOnPtfm2.
     
    Last edited: Apr 4, 2023
  2. DeltaWooloo

    DeltaWooloo

    Be a boss-man and come to my big and tall man shop Member
    I managed to implement the Super Flickies from S3K to S1 successfully as you can see in the image below:
    SuperSonicChallenge000.png
    (haven't got around to giving the birds its palette cycling yet)

    In S3K, the flickies tend to target any nearby enemies and destroy them in your path, however, in this case:
    SuperSonicChallenge001.png
    they don't seem to target enemies.

    I managed to look into this section of the code to see what was the culprit and this seems to be the issue:
    Code (Text):
    1. Obj_SuperTailsBirds_GetDestination:
    2.         tst.b    superTailsBirds_target_found(a0)
    3.         bne.s    .fly_towards_enemy
    4.         tst.b    superTailsBirds_search_delay(a0)
    5.         beq.s    .look_for_target
    6.         subq.b    #1,superTailsBirds_search_delay(a0)
    7.         bra.s    .fly_around_tails
    8. ; ---------------------------------------------------------------------------
    9.  
    10.     .look_for_target:
    11.         bsr.w    Obj_SuperTailsBirds_FindTarget          
    12.         tst.w    d1  
    13.         bne.s    .fly_towards_enemy
    14.  
    15.     .fly_around_tails:
    16.         move.b    superTailsBirds_angle(a0),d0
    17.         jsr    (CalcSine).l
    18.         asr.w    #3,d0
    19.         asr.w    #4,d1
    20.         move.w    (v_player+x_pos).w,d2
    21.         move.w    (v_player+y_pos).w,d3
    22.         subi.w    #$20,d3
    23.         add.w    d0,d2
    24.         add.w    d1,d3
    25.         rts
    26. ; ---------------------------------------------------------------------------
    When I tried to replace the check in .look_for_target so it can detect almost every badnik that shares a similar collision type, they tend to fly out of bounds and/or in random occurrences on-screen and never fly back to Tails. Does anyone know a preferable check I can use so the flickies can target the correct objects? If you want me to send the full object code, then let me know and I can DM you it. (also I ported S3K's collision response list if that helps in any way)
     
  3. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,245
    1,415
    93
    your mom
    It would definitely help to see your implementation of "Obj_SuperTailsBirds_FindTarget" and ".fly_towards_enemy" to understand what's going on.
     
  4. DeltaWooloo

    DeltaWooloo

    Be a boss-man and come to my big and tall man shop Member
    Here you go:

    Code (Text):
    1. Obj_SuperTailsBirds_FindTarget:
    2.         moveq    #0,d1
    3.         lea    (Collision_response_list).w,a4
    4.         move.w    (a4)+,d6
    5.         beq.s    loc_1A41A.return
    6.         moveq    #0,d0
    7.         addq.b    #2,(_unkF66C).w
    8.         cmp.b    (_unkF66C).w,d6
    9.         bhi.s    loc_1A41A
    10.         move.b    #0,(_unkF66C).w
    11.        
    12. loc_1A41A:
    13.         move.b    (_unkF66C).w,d0
    14.         sub.w    d0,d6
    15.         lea    (a4,d0.w),a4
    16.  
    17.     .loop:
    18.         movea.w    (a4)+,a1
    19.         move.b    obColType(a1),d0
    20.         beq.s    .ignore_object
    21.         bsr.s    .check_if_object_valid
    22.  
    23.     .ignore_object:
    24.         subq.w    #2,d6
    25.         bne.s    loc_1A41A
    26.        
    27.     .return:
    28.         rts
    29. ; End of function Obj_SuperTailsBirds_FindTarget
    Just as a quick note, uncommenting the lines that use _unkF66C doesn't change much.

    Code (Text):
    1.     .fly_towards_enemy:
    2.         movea.w    superTailsBirds_target_address(a0),a1
    3.         move.w    x_pos(a1),d2
    4.         move.w    y_pos(a1),d3
    5.         tst.b    render_flags(a1)
    6.         bpl.s    .enemy_off_screen
    7.         move.w    x_pos(a0),d0
    8.         sub.w    d2,d0
    9.         addi.w    #$C,d0
    10.         cmpi.w    #$18,d0
    11.         bhs.s    .enemy_out_of_range
    12.         move.w    y_pos(a0),d1
    13.         sub.w    d3,d1
    14.         addi.w    #$C,d1
    15.         cmpi.w    #$18,d1
    16.         bhs.s    .enemy_out_of_range
    17.         bsr.s    .hit_enemy
    18.  
    19.     .enemy_off_screen:
    20.         move.b    #0,superTailsBirds_target_lock(a1)
    21.         move.b    #0,superTailsBirds_target_found(a0)
    22.         move.b    #60*2,superTailsBirds_search_delay(a0)
    23.  
    24.     .enemy_out_of_range:
    25.         rts
    26. ; End of function Obj_SuperTailsBirds_GetDestination
     
    Last edited: Apr 4, 2023
  5. Aight, my hack currently has hit a snag. This is with the implementation of uncompressed chunks and its co-operation with water. My model 2 Genesis and the recent public Blastem release (0.6.2) freeze when loading in on Labyrinth Act 1/2 (basically when water's on screen). I was told that another hack also had the same problem, and was clued in how they fixed it: apparently they redid the HBlank (PalToCRAM) code. How can I optimize it to work with uncompressed chunks?
     
  6. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,742
    338
    63
    SonLVL
    Why does PalToCRAM care about the chunks?
     
  7. That's the weird part. I was trying to figure out what could've caused the freeze when I came across it, and I was worried that the implementation had something to do with it. It came into play with the integration of uncompressed chunks, the lack of the surface object didn't change anything (was ported to a different project without those chunks, it worked). When stripping away the code within PalToCRAM, it doesn't freeze in that spot on the aforementioned devices.
     
  8. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    Anyone have any clue what these errors are?
    [​IMG]

    It's the Xenowhirl S2 disassembly by the way (been trying to get the Clone Driver working).
     
  9. OrionNavattan

    OrionNavattan

    Tech Member
    165
    164
    43
    Oregon
    It’s a missing conditional assembly terminator. For every ‘if’ directive, there has to be a corresponding ‘endif’. In this case, there’s one missing somewhere (and unfortunately it can be difficult to find the missing one).
     
  10. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    I think I fixed the endif, though the first error still won't go away and I have no idea what's wrong with the line it pinpoints.
     
  11. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,742
    338
    63
    SonLVL
    Can you show us the line? It seems like you used a character that the assembler doesn't support in a symbol name, only standard latin letters (A-Z), numbers, and underscores are valid in names.
     
  12. OrionNavattan

    OrionNavattan

    Tech Member
    165
    164
    43
    Oregon
    If '0xa0' is the problem symbol, then this would be the result of AS not allowing digits as the first character in symbol names. As MainMemory said, sharing the problematic line would help.
     
  13. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    I was gonna copy the routine onto here, but when I pasted it, the anomoly shown up on here, seems to not be visible on the editor. Managed to fix it.

    It does link to another problem however, my disassembler is out of sync with my ASM it seems. For example the second error lists it on line 94909 but my asm only goes to 94891. Is there anything that would make it skip forward a few lines like that, since it make looking for errors kinda hard.
     
  14. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,200
    430
    63
    Japan
    I'm gonna take a wild stabbing guess...

    ...have you included a binary file using "include" instead of "binclude", and as a result it's trying to process the binary file as if it were an assembly file of text?

    ...another posibility might be the text file format the source is saved as, I donno if AS supports anything other than ANSI?
     
  15. Okay, I'm trying to get this guide working on the Github disassembly, but I'm getting pelt with nonsensical errors in the handler.
    https://info.sonicretro.org/SCHG_How-to:Add_a_new_zone_in_Sonic_1

    This is just one of these errors, oddly complaining about player mappings/DPLC's when all I'm doing is just applying the "add a new zone" guide and following its steps.
    [​IMG]

    I'm legitimately frustrated here since as I've mentioned, I've done what the guide says but this BS keeps happening.
     
  16. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,200
    430
    63
    Japan
    I think it's AniArt_Load, and I think you haven't put a pointer to "AniArt_none" on the end of the list.

    The register contents are telling me it's just done the level drawing from V-blank. Given most data registers are blank, there was no drawing.

    d0 contains 533C. The word which exists of the first instruction for "AniArt_GHZ", is 5338, so if "AniArt_none"s pointer is not there, that's the word it'll read.

    On a normal copy of Sonic 1, the "AniArt_Load" routine is at approximately 1BFXX + 5CC8 = 212XX approx. Your crash location is very close. I suspect random garbage code caused d0 to increment by 4 before eventually hitting a word beginning with F, and triggering F emulation.
     
  17. Cokie

    Cokie

    C. Okie Member
    75
    22
    8
    Want to ensure I understand some things correctly regarding The Nemesis Compression Subroutine Nem_Build_Code_Table.Assuming we are In normal mode ( not xor).

    The first few questions relate to the first descriptive field read (pallets index , repeat, length In bits etc) upon entering sub routine Nem_Build_Code_Table
    isn't the first byte read at the top instruction: move.b (a0)+,d0 ; read first byte .. Isn't this the same thing as the byte that can be read 3 ways ( sign set , sign clear, FF ). But it is just that it is the initial time read so it is like the starts the end . And wont this first read byte read the first time always have sign bit set?

    the next byte read just at NemBCT_Chkend in this first loop around for the first loop is the repeat count and length of code.
    the comment for the code that checks if sign bit being set that signifies a new pallete index... Isn't this comment intended for proceeding loops adter the first one that go from using the same palette index to a new one?
    the next byte is the code itslf is the code (is both cases of whether or not we branch to NemBCT_ShortCode)

    Then it loops back to NemBCT_Looop. And we gon on to the next pass:

    Am I understanding this right:

    The first byte here would be the second byte in the first loop through upon the call to Nem_Build_Code_Table, here it is the

    first byte read. ( More questions on this obscuritiy below at bottom )

    the first byte read is the first byte if the msb clear and we dont branch , is not the new pallete index, but rather the copy count and the code length in bits. Otherwise, if the MSB is clear of this byte ,this byte is the new pallete index and we branch to Nem_ChkEnd and if its' not FF then we pass down again to NEMBCT_LOOP where we read a byte at the top of this subroutine
    and this byte read is now the second byte ( where it just was the first ). ( again more question below on this )
    then next byte is no different - it's the code itself
    the byte after is the three different possible meaning byte (MSB set , MSB clear, FF )

    So weird flow seen in this subroutine occurs .... where the the meaning of an instruction and the purpose of the part of the descriptive

    byte read changes ...

    like the cmp.b #$80,d0 isn't functional if new palette index since this is the second byte

    and where a pass of NEMBCT_LOOP gets the first byte then if the sign is set for new pallete

    index goes back to Nem_ChkEnd and so when it goes and passes down through to NEMBCT_Loop again

    and reads another byte of what would be the first byte, it isn't the first byte....

    or how the first byte read from NEMBCT_LOOP if sign bit is clear is the copy count and length of code instead of the

    pallete index -

    the purposed of an instruction and the part of the compression byte read is changing .... What do you call this kind of codeing phenemeon in the world of code design?

    What are ways to figure out the purpose of code and what it does that while rev engineering it without comments ...

    for example when a certain instruction can be useless in certain cases but yet they aren't branched
    over , making it seem they always serve a purpose in all cased... It would make one reverse engineering it tio assume it always have a purpose.

    Or a rev engineer may assume the bytes read's purpose is always the same, when in fact it changes if new pallete in example. How can one quickly sort out these kinds of thing's while breaking apart code?

    did they use this confusing code style like this for the reason of efficiency? I assume it's to make the bytes after
    them (code itself etc ) to always have the same purpose regardless of if same pallete index or new pallete index...?

    What is the best way to comment code that where some code loops back and thus instructions get different parts of the byte-stream in different
    cases for example in our subroutine here? Can you have layers of comments based on flow - seems too confusing? Is there literature of any kind
    on programming code design theories / principles in assembly that cover such a phenomenon where all this craziness happens? And the best way to reverse engineer it and comment it? Sonic dissasemblies comments them in a way where it is assumed you know which of the several flows that lead to the
    insturction that the instruction is commenting about.

    Lastly, looking through Nem_BCT_ShortCode I see d5 is the number of iterations of possible codes that are longer
    codes with the prefix of this code and since it uses prefix-free codes (no valid code is a prefix of a longer code) , it
    needs to have multiple entries for all the longer coded with it's prefix in the table? I don’t understand this table. I dont The first few questions relate to the first descriptive field read ( pallets index , repeat, length In bits etc) upon entering sub routine Nem_Build_Code_Table

    understand this table can someone explain it to me and how it relates to describing a compressed form of the patterns?

    Also Why when the code is 0 or if the code is 1 or if the code is 2 , d1 ( the number bits needed to shift left for the codes highest bit to be in bit 7 ) is equal to 5
    and I see other weird things like if it's 0x7 binary then d1 is 4 and not 5 since ? it seems it'd be 5 shifts for 7 to go to being the biggest number
    but maybe it's code is 7 but i's length is not the bits needed for 7 ( 3 ) but rather 4 ????
    Thnx
     
  18. XPointZPoint

    XPointZPoint

    That's no good! Member
    56
    13
    8
    nevermind, the branch to the milliseconds code was in the wrong place, it should've been at the start of HUD_ChkLives, not at the end

    (move this to the trash if you want to mods)

    [​IMG]
    I added a milliseconds / centiseconds counter to the timer in stages, but it doesn't appear. As a matter of fact, if I perform a spin dash, the VRAM overwrites the missing extra numbers that are supposed to be drawn.

    Any way I can move the VRAM around to prevent this from happening?

    I'll provide the HUD_Update subroutine as well as mappings for the HUD (this is Hivebrain btw)

    Code (Text):
    1. ; ---------------------------------------------------------------------------
    2. ; Subroutine to    update the HUD
    3. ; ---------------------------------------------------------------------------
    4.  
    5. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    6.  
    7.  
    8. HudUpdate:
    9.         tst.w    ($FFFFFFFA).w    ; is debug mode    on?
    10.         bne.w    HudDebug    ; if yes, branch
    11.         tst.b    ($FFFFFE1F).w    ; does the score need updating?
    12.         beq.s    Hud_ChkRings    ; if not, branch
    13.         clr.b    ($FFFFFE1F).w
    14.         move.l    #$5C800003,d0    ; set VRAM address
    15.         move.l    ($FFFFFE26).w,d1 ; load    score
    16.         bsr.w    Hud_Score
    17.  
    18. Hud_ChkRings:
    19.         tst.b    ($FFFFFE1D).w    ; does the ring    counter    need updating?
    20.         beq.s    Hud_ChkTime    ; if not, branch
    21.         bpl.s    loc_1C6E4
    22.         bsr.w    Hud_LoadZero
    23.  
    24. loc_1C6E4:
    25.         clr.b    ($FFFFFE1D).w
    26.         move.l    #$5F400003,d0    ; set VRAM address
    27.         moveq    #0,d1
    28.         move.w    ($FFFFFE20).w,d1 ; load    number of rings
    29.         bsr.w    Hud_Rings
    30.  
    31. Hud_ChkTime:
    32.         tst.b    ($FFFFFE1E).w    ; does the time    need updating?
    33.         beq.s    Hud_ChkLives    ; if not, branch
    34.         tst.w    ($FFFFF63A).w    ; is the game paused?
    35.         bne.s    Hud_ChkLives    ; if yes, branch
    36.         lea    ($FFFFFE22).w,a1
    37.         cmpi.l    #$93B3B,(a1)+    ; is the time 9.59?
    38.         beq.s    TimeOver    ; if yes, branch
    39.         addq.b    #1,-(a1)
    40.         cmpi.b    #60,(a1)
    41.         bcs.s    Hud_ChkLives
    42.         move.b    #0,(a1)
    43.         addq.b    #1,-(a1)
    44.         cmpi.b    #60,(a1)
    45.         bcs.s    loc_1C734
    46.         move.b    #0,(a1)
    47.         addq.b    #1,-(a1)
    48.         cmpi.b    #9,(a1)
    49.         bcs.s    loc_1C734
    50.         move.b    #9,(a1)
    51.  
    52. loc_1C734:
    53.         move.l    #$5E400003,d0
    54.         moveq    #0,d1
    55.         move.b    ($FFFFFE23).w,d1 ; load    minutes
    56.         bsr.w    Hud_Mins
    57.         move.l    #$5EC00003,d0
    58.         moveq    #0,d1
    59.         move.b    ($FFFFFE24).w,d1 ; load    seconds
    60.         bsr.w    Hud_Secs
    61.  
    62. Hud_ChkLives:
    63.         tst.b    ($FFFFFE1C).w    ; does the lives counter need updating?
    64.         beq.s    Hud_ChkBonus    ; if not, branch
    65.         clr.b    ($FFFFFE1C).w
    66.         bsr.w    Hud_Lives
    67.         bsr.s   CentiSecond
    68.  
    69. Hud_ChkBonus:
    70.         tst.b    ($FFFFF7D6).w    ; do time/ring bonus counters need updating?
    71.         beq.s    Hud_End        ; if not, branch
    72.         clr.b    ($FFFFF7D6).w
    73.         move.l    #$6E000002,($C00004).l
    74.         moveq    #0,d1
    75.         move.w    ($FFFFF7D2).w,d1 ; load    time bonus
    76.         bsr.w    Hud_TimeRingBonus
    77.         moveq    #0,d1
    78.         move.w    ($FFFFF7D4).w,d1 ; load    ring bonus
    79.         bsr.w    Hud_TimeRingBonus
    80.  
    81. Hud_End:
    82.         rts
    83.  
    84.  
    85. TimeOver:                ; XREF: Hud_ChkTime
    86.         clr.b    ($FFFFFE1E).w
    87.         lea    ($FFFFD000).w,a0
    88.         movea.l    a0,a2
    89.         bsr.w    KillSonic
    90.         move.b    #1,($FFFFFE1A).w
    91.         rts
    92.  
    93. CentiSecond:
    94.       move.l   #$75400003,d0
    95.       moveq   #0,d1
    96.       move.b   ($FFFFFE25).w,d1
    97.       mulu.w   #$1AA,d1
    98.       lsr.l   #8,d1
    99.       jsr   Hud_Secs
    100.       rts

    Code (Text):
    1. ; ---------------------------------------------------------------------------
    2. ; Sprite mappings - SCORE, TIME, RINGS
    3. ; ---------------------------------------------------------------------------
    4.       dc.w byte_1C5BC-Map_obj21, byte_1C5F0-Map_obj21
    5.       dc.w byte_1C624-Map_obj21, byte_1C658-Map_obj21
    6.    
    7.       ; Y-Pos, layout, VDP (2bytes - plane, pallet line (2bits), flip, mirror, first tile (11bits) - ), X-Pos
    8. byte_1C5BC:   dc.b $C
    9.       dc.b $80, $D, $80, 0, 0      ; SCOR
    10.       dc.b $80, $D, $80, $18,   $20   ; E + score left numbers
    11.       dc.b $80, $D, $80, $20,   $40   ; score right numbers
    12.       dc.b $90, $D, $80, $10,   0   ; TIME
    13.       dc.b $90, $D, $80, $28,   $28   ; minutes : seconds
    14.       dc.b $90, 1, $80, $2A, $48   ; :
    15.       dc.b $90, 5, $80, $E0, $50   ; centiseconds
    16.       dc.b $A0, $D, $80, 8, 0      ; RING
    17.       dc.b $A0, 1, $80, 0, $20   ; S
    18.       dc.b $A0, 9, $80, $30, $30   ; number of rings
    19.       dc.b $40, 5, $81, $A, 0      ; lives icon
    20.       dc.b $40, $D, $81, $E, $10   ; lives counter
    21.       dc.b 0
    22. byte_1C5F0:   dc.b $C
    23.       dc.b $80, $D, $80, 0, 0      ; SCOR
    24.       dc.b $80, $D, $80, $18,   $20   ; E + score left numbers
    25.       dc.b $80, $D, $80, $20,   $40   ; score right numbers
    26.       dc.b $90, $D, $80, $10,   0   ; TIME
    27.       dc.b $90, $D, $80, $28,   $28   ; minutes : seconds
    28.       dc.b $90, 1, $80, $2A, $48   ; :
    29.       dc.b $90, 5, $80, $E0, $50   ; centiseconds
    30.       dc.b $A0, $D, $A0, 8, 0      ; RING
    31.       dc.b $A0, 1, $A0, 0, $20   ; S
    32.       dc.b $A0, 9, $80, $30, $30   ; number of rings
    33.       dc.b $40, 5, $81, $A, 0      ; lives icon
    34.       dc.b $40, $D, $81, $E, $10   ; lives counter
    35.       dc.b 0
    36. byte_1C624:   dc.b $C
    37.       dc.b $80, $D, $80, 0, 0      ; SCOR
    38.       dc.b $80, $D, $80, $18,   $20   ; E + score left numbers
    39.       dc.b $80, $D, $80, $20,   $40   ; score right numbers
    40.       dc.b $90, $D, $A0, $10,   0   ; TIME
    41.       dc.b $90, $D, $80, $28,   $28   ; minutes : seconds
    42.       dc.b $90, 1, $80, $2A, $48   ; :
    43.       dc.b $90, 5, $80, $E0, $50   ; centiseconds
    44.       dc.b $A0, $D, $80, 8, 0      ; RING
    45.       dc.b $A0, 1, $80, 0, $20   ; S
    46.       dc.b $A0, 9, $80, $30, $30   ; number of rings
    47.       dc.b $40, 5, $81, $A, 0      ; lives icon
    48.       dc.b $40, $D, $81, $E, $10   ; lives counter
    49.       dc.b 0
    50. byte_1C658:   dc.b $C
    51.       dc.b $80, $D, $80, 0, 0      ; SCOR
    52.       dc.b $80, $D, $80, $18,   $20   ; E + score left numbers
    53.       dc.b $80, $D, $80, $20,   $40   ; score right numbers
    54.       dc.b $90, $D, $A0, $10,   0   ; TIME
    55.       dc.b $90, $D, $80, $28,   $28   ; minutes : seconds
    56.       dc.b $90, 1, $80, $2A, $48   ; :
    57.       dc.b $90, 5, $80, $E0, $50   ; centiseconds
    58.       dc.b $A0, $D, $A0, 8, 0      ; RING
    59.       dc.b $A0, 1, $A0, 0, $20   ; S
    60.       dc.b $A0, 9, $80, $30, $30   ; number of rings
    61.       dc.b $40, 5, $81, $A, 0      ; lives icon
    62.       dc.b $40, $D, $81, $E, $10   ; lives counter
    63.       even
     
  19. Cokie

    Cokie

    C. Okie Member
    75
    22
    8
    A dumb question. How did the snasm68 / asm68k assemblers get edited to fix stuff as people have in the SR community? Did they have to reverse engineer it and modify it in assembly or is their a C source?
     
  20. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,245
    1,415
    93
    your mom
    They were reverse engineered. There is no source code leak or even a full decompilation that I know of.