Hacking the ports of the Genesis games in Sonic Jam

Discussion in 'Discussion & Q&A' started by Devon, May 14, 2023.

  1. Devon

    Devon I'm a loser, baby, so why don't you kill me? Member

    Joined:
    Aug 26, 2013
    Messages:
    1,377
    Location:
    your mom
    It has been stated before that, yes, the Genesis games were ported to the Saturn. However, what I noticed was that the code looked to be a 1:1 machine translation of the original 68000 code to SH-2 assembly with changes made on top of it. Why do I think that? Lemme show some example.

    Let's take this tidbit of 68000 code:
    Code:
                    asr.w oYVel(a0)
                    asr.w oYVel(a0)

    In Jam, that got translated into:
    Code:
                   mov.w   #oYVel, r0
                   mov.w   @(r0,r8), r3
                   shar    r3
                   mov.w   r3, @(r0,r8)
    
                   mov.w   #oYVel, r0
                   mov.w   @(r0,r8), r3
                   shar    r3
                   mov.w   r3, @(r0,r8)
    Since the SH-2 is a RISC architecture, the instruction set is more limited and simpler, which is why each shift instruction was translated as so. Now, you may also notice that it sets up "oYVel" twice, and does a read and write for the individual shifts, instead of just reading once, doing both shifts, and then writing.

    Here's another example. Take this instruction:
    Code:
                   divs.w #$68,d2

    This has been translated into:
    Code:
                   mov     #h'68, r0
                   mov.l   @(8,r6), r3
                   tst     r0, r0
                   bf      loc_604152A
    
    loc_6041526:
                   bra     loc_6041526
                   nop
    
    loc_604152A:
                   exts.w  r0, r0
                   mov     #h'FFFFFF80, r2
                   shll    r2
                   mov.l   r0, @(0,r2)
                   mov.l   r3, @(4,r2)
                   mov.l   @(h'14,r2), r3
                   mov.l   @(h'10,r2), r1
                   shll16  r3
         
                   mov.l   r3, @(8,r6)

    The thing to note here is that after r0 is set to 0x68, it then checks if r0 is 0, and if it somehow is, then it enters an infinite loop. Obviously, that check if completely unnecessary, because r0 will always be 0x68 in this instance.

    Here's one last example that I think is pretty damning that the 68000 code was machine translated: the sheet abundance of 68000 data register simulation. What do I mean by this? Well, let's take this tidbit of 68000 code:
    Code:
                   moveq    #0,d0
                   move.b    oRoutine(a0),d0
                   move.w    ObjSonic_Index(pc,d0.w),d1
                   jmp    ObjSonic_Index(pc,d1.w)

    That was translated into:
    Code:
                   mov     #0, r0
                   mov.l   r0, @(m68kD0,gbr)
                   mov.w   #oRoutine, r0
                   mov.b   @(r0,r8), r0
                   mov.b   r0, @(m68kD0+3,gbr)
                   mov.w   @(m68kD0+2,gbr), r0
                   mov.l   #ObjSonic_Index, r2
                   mov.w   @(r0,r2), r0
                   mov.w   r0, @(m68kD1+2,gbr)
                   mov.w   @(m68kD1+2,gbr), r0
                   mov.l   #ObjSonic_Index, r1
                   add     r1, r0
                   jmp     @r0

    "gbr" here points to a set of additional variables and settings in RAM, and at the start of it are 8 variables that simulate the 8 data registers on the 68000. The address registers are simulated via the r8-r14 registers.

    Here's another example:
    Code:
    PaletteCycle:
                   moveq    #0,d2
                   moveq    #0,d0
                   move.b    zone,d0
                   add.w    d0,d0
                   move.w    PalCycle_Index(pc,d0.w),d0
                   jmp    PalCycle_Index(pc,d0.w)
    Code:
    PaletteCycle:
                   mov     #0, r0
                   mov.l   r0, @(m68kD2,gbr)
                   mov     #0, r0
                   mov.l   r0, @(m68kD0,gbr)
                   mov.l   #zone, r0
                   mov.b   @r0, r0
                   mov.b   r0, @(m68kD0+3,gbr)
                   mov.w   @(m68kD0+2,gbr), r0
                   add     r0, r0
                   mov.w   r0, @(m68kD0+2,gbr)
                   mov.w   @(m68kD0+2,gbr), r0
                   mov.l   #PalCycle_Index, r2
                   mov.w   @(r0,r2), r0
                   mov.w   r0, @(m68kD0+2,gbr)
                   mov.w   @(m68kD0+2,gbr), r0
                   mov.l   #PalCycle_Index, r1
                   add     r1, r0
                   jmp     @r0
                   nop

    Now, let's talk about the RAM layout for the games. They're exactly the same as they are in the original Genesis ROMs. RAM used in the original code is allocated at 0x60F0000-0x60FFFFF. Again, the gbr register points to another set of variables used for settings and other changes and such.

    So, what about the game data. Have they been changed to fit the Saturn?

    NO.

    That's right, the game data is still left in their Genesis formats. Graphics, stage data, palettes, you name it. Sound is the exception here, seeing as the the way audio is done on the Saturn is very very different from the way it's done on the Genesis. Sound is just handled using Sega's stock sound driver, and all the music and sound effects are just sampled and played back. Other than that, graphics wise, the Saturn actually supports the Genesis' tile format if you set it up to do so. With palettes and sprites, they have a set of translation functions that convert the Genesis formats into the Saturn formats.

    Here's a sample of palette data from Sonic 1 in Jam:
    Code:
    Pal_Title:
                  .data.w h'A20, h'600, h'C00, h'E44, h'E66, h'E88, h'EEE, h'AE, h'6A, h'26, h'EE, h'EAA, h'C, 6, 2, 0
                  .data.w 0, h'C00, h'E22, h'E44, h'E66, h'E88, h'EEE, h'AAA, h'888, h'666, h'444, h'248, h'8AE, h'68C, 0, h'E
                  .data.w h'800, 2, h'EEE, h'26, h'48, h'6C, h'8E, h'CE, h'C42, h'E86, h'ECA, h'EEC, h'40, h'60, h'A4, h'E8
                  .data.w h'C82, h'A02, h'C42, h'E86, h'ECA, h'EEC, h'EEE, h'EAC, h'E8A, h'E68, h'E8, h'A4, 2, h'26, h'6C, h'CE
    
    Pal_LevelSel:
                  .data.w 0, 0, 2, 2, h'224, h'224, h'446, h'446, h'224, h'224, h'446, h'668, h'224, 2, 0, 0
                  .data.w 0, 0, 2, h'224, h'224, h'446, h'668, h'224, h'446, h'224, 2, h'224, h'446, h'224, 0, h'224
                  .data.w 0, h'EE, 0, 0, 0, 0, h'EE, 0, 0, 0, 0, 0, 0, 0, 0, 0
                  .data.w 0, h'EEC, 0, 0, 0, 0, h'EEC, 0, 0, 0, 0, 0, 0, 0, 0, 0
    
    Pal_Sonic:
                  .data.w 0, 0, h'822, h'A44, h'C66, h'E88, h'EEE, h'AAA, h'888, h'444, h'8AE, h'46A, h'E, 8, 4, h'EE
    
    Pal_GHZ:
                  .data.w h'800, 0, h'242, h'464, h'686, h'8C8, h'EEE, h'AAA, h'888, h'444, h'8EA, h'46A, h'EE, h'88, h'44, h'E
                  .data.w h'E80, 2, h'EEE, h'26, h'48, h'6C, h'8E, h'CE, h'A86, h'E86, h'EA8, h'ECA, h'40, h'60, h'A4, h'E8
                 .data.w h'C82, h'A02, h'C42, h'E86, h'ECA, h'EEC, h'EEE, h'EAC, h'E8A, h'E68, h'E8, h'A4, 2, h'26, h'6C, h'CE

    If you were to compare this to how they are stored in the original game, you will see that they are exactly the same. These are loaded into the palette buffer, and then the buffer gets translated into the proper palette format for the Saturn's CRAM. With sprites, they just build the sprite table in the Genesis' sprite table format, and then take use that to convert it into a display list for VDP1.

    Hell, with the rest of the game data, they're even compressed in the same compression algorithms as they were in the original games, Nemesis, Enigma, and Kosinski. They still have the PLC systems, Nemesis still decompresses directly into VRAM, etc.

    To demonstrate this, a friend of mine ported some stage assets from an old hack of his into Jam with ease:


    So yeah, in conclusion, the ports basically work just about exactly the same as they did in the original Genesis versions, with the code being a direct translation of the 68000 code into SH-2 and the assets being left in their original formats, with the VDPs set to work with the Genesis format for tiles, and a translation layer added in to convert palettes and sprites for the Saturn. Regarding hacking, the only learning curve involved would be having to learn SH-2 assembly to modify some of the code, and even then, if you only really contain yourself within the game port itself, it shouldn't be too terribly hard. You can basically use the same hacking tools used in the original Genesis games for editing the assets in the ports in Jam (Only things like graphics and stage data, though. Again, the audio is handled by Sega's stock sound driver for the Saturn). Of course, anything outside of this scope would start requiring you to understand more of how the Saturn works.


    You can also even just take the assets and import them back into the Genesis ROMs, and they will work... mostly. I say "mostly" because I did find some additional subtypes that would need to be ported over as well.
     
    Last edited: May 14, 2023
  2. MDTravis

    MDTravis Local Prototyper Member

    Joined:
    Dec 22, 2019
    Messages:
    24
    All the sound effects? I swear some sounds (like jumping and rings) are synthesized...
     
  3. Devon

    Devon I'm a loser, baby, so why don't you kill me? Member

    Joined:
    Aug 26, 2013
    Messages:
    1,377
    Location:
    your mom
    I'll admit that was a bit of an assumption. The music definitely is sampled, as are a lot of sound effects. I'd need to look into the sound stuff more to know for sure.
     
    ProjectFM likes this.
  4. MDTravis

    MDTravis Local Prototyper Member

    Joined:
    Dec 22, 2019
    Messages:
    24
    I would've thought it was safe to assume anything that's not in the "SONIC12.SND" file was synthesized, though I believe some sounds here are synthesized anyway in-game (such as rolling).
    Converted file into WAV format (16000 Hz playback)
     
  5. LackofTrack

    LackofTrack Well-Known Member Member

    Joined:
    May 30, 2018
    Messages:
    51
    I don't think any sound effects from the ports are synthesized. While the SCSP, the Saturn's sound chip, can do FM, the way it does it is a lot different to how the YM2612 does it. 98% of Saturn titles do not use it for a reason. Not to mention handling PSG. While it CAN be done, it would be tricky to pull off correctly and since the rest of the porting job seems pretty straight forward (machine translated code, almost untouched graphics and compression) it would be weird for them to suddenly go all out on sound. Especially when you can just sample everything due to the increased space of a CD compared to cartridge.
     
    ProjectFM and DeltaWooloo like this.
  6. MDTravis

    MDTravis Local Prototyper Member

    Joined:
    Dec 22, 2019
    Messages:
    24
    If they are samples, they've done it in a very strange way.
     
    DeltaWooloo likes this.
  7. LackofTrack

    LackofTrack Well-Known Member Member

    Joined:
    May 30, 2018
    Messages:
    51
    After doing a bit of digging myself, it seems the sound effects are done in an unusual way. From what I can tell, instead of them sampling the complete SFX, they instead sampled individual parts of the SFX. For example, the spin dash revving SFX you show in the video has two samples: One that is the recognizable "whirring" SFX, and one that is just white noise to make the SFX a bit more fuller. You can see it showcased in your video. It's actually kind of weird because originally that SFX only took up one FM channel. It seems a lot of SFX where done this way, individual parts sampled, and sometimes not exactly done how the original SFX was.
    One other funny thing is that since white noise is often used in the SFXs, there are a lot of variations of white noise samples such as the one I've attached. This is despite the fact that the SCSP actually can just create white noise on its own. No samples required.
    It's important to note that FM Synthesis is still not being employed here. In your debugger drop down menu, if you click "SCSP" and look at the individual slot parameters, you'll notice "Modulation Level", "Modulation Input X", and "Modulation Input Y". These three parameters dictate if a slot, or channel, is doing FM Synthesis. None of those params are set to any besides 0 during the course of my testing which means there is no synthesis at play here. You'll also notice "Sound Source = External DRAM Data" which means internally generated white noise isn't being used here either. It's probably for the best that they didn't used the internal white noise though, as most emulators do not emulate it at all.
     

    Attached Files:

    Last edited: May 15, 2023
  8. Devon

    Devon I'm a loser, baby, so why don't you kill me? Member

    Joined:
    Aug 26, 2013
    Messages:
    1,377
    Location:
    your mom
    Extracted object (and ring) layouts from Sonic 1 and 2

    For the most part, they are totally compatible with the original Genesis ROMs. I say "mostly", because Jam actually adds a couple subtypes in Sonic 1 that will need to be ported over.

    The first is a new spike subtype, value 6x. It's basically just subtype 0x, but only the leftmost spike is displayed

    [​IMG]

    To implement this, add this line to the end of Spik_Var in "_incObj/36 Spikes.asm"
    Code:
            dc.b 6, $14
    Then, in "_maps/Spikes.asm" add this entry at the end of the index table
    Code:
            dc.w Map_Spike_06-Map_Spike_internal
    and then add this sprite data
    Code:
    Map_Spike_06:    dc.b 1
            dc.b $F0, 3, 0, 4, $E
    The other subtype is a new platform subtype, value 0D, It's basically just subtype 00, but its solid hitbox is expanded by 16 pixels, and also has higher drawing priority

    [​IMG]

    To implement this, add this set of code after obFrame(a0) is set in .setframe in "_incObj/18 Platforms.asm", rigth before the Plat_Solid label

    Code:
            cmpi.b    #$D,obSubtype(a0)
            bne.s    Plat_Solid
            move.b    #$28,obActWid(a0)
            move.b    #3,obPriority(a0)

    Then, go to Plat_Move, and add this at the end of its index table
    Code:
            dc.w .type00-.index
    What's the purpose of these new subtypes? To artificially fill in gaps in this section of Green Hill Zone

    [​IMG]

    An additional note is that the original layouts do some small modifications, particularly with filling in gaps with a solid object and all that sort of stuff.
     
  9. Devon

    Devon I'm a loser, baby, so why don't you kill me? Member

    Joined:
    Aug 26, 2013
    Messages:
    1,377
    Location:
    your mom
    Oh yeah, by the way, here's a tool I made for comparing object layouts for Sonic 1 and 2. Useful for tracking changes between revisions and the Jam layouts.

    UPDATE: Forgot to add the ring layout comparison tool.
     
    Last edited: Aug 20, 2023
    ProjectFM likes this.
  10. Devon

    Devon I'm a loser, baby, so why don't you kill me? Member

    Joined:
    Aug 26, 2013
    Messages:
    1,377
    Location:
    your mom
    Sonic Jam's "original" object layouts for Sonic 1 and 2 actually have a few additions.

    Sonic 1 only has one addition and it's this barrier object in Star Light Zone Act 2:
    [​IMG]

    This was added to prevent Sonic from jumping up over there like so:
    [​IMG]

    Other than this, the object layouts match REV01.

    Sonic 2 has a few additions. 2 in Emerald Hill Zone Act 2 and 1 in Aquatic Ruin Zone Act 2.

    The first addition in Emerald Hill is this small barrier object in the small gap between the wall and spring:
    [​IMG]

    This was added to prevent Sonic from clipping into that small corner from below, which can be done as Super Sonic easily:
    [​IMG]

    The other addition in Emerald Hill is this pathswapper in this corridor near the bottom of the level:
    [​IMG]
    Now, ideally, this is to prevent Sonic from getting stuck in this corridor, which can happen if Sonic gets swapped over to path 1 and somehow gets stuck in there, because in path 1, walls are set up there.
    [​IMG]

    However, I'm not quite sure how you can get stuck in there legitimately without the use of debug mode...
    [​IMG]

    At first, I thought maybe it Sonic could creep on the ledge and somehow activate that path 1 pathswapper, but I can't get that to happen, and every possible entrance around here is pretty much blocked off once Sonic is on path 1 here, so I dunno. If anyone has any ideas, let me know.

    The addition made in Aquatic Ruin is this:
    [​IMG]

    This adds an extra pathswapper that fixes Sonic being able to go into that loop section past that path 0 pathswapper and not being able to go up the loop without moving left past the pathswapper:
    [​IMG] [​IMG]

    And those are the object layout changes between the Genesis games and Sonic Jam's "original" mode.
     
  11. Devon

    Devon I'm a loser, baby, so why don't you kill me? Member

    Joined:
    Aug 26, 2013
    Messages:
    1,377
    Location:
    your mom
    Extracted object and ring layouts from Sonic 3 and Sonic & Knuckles
    Object and ring layout comparison tools

    Unlike Sonic 1 and 2, Sonic 3 & Knuckles is modular. Each zone's data is separated into their own files (ZONExx.SNx). As such, I had to figure out the addresses in which each file was loaded into to find and extract the data.

    With Sonic 3, no unplayable zones were given their own files, and instead would have blank object and ring layout data appended in the ACTTBL (start position, object layout, and ring layout index) files that are pointed to for those zone IDs instead. Sonic & Knuckles only does this for the ending (which does have its own file ZONEEND.SN4, but the (blank) layout data is excluded).

    Now let's get into differences between the Genesis object layouts and Jam's "Original" object layouts.

    First, let's check out Hydrocity Zone Act 2 in Sonic 3 alone only. First off, it removes the capsule after the boss.
    [​IMG]

    Basically, in Sonic 3 alone, it was possible to skip the boss by moving quickly enough towards the capsule. Sonic Jam basically applies the fix from Sonic & Knuckles by having the boss itself control when to spawn the capsule instead.
    [​IMG]

    They also removed these spikes from Knuckles' route for some reason, even though you can't play as Knuckles in Sonic 3 alone.
    [​IMG]

    On top of this, they actually bothered to add in the boss in this route as well.
    [​IMG]

    Let's go to Marble Garden Zone Act 1 now for Sonic 3 alone only. The only thing changed was that this object designed to kill the player when crushed here was moved down by 8 pixels.
    [​IMG]

    The reason they did this is basically due to the object being able to be touched if the collision engine hasn't caught up fully when Sonic is moving quick enough. This is explained in more depth by MarkeyJester.
    [​IMG]

    This is the only instance of this fix for all the crushers, and it's not even in the Sonic & Knuckles version! They most definitely only noticed the problem in this one case.

    Let's go to Carnival Night Zone Act 2 now for both Sonic 3 alone and Sonic & Knuckles. Here, this object designed was added to crush the player if the platform below goes up too high towards the ceiling was added.
    [​IMG]

    Without it, the player wouldn't get crushed, because the engine relies on separate objects to handle crushing if they're standing on a moving object. Without it, the player would clip through like this.
    [​IMG]

    The other addition in this stage is this solid object added in this corner, but only in Sonic 3 alone.
    [​IMG]

    The reason this is here is because Sonic 3 alone actually has an issue that stems as far back as Sonic 1, where if the player is not moving on the floor, but rather a wall or ceiling, then collision with solid terrain in front of them will not work properly, with the player phasing right through it in certain circumstances. See this for more information.
    [​IMG]

    Sonic & Knuckles fixes this bug, but only if the player is on a flat surface (0, 90, 180, or 270 degrees). Because of that, though, the Sonic & Knuckles version doesn't add this object, since it's unnecessary.

    Next, let's go to Icecap Zone Act 2 in Sonic 3 alone only. They added another object designed to crush the player if the platform they're on moves too close to the ceiling.
    [​IMG]

    Again, without it, the player would just clip through the ceiling.
    [​IMG]

    Oddly though, they didn't add it to the Sonic & Knuckles version, so you can perform that clip there.

    Let's go to Launch Base Zone Act 2 now for Sonic 3 alone only. These two objects were added.
    [​IMG]

    The left object kills the player if they touch it, and the right object acts as a wall that keeps the player from clipping inside, due to the bug I mentioned for Carnival Night Zone Act 2 in Sonic 3 alone.
    [​IMG]

    The Sonic & Knuckles version of course doesn't have this, since it fixes that bug. However, the Sonic & Knuckles version adds this solid block.
    [​IMG]

    In the original game, you can double jump over there (thanks to muteKi for pointing it out), but due to an issue with handling collision in blank rows (it reads from the ROM header when a layout row pointer is 0 due to the use of signed 16-bit addresses), it reads garbage collision data that prevents the player from advancing.
    [​IMG]

    However, in Sonic Jam, the way collision is read for blank layout rows was slightly altered due to the lack of support for reading signed 16-bit addresses on the SH-2, in which it adds the 16-bit value onto the base address for the game's variable space in RAM, so it'll end up reading from a blank chunk instead, so the effect from the above GIF doesn't happen, and the player can move past... and end up in Knuckles' boss arena.
    [​IMG]

    Beating the boss will result in a glitched cutscene that doesn't end, so they placed that solid block there to prevent the player from being able to jump over there and cause this.
    [​IMG]

    Thanks to nineko for pointing this out, as this same issue also exists in Sonic & Knuckles Collection.

    And that's it, those were the changes made to Jam's "original" object layouts. None of the Sonic & Knuckles specific zones were updated, just the Sonic 3 ones.
     
    Last edited: Aug 31, 2023