don't click here

Sonic CD decompilation

Discussion in 'Engineering & Reverse Engineering' started by BenoitRen, Jul 17, 2023.

  1. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    Another file done: BMP.C!

    This houses the code that reads the decompressed bitmap file data and creates bitmap objects for tiles and sprites.
     
  2. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    Another file done: WAVE.C!

    This has the code for loading and managing PCM sound effects.
     
  3. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    Another day, another file: DLL.C

    This was rather boring to decompile, because there's a lot of repetition. Each DLL just has to have its own function for loading and unloading, even if the code is exactly the same.

    I also cleaned up and updated the code I already had by making more headers and updating function names.
     
  4. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,488
    1,818
    93
    your mom
    Not unlike the original Sega CD version, with the massive amount of system commands for loading various screens, stages, playing music, etc. Lots of repetitive code there.
     
  5. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    I went ahead and started coding my replacement executable for the game yesterday. So far, I've got the decompression of tiles done. You can view the work-in-progress in the CHEDGEHOG folder of the repo.

    Yes, CHEDGEHOG. It's short for Compact Hedgehog, a play on the game's title. Though the abbreviation could also refer to the C programming language. ;) It's a code name for the project until I can think up of something better. "OpenSonicCD" would be cool, but something that doesn't use any trademarks is preferable.

    It's coded in C89, just like the game's decompilation. I hope my code is easy to read and that I didn't over-engineer it in my attempt to make it endian agnostic.
     
    • Like Like x 1
    • Informative Informative x 1
    • List
  6. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    I've got sprites mostly working. What's not implemented is sprite horizontal and vertical flip, which right now causes Sonic to seemingly warp when he changes directions. But there are some problems: not all sprites (most notably, rings) seem to be the correct ones, and transparency (colour keying) doesn't seem to be working.



    As you can see, I can't get past the first part of the level, either. While adding the decompiled source files to my project, I noticed that some functions that were supposed to return a value didn't. I was hoping that fixing those would have fixed that.

    Questions I have, should anyone have the knowledge to answer them:
    • What are the X and Y offsets that are stored for each sprite?
    • When manipulating pixels of an SDL surface, you're supposed to skip the "pitch" part that's at the end of each line. Doing so, however, results in a segmentation fault, as it's writing outside array bounds. Not skipping results in the bitmap being correctly loaded.
    • Am I really supposed to apply the colour palette to each SDL surface I create? It would make more sense to apply it to the screen surface once.
     
  7. Clownacy

    Clownacy

    OCs are for the weak Tech Member
    1,116
    727
    93
    The pitch is the size of the whole line, not just the part at the end.

    SDL's indexed surface support is an absolute mess, so I suggest just avoiding it completely and rolling your own software renderer. If you try to blit an indexed surface to another indexed surface, it will attempt to map the colours of the source surface to those of the destination surface, despite them both being indexed. Not only is this a nonsensical waste of CPU time, but it also requires that all surfaces be given a copy of the palette.

    The rings being incorrect might be down to mistakes in the decompilation: you mentioned that the assembly code matches, but you never mentioned the data matching. In 'SCORE.C', you found that different zones use different sprite indices for the various HUD elements, so this likely applies to other sprites as well.

    I recommend comparing your executable to mine, to double-check which issues are caused by your executable and which are caused by the decompilation. As seen in the screenshot below, our executables share many of the same issues.

    upload_2024-12-18_15-37-28.png
     
  8. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    I haven't been able to ascertain that the data matches, because the structure of the original ELF is different compared to my compiled ELF. I'm waiting for a more experienced person to make me a link script to fix that.

    In the meantime, I checked several files, and there is indeed more data specific to certain rounds and zones. The pattern seems to be that there are different ids for R31+R32, R33, R6, R81, R82, and R83. I solved all those using the preprocessor. There might be a better way for SCORE.C, but you can't do arithmetic.

    As for the zone's title sprites, I somehow used the ids of R8, so that's completely my fault. I've corrected them.

    All changes have been pushed, and it's looking much better:

    upload_2024-12-18_23-6-39.png

    Going to look at your implementation tomorrow.
     
  9. Blastfrog

    Blastfrog

    See ya starside. Member
    Hey, the zone title might be cut off, but at least it still has alliteration! :P

    On a more serious note, I can't wait for this to be playable, you guys are doing great work. I'd love to mess around with it for some light mods.
     
  10. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    I got tiles working a couple days ago, but it really slowed the game down. Got horizontal scrolling partially working. But if I want to support all the possibilities I'll have to blit them line by line like Clownacy's already doing. All of this meant that I couldn't put off rolling my own software blitter.

    First, I rewrote the decompression code to no longer create SDL surfaces, but to store the bitmap data in an array instead. Next, I gave sprite code and variables their own file, and implemented the different blitting possibilities: normal, horizontally flipped, vertically flipped, and flipped both ways. Transparency was implemented on the way.

    I'm pretty happy with how the code turned out. I'm pretty sure I could combine the four blitting possibilities into one function, as most of the code is duplicated. But I wanted to put this version out first.

    A better implementation of tiles is next!
     
    • Like Like x 2
    • Informative Informative x 1
    • List
  11. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    I got tiles working at a basic level. Meaning I only blit the first line of each tile, and don't do any flipping. It results in a blocky look that I think looks rather nice, so I made a video of this progress for your enjoyment:



    Yes, I got past the loop! For some reason I need to hold the jump button to make it work. Once past it, I noticed that there are some more sprite ids I need to correct. :)
     
    Last edited: Dec 22, 2024
  12. Kilo

    Kilo

    Starting new projects every week Tech Member
    1,215
    1,162
    93
    Canada
    Changes with the weather
    Very cool, reminds me of the SNES' mosaic effect and how it's handled by sampling the top left most pixels, which is actually something I want to recreate for a project.
     
  13. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    Tiles are working, including flipped versions! Scrolling works as well, but, compared to the original, I seem to be scrolling too fast horizontally, which is why you can see water in the sky. I should note that per-line horizontal scrolling isn't implemented, yet.

    Speaking of the sky, it should be blue, but I currently don't know where I'm supposed to get the background colour from. I'm guessing the palette?

    At any rate, it now looks close to the real thing:



    You might have noticed that some sprites seem to be bugged.

    Sprites high on the screen don't show entirely. Might be a bug in the code that tries to not blit pixels outside of the screen.

    There's also an issue with the position of some sprites, most of them being horizontally flipped versions. I have no idea how to correct that at the moment. I've looked at the X and Y offsets loaded from the sprite bitmap files, but they seem to all be zero.
     
  14. Kilo

    Kilo

    Starting new projects every week Tech Member
    1,215
    1,162
    93
    Canada
    Changes with the weather
    At least in regards to the Mega CD version, the background colour is taken from the palette line 2, colour 0 in the palette.
    upload_2024-12-23_17-21-10.png
     
    • Like Like x 1
    • Agree Agree x 1
    • List
  15. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,254
    516
    93
    Japan
    Kilo is spot on, on the Mega Drive (MCD) it's what's known as the "backdrop" colour, there's a VDP register for it ($87), format is 00PPCCCC where PP is the pallet line 0 to 3 and C is the colour 0 to F. Most Sonic games (as Kilo pointed out) set this to $20, i.e. line 2 (3rd line), colour 0, the transparent colour.

    If no pixels are rendered, this colour is rendered by default, including in the boarder when pixel rendering is disabled and VDP V/H-blank are running.
     
    Last edited: Dec 24, 2024
  16. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    Thanks, that worked!

    I've fixed the bug that made high sprites not blit entirely when high up on the screen. But I'm at a loss when it comes to the wrong positioning of some horizontally flipped sprites.

    Sprites usually get screen coordinates for the top-left corner. When a sprite is horizontally flipped, I get those for the top-right corner. I'm guessing the idea is to blit pixels from right to left, but I find it easier to change the way the sprite's pixels are read. At any rate, I account for this by subtracting the sprite's width from the X coordinate.

    The problem is that some sprites, when horizontally flipped, send the coordinates for the top-left corner. I've isolated the index for the item monitor static sprite (280), and checked if there are any offsets defined for it in the bitmap file. There aren't. Maybe there's an exception for some widths? Checked the larger Past and Future panels. Nope, that's not it, either.
     
  17. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    I re-verified ITEM.C's assembly, as the graphical oddity came from objects it implements. It matched.

    Maybe a data problem? Verified the data, especially those that had the relevant sprite ids. They matched.

    Next I checked the function that sends the sprite data to the main program, spatset. I noticed that the coordinates of flipped sprites were being affected by values inside SprBmp:
    Code (C):
    1. switch (flag) {
    2.   case 0:
    3.     x = xposi + (short)sprdat->xoff;
    4.     y = yposi + (short)sprdat->yoff;
    5.     break;
    6.   case 1:
    7.     x = xposi + (short)(-sprdat->xoff - (short)SprBmp[sprdat->index].xs);
    8.     y = yposi + (short)sprdat->yoff;
    9.     break;
    10.   case 2:
    11.     x = xposi + (short)sprdat->xoff;
    12.     y = yposi + (short)(-sprdat->yoff - (short)SprBmp[sprdat->index].ys);
    13.     break;
    14.   case 3:
    15.     x = xposi + (short)(-sprdat->xoff - (short)SprBmp[sprdat->index].xs);
    16.     y = yposi + (short)(-sprdat->yoff - (short)SprBmp[sprdat->index].ys);
    17.     break;
    18. }
    SprBmp is an array of 700 bmp_info structs. Such a struct has three elements: xs, ys, and ofs. I noticed that the original sprite bitmap loading code would fill gKeepWork.pSprBmp xs with bitmap's width, and ys with the bitmap's height. I thought it was silly because when a stage/DLL loads, that pointer is overwritten by SprBmp.

    However, I was wrong about the order. First the stage/DLL is loaded, where the pointer is overwritten. After that, the bitmaps are loaded, and the data being set is not lost.

    After adding code to my sprite bitmap loading function to fill SprBmp, all flipped sprites had the wrong positions. Turns out that the coordinates for horizontally flipped sprites are not set at the top-right corner after all! So I removed my adjustment, and now everything blits correctly!

    All that's left to fix is the horizontal scrolling speed.

    EDIT: Implemented line-based horizontal scrolling. It was surprisingly easy. There are still incorrect tiles in the background, unfortunately.



    Yes, I finally figured out how to crop the capturing canvas!
     
    Last edited: Dec 24, 2024
  18. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    I fixed the horizontal scrolling speed! The problem was that I was using the scrb_h_posiw value for plane B. The original code retrieves that value, but never uses it, setting the plane's left offset to zero. Instead, it is hard-coded to use line-based horizontal scrolling, which the original code called the FX_HORIZ_SHEAR effect.

    This means that all graphical bugs are fixed. But I'm not out of the woods yet.

    The propeller wheel at the start of the level still doesn't work like it should. I've also noticed that holding up or down while Sonic is still doesn't scroll the camera. Something must be wrong somewhere in the code that I decompiled when it comes to reading held down buttons.

    EDIT: Verified all the places where input is read. All the accesses match.
    EDIT2: If I mash the up or down button the screen does scroll. Makes me think that it's reading the wrong part of the input, but again, I checked.
    EDIT3: I don't need to mash; just press the direction twice and hold. I decided to check the manual, and this is expected behaviour.
     
    Last edited: Dec 25, 2024
    • Informative Informative x 1
    • List
  19. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,488
    1,818
    93
    your mom
    Yeah, it checks for double tapping of up or down to scroll the screen. It's basically just Sonic CD's solution to the problem of the camera scrolling every time you press up or down to perform a move. It behaves the same way in the original Sega CD version as well.

    [​IMG]
     
    Last edited: Dec 25, 2024
    • Informative Informative x 1
    • List
  20. BenoitRen

    BenoitRen

    Tech Member
    853
    455
    63
    I couldn't let this rest, so I started commenting checks for any of the three jump buttons being pressed, to check when the workaround of holding the jump button would stop working. This led me to function jumpchk2, which seems to be used for the Super Peel Out:
    Code (C):
    1. void jumpchk2() {
    2.   short cal_speed;
    3.  
    4.   if (actwk[0].actfree[18] != 0) {
    5.     cal_speed = -1024;
    6.     if (actwk[0].cddat & 64) cal_speed = -512;
    7.     if (cal_speed > actwk[0].yspeed.w) {
    8.       if (!(swdata.b.h & 112)) {
    9.         actwk[0].actfree[0] = 0;
    10.         actwk[0].yspeed.w = cal_speed;
    11.       }
    12.     }
    13.   }
    14.   else {
    15.     if (actwk[0].yspeed.w < -4032) actwk[0].yspeed.w = -4032;
    16.   }
    17. }
    Checked the function in Ghidra, and BINGO! The subscript used in the very first condition (2) was wrong. It should be 18! With that corrected, the propeller object, kasoku, works as it should!