(WIP) Sonic CD Disassembly

Discussion in 'Showroom' started by ⸸ devon ⸸, May 22, 2022.

  1. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    So, over the past several years, I have dug into Sonic CD's code. As of today, I have a disassembly of Palmtree Panic Act 1 Present (R11A), and also various core engine files. They all should build bit perfect files.

    Link

    It's kinda shit, as I'm not the greatest at formatting things, but I hope that this is useful in some capacity. At least it'll give you an idea of how Sonic CD's core engine works (spoiler: like shit), and also how 1 level works. None of the other levels have been disassembled, and I don't really have the motivation to do so right now. In the end, though, this should provide a decently solid base for hacks, if you ever desired to make one for Sonic CD (although, in my personal opinion, it'd probably be a better idea to make your own Sega CD engine and port Sonic 1 to it, or something).

    R11A is properly split up and even has SonLVL support. Only thing that's really off with it is that code documentation wasn't finished.
     
    Last edited: Jun 3, 2022
  2. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    Updated the "misc" repo. Title screen and sound test are disassembled and mostly documented. The repository now allows you to build a working ISO image, if you provide the original CD files as well.
     
    Last edited: Jun 2, 2022
  3. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
  4. Nik Pi

    Nik Pi Active Member Member

    Joined:
    Feb 14, 2022
    Messages:
    39
    Location:
    Kazakhstan
    Nice work! Do you think to make a full disasm, or just partially? Anyway- thank you for it. Good luck!
     
  5. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    We'll see how I feel. This definitely won't be deemed complete any time soon. There's still a shit ton of level files to go through, on top of special stages and other misc. files. I've been working on and off on this thing since 2015.
     
  6. Nik Pi

    Nik Pi Active Member Member

    Joined:
    Feb 14, 2022
    Messages:
    39
    Location:
    Kazakhstan
    Yeah, I know, it's really difficult. Just... it's really cool that someone decided to do this :)
     
  7. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    Let's talk about the real reason making a full Sonic CD disassembly is a pain in the ass. It's not just that there's over 70 level files to go through, but each of them have a bunch of their own minute little quirks and differences that just throw off a bunch of things.

    Let's take LevelSizeLoad for example. In R11A, it starts like this
    Code:
    ; -------------------------------------------------------------------------
    ; Get level size and start position
    ; -------------------------------------------------------------------------
    
    LevelSizeLoad:
        moveq    #0,d0                ; Clear unused variables
        move.b    d0,unusedF740.w
        move.b    d0,unusedF741.w
        move.b    d0,unusedF746.w
        move.b    d0,unusedF748.w
        move.b    d0,eventRoutine.w        ; Clear level event routine
    Well, in R12A, it's this
    Code:
    ; -------------------------------------------------------------------------
    ; Get level size and start position
    ; -------------------------------------------------------------------------
    
    LevelSizeLoad:
        lea    objPlayerSlot.w,a6        ; Get player
    
        moveq    #0,d0                ; Clear unused variables
        move.b    d0,unusedF740.w
        move.b    d0,unusedF741.w
        move.b    d0,unusedF746.w
        move.b    d0,unusedF748.w
        move.b    d0,eventRoutine.w        ; Clear level event routine
    R11A directly accesses the player object variables, while R12A accesses them through the "a6" register. On top of that, R12A retains leftover Sonic 1 code that checks the credits, while R11A removes it.

    This is just ONE example.
     
  8. Clownacy

    Clownacy Retired Staff lolololo Member

    Joined:
    Aug 15, 2014
    Messages:
    956
    Oh geez, so the various level blobs may not have been built from the same codebase, but rather separately maintained ones? That's just gross.
     
  9. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    Pretty much. A good chunk files did appear to be commonly used between levels thankfully, but more per level specific things weren't. You can even see how it is in the file names found in the Gems collection version. (The files for the player object, collision, and other common objects and functions are shared, then it goes down to per zone objects, and then finally per act/time zone stuff, as you can see with files like ACT11A, DEV11A, and SCR11A)

    LevelSizeLoad appears to be lumped in with the scrolling and drawing code (makes sense considering it calls the scrolling initialization routine), which was something separately maintained, hence why we have this specific example. Still annoying to deal with. I can try my best to maintain some commonality, but I'd rather not make it an if/else hell.

    Another good reason to just port Sonic 1 or whatever to the Sega CD yourself if you really wanna use it. At this point, this is just better for documentation purposes, not so much hacking.

    EDIT: A quick visualization of the file structure in Sonic CD in Gems collection:
    [​IMG]
    ACTxxx = Object index/Null object
    BOSS_x = Boss object
    COLxx = Palette cycling/fading/data
    COLIx = Player object collision
    DEVxxx = Level tiles/blocks/chunks index
    EDTBLxxx = Debug object index
    EMIEx = Amy object
    ETx = Robot generator
    FRIENDx = Animal object
    KUZURxxx = Collapsing platform data
    MOVIEx = Metal Sonic projector
    PLAYPATx = Sonic sprite mappings
    SCRxxx = Level scroll/drawing
    ZxxACT = Object layout
    ZxxxTBL = Collision data/Level layout
    ZONETBLx = Title card mappings
     
    Last edited: Jun 5, 2022
  10. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    After bashing my head and having several drinks, I have a decent-ish split of the level code, and got R11B (Palmtree Panic Act 1 Past) disassembled and buildable, with SonLVL support. It has been pushed into the repository.

    Honestly, don't be expecting this to be the most "hack friendly" thing. I've already explained above the nature of how the original source code was assembled, and how that affects this. I am trying my best to make it manageable, and even incorporating things like SonLVL support, but I have my limitations.
     
    Clownacy and yami like this.
  11. saan1ty

    saan1ty Well-Known Member Member

    Joined:
    Mar 1, 2021
    Messages:
    68
    Location:
    North Carolina
    Like the R11A version that was playable as a genesis standalone rom (i think it was) will there be an option to build it without cd music so it can be played on the genesis or will it become cd only if you know what i mean?
     
  12. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    No, this just aims to be a buildable disassembly of Sonic CD as a whole.

    btw, just added R11C
     
  13. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    tfw the demo variant of R11A in the Japanese and European version of the game was built using a slightly older version of R11A, making me have to insert more if/else statements to keep things building bit-perfect...

    Added DEMO11A, and also R11D was added a few days ago.

    Also imagine if GOODEND.BIN streamed data from BADEND.STM and BADEND.BIN streamed data from GOODEND.STM, making it so that the game has to load GOODEND.BIN for the bad ending, and vice versa... Man, the development cycle must've been a pain.
     
    Last edited: Jun 10, 2022
  14. Nik Pi

    Nik Pi Active Member Member

    Joined:
    Feb 14, 2022
    Messages:
    39
    Location:
    Kazakhstan
    @ralakimus, I have a question.
    As you can (or can't) know, if you'll change a mappings, or insert more animation frames for Sonic, and after- build it, you'll get a CHAOS from pixels instead of a level (although the level assets have not changed). I wonder why this is happening? You have already mentioned that this thing is not too friendly to hacking, but.. is there any explanation for this?
     
  15. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    Chunks are hardcoded to be located at offset $20000 in Word RAM. Shifting them away from that location causes problems. I made a guide to fix that a while back in the old R11A disassembly, or you can just go into the "data" files for each level and turn the padding using leftover data into proper align macros.
     
  16. Nik Pi

    Nik Pi Active Member Member

    Joined:
    Feb 14, 2022
    Messages:
    39
    Location:
    Kazakhstan
    Thank you very much!!!!
     
  17. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    Last edited: Aug 28, 2022
  18. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    So, how about some specifications for the special stage maps (possibly to use for making a tool)?
    • The map itself is just a standard 4096x4096px Sega CD stamp map, with 32x32 stamps. Stamp data and stamp map data are compressed in Kosinski.
      • Stamps are basically just Genesis tiles, arranged vertically, exactly like how tiles are arranged for sprites.
      • [​IMG]
      • Stamp IDs are the address relative to Word RAM divided by 0x80, making 32x32 stamp IDs multiples of 4 only (remember, 16x16 stamps are also a thing on the Sega CD).
      • Stamp maps then are 128x128 stamps in size. Each stamp ID is a word.
        • HRR0 0SSS SSSS SSSS
          • S = Stamp ID (last 2 bits are actually ignored with 32x32 stamps, again, multiples of 4 only)
          • R = Rotation (00 = 0deg, 01 = 90deg, 10 = 180deg, 11 = 270deg)
          • H = Horizontal flip (done before rotation)
          • [​IMG]
    • Stamps are loaded 0x200 bytes into Word RAM, making the first stamp always blank (the stamp bank address is always fixed at the start of Word RAM, by the way).
      • Stage 3 has a secondary set of stamps loaded 0x10000 bytes into Word RAM.
    • Each stamp is assigned a type via a separate array of byte sized IDs (like how each 16x16 in regular stages gets a collision block ID via its own separate array).
      • 0 = Path
      • 1 = Bumper
      • 2 = Fan
      • 3 = Water
      • 4 = Rough surface
      • 5 = Spring
      • 6 = Hazard
      • 7 = Big boost pad
      • 8 = Small boost pad
      • 9 = Oil (Yes, the unused oil stamps have this. No, it doesn't do anything)
    • Stamp animations are handled in its own routine with its own stage specific data
      • 2 animations types: hazard and fan
      • Animation data is an array of stamp IDs to place in various locations in the stage
        • Each entry starts off with a word value indicating the raw offset in the stamp map to write the stamp ID. It's then followed with the stamp IDs used in the animation.
        • Hazard animations have 4 frames, and fan animations have 3.
    • Object positions on the map are 16.16 fixed point.
    • Each UFO has their own path data to follow. No, they do not actively dodge the player, they stay strictly on the path.
      • First byte is the UFO item type
        • 0 = Rings
        • 1 = Speed shoes
        • 2 = Time (Only valid for the time UFO, which is actually a separate object. Regular UFOs will just give you rings)
        • 3 = Hand (Unused, just gives you rings)
      • Second byte is some unknown, unused flag.
      • Each path node contains 5 words:
        • First word is how many frames it takes to move to the next node. Basically, its speed.
        • Second and third words are the node's starting X and Y positions respectively. When a UFO starts on a node, it automatically snaps to this position.
        • Fourth and fifth words are the node's target X and Y positions respectively. It'll move towards this position at within the time specified.
      • Data is terminated with a single 0xFFFF.
    • Time UFO has its own path data that's shared between every stage.
    • A quick fun fact: regular UFOs are always purple and the vertical stripes on them change color depending on the item they have (yellow for rings, white for speed shoes). The time UFO is always blue.
    Data in the disassembly can be referenced, of course.
     
    Last edited: Aug 30, 2022
  19. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
  20. ⸸ devon ⸸

    ⸸ devon ⸸ There's nothing left but faith Member

    Joined:
    Aug 26, 2013
    Messages:
    1,147
    So, the opening FMV has been extracted into a GIF and 2 WAV files (1 for USA, 1 for Japan/Europe). I also hacked together a quick program to convert them back into an STM file (bit perfect, too), and is included in the disassembly. Currently only supports the opening FMV format, support for the ending and pencil test FMV formats will come later down the line.

    If you want to implement your own FMV with this, make sure that the GIF is 256x112 and has at max 16 colors per frame, and runs at 8 FPS. WAV file can be unsigned 8-bit or signed 16-bit mono or stereo at any sample rate (although the resampling code is very very rudimentary and shit, so it may be best to do that manually to 32768 Hz. Really, the program as a whole was hacked together, but it works for now. I may come back to it later and do a """proper""" version, or maybe someone will come along and do a better job.

    I have this ffmpeg command for converting a video into a compatible GIF:
    Code:
    ffmpeg -i [INPUT FILE] \
    -vf "fps=8,scale=256:112:force_original_aspect_ratio=decrease,pad=256:112:(ow-iw)/2:(oh-ih)/2,setsar=1,split[s0][s1];[s0]palettegen=max_colors=16:stats_mode=single[p];[s1][p]paletteuse=dither=[DITHER ALGORITHM]:new=1" \
    [OUTPUT FILE]
    If for whatever reason you want the video to stretch out to 256x112, you can remove the "force_original_aspect_ratio" bit. You can also make it so that only 1 palette is generated for the entire video by removing the "stats_mode=single" bit as well.

    This is MakeSTM's usage:
    Code:
    USAGE: MakeSTM [GIF file] [Wave file] [Color mode] [Sync mode] [Output file]
    
    Note: Only supports the opening FMV format so far!
    
    Color mode:
        0 = Hardware
        1 = Multiples of 16
        2 = Multiples of 18
    
    Sync mode:
        0 = Don't auto-sync video and audio
        1 = Enable auto-sync
    For the opening FMV provided by the disassembly, it's already set up to handle the timing issues with the FMV player, so auto-sync is set to off for it in the build script. However, for custom videos, you should set it to 1.
     
    Last edited: Sep 27, 2022 at 6:51 PM