don't click here

Sonic CD decompilation

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

  1. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,491
    1,829
    93
    your mom
    The existence of PCM.CMP in the original DINO version isn't anything new, and in fact is supported in Sega PC Reloaded, and the file has even been archived (here and here). Even better is that it's also stored as signed 16-bit instead of unsigned 8-bit, which is what PCM8.CMP is stored as. Still in mono, though.
     
    Last edited: Jul 21, 2024
  2. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    Aw. I hope the technical details were at least an interesting read.
     
  3. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,797
    383
    63
    SonLVL
    It's very cool you found all that unused code for it!
     
  4. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    The following technical explanation is an attempt at demonstrating how close the decompiled C code is to the original, but that assumptions still had to be made because there is often more than one way to write something in C. Those assumptions were based on the debug information that we do have, but the assumption was also made that the programmers' code style was at least consistent within the same C source code file. This was not necessarily the case.

    First, a quick summary of the debug information that each ELF file contains:

    • Symbol Table (.symtab). Contains an index of function names and global variables. Ghidra can read this.
    • DWARF Information (.debug). An index of all functions, global variables, local variables, and user-defined types. It even records the full path of the source code files (they're all under C:\project\GEMS\application\SonicCD\src\ps2\main\).
    • DWARF Line Table (.line). If you've looked at the decompiled source code, you must have noticed that each line has a comment with the original line number and its associated memory address. Those were generated from this table, which contains the address of each function and an offset for each line of code. Essentially, this links chunks of ASM to lines.

    Next, I'm going to explain how I could use the debug information to figure out what was originally written. I've tried to explain things clearly, but I'm not used to making detailed explanations like this.

    Assignments

    There's only one way to assign a value to a variable in C, so there's no ambiguity there. But when you do multiple assignments on one line, there is [ambiguity]. The most straight-forward way is to group multiple statements:
    Code (C):
    1. scrflaga.w = 0; scrflagb.w = 0; scrflagc.w = 0; scrflagz.w = 0;
    Or you can make it one statement by replacing end statements ( ; ) with the comma operator:
    Code (C):
    1. scrflaga.w = 0, scrflagb.w = 0, scrflagc.w = 0, scrflagz.w = 0;
    Or, if you're assigning the same value to all of them, you can chain them:
    Code (C):
    1. scrflaga.w = scrflagb.w = scrflagc.w = scrflagz.w = 0;
    It is not possible to distinguish between the first two ways. Both will generate ASM like this:
    Code (ASM):
    1. lui          v0,hi(scrflaga)
    2. sh           $0,lo(scrflaga)(v0)
    3. lui          v0,hi(scrflagb)
    4. sh           $0,lo(scrflagb)(v0)
    5. lui          v0,hi(scrflagc)
    6. sh           $0,lo(scrflagc)(v0)
    7. lui          v0,hi(scrflagz)
    8. sh           $0,lo(scrflagz)(v0)
    However, chaining assignments generates distinct ASM:
    Code (ASM):
    1. lui          v0,hi(scrflagz)
    2. sh           $0,lo(scrflagz)(v0)
    3. andi         v1,$0,0xffff
    4. lui          v0,hi(scrflagc)
    5. sh           v1,lo(scrflagc)(v0)
    6. andi         v1,v1,0xffff
    7. lui          v0,hi(scrflagb)
    8. sh           v1,lo(scrflagb)(v0)
    9. andi         v1,v1,0xffff
    10. lui          v0,hi(scrflaga)
    11. sh           v1,lo(scrflaga)(v0)
    The value is put into a register once, then the value is loaded from that register each time. (Note: MIPS has a dedicated register for the value zero, which is why it's not explicitly loaded into a register on the first assignment)

    if statements

    You can use the if statement in three ways for program flow. The most commonly used case is as follows:
    Code (C):
    1. if (d0 < 0) {
    2.   d0 *= -1;
    3. }
    However, you can achieve the same result and ASM by inverting the condition and pairing it with a goto statement:
    Code (C):
    1. if (d0 >= 0) goto label1;
    2. d0 *= -1;
    3.  
    4. label1:
    If the if statement causes execution to jump to the end of the function if the condition is evaluated to false, you can also just return:
    Code (C):
    1. if (d0 >= 0) return;
    As all of these result in the same ASM, I could only guess as to which was used.

    As C is a structured programming language, I always tried the first approach first. Then I'd see if everything fit into the space reserved for the function (as I know exactly on which line its closing brace is).

    For example, if there is only room for two lines, I can't do this:
    Code (C):
    1. function hello() {
    2.   if (plflag != 0) {
    3.     plflag = 0;
    4.   }
    5. }
    I have to write it like this:
    Code (C):
    1. function hello() {
    2.   if (plflag != 0)
    3.     plflag = 0;
    4. }
    Of course, that's assuming they didn't do it like this:
    Code (C):
    1. function hello() {
    2.   if (plflag != 0)
    3.   { plflag = 0; }
    4. }
    In large functions, it happens that there's no room for the first if statement's closing brace. In that case, I have to invert the condition and return. This is the case in patset():
    Code (C):
    1. void patset() {
    2.   /* LOCAL VARIABLES */
    3.  
    4.   if (lpKeepWork->GamePass != 0) return;
    5.  
    6.   linkdata = 0;
    7.   for (i = 0; i < 8; ++i) {
    8.  
    9.     /* SNIP */
    10.   }
    11.  
    12.   for (i = (unsigned short)linkdata; i < 80; ++i) {
    13.     EAsprset(0, 0, 0, i, 0);
    14.   }
    15. }
    The goto statement is used as a last resort, when I can't match the control flow using structured programming. The speedset function is a great example of this:
    Code (C):
    1. void speedset(sprite_status* pActwk) {
    2.   int_union xpos, ypos;
    3.   short_union spd;
    4.  
    5.   xpos.l = pActwk->xposi.l;
    6.   ypos.l = pActwk->yposi.l;
    7.   spd.w = pActwk->xspeed.w;
    8.   xpos.l += spd.w << 8;
    9.   spd.w = pActwk->yspeed.w;
    10.   if (pActwk->actfree[2] & 8) goto label2;
    11.   if (spd.w >= 0) goto label1;
    12.   if (!(pActwk->actfree[2] & 2)) goto label1;
    13.   if (pActwk->yspeed.w < -2048) goto label2;
    14. label1:
    15.   if (pActwk->actfree[2] & 4) goto label2;
    16.   pActwk->yspeed.w += 56;
    17. label2:
    18.   if (pActwk->yspeed.w < 0) goto label3;
    19.   if (pActwk->yspeed.w < 4096) goto label3;
    20.   pActwk->yspeed.w = 4096;
    21. label3:
    22.   ypos.l += spd.w << 8;
    23.   pActwk->xposi.l = xpos.l;
    24.   pActwk->yposi.l = ypos.l;
    25. }
    As you can see, there are lots of if statements that redirect execution to the same locations, and there's no room for closing braces. And even if there was, I don't think you can reproduce the same flow using nested if ... else blocks. Speaking of those...

    if ... else statements

    Yes, I'm going to talk about this compound statement separately, because different forms lead to different ASM and line numbers.

    Let's look at an example of the most common form:
    Code (C):
    1. if (pActwk->actfree[5] == knum) {
    2.  
    3.   pNewact->patno = 1;
    4. }
    5. else {
    6.   pNewact->patno = 2;
    7. }
    Thanks to the debug info, I know the if statement made use of braces. This is because the closing brace is associated with a branch instruction that jumps execution to after the else block.

    If you rewrite it like this:
    Code (C):
    1.  
    2. if (pActwk->actfree[5] == knum)
    3.   pNewact->patno = 1;
    4.  
    5. else {
    6.   pNewact->patno = 2;
    7. }
    8.  
    Then the branch instruction will be part of the "pNewact->patno = 1;" line.

    There's no ASM associated with the closing brace of the else statement. So in the case that the block contained only one statement, I tried to be consistent with the if statement, if there was space.

    Sometimes I had to squish things together like in the following example:
    Code (C):
    1. if (lenwk >= 0) {
    2.   actwk[0].actfree[1] = 0;
    3. } else {
    4.   actwk[0].actfree[1] = 128;
    5.   lenwk = -lenwk;
    6. }
    This is because there was no room to put the else statement on its own line, below the brace. If the else block had only one statement, I'd have been able to get away with this:
    Code (C):
    1. if (lenwk >= 0) {
    2.   actwk[0].actfree[1] = 0;
    3. }
    4. else actwk[0].actfree[1] = 128;
    Other flow control statements

    The principle of having ASM associated with the closing brace applies to other control statements as well, like for loops and while loops.

    Here's an example of a for loop:
    Code (C):
    1. for (i = 0; i < 128; ++i, ++pActwk) {
    2.   if (pActwk->actno) {
    3.  
    4.     act_tbl[pActwk->actno - 1](pActwk);
    5.   }
    6.  
    7. }
    If there was a brace originally, there will be ASM associated with a line below the last statement of the for loop's body. This ASM will contain the instructions for the increment step of the loop, then the instructions for the condition step, with the destination of the branch being the first statement of the body.

    If no brace was present, similar to the braceless if statement, this ASM will be associated with the sole line of the loop.

    The while loop works the same, except that it has no increment step.

    Finally, in most cases you can distinguish between for loops, while loops, and do while loops.
    • The first line of a for loop will execute the init step, then branch to the condition step.
    • A while loop will typically immediately branch to its condition logic, which is placed after the loop's body. However, it could be a for loop with an empty init step. In that case, you can recognise that it's a for loop when the closing brace is associated not only with a condition step, but an increment step as well.
    • A do while loop does not branch to its condition logic. You could be decompiling a do while loop and not notice until you come across a condition that branches back to its start. Careful, though, because in some rare cases that doesn't mean there's a loop, and what you found was a goto statement that jumps back to an earlier point in the function.

    Indentation style (braces)

    Many a holy war has been fought over where to put opening braces. Do you put them on the same line as the opening statement (K&R), or by its own on the next line (Allman)?

    As the K&R style is my preference, that's what I started out using. It also has the advantage of taking up less lines, meaning I didn't need to worry about needing a free line for the opening brace. However, there are plenty of places where there are blank lines after flow control statements that provide room for the Allman style opening brace. Opening braces never have ASM associated with them, so they could be placed wherever.

    I decided that if the Allman style could be applied to all the code in the file, that's what it would use. As a result, there are plenty of files that use the Allman style, but they are still a minority. I know it's not consistent, but multiple programmers worked on the original code, and there's no guarantee that they were consistent themselves.

    I'd also like to mention that, despite the K&R style being my preference, I preferred decompiling code that used the Allman style. Because consistent gaps in those files would tell me with some certainty where a flow control statement was used, and where an else statement was used.

    Let me show you an example from the code for the boss of Palmtree Panic:
    Code (C):
    1. int egg1_01(sprite_status* pActwk) {
    2.   scralim_right = 2752;
    3.   scralim_n_right = 2752;
    4.  
    5.   if (actwk[0].xposi.w.h < 2666) return 1;
    6.   if (actwk[0].xposi.w.h - 160 < scralim_left) return 1;
    7.  
    8.   if (actwk[0].xposi.w.h >= 2912)
    9.   {
    10.     pActwk->r_no0 = 12;
    11.     scralim_right = 2752;
    12.     scralim_n_right = 2752;
    13.     scralim_left = 2752;
    14.     scralim_n_left = 2752;
    15.   }
    16.   else
    17.   {
    18.     scralim_left = actwk[0].xposi.w.h - 160;
    19.     scralim_n_left = actwk[0].xposi.w.h - 160;
    20.   }
    21.   return 1;
    22. }
    The first if statement doesn't have a gap after it, and the code that executes if the condition evaluates to true fills in a return value, so I knew for certain this wasn't the start of a block. The second if statement does have a gap after it, but as the following code also fills in a return value, there's no block here either.

    What I really want to show is the third if statement and its accompanying else statement. Debug information shows where the statements making up the blocks are located, so there's no question about those. The closing brace of the if statement has a branch instruction associated to it, so we know it is there.

    After the closing brace, however, there are two blank lines, which perfectly fit the start of an Allman style else block, where the else keyword is on its own line, and the opening brace is on its own line.

    Conclusion

    The debug information contained in the ELF files is so detailed that it made it possible to recreate the original source code with a high degree of accuracy, but in many parts it's only an approximation.
     
    • Like Like x 3
    • Informative Informative x 1
    • List
  5. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    I've put what I've decompiled of the Windows PC version's executable so far in the WINEXE folder of the Git repo. I'm also attaching an export of all the labeling I've done. (Note: I don't know the proper way to mark functions in an export, so I've left those as "Function")

    It's getting increasingly hard to move forward without introducing even more unknowns, so if you can help, it'd be appreciated.
     

    Attached Files:

  6. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    NotABug.org is regularly unavailable for a short time. I shrugged it off because, hey, you get what you pay for, right? However, until a couple hours ago it was unavailable for at least a full day, preventing me from pushing my changes. That's not acceptable, and honestly I've had enough of the frequent downtime.

    Moving forward, the Git repository will be hosted at sourcehut. You're expected to pay, but as it's officially still in alpha (but according to them already very stable and reliable) they tolerate free use. I'm going to use it for a bit first as an evaluation and then pay the very reasonable fee of 2 bucks a month.

    I've already saved the useful wiki pages and will be hosting them at my personal website after translating them to clean HTML.
     
  7. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    I've finally made time to create a page for the project at my personal website. Aside from a short description and the current status, it has the information that used to be featured on the wiki, with some changes:
    • For the information on types, instead of listing which names were made up, I listed which were recorded in the debug information, as that list is much shorter.
    • The missing symbols were condensed where appropriate, as there was a lot of duplication.
    I didn't mention my decompilation of the Windows EXE because it's technically another project.

    While making the page I discovered that there was one data file for R3 that wasn't filled in. That has been remedied.
     
  8. Clownacy

    Clownacy

    OCs are for the weak Tech Member
    1,116
    728
    93
    Lately I've been trying to port this decompilation back to PC by making a custom executable for it. I wrote a blog post explaining some of the game's internals and how the port was done, which I have cross-posted below.

    Sonic CD received a PC port back in 1995. Like the Mega CD original, the game's code was divided into multiple modules, in this case being DLL files. This port was ported to the PlayStation 2 and GameCube as part of Sonic Gems Collection, with the DLL files becoming ELF files. Notably, these ELF files contained full DWARF debugging information, exposing a great deal of information about the game's source code as well as greatly increasing the feasibility of decompiling the code.

    BenoitRen took up the challenge of decompiling Sonic CD, eventually producing full decompilations of the game's many DLL files. However, the decompilation has yet to fully extend to the game's main executable (EXE file), as that lacks debug data unlike the game's ELF files. As a result, the decompilation remains incomplete, featuring the vast majority of the game's code, but lacking essential code that is required to make the game functional.

    As somebody who is very familiar with Sonic CD's engine, I knew that each of the DLLs contained a full copy of the game engine, meaning that the executable would only have been responsible for rudimentary IO tasks like displaying the game's sprite and tile graphical data to the user and sending keyboard inputs to the engine. With this in mind, it occurred to me that the executable may be simple enough that it would be feasible to make a replacement for it from scratch, bypassing the need to decompile it.

    Examining the decompiled DLL code revealed this to be the case: the DLLs expose a few functions and buffers to the executable (and vice versa) with an exported struct, with the functions being responsible for playing music, displaying sprites, reading files, and other tasks. In theory, all I would have to do is implement these functions, and the DLLs would work, allowing the game to be played.

    To begin, I downloaded the decompilation and wrote a small CMake build script, along with a custom 'MAIN.C' file with a dummy 'main' function inside it. I began by compiling just the 'MAIN.C' and 'GAME.C' files, and then added other source files to address any 'symbol undefined' errors that appeared. All DLLs share many source files, but some files are specific to certain DLLs. Because of this, when given a choice, I opted to use source files from the 'R1' directory, which contains source files that are exclusive to the DLL of the first level, Palmtree Panic.

    The game's code was written for compilers from the 1990s and early 2000s, so it does not compile without errors with modern Clang. In particular, there were errors about initialising flexible array members as well as functions that were declared as global being defined as static. The former was solved by changing the flexible array members to arrays with a size of 0 (a common GNU extension, I believe), and the latter was solved by correctly declaring the functions as static. The code for Amy (or 'Emie', as the code calls her) also contained a static function that shared a name with a global function in another source file; to resolve this error, I simply renamed the static function so that it had a unique name. Additionally, the decompiled code is incompatible with 64-bit architectures due to its use of hardcoded struct offsets, so the code needed to be compiled into a 32-bit executable instead in order for it to avoid corrupting its own memory.

    Eventually I had the executable successfully building. Running it, of course, did nothing as the 'main' function did nothing. As the next step, I began implementing the many functions that the DLL expected from the executable, which included...
    • Writing a tile to one of two tile grids (these are analogous to the two planes of the Mega Drive's Video Display Processor).
    • Displaying a sprite at a given coordinate with a given orientation.
    • Playing music and sounds.
    • Bulk-filling memory (basically C's 'memset' function).
    • Bulk-copying memory (basically C's 'memcpy' function).
    • Generating a random function (basically C's 'rand' function).
    • Printing a message to a buffer using a formatted string (basically C's 'sprintf' function).
    • Printing a message to a debug log (basically C's 'puts' function).
    • Opening a file (basically C's 'fopen' function).
    • Reading a file (basically C's 'fread' function).
    • Closing a file (basically C's 'fclose' function).

    Some of these implementations were mere stubs (i.e. they were empty functions that did nothing), but others, such as the memory filling/copying functions as well as the file IO functions, were fully-implemented.

    With the functions rigged-up and the necessary buffers allocated, I was able to run the game and see it print a few error messages to the debug console about some files being missing.

    [​IMG]
    Progress!

    The next step was getting the files that the game expected. This was simple enough: the PS2 version of Sonic Gems Collection can be dumped to an ISO file, which can be opened with 7zip; once opened, inside the 'SonicCD' folder can be found a file called 'SonicCD.CVM'; this file can also be opened in 7zip, revealing it to contain the files that the game needs; these files can then be extracted to complete the process.

    With the files obtained, the game would now successfully load them and then silently run indefinitely.

    [​IMG]
    Progress?

    At this point, relying on just the terminal to see what the game is doing had reached its limit, which meant that it was time to display some actual graphics instead!

    To begin with, I made the executable use the SDL2 library to create a window. I then made the sprite-drawing function draw some red rectangles at the location of the sprites. This was the result:

    [​IMG]

    The game appeared to be trying to draw something, but I could not make out what it was. So, I began to implement code for displaying the sprites properly. This proved to be more complicated than I expected:

    In the original Mega CD version of Sonic CD, each module (DLL) contained its own copy of the sprite data and code. In the PC port, however, the sprite code was completely rewritten and moved to the executable instead. Likewise, the sprite data was converted to an entirely new format and stored separately as a file instead. When the DLL attempts to draw a sprite, it does so by simply specifying the sprite's numerical ID code. Because of all of this, it is up to the executable to load the sprite data and associate each sprite with the correct ID.

    The Sonic Community Hacking Guide contains extremely helpful documentation of the game's sprite data: it is stored as a series of 4-bit bitmaps packed into a single file along with metadata, which is compressed using DOS's 'COMPRESS.EXE' tool. Coincidentally, this tool appears to use the same LZSS variant that Sonic's Saxman compression is based on - Okumura's LZSS. Small world.

    With code written to load this file and display its contents (albeit with a placeholder colour palette), I ran the executable again and was greeted by this:

    [​IMG]

    It worked: sprites were being displayed!

    After some bug-fixing, it became clearer what the game was trying to display:

    [​IMG]

    Sonic was spawning and immediately falling to his death! This was unusual, as Sonic is supposed to be standing on solid ground. This had me thinking that the game was loading the level data incorrectly. I noticed that the debug messages mentioned loading data for '11B', which is not the correct level; it should be '11A'. This turned out to be related to one of those exposed buffers, which is a struct called 'game_info', inside which there is a variable called 'time_flag'. This variable needed to be set to 1 instead of 0 in order for the game to load '11A' instead of '11B'. With this done, Sonic no longer fell to his death.

    [​IMG]
    He lives, for now.

    While this was cool to look at, I wanted to be able to move Sonic around and explore the level (even if it was completely invisible due to only sprites currently being able to render). To do this, I needed to be able to ferry keyboard inputs to the game engine. This is done via the DLL's exposed 'SWdataSet' function, which accepts two bit-fields. It appears that the second bit-field is for a second controller, but I am not aware of Sonic CD ever using a second controller. Perhaps this is a leftover from that early Sonic CD prototype which had a two-player mode. Anyway, with some trial-and-error, I was able to figure out which bits in the bit-fields corresponded to which buttons on a Mega Drive controller, allowing me to rig-up the movement and jump actions. With this, I could navigate Sonic around a small section of the level! Unfortunately, the decompilation appears to suffer from some inaccuracies, including the first obstacle in the level being impossible to pass, restricting Sonic to the first section of the level.

    [​IMG]

    And that is where the project currently is. I am surprised to have gotten this far after only two days. It would be nice to polish this further and get Sonic CD to be truly playable, but between the Mega CD version being simple enough to emulate, and the 2011 remaster of the game also existing (as well as having its own decompilation), there is not a huge need for this project to exist beyond being a fun novelty.

    The source code to the decompilation and my custom executable are available here.
     
    Last edited: Nov 6, 2024
    • Like Like x 9
    • Informative Informative x 1
    • List
  9. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    Great to see someone else make an attempt at getting this to run!

    Flexible array members are legal as of C99 by not specifying an array size, yet for some reason neither MinGW nor MWCC accept that for the array containing the list of objects for edit/debug mode.

    As for static versus global, my understanding is that you can have one static and one global function with the same name, as they're in different scopes. Has that changed?

    Finally, it is definitely possible that the decompilation has some inaccuracies left. Barring four inconsequential differences, the code compiles to the same MIPS assembly, but I was not able to match global variable addresses. Which means some writes or reads could be incorrect.
     
    Last edited: Nov 6, 2024
    • Informative Informative x 1
    • List
  10. Clownacy

    Clownacy

    OCs are for the weak Tech Member
    1,116
    728
    93
    In my case I'm using MSYS2's Clang compiler. The compiler errors about flexible array members have to do with initialising a flexible array member within a union.

    The static versus global problem has to do with 'EMIE.C' including 'ACTION.H', causing both the global and static versions of the function to be declared in the same scope.
     
  11. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    I'm back to decompiling SONICCD.EXE! So what if I don't understand the DINO2D graphics calls? There's still everything else to uncover!

    Currently decompiling the function that's responsible for reading player input. It seems to build a bitfield variable of the same format as the Sonic games. Interestingly, despite only having one button (jump) in the game, it reads input for both A and B buttons.
     
  12. Blastfrog

    Blastfrog

    See ya starside. Member
    Perhaps for menu use?
     
  13. Blue Spikeball

    Blue Spikeball

    Member
    2,593
    1,086
    93
    Pretty much. IIRC spacebar is A/confirm and escape is B/back, making it intuitive in menus.

    Also for debug mode. Here the c key is C/place object (again, IIRC). Space/escape/c is less intuitive in debug mode, but still functional.
     
  14. Ch1pper

    Ch1pper

    Fighting the Battle of Who Could Care Less Member
    865
    115
    43
    Life.
    Per the game's Hidden Content page:
     
    • Like Like x 3
    • Informative Informative x 2
    • List
  15. Bobblen

    Bobblen

    Member
    452
    232
    43
    There's a question. Does the title screen cloud debug code exist in the PC version? Presumably player 2 inputs are not mapped to the keyboard so it wouldn't work without modification even if the game accepts the code.
     
  16. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    With help from Chippy, a fellow reverse engineer, I've started properly decompiling SONICCD.EXE. You can follow the progress on decomp.me.

    The program seems to have been compiled using Microsoft Visual Studio C++ 4.2. What I didn't expect was that the length of local variable names affects where they are put on the stack. It makes it a challenge to match the original assembly at times.

    The first part of the executable contains functions related to CD audio playback, followed by some dummy assembly, so I've taken that to mean it's the end of a source file, put it all in CDAUDIO.C.
     
    • Like Like x 3
    • Informative Informative x 2
    • List
  17. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    Decompilation is still on-going. A lot of graphics-related functions have been decompiled, and some global variables have been documented.

    I've also had to look a bit more into both RDX/DINO DLLs, and can now confirm that DirectX is used. It looks like instead of converting Sonic CD to use DirectX directly, they still use the RDX libraries, which have been upgraded to use DirectX under the hood instead of DINO's own routines.
     
    • Informative Informative x 4
    • List
  18. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    I've now decompiled all the code related to graphics operations. However, there are more than 10 functions where I wasn't able to match the original assembly exactly. I also have to wonder why some variables are a union type that allows them to be used either like a 4-byte integer or a 2-byte short.

    Needless to say, without symbols or RDX documentation, the bulk of the code is a big question mark.
     
  19. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    WINMAIN.C is now available! This houses the program's entrypoint (WinMain), the windows procedure (WndProc), and core logic.
     
  20. BenoitRen

    BenoitRen

    Tech Member
    861
    468
    63
    Turns out the next file was a short one! Say hello to DECOMPRESS.C.

    It has three functions: LZ decompression (using the Windows API), 4-bit bitmap conversion, and some other conversion that's done after the previous one that I haven't figured out yet.