don't click here

Some changes/fixes for Sonic 1

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

  1. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    EDIT: I have added to this, below. We are now covering many previously unseen changes/fixes, and edits to currently known fixes/changes.
    I've created a mod to prevent score padding via dying... you know, rack up a nice score, and kill yourself to continue from before and snag more points.
    I'm sure it's not something everyone does... and probably sounds like a silly thing to consider, but I have, and have prevented such a thing from happening.

    Basically I added a variable that stores the score at the start of the game and after a level/Special Stage and when you die, at a check point or not, it will reset to this value. Newer Sonic games do this (except they only store individual level values so it works slightly differently, but same principle that I'm working under). It works to punish players for failing.

    My question is would this be a mod anyone would like to see? If so I will share it.

    ALSO I'm looking into modding Monitor behavior to prevent dying and padding the lives counter much the same way (die and go back to get the life monitor(s)) by changing their subtype to a ring monitor.

    Would anything like this be appealing or interesting at all to anyone?
     
  2. Ravenfreak

    Ravenfreak

    2 Edgy 4 U Tech Member
    3,087
    181
    43
    O'Fallon Mo
    Sonic 1 Game Gear Disassembly
    I personally think the second one it's a cool concept, but not really the concept you've already coded in. :\ I'm just not too keen on punishing the player for dying, but then again it's the classic games and I'm sure most of us on here don't die that often while playing the games if not at all. Also wouldn't this be better in the E/RE forum? Since after all, you're hacking a rom. xP
     
  3. Endri

    Endri

    Officer I don't have my drivers license with me. C Tech Member
    Precisely.

    Actually, I apply both of these concepts in my fangame. In fact, there's only one 1up Monitor in each act, and the object is pretty much hidden. I would also recommend to, instead of changing the subtype of the Monitor to a Super Rings Monitor after restarting the level (if said monitor was already broken by the player), simply set the object status to "broken" upon initialization.

    Another thing I do is to prevent, using your terms, "Time Padding." For example, if the player hit the furthest Star Post in the level, and, intentionally, perform a Time Over to make the Time reset to 0:00, so them can finish the level with the most absurd low times possible. Quite simple to solve, also: during the score tally, check if the Time Over flag is set, and, if it is, set the Time Bonus to 0.
     
  4. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    I didn't think of that at all... and now plan on implementing this. Awesome stuff.
     
  5. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    I guess that is a difference of preference :)

    And I didn't post in ERE with this because I haven't really posted anything of merit... YET. Only a concept. I'll put some code up when all is finished then maybe it can be moved later.
     
  6. Endri

    Endri

    Officer I don't have my drivers license with me. C Tech Member
    In the end of the day, these are not really fixes; they are more design decisions. Because: 1)a unexperienced player wouldn't risk attemping any of these "exploits" in the first place; 2)only a experienced player would figure out that these exploits are possible, and would be able to successfully put them into practice; 3)it all boils down to if you want to remove the exploits and punish the unexperienced players, or, if you want to just leave the exploits there and reward the experienced players.

    That being said, that is why I recommeded you to not transform the already-used 1up Monitors into Super Ring Monitors, because you would eventually end up making room for more exploits (say that you designed a level that has just 190 rings exactly... if the player grabs the 1up Monitor and die purposefully, now he has the chance to get 200 rings instead, and earn an extra life for these 200 rings).

    Oh, which just reminded me, you probably don't want the 100 and 200 rings extra lives flags to be reseted when the player dies, otherwise, the player would still be able to "pad" the number of life: grab 200 rings, earn 2 extra lives, die, grab 200 rings, earn 2 extra lives, die, rinse and repeat. You could clear the rings lives flags only in the part of the level initiation code that is run only when the level is not started from a Star Post. This would solve the problem.
     
  7. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    Damn... First off, you're right. the term fix was wrong in this case.
    Second, another idea I didn't think of with the 100/200 rings thing. I'm loving it.
    Third... I'll do you one better. Broken monitors are a tad inconsistent given that nothing is broken when returning... how about instead of those, OR rings, they are empty static monitors.

    :)
     
  8. Aesculapius Piranha

    Aesculapius Piranha

    つづく Oldbie
    4,534
    143
    43
    Unknown
    Diva
    Really I think the monitor thing isn't an issue for the experienced, because what player is going to rack up lives like that if they are experienced enough not to need them? That is a change to prevent n00bs from building cushion.

    The first isn't an issue for the experienced either per say. It's more an issue for the niche that goes for score attacks versus everyone else.
     
  9. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    I'm just looking for something to do. In the right type of hack, this would be a good thing to implement. In a standard Sonic 1, it's not that big of a deal. But I'm looking at practicing and learning more about how to hack, so I'm trying my hand at implementing different things just for the hell of it, and for experience. And since I didn't recall seeing these implemented, I thought... why not share my thoughts.


    SPEAKING OF WHICH... I have bumped into a coding problem, and perhaps we have started to reach the point where this COULD be moved to ERE, but I'm posting the question here as it is in direct relation to the current topic.

    I have attempted to edit the Level Select selection code, to make it reset these certain variables when selecting from level select...
    If one plays through a game, and either finishes or get Game Over, and then comes back via Level Select, Not everything is cleared correctly. I have added the move codes necessary... take a look

    Code (Text):
    1.  
    2.  
    3. LevSel_Level_SS:            ; XREF: LevelSelect
    4.         add.w   d0,d0
    5.         move.w  LevSel_Ptrs(pc,d0.w),d0 ; load level number ; ERROR VALUE <--------------------------------
    6.         bmi.w   LevelSelect
    7.         cmpi.w  #id_SS*$100,d0  ; check if level is 0700 (Special Stage)
    8.         bne.s   LevSel_Level    ; if not, branch
    9.         move.b  #id_Special,(v_gamemode).w ; set screen mode to $10 (Special Stage)
    10.         clr.w   (v_zone).w  ; clear level
    11.         move.b  #3,(v_lives).w  ; set lives to 3
    12.         moveq   #0,d0
    13.         move.w  d0,(v_rings).w  ; clear rings
    14.         move.l  d0,(v_time).w   ; clear time
    15.         move.l  d0,(v_score).w  ; clear score
    16.         ; KingofHarts Level Select Mod
    17.         ;move.l  d0,(v_startscore).w ; clear start score
    18.         ;move.b d0,(v_lastspecial).w ; clear special stage number
    19.         ;move.b d0,(v_emeralds).w ; clear emeralds
    20.         ;move.l d0,(v_emldlist).w ; clear emeralds
    21.         ;move.l d0,(v_emldlist+4).w ; clear emeralds
    22.         ;move.b d0,(v_continues).w ; clear continues
    23.         ; end of mod
    24.         move.l  #5000,(v_scorelife).w ; extra life is awarded at 50000 points
    25.         rts
    26.  
    and for the other part, selecting a normal level, it has more of the clears...
    Code (Text):
    1.  
    2.  
    3. LevSel_Level:               ; XREF: LevSel_Level_SS
    4.         andi.w  #$3FFF,d0
    5.         move.w  d0,(v_zone).w   ; set level number
    6.  
    7. PlayLevel:
    8.         move.b  #id_Level,(v_gamemode).w ; set screen mode to $0C (level)
    9.         move.b  #3,(v_lives).w  ; set lives to 3
    10.         moveq   #0,d0
    11.         move.w  d0,(v_rings).w  ; clear rings
    12.         move.l  d0,(v_time).w   ; clear time
    13.         move.l  d0,(v_score).w  ; clear score
    14.         ; KingofHarts Level Select Mod
    15.         ;move.l  d0,(v_startscore).w ; clear start score
    16.         move.b  d0,(v_lastspecial).w ; clear special stage number
    17.         move.b  d0,(v_emeralds).w ; clear emeralds
    18.         move.l  d0,(v_emldlist).w ; clear emeralds
    19.         move.l  d0,(v_emldlist+4).w ; clear emeralds
    20.         move.b  d0,(v_continues).w ; clear continues
    21.         move.l  #5000,(v_scorelife).w ; extra life is awarded at 50000 points
    22.         move.b  #$E0,d0
    23.         bsr.w   PlaySound_Special ; fade out music
    24.         rts
    25.  

    I have commented out my Level Select mods for a reason. Implementing them in the code causes a build error, stating "Illegal Value"
    Now with all of these implemented, that value comes out to 152. Using less lines of mine, results in a smaller value, the smallest being 128.

    The line in question is the one I marked ERROR VALUE.

    Any reason why this could be?
    EDIT: Disregard this, I simply moved the level select table closer to the instruction calling it. I think it did the trick.
     
  10. Endri

    Endri

    Officer I don't have my drivers license with me. C Tech Member
    Ah, okay then, because the code itself you posted is fine. If you use too great jump distances, misname the calling of a routine label or put data tables too far from where you call them, while building you will pretty much receive errors coming out of everywhere inside the code.

    The only thing I'm missing in your code is the:
    Code (Text):
    1.         move.w  (Level_select_zone).w,d0
    right before:
    Code (Text):
    1.         add.w   d0,d0
    on the LevSel_Level_SS routine (the address costant is called Level_select_zone in the Mercurial disassembly; I don't remember the name of the equivalent constant in your disassembly).

    By the way, I highly recommend the use of the Mercurial disasm (the subroutines and macros are way more commented and explained, and everything is far more organized). So organized, in fact, that you can move things around and still make a perfectly working build. It probably should give less readaches.
     
  11. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    I do have the Mercurial. Unless I had inadvertently changed a name somewhere, I think mine should be the same, if not... at least similar.
    BTW I don't have that code in either my code or the Mercurial disasm. And... while I've messed with my disassembly, I know for a fact I have changed ZERO with the Mercurial.





    Also I wanted to ask about this. Is there another Time Over flag... or something you are referring to? f_timeover returns to 0 after the zone restarts from a Time Over. I've thought about stopping it from doing that, to implement this change, but woldn't that mess stuff up?
     
  12. Endri

    Endri

    Officer I don't have my drivers license with me. C Tech Member
    Oh, I just noticed that you were actually working with the Sonic 1 disasm. For some reason, I assumed you were working with Sonic 2! That's why the routines and constants have different names. :specialed:

    If you do that, what will happen is that, everytime you die, the Time Over object is gonna be called! :v:

    You can do the following: when the level restarts from a Time Over, instead of setting the flag to 0, set it to $FF. Now, any function that tst.b (f_timeover).w branches with either a beq.s or bne.s. Change these bne.s to bpl.s (so the functions will only branch if the Time Over flag is 1 or higher). In your code to clear the time bonus if the character had a Time Over, you'll branch with a bmi.s (branch if the Time Over flag is -1 or lower, in this case, $FF).

    That's about it.
     
  13. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    That's quite alright Endri. I am also working with Sonic 2 (and 3, actually) as well, just not at this particular moment, though I plan on making the SAME additions to Sonic 2 REV C in the future, so I will bear this in mind... and I will try the thing with the Time Over.
    Also that means I will need to add an extra instruction(s) to reset f_timeover from FF to 0 as well in other areas, correct?
     
  14. Endri

    Endri

    Officer I don't have my drivers license with me. C Tech Member
    Not necessary, because any object which tries to reset f_timeover will do so with either a move.b #0,(f_timeover).w, clr.b (f_timeover).w, or moveq #0,d0 > move.b d0,(f_timeover).w. In other words, they all set the time over flag to 0 no matter what was the previous value of f_timeover. What you have to do, however, is to make sure to only set f_timeover to 0 if the value is positive when restarting from a checkpoint, otherwise it will wipe out your $FF.
    For example:
    [68k] Level_SkipClr:
    tst.b (f_timeover).w ; <<<<<<<<<<<<<<<<<<<<<<<<< test the time over flag
    bmi.s @skiptimeclear ; <<<<<<<<<<<<<<<<<<<<<<<<< if negative, branch
    move.b d0,(f_timeover).w
    @skiptimeclear:
    move.b d0,(v_shield).w ; clear shield
    move.b d0,(v_invinc).w ; clear invincibility
    move.b d0,(v_shoes).w ; clear speed shoes
    move.b d0,($FFFFFE2F).w
    ...[/68k]

    and, in Sonic (part 2).asm:
    [68k]GameOver:
    ...
    loc_138D4:
    move.w #60,$3A(a0) ; set time delay to 1 second
    tst.b (f_timeover).w ; is TIME OVER tag set?
    beq.s locret_13900 ; if not, branch
    bmi.s locret_13900 ; <<<<<<<<<<<<<<<<<<<<<<<<<< if negative, also branch
    move.b #$FF,(f_timeover).w ; <<<<<<<<<<<<<<<<<<<<<< set the flag to -1
    move.w #0,$3A(a0)
    move.b #$39,(v_objspace+$80).w ; load TIME object
    move.b #$39,(v_objspace+$C0).w ; load OVER object
    move.b #2,(v_objspace+$80+obFrame).w
    move.b #3,(v_objspace+$C0+obFrame).w
    bra.s loc_138C2
    ...[/68k]

    and, in Object 0D Signpost:
    [68k]GotThroughAct:
    ...
    @hastimebonus:
    add.w d0,d0
    move.w TimeBonuses(pc,d0.w),(v_timebonus).w ; set time bonus
    tst.b (f_timeover).w ; <<<<<<<<<<<<<<<<<<<<<<<< is the TIME OVER flag set?
    beq.s @skiptimebonusclear ; <<<<<<<<<<<<<<<<<<<<<<<< if not, branch
    clr.w (v_timebonus).w ; <<<<<<<<<<<<<<< set Time Bonus to 0
    clr.b (f_timeover).w ; <<<<<<<<<<<<<<< reset TIME OVER flag back to 0
    @skiptimebonusclear:
    move.w (v_rings).w,d0 ; load number of rings
    mulu.w #10,d0 ; multiply by 10
    ...[/68k]

    This is the general idea. Though, since you are working with Sonic 1, you could create a new variable for doing that, since there are more free RAM areas in Sonic 1 than there are for the other games. I suggested the approach of using the same variable for this purpose because I had Sonic 2 in mind at the time.

    By the way, I just remembered that Rev 00 of Sonic 1 doesn't reset the time when you die from a Time Over anyways.
     
  15. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    Couple notes:
    1. You actually gave me an easier process than the one I had put together... which had worked somewhat... but was a mess... So I'm using yours. Much thanks!
    2. I'm glad to not have to use another RAM address after all, as I want to save as many as I can for something else...
    3. I took out all of the Revision 00 stuff anyways.

    Oh, and on a side note...


    That is actually what the Sonic 1 ROM already does. I am opting to instead remove this instruction from level init, and instead clear the ring lives flag at signpost, much like the time over flag instead.

    :) Much thanks Endri!
     
  16. Endri

    Endri

    Officer I don't have my drivers license with me. C Tech Member
    No problem!

    Oh, and just let me conclude my reasoning on the design choice, which I almost forgot:
    Precisely, and that's the point: it becomes an issue if you are designing for example, a hack/game that saves high scores, has leaderboards or scoreboards of any sort. In my case, my game does all the three.

    I'll give an {silly} example:

    When we were beta testing Doc Egg Fan's Sonic 2 LD, he held a small contest for us, where we would send him our save states from the game, and, he would calculate the scores and give us positions on the "Leaderboards." So, the score at the end of the level, the time you took to beat the level, the amount of rings you had, the amount of lives, if you found or not the emerald in the level, etc, accounted for the final score.

    I would destroy every single enemy in the level to inflate my score and grab every ring, to rack up as many lives as possible, and then I would purposefully die. I repeated the process until I was tired of it, then, I would grab the emerald and finish the level as fast as I could with 99 rings.

    I'm pretty sure I was the #1 on the Leaderboards a most of the times.

    Another {not so silly} example: people do this process (especially the Time Over one) all the time on Taxman's Sonic CD Leaderboards. :v:
     
  17. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    My #1 reason for wanting to implement such things. I too would like some kind of record saving implemented into Sonic 1...
    But if you wanted to get to the top of those leaderboards so easily, you could just as easily follow my save game editing guide. [​IMG] Cheap plug FTW!

     
  18. Endri

    Endri

    Officer I don't have my drivers license with me. C Tech Member
    The last level in my game is huge ass long, and people will time over frequently, so I had to implement something to keep people from cheating the other people who actually play right.

    As I've said earlier, I had Sonic 2 in mind at the time, and Sonic 2 doesn't do it this way.

    I'm glad to hear that I was able to shed you some light to the right direction. :)
     
  19. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    So, I have come up with a rather SMALL fix, that is really only notable for nitpickers. Sonic 1's title screen is off-center, and misaligned. I have resolved this, because quite frankly it

    FIRST, we will start with the easier part: moving the two title screen objects.
    First, go to 0E Title Screen Sonic.asm and go to the second line of TSon_Main: This is Sonic's x position. It should be $F0. Change this to $F8.
    Next, open up 0F Press Start and TM.asm and go to PSB_Main: There are 2 lines we will change here. One of them is the PRESS START BUTTON's x position, the other is the TM's x position. Change the first obX from $D0 to $D8. Change the second obX from $170 to $178. Notice that we are moving everything to the right by 8.

    Now, for the slightly harder part (for me it was because I'm still learning... but even then it took me only a minute to figure out what I needed to do, so it'll be even easier for you! NO EXCUSES) Now, the rest of the title screen foreground is NOT an object. So... how do we move this over? Easy... we just need to shift the foreground tiles.

    Go to sonic.asm and find this tidbit of code, located under Tit_LoadText:
    Code (Text):
    1.  
    2. copyTilemap $FF0000,$C206,$21,$15
    3.  

    See the $C206? Change it to C208! I don't know exactly how it works, but I know that it works, shifting the emblem over 16 pixels... and that's all that matters since everything is centered and looks better.

    Like, dislike, suggestions?
     
  20. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,200
    431
    63
    Japan
    Well since everyone else is in the game, I may as well join in, I have a fix for a bug that you cannot cause very easily in the original Sonic 1 game, but if one were to do something like a special cut-scene involving the screen moving to the right somewhere away from Sonic, and then letting the engine move the screen back to Sonic, then they would probably see this bug in action.

    The engine that allows the screen to follow Sonic has the responsibility of ensuring that the screen does not move more than 10 (hex) pixels any direction at one time, this is to allow the draw code to draw the lines of 16x16 blocks correctly without skipping any lines, this means that if Sonic is away from the screen's "central box" position by less than 10 (hex) pixels, then the screen's "central box" will move directly to Sonic, if however, Sonic is further than that, the screen will move at a maximum of 10 (hex) pixels in the direction it is suppose to be moving to. The routine "ScrollVertical:" handles this for moving the screen up and down, "ScrollHoriz:" on the other hand (or should I say "ScrollHoriz2:") handles this for the screen moving right, but not when moving left.

    Observe the code for right movement:

    Code (ASM):
    1. loc_65CC:
    2.         cmpi.w  #$10,d0                 ; <- Restriction
    3.         bcs.s   loc_65D6                ; <- Restriction
    4.         move.w  #$10,d0                 ; <- Restriction
    5.  
    6. loc_65D6:
    7.         add.w   ($FFFFF700).w,d0
    8.         cmp.w   ($FFFFF72A).w,d0
    9.         blt.s   loc_65E4
    10.         move.w  ($FFFFF72A).w,d0
    As you can see by the code marked "Restriction", this is to prevent the screen from moving right by 10 pixels, thereas if we look at the code for left movement:

    Code (ASM):
    1. loc_65F6:
    2.         add.w   ($FFFFF700).w,d0
    3.         cmp.w   ($FFFFF728).w,d0
    4.         bgt.s   loc_65E4
    5.         move.w  ($FFFFF728).w,d0
    6.         bra.s   loc_65E4
    The "Restriction" does not exist, the fix is moderately simple:

    Code (ASM):
    1. loc_65F6:
    2.         cmpi.w  #$FFF0,d0               ; has the screen moved more than 10 pixels left?
    3.         bcc.s   Left_NoMax              ; if not, branch
    4.         move.w  #$FFF0,d0               ; set the maximum move distance to 10 pixels left
    5.  
    6. Left_NoMax:
    7.         add.w   ($FFFFF700).w,d0
    8.         cmp.w   ($FFFFF728).w,d0
    9.         bgt.s   loc_65E4
    10.         move.w  ($FFFFF728).w,d0
    11.         bra.s   loc_65E4
    There is another bug related to this which funnily enough is also related to the good old "screen wrapping vertically" bug in Sonic 2 and up, but this is to do with horizontal rather than vertical, at routing "ScrollHoriz2:" we have:

    Code (ASM):
    1. ScrollHoriz2:               ; XREF: ScrollHoriz
    2.         move.w  ($FFFFD008).w,d0
    3.         sub.w   ($FFFFF700).w,d0
    4.         subi.w  #$90,d0
    5.         bcs.s   loc_65F6
    6.         subi.w  #$10,d0
    7.         bcc.s   loc_65CC
    8.         clr.w   ($FFFFF73A).w
    9.         rts
    bcc and bcs are unsigned branches, the problem here is that if Sonic is behind the screen (I.e. to the left), the result will be negative (I.e. a value from 8000 to FFFF), but with the unsigned branches it will obviously treat 8000 to FFFF as positive and higher than 7FFF, thus it believes it has to move right rather than left, the fix is also simple, change the unsigned branch conditions with signed branch conditions:

    Code (ASM):
    1. ScrollHoriz2:               ; XREF: ScrollHoriz
    2.         move.w  ($FFFFD008).w,d0
    3.         sub.w   ($FFFFF700).w,d0
    4.         subi.w  #$90,d0
    5.         bmi.s   loc_65F6                ; cs to mi (for negative)
    6.         subi.w  #$10,d0
    7.         bpl.s   loc_65CC                ; cc to pl (for negative)
    8.         clr.w   ($FFFFF73A).w
    9.         rts
    The cc to pl should not matter, but it's best to be safe.