don't click here

Some changes/fixes for Sonic 1

Discussion in 'Engineering & Reverse Engineering' started by RetroKoH, Sep 4, 2012.

  1. Kilo

    Kilo

    Deathly afraid of the YM2612 Tech Member
    1,039
    1,038
    93
    Canada
    Sonic 1 Source Code Recreation + Source Code Wiki Page
    So a 2005 Hivebrain disassembly with ASMX was posted here a bit ago. And the link is dead because Discord is not a file hosting site. So instead, let's teach you how to put ASMX into a 2005 Hive disasm. Not covering GitHub because, and I tried, but it's a web of errors that would require it's own thread to work out, besides, you shouldn't use the Git disasm anyways, really want to talk to the genius that thought splitting every single subroutine into a separate file was a good idea. :british:

    So what is ASMX? Well it's a multi CPU assembler like AS, except not total garbage that'll scream errors at you that disappear upon rebuilding, and doesn't take 3 minutes to assemble 512kb games from the early 90's. It's nice and simple like ASM68k, but with Z80 support, without having to use hacks like macros. Adding it to a 2005 Hive disasm is very easy, let's get started.


    First, download the version of ASMX provided, this is a build I made that just contains the 68k and Z80 assemblers, since we won't be needing all the other CPUs. Place it in the root of your disassembly, and delete ASM68k.

    Then we need to change build.bat. Replace it's contents with this.
    Code (Text):
    1. @echo off
    2. asmx -C 68k -b -l -e -w sonic1.asm
    3. rompad.exe sonic1.asm.bin 255 0
    4. fixheadr.exe sonic1.asm.bin
    5. pause
    The options I input to the assembler here is setting the default CPU to the 68k, have the code output to a binary (It will output as sonic1.asm.bin, doesn't provide the option to give it a custom name), generate a listing file, and print errors and warnings.

    Now we need to fix errors, the 2005 Hive disasm uses backslashes to declare directories when including files, which ASMX no likey, it only takes forward slashes for paths. There's a lot of instances of them, so rather than running around and changing each instance of it, in an editor like VS Code, select a backslash and do "Change all occurrences" and of course, change it to a forward slash. Once you do this you will have to go back to the top of sonic1.asm and change
    Code (Text):
    1. align macro
    2.     cnop 0,/1
    3.     endm
    Back to using a backslash, since it's a macro parameter and not a path.

    Run build.bat and it should successfully build. You will get warnings about branches that could use .s instead of .w, but it's not really anything to worry about. (And if it bugs you just remove the -w from the assembler arguments, but I wouldn't recommend it!)

    I cannot guarantee that this will work with a lot of the external tools and utilities made over the years. But I'm hoping this will set a precedence in hackers using an actually good assembler and developing utilities around it. Also to note when it runs into errors, it will continue to forcibly build the ROM, so be careful about that too!
     

    Attached Files:

    Last edited: Jun 19, 2024
  2. A couple months back, I tried out every assembler I could find to see if I could figure out if any of them I wanted. It turned out that aside from AS, which has the issues you mentioned, ASM68k and the hacked version AXM68k came out on top. The only things it lacked that I wanted was the ability to reference local labels globally (which AS supports) and multi-line comments (which only AS-GNU supports as far as I know).

    I bring this up because, from what I could tell, ASMX was pretty poor in terms of having features I wanted. It doesn't have string functions, global referencing of local labels, or the ability to name registers (which you can do with reg with AS or equr with ASM68k). The string functions is especially important to me since it's required for Vladikcomper's Error Handler, which I find invaluable for my debugging process. I also imagine that the Github Sonic 1 disassembly isn't going to have nearly as smooth of a transition to ASMX, and I think that's also important to note since it's going to be the preferred disassembly for the vast majority of people.

    There's no shame in using ASMX since I know not everyone's programming process is the same is mine. I just thought this information would be relevant to share for those deciding which assemblers to use. I'd love to start a conversation about emulators outside of AS and ASM68k if possible. Please let me know if I overlooked anything about ASMX because I'd love to be wrong.
     
    • Agree Agree x 2
    • Informative Informative x 2
    • List
  3. Clownacy

    Clownacy

    Tech Member
    1,099
    689
    93
    ASMX is far from the ideal assembler: missing features aside, from 2010 to 2023, the latest official release not only failed to parse basic code like 'ld a,(100*2)-1', but also did not work at all on Windows. Bemoaning AS's stability while promoting an assembler that was so broken that it could not even run properly on the vast majority of PCs for over a decade is ridiculous.
     
    Last edited: Jun 20, 2024
    • Like Like x 2
    • Informative Informative x 2
    • Agree Agree x 1
    • List
  4. Brainulator

    Brainulator

    Regular garden-variety member Member
    Oh, wow, you didn't even mention that here.
     
  5. Kilo

    Kilo

    Deathly afraid of the YM2612 Tech Member
    1,039
    1,038
    93
    Canada
    Sonic 1 Source Code Recreation + Source Code Wiki Page
    Wasn't really sure where else to put this, but it is Sonic 1 so it counts!
    Changing the Border Colour in S1 J2ME (Without Decomp)
    This applies to both part 1 and 2, with any resolution. We have 3 colors to edit:
    upload_2024-7-1_8-33-59.png
    0x81CDF2 for the bright top line.
    0x2273FB for the main background blue.
    0x1C5ACC for the darker line.

    All we have to do is open c.class in a hex editor and look up these colours. In my particular copy of part 1, it's at offsets 0xE52, 0xE3E, and 0xE39. But the offset is likely to vary between resolutions, parts, and carriers. Then just modify to whatever you like. For instance, let's make it look like Sonic Retro.
    upload_2024-7-1_8-42-29.png
    Alright, maybe not the greatest combo but it's an example.
     
    • Like Like x 2
    • Informative Informative x 1
    • List
  6. Filter

    Filter

    Member
    5
    7
    3
    Canada
    Not sure if I have posted this anywhere yet, but I managed to fix a bug within Sonic 1 and 2 (not Sonic 3 & Knuckles, it's fixed there) in which player collision against level walls is 1 pixel too short on the left. Here's a visual on the bug in Marble Zone Act 1:
    [​IMG]

    Notice how Sonic gets pushed back by 1 pixel from the object? Or how his art clips into the foreground? That is what we'll fix here.
    But asking the real questions here, how did I fix this? Completely by accident surprisingly!
    So what I did was that I was looking through the collision routines within Sonic 1, and I found Sonic_HitWall which I presumed manages how the player reacts to hitting walls, and in loc_1504A, I replaced the line:

    Code (Text):
    1.       subi.w    #$A,d3
    with this:
    Code (Text):
    1.       subi.w    #$B,d3
    Originally I was just messing around with the value, but I noticed that it controlled how much Sonic goes into the level collision on the left. And by complete accident this happened to appear to fix the bug entirely. I went into Marble Zone in Sonic 1 (which is a great example on where to tell if this bug has been fixed or not) and when I was at the section with the sliding glass blocks in Act 1, pushing against a wall on the left side, Sonic was not actually clipping into it by 1 pixel, which I was very happy to see! Like I said earlier, this bug persists in Sonic 2 and can be fixed there as well. I think I fixed this bug 2 years ago, just never really got to talking about it publicly until now. Hope this helps with making Sonic 1 just a little bit better!
     
    Last edited: Aug 2, 2024
  7. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,423
    1,740
    93
    your mom
    Yeah, in that function, 0xA acts as the collision "sensor width". It is subtracted from the X position, and that offset position is used to find a wall.
     
  8. Filter

    Filter

    Member
    5
    7
    3
    Canada
    Interesting! I didn't know that, so most likely the culprit of the bug was that the sensor width did not match up with Sonic's width, which is an odd value of 0x9 if I can recall correctly, so the fact that it's an even integer here is rather interesting.
     
  9. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,423
    1,740
    93
    your mom
    I'd like to add a note that this does affect the speed when you launch off an upward ramp, where you won't reach as high. An alternative method is to apply the check to each individual axis:
    Code (Text):
    1.                 move.b  obAngle(a0),d0
    2.                 jsr     (CalcSine).l
    3.                
    4.                 move.w  obInertia(a0),d2
    5.  
    6.                 muls.w  d2,d0
    7.                 asr.l   #8,d0
    8.                
    9.                 cmpi.w  #$1000,d0
    10.                 ble.s   SonicRollSpeed_CheckNegY
    11.                 move.w  #$1000,d0
    12.  
    13. SonicRollSpeed_CheckNegY:
    14.                 cmpi.w  #-$1000,d0
    15.                 bge.s   SonicRollSpeed_SetVelocityY
    16.                 move.w  #-$1000,d0
    17.  
    18. SonicRollSpeed_SetVelocityY:
    19.                 move.w  d0,obVelY(a0)
    20.  
    21.                 muls.w  d2,d1
    22.                 asr.l   #8,d1
    23.  
    24.                 cmpi.w  #$1000,d1
    25.                 ble.s   SonicRollSpeed_CheckNegX
    26.                 move.w  #$1000,d1
    27.  
    28. SonicRollSpeed_CheckNegX:
    29.                 cmpi.w  #-$1000,d1
    30.                 bge.s   SonicRollSpeed_SetVelocityX
    31.                 move.w  #-$1000,d1
    32.  
    33. SonicRollSpeed_SetVelocityX:
    34.                 move.w  d1,obVelX(a0)
     
    Last edited: Jul 29, 2024
  10. RetroKoH

    RetroKoH

    Member
    1,728
    107
    43
    S1Fixed: A successor to ReadySonic
    This DOES fix the minor bug you are alluding to, but I'd be careful with what else this might affect. It's important to note that Sonic_HitWall: is a slight misnomer, as it actually only detects wall collision by measuring distance on the left side (S3K equivalent is CheckLeftWallDist). The right side equivalent (which doesn't seem to have this bug) is sub_14EB4 (S3K equivalent is CheckRightWallDist).

    I bring up the S3K equivalents because it seems that the S3K subroutines do not make this adjustment, and probably fix it in another manner. I was discussing with DeltaW who was having difficulty backporting Knuckles' climbing to Sonic 1, as he couldn't get Knux to latch to left-side walls. Turns out this change affected that.

    I don't want to knock your fix suggestion, as it's a good thing to tackle, but I suggest we dig further into how S3K fixed this anomaly, and explore other options.
     
    • Agree Agree x 2
    • Like Like x 1
    • List
  11. Filter

    Filter

    Member
    5
    7
    3
    Canada
    Yeah, Delta and I already talked it over and I'm gonna try investigating how S3K does it, since I'm currently working with S3K. Like I said before this was completely by accident, guess I didn't account for S3K object ports. :P
     
  12. Kilo

    Kilo

    Deathly afraid of the YM2612 Tech Member
    1,039
    1,038
    93
    Canada
    Sonic 1 Source Code Recreation + Source Code Wiki Page
    Here's a quick port of S3K's after image object for Sonic 1. It's an effect I really like so I figured it'd be helpful to just have it ready for those that want to use it.
    Code (Text):
    1. Obj_AfterImage:
    2.         moveq    #0,d0
    3.         move.b    $24(a0),d0
    4.         move.w    AfterImage_Index(pc,d0.w),d1
    5.         jmp        AfterImage_Index(pc,d1.w)
    6.  
    7. AfterImage_Index:
    8.         dc.w    AfterImage_Init-AfterImage_Index
    9.         dc.w    AfterImage_Main-AfterImage_Index
    10.  
    11. AfterImage_Init:
    12.         addq.b    #2,$24(a0)
    13.         move.l    #Map_Sonic,4(a0)
    14.         move.w    #$F000/32,2(a0)
    15.         move.w    #2,$18(a0)
    16.         move.b    #$18,$19(a0)
    17.         move.b    #4,1(a0)
    18.  
    19. AfterImage_Main:
    20.         tst.b    (SuperFlag).w        ; Replace this line with whatever condition you may need.
    21.         beq.w    DeleteObject
    22.         moveq    #$C,d1                ; Value to subtract from position index to make after image trail properly.
    23.         btst    #0,($FFFFFE04+1).w    ; Is this an odd frame?
    24.         beq.s    @Even                ; If not continue.
    25.         moveq    #$14,d1                ; If it is an odd frame, use an even older entry.
    26.  
    27. @Even:
    28.         move.w    ($FFFFF7A8).w,d0    ; Get previous position array index.
    29.         lea    ($FFFFCB00).w,a1
    30.         sub.b    d1,d0                ; Apply offset in array.
    31.         lea    (a1,d0.w),a1
    32.         move.w    (a1)+,8(a0)            ; Set positions for this frame.
    33.         move.w    (a1)+,$C(a0)
    34.         move.b    ($FFFFD000+$1A).w,$1A(a0)    ; Copy player's sprite.
    35.         move.b    ($FFFFD000+1).w,1(a0)        ; Copy player's render flags.
    36.         move.w    ($FFFFD000+$18).w,$18(a0)    ; Copy player's priority.
    37.         jmp    DisplaySprite
     
    Last edited: Aug 29, 2024
    • Like Like x 2
    • Useful Useful x 1
    • List
  13. Lapper

    Lapper

    Lappering Tech Member
    1,782
    1,014
    93
    England
    Sonic Studio, Sonic Physics Guide, Kyle & Lucy, Freedom Planet 2
    I can't see the original image of the bug so I am just here assuming this is the 1px misalignment when pushing leftwards thing - if so, I actually went over this and the causes behind it recently in another thread.
    I don't think Sonic 3 fixes anything mechanics-wise, Sonic 3 has its push animation aligned differently so he always overlaps everything by a few pixels when pushing.
     
  14. Filter

    Filter

    Member
    5
    7
    3
    Canada
    Yep, the original gif was of the left wall 1 pixel misalignment. I had no idea that you covered this by the way, but glad you hopped in and pointed to that thread of yours! I'm still bug hunting in Sonic 1 so any other obscure nitpick bugs I come across I'll post about here.
     
  15. RetroKoH

    RetroKoH

    Member
    1,728
    107
    43
    S1Fixed: A successor to ReadySonic
    So, many of us have been trying to find ways to add some of the visual flair from Sonic 1's Taxman remake to the original game. I got one for y'all to add to your Sonic 1 hacks, and it's already in Fixed. One of the earliest and most apparent changes is the wrecking ball of the first boss, enough so that the devs removed collision before the start of its attack phase so that we are forced to bask in its visual glory. Well, I decided to add that to my hack, and now you can too!

    So, we are going to open 48 Eggman's Swinging Ball.asm. Now, the ball has 4 possible frames it can display, with the first being the solid orb with the shine (it uses a flicker effect in an attempt to emulate a shine on the ball). This means it displays frames 1, 2 or 3 in order to get the ball to display correctly based on its current angle, or, it would, had the original developers done the legwork to get that running. Instead, we only ever see frame 1, which is just the standard non-angled checkerboard pattern. So, I thought it should be as simple as adjusting the frame based on the ball's angle, but the ball never stores the angle in its own RAM. This is instead stored at the head of the chain. So, in the ball's OST RAM at bytes $30 (and $31), I'm going to store the chain head's RAM address like so:
    Code (Text):
    1. GBall_MakeBall:
    2.         move.b    #8,obRoutine(a1)
    3.         move.l    #Map_GBall,obMap(a1)        ; load different mappings for final link
    4.         move.w    #make_art_tile(ArtTile_GHZ_Giant_Ball,2,0),obGfx(a1) ; use different graphics
    5.         move.b    #1,obFrame(a1)
    6.         move.b    #5,obPriority(a1)
    7.         move.b    #$81,obColType(a1)           ; make object hurt Sonic
    8.         move.w    a0,objoff_30(a1)            ; store address of head chain object to transfer angle to the ball
    9.         rts
    10.  
    Now to use this. Go down the file until you find GBall_ChkVanish. You'll see this code:
    Code (Text):
    1.  
    2. GBall_ChkVanish:; Routine 8
    3.         moveq    #0,d0
    4.         tst.b    obFrame(a0)
    5.         bne.s    GBall_Vanish
    6.         addq.b    #1,d0
    7.  
    8. GBall_Vanish:
    9.         move.b    d0,obFrame(a0)
    10.  
    This basically hardsets the sprite frame to 0 if it was non-zero on the previous frame. If it was already 0, it sets the frame to 1. Nothing about angles is taken into account, and we NEVER see frames 2 or 3. Let's fix that now:
    Code (Text):
    1.  
    2. GBall_ChkVanish:    ; Routine 8
    3.         moveq    #0,d0
    4.         tst.b    obFrame(a0)                ; is ball showing checkered?
    5.         bne.s    GBall_Vanish            ; if yes, branch to alt frame (frame 0)
    6.     ; RetroKoH angled ball mod
    7.         movea.w    objoff_30(a0),a2        ; store chain head address in a2
    8.         move.b    obAngle(a2),d0            ; fetch chain's current angle; store it in d0
    9.         subq.b    #1,d0                    ; subtract 1, because it ranges from 1-$81
    10.         lsr.b    #1,d0                    ; cut range down to 0-$40
    11.         move.b    GBall_Frames(pc,d0.w),d0
    12.     ; angled ball mod end
    13.  
    14. GBall_Vanish:
    15.         move.b    d0,obFrame(a0)
    16.  
    At the very bottom, we will put a look up table for frames corresponding to each angle. Because we divided the number of angles by 2, we can make due with a smaller look-up table. If we divide even more, we can probably make due with even less. Anyway, here is the list.
    Code (Text):
    1.  
    2. GBall_Angles:
    3.         dc.b    1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3        ; 0 - $F
    4.         dc.b    3,3,3,3,3,3,3,3,1,1,1,1,1,1,1,1        ; $10 - $1F
    5.         dc.b    1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2        ; $20 - $2F
    6.         dc.b    2,2,2,2,3,3,3,3,3,3,3,3,1,1,1,1,1    ; $30 - $40
    7.         even
    8.  
    We have an uneven number of values ($41), so let's put an even at the end to make sure no address errors occur. EDIT: I edited the LUT slightly. I think these values make the rotation look slightly better.

    I'm sure this can be improved upon. I'll give it a go later. Enjoy!
     
    Last edited: Sep 14, 2024
  16. RetroKoH

    RetroKoH

    Member
    1,728
    107
    43
    S1Fixed: A successor to ReadySonic
    I've been in the midst of documenting and annotating Obj72 (Scrap Brain 2's Teleporters) both for S1Fixed, as well as for Retro's disassembly, and I've stumbled upon an area that can be optimized, using a method that may be familiar to many of you.

    Anyone remember this guide by RHS? This optimized SpeedToPos and ObjectFall. DeltaW provided a similar one for BossMove. I'm giving you one for the Teleporters. Open incObj/72 Teleporter.asm and find loc_167DA. This code is nearly identical to that of SpeedToPos, only difference being that Sonic's RAM is in (a1) instead of (a0). Here is the code as is in Sonic 1:

    Code (Text):
    1. loc_167DA:
    2.         move.l    obX(a1),d2
    3.         move.l    obY(a1),d3
    4.         move.w    obVelX(a1),d0
    5.         ext.l    d0
    6.         asl.l    #8,d0
    7.         add.l    d0,d2
    8.         move.w    obVelY(a1),d0
    9.         ext.l    d0
    10.         asl.l    #8,d0
    11.         add.l    d0,d3
    12.         move.l    d2,obX(a1)
    13.         move.l    d3,obY(a1)
    14.         rts
    We're gonna optimize it like so, identical to how the linked guide did it before:
    Code (Text):
    1. loc_167DA:
    2.         move.w    obVelX(a1),d0    ; load horizontal speed
    3.         ext.l    d0
    4.         asl.l    #8,d0            ; multiply by $100
    5.         add.l    d0,obX(a1)        ; add to x-axis    position
    6.         move.w    obVelY(a1),d0    ; load vertical speed
    7.         ext.l    d0
    8.         asl.l    #8,d0            ; multiply by $100
    9.         add.l    d0,obY(a1)        ; add to y-axis    position
    10.         rts
    11.  
    There ya go. I'll be in touch with the Retro team to provide annotations for this object code in due course.
     
    Last edited: Sep 15, 2024
  17. DeltaW

    DeltaW

    Originally a Wooloo Member
    Your method uses 144 cycles (26 bytes) compared to the original method which uses 176 cycles (38 bytes)

    However, we can go the extra mile here and optimise what you have to this:

    Code (Text):
    1. loc_167DA:
    2.         movem.w    obVelX(a1),d0/d2
    3.         lsl.l    #8,d0
    4.         add.l    d0,obX(a1)
    5.         lsl.l    #8,d2
    6.         add.l    d2,obY(a1)
    7.         rts
    We can use movem.w to simultaneously load and sign-extend obVelX and obVelY into d0 and d2 as long words in a single instruction. This eliminates the need for ext.l. Since sign-extension is already handled, we can safely use lsl (logical shift left) instead of asl, as the sign-extension is no longer required.

    All of that saves you 8 cycles compared to your optimization, meaning the version I posted uses 136 cycles (18 bytes)
     
    Last edited: Sep 15, 2024
    • Informative Informative x 4
    • Like Like x 3
    • List
  18. Hivebrain

    Hivebrain

    Administrator
    3,068
    204
    43
    53.4N, 1.5W
    Github
    It would've never occurred to me to use movem like that. SpeedToPos is used by a lot of objects, so this is going to mean big CPU savings! I bet there are plenty of other situations where I can use movem now.

    Another optimisation would be to have additional separate SpeedToPos routines for objects that only move vertically or horizontally, so you're not unnecessarily adding 0 to its position.
     
    • Agree Agree x 4
    • Like Like x 2
    • List
  19. Brainulator

    Brainulator

    Regular garden-variety member Member
    Original source for this modification, for reference.
     
  20. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,423
    1,740
    93
    your mom
    There's also an earlier post (on a completely different website) from a couple weeks prior, as well.