I made save editors for Sonic 3 and Sonic CD

Discussion in 'Engineering & Reverse Engineering' started by typhoon, Jun 21, 2020.

  1. I made a few Sonic-related web apps over the last year that I'd like to share with you guys to receive your input.

    Sonic 3 Save Editor

    Sonic 3 Save Editor (source code)

    I made this last September and posted it to Reddit to thunderous reception. As far as I know, it's the only editor implements the checksum algorithm used by the original game and works with original emulator saves. It can also do goofy things like enabling "Blue Knuckles" or assigning Death Egg Zone to Knuckles if you check "Show advanced options" (which reveals some extra save options as well). Based on Xfox Prower's format specification for the PC version.

    It originally only supported emulator saves, but I recently added support for the 1997 Sonic & Knuckles Collection, the 2011 Steam re-release, and Sonic 3 AIR. If anybody comes across a valid save that can't be opened in the editor or a save generated by the editor that can't be opened in one of the supported versions of Sonic 3, please post it here.

    (Edit: I recently reworked the Sonic 3 AIR part of the editor to work on persistentdata.bin rather than sram.bin. This lets it edit competition mode times as well. It's possible that this approach will unknowingly overwrite some data I don't know about (though I think it's probably fine, since achievement and non-competition time attack data are stored in other files), so, you know, use backups and let me know if it screws anything up.)

    Sonic CD Save Editor

    Sonic CD Save Editor (source code)

    I made this last week. It only supports emulator saves for the original Sega CD game. I've tested it in Gens and Kega Fusion, and it seems to work alright. I realize the original release is probably the least popular version of the game these days, so I don't expect that this will draw much interest. I do hope to add support for the 2011 Retro Engine release eventually.

    I'd also like to support the 1996 Sega PC release (for the sake of consistency with the Sonic 3 editor's support), but its save format looks completely inscrutable to me (maybe it's compressed?). If anybody knows of a resource that demystifies it any, please let me know (though, again, I understand that there probably isn't a ton of interest in the Windows 95 version of this game).

    Tails Adventure Password Generator

    Tails Adventure Password Generator (source code)

    Oh, and I made this thing too, last November. It's based on Xfox Prower's password generator (which, like his S3&KC save editor, doesn't work in modern browsers anymore without some modification). It always generates the same password as Xfox Prower's generator, which, in my testing, is usually but not always a valid password. There isn't a nice disassembly of this game like Sonic 3, and I couldn't work out the password algorithm any better with a debugger, so this is probably as good as this will get unless somebody has some additional insight to share.
     
    Last edited: Jun 28, 2020
  2. MainMemory

    MainMemory

    Have no fear...Amy Rose is here! Tech Member
    4,412
    67
    28
    SonLVL
    Sonic CD PC
    Code (Text):
    1. signed int __stdcall WriteSaveData(int savenum, int *sdata, HFILE hFile)
    2. {
    3.   char PathName[256]; // [esp+Ch] [ebp-3E0h]
    4.   _BYTE *_sdata; // [esp+10Ch] [ebp-2E0h]
    5.   int i; // [esp+110h] [ebp-2DCh]
    6.   int Buffer; // [esp+114h] [ebp-2D8h]
    7.   HFILE _hFile; // [esp+118h] [ebp-2D4h]
    8.   _BYTE sdata2[720]; // [esp+11Ch] [ebp-2D0h]
    9.  
    10.   if ( sdata )
    11.     _sdata = sdata;
    12.   else
    13.     _sdata = &dword_42CA70;
    14.   if ( hFile )
    15.   {
    16.     _hFile = hFile;
    17.   }
    18.   else
    19.   {
    20.     GetSaveFileName(PathName);
    21.     _hFile = lopen(PathName, 2);
    22.     if ( _hFile == -1 )
    23.     {
    24. LABEL_17:
    25.       MessageBoxA(0, aSaveDataErrorP, aSonicError_9, 0x10u);
    26.       if ( !hFile && _hFile != -1 )
    27.         lclose(_hFile);
    28.       return 0;
    29.     }
    30.   }
    31.   if ( hread(_hFile, &Buffer, 4) == -1 )
    32.     goto LABEL_17;
    33.   for ( i = 0; savenum > i; ++i )
    34.   {
    35.     if ( hread(_hFile, sdata2, 720) == -1 )
    36.       goto LABEL_17;
    37.   }
    38.   WriteSaveChecksum(_sdata);
    39.   DoWeirdSaveThing(_sdata, sdata2, 720);
    40.   if ( hwrite(_hFile, sdata2, 720) == -1 )
    41.     goto LABEL_17;
    42.   if ( !hFile )
    43.     lclose(_hFile);
    44.   return 1;
    45. }
    46.  
    47. int __cdecl WriteSaveChecksum(char *a1)
    48. {
    49.   int result; // eax
    50.   unsigned int i; // [esp+Ch] [ebp-8h]
    51.   int v3; // [esp+10h] [ebp-4h]
    52.  
    53.   v3 = 0;
    54.   for ( i = 0; i < 716; ++i )
    55.     v3 += *a1++;
    56.   result = v3;
    57.   *(_DWORD *)a1 = v3;
    58.   return result;
    59. }
    60.  
    61. void __cdecl DoWeirdSaveThing(_BYTE *src, _BYTE *dst, int a3)
    62. {
    63.   int v3; // eax
    64.   int v4; // [esp+Ch] [ebp-4h]
    65.  
    66.   srand(0x551u);
    67.   v4 = 0;
    68.   while ( v4 < a3 )
    69.   {
    70.     v3 = rand();
    71.     *dst = *src ^ (((v3 >> 31) ^ abs((_BYTE)v3)) - (v3 >> 31));
    72.     ++v4;
    73.     ++dst;
    74.     ++src;
    75.   }
    76. }
     
  3. Wow, thanks a lot. Very weird and interesting. Is this your own finding? I'd like to get the attribution right.

    So, basically:
    • The first four bytes of the file are always 0x00. (Edit: Not quite true, the first byte stores the currently selected save file, between 0 and 5.)
    • The rest of the file is broken into 720-byte sections (since the game supports multiple save files), which gives 6 sections. The last 4 bytes of each section are a checksum.
    • Each section (except the checksum) is encoded using srand with a particular seed, I assume just for obfuscation. The series that srand produces is implementation-specific, so I used msvc to reproduce it. Each byte in the section is xor-ed to the next word generated by rand. The provided code also does some other weird things (like shifting to the right by 31, which always gives 0 since msvc's rand gives 16-bit numbers, and then xor-ing that, which has no effect since xor with 0 doesn't do anything), which I assume is weirdness introduced by the compiler. We can also discard the high byte given by rand since we're working with bytes and not words (so it gets zeroed out anyway).
    Anyway, that lets me turn this nonsense (the highlighted part is the first save file) into this, which is definitely what I want. Interestingly, it's completely different from the Sega CD version's save format (for instance, it clearly uses ASCII for time attack initials whereas the Sega CD version simply indexes the sprite for the initial rather than using an actual character encoding). In comparison, Sonic & Knuckles Collection basically uses the same save format as Sonic 3 for the Genesis with minor changes (like no duplication, no checksum, and different start offsets). At any rate, I should be able to figure this out and add support for it over the weekend, which I'm happy about (and then the Retro Engine remake sometime after that).

    (Edit: I wrote this program to decode/encode PC saves in case anybody wants to play with the game's save files themselves.)
     
    Last edited: Jun 29, 2020
  4. MainMemory

    MainMemory

    Have no fear...Amy Rose is here! Tech Member
    4,412
    67
    28
    SonLVL
    Yeah, I just dug into the exe with IDA and that's what I found. Glad to know it helped!