don't click here

Sonic CD decompilation

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

  1. Devon

    Devon

    Please do not contact me, overwhelmed with stuff Tech Member
    1,522
    1,877
    93
    your mom
    I described what the object was in the quirks thread. It used to have code in the prototype.
     
  2. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    Sorry, I was under the impression that the evidence was inconclusive, given that we don't even have an object ID.
     
  3. Devon

    Devon

    Please do not contact me, overwhelmed with stuff Tech Member
    1,522
    1,877
    93
    your mom
    That object is defined in the object index at ID 0x2B in the Sega CD version, at least.
     
  4. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    All the source code related to R4, Tidal Tempest, has been committed!

    Each zone has more files to decompile. R1 has 66 files. R3 has 76 files. R4 has a whopping 101 files! In the case of Tidal Tempest, it's not only because of more objects, but also because it has water.

    The zones usually share a GAME.C file which contains all of the initialisation code, and a PLAYSUB.C file that contains code for common zone-related objects like sign posts and flowers. R4, however, has its own copy of these to have custom code for water.

    R4 also has its own version of SCRCHK.C, but only for the first act's present.

    Now, as for my nemesis, scrolling code...

    R3's files for scrolling management were largely identical. It's mostly the scroll() function that differed each time. Thankfully, whoever did R4 did some optimisation.

    The scroll() function used to be a 200-line monster. Now, it's 70 lines for the first act's present, 50 lines for the rest, and most of the time it's identical. Some files were even merged; R42A and R42B, and R42C and R42D, share the same scrolling file. They could have gone even further, as the files are still largely identical, but for some reason they didn't.
    Wait, you mean the position in the index, not its "actno"? I took a look, and it's indeed in the index at position 43 (0x2B)!
     
  5. Devon

    Devon

    Please do not contact me, overwhelmed with stuff Tech Member
    1,522
    1,877
    93
    your mom
    It is this object. Basically, 3 of these teleporters circle around, and when Sonic enters one, he gets teleported to a nearby teleporter. Again, it existed in the 510 prototype, but ultimately got removed, with its graphics being left behind.

    [​IMG]

    (With graphical fix)

    [​IMG]

    Here's a disassembly of the object from the prototype, done in the style of the source code/decompilation:
    Code (ASM):
    1. ; -------------------------------------------------------------------------
    2. ; Teleporter object
    3. ; -------------------------------------------------------------------------
    4.  
    5. miracle:
    6.         moveq   #0,d0                           ; Run routine
    7.         move.b  r_no0(a0),d0
    8.         move.w  miracle_move_tbl(pc,d0.w),d0
    9.         jsr     miracle_move_tbl(pc,d0.w)
    10.  
    11.         addq.w  #1,actfree+4(a0)                ; Get sine and cosine of angle
    12.         move.w  actfree+4(a0),d0
    13.         jsr     sinset
    14.         asr.w   #1,d1
    15.         asr.w   #1,d0
    16.         addq.w  #8,d1
    17.         addq.w  #8,d0
    18.         asr.w   #1,d1
    19.         asr.w   #1,d0
    20.         add.w   actfree(a0),d1                  ; Add origin point
    21.         add.w   actfree+2(a0),d0
    22.         move.w  d1,xposi(a0)                    ; Set position
    23.         move.w  d0,yposi(a0)
    24.  
    25.         movea.w actfree+6(a0),a1                ; Has one of the teleporters despawned?
    26.         cmpi.b  #$2B,actno(a1)
    27.         beq.w   .check2                         ; If not, branch
    28.         jmp     frameout                        ; If so, delete ourselves
    29.  
    30. .check2:
    31.         movea.w actfree+8(a0),a1                ; Has one of the teleporters despawned?
    32.         cmpi.b  #$2B,actno(a1)
    33.         beq.w   .draw                           ; If not, branch
    34.         jmp     frameout                        ; If so, delete ourselves
    35.  
    36. .draw:
    37.         jsr     actionsub                       ; Draw sprite
    38.         move.w  actfree+$14(a0),d0              ; Check despawn
    39.         jmp     frameout_s00
    40.  
    41. ; -------------------------------------------------------------------------
    42.  
    43. miracle_move_tbl:
    44.         dc.w    miracle_init-miracle_move_tbl
    45.         dc.w    miracle_checkcoli-miracle_move_tbl
    46.         dc.w    miracle_reset-miracle_move_tbl
    47.         dc.w    miracle_release-miracle_move_tbl
    48.         dc.w    miracle_flash0-miracle_move_tbl
    49.         dc.w    miracle_flash1-miracle_move_tbl
    50.         dc.w    miracle_flash2-miracle_move_tbl
    51.         dc.w    miracle_settarget-miracle_move_tbl
    52.  
    53. ; -------------------------------------------------------------------------
    54. ; Initialize
    55. ; -------------------------------------------------------------------------
    56.  
    57. miracle_init:
    58.         jsr     actwkchk                        ; Allocate object slot
    59.         beq.w   .spawn3                         ; If it was successful, branch
    60.         lea     (a0),a1                         ; If it wasn't, just use ourselves
    61.  
    62. .spawn3:
    63.         move.b  actno(a0),actno(a1)             ; Set object ID
    64.         lea     (a1),a2                         ; Save object slot pointer
    65.  
    66.         jsr     actwkchk                        ; Allocate object slot
    67.         beq.w   .spawn2                         ; If it was successful, branch
    68.         lea     (a0),a1                         ; If it wasn't, just use ourselves
    69.  
    70. .spawn2:
    71.         move.b  actno(a0),actno(a1)             ; Set object ID
    72.  
    73.         move.w  #0,actfree+4(a0)                ; Set first teleporter's angle
    74.         move.w  a1,actfree+6(a0)                ; Set teleporter object links
    75.         move.w  a2,actfree+8(a0)
    76.  
    77.         move.w  #$55,actfree+4(a1)              ; Set second teleporter's angle
    78.         move.w  a0,actfree+6(a1)                ; Set teleporter object links
    79.         move.w  a2,actfree+8(a1)
    80.  
    81.         move.w  #$AA,actfree+4(a2)              ; Set third teleporter's angle
    82.         move.w  a0,actfree+6(a2)                ; Set teleporter object links
    83.         move.w  a1,actfree+8(a2)
    84.  
    85.         lea     (a0),a6                         ; Set up first teleporter
    86.         bsr.w   miracle_setup
    87.         lea     (a1),a6                         ; Set up second teleporter
    88.         bsr.w   miracle_setup
    89.         lea     (a2),a6                         ; Set up third teleporter
    90.  
    91. ; -------------------------------------------------------------------------
    92. ; Set up teleporter
    93. ; -------------------------------------------------------------------------
    94.  
    95. miracle_setup:
    96.         ori.b   #4,actflg(a6)                   ; Set sprite flags
    97.         move.b  #3,sprpri(a6)                   ; Set priority
    98.         move.b  #24,sprhs(a6)                   ; Set sprite width
    99.         move.b  #16,sprvsize(a6)                ; Set sprite height
    100.         move.l  #miracle_pat,patbase(a6)        ; Set sprite data
    101.         move.b  #2,r_no0(a6)                    ; Advance routine
    102.  
    103.         move.w  #$3AF,d0                        ; Base tile ID
    104.         cmpi.b  #2,stageno                      ; Are we in act 3? (BUG: should be "stageno+1")
    105.         bne.s   .set_tile                       ; If not, branch
    106.         move.w  #$41C,d0                        ; If so, use other base tile ID
    107.  
    108. .set_tile:
    109.         move.w  d0,sproffset(a6)
    110.  
    111.         move.w  xposi(a0),actfree+$14(a6)       ; Set despawn check position
    112.         move.w  xposi(a0),actfree(a6)           ; Set origin point X
    113.         move.w  yposi(a0),actfree+2(a6)         ; Set origin point Y
    114.         rts
    115.  
    116. ; -------------------------------------------------------------------------
    117. ; Check collision with the player
    118. ; -------------------------------------------------------------------------
    119.  
    120. miracle_checkcoli:
    121.         lea     actwk,a6                        ; Check collision with player 1
    122.         bsr.w   miracle_coli
    123.         tst.w   actfree+$A(a0)                  ; Has there been a collision?
    124.         bne.w   .collided                       ; If so, branch
    125.  
    126.         lea     actwk+$40,a6                    ; Check collision with player 2
    127.         bsr.w   miracle_coli
    128.         tst.w   actfree+$A(a0)                  ; Has there been a collision?
    129.         bne.w   .collided                       ; If so, branch
    130.         rts
    131.  
    132. .collided:
    133.         move.b  #8,r_no0(a0)                    ; Start flashing
    134.         move.w  #21,actfree+$C(a0)              ; Set flash timer
    135.  
    136.         movea.w actfree+6(a0),a1                ; Disable collision in the second teleporter
    137.         move.b  #4,r_no0(a1)
    138.         move.w  #0,actfree+$C(a1)
    139.  
    140.         movea.w actfree+8(a0),a1                ; Disable collision in the third teleporter
    141.         move.b  #4,r_no0(a1)
    142.         move.w  #0,actfree+$C(a1)
    143.         rts
    144.  
    145. ; -------------------------------------------------------------------------
    146.  
    147. miracle_coli:
    148.         tst.w   yspeed(a6)                      ; Is the player moving upwards?
    149.         bmi.w   .end                            ; If so, branch
    150.  
    151.         move.w  yposi(a6),d0                    ; Check vertical collision
    152.         sub.w   yposi(a0),d0
    153.         subi.w  #-24,d0
    154.         subi.w  #16,d0
    155.         bcc.w   .end                            ; If there was no collision, branch
    156.  
    157.         move.w  xposi(a6),d0                    ; Check horizontal collision
    158.         sub.w   xposi(a0),d0
    159.         subi.w  #-16,d0
    160.         subi.w  #32,d0
    161.         bcc.w   .end                            ; If there was no collision, branch
    162.  
    163.         move.w  a6,actfree+$A(a0)               ; Set link to player object
    164.         bset    #0,actfree+2(a6)                ; Shift downwards to next odd pixel
    165.  
    166. .end:
    167.         rts
    168.  
    169. ; -------------------------------------------------------------------------
    170. ; Check reset
    171. ; -------------------------------------------------------------------------
    172.  
    173. miracle_reset:
    174.         addi.w  #-1,actfree+$C(a0)              ; Decrement wait timer
    175.         bne.w   .end                            ; If it hasn't run out, branch
    176.  
    177.         move.b  #2,r_no0(a0)                    ; Enable collision in first teleporter
    178.         movea.w actfree+6(a0),a1                ; Enable collision in second teleporter
    179.         move.b  #2,r_no0(a1)
    180.         movea.w actfree+8(a0),a1                ; Enable collision in third teleporter
    181.         move.b  #2,r_no0(a1)
    182.  
    183. .end:
    184.         rts
    185.  
    186. ; -------------------------------------------------------------------------
    187. ; Release the player
    188. ; -------------------------------------------------------------------------
    189.  
    190. miracle_release:
    191.         addi.w  #-1,actfree+$C(a0)              ; Decrement wait timer
    192.         bne.w   .flash                          ; If it hasn't run out, branch
    193.  
    194.         move.b  #4,r_no0(a0)                    ; Set reset time
    195.         move.w  #61,actfree+$C(a0)
    196.         move.b  #0,patno(a0)                    ; Stop flashing
    197.  
    198.         movea.w actfree+$A(a0),a1               ; Get player object
    199.         move.w  #0,actfree+$A(a0)               ; Unlink it
    200.  
    201.         bset    #2,cddat(a1)                    ; Set player's rolling flag
    202.         move.w  #0,xspeed(a1)                   ; Halt the player horizontally
    203.         move.w  #-$700,yspeed(a1)               ; Make the player jump up
    204.  
    205.         move.w  xposi(a0),xposi(a1)             ; Move player to us
    206.         move.w  yposi(a0),d0
    207.         addi.w  #-$10,d0
    208.         move.w  d0,yposi(a1)
    209.  
    210.         bclr    #0,actfree+2(a1)                ; Shift upwards to previous even pixel
    211.         rts
    212.  
    213. .flash:
    214.         lea     miracle_pchg(pc),a1             ; Flash sprite
    215.         jmp     patchg
    216.  
    217. ; -------------------------------------------------------------------------
    218. ; Flash #1
    219. ; -------------------------------------------------------------------------
    220.  
    221. miracle_flash0:
    222.         addi.w  #-1,actfree+$C(a0)              ; Decrement wait timer
    223.         bne.w   .flash                          ; If it hasn't run out, branch
    224.  
    225.         addq.b  #2,r_no0(a0)                    ; Advance routine
    226.         move.w  #21,actfree+$C(a0)              ; Set flash timer
    227.  
    228. .flash:
    229.         lea     miracle_pchg(pc),a1             ; Flash sprite
    230.         jmp     patchg
    231.  
    232. ; -------------------------------------------------------------------------
    233. ; Flash #2
    234. ; -------------------------------------------------------------------------
    235.  
    236. miracle_flash1:
    237.         addi.w  #-1,actfree+$C(a0)              ; Decrement wait timer
    238.         bne.w   .flash                          ; If it hasn't run out, branch
    239.  
    240.         addq.b  #2,r_no0(a0)                    ; Advance routine
    241.         move.w  #21,actfree+$C(a0)              ; Set flash timer
    242.  
    243. .flash:
    244.         lea     miracle_pchg(pc),a1             ; Flash sprite
    245.         jmp     patchg
    246.  
    247. ; -------------------------------------------------------------------------
    248. ; Flash #3
    249. ; -------------------------------------------------------------------------
    250.  
    251. miracle_flash2:
    252.         addi.w  #-1,actfree+$C(a0)              ; Decrement wait timer
    253.         bne.w   .flash                          ; If it hasn't run out, branch
    254.  
    255.         addq.b  #2,r_no0(a0)                    ; Advance routine
    256.         move.w  #21,actfree+$C(a0)              ; Set flash timer
    257.  
    258. .flash:
    259.         lea     miracle_pchg(pc),a1             ; Flash sprite
    260.         jmp     patchg
    261.  
    262. ; -------------------------------------------------------------------------
    263. ; Set teleportation targer
    264. ; -------------------------------------------------------------------------
    265.  
    266. miracle_settarget:
    267.         addi.w  #-1,actfree+$C(a0)              ; Decrement wait timer
    268.         bne.w   .flash                          ; If it hasn't run out, branch
    269.  
    270.         move.b  #4,r_no0(a0)                    ; Deactivate
    271.         move.w  #0,actfree+$C(a0)
    272.         move.b  #0,patno(a0)
    273.  
    274.         movea.w actfree+6(a0),a1                ; Set target to the second teleporter
    275.         movea.w actfree+8(a0),a2                ; Get third teleporter
    276.         tst.b   ranum                           ; Should we randomly switch to the third teleporter?
    277.         bpl.w   .set_target                     ; If not, branch
    278.         exg     a1,a2                           ; Set target to the third teleporter
    279.  
    280. .set_target:
    281.         move.w  actfree+$A(a0),actfree+$A(a1)   ; Set player object link in target
    282.         move.w  #0,actfree+$A(a0)               ; Unlink player object from us
    283.         move.b  #6,r_no0(a1)                    ; Flash and release the player from the target teleporter
    284.         move.w  #61,actfree+$C(a1)
    285.         rts
    286.  
    287. .flash:
    288.         lea     miracle_pchg(pc),a1             ; Flash sprite
    289.         jmp     patchg
    290.  
    291. ; -------------------------------------------------------------------------
    292. ; Animation data
    293. ; -------------------------------------------------------------------------
    294.  
    295. miracle_pchg:
    296.         dc.w    miracle_pchg0-miracle_pchg
    297.  
    298. miracle_pchg0:
    299.         dc.b    3
    300.         dc.b    0, 1, $FF
    301.         even
    302.  
    303. ; -------------------------------------------------------------------------
    304. ; Sprite data
    305. ; -------------------------------------------------------------------------
    306.  
    307. miracle_pat:
    308.         dc.w    miracle_00-miracle_pat
    309.         dc.w    miracle_01-miracle_pat
    310.  
    311. miracle_00:
    312.         dc.b    2
    313.         dc.b    $F0, $B, 0, 0, $E8
    314.         dc.b    $F0, $B, 8, 0, 0
    315.  
    316. miracle_01:
    317.         dc.b    4
    318.         dc.b    0, 1, 0, $C, $F8
    319.         dc.b    0, 1, 8, $C, 0
    320.         dc.b    $F0, $B, 0, 0, $E8
    321.         dc.b    $F0, $B, 8, 0, 0
    322.         even
    323.  
    324. ; -------------------------------------------------------------------------

    Also, I would like to shed some light on the "sprite status table" in the wiki section.

    "cddat" is another set of flags that are pretty much object specific, unlike "actflg" which holds flags for more general object stuff. Objects do tend to use it to handle sprite flipping if it uses the animation system, as it is possible to also set flipping with each sprite defined in an animation, and Sonic himself uses it, because sprite flipping is utilized for displaying the correct angled running sprite on the ground. Outside of that, Sonic, for example, uses this to keep track of if he's in the air, if he's in a rolling state, if he's pushing, if he's standing on an object, and if he jumped while he was rolling on the ground (used to locked his controls in the air in the other Genesis games to get around an issue with the speed cap in the air that rolling speeds can get cancelled out by).

    "cdsts" is the index to its entry in the global object status table. If it's 0, then it has no entry. The object status table is mainly used to manage object respawning. Bit 7 is set when an object in the stage is spawned, which prevents it from being respawned until it gets cleared. An object like a monitor also utilizes it to keep track of whether it was destroyed or not. Enemies will clear bit 7 if they have gone far enough off screen to despawn, so that they can spawn again. When they get destroyed, bit 7 will remain set to keep them from respawning.

    "userflag" is basically the object's subtype ID/properties that are set per object in the stage layout (set by the "user" of the stage editor). In the other Genesis games, this is 1 byte, but in Sonic CD, it's 2 bytes. An example of this is having a flag that determines if an enemy should be broken/aged or not, or what kind of item a monitor should have.
     
    Last edited: Mar 6, 2024
    • Like Like x 3
    • Informative Informative x 1
    • List
  6. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    I've whined mentioned before that the ASM produced by the PS2 compiler has a lot of small differences with the original ASM. Instead of being a configuration issue, it is possible that I have the wrong version of the compiler.

    The disassembled ELF files's debug info mentions that it was compiled with Metrowerks CodeWarrior version 2.4.1.01. If I use the version found on archive.org to compile the decompiled code, that same version string is inserted into the result. However, if you launch it on the command line without any arguments and actually look at the compiler info, it says... 3.0.1:
    This suggests there's a discrepancy between the version inserted into the debug info and the actual version of the compiler used.

    I hope to find the actual compiler used in the near future.
     
    • Informative Informative x 2
    • List
  7. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    Today's the project's first anniversary! The first commit to my local SVN repository was exactly one year ago.

    The following has been decompiled the past year:
    • core/shared code
    • Palmtree Panic
    • Collision Chaos
    • Tidal Tempest
    • Special Stage
    • AVIGOOD (ending movie code)
    • AVIOPEN (opening movie code)
    • Best Time
    • Title screen
    • D.A. Garden
    • Save Data
    • Sound Test
    • Stage Select
    • Time Attack
    • THANKS (post-credits scene)
    • Visual Mode
    The following is what remains to be decompiled:
    • Quartz Quadrant
    • Wacky Workbench
    • Stardust Speedway
    • Metallic Madness
    There's something missing, though: the control program that ties all of this together. That doesn't have debug info, as far as I know, so it can't easily be decompiled. I'll have to look deeper into this so I can have something playable to show.

    As for the compiler issue I mentioned last month: it turns out that several versions of CodeWarrior output 2.4.1.01 as the version string in the ELF file. The most recent one to do so is CodeWarrior 3.0.4. A preview version of this one is available, and it seems to fix most ASM discrepancies. The main discrepancy still present is the ASM generated for the initialisation of local arrays, which I don't know yet how to solve. A bug-fixed version of the compiler shipped with 3.0.4 is part of the CodeWarrior 3.5 package, but that also has this discrepancy, so it's most likely due to the decompiled code.
     
  8. Devon

    Devon

    Please do not contact me, overwhelmed with stuff Tech Member
    1,522
    1,877
    93
    your mom
    I just wanna say that I'm super happy about the progress made after I managed to dump the remaining missing symbols, on top of all the additional knowledge we gained about the original source code. Definitely would also be cool to see this ported to other platforms (especially modern Windows) as an alternative to the 2011 remake for a more "authentic" experience in regards the original Sega CD version... with some bug fixes of course.
     
    Last edited: Apr 8, 2024
  9. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    I focused on my C port of Sonic & Knuckles Collection in March and April, hoping in the meantime that I'd find the version of the compiler that was used. I did try to find the control program used for the game, but haven't found it.

    When I got fired near the end of April I figured I'd use the extra time to decompile another zone. It's almost done, but, as always, the copy of the scrolling code for each act version takes time.

    Now, let's move on to the reason I'm writing this update.

    I mentioned that there seems to be a discrepancy between the version inserted into the debug info and the actual version of the compiler used.

    The version number given on the command line by the compiler seems to vary on the environment, and it's not clear where it gets it from. The only thing you can rely on is the build date.

    A fellow reverse engineer, who's also decompiling a game compiled with Metrowerks CodeWarriors, created a list of CodeWarrior releases to aid in finding the right version. As the debug info contained version 2.4.1.01 in the comment section, I narrowed my search to compilers inserting that version string. According to the list, this meant a compiler as old as the one shipped with CodeWarrior R2.5, and not newer than the one shipped with CodeWarrior R3.04.

    Like I said before, the output from the version shipped with CodeWarrior R3.04 is closer to the original, but it's not there yet.

    Then, yesterday evening, it was revealed to me that, while the compilers that ship with CodeWarrior R3.5 and newer do emit 3.0.0 to the debug info's comment section, the linker overrides this with 2.4.1.01! In other words, one of the newer compilers could also have been the one used to compile the original ASM!

    I tested the newer compilers. Their output is almost identical to the original. The only remaining difference is that in 1% of the output they use a different register. Most of the time this means it uses $v0 or $v1 instead of $at.

    I went back to WARP.ELF, and managed to make my version's main (code) section 99% identical (if you disregard the memory addresses). Next, I got the data section identical save for a couple numeric identifiers. Memory addresses are still off because in memory the global data seems to start at another location. I suspect it requires tweaking the linker command file, which I know little about, so I'm putting that off.

    I'm already reviewing the core files used by R5, and will resume my R5 work after that. You can expect a release of R5's code soon!
     
  10. Devon

    Devon

    Please do not contact me, overwhelmed with stuff Tech Member
    1,522
    1,877
    93
    your mom
    I took a look at the "missing symbols" page again, and I want to point out that tagPALETTEENTRY and tagPOINT come from the Win32 API.
     
    • Informative Informative x 5
    • Agree Agree x 1
    • List
  11. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    All the source code related to R5, Quartz Quadrant, has been committed!

    I've found the cause of the remaining 1% of differences, which means that this is the first zone to compile to identical ASM! Except for data offsets, of course.

    This zone had less code than previous ones, which is why I was able to decompile it in a month despite also working on other things for a part of that. The size of the boss code, however, is second only to the first zone's, as that easy running challenge consists of more than a couple parts.
     
  12. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    I've gone back through the code of the previous zones I've decompiled, and corrected those until they also matched the original ASM. This was mostly successful.

    I say "mostly", because there's one file of R4 where the decompiled C code generates one less instruction than the original. It's not an important instruction (it's actually superfluous), but ideally I'd also like to match it.

    Then there's some good news and bad news.

    The good news: I've found the control program that ties all of the ELF files together. It's S1.DAT in the root of the disc.

    The bad news: it has no symbols whatsoever, and I know nothing about PS2 programming that would enable me to reverse engineer it. It does have some debug messages, which enabled me to identify some functions, but that's pretty much it.
     
    • Like Like x 3
    • Informative Informative x 2
    • List
  13. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    Everything under TITLE (title screen, Sound Test, D.A. Garden, etc.) has been checked again and corrected until it matched 99% of the original ASM. Not 100%, because the main source file of Time Attack doesn't match one superfluous instruction. Yes, it's the same case as for R4...

    Also checked WARP again, corrected it, and it matches 100%.

    Now that that's out of the way, I can get back to decompiling zones. Or rounds, as the game calls them.
     
  14. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    All the source code related to R6, Wacky Workbench, has been committed!

    This only took two weeks due to two factors:
    • I could copy/paste most of the scrolling code from R5, and then between R6 versions
    • I made maximum use of my jobless free time to decompile like a madman. On my most productive day I processed 18 files.

    This zone uses its own version of the PLAYER object that seems to remove some sound-related code, and adds some functions. One of those functions I can't match 100% because one variable is put into a register instead of on the stack, and I can't figure out why.

    Two more zones to go.
     
  15. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    All the source code related to R7, Stardust Speedway, has been committed! Took me less than two weeks!

    In other news, I recently revamped my ASM differences wiki page, detailing the few differences that are left, along with links to decomp.me scratches. If you're feeling brave, feel free to play around with those to try to match the original assembly.
     
  16. Rrose80149

    Rrose80149

    Member
    93
    19
    8
    Only 1 more Zone left to go!
     
  17. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    All the source code related to R8, Metallic Madness, has been committed! This means that everything we have debug symbols for has been decompiled.

    Now we need to somehow recreate the game's engine that is responsible for loading files, drawing to the screen, processing input, etc. We could decompile the Gems version, but without debug symbols, it won't be easy. I wonder if the PC version's executable would be more manageable to dissect.

    I'll do a technical explanation on the authenticity of the decompiled code later.
     
  18. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    I'm still working on the technical explanation referred to earlier, but I need more time as it's going to be pretty in-depth and I want to explain things clearly.

    In the meantime, I've been looking into the engine.

    The PS2 version of the engine is pretty impenetrable without debug symbols for someone not familiar with PS2 programming, so I gave up on that and turned my attention to the PC version's executable.

    Being familiar with Windows programming, and Ghidra being well-suited to dissecting Windows executables, I'm pretty far into decompiling it. While I'm still far from done, I got most of the initialisation code covered, and would like to share my findings.

    The Dino survives

    It is said that the 1995 OEM release of the game used Intel's Dino libraries to abstract multimedia features, and that the 1996 retail release replaced Dino in favor of DirectX. However, this doesn't seem to be entirely correct.

    Even without diving into the executable, this should be apparent. The game disc indeed has a folder named DIRECTX, but it also has a folder named RDX. This is short for "Realistic Display miXer", the official name of the graphics library that's part of Dino. In this folder, you can find the files DINO2D.DLL and DMIX.DLL.

    Decompiling the executable, I noticed pretty quickly that all function calls related for sound were done to MCI (Multimedia Control Interface), a high-level multimedia API created by Microsoft and IBM and implemented into Windows 95. The original release used Dino's RSX (Realistic Sound Experience).

    However, graphics operations are still handled by RDX. For example, the function to create a bitmap surface is srfCreate, and this function lives in... DMIX.DLL.

    At this point it's not clear what part of the program uses DirectX.

    A Windows 3.x program?

    While decompiling the PS2 version, I noticed that the functions LibMain and WEP were stubbed, so I had to go looking for their signature. They weren't easy to find, because it turns out that these are 16-bit DLL functions. Both of these were replaced by DllMain in 32-bit Windows.

    But that's not all.

    Decompiling the PC executable reveals that it uses file handling API functions that were deprecated with the introduction of Windows 95. They continued to exist in 32-bit Windows for compatibility with 16-bit programs. They are: _lopen (instead of CreateFile), _hread (instead of ReadFile), and _lclose (instead of CloseHandle).

    Initialisation check oddities

    Those of us who used to play the PC release are no doubt familiar with the program checking that our display setting is 256 colours. But there's more:
    • It checks the computer's Windows version. If it's too low, a message pops up that says: "You must have a Windows® 95 inside your system to run Sonic the Hedgehog CD.".
    • At one point the plan was to check that your computer had a Pentium processor. However, this function is stubbed and always returns TRUE. If the check failed, it'd have said: "You must have a Pentium® Processer (or better) inside your system to run Sonic the Hedgehog CD.".
    • There's a function that checks if your A drive is not a floppy drive, and if your system's system.drv file contains the value "NEC" for key "system.drv" in section "boot.description". If either of these is true, the function returns TRUE. From what I've seen so far this only affects whether one submenu is enabled or not.
     
    • Informative Informative x 3
    • Like Like x 2
    • List
  19. BenoitRen

    BenoitRen

    Tech Member
    904
    547
    93
    Going to put all this info on the wiki soon, but I'll be posting about it here first.
    I was talking about this with a friend yesterday, and he could actually explain this.

    Back when we were using IBM PCs with DOS and Windows 3.x, people in Japan were using NEC's PC88 and PC98 computers, which mapped drive letters differently. We're used to always having floppy drives mapped to drive letters A and B, with the HDD being mapped to C. But NEC's computers would (when the OS is booted from the HDD) map drive letter A to the HDD, and the floppy drive to B.

    In other words, as suspected, those two checks were checking if Sonic CD was running on a NEC PC98.

    I've since reverse-engineered the menu resources, and can now say that what was being disabled is the "Full Screen" menu item.

    Good and Better sound quality

    The retail release contains evidence of a removed option for better sound quality.

    During initialisation, the program queries the configuration file SONIC.INI's "SOUND" section for the value of a key named "QUALITY". The default return value is "GOOD ". If the first character of this return value is 'B' (presumably the first letter of "BETTER"), a global boolean variable (from this point on called gbBetterSoundQuality) is set to 1. The program uses gbBetterSoundQuality to choose from two strings, which it passes to the function that loads the game's PCM (sound) file. However, both strings have the same text: "pcm8.cmp".

    That's not all that gbBetterSoundQuality is used for, however. When setting up the sound output, it uses the value to select from two values for the number of samples per second: 11025 and 22050.

    The most important piece of evidence, however, is a function I've found that bears similarities to the above initialisation logic. It toggles gbBetterSoundQuality between 0 and 1, then changes the text of a menu item based on the new value. The texts? "Good Sound Quality" and "Better Sound Quality". Next, just like during initialisation, it chooses from two strings based on the value of gbBetterSoundQuality. But this time, they are different: "pcm.cmp" and "pcm8.cmp".

    But here comes the kicker. This menu item does not exist in the program's menu resource! Furthermore, this function that toggles between both sound qualities is only called when a certain global variable has a value different from zero. And the initial value of this global variable is zero, and never changes outside of this function.

    At this point you could argue that it was planned during development, but they ended up not including the higher quality sound. Then I wondered: could the higher quality PCM file exist in the OEM release? I downloaded it, mounted the disc, and...

    upload_2024-7-21_17-26-20.png

    Holy shit.

    I loaded this version's main executable into Ghidra to verify if it actually uses this file. Unfortunately, this executable seems to be very different from the retail one, and as a result Ghidra's analysis is lacking here. I wasn't able to find WinMain, the starting point for all Windows executables. However, I found that it contained two sets of the strings "pcm.cmp" and "pcm8.cmp", hinting that both the initialisation and the sound quality toggle logic choose between the two files. More importantly, the menu resource contains the menu item "Better Sound Quality" that's missing from the retail version!

    So there you have it. The OEM Dino version actually has better PCM sound quality than the retail version that released a year later.
     
    • Informative Informative x 3
    • Like Like x 1
    • Useful Useful x 1
    • List
  20. Chimes

    Chimes

    The One SSG-EG Maniac Member
    998
    700
    93
    This is like finding out the best quality version of a anime film's audio is from some random vinyl record of the movie's soundtrack
    What in the absolute fuck