Sonic 2 REV02

Discussion in 'Discussion & Q&A' started by Clownacy, Nov 11, 2018.

  1. Clownacy

    Clownacy UP - ON - CPU Staff

    Joined:
    Aug 15, 2014
    Messages:
    842
    It seems information about REV02 is pretty muddled these days. When I started looking into it, it seemed the consensus was that REV02 was some later version that could be found in Sonic Classics, which added just as many bugs as it fixed.

    If you want an idea how backwards things are: a few months ago I figured out REV02 was used by the S2 Mega Play port... This was known back in 2005. I've been doing this REV02 stuff for for years.

    So I guess I should start from the beginning - keep all this stuff in one place:

    Background

    There's REV00 and REV01. REV01 is really common. Unlike S1 REV00, I don't think I've ever seen S2 REV00 in any compilations or rereleases. I guess REV00 is just that much of a glorified beta ('beta' in the traditional sense, not 'early prototype' like it seems to mean around here).

    Anyway, then you have REV02. This build doesn't seem to have ever been released on its own. It's always been used as the basis for something else. Each derivative has its own quirks and changes, making it hard to tell what edits actually belong to REV02.

    So how do we know REV02 even exists, and that Sonic Classics and co. don't just share some bugfixes? Well, for the longest time, it's been S&K that gave us the biggest clue: its lock-on works by detecting the attached game's serial code, and matching it against a small database of known S1/S2/S3 versions:

    Code:
    aGm000010090:   dc.b "GM 00001009-0"
    aGm000040490:   dc.b "GM 00004049-0"
    
    LockonSerialsText:
           dc.b "GM 00001051-00"   ; Sonic 2 REV00/1/2
           dc.b "GM 00001051-01"
           dc.b "GM 00001051-02"
           dc.b "GM MK-1079 -00"   ; Sonic 3
    As you can see, a third S2 revision is listed.

    So let's go down the list of REV02 instances I'm aware of, in the order I found out about them:

    Derivatives

    The go-to example of REV02 that everyone seems to bring up is Sonic Classics/Compilation, a cartridge containing Sonic 1 (REV00 for some reason), Sonic 2, and Mean Bean Machine. According to the wiki, this first came out in 1995. Sonic 1 is completely vanilla besides a RAM rearrangement, the removal of its checksum routine, and the introduction of a bug. Mean Bean, I have no clue.

    Sonic 2, on the other hand, is substantially different: the Rexon crash was fixed, the Super-Sonic-at-the-end-of-the-level hang was fixed, Sonic/Tails can't Spin Dash off the Tornado, etc., but now Silver Sonic and Grabber have sprite mirroring issues, and certain conveyor belts in WFZ would cause you to walk on air. Despite knowing REV02 had a unique serial code, that couldn't help us identify if this was REV02, since Sonic 2's header is completely absent. Ironically, S1 and Mean Bean got to keep their boring headers though.

    Then there's Knuckles in Sonic 2. This one, I don't think is as well known for being a REV02 derivative. I wound up just figuring it out myself. What's notable about this is that, while Sonic Classics' improvements are present, the bugs are missing. But considering how heavily-modified this game was from the original, it wouldn't be a stretch to just assume Sonic Team fixed them, like they did with the EHZ scrolling bug. Once again, just like Classics, there's no way to confirm the serial code, since KiS2 has no serial code. This game came out in 1994, roughly a year before Classics.

    Then we have Sonic 3. There's not much to say about this one, since it's so extensively modified from its predecessor that I could only identify it by its linker data (more on that later). 1994.

    Sonic Jam. One day I just had a crazy thought: could Sonic Jam's S2 port be based on REV02? I started digging and yep, yes it is. This is where a trend begins to appear: yet again, while the bugfixes seen in Sonic Classics and KiS2 are present, the former's bugs are not. Once again, there's no header to speak of. Released in 1997, two years after Classics.

    Finally, arriving 13 years late to the party, I learned about the Mega Play arcade port. I was scouring SonAR and elsewhere, looking at the headers of various odd builds. At one point I even looked at that one prototype of KiS2 that came with a ROM of S2 already attached to it (REV01, sadly). I looked at Sonic Mega Collection, nope, I looked at the Mega Tech port, nope, I looked at the Mega Play version...

    Code:
    SEGA GENESIS  
    (C)SEGA 1992.SEP
    SONIC THE      
          HEDGEHOG 2
                  
    SONIC THE      
          HEDGEHOG 2
                  
    GM 00001051-02Œ>
    J              
    ......ÿÿ.ÿ...ÿÿÿ
                  
                  
                  
                  
    
    
    
    
    JUE             
    There it was. A completely untouched header. The checksum doesn't match the ROM, so even that might match the original build.

    So I'd finally found REV02. And guess what? It had all of Classics' fixes, and none of its bugs. And the kicker? The Mega Play version came out in 1993. A year before S3 and KiS2, and two years before Classics.

    Bugs

    You might be wondering why I keep bringing up these bugs. Well, there's a very persistent idea going around that those bugs are part of REV02, and that KiS2 and Sonic Jam just fixed them. TCRF, and even the Retro wiki, state this as fact. I don't think it's true in the slightest. When you have four games, all based on the same thing, all made at different times, and only one of them has bugs, it starts to look like a bit of a stretch when people break out the 'nah, those three other games just fixed them' card.

    The funny thing about S2 Mega Play is that, unlike KiS2, its devs didn't go out of the way to fix any bugs. In fact, it actually introduced one: Obj67 - the MTZ teleporter object - is invisible (most of the teleporter itself is part of the level, so the object just controls the flashing).

    Oh, but wait, actually every build but Classics is bugged! Only in Classics is the bug where Sonic/Tails can Spin Dash off the Tornado fixed! All the others broke it again!

    Yeah... no, that wasn't a bugfix. Whether or not being able to fall of the Tornado is a bug, I don't know. I've never looked at the relevant code. But Classics' "fix" is complete nonsense:

    What's a usual Sonic 2 bug to you? A missing branch? Using the wrong operand size? Try broken assembler bitmask constants:

    REV01:
    Code:
       move.b   objoff_2E(a0),d0
       move.b   status(a0),d1
       andi.b   #p1_standing,d0   ; 'on object' bit
       andi.b   #p1_standing,d1   ; 'on object' bit
       eor.b   d0,d1
       move.b   d1,objoff_2E(a0)
    Sonic Classics:
    Code:
       move.b   objoff_2E(a0),d0
       move.b   status(a0),d1
       ; [Classics/Compilation] "fixes" the player being able to spin dash off the Tornado
       andi.b   #1,d0   ; 'X-flipped' bit???
       andi.b   #1,d1   ; 'X-flipped' bit???
       eor.b   d0,d1
       move.b   d1,objoff_2E(a0)
    Kind of a weird bug to introduce in a harmless revision. REV01 introduced a bug by removing a check that was seemingly used for debugging, but this is something else entirely.

    And that applies to all of "REV02"'s bugs: all busted bitmasks.

    Silver Sonic/Grabber mirroring:

    REV01:
    Code:
    InheritParentXYFlip:
       move.b   render_flags(a0),d0
       andi.b   #$FC,d0
       move.b   status(a0),d2
       andi.b   #$FC,d2
       move.b   render_flags(a1),d1
       andi.b   #3,d1
       or.b   d1,d0
       or.b   d1,d2
       move.b   d0,render_flags(a0)
       move.b   d2,status(a0)
    Classics:
    Code:
    InheritParentXYFlip:
       move.b   render_flags(a0),d0
       ; [Classics/Compilation] Peculiarly, Classics changes this to only inherit the Y-flip.
       ; This causes a bug where some sprites are displayed backwards (Silver Sonic's sparks and Grabber's legs).
       ; Presumably this (and the other Classics-specific bugs) was caused by bugged constants.
       andi.b   #$FD,d0
       move.b   status(a0),d2
       andi.b   #$FD,d2
       move.b   render_flags(a1),d1
       andi.b   #2,d1
       or.b   d1,d0
       or.b   d1,d2
       move.b   d0,render_flags(a0)
       move.b   d2,status(a0)
    WFZ walking-on-air:

    REV01:
    Code:
       move.b   status(a0),d0
       andi.b   #standing_mask,d0
       beq.s   return_3B7F6
       bclr   #p1_standing_bit,status(a0)
       beq.s   loc_3B7DE
       lea   (MainCharacter).w,a1 ; a1=character
       bclr   #3,status(a1)
       bset   #1,status(a1)
    
    loc_3B7DE:
       bclr   #p2_standing_bit,status(a0)
       beq.s   return_3B7F6
       lea   (Sidekick).w,a1 ; a1=character
       bclr   #4,status(a1)
       bset   #1,status(a1)
    Classics:
    Code:
       move.b   status(a0),d0
       ; [Classics/Compilation] This causes Sonic to not fall off ObjBD's
       ; ascending platforms when they retract, making him hover.
       andi.b   #2,d0   ; 'Y-flipped' bit???
       beq.s   return_3B7F6
       bclr   #p1_standing_bit,status(a0)
       beq.s   loc_3B7DE
       lea   (MainCharacter).w,a1 ; a1=character
       bclr   #3,status(a1)
       bset   #1,status(a1)
    
    loc_3B7DE:
       bclr   #p2_standing_bit,status(a0)
       beq.s   return_3B7F6
       lea   (Sidekick).w,a1 ; a1=character
       bclr   #4,status(a1)
       bset   #1,status(a1)
    Differences

    But I suppose that's enough ranting... So, what do we know is part of REV02? Well, TCRF does a good job listing its fixes:

    • The background going crazy in WFZ if you Time Over during the cutscene at the end is fixed
    • The game softlocking if you go Super after hitting the signpost is fixed
    • The game crashing if you destroy a Rexon at the right time is fixed

    On top of that, there are some interesting under-the-hood changes:

    Firstly, some additions and subtractions were changed from their normal add/sub instructions to the faster addq/subq versions. This may have been automatically done by the assembler, but considering there are a couple of instructions that weren't changed, it's possible they were just done by-hand.

    Similarly, a couple of lea instructions were changed, but these ones don't make much sense: for some reason, they were changed from their short-range address mode to the larger long-range one. And no, it's got nothing to do with them suddenly being out of range.

    Also, the leftover Wood Zone background scrolling code was removed.

    The animated art tables for HPZ and OOZ were changed slightly. What's strange is that they only edited the palette lines of blank tiles, to make them use line 3/4 instead of 1. This is actually consistent with how MTZ (the only other zone to use blank tiles) has always done it.

    And then we get to the last change... this is the big one. If you've ever seen the Nick Arcade prototype's source code snippet, you'd know the original source code relied on importing and exporting labels to/from other files. The process of resolving all these labels, and tying the files together to produce the final ROM, is called 'linking', and the program that does it is called the 'linker'. This is pretty standard stuff in higher-level languages like C, but the concept can apply to assembly too. Our disassemblies just don't do it.

    I bring this up because REV02's final change affects how linking works, and it's a pain in the arse:

    An instruction that calls an imported function will sometimes branch to a proxy function, that in turn jumps to the actual function. We call these proxy functions 'JmpTos'. These are automatically inserted by the assembler. Theoretically, at least. Between REV00 and REV01, only one JmpTo changed, and that's just because the instruction that used it was removed. Between REV01 and REV02 though? Hundreds, if not thousands of the bloody things were changed.

    I tell you, making the REV02 disassembly was absolute torture because of that. Picking apart each change in a hex editor, and tediously replicating them in the Git disassembly... it took weeks to finish that thing. But more on that later.

    So why were the JmpTos so different? Well for some reason, REV02 inlined most of them. In 68k assembly lingo, a branch is a short, fast way of travelling from one bit of code to another. A jump is a long, slow way. In the explanation above, I mentioned the code branching to a JmpTo, which then jumps to the intended destination. REV02 skips the middleman by just jumping to the function from the very start. It's faster, even if it makes the code a little larger by adding multiple jumps to the same thing, instead of multiple branches to a single JmpTo.

    My guess is the devs either updated their assembler, or just changed its settings. That would explain why REV02 optimises additions/subtractions, unoptimises lea instructions, and suddenly handles linking differently.

    Replication

    Using all the info above, I added an option to the Git disassembly a few years back to produce a hypothetical REV02 ROM. Sadly, it's precisely because of the JmpTos that I don't think we'll ever make a truly bit-perfect 'pure REV02' replica: REV00 and REV01 only have one JmpTo difference, REV01 and Sonic Classics have a massive number of JmpTo differences ...and so do Sonic Classics and S2 Mega Play. There's no consistent REV02 JmpTo arrangement, and unless the exact algorithm for how JmpTos are produced is found, I don't know what can be done about it.

    Anyway, so... yeah. I think that's everything I know about REV02. Hopefully this helps clear up any weird misconceptions about the thing.

    ...I was not expecting this post to be so long.
     
    Last edited: Nov 12, 2018