├── LICENSE ├── README.md ├── backends ├── README.md ├── actionreplay.c ├── actionreplay.h ├── backend.c ├── backend.h ├── cd.c ├── cd.h ├── mode.c ├── mode.h ├── mode │ ├── mode_intf.a │ └── mode_intf.h ├── sat.c ├── sat.h ├── satiator.c ├── satiator.h ├── satiator │ ├── cd.c │ ├── cd.h │ ├── satiator-types.h │ ├── satiator.c │ └── satiator.h ├── saturn.c └── saturn.h ├── bup_header.c ├── bup_header.h ├── cd ├── ABS.TXT ├── BIB.TXT ├── CPY.TXT └── SATSAVES │ ├── BIOUDAT2.BUP │ ├── BIOUDATA.BUP │ ├── B_GAREGG.BUP │ ├── CREDITS.TXT │ ├── DAYTONA_.BUP │ ├── DRACUL01.BUP │ ├── DRACUL02.BUP │ ├── DRACULAX.BUP │ ├── DR_FORCE.BUP │ ├── FALC2COM.BUP │ ├── FALC2Y20.BUP │ ├── FALC2Y21.BUP │ ├── FALC2Y22.BUP │ ├── FANTASYZ.BUP │ ├── FIGHTERS.BUP │ ├── GOIKE001.BUP │ ├── GOIKEDAT.BUP │ ├── GUARDIAN.BUP │ ├── HARRIE01.BUP │ ├── HOUSEOFD.BUP │ ├── KEIO2___.BUP │ ├── LANGRIS5.BUP │ ├── NIGHTS02.BUP │ ├── OUTRUN01.BUP │ ├── POCKET_F.BUP │ ├── PZL_F_TU.BUP │ ├── QUTENKAI.BUP │ ├── RADIANT_.BUP │ ├── SALAMAND.BUP │ ├── SBOMBERM.BUP │ ├── SEGARALL.BUP │ ├── SENGOKU_.BUP │ ├── SFORCE31.BUP │ ├── SFZER001.BUP │ ├── SFZER002.BUP │ ├── SF_ALPH1.BUP │ ├── SF_ALPHA.BUP │ ├── SF_COL02.BUP │ ├── SF_COL03.BUP │ ├── SONICR__.BUP │ ├── SOQGUREN.BUP │ ├── TAMAGOTC.BUP │ ├── TENCHIRE.BUP │ ├── ZENNIC01.BUP │ └── ZENNIC02.BUP ├── clean.bat ├── clean.sh ├── compile.bat ├── compile.sh ├── game_cue_for_mode.cue ├── main.c ├── main.h ├── makefile ├── md5 ├── md5.c └── md5.h ├── run_with_daemon_tools_and_ssf.bat ├── run_with_mednafen.bat ├── run_with_nova.bat ├── run_with_powershell_and_ssf.ps1 ├── run_with_virtual_clone_drive_and_ssf.bat ├── run_with_yabaSanshiro.bat ├── run_with_yabause.bat ├── run_with_yabause.sh ├── screenshots ├── cd.png ├── copy.png ├── dump.png ├── main.png ├── mode.png ├── satiator.png └── saves.png ├── util.c └── util.h /README.md: -------------------------------------------------------------------------------- 1 | # Save Game Copier (SGC) 2 | Copy Sega Saturn save game files to and/or from internal memory, cartridge memory, external devices (e.g. Sega Saturn [Floppy Disk Drive](https://segaretro.org/Saturn_Floppy_Drive)), Action Replay cart, Satiator, MODE, and CD. Build with [Jo Engine](https://github.com/johannes-fetz/joengine) or download an ISO from [releases](https://github.com/slinga-homebrew/Save-Game-Copier/releases). One of the most useful features of SGC is to create a custom SGC ISO with your own save game files and copy them to your Saturn. 3 | 4 | SGC is for copying save games to a Saturn. To copy save games from Saturn -> PC use [Save Game Extractor](https://github.com/slinga-homebrew/Save-Game-Extractor). 5 | 6 | ## Screenshots 7 | ![Main](screenshots/main.png) 8 | ![List Saves](screenshots/saves.png) 9 | ![CD Saves](screenshots/cd.png) 10 | ![Copy](screenshots/copy.png) 11 | ![Satiator](screenshots/satiator.png) 12 | ![MODE](screenshots/mode.png) 13 | ![Dump](screenshots/dump.png) 14 | 15 | ## Save Games Format (.BUP) 16 | SGC uses saves in the .BUP save format. The .BUP format consists of the metadata along with the save data itself. The format is documented in [Save Game BUP Scripts](https://github.com/slinga-homebrew/Save-Game-BUP-Scripts) along with a script to convert between .BUP and raw saves. 17 | 18 | * All files on a CD are limited to 8 character names plus 3 characters for the .BUP extension. 19 | * The .BUP extension is required. 20 | 21 | * If you want your ISO to work properly on a Saturn you have to rename the file to an 8.3 character file. For example if the file you are adding is FPS_6MEN_01.BUP, rename to FPS_6MEN.BUP. 22 | 23 | Additional examples: 24 | GRANDIA_001.BUP -> GRANDIA_.BUP 25 | THREE_DIRTY.BUP -> THREE_DI.BUP 26 | 27 | MODE and Satiator can use longer 14.3 filenames. 28 | 29 | ## Adding Custom Save Games to the SGC ISO 30 | There are two ways to add your custom save game files to SGC: 31 | 1a) (Windows) Using something like WinISO add your save game file to the SATSAVES directory. Again read the instructions in "Save Game Format" so you have the correct type of file and filename. The filename must be in the 8.3 format. 32 | 1b) (Linux) 33 | ``` 34 | # mount the original 35 | mkdir /tmp/sgc_custom 36 | sudo mount -t iso9660 -o loop sgc_original.iso /mnt/ 37 | cd /mnt/ 38 | tar cf - . | (cd /tmp/sgc_custom; tar xfp -) 39 | 40 | # make the necessary changes 41 | # remember that filenames must be in 8.3 format 42 | cd /tmp/sgc_custom/SATSAVES 43 | 44 | 45 | # convert your changes back into an iso 46 | mkisofs -o sgc_modified.iso /tmp/sgc_custom 47 | 48 | # insert the boot headers from the original iso into the modified 49 | dd conv=notrunc if=sgc_original.iso of=sgc_modified.iso bs=1 count=32768 50 | ``` 51 | 52 | 2) If you are comfortable compiling SGC, you can also add saves at build time. Checkout SGC from source. Add your save game files (in a raw format) to cd/SATSAVES/ and recompile. Again read the instructions in "Save Game Format" so you have the correct type of file and filename. The newly built ISO will include your saves. 53 | 54 | ## Satiator Support 55 | When using Satiator: 56 | * Make sure you upgrade to the latest firmware. There have been firmware fixes 57 | * Create a "SATSAVES" directory on the root of the drive. SGC is hardcoded to use that folder. Copy saves to and from that folder. 58 | * Saves must use the .BUP file extensions or they will not be visible. Filenames can be 11 characters + 3 more for the extension. 59 | 60 | ## MODE Support 61 | When using MODE: 62 | * Ensure you are running firmware >= 1.04 63 | * Create a "SATSAVES" directory on the root of the SD card. SGC is hardcoded to use that folder and the SD card. HDD is not supported yet. 64 | * Saves must use the .BUP file extensions or they will not be visible. Filenames can be 11 characters + 3 more for the extension. 65 | * You must use the cue file named *game_cue_for_mode.cue* **INSTEAD OF** *game.cue* file, **and then rename** *game_cue_for_mode.cue* to *game.cue*. Ensure only 1 cue is present along with game.iso file. This is required because MODE needs a large TOC for the command interface. 66 | * Set the Software Reset option to "Direct to Mode" and make sure "Fast Boot" is enabled. 67 | 68 | ## Dumping Memory 69 | SGC also supports an advanced feature to dump arbitrary memory. This can allow you to dump: 70 | * Saturn BIOS (address: 0x80000, size: 0x80000) 71 | * Internal backup memory (address: 0x00180000, size: 0x001FFFFF) 72 | * Cartridge backup memory (address: 0x02000000, size: 0x01FFFFFF) 73 | * etc 74 | 75 | ## Issues 76 | * Non-English save game comments are not displayed. This is a limitation of the print routine I'm using. However the comments are copied correctly and can be viewed within the Saturn BIOS. I'm researching a workaround. 77 | * Once you access the Satiator you can no longer list the saves in the "CD Memory" option. I don't know if this is an issue with Satiator or the Satiator-Yabause fork I am testing with. If you really want to transfer multiple "CD Memory" saves to your Satiator, transfer them to your Internal Memory first. From there you can transfer multiple saves to the Satiator without issue. Or since you have a Satiator you can just put the saves on your SD card... 78 | * Some Satiator users have reported the first transfer takes ~90 seconds and then all other transfers are fast. Professor Abrasive is aware of the issue. It's possible the issue is related to the SD card itself. Try running a chkdsk. 79 | 80 | ## Troubleshooting 81 | When debugging saves that don't work, load SGC on both systems and verify the MD5 hash and file size of both saves. The most likely issue is that of metadata being included. The other common issue is the name of the file. 82 | 83 | SGC uses a dynamic menu and will remove devices it doesn't detect from your menu. If SGC is having trouble seeing your backup cartridge make sure the builtin Saturn memory manager can see the cartridge. Most likely the Saturn can't see the cartridge and therefore neither can SGC. 84 | 85 | MODE is not detected. Make sure you use the game_cure_for_mode.cue otherwise the MODE will not be detected. 86 | 87 | ## Adding New Backend Devices 88 | See [backends/README.MD](https://github.com/slinga-homebrew/Save-Game-Copier/blob/master/backends/README.md) for notes on how to add a new backend device to Save Game Copier. 89 | 90 | ## Saturn Save Games Collect Project 91 | Want to share your save games on the web? Send them to the [Save Games Collect](https://ppcenter.webou.net/pskai/savedata/) project. Made by Cafe-Alpha, the author of the Gamer's Cartridge. Please submit your ".BUP" files. 92 | 93 | ## License 94 | Licensed under GPL3 to comply with the Iapetus license. 95 | 96 | ## 3rd Party Code 97 | Save Game Copier uses code from: 98 | * [Jo Engine](https://github.com/johannes-fetz/joengine) - MIT 99 | * [MD5](http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5) - BSD 100 | * [satiator-menu](https://github.com/satiator/satiator-menu) - MPL and GPL 101 | * [iapetus](https://github.com/cyberwarriorx/iapetus) - GPL 102 | 103 | ## Credits 104 | * Thank you to [Johannes Fetz](https://github.com/Johannes-Fetz) for [Jo Engine](https://github.com/johannes-fetz/joengine) and adding features needed by Save Game Copier. 105 | * Thank you to [Emerald Nova](https://github.com/EmeraldNova) for volunteering to test the code on his Satiator. 106 | * Thank you to [Terraonion](https://github.com/Terraonion-dev) for contributing MODE support. 107 | * Thank you to [RevQuixo](https://github.com/RevQuixo) for numerous bug reports. 108 | * Thank you to [Cafe-Alpha](https://github.com/cafe-alpha/) for .BUP file format and advice. 109 | * Thank you to [Hitomi2500](https://github.com/hitomi2500) for help parsing Action Replay and advice. 110 | * Special thanks to Antime, Ponut, VBT, and everyone else at SegaXtreme keeping the Saturn dev scene alive. 111 | * Thank you to Takashi for the original Save Game Copier idea back in ~2002. 112 | * [Shentokk](https://github.com/Shentokk) for information regarding emulator save game extraction 113 | -------------------------------------------------------------------------------- /backends/README.md: -------------------------------------------------------------------------------- 1 | # Backends 2 | Save Game Copier (SGC) currently supports the following backends to read\write save game files to: 3 | * internal\cartridge\external (Floppy) (saturn.c) 4 | * Action Replay Cartridge (actionreplay.c) 5 | * CD (cd.c) 6 | * MODE (mode.c) 7 | * Satiator (satiator.c) 8 | 9 | # Adding a New Backend 10 | To be compatible with SGC, each new backend must support the following : 11 | * safe detection of backend device 12 | * change directory to /SATSAVES 13 | * directory listing 14 | * query file size 15 | * read file 16 | * write file (optional) 17 | * delete file (optional) 18 | * format device (optional) 19 | * GPL3 or compatible license 20 | 21 | ## Backup.h 22 | Edit backup.h adding a new #define for the new backup device. ActionReplayBackup is currently the last one. 23 | 24 | ## Backend Specific Source 25 | Create a mybackend.h and mybackend.c. See saturn.h and saturn.c as examples. The file must have the following functions: 26 | 27 | * bool mydeviceIsBackupDeviceAvailable(int backupDevice) 28 | * returns true if the backup device is present. Should be safe to call when the device isn't present. 29 | * int mydeviceListSaveFiles(int backupDevice, PSAVES saves, unsigned int numSaves) 30 | * queries the saves on the backup device and fills out the fileSaves array. Returns the number of saves found. 31 | * int mydeviceReadSaveFile(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outBufSize) 32 | * reads the save to outBuffer 33 | * (optional) int mydeviceWriteSaveFile(int backupDevice, char* filename, unsigned char* saveData, unsigned int saveDataLen) 34 | * writes the save file 35 | * (optional) int mydeviceDeleteSaveFile(int backupDevice, char* filename) 36 | * deletes the specified file 37 | 38 | ## Backend.c 39 | Edit backend.c adding the new device to the switch statements for isBackupDeviceAvailalbe(), listSaveFiles(), readSaveFile(), writeSaveFile() (optional), deleteSaveFile() optional, and formatDevice() (optional). 40 | 41 | Update getBackupDeviceName() to return the name of your device. 42 | 43 | ## Main.h 44 | Add a #define MAIN_OPTION_MY_DEVICE. If your device is writeable, add a SAVE_OPTION_MY_DEVICE #define as well. 45 | 46 | In the _GAME struct, add a bool deviceMyDeviceBackup member. 47 | 48 | ## Main.c 49 | Add your device to queryBackupDevices(). g_Game.deviceMyDeviceBackup = isBackupDeviceAvailable(MyDeviceBackup). 50 | 51 | Edit initMenuOptions() adding your device under STATE_MAIN, STATE_DISPLAY_SAVE (if your device is writeable), and STATE_FORMAT (if your device is formatable). 52 | 53 | Edit main_input(), add a MAIN_OPTION_MY_DEVICE case. 54 | 55 | Edit listSaves_draw() add a your device to the check. 56 | 57 | Edit displaySave_input(), adding a case statement for your device. 58 | 59 | ## Makefile 60 | Update the makefile 61 | -------------------------------------------------------------------------------- /backends/actionreplay.c: -------------------------------------------------------------------------------- 1 | // Action Replay Cartridge 2 | #include "actionreplay.h" 3 | #include "sat.h" 4 | 5 | // returns true if the backup device is found 6 | bool actionReplayIsBackupDeviceAvailable(int backupDevice) 7 | { 8 | char* magic = NULL; 9 | 10 | if(backupDevice != ActionReplayBackup) 11 | { 12 | return false; 13 | } 14 | 15 | // check for the action replay signature 16 | magic = (char*)(CARTRIDGE_MEMORY + ACTION_REPLAY_MAGIC_OFFSET); 17 | 18 | if(strncmp(magic, ACTION_REPLAY_MAGIC, sizeof(ACTION_REPLAY_MAGIC) - 1) == 0) 19 | { 20 | return true; 21 | } 22 | 23 | return false; 24 | } 25 | 26 | // queries the saves on the Action Replay cartridge device and fills out the saves array 27 | int actionReplayListSaveFiles(int backupDevice, PSAVES saves, unsigned int numSaves) 28 | { 29 | unsigned char* partitionBuf = NULL; 30 | unsigned int partitionSize = 0; 31 | int foundSaves = 0; 32 | int result = 0; 33 | 34 | if(backupDevice != ActionReplayBackup) 35 | { 36 | sgc_core_error("Failed 1"); 37 | return -1; 38 | } 39 | 40 | // decompress the partition 41 | result = decompressPartition((unsigned char*)(CARTRIDGE_MEMORY + ACTION_REPLACE_SAVES_OFFSET), ACTION_REPLACE_SAVES_SIZE, &partitionBuf, &partitionSize); 42 | if(result != 0) 43 | { 44 | return result; 45 | } 46 | 47 | // enumerate the saves 48 | foundSaves = satListSaves(partitionBuf, partitionSize, ACTION_REPLAY_PARTITION_SIZE, saves, numSaves); 49 | if(foundSaves < 0) 50 | { 51 | sgc_core_error("AR: failed to list %d\n", foundSaves); 52 | goto cleanup; 53 | } 54 | 55 | cleanup: 56 | jo_free(partitionBuf); 57 | 58 | return foundSaves; 59 | } 60 | 61 | // copies the specified actionReplay save game to the saveFileData buffer 62 | int actionReplayReadSaveFile(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outSize) 63 | { 64 | PSAT_START_BLOCK_HEADER saveStartBlock = NULL; 65 | PSAT_BLOCK satBlocks = NULL; 66 | PBUP_HEADER bupHeader = NULL; 67 | unsigned char* partitionBuf = NULL; 68 | unsigned int partitionSize = 0; 69 | int result = 0; 70 | 71 | if(backupDevice != ActionReplayBackup) 72 | { 73 | return -1; 74 | } 75 | 76 | if(outBuffer == NULL || filename == NULL) 77 | { 78 | sgc_core_error("actionReplayReadSaveFile: Save file data buffer is NULL!!"); 79 | return -1; 80 | } 81 | 82 | if(outSize < sizeof(BUP_HEADER) || outSize > (MAX_SAVE_SIZE + sizeof(BUP_HEADER))) 83 | { 84 | sgc_core_error("actionReplayReadSaveFile: Save file size is invalid %d!!", outSize); 85 | return -2; 86 | } 87 | bupHeader = (PBUP_HEADER)outBuffer; 88 | 89 | // 90 | // decompress the Action Replay compressed save buffer 91 | // 92 | result = decompressPartition((unsigned char*)(CARTRIDGE_MEMORY + ACTION_REPLACE_SAVES_OFFSET), ACTION_REPLACE_SAVES_SIZE, &partitionBuf, &partitionSize); 93 | if(result != 0) 94 | { 95 | return result; 96 | } 97 | 98 | // 99 | // Find the save, read it's SAT table, then read the save data 100 | // 101 | 102 | // find the start of the save block 103 | result = getSaveStartBlock(partitionBuf, partitionSize, ACTION_REPLAY_PARTITION_SIZE, filename, &saveStartBlock); 104 | if(result < 0) 105 | { 106 | sgc_core_error("Failed to find save!!\n"); 107 | goto cleanup; 108 | } 109 | 110 | // set the bup header metadata 111 | memset(bupHeader, 0, sizeof(BUP_HEADER)); 112 | 113 | memcpy(bupHeader->magic, VMEM_MAGIC_STRING, VMEM_MAGIC_STRING_LEN); 114 | strncpy((char*)bupHeader->dir.filename, saveStartBlock->saveName, SAT_MAX_SAVE_NAME); 115 | strncpy((char*)bupHeader->dir.comment, saveStartBlock->comment, SAT_MAX_SAVE_COMMENT); 116 | bupHeader->dir.language = saveStartBlock->language; 117 | bupHeader->dir.date = saveStartBlock->date; 118 | bupHeader->dir.datasize = saveStartBlock->saveSize; 119 | bupHeader->dir.blocksize = 0; // not needed 120 | bupHeader->date = saveStartBlock->date; // date is duplicated 121 | 122 | // validate the size 123 | if(saveStartBlock->saveSize > outSize - sizeof(BUP_HEADER)) 124 | { 125 | sgc_core_error("Save size is too big!!\n"); 126 | result = -1; 127 | goto cleanup; 128 | } 129 | 130 | // get the SAT block table 131 | result = getSATBlocks(partitionBuf, partitionSize, ACTION_REPLAY_PARTITION_SIZE, saveStartBlock, &satBlocks); 132 | if(result < 0) 133 | { 134 | sgc_core_error("Failed to get SAT table"); 135 | goto cleanup; 136 | } 137 | 138 | // finally read the save data 139 | result = getSATSave(partitionBuf, partitionSize, ACTION_REPLAY_PARTITION_SIZE, satBlocks, outBuffer + sizeof(BUP_HEADER), saveStartBlock->saveSize); 140 | if(result < 0) 141 | { 142 | sgc_core_error("Failed to read save!!\n"); 143 | goto cleanup; 144 | } 145 | 146 | result = 0; 147 | 148 | cleanup: 149 | if(partitionBuf) 150 | { 151 | jo_free(partitionBuf); 152 | } 153 | 154 | if(satBlocks) 155 | { 156 | jo_free(satBlocks); 157 | } 158 | 159 | return result; 160 | } 161 | 162 | // write the save game to the actionReplay 163 | int actionReplayWriteSaveFile(int backupDevice, char* filename, unsigned char* inBuffer, unsigned int inSize) 164 | { 165 | UNUSED_ARG(backupDevice); 166 | UNUSED_ARG(filename); 167 | UNUSED_ARG(inBuffer); 168 | UNUSED_ARG(inSize); 169 | 170 | /* 171 | int result = 0; 172 | int fd = 0; 173 | 174 | if(backupDevice != actionReplayBackup) 175 | { 176 | return -1; 177 | } 178 | 179 | result = actionReplayEnter(); 180 | if(result != 0) 181 | { 182 | sgc_core_error("Failed to detect actionReplay"); 183 | return -1; 184 | } 185 | 186 | if(filename == NULL) 187 | { 188 | sgc_core_error("writeactionReplaySaveData: Save file data buffer is NULL!!"); 189 | return -1; 190 | } 191 | 192 | if(inBuffer == NULL || filename == NULL) 193 | { 194 | sgc_core_error("writeactionReplaySaveData: Save file size is invalid %d!!", inSize); 195 | return -2; 196 | } 197 | 198 | fd = s_open(filename, FA_WRITE|FA_CREATE_ALWAYS); 199 | if(fd < 0) 200 | { 201 | sgc_core_error("writeactionReplaySaveData: Failed to open actionReplay file!!"); 202 | return -2; 203 | } 204 | 205 | for(unsigned int bytesWritten = 0; bytesWritten < inSize; ) 206 | { 207 | unsigned int count; 208 | 209 | count = MIN(inSize - bytesWritten, S_MAXBUF); 210 | 211 | // BUGBUG: fix this 212 | result = s_write(fd, inBuffer + bytesWritten, count); 213 | 214 | s_sync(fd); 215 | 216 | if(result <= 0) 217 | { 218 | sgc_core_error("Bad write result: %x", result); 219 | s_close(fd); 220 | return result; 221 | break; 222 | } 223 | 224 | bytesWritten += count; 225 | } 226 | 227 | s_close(fd); 228 | 229 | if(result < 0) 230 | { 231 | sgc_core_error("writeactionReplaySaveData: Failed to read actionReplay file!!"); 232 | return -3; 233 | } 234 | 235 | return 0; 236 | */ 237 | 238 | return -1; 239 | 240 | } 241 | 242 | // delete the save 243 | int actionReplayDeleteSaveFile(int backupDevice, char* filename) 244 | { 245 | PSAT_START_BLOCK_HEADER saveStartBlock = NULL; 246 | PSAT_BLOCK satBlocks = NULL; 247 | PRLE01_HEADER rleHeader = NULL; 248 | unsigned char* partitionBuf = NULL; 249 | unsigned int partitionSize = 0; 250 | unsigned char* block = NULL; 251 | unsigned char rleKey = 0; 252 | unsigned char* compressedBuf = NULL; 253 | unsigned int compressedSize = 0; 254 | int result = 0; 255 | 256 | if(backupDevice != ActionReplayBackup) 257 | { 258 | return -1; 259 | } 260 | 261 | if(filename == NULL) 262 | { 263 | sgc_core_error("actionReplayDeleteSaveFile: Filename is NULL!!"); 264 | return -2; 265 | } 266 | 267 | // 268 | // decompress the Action Replay compressed save buffer 269 | // 270 | // decompress the partition 271 | result = decompressPartition((unsigned char*)(CARTRIDGE_MEMORY + ACTION_REPLACE_SAVES_OFFSET), ACTION_REPLACE_SAVES_SIZE, &partitionBuf, &partitionSize); 272 | if(result != 0) 273 | { 274 | goto cleanup; 275 | } 276 | 277 | // 278 | // Find the save, read it's SAT table, then read the save data 279 | // 280 | 281 | // find the start of the save block 282 | result = getSaveStartBlock(partitionBuf, partitionSize, ACTION_REPLAY_PARTITION_SIZE, filename, &saveStartBlock); 283 | if(result < 0) 284 | { 285 | sgc_core_error("Failed to find save!!\n"); 286 | goto cleanup; 287 | } 288 | 289 | // get the SAT block table 290 | result = getSATBlocks(partitionBuf, partitionSize, ACTION_REPLAY_PARTITION_SIZE, saveStartBlock, &satBlocks); 291 | if(result < 0) 292 | { 293 | sgc_core_error("Failed to get SAT table"); 294 | goto cleanup; 295 | } 296 | 297 | // iterate through the allocated blocks and zero them out 298 | while(satBlocks->blockNum) 299 | { 300 | block = (unsigned char*)(partitionBuf + (satBlocks->blockNum * ACTION_REPLAY_PARTITION_SIZE)); 301 | 302 | // zero out each block 303 | memset(block, 0, ACTION_REPLAY_PARTITION_SIZE); 304 | 305 | ++satBlocks; 306 | } 307 | 308 | // compute the new RLE rleKey 309 | result = calcRLEKey(partitionBuf, partitionSize, &rleKey); 310 | if(result != 0) 311 | { 312 | sgc_core_error("Failed to calculate key!! %d", result); 313 | goto cleanup; 314 | } 315 | 316 | // 317 | // compress partition buffer 318 | // 319 | 320 | result = compressRLE01(rleKey, partitionBuf, partitionSize, NULL, &compressedSize); 321 | if(result != 0) 322 | { 323 | sgc_core_error("compress: %d\n", result); 324 | goto cleanup; 325 | } 326 | 327 | if(compressedSize > ACTION_REPLACE_SAVES_SIZE || compressedSize + sizeof(RLE01_HEADER) > ACTION_REPLACE_SAVES_SIZE) 328 | { 329 | sgc_core_error("compressSize too big: %x\n", compressedSize); 330 | result = -4; 331 | goto cleanup; 332 | } 333 | 334 | // we need room fo rthe RLE01 header 335 | compressedBuf = jo_malloc(compressedSize + sizeof(RLE01_HEADER)); 336 | if(compressedBuf == NULL) 337 | { 338 | sgc_core_error("failed to alloc"); 339 | result = -3; 340 | goto cleanup; 341 | } 342 | 343 | // set the header 344 | rleHeader = (PRLE01_HEADER)compressedBuf; 345 | memcpy(rleHeader->compressionMagic, RLE01_MAGIC, sizeof(rleHeader->compressionMagic)); 346 | rleHeader->rleKey = rleKey; 347 | rleHeader->compressedSize = compressedSize; 348 | 349 | result = compressRLE01(rleKey, partitionBuf, partitionSize, compressedBuf + sizeof(RLE01_HEADER), &compressedSize); 350 | if(result != 0) 351 | { 352 | sgc_core_error("compress: %d", result); 353 | goto cleanup; 354 | } 355 | 356 | sgc_core_error("RLE key: %x", rleKey); 357 | sgc_core_error("comp: %x", compressedSize); 358 | 359 | // BUGBUG: this should be a cart specific write operation 360 | memcpy((unsigned char*)(CARTRIDGE_MEMORY + ACTION_REPLACE_SAVES_OFFSET), compressedBuf, compressedSize); 361 | 362 | result = 0; 363 | 364 | cleanup: 365 | if(partitionBuf) 366 | { 367 | jo_free(partitionBuf); 368 | } 369 | 370 | if(satBlocks) 371 | { 372 | jo_free(satBlocks); 373 | } 374 | 375 | if(compressedBuf) 376 | { 377 | jo_free(compressedBuf); 378 | } 379 | 380 | return result; 381 | } 382 | 383 | // 384 | // Utility Functions 385 | // 386 | 387 | // Takes in a compressed buffer (including header) from an Action Replay cart 388 | // On success dest contains the uncompressed buffer of destSize bytes 389 | // Caller must free dest on success 390 | // returns 0 on success, non-zero on failure 391 | int decompressPartition(unsigned char *src, unsigned int srcSize, unsigned char **dest, unsigned int* destSize) 392 | { 393 | PRLE01_HEADER header = NULL; 394 | int result = 0; 395 | 396 | if(src == NULL || srcSize == 0 || dest == NULL || destSize == NULL) 397 | { 398 | sgc_core_error("decomp: invalid args"); 399 | return -1; 400 | } 401 | 402 | if(srcSize < sizeof(RLE01_HEADER)) 403 | { 404 | sgc_core_error("decomp: invalid srcSize"); 405 | return -2; 406 | } 407 | 408 | header = (PRLE01_HEADER)src; 409 | 410 | // must begin with "RLE01" 411 | // "DEF01" and "DEF02" are not supported 412 | if(memcmp(header->compressionMagic, RLE01_MAGIC, sizeof(header->compressionMagic)) != 0) 413 | { 414 | sgc_core_error("decomp: bad magic %c%c%c%c%c", header->compressionMagic[0], header->compressionMagic[1], header->compressionMagic[2], header->compressionMagic[3], header->compressionMagic[4]); 415 | return -3; 416 | } 417 | 418 | if(header->compressedSize >= srcSize || header->compressedSize < sizeof(RLE01_HEADER)) 419 | { 420 | // we will read out of bounds 421 | sgc_core_error("decomp: compressed size"); 422 | return -4; 423 | } 424 | 425 | // 426 | // decompress the Action Replay compressed save buffer 427 | // 428 | result = decompressRLE01(header->rleKey, src + sizeof(RLE01_HEADER), header->compressedSize - sizeof(RLE01_HEADER), NULL, destSize); 429 | if(result < 0) 430 | { 431 | sgc_core_error("Failed RLE01 %d", result); 432 | return -5; 433 | } 434 | 435 | *dest = jo_malloc(*destSize); 436 | if(*dest == NULL) 437 | { 438 | sgc_core_error("Failed to allocate %d", *destSize); 439 | return -6; 440 | } 441 | 442 | result = decompressRLE01(header->rleKey, src + sizeof(RLE01_HEADER), header->compressedSize - sizeof(RLE01_HEADER), *dest, destSize); 443 | if(result < 0) 444 | { 445 | sgc_core_error("Failed 2 RLE01 %d", result); 446 | jo_free(*dest); 447 | return -7; 448 | } 449 | 450 | return 0; 451 | } 452 | 453 | // Decompresses RLE01 compressed buffer into dest 454 | // To calculate number of bytes needed, set dest to NULL 455 | // This function was reversed from function 0x002897dc in ARP_202C.BIN 456 | int decompressRLE01(unsigned char rleKey, unsigned char *src, unsigned int srcSize, unsigned char *dest, unsigned int* bytesNeeded) 457 | { 458 | unsigned int i = 0; 459 | unsigned int j = 0; 460 | unsigned int k = 0; 461 | 462 | if(src == NULL || bytesNeeded == NULL) 463 | { 464 | return -1; 465 | } 466 | 467 | j = 0; 468 | i = 0; 469 | 470 | do 471 | { 472 | unsigned int count = 0; 473 | unsigned char val = 0; 474 | 475 | // three compressed cases 476 | // 1) not key 477 | // - copy the byte directly 478 | // - src + 1, dest + 1 479 | // 2) key followed by zero 480 | // -- copy key 481 | // -- src + 2, dest + 1 482 | // 3) key followed by non-zero, followed by val 483 | // -- copy val count times 484 | // -- src + 3, dest + count 485 | 486 | if (src[i] == rleKey) 487 | { 488 | count = (int)(char)src[i + 1] & 0xff; 489 | if (count == 0) 490 | { 491 | if(dest) 492 | { 493 | dest[j] = rleKey; 494 | } 495 | 496 | i = i + 2; 497 | goto continue_loop; 498 | } 499 | 500 | val = src[i + 2]; 501 | i = i + 3; 502 | k = 0; 503 | 504 | if (count != 0) 505 | { 506 | do 507 | { 508 | if(dest) 509 | { 510 | dest[j] = val; 511 | } 512 | 513 | j = j + 1; 514 | k = k + 1; 515 | 516 | } while (k < count); 517 | } 518 | } 519 | else 520 | { 521 | if(dest) 522 | { 523 | dest[j] = src[i]; 524 | } 525 | 526 | i = i + 1; 527 | continue_loop: 528 | j = j + 1; 529 | } 530 | 531 | // check if we are at the end 532 | if (srcSize <= i) 533 | { 534 | if(bytesNeeded) 535 | { 536 | *bytesNeeded = j; 537 | } 538 | 539 | return 0; 540 | } 541 | } while(1); 542 | } 543 | 544 | // on success sets key to the least used byte in src 545 | int calcRLEKey(unsigned char* src, unsigned int size, unsigned char* key) 546 | { 547 | unsigned int* keyCounts = NULL; 548 | unsigned char val = 0; 549 | unsigned int minCount = -1; 550 | 551 | if(!src || !size || !key) 552 | { 553 | return -1; 554 | } 555 | 556 | keyCounts = (unsigned int*)jo_malloc(RLE01_MAX_COUNT * sizeof(unsigned int)); 557 | if(keyCounts == NULL) 558 | { 559 | return -2; 560 | } 561 | memset(keyCounts, 0, RLE01_MAX_COUNT * sizeof(unsigned int)); 562 | 563 | for(unsigned int i = 0; i < size; i++) 564 | { 565 | val = src[i]; 566 | keyCounts[val]++; 567 | } 568 | 569 | for(unsigned int j = 0; j < RLE01_MAX_COUNT; j++) 570 | { 571 | if(keyCounts[j] < minCount) 572 | { 573 | // found less used key val 574 | *key = j; 575 | minCount = keyCounts[j]; 576 | } 577 | 578 | if(minCount == 0) 579 | { 580 | // fast exit if a count of zero has been found 581 | break; 582 | } 583 | } 584 | 585 | if(keyCounts) 586 | { 587 | jo_free(keyCounts); 588 | } 589 | 590 | return 0; 591 | } 592 | 593 | // Compresses RLE01 compressed buffer into dest 594 | // To calculate number of bytes needed, set dest to NULL 595 | // This function was reversed from function 0x0028970e in ARP_202C.BIN 596 | int compressRLE01(unsigned char rleKey, unsigned char *src, unsigned int srcSize, unsigned char *dest, unsigned int* bytesNeeded) 597 | { 598 | unsigned int i = 0; 599 | unsigned int j = 0; 600 | 601 | if(src == NULL || bytesNeeded == NULL) 602 | { 603 | return -1; 604 | } 605 | 606 | j = 0; 607 | i = 0; 608 | 609 | do 610 | { 611 | // three compressed cases 612 | // 1) not key 613 | // - copy the byte directly 614 | // - src + 1, dest + 1 615 | // 2) key followed by zero 616 | // -- copy key 617 | // -- src + 2, dest + 1 618 | // 3) key followed by non-zero, followed by val 619 | // -- copy val count times 620 | // -- src + 3, dest + count 621 | 622 | unsigned char count = 0; 623 | unsigned char val = 0; 624 | 625 | val = src[i]; 626 | 627 | // check how many times in a row curVal appears 628 | do 629 | { 630 | count++; 631 | if (*(src + i + count) != val) 632 | { 633 | break; 634 | } 635 | } while (count < RLE_MAX_REPEAT); 636 | 637 | // if count small, don't bother RLEing 638 | if(count < 4) 639 | { 640 | i++; 641 | 642 | if(dest) 643 | { 644 | dest[j] = val; 645 | } 646 | j++; 647 | 648 | if(val == rleKey) 649 | { 650 | dest[j] = 0; 651 | j++; 652 | } 653 | } 654 | else 655 | { 656 | if(srcSize < i + count) 657 | { 658 | count = srcSize - i; 659 | } 660 | 661 | if(dest) 662 | { 663 | dest[j] = rleKey; 664 | } 665 | j++; 666 | 667 | if(dest) 668 | { 669 | dest[j] = count; 670 | } 671 | j++; 672 | 673 | if(dest) 674 | { 675 | dest[j] = val; 676 | } 677 | j++; 678 | 679 | i += count; 680 | } 681 | 682 | // check if we are at the end 683 | if (srcSize <= i) 684 | { 685 | if(bytesNeeded) 686 | { 687 | *bytesNeeded = j; 688 | } 689 | 690 | return 0; 691 | } 692 | } while(1); 693 | } 694 | -------------------------------------------------------------------------------- /backends/actionreplay.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend.h" 4 | 5 | // 6 | // Action Replay Cartridge 7 | // 8 | 9 | #define CARTRIDGE_MEMORY 0x02000000 10 | #define ACTION_REPLAY_MAGIC_OFFSET 0x50 11 | #define ACTION_REPLACE_SAVES_OFFSET 0x20000 12 | #define ACTION_REPLACE_SAVES_SIZE 0x60000 // BUGBUG: just guessing here 13 | #define ACTION_REPLAY_MAGIC "ACTION REPLAY" 14 | #define ACTION_REPLAY_PARTITION_SIZE 64 15 | 16 | #define RLE01_MAGIC "RLE01" 17 | #define RLE01_MAX_COUNT 0x100 18 | #define RLE_MAX_REPEAT 0xFF 19 | 20 | #pragma pack(1) 21 | typedef struct _RLE01_HEADER 22 | { 23 | char compressionMagic[5]; // should be "RLE01" 24 | unsigned char rleKey; // key used to compress the datasize 25 | unsigned int compressedSize; // size of the compressed data 26 | }RLE01_HEADER, *PRLE01_HEADER; 27 | #pragma pack() 28 | 29 | bool actionReplayIsBackupDeviceAvailable(int backupDevice); 30 | int actionReplayListSaveFiles(int backupDevice, PSAVES fileSaves, unsigned int numSaves); 31 | int actionReplayReadSaveFile(int backupDevice, char* filename, unsigned char* ouBuffer, unsigned int outBufSize); 32 | int actionReplayWriteSaveFile(int backupDevice, char* filename, unsigned char* saveData, unsigned int saveDataLen); 33 | int actionReplayDeleteSaveFile(int backupDevice, char* filename); 34 | 35 | // utility functions 36 | int decompressPartition(unsigned char *src, unsigned int srcSize, unsigned char **dest, unsigned int* destSize); 37 | int decompressRLE01(unsigned char rleKey, unsigned char *src, unsigned int srcSize, unsigned char *dest, unsigned int* bytesNeeded); 38 | int calcRLEKey(unsigned char* src, unsigned int size, unsigned char* key); 39 | int compressRLE01(unsigned char rleKey, unsigned char *src, unsigned int srcSize, unsigned char *dest, unsigned int* bytesNeeded); 40 | 41 | -------------------------------------------------------------------------------- /backends/backend.c: -------------------------------------------------------------------------------- 1 | #include "backend.h" 2 | #include "saturn.h" 3 | #include "actionreplay.h" 4 | #include "mode.h" 5 | #include "satiator.h" 6 | #include "cd.h" 7 | 8 | // returns true if the backup device is found 9 | bool isBackupDeviceAvailable(int backupDevice) 10 | { 11 | switch(backupDevice) 12 | { 13 | case JoInternalMemoryBackup: 14 | case JoCartridgeMemoryBackup: 15 | case JoExternalDeviceBackup: 16 | return saturnIsBackupDeviceAvailable(backupDevice); 17 | 18 | case ActionReplayBackup: 19 | return actionReplayIsBackupDeviceAvailable(backupDevice); 20 | 21 | case SatiatorBackup: 22 | return satiatorIsBackupDeviceAvailable(backupDevice); 23 | 24 | case MODEBackup: 25 | return modeIsBackupDeviceAvailable(backupDevice); 26 | 27 | case CdMemoryBackup: 28 | return true; // always assume CD backups are available 29 | 30 | default: 31 | sgc_core_error("Invalid backup device specified!! %d\n", backupDevice); 32 | return false; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | // returns true if the backup device is writeable 39 | // TODO: each device should implement this function 40 | bool isBackupDeviceWriteable(int backupDevice) 41 | { 42 | switch(backupDevice) 43 | { 44 | // writeable 45 | case JoInternalMemoryBackup: 46 | case JoCartridgeMemoryBackup: 47 | case JoExternalDeviceBackup: 48 | case SatiatorBackup: 49 | case MODEBackup: 50 | return true; 51 | 52 | // non-writeable 53 | case CdMemoryBackup: // cd is never writeable 54 | return false; 55 | 56 | case ActionReplayBackup: // flashing AR is nontrivial, a ton of work to support 57 | return false; 58 | 59 | default: 60 | sgc_core_error("Invalid backup device specified!! %d\n", backupDevice); 61 | return false; 62 | } 63 | 64 | return false; 65 | } 66 | 67 | // queries the saves on the backup device and fills out the saves array 68 | int listSaveFiles(int backupDevice, PSAVES saves, unsigned int numSaves) 69 | { 70 | switch(backupDevice) 71 | { 72 | case JoInternalMemoryBackup: 73 | case JoCartridgeMemoryBackup: 74 | case JoExternalDeviceBackup: 75 | return saturnListSaveFiles(backupDevice, saves, numSaves); 76 | 77 | case ActionReplayBackup: 78 | return actionReplayListSaveFiles(backupDevice, saves, numSaves); 79 | 80 | case SatiatorBackup: 81 | return satiatorListSaveFiles(backupDevice, saves, numSaves); 82 | 83 | case MODEBackup: 84 | return modeListSaveFiles(backupDevice, saves, numSaves); 85 | 86 | case CdMemoryBackup: 87 | return cdListSaveFiles(backupDevice, saves, numSaves); 88 | 89 | default: 90 | sgc_core_error("Invalid backup device specified!! %d\n", backupDevice); 91 | return -1; 92 | } 93 | 94 | return -1; 95 | } 96 | 97 | // reads the specified save game from the backup device 98 | int readSaveFile(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outSize) 99 | { 100 | switch(backupDevice) 101 | { 102 | case JoInternalMemoryBackup: 103 | case JoCartridgeMemoryBackup: 104 | case JoExternalDeviceBackup: 105 | return saturnReadSaveFile(backupDevice, filename, outBuffer, outSize); 106 | 107 | case ActionReplayBackup: 108 | return actionReplayReadSaveFile(backupDevice, filename, outBuffer, outSize); 109 | 110 | case SatiatorBackup: 111 | return satiatorReadSaveFile(backupDevice, filename, outBuffer, outSize); 112 | 113 | case MODEBackup: 114 | return modeReadSaveFile(backupDevice, filename, outBuffer, outSize); 115 | 116 | case CdMemoryBackup: 117 | return cdReadSaveFile(backupDevice, filename, outBuffer, outSize); 118 | 119 | default: 120 | sgc_core_error("Invalid backup device specified!! %d\n", backupDevice); 121 | return -1; 122 | } 123 | 124 | return -1; 125 | } 126 | 127 | // write the save game to the backup device 128 | int writeSaveFile(int backupDevice, char* filename, unsigned char* inBuffer, unsigned int inSize) 129 | { 130 | switch(backupDevice) 131 | { 132 | case JoInternalMemoryBackup: 133 | case JoCartridgeMemoryBackup: 134 | case JoExternalDeviceBackup: 135 | return saturnWriteSaveFile(backupDevice, filename, inBuffer, inSize); 136 | 137 | case SatiatorBackup: 138 | return satiatorWriteSaveFile(backupDevice, filename, inBuffer, inSize); 139 | 140 | case MODEBackup: 141 | return modeWriteSaveFile(backupDevice, filename, inBuffer, inSize); 142 | 143 | case CdMemoryBackup: 144 | return -1; 145 | 146 | default: 147 | sgc_core_error("Invalid backup device specified!! %d\n", backupDevice); 148 | return -1; 149 | } 150 | } 151 | 152 | // delete the save from the backup device 153 | int deleteSaveFile(int backupDevice, char* filename) 154 | { 155 | switch(backupDevice) 156 | { 157 | case JoInternalMemoryBackup: 158 | case JoCartridgeMemoryBackup: 159 | case JoExternalDeviceBackup: 160 | return saturnDeleteSaveFile(backupDevice, filename); 161 | 162 | case SatiatorBackup: 163 | return satiatorDeleteSaveFile(backupDevice, filename); 164 | 165 | case MODEBackup: 166 | return modeDeleteSaveFile(backupDevice, filename); 167 | 168 | case ActionReplayBackup: 169 | return actionReplayDeleteSaveFile(backupDevice, filename); 170 | 171 | case CdMemoryBackup: 172 | return -1; 173 | 174 | default: 175 | sgc_core_error("Invalid backup device specified!! %d\n", backupDevice); 176 | return -1; 177 | } 178 | 179 | return -1; 180 | } 181 | 182 | // format a backup device 183 | // TODO: each device should implement this function 184 | int formatDevice(int backupDevice) 185 | { 186 | bool result = false; 187 | 188 | switch(backupDevice) 189 | { 190 | case JoInternalMemoryBackup: 191 | case JoCartridgeMemoryBackup: 192 | case JoExternalDeviceBackup: 193 | break; 194 | default: 195 | { 196 | sgc_core_error("Invalid device to format!!"); 197 | return -1; 198 | } 199 | } 200 | 201 | result = jo_backup_mount(backupDevice); 202 | if(result == false) 203 | { 204 | char* deviceName = NULL; 205 | getBackupDeviceName(backupDevice, &deviceName); 206 | 207 | sgc_core_error("Failed to mount %s!!", deviceName); 208 | return -2; 209 | } 210 | 211 | result = jo_backup_format_device(backupDevice); 212 | if(result == false) 213 | { 214 | sgc_core_error("Failed to format device!!"); 215 | return -3; 216 | } 217 | 218 | return 0; 219 | } 220 | 221 | // get device name from device id 222 | int getBackupDeviceName(unsigned int backupDevice, char** deviceName) 223 | { 224 | switch(backupDevice) 225 | { 226 | case JoInternalMemoryBackup: 227 | *deviceName = "Internal Memory"; 228 | break; 229 | case JoCartridgeMemoryBackup: 230 | *deviceName = "Cartridge Memory"; 231 | break; 232 | case JoExternalDeviceBackup: 233 | *deviceName = "External Device"; 234 | break; 235 | case ActionReplayBackup: 236 | *deviceName = "Action Replay (Read-Only)"; 237 | break; 238 | case SatiatorBackup: 239 | *deviceName = "Satiator"; 240 | break; 241 | case MODEBackup: 242 | *deviceName = "MODE"; 243 | break; 244 | case CdMemoryBackup: 245 | *deviceName = "CD File System"; 246 | break; 247 | case MemoryBackup: 248 | *deviceName = "RAM"; 249 | break; 250 | 251 | default: 252 | sgc_core_error("Invalid backup device specified!! %d\n", backupDevice); 253 | return -1; 254 | } 255 | 256 | return 0; 257 | } 258 | 259 | // check if the filename ends with ".BUP" 260 | bool isFileBUPExt(char* filename) 261 | { 262 | unsigned int len = 0; 263 | char* ext = NULL; 264 | int result = 0; 265 | 266 | if(!filename) 267 | { 268 | sgc_core_error("Filename cannot be NULL"); 269 | return false; 270 | } 271 | 272 | len = strlen(filename); 273 | if(len < sizeof(BUP_EXTENSION)) 274 | { 275 | return false; 276 | } 277 | 278 | ext = &filename[len - strlen(BUP_EXTENSION)]; 279 | result = strcmp(ext, BUP_EXTENSION); 280 | if(result == 0) 281 | { 282 | return true; 283 | } 284 | 285 | return false; 286 | } 287 | 288 | // validates the BUP header and extracts the various fields contained within 289 | int parseBupHeaderValues(PBUP_HEADER bupHeader, unsigned int totalBupSize, char* saveName, char* saveComment, unsigned char* saveLanguage, unsigned int* saveDate, unsigned int* saveSize, unsigned short* saveBlocks) 290 | { 291 | int result = 0; 292 | 293 | if(!bupHeader || !totalBupSize || !saveName || !saveComment || !saveLanguage || !saveDate || !saveSize || !saveBlocks) 294 | { 295 | return -1; 296 | } 297 | 298 | if(totalBupSize < BUP_HEADER_SIZE) 299 | { 300 | return -2; 301 | } 302 | 303 | result = memcmp(bupHeader->magic, VMEM_MAGIC_STRING, sizeof(bupHeader->magic)); 304 | if(result != 0) 305 | { 306 | return -3; 307 | } 308 | 309 | if(totalBupSize != bupHeader->dir.datasize + BUP_HEADER_SIZE) 310 | { 311 | // return -4 312 | } 313 | 314 | memcpy(saveName, bupHeader->dir.filename, sizeof(bupHeader->dir.filename)); 315 | saveName[sizeof(bupHeader->dir.filename) -1] = '\0'; 316 | 317 | memcpy(saveComment, bupHeader->dir.comment, sizeof(bupHeader->dir.comment)); 318 | saveComment[sizeof(bupHeader->dir.comment) -1] = '\0'; 319 | 320 | *saveLanguage = bupHeader->dir.language; 321 | *saveDate = bupHeader->dir.date; 322 | *saveSize = bupHeader->dir.datasize; 323 | *saveBlocks = bupHeader->dir.blocksize; 324 | 325 | return 0; 326 | } 327 | -------------------------------------------------------------------------------- /backends/backend.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "../util.h" 6 | #include "../bup_header.h" 7 | 8 | #define MAX_SAVE_SIZE (256 * 1024) // according to Cafe-Alpha this is the maximum size supported by the BIOS 9 | #define MAX_SAVE_FILENAME 12 10 | #define MAX_SAVE_COMMENT 11 11 | #define MAX_FILENAME 32 12 | #define MAX_SAVES 255 13 | 14 | // all devices should standardize on this directory 15 | // for storing saves 16 | #define SAVES_DIRECTORY "SATSAVES" 17 | 18 | #define SatiatorBackup (JoExternalDeviceBackup + 1) 19 | #define CdMemoryBackup (SatiatorBackup + 1) 20 | #define MemoryBackup (CdMemoryBackup + 1) 21 | #define MODEBackup (MemoryBackup + 1) 22 | #define ActionReplayBackup (MODEBackup + 1) 23 | 24 | // meta data related to save files 25 | typedef struct _SAVES { 26 | char filename[MAX_FILENAME]; // filename on the medium. Will have .BUP extension on CD FS and ODEs. 27 | char name[MAX_SAVE_FILENAME]; // selected save name 28 | char comment[MAX_SAVE_COMMENT]; // selected save comment 29 | unsigned char language; 30 | unsigned int date; 31 | unsigned int datasize; 32 | unsigned short blocksize; 33 | } SAVES, *PSAVES; 34 | 35 | typedef int (*BACKUP_LIST_FN)(int backupDevice, PSAVES saves, unsigned int numSaves); 36 | typedef int (*BACKUP_READ_FN)(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outSize); 37 | typedef int (*BACKUP_WRITE_FN)(int backupDevice, char* filename, unsigned char* inBuffer, unsigned int inSize); 38 | typedef int (*BACKUP_DELETE_FN)(int backupDevice, char* filename); 39 | typedef int (*BACKUP_FORMAT_FN)(int backupDevice); 40 | 41 | typedef struct _BACKUP_MEDIUM 42 | { 43 | int backupDevice; 44 | char* deviceName; 45 | 46 | BACKUP_LIST_FN listSaveFiles; 47 | BACKUP_READ_FN readSaveFile; 48 | BACKUP_WRITE_FN writeSaveFile; 49 | BACKUP_DELETE_FN deleteSaveFile; 50 | BACKUP_FORMAT_FN formatDevice; 51 | } BACKUP_MEDIUM, *PBACKUP_MEDIUM; 52 | 53 | // access the save data 54 | bool isBackupDeviceAvailable(int backupDevice); 55 | bool isBackupDeviceWriteable(int backupDevice); 56 | int listSaveFiles(int backupDevice, PSAVES saves, unsigned int numSaves); 57 | int readSaveFile(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outSize); 58 | int writeSaveFile(int backupDevice, char* filename, unsigned char* inBuffer, unsigned int inSize); 59 | int deleteSaveFile(int backupDevice, char* filename); 60 | int formatDevice(int backupDevice); 61 | 62 | // helper functions 63 | int getBackupDeviceName(unsigned int backupDevice, char** deviceName); 64 | bool isFileBUPExt(char* filename); 65 | int parseBupHeaderValues(PBUP_HEADER bupHeader, unsigned int totalBupSize, char* saveName, char* saveComment, unsigned char* saveLanguage, unsigned int* saveDate, unsigned int* saveSize, unsigned short* saveBlocks); 66 | 67 | // prototypes to keep compiler happy 68 | int snprintf(char *str, size_t size, const char *format, ...); 69 | 70 | -------------------------------------------------------------------------------- /backends/cd.c: -------------------------------------------------------------------------------- 1 | #include "cd.h" 2 | 3 | static SAVES g_CachedSaveFiles[255] = {0}; 4 | static unsigned int g_CachedSaveFilesCount = 0; 5 | 6 | // always return true for saves being present 7 | bool cdIsBackupDeviceAvailable(int backupDevice) 8 | { 9 | if(backupDevice != CdMemoryBackup) 10 | { 11 | return false; 12 | } 13 | 14 | return true; 15 | } 16 | 17 | // queries the saves on the CD device and fills out the saves array 18 | int cdListSaveFiles(int backupDevice, PSAVES saves, unsigned int numSaves) 19 | { 20 | unsigned int count = 0; 21 | GfsHn gfs = 0; 22 | char* sub_dir = SAVES_DIRECTORY; 23 | BUP_HEADER bupHeader = {0}; 24 | 25 | if(backupDevice != CdMemoryBackup) 26 | { 27 | return -1; 28 | } 29 | 30 | // check if we already cached the cd dir for faster perf 31 | if(g_CachedSaveFilesCount > 0) 32 | { 33 | count = MIN(numSaves, g_CachedSaveFilesCount); 34 | memcpy(saves, g_CachedSaveFiles, count * sizeof(SAVES)); 35 | return (int)count; 36 | } 37 | 38 | if (sub_dir != JO_NULL) 39 | { 40 | jo_fs_cd(sub_dir); 41 | } 42 | 43 | // Save-Game-Copier/issues/53 44 | // On large SATSAVES folder a blank screen appears for a while 45 | // making it appear as if SGC has hung 46 | jo_printf(2, 5, "Reading saves, please wait..."); 47 | 48 | // loop through the files on the directory 49 | for(unsigned int i = 0; i < numSaves; i++) 50 | { 51 | int numBytes = 0; 52 | char* filename = NULL; 53 | 54 | gfs = GFS_Open(i+2); 55 | if(gfs == NULL) 56 | { 57 | break; 58 | } 59 | 60 | // query the file size 61 | GFS_GetFileInfo(gfs, NULL, NULL, (Sint32*)&numBytes, NULL); 62 | GFS_Close(gfs); 63 | 64 | filename = (char*)GFS_IdToName(i+2); 65 | 66 | if(filename && numBytes) 67 | { 68 | bool result = false; 69 | 70 | result = isFileBUPExt(filename); 71 | if(result == false) 72 | { 73 | // not a .BUP file, skip 74 | //sgc_core_error("Not a .BUP %s", filename); 75 | continue; 76 | } 77 | 78 | result = cdReadBUPHeader(filename, &bupHeader); 79 | if(result != 0) 80 | { 81 | sgc_core_error("Failed to read .BUP %s (%d)", filename, result); 82 | continue; 83 | } 84 | 85 | result = parseBupHeaderValues(&bupHeader, numBytes, saves[count].name, saves[count].comment, &saves[count].language, &saves[count].date, &saves[count].datasize, &saves[count].blocksize); 86 | if(result != 0) 87 | { 88 | sgc_core_error("parseBup fail %s (%d)", filename, result); 89 | continue; 90 | } 91 | 92 | // copy over the filename as well 93 | strncpy((char*)saves[count].filename, filename, MAX_FILENAME); 94 | count++; 95 | } 96 | } 97 | 98 | // "erase" the waiting message 99 | jo_printf(2, 5, " "); 100 | 101 | // cache the cd results for faster perf 102 | g_CachedSaveFilesCount = MIN((unsigned int)count, COUNTOF(g_CachedSaveFiles)); 103 | memcpy(g_CachedSaveFiles, saves, g_CachedSaveFilesCount * sizeof(SAVES)); 104 | 105 | if (sub_dir != JO_NULL) 106 | { 107 | jo_fs_cd(JO_PARENT_DIR); 108 | } 109 | 110 | return count; 111 | } 112 | 113 | // read the save game 114 | int cdReadSaveFile(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outSize) 115 | { 116 | unsigned char* saveData = NULL; 117 | char* sub_dir = SAVES_DIRECTORY; 118 | int length = 0; 119 | 120 | if(backupDevice != CdMemoryBackup) 121 | { 122 | return -1; 123 | } 124 | 125 | if(outBuffer == NULL || filename == NULL) 126 | { 127 | sgc_core_error("Save file data buffer is NULL!!"); 128 | return -1; 129 | } 130 | 131 | if(outSize == 0 || outSize > MAX_SAVE_SIZE) 132 | { 133 | sgc_core_error("Save file size is invalid %d!!", outSize); 134 | return -2; 135 | } 136 | 137 | if(sub_dir != JO_NULL) 138 | { 139 | jo_fs_cd(sub_dir); 140 | } 141 | 142 | saveData = (unsigned char*)jo_fs_read_file(filename, &length); 143 | 144 | if(sub_dir != JO_NULL) 145 | { 146 | jo_fs_cd(JO_PARENT_DIR); 147 | } 148 | 149 | if(saveData != NULL) 150 | { 151 | // copy the save game data and free the jo engine buffer 152 | memcpy(outBuffer, saveData, length); 153 | jo_free(saveData); 154 | return 0; 155 | } 156 | 157 | // failed to read the save file 158 | return -3; 159 | } 160 | 161 | // read the bup header 162 | int cdReadBUPHeader(char* filename, PBUP_HEADER bupHeader) 163 | { 164 | int result = 0; 165 | jo_file joFile = {0}; 166 | bool openedFile = false; 167 | 168 | if(!filename || !bupHeader) 169 | { 170 | return -1; 171 | } 172 | 173 | result = jo_fs_open(&joFile, filename); 174 | if(result != true) 175 | { 176 | sgc_core_error("failed to open %s", filename); 177 | result = -2; 178 | goto exit; 179 | } 180 | 181 | openedFile = true; 182 | 183 | // read the .BUP header 184 | result = jo_fs_read_next_bytes(&joFile, (char*)bupHeader, sizeof(BUP_HEADER)); 185 | if(result < 0) 186 | { 187 | sgc_core_error("failed to read bup header %d", result); 188 | result = -3; 189 | goto exit; 190 | } 191 | 192 | if(result < (int)sizeof(BUP_HEADER)) 193 | { 194 | sgc_core_error("bup header is too small"); 195 | result = -4; 196 | goto exit; 197 | } 198 | 199 | result = 0; 200 | 201 | exit: 202 | if(openedFile == true) 203 | { 204 | jo_fs_close(&joFile); 205 | } 206 | 207 | return result; 208 | } 209 | 210 | -------------------------------------------------------------------------------- /backends/cd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend.h" 4 | 5 | bool cdIsBackupDeviceAvailable(int backupDevice); 6 | int cdListSaveFiles(int backupDevice, PSAVES fileSaves, unsigned int numSaves); 7 | int cdReadSaveFile(int backupDevice, char* filename, unsigned char* ouBuffer, unsigned int outBufSize); 8 | 9 | // helper functions 10 | int cdReadBUPHeader(char* filename, PBUP_HEADER bupHeader); 11 | 12 | -------------------------------------------------------------------------------- /backends/mode.c: -------------------------------------------------------------------------------- 1 | #include "mode.h" 2 | #include "mode/mode_intf.h" 3 | #include "STRING.H" 4 | #include "SEGA_CDC.H" 5 | 6 | // 7 | // MODE support contributed by Terraonion (https://github.com/Terraonion-dev) 8 | // 9 | 10 | // Adjust this to be sector aligned, as MODE transfers entire sector blocks so align to sector size (+1) add an extra sector block at the end (+16) 11 | static struct _SatDirList SatSaves[MAX_SAVES + 1 + 16] __attribute__((section(".bss"))); 12 | static unsigned char SectorBuffer[2048] __attribute__((section(".bss"))); 13 | const char* SaveDirectory = "0:/SATSAVES"; 14 | static char tmpFilename[64] __attribute__((section(".bss"))); 15 | 16 | // returns true if the backup device is found 17 | bool modeIsBackupDeviceAvailable(int backupDevice) 18 | { 19 | struct _MountStatus* ms; 20 | struct _VersionInfo* vi; 21 | 22 | if(backupDevice != MODEBackup) 23 | { 24 | return false; 25 | } 26 | 27 | Uint32* toc = (Uint32*)SectorBuffer; 28 | 29 | // TOC must be at least 60 minutes for the MODE interface to work 30 | CDC_TgetToc(toc); 31 | 32 | if((toc[101] & 0xFFFFFF) < 0x040000) 33 | { 34 | return false; 35 | } 36 | 37 | // Trying to open MODE interface in a disc with a <40000h toc will hang due to the seek address wait. 38 | MODE_Open(); 39 | 40 | ms = MODE_GetMountStatus(); // GetMountStatus returns NULL if the MODE response is not right 41 | vi = MODE_GetVersionInfo(); // Actually mi and vi will share the same pointer if right, but we are only using data from vi from now on 42 | 43 | modeExit(); 44 | 45 | // found MODE 46 | return ms != NULL && ((vi->Ver << 8) | vi->Subver) >= 0x0104; // Needs at least 1.04 version for the File IO Interface 47 | } 48 | 49 | // queries the saves on the MODE and fills out the saves array 50 | int modeListSaveFiles(int backupDevice, PSAVES saves, unsigned int numSaves) 51 | { 52 | int len = 0; 53 | int result = 0; 54 | unsigned int count = 0; 55 | BUP_HEADER bupHeader = {0}; 56 | 57 | if(backupDevice != MODEBackup) 58 | { 59 | return -1; 60 | } 61 | 62 | modeEnter(); 63 | 64 | result = MODE_ReadFileListing(SaveDirectory, SatSaves, MAX_SAVES); 65 | 66 | if(result < 0) 67 | { 68 | sgc_core_error("modeListSaveFiles: %d Failed to open SAVES directory", result); 69 | return -2; 70 | } 71 | 72 | // loop through the files in the directory 73 | for (int n = 0; n < result; ++n) 74 | { 75 | // skip directories 76 | if (SatSaves[n].Size == 0xFFFFFFFF) 77 | { 78 | continue; 79 | } 80 | 81 | // skip . and .. (and anything beginning with .) (MODE would have already filtered them) 82 | if (SatSaves[n].Name[0] == '.') 83 | { 84 | continue; 85 | } 86 | 87 | len = strlen(SatSaves[n].Name); 88 | 89 | if (len < 4) 90 | continue; 91 | 92 | //dunno what's wrong with that function 93 | /* 94 | result = isFileBUPExt(SatSaves[n].Name); 95 | if(result == false) 96 | { 97 | // not a .BUP file, skip 98 | continue; 99 | } 100 | */ 101 | 102 | if (memcmp(SatSaves[n].Name + len - 4, BUP_EXTENSION, 4) != 0) 103 | { 104 | continue; 105 | } 106 | 107 | strncpy((char*)saves[count].filename, SatSaves[n].Name, MAX_FILENAME); 108 | 109 | saves[count].datasize = SatSaves[n].Size; 110 | saves[count].blocksize = 0; 111 | 112 | count++; 113 | 114 | if (count >= numSaves) 115 | { 116 | break; 117 | } 118 | } 119 | 120 | for (unsigned int n = 0; n < count; ++n) 121 | { 122 | result = modeReadBUPHeader(saves[n].filename, &bupHeader); 123 | if (result != 0) 124 | { 125 | sgc_core_error("bup header %s", saves[n].filename); 126 | continue; 127 | } 128 | 129 | result = parseBupHeaderValues(&bupHeader, saves[n].datasize, saves[n].name, saves[n].comment, &saves[n].language, &saves[n].date, &saves[n].datasize, &saves[n].blocksize); 130 | if (result != 0) 131 | { 132 | sgc_core_error("Failed with %d %s", result, saves[n].filename); 133 | 134 | // BUGBUG: handle error conditions gracefully 135 | strncpy(saves[n].name, "Error", MAX_SAVE_FILENAME); 136 | continue; 137 | } 138 | } 139 | 140 | modeExit(); 141 | 142 | return count; 143 | } 144 | 145 | // copies the specified MODE save game to the saveFileData buffer 146 | int modeReadSaveFile(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outSize) 147 | { 148 | int result = 0; 149 | 150 | if(backupDevice != MODEBackup) 151 | { 152 | return -1; 153 | } 154 | 155 | if(outBuffer == NULL || filename == NULL) 156 | { 157 | sgc_core_error("modeReadSaveFile: Save file data buffer is NULL!!"); 158 | return -1; 159 | } 160 | 161 | if(outSize == 0 || outSize > MAX_SAVE_SIZE) 162 | { 163 | sgc_core_error("modeReadSaveFile: Save file size is invalid %d!!", outSize); 164 | return -2; 165 | } 166 | 167 | modeEnter(); 168 | 169 | strcpy(tmpFilename, SaveDirectory); 170 | strcat(tmpFilename, "/"); 171 | strcat(tmpFilename, filename); 172 | 173 | result = MODE_OpenFile(tmpFilename, 0); 174 | if(result != 0) 175 | { 176 | modeExit(); 177 | sgc_core_error("modeReadSaveFile: Failed to open MODE file!!"); 178 | return -2; 179 | } 180 | 181 | for(unsigned int bytesRead = 0; bytesRead < outSize; ) 182 | { 183 | unsigned int count; 184 | 185 | count = MIN(outSize - bytesRead, 2048); 186 | 187 | MODE_ReadFile(SectorBuffer, bytesRead, 2048); 188 | 189 | for (unsigned int c = 0; c < count; ++c) 190 | { 191 | outBuffer[bytesRead + c] = SectorBuffer[c]; 192 | } 193 | 194 | bytesRead += count; 195 | } 196 | 197 | MODE_CloseFile(); 198 | 199 | modeExit(); 200 | 201 | return 0; 202 | } 203 | 204 | // write the save game to the MODE 205 | int modeWriteSaveFile(int backupDevice, char* filename, unsigned char* inBuffer, unsigned int inSize) 206 | { 207 | int result = 0; 208 | 209 | if(backupDevice != MODEBackup) 210 | { 211 | return -1; 212 | } 213 | 214 | if(filename == NULL) 215 | { 216 | sgc_core_error("modeWriteSaveFile: Save file data buffer is NULL!!"); 217 | return -1; 218 | } 219 | 220 | if(inBuffer == NULL || filename == NULL) 221 | { 222 | sgc_core_error("modeWriteSaveFile: Save file size is invalid %d!!", inSize); 223 | return -2; 224 | } 225 | 226 | modeEnter(); 227 | 228 | strcpy(tmpFilename, SaveDirectory); 229 | strcat(tmpFilename, "/"); 230 | strcat(tmpFilename, filename); 231 | 232 | result = MODE_OpenFile(tmpFilename, 1); 233 | if (result != 0) 234 | { 235 | modeExit(); 236 | sgc_core_error("modeWriteSaveFile: Failed to open MODE file for writing!!"); 237 | return -2; 238 | } 239 | 240 | for(unsigned int bytesWritten = 0; bytesWritten < inSize; ) 241 | { 242 | unsigned int count; 243 | 244 | count = MIN(inSize - bytesWritten, 2048); 245 | 246 | MODE_WriteFile(inBuffer + bytesWritten, bytesWritten, count); 247 | 248 | bytesWritten += count; 249 | } 250 | 251 | MODE_CloseFile(); 252 | 253 | modeExit(); 254 | 255 | return 0; 256 | } 257 | 258 | // delete the save 259 | int modeDeleteSaveFile(int backupDevice, char* filename) 260 | { 261 | if(backupDevice != MODEBackup) 262 | { 263 | return -1; 264 | } 265 | 266 | if(filename == NULL) 267 | { 268 | sgc_core_error("modeDeleteSaveFile: Filename is NULL!!"); 269 | return -1; 270 | } 271 | 272 | modeEnter(); 273 | 274 | strcpy(tmpFilename, SaveDirectory); 275 | strcat(tmpFilename, "/"); 276 | strcat(tmpFilename, filename); 277 | 278 | MODE_DeleteFile(tmpFilename); 279 | 280 | modeExit(); 281 | 282 | return 0; 283 | } 284 | 285 | int modeEnter(void) 286 | { 287 | MODE_Open(); 288 | 289 | return 0; 290 | } 291 | 292 | int modeExit(void) 293 | { 294 | MODE_Close(); 295 | 296 | return 0; 297 | } 298 | 299 | void MODE_WaitVSync(void) 300 | { 301 | slSynch(); 302 | } 303 | 304 | // read the bup header 305 | int modeReadBUPHeader(char* filename, PBUP_HEADER bupHeader) 306 | { 307 | int result = 0; 308 | 309 | if(!filename || !bupHeader) 310 | { 311 | return -1; 312 | } 313 | 314 | strcpy(tmpFilename, SaveDirectory); 315 | strcat(tmpFilename, "/"); 316 | strcat(tmpFilename, filename); 317 | 318 | result = MODE_OpenFile(tmpFilename, 0); 319 | if(result != 0) 320 | { 321 | sgc_core_error("modeBUP: Failed to open MODE file!!"); 322 | return -2; 323 | } 324 | 325 | //MODE always reads in sector chunks, so we would be overwritting the stack if reading directly to the header buffer 326 | MODE_ReadFile(SectorBuffer, 0, 2048); 327 | memcpy((unsigned char*)bupHeader, SectorBuffer, sizeof(BUP_HEADER)); 328 | 329 | MODE_CloseFile(); 330 | 331 | return 0; 332 | } 333 | -------------------------------------------------------------------------------- /backends/mode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend.h" 4 | 5 | // 6 | // MODE support contributed by Terraonion (https://github.com/Terraonion-dev) 7 | // 8 | 9 | bool modeIsBackupDeviceAvailable(int backupDevice); 10 | int modeListSaveFiles(int backupDevice, PSAVES fileSaves, unsigned int numSaves); 11 | int modeReadSaveFile(int backupDevice, char* filename, unsigned char* ouBuffer, unsigned int outBufSize); 12 | int modeWriteSaveFile(int backupDevice, char* filename, unsigned char* saveData, unsigned int saveDataLen); 13 | int modeDeleteSaveFile(int backupDevice, char* filename); 14 | 15 | // helper functions 16 | int modeEnter(void); 17 | int modeExit(void); 18 | int modeReadBUPHeader(char* filename, PBUP_HEADER bupHeader); 19 | -------------------------------------------------------------------------------- /backends/mode/mode_intf.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/backends/mode/mode_intf.a -------------------------------------------------------------------------------- /backends/mode/mode_intf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MODE SATURN BETA COMMAND INTERFACE. 3 | * This library depends on the SEGA_CDC library 4 | * If you use it in your own project and find a bug, or need the library source code, please contact us through discord 5 | */ 6 | 7 | struct _SatDirList 8 | { 9 | char Name[124]; 10 | Uint32 Size; 11 | }; 12 | 13 | struct _SatGameList 14 | { 15 | char Name[120]; 16 | Uint32 NameChecksum; 17 | Uint8 Unused; 18 | Uint8 Flags; 19 | Uint16 GameID; 20 | }; 21 | 22 | 23 | struct _MountStatus 24 | { 25 | Uint8 SDStatus; 26 | Uint8 HDDStatus; 27 | Uint8 USBDetStatus; 28 | Uint8 Unused[5]; 29 | }; 30 | 31 | struct _VersionInfo 32 | { 33 | Uint32 Serial; 34 | Uint8 Ver; 35 | Uint8 Subver; 36 | Uint8 Build; 37 | Uint8 dummy; 38 | }; 39 | 40 | //Open and close the command stream. If you need to read from CDRom, you should close the command stream first 41 | void MODE_Open(); 42 | void MODE_Close(); 43 | 44 | //Implement this function in your code with the wait for vsync function in the library you are using (slSynch() for SGL, SCL_DisplayFrame() for SBL) 45 | extern void MODE_WaitVSync(); 46 | 47 | struct _MountStatus *MODE_GetMountStatus(); 48 | struct _VersionInfo* MODE_GetVersionInfo(); 49 | 50 | 51 | 52 | 53 | unsigned char MODE_OpenFile(const char* filename, unsigned char forwrite); 54 | void MODE_DeleteFile(const char* filename); 55 | void MODE_CloseFile(); 56 | void MODE_WriteFile(unsigned char* buffer, unsigned int offset, unsigned int size); 57 | void MODE_ReadFile(unsigned char* buffer, unsigned int offset, unsigned int size); 58 | Sint32 MODE_ReadFileListing(const char* path, struct _SatDirList* list, int maxfiles); 59 | /* 60 | * USING THE FILE I/O INTERFACE 61 | * File IO is a simple interface to read/write data from SD and HDD. The basic sequence is: 62 | * 63 | * MODE_OpenFile() 64 | * Do some operations (MODE_WriteFile, MODE_ReadFile) 65 | * MODE_CloseFile() 66 | * 67 | * Only one file can be open at a time. Also ensure to call MODE_CloseFile() for every MODE_OpenFile(), otherwise descriptors may leak internally. 68 | * MODE_OpenFile returns 0 on success, any other value is an error. 69 | * drive "0:/" is SD card, drive "1:/" is HDD. To check which drives are mounted, you can use MODE_GetMountStatus. 70 | * If SDStatus or HDDStatus are 0, that means they are present and mounted. Other values mean error. 71 | * If USBDetStatus is not 13 (0xD) that means MODE is still scanning for SATA and USB disks, so don't attempt to access drive 1:/ , and also HDDStatus is not accurate. 72 | * 73 | * There is no current directory in MODE, so all paths must be absolute! 74 | * 75 | * MODE_ReadFileListing will list the files in the current directory. It will skip . and .. entries and any hidden file. 76 | * Size is 0xFFFFFFFF for directories. 77 | * Files are listed unsorted. 78 | * The path must NOT end with a /, otherwise it will fail (0:/SATSAVES work but 0:/SATSAVES/ doesn't) 79 | * 80 | * */ 81 | 82 | 83 | //Gamelist interface 84 | void MODE_Initialize(); 85 | Sint32 MODE_ReadDirectoryListing(struct _SatGameList* gamelist); 86 | int MODE_AddGame(Uint16 gameid, Uint8* ndiscs); 87 | void MODE_ClearQueue(); 88 | int MODE_GetGameFlags(Uint8 disc); 89 | void MODE_MountGame(); 90 | //Pass 0xFFFE or 0xFEFF to go to the UP directory. 91 | int MODE_SelectDir(Uint16 gameid); 92 | void MODE_HWReset(); 93 | 94 | /* 95 | * USING THE GAMELIST INTERFACE (BETA) 96 | * For the game list interface and launch to work properly, MODE_Initialize() must be called prior to any calls, as it setups the internal configuration and console region 97 | * 98 | * Then, obtain the directory list with MODE_ReadDirectoryListing. Prepare a large enough structure to hold the data (3000 max) it returns the number of files in the list. 99 | * If the listing belongs to a sub folder, then the first entry will always be "UP" with 0xFFFE game id. 100 | * Returned list is not sorted. 101 | * 102 | * Then, when selecting a game, call AddGame, passing the gameid from the gamelist struct. the resulting values can be: 103 | * 0: the game was a saturn game and has been queued (ndiscs receives the current disc count in the queue, after adding the game) 104 | * -1 or 0xFF: the selected item was a folder with more discs, so you can enter it by passing gameid to MODE_SelectDir(), then do MODE_ReadDirectoryListing again to get the games in the current directory. 105 | * -2 or 0xFE: the selected item is a disc, but it's not a saturn game (CDDA or other data cd), but has been queued anyways 106 | * -3 or 0xFD: the selected item is not a disc. 107 | * 108 | * If you want to launch the current queue (right after adding a game, or when the users selects to launch), first you must call MODE_GetGameFlags() with the disc number to load (0 to 7). This will 109 | * parse the cue file and preload the system sectors. 110 | * Once you call this function, you won't be able to read the CD anymore, so ensure all your assets are in ram. 111 | * The returned value is the value set in Flags in MODE.CFG file in the disc folder. 112 | * 113 | * Then call MODE_MountGame(). From now on, the game disc is "inserted", and the CD Block will have read its TOC. 114 | * 115 | * Then you must load the game into memory (fastload) or fully reset the system. You can just call MODE_HwReset() to fully reset the system via SMPC. 116 | * 117 | */ 118 | 119 | -------------------------------------------------------------------------------- /backends/sat.c: -------------------------------------------------------------------------------- 1 | // Saturn Allocation Table (SAT) save partition parsing 2 | #include 3 | #include "backend.h" 4 | #include "sat.h" 5 | 6 | // given a save size in bytes, calculate how many save blocks are required 7 | // block size is the size of the block including the 4 byte tag field 8 | int calcNumBlocks(unsigned int saveSize, unsigned int blockSize, unsigned int* numSaveBlocks) 9 | { 10 | unsigned int totalBytes = 0; 11 | unsigned int fixedBytes = 0; 12 | unsigned int numBlocks = 0; 13 | unsigned int numBlocks2 = 0; 14 | 15 | if(saveSize == 0) 16 | { 17 | return -1; 18 | } 19 | 20 | if(numSaveBlocks == NULL) 21 | { 22 | return -2; 23 | } 24 | 25 | // block size must be 64-byte aligned 26 | if((blockSize % 0x40) != 0 || blockSize == 0) 27 | { 28 | return -3; 29 | } 30 | 31 | // 32 | // The stored save consists of: 33 | // - the save metadata header 34 | // - the variable length SAT table 35 | // - the save data itself 36 | // 37 | // What makes this tricky to compute is that the SAT table itself is stored 38 | // on the blocks 39 | // 40 | 41 | fixedBytes = sizeof(SAT_START_BLOCK_HEADER) - SAT_TAG_SIZE + saveSize; 42 | 43 | // calculate the number of SAT table entries required 44 | numBlocks = fixedBytes / (blockSize - SAT_TAG_SIZE); 45 | 46 | // +1 for 0x0000 SAT table terminator 47 | totalBytes = fixedBytes + ((numBlocks + 1) * sizeof(unsigned short)); 48 | 49 | // we need to do this in a loop because it's possible that by adding a SAT 50 | // table entry we increased the number of bytes we need such that we need 51 | // yet another SAT table entry. 52 | do 53 | { 54 | numBlocks = numBlocks2; 55 | totalBytes = fixedBytes + ((numBlocks + 1) * sizeof(unsigned short)); 56 | numBlocks2 = totalBytes / (blockSize - SAT_TAG_SIZE); 57 | 58 | if(totalBytes % (blockSize - SAT_TAG_SIZE)) 59 | { 60 | numBlocks2++; 61 | } 62 | 63 | }while(numBlocks != numBlocks2); 64 | 65 | *numSaveBlocks = numBlocks; 66 | 67 | return 0; 68 | } 69 | 70 | // find all saves in the partition 71 | int satListSaves(unsigned char* partitionBuf, unsigned int partitionSize, unsigned int blockSize, PSAVES saves, unsigned int numSaves) 72 | { 73 | PSAT_START_BLOCK_HEADER metadata = NULL; 74 | unsigned int savesFound = 0; 75 | 76 | if(partitionBuf == NULL) 77 | { 78 | sgc_core_error("Buffer can't be NULL"); 79 | return -1; 80 | } 81 | 82 | if(partitionSize == 0 || (partitionSize % blockSize) != 0) 83 | { 84 | sgc_core_error("Invalid partition size\n"); 85 | return -2; 86 | } 87 | 88 | for(unsigned int i = 0; i < partitionSize; i += blockSize) 89 | { 90 | metadata = (PSAT_START_BLOCK_HEADER)(partitionBuf + i); 91 | 92 | // validate range 93 | if((unsigned char*)metadata < partitionBuf || (unsigned char*)metadata >= partitionBuf + partitionSize) 94 | { 95 | sgc_core_error("%x %x %x\n", partitionBuf, metadata, partitionSize); 96 | return -3; 97 | } 98 | 99 | // every save starts with a tag 100 | if(metadata->tag == SAT_START_BLOCK_TAG) 101 | { 102 | // save name 103 | // BUGBUG: figure out how to set filename (versus savename) 104 | memcpy(saves[savesFound].name, metadata->saveName, MAX_SAVE_FILENAME - 1); 105 | saves[savesFound].name[MAX_SAVE_FILENAME -1] = '\0'; 106 | 107 | snprintf(saves[savesFound].filename, MAX_FILENAME - 1, "%s.BUP", saves[savesFound].name); 108 | saves[savesFound].filename[MAX_FILENAME -1] = '\0'; 109 | 110 | // langugae 111 | saves[savesFound].language = metadata->language; 112 | memcpy(saves[savesFound].comment, metadata->comment, MAX_SAVE_COMMENT - 1); 113 | saves[savesFound].date = metadata->date; 114 | saves[savesFound].datasize = metadata->saveSize; 115 | 116 | // blocksize isn't needed 117 | saves[savesFound].blocksize = 0; 118 | 119 | //sgc_core_error("%s %d %s %x %d", savename, language, comment, date, saveSize); 120 | 121 | savesFound++; 122 | 123 | // check if we are finished looking for saves 124 | if(savesFound >= numSaves) 125 | { 126 | // no more room in our saves array 127 | return savesFound; 128 | } 129 | } 130 | } 131 | 132 | return savesFound; 133 | } 134 | 135 | // locate a save start block based on save name 136 | int getSaveStartBlock(unsigned char* partitionBuf, unsigned int partitionSize, unsigned int blockSize, char* saveName, PSAT_START_BLOCK_HEADER* metadata) 137 | { 138 | if(partitionBuf == NULL) 139 | { 140 | return -1; 141 | } 142 | 143 | // block size must be 64-byte aligned 144 | if((blockSize % 0x40) != 0 || blockSize == 0) 145 | { 146 | return -2; 147 | } 148 | 149 | if(partitionSize == 0 || (partitionSize % blockSize) != 0) 150 | { 151 | return -3; 152 | } 153 | 154 | if(saveName == NULL || metadata == NULL) 155 | { 156 | return -4; 157 | } 158 | 159 | // parse through all blocks looking for a save start tag 160 | for(unsigned int i = 0; i < partitionSize; i += blockSize) 161 | { 162 | *metadata = (PSAT_START_BLOCK_HEADER)(partitionBuf + i); 163 | 164 | // start tag 165 | if((*metadata)->tag == SAT_START_BLOCK_TAG) 166 | { 167 | // found a save 168 | (*metadata)->date = (*metadata)->date; 169 | (*metadata)->saveSize = (*metadata)->saveSize; 170 | 171 | // found a save start block, check if it's for our game 172 | if(strncmp((*metadata)->saveName, saveName, SAT_MAX_SAVE_NAME) == 0) 173 | { 174 | return 0; 175 | } 176 | } 177 | } 178 | 179 | // save not found 180 | return -5; 181 | } 182 | 183 | // returns the SAT blocks on success. Must be freed by caller 184 | int getSATBlocks(unsigned char* partitionBuf, unsigned int partitionSize, unsigned int blockSize, PSAT_START_BLOCK_HEADER metadata, PSAT_BLOCK* satBlocks) 185 | { 186 | unsigned int numSatBlocks = 0; 187 | unsigned int writtenSatEntries = 0; 188 | unsigned int curSatTableIndex = 0; 189 | unsigned short firstEntry = 0; 190 | int result = 0; 191 | 192 | if(partitionBuf == NULL) 193 | { 194 | return -1; 195 | } 196 | 197 | // block size must be 64-byte aligned 198 | if((blockSize % 0x40) != 0 || blockSize == 0) 199 | { 200 | return -2; 201 | } 202 | 203 | if(partitionSize == 0 || (partitionSize % blockSize) != 0) 204 | { 205 | return -3; 206 | } 207 | 208 | if(metadata == NULL || satBlocks == NULL) 209 | { 210 | return -4; 211 | } 212 | 213 | result = calcNumBlocks(metadata->saveSize, blockSize, &numSatBlocks); 214 | if(result < 0) 215 | { 216 | return -5; 217 | } 218 | 219 | // +1 for the first SAT entry which isn't included 220 | numSatBlocks++; 221 | 222 | // check for integer overflow 223 | if(numSatBlocks > numSatBlocks * sizeof(SAT_BLOCK)) 224 | { 225 | return -6; 226 | } 227 | 228 | *satBlocks = (PSAT_BLOCK)jo_malloc(numSatBlocks * sizeof(SAT_BLOCK)); 229 | if(*satBlocks == NULL) 230 | { 231 | return -3; 232 | } 233 | memset(*satBlocks, 0, numSatBlocks * sizeof(SAT_BLOCK)); 234 | 235 | // the first SAT entry isn't written in the SAT blocks array 236 | firstEntry = ((unsigned char*)metadata - partitionBuf)/SAT_PARTITION_SIZE; 237 | 238 | (*satBlocks)[0].blockNum = firstEntry; 239 | writtenSatEntries = 1; 240 | 241 | // loop through the blocks while we read the SAT table 242 | // this is tricky because we are writing to the satBlocks array while parsing it 243 | do 244 | { 245 | // make sure we aren't beyond the end of our array 246 | if(curSatTableIndex > writtenSatEntries) 247 | { 248 | jo_free(*satBlocks); 249 | *satBlocks = NULL; 250 | return -4; 251 | } 252 | 253 | // read SAT table entries from this block 254 | result = readSATFromBlock(partitionBuf, partitionSize, blockSize, curSatTableIndex, *satBlocks, numSatBlocks, &writtenSatEntries); 255 | if(result < 0) 256 | { 257 | jo_free(*satBlocks); 258 | *satBlocks = NULL; 259 | return -5; 260 | } 261 | 262 | curSatTableIndex++; 263 | 264 | }while(result != 0); 265 | 266 | // success 267 | return 0; 268 | } 269 | 270 | // reads the SAT table from satBlocks[currBlock] 271 | int readSATFromBlock(unsigned char* partitionBuf, unsigned int partitionSize, unsigned int blockSize, unsigned int currBlock, PSAT_BLOCK satBlocks, unsigned int maxBlocks, unsigned int* numBlocks) 272 | { 273 | PSAT_START_BLOCK_HEADER metadata = NULL; 274 | unsigned int startByte = 0; 275 | 276 | if(partitionBuf == NULL) 277 | { 278 | return -1; 279 | } 280 | 281 | // block size must be 64-byte aligned 282 | if((blockSize % 0x40) != 0 || blockSize == 0) 283 | { 284 | return -2; 285 | } 286 | 287 | if(partitionSize == 0 || (partitionSize % blockSize) != 0) 288 | { 289 | return -3; 290 | } 291 | 292 | if(satBlocks == NULL) 293 | { 294 | return -4; 295 | } 296 | 297 | if(currBlock >= *numBlocks) 298 | { 299 | return -5; 300 | } 301 | 302 | metadata = (PSAT_START_BLOCK_HEADER)(partitionBuf + (satBlocks[currBlock].blockNum * blockSize)); 303 | 304 | // validate we are still in range 305 | if(metadata == NULL || (unsigned char*)metadata < partitionBuf || (unsigned char*)metadata >= partitionBuf + partitionSize) 306 | { 307 | return -6; 308 | } 309 | 310 | // first entry 311 | if(currBlock == 0) 312 | { 313 | // first block must have the start tag 314 | if(metadata->tag != SAT_START_BLOCK_TAG) 315 | { 316 | return -1; 317 | } 318 | 319 | // flags will be needed for retrieving save data 320 | satBlocks[currBlock].flags |= (SAT_START_BLOCK_FLAG | SAT_TABLE_BLOCK_FLAG); 321 | 322 | // where the block's data starts 323 | // first block has the PSAT_START_BLOCK_HEADER 324 | startByte = sizeof(SAT_START_BLOCK_HEADER); 325 | } 326 | else 327 | { 328 | // other blocks must not have the continuation tag 329 | if(metadata->tag != SAT_CONTINUE_BLOCK_TAG) 330 | { 331 | return -1; 332 | } 333 | 334 | // flags will be needed for retrieving save data 335 | satBlocks[currBlock].flags |= (SAT_TABLE_BLOCK_FLAG); 336 | 337 | // where the block's data starts 338 | // continuation block's only have a 4-byte tag field 339 | startByte = SAT_TAG_SIZE; 340 | } 341 | 342 | // loop through the block, recording SAT table entries until you find the 343 | // 0x0000 terminator or reach the end of the block 344 | for(; startByte < blockSize; startByte += sizeof(unsigned short)) 345 | { 346 | unsigned short index = *(unsigned short*)((unsigned char*)metadata + startByte); 347 | 348 | // found the last entry 349 | if(*numBlocks >= maxBlocks) 350 | { 351 | return -1; 352 | } 353 | 354 | // found a table entry, record it 355 | satBlocks[*numBlocks].blockNum = index; 356 | //sgc_core_error("%x", index); 357 | (*numBlocks)++; 358 | 359 | // end index 360 | if(index == 0) 361 | { 362 | // we are at the end index 363 | satBlocks[currBlock].flags |= SAT_TABLE_END_BLOCK_FLAG; 364 | return 0; 365 | } 366 | } 367 | 368 | // didn't find end val, continue checking 369 | return 1; 370 | } 371 | 372 | // given a SAT table, reads the save to save data 373 | int getSATSave(unsigned char* partitionBuf, unsigned int partitionSize, unsigned int blockSize, PSAT_BLOCK satBlocks, unsigned char* saveData, unsigned int saveSize) 374 | { 375 | unsigned int bytesWritten = 0; 376 | unsigned int bytesToCopy = 0; 377 | unsigned int blockDataSize = 0x40 -4; 378 | unsigned char* block = NULL; 379 | 380 | if(partitionBuf == NULL) 381 | { 382 | return -1; 383 | } 384 | 385 | // block size must be 64-byte aligned 386 | if((blockSize % 0x40) != 0 || blockSize == 0) 387 | { 388 | return -2; 389 | } 390 | 391 | if(partitionSize == 0 || (partitionSize % blockSize) != 0) 392 | { 393 | return -3; 394 | } 395 | 396 | if(satBlocks == NULL) 397 | { 398 | return -4; 399 | } 400 | 401 | if(saveData == NULL || saveSize == 0) 402 | { 403 | return -5; 404 | } 405 | 406 | // Edge cases 407 | // - last block isn't necessarily full, need save size for this 408 | // - first block contains header, sat table, and possibly data 409 | // - the last SAT table block can possibly include data 410 | 411 | while(satBlocks->blockNum) 412 | { 413 | block = (unsigned char*)(partitionBuf + (satBlocks->blockNum * blockSize)); 414 | 415 | // validate we are still in range 416 | if(block < partitionBuf || block >= partitionBuf + partitionSize) 417 | { 418 | return -6; 419 | } 420 | 421 | // no flags means we are just a data block (no metadata, no SAT entries) 422 | if(satBlocks->flags == 0) 423 | { 424 | // check if we are the very last block and we aren't full 425 | if(saveSize - bytesWritten < blockDataSize) 426 | { 427 | // last block isn't full, copy less data 428 | bytesToCopy = saveSize - bytesWritten; 429 | } 430 | else 431 | { 432 | bytesToCopy = blockDataSize; 433 | } 434 | 435 | // copy the save bytes 436 | memcpy(saveData + bytesWritten, block + SAT_TAG_SIZE, bytesToCopy); 437 | bytesWritten += bytesToCopy; 438 | } 439 | 440 | // SAT table end means this block contains the last of the SAT blocks. It is possible there is save data here 441 | else if(satBlocks->flags & SAT_TABLE_END_BLOCK_FLAG) 442 | { 443 | unsigned int skipBytes = 0; 444 | 445 | // end of the SAT array, data can follow 446 | bytesToCopy = blockDataSize; 447 | 448 | // is this the start block? 449 | if(satBlocks->flags & SAT_START_BLOCK_FLAG) 450 | { 451 | // skip over the header data 452 | skipBytes += sizeof(SAT_START_BLOCK_HEADER) - SAT_TAG_SIZE; 453 | } 454 | 455 | // This is a SAT table block, parse until the 0x0000 and then start reading save bytes 456 | if(satBlocks->flags & SAT_TABLE_BLOCK_FLAG) 457 | { 458 | // skip over all SAT entries including the terminating 0x0000 459 | for(unsigned int i = skipBytes; i < blockDataSize; i += sizeof(unsigned short)) 460 | { 461 | unsigned short satIndex = *(unsigned short*)(block + i + SAT_TAG_SIZE); 462 | 463 | if(satIndex == 0) 464 | { 465 | // found the 0s 466 | skipBytes = i + sizeof(unsigned short); 467 | break; 468 | } 469 | } 470 | } 471 | 472 | bytesToCopy -= skipBytes; 473 | 474 | // check if we are the last very last block and we aren't full 475 | if(saveSize - bytesWritten < blockDataSize) 476 | { 477 | // last block isn't full, copy less data 478 | bytesToCopy = saveSize - bytesWritten; 479 | } 480 | 481 | // at maximum we can only copy blockDataSize at once 482 | if(bytesToCopy > blockDataSize) 483 | { 484 | return -1; 485 | } 486 | 487 | // another sanity check 488 | if(bytesWritten + bytesToCopy > saveSize) 489 | { 490 | return -1; 491 | } 492 | 493 | memcpy(saveData + bytesWritten, block + skipBytes + SAT_TAG_SIZE, bytesToCopy); 494 | bytesWritten += bytesToCopy; 495 | } 496 | 497 | ++satBlocks; 498 | } 499 | 500 | return 0; 501 | } 502 | 503 | -------------------------------------------------------------------------------- /backends/sat.h: -------------------------------------------------------------------------------- 1 | // Saturn Allocation Table (SAT) save partitions parsing 2 | #pragma once 3 | 4 | // 5 | // SAT structures 6 | // 7 | 8 | // 9 | // Saturn saves are stored in 64-byte blocks 10 | // - the first 4 bytes of each block is a tag 11 | // -- 0x80000000 = start of new save 12 | // -- 0x00000000 = continuation block? 13 | // - the next 30 bytes are metadata for the save (save name, language, comment, date, and size) 14 | // -- the size is the size of the save data not counting the metadata 15 | // - next is a variable array of 2-byte block ids. This array ends when 00 00 is encountered 16 | // -- the block id for the 1st block (the one containing the metadata) is not present. In this case only 00 00 will be present 17 | // -- the variable length array can be 0-512 elements (assuming max save is on the order of ~32k) 18 | // -- to complicate matters, the block ids themselves can extend into multiple blocks. This makes computing the block count tricky 19 | // - following the block ids is the save data itself 20 | // 21 | 22 | #define SAT_PARTITION_SIZE 0x40 // BUGBUG: this should be dynamic 23 | 24 | #define SAT_MAX_SAVE_NAME 11 25 | #define SAT_MAX_SAVE_COMMENT 10 26 | 27 | #define SAT_START_BLOCK_TAG 0x80000000 // beginning of a save must start with this 28 | #define SAT_CONTINUE_BLOCK_TAG 0x0 // all other blocks have a 0 tag 29 | 30 | 31 | #define SAT_TAG_SIZE sizeof(((SAT_START_BLOCK_HEADER *)0)->tag) 32 | 33 | // BUGBUG: get rid of this 34 | #define SAT_BLOCK_USABLE_SIZE 0x40 - 4 35 | #define SAT_BLOCK_HEADER_SIZE 0x1E 36 | 37 | // struct at the beginning of a save block 38 | #pragma pack(1) 39 | typedef struct _SAT_START_BLOCK_HEADER 40 | { 41 | unsigned int tag; 42 | char saveName[SAT_MAX_SAVE_NAME]; // not necessarily NULL terminated 43 | unsigned char language; 44 | char comment[SAT_MAX_SAVE_COMMENT]; // not necessarily NULL terminated 45 | unsigned int date; 46 | unsigned int saveSize; // in bytes 47 | }SAT_START_BLOCK_HEADER, *PSAT_START_BLOCK_HEADER; 48 | #pragma pack() 49 | 50 | 51 | #define SAT_START_BLOCK_FLAG 0x1 // first block in the save. It contains SAT_START_BLOCK_HEADER followed by SAT table 52 | #define SAT_TABLE_BLOCK_FLAG 0x2 // block contains the variable lenght SAT table 53 | #define SAT_TABLE_END_BLOCK_FLAG 0x4 // block contains the end of the SAT table 54 | 55 | // represents a SAT block. Used to find data within a SAT partition 56 | typedef struct _SAT_BLOCK 57 | { 58 | unsigned int blockNum; // blockNum is the index into the partition. Multiply by block size 59 | unsigned int flags; // it's possible for a block to have multiple flags at once 60 | } SAT_BLOCK, *PSAT_BLOCK; 61 | 62 | 63 | // parsing functions 64 | int calcNumBlocks(unsigned int saveSize, unsigned int blockSize, unsigned int* numSaveBlocks); 65 | int satListSaves(unsigned char* partitionBuffer, unsigned int partitionSize, unsigned int blockSize, PSAVES saves, unsigned int numSaves); 66 | int getSaveStartBlock(unsigned char* partitionBuf, unsigned int partitionSize, unsigned int blockSize, char* saveName, PSAT_START_BLOCK_HEADER* metadata); 67 | int getSATBlocks(unsigned char* partitionBuf, unsigned int partitionSize, unsigned int blockSize, PSAT_START_BLOCK_HEADER metadata, PSAT_BLOCK* satBlocks); 68 | int readSATFromBlock(unsigned char* partitionBuf, unsigned int partitionSize, unsigned int blockSize, unsigned int currBlock, PSAT_BLOCK satBlocks, unsigned int maxBlocks, unsigned int* numBocks); 69 | int getSATSave(unsigned char* partitionBuffer, unsigned int partitionSize, unsigned int blockSize, PSAT_BLOCK satTable, unsigned char* saveData, unsigned int saveSize); 70 | 71 | 72 | -------------------------------------------------------------------------------- /backends/satiator.c: -------------------------------------------------------------------------------- 1 | #include "satiator.h" 2 | #include "satiator/satiator.h" 3 | 4 | // returns true if the backup device is found 5 | bool satiatorIsBackupDeviceAvailable(int backupDevice) 6 | { 7 | int result; 8 | 9 | if(backupDevice != SatiatorBackup) 10 | { 11 | return false; 12 | } 13 | 14 | result = satiatorEnter(); 15 | if(result != 0) 16 | { 17 | // failed to find Satiator 18 | return false; 19 | } 20 | 21 | // found Satiator 22 | return true; 23 | } 24 | 25 | // queries the saves on the Satiator device and fills out the saves array 26 | // BUGBUG: code copied from satiator-menu/main.c and needs to be under the MPL 27 | int satiatorListSaveFiles(int backupDevice, PSAVES saves, unsigned int numSaves) 28 | { 29 | int result = 0; 30 | unsigned int count = 0; 31 | char statbuf[280] = {0}; 32 | s_stat_t *st = (s_stat_t*)statbuf; 33 | int len = 0; 34 | 35 | if(backupDevice != SatiatorBackup) 36 | { 37 | return -1; 38 | } 39 | 40 | result = satiatorEnter(); 41 | if(result != 0) 42 | { 43 | sgc_core_error("Failed to detect satiator"); 44 | return -1; 45 | } 46 | 47 | result = s_opendir("."); 48 | if(result != 0) 49 | { 50 | sgc_core_error("readSatiatorSaveFiles: Failed to open SATSAVES directory"); 51 | return -2; 52 | } 53 | 54 | // loop through the files in the directory 55 | while ((len = s_stat(NULL, st, sizeof(statbuf)-1)) > 0) 56 | { 57 | st->name[len] = 0; 58 | 59 | //skip directories 60 | if(st->attrib & AM_DIR) 61 | { 62 | continue; 63 | } 64 | 65 | // skip . and .. (and anything beginning with .) 66 | if (st->name[0] == '.') 67 | { 68 | continue; 69 | } 70 | 71 | result = isFileBUPExt(st->name); 72 | if(result == false) 73 | { 74 | // not a .BUP file, skip 75 | continue; 76 | } 77 | 78 | strncpy((char*)saves[count].filename, st->name, MAX_FILENAME); 79 | saves[count].filename[MAX_FILENAME - 1] = '\0'; 80 | saves[count].datasize = st->size - sizeof(BUP_HEADER); 81 | saves[count].blocksize = 0; // blocksize on the Satiator doesn't matter 82 | count++; 83 | 84 | if(count >= numSaves) 85 | { 86 | break; 87 | } 88 | } 89 | 90 | // for each file found, read the BUP header and parse the metadata 91 | for(unsigned int i = 0; i < count; i++) 92 | { 93 | BUP_HEADER bupHeader = {0}; 94 | 95 | result = satiatorReadBUPHeader(saves[i].filename, &bupHeader); 96 | if(result != 0) 97 | { 98 | sgc_core_error("bup header %s", saves[i].filename); 99 | continue; 100 | } 101 | 102 | result = parseBupHeaderValues(&bupHeader, saves[count].datasize + sizeof(BUP_HEADER), saves[i].name, saves[i].comment, &saves[i].language, &saves[i].date, &saves[i].datasize, &saves[i].blocksize); 103 | if(result != 0) 104 | { 105 | sgc_core_error("Failed with %d", result); 106 | 107 | 108 | // BUGBUG: handle error conditions gracefully 109 | strncpy(saves[i].name, "Error", MAX_SAVE_FILENAME); 110 | 111 | continue; 112 | } 113 | } 114 | 115 | // BUGBUG: close the directory?? 116 | return count; 117 | } 118 | 119 | // copies the specified Satiator save game to the saveFileData buffer 120 | int satiatorReadSaveFile(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outSize) 121 | { 122 | int result = 0; 123 | int fd = 0; 124 | 125 | if(backupDevice != SatiatorBackup) 126 | { 127 | return -1; 128 | } 129 | 130 | result = satiatorEnter(); 131 | if(result == 0) 132 | { 133 | // why is it failing to detect now?? 134 | //sgc_core_error("Failed to detect satiator %d", result); 135 | //return -1; 136 | } 137 | 138 | if(outBuffer == NULL || filename == NULL) 139 | { 140 | sgc_core_error("readSatiatorSaveFile: Save file data buffer is NULL!!"); 141 | return -1; 142 | } 143 | 144 | if(outSize == 0 || outSize > MAX_SAVE_SIZE) 145 | { 146 | sgc_core_error("readSatiatorSaveFile: Save file size is invalid %d!!", outSize); 147 | return -2; 148 | } 149 | 150 | fd = s_open(filename, FA_READ); 151 | if(fd < 0) 152 | { 153 | sgc_core_error("readSatiatorSaveFile: Failed to open satiator file!!"); 154 | return -2; 155 | } 156 | 157 | for(unsigned int bytesRead = 0; bytesRead < outSize; ) 158 | { 159 | unsigned int count; 160 | 161 | count = MIN(outSize - bytesRead, S_MAXBUF); 162 | 163 | // BUGBUG: fix this 164 | result = s_read(fd, outBuffer + bytesRead, count); 165 | if(result <= 0) 166 | { 167 | sgc_core_error("Bad read result: %x", result); 168 | s_close(fd); 169 | return result; 170 | } 171 | 172 | bytesRead += count; 173 | } 174 | 175 | s_close(fd); 176 | 177 | if(result < 0) 178 | { 179 | sgc_core_error("readSatiatorSaveFile: Failed to read satiator file!!"); 180 | return -3; 181 | } 182 | 183 | return 0; 184 | } 185 | 186 | // write the save game to the Satiator 187 | int satiatorWriteSaveFile(int backupDevice, char* filename, unsigned char* inBuffer, unsigned int inSize) 188 | { 189 | int result = 0; 190 | int fd = 0; 191 | 192 | if(backupDevice != SatiatorBackup) 193 | { 194 | return -1; 195 | } 196 | 197 | result = satiatorEnter(); 198 | if(result != 0) 199 | { 200 | sgc_core_error("Failed to detect satiator"); 201 | return -1; 202 | } 203 | 204 | if(filename == NULL) 205 | { 206 | sgc_core_error("writeSatiatorSaveData: Save file data buffer is NULL!!"); 207 | return -1; 208 | } 209 | 210 | if(inBuffer == NULL || filename == NULL) 211 | { 212 | sgc_core_error("writeSatiatorSaveData: Save file size is invalid %d!!", inSize); 213 | return -2; 214 | } 215 | 216 | fd = s_open(filename, FA_WRITE|FA_CREATE_ALWAYS); 217 | if(fd < 0) 218 | { 219 | sgc_core_error("writeSatiatorSaveData: Failed to open satiator file!!"); 220 | return -2; 221 | } 222 | 223 | for(unsigned int bytesWritten = 0; bytesWritten < inSize; ) 224 | { 225 | unsigned int count; 226 | 227 | count = MIN(inSize - bytesWritten, S_MAXBUF); 228 | 229 | // BUGBUG: fix this 230 | result = s_write(fd, inBuffer + bytesWritten, count); 231 | 232 | s_sync(fd); 233 | 234 | if(result <= 0) 235 | { 236 | sgc_core_error("Bad write result: %x", result); 237 | s_close(fd); 238 | return result; 239 | break; 240 | } 241 | 242 | bytesWritten += count; 243 | } 244 | 245 | s_close(fd); 246 | 247 | if(result < 0) 248 | { 249 | sgc_core_error("writeSatiatorSaveData: Failed to read satiator file!!"); 250 | return -3; 251 | } 252 | 253 | return 0; 254 | } 255 | 256 | // delete the save 257 | int satiatorDeleteSaveFile(int backupDevice, char* filename) 258 | { 259 | int result = 0; 260 | 261 | if(backupDevice != SatiatorBackup) 262 | { 263 | return -1; 264 | } 265 | 266 | result = satiatorEnter(); 267 | if(result != 0) 268 | { 269 | sgc_core_error("Failed to detect satiator"); 270 | //return -1; 271 | } 272 | 273 | if(filename == NULL) 274 | { 275 | sgc_core_error("deleteSatiatorSaveData: Filename is NULL!!"); 276 | return -1; 277 | } 278 | 279 | result = s_unlink(filename); 280 | 281 | if(result < 0) 282 | { 283 | sgc_core_error("deleteSatiatorSaveData: Failed to read satiator file!!"); 284 | return -3; 285 | } 286 | 287 | return 0; 288 | } 289 | 290 | // enable satiator extra mode 291 | // without this you cannot access the filesystem 292 | int satiatorEnter(void) 293 | { 294 | int result; 295 | 296 | result = s_mode(s_api); 297 | if(result != 0) 298 | { 299 | return -1; 300 | } 301 | s_chdir("/SATSAVES"); 302 | return 0; 303 | } 304 | 305 | // exit satiator extra mode 306 | // BUGBUG: this does not appear to work and ends up with the ISO no longer being accessible 307 | int satiatorExit(void) 308 | { 309 | 310 | // BUGBUG: this doesn't quite work correctly 311 | //s_chdir(".."); 312 | //s_mode(S_MODE_CDROM); 313 | 314 | return 0; 315 | } 316 | 317 | // read the bup header 318 | int satiatorReadBUPHeader(char* filename, PBUP_HEADER bupHeader) 319 | { 320 | int result = 0; 321 | int fd = 0; 322 | bool openedFile = false; 323 | 324 | if(!filename || !bupHeader) 325 | { 326 | return -1; 327 | } 328 | 329 | fd = s_open(filename, FA_READ); 330 | if(fd < 0) 331 | { 332 | sgc_core_error("readSatiatorSaveFile: Failed to open satiator file!!"); 333 | return -2; 334 | } 335 | 336 | // read the .BUP header 337 | result = s_read(fd, (unsigned char*)bupHeader, sizeof(BUP_HEADER)); 338 | if(result <= 0) 339 | { 340 | sgc_core_error("Bad read result: %x", result); 341 | result = -3; 342 | goto exit; 343 | } 344 | 345 | openedFile = true; 346 | 347 | if(result < (int)sizeof(BUP_HEADER)) 348 | { 349 | sgc_core_error("bup header is too small"); 350 | result = -4; 351 | goto exit; 352 | } 353 | 354 | result = 0; 355 | 356 | exit: 357 | if(openedFile == true) 358 | { 359 | s_close(fd); 360 | } 361 | 362 | return result; 363 | } 364 | 365 | // relaunch the satiator menu 366 | void satiatorReboot(void) 367 | { 368 | s_mode(s_api); 369 | for (volatile int i=0; i<2000; i++) 370 | ; 371 | 372 | int (**bios_get_mpeg_rom)(uint32_t index, uint32_t size, uint32_t addr) = (void*)0x06000298; 373 | (*bios_get_mpeg_rom)(2, 2, 0x200000); 374 | 375 | ((void(*)(void))0x200000)(); 376 | 377 | // should never get here 378 | } 379 | 380 | -------------------------------------------------------------------------------- /backends/satiator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend.h" 4 | 5 | bool satiatorIsBackupDeviceAvailable(int backupDevice); 6 | int satiatorListSaveFiles(int backupDevice, PSAVES fileSaves, unsigned int numSaves); 7 | int satiatorReadSaveFile(int backupDevice, char* filename, unsigned char* ouBuffer, unsigned int outBufSize); 8 | int satiatorWriteSaveFile(int backupDevice, char* filename, unsigned char* saveData, unsigned int saveDataLen); 9 | int satiatorDeleteSaveFile(int backupDevice, char* filename); 10 | 11 | // helper functions 12 | int satiatorEnter(void); 13 | int satiatorExit(void); 14 | int satiatorReadBUPHeader(char* filename, PBUP_HEADER bupHeader); 15 | void satiatorReboot(void); 16 | -------------------------------------------------------------------------------- /backends/satiator/cd.c: -------------------------------------------------------------------------------- 1 | #include "cd.h" 2 | 3 | u32 interrupt_get_level_mask(void) 4 | { 5 | u32 sr; 6 | 7 | asm("stc sr,%0": "=r"(sr)); 8 | sr = (sr & 0xF0) >> 4; 9 | 10 | return sr; 11 | } 12 | 13 | void interrupt_set_level_mask(u32 imask) 14 | { 15 | u32 sr; 16 | 17 | asm("stc sr,%0": "=r"(sr)); 18 | sr &= 0xFFFFFF0F; 19 | imask <<= 4; 20 | sr |= imask; 21 | asm("ldc %0,sr": : "r" (sr)); 22 | } 23 | 24 | 25 | void cd_write_command(cd_cmd_struct *cd_cmd) 26 | { 27 | CDB_REG_CR1 = cd_cmd->CR1; 28 | CDB_REG_CR2 = cd_cmd->CR2; 29 | CDB_REG_CR3 = cd_cmd->CR3; 30 | CDB_REG_CR4 = cd_cmd->CR4; 31 | } 32 | 33 | void cd_read_return_status(cd_cmd_struct *cd_cmd_rs) 34 | { 35 | cd_cmd_rs->CR1 = CDB_REG_CR1; 36 | cd_cmd_rs->CR2 = CDB_REG_CR2; 37 | cd_cmd_rs->CR3 = CDB_REG_CR3; 38 | cd_cmd_rs->CR4 = CDB_REG_CR4; 39 | } 40 | 41 | int cd_wait_hirq(int flag) 42 | { 43 | int i; 44 | u16 hirq_temp; 45 | 46 | for (i = 0; i < 0x240000; i++) 47 | { 48 | hirq_temp = CDB_REG_HIRQ; 49 | if (hirq_temp & flag) 50 | return 1; 51 | } 52 | return 0; 53 | } 54 | 55 | int cd_exec_command(u16 hirq_mask, cd_cmd_struct *cd_cmd, cd_cmd_struct *cd_cmd_rs) 56 | { 57 | int old_level_mask; 58 | u16 hirq_temp; 59 | u16 cd_status; 60 | 61 | // Mask any interrupts, we don't need to be interrupted 62 | old_level_mask = interrupt_get_level_mask(); 63 | interrupt_set_level_mask(0xF); 64 | 65 | hirq_temp = CDB_REG_HIRQ; 66 | 67 | // Make sure CMOK flag is set, or we can't continue 68 | if (!(hirq_temp & HIRQ_CMOK)) 69 | return IAPETUS_ERR_CMOK; 70 | 71 | // Clear CMOK and any other user-defined flags 72 | CDB_REG_HIRQ = ~(hirq_mask | HIRQ_CMOK); 73 | 74 | // Alright, time to execute the command 75 | cd_write_command(cd_cmd); 76 | 77 | // Let's wait till the command operation is finished 78 | if (!cd_wait_hirq(HIRQ_CMOK)) 79 | return IAPETUS_ERR_TIMEOUT; 80 | 81 | // Read return data 82 | cd_read_return_status(cd_cmd_rs); 83 | 84 | cd_status = cd_cmd_rs->CR1 >> 8; 85 | 86 | // Was command good? 87 | if (cd_status == STATUS_REJECT) 88 | return IAPETUS_ERR_INVALIDARG; 89 | else if (cd_status & STATUS_WAIT) 90 | return IAPETUS_ERR_BUSY; 91 | 92 | // return interrupts back to normal 93 | interrupt_set_level_mask(old_level_mask); 94 | 95 | // It's all good 96 | return IAPETUS_ERR_OK; 97 | } 98 | 99 | int cd_get_stat(cd_stat_struct *cd_status) 100 | { 101 | cd_cmd_struct cd_cmd; 102 | cd_cmd_struct cd_cmd_rs; 103 | int ret; 104 | 105 | cd_cmd.CR1 = 0x0000; 106 | cd_cmd.CR2 = 0x0000; 107 | cd_cmd.CR3 = 0x0000; 108 | cd_cmd.CR4 = 0x0000; 109 | 110 | if ((ret = cd_exec_command(0, &cd_cmd, &cd_cmd_rs)) != 0) 111 | return ret; 112 | 113 | cd_status->status = cd_cmd_rs.CR1 >> 8; 114 | cd_status->flag = (cd_cmd_rs.CR1 >> 4) & 0xF; 115 | cd_status->repeat_cnt = cd_cmd_rs.CR1 & 0xF; 116 | cd_status->ctrl_addr = cd_cmd_rs.CR2 >> 8; 117 | cd_status->track = cd_cmd_rs.CR2 & 0xFF; 118 | cd_status->index = cd_cmd_rs.CR3 >> 8; 119 | cd_status->FAD = ((cd_cmd_rs.CR3 & 0xFF) << 16) | cd_cmd_rs.CR4; 120 | 121 | return IAPETUS_ERR_OK; 122 | } 123 | 124 | int cd_stop_drive() 125 | { 126 | int ret; 127 | cd_cmd_struct cd_cmd; 128 | cd_cmd_struct cd_cmd_rs; 129 | cd_stat_struct cd_status; 130 | int i; 131 | 132 | // CD Init Command 133 | cd_cmd.CR1 = 0x0400; 134 | cd_cmd.CR2 = 0x0001; 135 | cd_cmd.CR3 = 0x0000; 136 | cd_cmd.CR4 = 0x040F; 137 | 138 | if ((ret = cd_exec_command(0, &cd_cmd, &cd_cmd_rs)) != 0) 139 | return ret; 140 | 141 | // Wait till operation is finished(fix me) 142 | 143 | // Wait till drive is stopped 144 | for (;;) 145 | { 146 | // wait a bit 147 | for (i = 0; i < 100000; i++) { } 148 | 149 | if (cd_get_stat(&cd_status) != 0) continue; 150 | 151 | if (cd_status.status == STATUS_STANDBY) break; 152 | else if (cd_status.status == STATUS_FATAL) return IAPETUS_ERR_UNKNOWN; 153 | } 154 | 155 | return IAPETUS_ERR_OK; 156 | } 157 | -------------------------------------------------------------------------------- /backends/satiator/cd.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2006-2007,2013 Theo Berkau 2 | 3 | This file is part of Iapetus. 4 | 5 | Iapetus is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | Iapetus is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with Iapetus; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef CD_H 21 | #define CD_H 22 | 23 | enum IAPETUS_ERR 24 | { 25 | IAPETUS_ERR_OK=0, // Everything is good 26 | IAPETUS_ERR_COMM=-1, // Communication error 27 | IAPETUS_ERR_HWNOTFOUND=-2, // Hardware not found 28 | IAPETUS_ERR_SIZE=-3, // Invalid size specified 29 | IAPETUS_ERR_INVALIDPOINTER=-4, // Invalid pointer passed 30 | IAPETUS_ERR_INVALIDARG=-5, // Invalid argument passed 31 | IAPETUS_ERR_BUSY=-6, // Hardware is busy 32 | IAPETUS_ERR_UNKNOWN=-7, // Unknown error 33 | IAPETUS_ERR_FILENOTFOUND=-8, // File not found error 34 | IAPETUS_ERR_UNSUPPORTED=-9, // Unsupported feature 35 | IAPETUS_ERR_TIMEOUT=-10, // Operation timed out 36 | IAPETUS_ERR_UNEXPECTDATA=-11, // Unexpected data 37 | IAPETUS_ERR_OUTOFMEMORY=-12, // Ran out of memory 38 | IAPETUS_ERR_BADHEADER=-13, // Bad Header 39 | 40 | // CD/MPEG Related 41 | IAPETUS_ERR_AUTH=-100, // Disc/MPEG card authentication error 42 | IAPETUS_ERR_CMOK=-101, // CD command ok hirq bit not set 43 | IAPETUS_ERR_CDNOTFOUND=-102, // CD not found 44 | IAPETUS_ERR_MPEGCMD=-103, // MPEG command hirq bit not set 45 | }; 46 | 47 | #include "satiator-types.h" 48 | 49 | #define HIRQ_CMOK 0x0001 50 | #define HIRQ_DRDY 0x0002 51 | #define HIRQ_CSCT 0x0004 52 | #define HIRQ_BFUL 0x0008 53 | #define HIRQ_PEND 0x0010 54 | #define HIRQ_DCHG 0x0020 55 | #define HIRQ_ESEL 0x0040 56 | #define HIRQ_EHST 0x0080 57 | #define HIRQ_ECPY 0x0100 58 | #define HIRQ_EFLS 0x0200 59 | #define HIRQ_SCDQ 0x0400 60 | #define HIRQ_MPED 0x0800 61 | #define HIRQ_MPCM 0x1000 62 | #define HIRQ_MPST 0x2000 63 | 64 | #define STATUS_BUSY 0x00 65 | #define STATUS_PAUSE 0x01 66 | #define STATUS_STANDBY 0x02 67 | #define STATUS_PLAY 0x03 68 | #define STATUS_SEEK 0x04 69 | #define STATUS_SCAN 0x05 70 | #define STATUS_OPEN 0x06 71 | #define STATUS_NODISC 0x07 72 | #define STATUS_RETRY 0x08 73 | #define STATUS_ERROR 0x09 74 | #define STATUS_FATAL 0x0a 75 | #define STATUS_PERIODIC 0x20 76 | #define STATUS_TRANSFER 0x40 77 | #define STATUS_WAIT 0x80 78 | #define STATUS_REJECT 0xff 79 | 80 | #define CDB_REG_HIRQ *((volatile u16 *)0x25890008) 81 | #define CDB_REG_HIRQMASK *((volatile u16 *)0x2589000C) 82 | #define CDB_REG_CR1 *((volatile u16 *)0x25890018) 83 | #define CDB_REG_CR2 *((volatile u16 *)0x2589001C) 84 | #define CDB_REG_CR3 *((volatile u16 *)0x25890020) 85 | #define CDB_REG_CR4 *((volatile u16 *)0x25890024) 86 | #define CDB_REG_DATATRNS *((volatile u32 *)0x25818000) 87 | #define CDB_REG_DATATRNSW *((volatile u16 *)0x25898000) 88 | 89 | #define CD_CON_TRUE (1 << 0) 90 | #define CD_CON_FALSE (1 << 1) 91 | 92 | #define CD_NO_CHANGE 0xFF 93 | 94 | typedef struct 95 | { 96 | u16 CR1; 97 | u16 CR2; 98 | u16 CR3; 99 | u16 CR4; 100 | } cd_cmd_struct; 101 | 102 | typedef struct 103 | { 104 | u8 status; 105 | u8 flag; 106 | u8 repeat_cnt; 107 | u8 ctrl_addr; 108 | u8 track; 109 | u8 index; 110 | u32 FAD; 111 | } cd_stat_struct; 112 | 113 | typedef struct 114 | { 115 | u8 hw_flag; 116 | u8 hw_ver; 117 | u8 mpeg_ver; 118 | u8 drive_ver; 119 | u8 drive_rev; 120 | } hw_info_struct; 121 | 122 | #define FM_FN (1 << 0) 123 | #define FM_CN (1 << 1) 124 | #define FM_SM (1 << 2) 125 | #define FM_CI (1 << 3) 126 | #define FM_REV (1 << 4) 127 | #define FM_FAD (1 << 6) 128 | 129 | #define SM_EOR (1 << 0) 130 | #define SM_VIDEO (1 << 1) 131 | #define SM_AUDIO (1 << 2) 132 | #define SM_DATA (1 << 3) 133 | #define SM_TRIGGER (1 << 4) 134 | #define SM_FORM (1 << 5) 135 | #define SM_RT (1 << 6) 136 | #define SM_EOF (1 << 7) 137 | 138 | typedef struct 139 | { 140 | u8 channel; 141 | u8 sm_mask; 142 | u8 ci_mask; 143 | u8 file_id; 144 | u8 sm_val; 145 | u8 ci_val; 146 | } cd_sh_cond_struct; 147 | 148 | typedef struct 149 | { 150 | u32 fad; 151 | u32 range; 152 | } cd_range_struct; 153 | 154 | typedef struct 155 | { 156 | u8 connect_flags; 157 | u8 true_con; 158 | u8 false_con; 159 | } cd_con_struct; 160 | 161 | enum SECTOR_SIZE 162 | { 163 | SECT_2048 = 0x0, 164 | SECT_2336 = 0x1, 165 | SECT_2340 = 0x2, 166 | SECT_2352 = 0x3 167 | }; 168 | 169 | enum SUBCODE_TYPE 170 | { 171 | SC_Q = 0x0, 172 | SC_RW = 0x1 173 | }; 174 | 175 | #define SCEF_PACKDATAERROR (1 << 0) 176 | #define SCEF_OVERRUNERROR (1 << 1) 177 | 178 | /* 179 | int cd_wait_hirq(int flag); 180 | int cd_exec_command(u16 hirq_mask, cd_cmd_struct *cd_cmd, cd_cmd_struct *cd_cmd_rs); 181 | int cd_debug_exec_command(font_struct *font, u16 hirq_mask, cd_cmd_struct *cd_cmd, cd_cmd_struct *cd_cmd_rs); 182 | int cd_get_hw_info(hw_info_struct *hw_info); 183 | int cd_get_toc(u32 *toc); 184 | int cd_get_session_num(u8 *num); 185 | int cd_get_session_info(u8 num, u32 *lba); 186 | int cd_play_fad(int play_mode, int start_fad, int num_sectors); 187 | int cd_seek_fad(int seekfad); 188 | int cd_get_subcode(enum SUBCODE_TYPE type, u16 *data, u8 *flags); 189 | int cd_connect_cd_to_filter(u8 filter_num); 190 | int cd_set_filter(u8 filter_num, u8 mode, cd_sh_cond_struct *sh_cond, cd_range_struct *cd_range, cd_con_struct *cd_con); 191 | int cd_reset_selector_all(); 192 | int cd_init(); 193 | int cd_end_transfer(); 194 | int cd_get_stat(cd_stat_struct *cd_status); 195 | int is_cd_auth(u16 *disc_type_auth); 196 | int cd_auth(); 197 | int cd_stop_drive(); 198 | int cd_start_drive(); 199 | int is_cd_present(); 200 | int cd_read_sector(void *buffer, u32 FAD, int sector_size, u32 num_bytes); 201 | int play_cd_audio(u8 audio_track, u8 repeat, u8 vol_l, u8 vol_r); 202 | int stop_cd_audio(void); 203 | int cd_set_sector_size(int size); 204 | int cd_abort_file(void); 205 | int cd_end_transfer(void); 206 | */ 207 | 208 | int cd_stop_drive(); 209 | 210 | #endif 211 | -------------------------------------------------------------------------------- /backends/satiator/satiator-types.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 James Laird-Wah 2 | 3 | This file is part of Yabause. 4 | 5 | Yabause is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | Yabause is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with Yabause; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | /*! \file satiator.h 21 | \brief Saturn Satiator emulation bits. 22 | */ 23 | #pragma once 24 | #ifndef _SATISFIER_H 25 | #define _SATISFIER_H 26 | 27 | #include 28 | 29 | // typedefs 30 | typedef unsigned char uint8_t; 31 | typedef unsigned short uint16_t; 32 | typedef unsigned int uint32_t; 33 | typedef unsigned int uintptr_t; 34 | 35 | typedef unsigned char u8; 36 | typedef unsigned short u16; 37 | typedef unsigned int u32; 38 | 39 | typedef enum { 40 | c_get_status = 0x90, 41 | c_write_buffer, 42 | c_read_buffer, 43 | 44 | c_mkfs = 0x94, 45 | c_info, 46 | c_settime, 47 | 48 | c_open = 0xA0, 49 | c_close, 50 | c_seek, 51 | c_read, 52 | c_write, 53 | c_truncate, 54 | c_stat, 55 | c_rename, 56 | c_unlink, 57 | c_mkdir, 58 | c_opendir, 59 | c_readdir, 60 | c_chdir, 61 | c_emulate, 62 | } satiator_cmd_t; 63 | 64 | typedef enum { 65 | i_fw_version = 0, 66 | } satisfier_info_cmd_t; 67 | 68 | #define C_SEEK_SET 0 69 | #define C_SEEK_CUR 1 70 | #define C_SEEK_END 2 71 | 72 | // CD image descriptor 73 | typedef struct { 74 | uint32_t start; // FAD 75 | uint32_t length; // sectors 76 | 77 | uint32_t file_offset; // byte offset within file where segment data starts 78 | uint32_t filename_offset; // byte offset within descfile where filename is. zero for no data 79 | 80 | uint16_t flags; // future proofing 81 | uint16_t secsize; // 2048 or 2352 82 | 83 | uint8_t track; // 1-99 for disc tracks. 84 | uint8_t index; // typically 0 for pregap, 1 for track, others not used. track MSF counts backwards in pregap 85 | uint8_t q_mode; // typically 0x41 (data) or 0x01 (audio) 86 | 87 | } __attribute__((packed)) seg_desc_t; 88 | 89 | #endif // _SATISFIER_H 90 | -------------------------------------------------------------------------------- /backends/satiator/satiator.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015 James Laird-Wah 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ 6 | 7 | //#include 8 | #include "cd.h" 9 | #include "satiator.h" 10 | 11 | #include 12 | //#include 13 | //#include 14 | 15 | // I/O primitives {{{ 16 | 17 | typedef uint16_t cmd_t[4]; 18 | 19 | void exec_cmd(cmd_t cr, uint16_t wait) { 20 | 21 | CDB_REG_HIRQ = ~(HIRQ_CMOK | wait); 22 | CDB_REG_CR1 = cr[0]; 23 | CDB_REG_CR2 = cr[1]; 24 | CDB_REG_CR3 = cr[2]; 25 | CDB_REG_CR4 = cr[3]; 26 | 27 | while (!(CDB_REG_HIRQ & HIRQ_CMOK)); 28 | 29 | if (wait) 30 | while (!(CDB_REG_HIRQ & wait)); 31 | } 32 | 33 | static uint16_t sat_result[4]; 34 | static inline void get_stat(void) { 35 | cmd_t cmd = {c_get_status<<8, 0, 0, 0}; 36 | exec_cmd(cmd, 0); 37 | sat_result[0] = CDB_REG_CR1; 38 | sat_result[1] = CDB_REG_CR2; 39 | sat_result[2] = CDB_REG_CR3; 40 | sat_result[3] = CDB_REG_CR4; 41 | } 42 | 43 | static inline int buffer_xfer(void *buf, int len, int dir) { 44 | uint8_t cmdbyte = dir ? c_write_buffer : c_read_buffer; 45 | cmd_t cmd = {cmdbyte<<8, 0, 0, len}; 46 | exec_cmd(cmd, HIRQ_EHST); 47 | if (CDB_REG_CR1) // error 48 | return -1; 49 | 50 | while (!(CDB_REG_HIRQ & HIRQ_DRDY)); 51 | 52 | uint32_t *p = buf; 53 | uint16_t n = (len+3)/4; 54 | if (dir) { 55 | while (n--) 56 | CDB_REG_DATATRNS = *p++; 57 | // mandatory but mysterious 58 | CDB_REG_DATATRNS = 0; 59 | CDB_REG_DATATRNS = 0; 60 | } else { 61 | while (n--) 62 | *p++ = CDB_REG_DATATRNS; 63 | } 64 | 65 | return 0; 66 | } 67 | 68 | #define buffer_read(buf, len) buffer_xfer(buf, len, 0) 69 | #define buffer_write(buf, len) buffer_xfer((void*)(uintptr_t)buf, len, 1) 70 | 71 | // }}} 72 | 73 | // Convenience functions {{{ 74 | // Most calls use a similar pattern 75 | static inline void set_cmd(cmd_t cmd, int op, int fd, int flags, int len) { 76 | cmd[0] = (op<<8) | (fd & 0xff); 77 | cmd[1] = flags; 78 | cmd[2] = len>>16; 79 | cmd[3] = len; 80 | } 81 | 82 | // Most API returns a negative number for errors 83 | #define get_check_stat() do { \ 84 | get_stat(); \ 85 | uint8_t __retval = sat_result[0] >> 8; \ 86 | if (__retval) \ 87 | return -__retval; \ 88 | } while(0); 89 | 90 | #define get_length() ((sat_result[2]<<16) | sat_result[3]) 91 | 92 | // Most calls use the same sequence 93 | #define simplecall(...) do { \ 94 | cmd_t cmd; \ 95 | set_cmd(cmd, __VA_ARGS__); \ 96 | exec_cmd(cmd, HIRQ_MPED); \ 97 | get_check_stat(); \ 98 | } while(0); 99 | 100 | // }}} 101 | 102 | // File API {{{ 103 | // Given a filename and some FA_xxx flags, return a file descriptor 104 | // (or a negative error corresponding to FR_xxx) 105 | int s_open(const char *filename, int flags) { 106 | buffer_write(filename, strlen(filename)); 107 | simplecall(c_open, 0, flags, strlen(filename)); 108 | return sat_result[3]; // handle 109 | } 110 | 111 | // Close a file descriptor 112 | int s_close(int fd) { 113 | simplecall(c_close, fd, 0, 0); 114 | return 0; 115 | } 116 | 117 | // Seek to a byte on an fd. Returns the offset 118 | int s_seek(int fd, int offset, int whence) { 119 | simplecall(c_seek, fd, whence, offset); 120 | return (sat_result[2]<<16) | sat_result[3]; 121 | } 122 | 123 | // Read some data. Returns bytes read 124 | int s_read(int fd, void *buf, int len) { 125 | if (len > S_MAXBUF || len < 0) 126 | return FR_INVALID_PARAMETER; 127 | simplecall(c_read, fd, 0, len); 128 | len = get_length(); 129 | buffer_read(buf, len); 130 | return len; 131 | } 132 | 133 | // Write some data. Returns bytes written 134 | int s_write(int fd, const void *buf, int len) { 135 | if (len > S_MAXBUF || len < 0) 136 | return FR_INVALID_PARAMETER; 137 | buffer_write(buf, len); 138 | simplecall(c_write, fd, 0, len); 139 | return get_length(); 140 | } 141 | 142 | // Flush any buffered data to file 143 | int s_sync(int fd) { 144 | return s_seek(fd, 0, C_SEEK_CUR); 145 | } 146 | 147 | // Truncate file at current pointer. Returns new length 148 | int s_truncate(int fd) { 149 | simplecall(c_truncate, fd, 0, 0); 150 | return get_length(); 151 | } 152 | 153 | // Get info on a named file. Pass in a pointer to a buffer and 154 | // its size - the filename may be truncated if the buffer is 155 | // short. Returns the length of the (truncated) filename. 156 | // If the filename is NULL, reads the next file from the 157 | // current directory (readdir). 158 | int s_stat(const char *filename, s_stat_t *stat, int statsize) { 159 | int len; 160 | if (statsize < 9) 161 | return FR_INVALID_PARAMETER; 162 | if (filename) { 163 | len = strlen(filename); 164 | buffer_write(filename, len); 165 | } else { 166 | len = 0; 167 | } 168 | simplecall(filename ? c_stat : c_readdir, 0, 0, len); 169 | len = get_length(); 170 | if (len > statsize) 171 | len = statsize; 172 | buffer_read(stat, len); 173 | return len - 9; 174 | } 175 | 176 | // Rename a file. 177 | int s_rename(const char *old, const char *new) { 178 | // Need both names, zero separated, for the write 179 | char namebuf[512]; // nasty huh 180 | int len1 = strlen(old); 181 | int len2 = strlen(new); 182 | memcpy(namebuf, old, len1+1); 183 | memcpy(namebuf+len1+1, new, len2); 184 | buffer_write(namebuf, len1+1+len2); 185 | simplecall(c_rename, 0, 0, len1+1+len2); 186 | return 0; 187 | } 188 | 189 | // Create a directory. 190 | int s_mkdir(const char *filename) { 191 | int len = strlen(filename); 192 | buffer_write(filename, len); 193 | simplecall(c_mkdir, 0, 0, len); 194 | return 0; 195 | } 196 | 197 | // Delete a file. 198 | int s_unlink(const char *filename) { 199 | int len = strlen(filename); 200 | buffer_write(filename, len); 201 | simplecall(c_unlink, 0, 0, len); 202 | return 0; 203 | } 204 | 205 | // Open a directory to read file entries. 206 | int s_opendir(const char *filename) { 207 | int len = strlen(filename); 208 | buffer_write(filename, len); 209 | simplecall(c_opendir, 0, 0, len); 210 | return 0; 211 | } 212 | 213 | // Change working directory. 214 | int s_chdir(const char *filename) { 215 | int len = strlen(filename); 216 | 217 | buffer_write(filename, len); 218 | 219 | simplecall(c_chdir, 0, 0, len); 220 | return 0; 221 | } 222 | 223 | // Get working directory. Adds terminating null. 224 | int s_getcwd(char *filename, int buflen) { 225 | buffer_write(".", 1); 226 | simplecall(c_chdir, 0, 0, 1); 227 | buflen--; 228 | if (buflen > get_length()) 229 | buflen = get_length(); 230 | buffer_read(filename, buflen); 231 | filename[buflen] = 0; 232 | return buflen; 233 | } 234 | // }}} 235 | 236 | // System API {{{ 237 | static int is_satiator_active(void) { 238 | // This checks the MPEG version field. 239 | // Real MPEG cards have version 1. 240 | // The Satiator has version 2. 241 | cmd_t cmd = {0x0100, 0, 0, 0}; 242 | exec_cmd(cmd, 0); 243 | return (CDB_REG_CR3 & 0xff) == 2; 244 | } 245 | static enum satiator_mode cur_mode = s_cdrom; 246 | 247 | int s_mode(enum satiator_mode mode) { 248 | /* Switch between emulating a CD drive and exposing the SD card API. 249 | * This function returns: 250 | * 0: success 251 | * -1: Satiator not detected 252 | */ 253 | if (mode == cur_mode) 254 | return 0; 255 | 256 | if (mode == s_cdrom) { 257 | cmd_t cmd = {0x9300, 1, 0, 0}; 258 | exec_cmd(cmd, HIRQ_MPED); 259 | } else { 260 | cmd_t cmd = {0xe000, 0x0000, 0x00c1, 0x05e7}; 261 | 262 | exec_cmd(cmd, HIRQ_EFLS); 263 | 264 | // is there actually a Satiator attached? 265 | if (!is_satiator_active()) 266 | return -1; 267 | 268 | cd_stop_drive(); 269 | } 270 | cur_mode = mode; 271 | return 0; 272 | } 273 | 274 | // Given the filename of a disc descriptor, try and boot into it. 275 | int boot_disc(void); 276 | int s_emulate(const char *filename) { 277 | int len = strlen(filename); 278 | buffer_write(filename, len); 279 | simplecall(c_emulate, 0, 0, len); 280 | return 0; 281 | } 282 | 283 | int s_get_fw_version(char *buf, int buflen) { 284 | simplecall(c_info, i_fw_version, 0, 0); 285 | buflen--; 286 | if (buflen > get_length()) 287 | buflen = get_length(); 288 | 289 | buffer_read(buf, buflen); 290 | buf[buflen] = 0; 291 | return buflen; 292 | } 293 | 294 | // }}} 295 | -------------------------------------------------------------------------------- /backends/satiator/satiator.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2017 James Laird-Wah 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ 6 | #pragma once 7 | 8 | #include "satiator-types.h" 9 | 10 | 11 | // XXX fatfs bits {{{ 12 | /* File access control and file status flags (FIL.flag) */ 13 | 14 | #define FA_READ 0x01 15 | #define FA_OPEN_EXISTING 0x00 16 | 17 | #define FA_WRITE 0x02 18 | #define FA_CREATE_NEW 0x04 19 | #define FA_CREATE_ALWAYS 0x08 20 | #define FA_OPEN_ALWAYS 0x10 21 | 22 | /* File attribute bits for directory entry */ 23 | 24 | #define AM_RDO 0x01 /* Read only */ 25 | #define AM_HID 0x02 /* Hidden */ 26 | #define AM_SYS 0x04 /* System */ 27 | #define AM_VOL 0x08 /* Volume label */ 28 | #define AM_LFN 0x0F /* LFN entry */ 29 | #define AM_DIR 0x10 /* Directory */ 30 | #define AM_ARC 0x20 /* Archive */ 31 | #define AM_MASK 0x3F /* Mask of defined bits */ 32 | 33 | typedef enum { 34 | FR_OK = 0, /* (0) Succeeded */ 35 | FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ 36 | FR_INT_ERR, /* (2) Assertion failed */ 37 | FR_NOT_READY, /* (3) The physical drive cannot work */ 38 | FR_NO_FILE, /* (4) Could not find the file */ 39 | FR_NO_PATH, /* (5) Could not find the path */ 40 | FR_INVALID_NAME, /* (6) The path name format is invalid */ 41 | FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ 42 | FR_EXIST, /* (8) Access denied due to prohibited access */ 43 | FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ 44 | FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ 45 | FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ 46 | FR_NOT_ENABLED, /* (12) The volume has no work area */ 47 | FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ 48 | FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any parameter error */ 49 | FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ 50 | FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ 51 | FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ 52 | FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_SHARE */ 53 | FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ 54 | } FRESULT; 55 | // }}} 56 | 57 | // API 58 | typedef struct { 59 | uint32_t size; 60 | uint16_t date, time; 61 | uint8_t attrib; 62 | char name[]; 63 | } __attribute__((packed)) s_stat_t; 64 | 65 | enum satiator_mode { 66 | s_cdrom = 0, 67 | s_api, 68 | }; 69 | 70 | int s_open(const char *filename, int flags); 71 | int s_close(int fd); 72 | int s_seek(int fd, int offset, int whence); 73 | int s_read(int fd, void *buf, int len); 74 | int s_write(int fd, const void *buf, int len); 75 | int s_sync(int fd); 76 | int s_truncate(int fd); 77 | int s_stat(const char *filename, s_stat_t *stat, int statsize); 78 | int s_rename(const char *old, const char *new); 79 | int s_mkdir(const char *filename); 80 | int s_unlink(const char *filename); 81 | int s_opendir(const char *filename); 82 | int s_chdir(const char *filename); 83 | int s_getcwd(char *filename, int buflen); 84 | int s_emulate(const char *filename); 85 | int s_mode(enum satiator_mode mode); 86 | int s_get_fw_version(char *buf, int buflen); 87 | 88 | #define S_MAXBUF 2048 89 | 90 | //#include 91 | #define dbgprintf(...) 92 | -------------------------------------------------------------------------------- /backends/saturn.c: -------------------------------------------------------------------------------- 1 | #include "saturn.h" 2 | 3 | // queries whether the backup device is present 4 | bool saturnIsBackupDeviceAvailable(int backupDevice) 5 | { 6 | bool isPresent = false; 7 | 8 | isPresent = jo_backup_mount(backupDevice); 9 | if(isPresent == true) 10 | { 11 | jo_backup_unmount(backupDevice); 12 | return true; 13 | } 14 | 15 | return false; 16 | } 17 | 18 | // queries the saves on the backup device and fills out the fileSaves array 19 | int saturnListSaveFiles(int backupDevice, PSAVES saves, unsigned int numSaves) 20 | { 21 | jo_list saveFilenames = {0}; 22 | bool result = false; 23 | int count = 0; 24 | 25 | jo_list_init(&saveFilenames); 26 | 27 | // mount the backup device 28 | result = jo_backup_mount(backupDevice); 29 | if(result == false) 30 | { 31 | // Don't need this error message because jo engine already errors 32 | sgc_core_error("Failed to mount backup device %d!!", backupDevice); 33 | return -1; 34 | } 35 | 36 | // get a list of files from the backup device 37 | result = jo_backup_read_device(backupDevice, &saveFilenames); 38 | if(result == false) 39 | { 40 | jo_list_free_and_clear(&saveFilenames); 41 | return -1; 42 | } 43 | 44 | for(unsigned int i = 0; i < (unsigned int)saveFilenames.count && i < numSaves; i++) 45 | { 46 | char comment[MAX_SAVE_COMMENT] = {0}; 47 | unsigned char language = 0; 48 | unsigned int date = 0; 49 | unsigned int numBytes = 0; 50 | unsigned int numBlocks = 0; 51 | 52 | char* filename = jo_list_at(&saveFilenames, i)->data.ch_arr; 53 | 54 | if(filename == NULL) 55 | { 56 | sgc_core_error("readSaveFiles list is corrupt!!"); 57 | return -1; 58 | } 59 | 60 | // query the save metadata 61 | result = jo_backup_get_file_info(backupDevice, filename, comment, &language, &date, &numBytes, &numBlocks); 62 | if(result == false) 63 | { 64 | //sgc_core_error("Failed to read file size!!"); 65 | continue; 66 | } 67 | 68 | // fill out the SAVES structure 69 | snprintf(saves[i].filename, MAX_FILENAME, "%s.BUP", filename); 70 | strncpy(saves[i].name, filename, MAX_SAVE_FILENAME); 71 | strncpy(saves[i].comment, (char*)comment, sizeof(comment)); 72 | saves[i].language = language; 73 | saves[i].date = date; 74 | saves[i].datasize = numBytes; 75 | saves[i].blocksize = numBlocks; 76 | count++; 77 | } 78 | 79 | jo_list_free_and_clear(&saveFilenames); 80 | 81 | return count; 82 | } 83 | 84 | // copies the specified save gane to the saveFileData buffer 85 | int saturnReadSaveFile(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outBufSize) 86 | { 87 | unsigned char* saveData = NULL; 88 | PBUP_HEADER temp = NULL; 89 | int result = 0; 90 | 91 | if(outBuffer == NULL || filename == NULL) 92 | { 93 | sgc_core_error("Save file data buffer is NULL!!"); 94 | return -1; 95 | } 96 | 97 | if(outBufSize < sizeof(BUP_HEADER) || outBufSize > (MAX_SAVE_SIZE + sizeof(BUP_HEADER))) 98 | { 99 | sgc_core_error("Save file size is invalid %d!!", outBufSize); 100 | return -2; 101 | } 102 | temp = (PBUP_HEADER)outBuffer; 103 | 104 | // read the file from the backup device 105 | // jo engine mallocs a buffer for us 106 | saveData = jo_backup_load_file_contents(backupDevice, filename, &outBufSize); 107 | if(saveData == NULL) 108 | { 109 | sgc_core_error("Failed to read save file!!"); 110 | return -3; 111 | } 112 | 113 | // query the save metadata 114 | unsigned int blockSize = 0; 115 | result = jo_backup_get_file_info(backupDevice, filename, (char*)&temp->dir.comment, &temp->dir.language, &temp->dir.date, &temp->dir.datasize, &blockSize); 116 | 117 | if(result == false) 118 | { 119 | sgc_core_error("Failed to save metadata size!!"); 120 | return -1; 121 | } 122 | memcpy(temp->magic, VMEM_MAGIC_STRING, VMEM_MAGIC_STRING_LEN); 123 | strncpy((char*)temp->dir.filename, filename, MAX_SAVE_FILENAME); 124 | temp->date = temp->dir.date; // date is duplicated 125 | temp->dir.blocksize = blockSize; 126 | 127 | // copy the save game data and free the jo engine buffer 128 | memcpy(outBuffer + sizeof(BUP_HEADER), saveData, outBufSize); 129 | jo_free(saveData); 130 | 131 | return 0; 132 | } 133 | 134 | int saturnWriteSaveFile(int backupDevice, char* filename, unsigned char* saveData, unsigned int saveDataLen) 135 | { 136 | bool result = false; 137 | PBUP_HEADER temp = NULL; 138 | jo_backup saveMeta = {0}; 139 | 140 | // BUP header is required 141 | if(saveDataLen < sizeof(BUP_HEADER)) 142 | { 143 | sgc_core_error("Invalid .BUP header"); 144 | return -1; 145 | } 146 | 147 | temp = (PBUP_HEADER)saveData; 148 | 149 | // mount the backup device 150 | result = jo_backup_mount(backupDevice); 151 | if(result == false) 152 | { 153 | sgc_core_error("Failed to mount backup device %d!!", backupDevice); 154 | return -2; 155 | } 156 | 157 | saveMeta.backup_device = backupDevice; 158 | saveMeta.fname = filename; 159 | saveMeta.comment = (char*)temp->dir.comment; 160 | saveMeta.contents = saveData + sizeof(BUP_HEADER); 161 | saveMeta.content_size = saveDataLen - sizeof(BUP_HEADER); 162 | saveMeta.language_num = temp->dir.language; 163 | saveMeta.save_timestamp = temp->dir.date; 164 | 165 | result = jo_backup_save(&saveMeta); 166 | if(result == false) 167 | { 168 | //sgc_core_error("Failed to write save backup device %d!!", backupDevice); 169 | return -3; 170 | } 171 | 172 | return 0; 173 | } 174 | 175 | int saturnDeleteSaveFile(int backupDevice, char* filename) 176 | { 177 | bool result; 178 | 179 | // mount the backup device 180 | result = jo_backup_mount(backupDevice); 181 | if(result == false) 182 | { 183 | sgc_core_error("Failed to mount backup device %d!!", backupDevice); 184 | return -1; 185 | } 186 | 187 | result = jo_backup_delete_file(backupDevice, filename); 188 | if(result == false) 189 | { 190 | return -1; 191 | } 192 | 193 | return 0; 194 | } 195 | -------------------------------------------------------------------------------- /backends/saturn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend.h" 4 | 5 | bool saturnIsBackupDeviceAvailable(int backupDevice); 6 | int saturnListSaveFiles(int backupDevice, PSAVES saves, unsigned int numSaves); 7 | int saturnReadSaveFile(int backupDevice, char* filename, unsigned char* outBuffer, unsigned int outSize); 8 | int saturnWriteSaveFile(int backupDevice, char* filename, unsigned char* inBuffer, unsigned int inSize); 9 | int saturnDeleteSaveFile(int backupDevice, char* filename); 10 | int saturnFormatDevice(int backupDevice); 11 | -------------------------------------------------------------------------------- /bup_header.c: -------------------------------------------------------------------------------- 1 | #include "bup_header.h" 2 | 3 | /* 4 | * Format void BUP_GetDate(unsigned int date, jo_backup_date *date) 5 | * Input pdate : data and time data of directory information 6 | * date : date and time table 7 | * Output none 8 | * Function value none 9 | * Function Expands the date and time data in the directory information table. 10 | */ 11 | void bup_getdate(unsigned int date, jo_backup_date *tb) 12 | { 13 | unsigned long div; 14 | 15 | /* Set minute count. */ 16 | tb->min = (unsigned char)(date % 60); 17 | 18 | /* Set hour. */ 19 | tb->time = (unsigned char)((date % (60*24)) / 60); 20 | 21 | /* Compute days count. */ 22 | div = date / (60*24); 23 | 24 | /* Set of week. */ 25 | if (div > 0xAB71) 26 | { 27 | tb->week = (unsigned char)((div + 1) % 7); 28 | } 29 | else 30 | { 31 | tb->week = (unsigned char)((div + 2) % 7); 32 | } 33 | 34 | /* To process leap year, simply consider a pack of 4 years whose first one 35 | * is a multiple of 4, and loop up to 48 months until getting remaining 36 | * days count fitting into one month. 37 | * 38 | * It's a bit kind of naive (not to say dumb) implementation, but it works, 39 | * and as it's not the kind of routine that require heavy optimization 40 | * there's no plan to make this algorithm smarter. 41 | */ 42 | unsigned long year_base = div / ((365*4) + 1); 43 | year_base = year_base * 4; 44 | unsigned long days_remain = div % ((365*4) + 1); 45 | const char days_count[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 46 | 47 | unsigned char month = 0; 48 | int i; 49 | for(i=0; i<(4*12); i++) 50 | { 51 | unsigned char days_per_month = days_count[i % 12]; 52 | if(i == 1) 53 | { 54 | days_per_month++; 55 | } 56 | 57 | if(days_remain < days_per_month) 58 | { 59 | break; 60 | } 61 | 62 | days_remain -= days_per_month; 63 | month++; 64 | 65 | if((i % 12) == 11) 66 | { 67 | month = 0; 68 | year_base++; 69 | } 70 | } 71 | 72 | tb->month = month + 1; 73 | tb->day = days_remain + 1; 74 | tb->year = year_base; 75 | 76 | 77 | /* Code below is here to reproduce the same bug as in Saturn BIOS, 78 | * so please disable it if you prefer a valid date rather than a 79 | * completely equivalent re-implementation. 80 | * 81 | * Description : 1st January following a leap year is incorrectly 82 | * returned as December 32nd of this leap year. 83 | * 84 | * Examples : 85 | * >Error2 # 1, BIO:1980/12/32 00:42 4, B:00080AEA 86 | * > , VMM:1981/01/01 00:42 4 87 | * > , ORG:1981/01/01 00:42 88 | * >Error2 # 2, BIO:1980/12/32 07:42 4, B:00080C8E 89 | * > , VMM:1981/01/01 07:42 4 90 | * > , ORG:1981/01/01 07:42 91 | * >Error2 # 5, BIO:1984/12/32 00:42 2, B:0028250A 92 | * > , VMM:1985/01/01 00:42 2 93 | * > , ORG:1985/01/01 00:42 94 | * 95 | * Note : it is preferable that the implementation here (vmem) should 96 | * return a valid date rather than pushing mimic of BIOS behavior 97 | * including its bugs. 98 | */ 99 | #if 0 100 | if((tb->month == 1) 101 | && (tb->day == 1) 102 | && ((tb->year % 4) == 1)) 103 | { 104 | tb->month = 12; 105 | tb->day = 32; 106 | tb->year--; 107 | } 108 | #endif 109 | } 110 | 111 | /* 112 | * Format Uint32 BUP_SetDate(BupDate *date) 113 | * Input date : date and time table 114 | * Output none 115 | * Function value Data in date and time data form in the directory information table. 116 | * Function Compresses the date and time data in the directory information table. 117 | */ 118 | unsigned int bup_setdate(jo_backup_date *tb) 119 | { 120 | /* Table of elapsed days per month. */ 121 | const unsigned short monthtbl[11] = 122 | { 123 | 31, 124 | 31+28, 125 | 31+28+31, 126 | 31+28+31+30, 127 | 31+28+31+30+31, 128 | 31+28+31+30+31+30, 129 | 31+28+31+30+31+30+31, 130 | 31+28+31+30+31+30+31+31, 131 | 31+28+31+30+31+30+31+31+30, 132 | 31+28+31+30+31+30+31+31+30+31, 133 | 31+28+31+30+31+30+31+31+30+31+30 134 | }; 135 | unsigned long date; 136 | unsigned char data; 137 | unsigned long remainder; 138 | 139 | 140 | /* Easter Egg when specified time is set to invalid null values 141 | * 142 | * Technical details on SegaXtreme forums : 143 | * https://segaxtreme.net/threads/backup-memory-structure.16803/ 144 | * 145 | * antime : If you take the 1980/0/0 value and go backwards from 146 | * 1980/1/1 you get 1963/10/8. I wonder if that's an 147 | * easter egg - the programmer's birthday or something similar. 148 | * TascoDLX : The value in question, 0x008246A0, corresponds to 149 | * 1996/3/26 0:00, which pretty much coincides with the 150 | * passing of Comet Hyakutake. 151 | * As far as easter eggs go, that'd be my guess. 152 | * antime : The comet wasn't discovered until January 1996. 153 | * That'd be hell of an easter egg... 154 | * 155 | * Note : in this re-implementation, the value before adding this 156 | * easter egg was : t:0x026CECE0 -> 2057/05/15 00:00 157 | * Probably something caused by random value read outside range, 158 | * or indicating the passing of another new comet ?! 159 | */ 160 | if((tb->year == 0) 161 | && (tb->month == 0) 162 | && (tb->day == 0) 163 | && (tb->time == 0) 164 | && (tb->min == 0)) 165 | { 166 | return 0x008246A0; 167 | } 168 | 169 | /* Add year to result. */ 170 | data = tb->year; 171 | date = (data / 4) * 0x5B5; // 0x5B5 = 365.25 * 4 172 | 173 | remainder = data % 4; 174 | if(remainder) 175 | { 176 | date += (remainder * 0x16D) + 1; // 0x16D = 365 177 | } 178 | 179 | /* Leap year fix. 180 | * Code from Yabause HLE BIOS seems broken regarding leap years support. 181 | * 182 | * Rather than making a "clean" fix, and also because I'm not smart enough 183 | * to understand the "365.25 * 4" code above, date is adjusted in cases 184 | * causing problems. 185 | * 186 | * Additionally, I don't want to spend days in optimizing that : writing 187 | * accurate date manipulation routines is some kind of art that may chew 188 | * a lot of free time that would be better being dedicated in more 189 | * interesting projects. 190 | * 191 | * Fix is verified under test bench implemented in Save Data Manager's 192 | * extra menu which compares result between BIOS and this custom BUP_SetDate 193 | * functions, so that it should behave fine in any possible case until 2199. 194 | */ 195 | unsigned short year = 1980 + tb->year; 196 | int leap_year; 197 | if((year % 4) != 0) 198 | { 199 | leap_year = 0; 200 | } 201 | else if((year % 100) != 0) 202 | { 203 | leap_year = 1; 204 | } 205 | else if((year % 400) != 0) 206 | { 207 | leap_year = 0; 208 | } 209 | else 210 | { 211 | leap_year = 1; 212 | } 213 | 214 | date--; 215 | if((leap_year) && (tb->month == 2)) 216 | { 217 | date--; 218 | } 219 | if((year > 2000) && ((year % 100) == 0) && (tb->month == 2)) 220 | { 221 | date--; 222 | } 223 | 224 | /* Add month to result. */ 225 | data = tb->month; 226 | if(data != 1 && data < 13) 227 | { 228 | date += monthtbl[data - 2]; 229 | if(date > 2 && remainder == 0) 230 | { 231 | date++; 232 | } 233 | } 234 | 235 | /* Add day to result. */ 236 | date += tb->day; 237 | date *= 0x5A0; 238 | 239 | /* Add hour to result. */ 240 | date += (tb->time * 0x3C); 241 | 242 | /* Add minute to result. */ 243 | date += tb->min; 244 | 245 | return date; 246 | } 247 | 248 | -------------------------------------------------------------------------------- /bup_header.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bup_header.h - structure definition for the .BUP file header used 3 | * by various Sega Saturn tools. 4 | * 5 | * Format developed by Cafe-Alpha (https://segaxtreme.net/threads/save-game-metadata-questions.24625/post-178534) 6 | * 7 | * Date conversion functions taken from Cafe-Alpha's Pseudo Saturn Kai 8 | * 9 | */ 10 | #pragma once 11 | 12 | /** .BUP extension is required to work wih PS Kai */ 13 | #define BUP_EXTENSION ".BUP" 14 | 15 | /** The BUP header structure (sizeof(vmem_bup_header_t)) is exactly 64 bytes */ 16 | #define BUP_HEADER_SIZE 64 17 | 18 | /** BUP header magic */ 19 | #define VMEM_MAGIC_STRING_LEN 4 20 | #define VMEM_MAGIC_STRING "Vmem" 21 | 22 | /** Max backup filename length */ 23 | #define JO_BACKUP_MAX_FILENAME_LENGTH (12) 24 | 25 | /** Max backup file comment length */ 26 | #define JO_BACKUP_MAX_COMMENT_LENGTH (10) 27 | 28 | /* 29 | * Language of backup save in jo_backup_file 30 | * taken from Jo Engine backup.c 31 | */ 32 | enum jo_backup_language 33 | { 34 | backup_japanese = 0, 35 | backup_english = 1, 36 | backup_french = 2, 37 | backup_deutsch = 3, 38 | backup_espanol = 4, 39 | backup_italiano = 5, 40 | }; 41 | 42 | /** 43 | * Backup file metadata information. Taken from Jo Engine. 44 | **/ 45 | typedef struct _jo_backup_file 46 | { 47 | unsigned char filename[JO_BACKUP_MAX_FILENAME_LENGTH]; 48 | unsigned char comment[JO_BACKUP_MAX_COMMENT_LENGTH + 1]; 49 | unsigned char language; // jo_backup_language 50 | unsigned int date; 51 | unsigned int datasize; 52 | unsigned short blocksize; 53 | unsigned short padding; 54 | } jo_backup_file; 55 | 56 | typedef struct _jo_backup_date { 57 | unsigned char year; // year - 1980 58 | unsigned char month; 59 | unsigned char day; 60 | unsigned char time; 61 | unsigned char min; 62 | unsigned char week; 63 | } jo_backup_date; 64 | 65 | #if defined( __GNUC__ ) 66 | #pragma pack(1) 67 | #else 68 | #pragma pack(push,1) 69 | #endif 70 | 71 | /** 72 | * Vmem usage statistics structure. 73 | * Statistics are reset on each vmem session, ie when Saturn is reset, 74 | * or when game calls BUP_Init function. 75 | **/ 76 | typedef struct _vmem_bup_stats_t 77 | { 78 | /* Number of times BUP_Dir function is called. */ 79 | unsigned char dir_cnt; 80 | 81 | /* Number of times BUP_Read function is called. */ 82 | unsigned char read_cnt; 83 | 84 | /* Number of times BUP_Write function is called. */ 85 | unsigned char write_cnt; 86 | 87 | /* Number of times BUP_Verify function is called. */ 88 | unsigned char verify_cnt; 89 | } vmem_bup_stats_t; 90 | 91 | 92 | /** 93 | * Backup data header. Multibyte values are stored as big-endian. 94 | **/ 95 | typedef struct _vmem_bup_header_t 96 | { 97 | /* Magic string. 98 | * Used in order to verify that file is in vmem format. 99 | */ 100 | char magic[VMEM_MAGIC_STRING_LEN]; 101 | 102 | /* Save ID. 103 | * "Unique" ID for each save data file, the higher, the most recent. 104 | */ 105 | unsigned int save_id; 106 | 107 | /* Vmem usage statistics. */ 108 | vmem_bup_stats_t stats; 109 | 110 | /* Unused, kept for future use. */ 111 | char unused1[8 - sizeof(vmem_bup_stats_t)]; 112 | 113 | /* Backup Data Informations Record (34 bytes + 2 padding bytes). */ 114 | jo_backup_file dir; 115 | 116 | /* Date stamp, in Saturn's BUP library format. 117 | * Used in order to verify which save data is the most 118 | * recent one when rebuilding index data. 119 | * Note #1 : this information is already present in BupDir structure, 120 | * but games are setting it, so it may be incorrect (typically, set 121 | * to zero). 122 | * Note #2 : this date information is the date when Pseudo Saturn Kai 123 | * last started, not the time the save was saved, so if information in 124 | * dir structure is available, it is more accurate. 125 | */ 126 | unsigned int date; 127 | 128 | /* Unused, kept for future use. */ 129 | char unused2[8]; 130 | } vmem_bup_header_t, BUP_HEADER, *PBUP_HEADER; 131 | 132 | #if defined( __GNUC__ ) 133 | #pragma pack() 134 | #else 135 | #pragma pack(pop) 136 | #endif 137 | 138 | // 139 | // Functions for converting dates 140 | // 141 | void bup_getdate(unsigned int date, jo_backup_date *tb); 142 | unsigned int bup_setdate(jo_backup_date *tb); 143 | -------------------------------------------------------------------------------- /cd/ABS.TXT: -------------------------------------------------------------------------------- 1 | NOT Abstracted by SEGA 2 | 3 | -------------------------------------------------------------------------------- /cd/BIB.TXT: -------------------------------------------------------------------------------- 1 | NOT Bibliographiced by SEGA 2 | 3 | -------------------------------------------------------------------------------- /cd/CPY.TXT: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /cd/SATSAVES/BIOUDAT2.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/BIOUDAT2.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/BIOUDATA.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/BIOUDATA.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/B_GAREGG.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/B_GAREGG.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/CREDITS.TXT: -------------------------------------------------------------------------------- 1 | Save games came from the following: 2 | 3 | ShinJohnpv: 4 | B_GAREGG.BUP 5 | BIOUDAT2.BUP 6 | BIOUDATA.BUP 7 | GOIKE001.BUP 8 | GOIKEDAT.BUP 9 | HOUSEOFD.BUP 10 | SALAMAND.BUP 11 | SF_ALPH1.BUP 12 | SF_ALPHA.BUP 13 | SF_COL02.BUP 14 | SF_COL03.BUP 15 | SFZER001.BUP 16 | SFZER002.BUP 17 | ZENNIC01.BUP 18 | ZENNIC02.BUP 19 | 20 | ncc1701p: 21 | DRACUL01.BUP 22 | DRACUL02.BUP 23 | FANTASYZ.BUP 24 | HARRIE01.BUP 25 | KEIO2___.BUP 26 | NIGHTS02.BUP 27 | OUTRUN01.BUP 28 | POCKET_F.BUP 29 | PZL_F_TU.BUP 30 | QUTENKAI.BUP 31 | RADIANT_.BUP 32 | SBOMBERM.BUP 33 | SENGOKU_.BUP 34 | SOQGUREN.BUP 35 | TENCHIRE.BUP 36 | 37 | fafling: 38 | TAMAGOTC.BUP 39 | 40 | Sixfortyfive: 41 | DAYTONA_.BUP 42 | DR_FORCE.BUP 43 | FIGHTERS.BUP 44 | GUARDIAN.BUP 45 | SONICR__.BUP 46 | 47 | dogos: 48 | LANGRISSER5.BUP 49 | FALCOMC2COM.BUP 50 | FALCOMC2Y20.BUP 51 | FALCOMC2Y21.BUP 52 | FALCOMC2Y22.BUP 53 | -------------------------------------------------------------------------------- /cd/SATSAVES/DAYTONA_.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/DAYTONA_.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/DRACUL01.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/DRACUL01.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/DRACUL02.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/DRACUL02.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/DRACULAX.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/DRACULAX.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/DR_FORCE.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/DR_FORCE.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/FALC2COM.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/FALC2COM.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/FALC2Y20.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/FALC2Y20.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/FALC2Y21.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/FALC2Y21.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/FALC2Y22.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/FALC2Y22.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/FANTASYZ.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/FANTASYZ.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/FIGHTERS.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/FIGHTERS.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/GOIKE001.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/GOIKE001.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/GOIKEDAT.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/GOIKEDAT.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/GUARDIAN.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/GUARDIAN.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/HARRIE01.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/HARRIE01.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/HOUSEOFD.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/HOUSEOFD.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/KEIO2___.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/KEIO2___.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/LANGRIS5.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/LANGRIS5.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/NIGHTS02.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/NIGHTS02.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/OUTRUN01.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/OUTRUN01.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/POCKET_F.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/POCKET_F.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/PZL_F_TU.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/PZL_F_TU.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/QUTENKAI.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/QUTENKAI.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/RADIANT_.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/RADIANT_.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SALAMAND.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SALAMAND.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SBOMBERM.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SBOMBERM.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SEGARALL.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SEGARALL.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SENGOKU_.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SENGOKU_.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SFORCE31.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SFORCE31.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SFZER001.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SFZER001.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SFZER002.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SFZER002.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SF_ALPH1.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SF_ALPH1.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SF_ALPHA.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SF_ALPHA.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SF_COL02.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SF_COL02.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SF_COL03.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SF_COL03.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SONICR__.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SONICR__.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/SOQGUREN.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/SOQGUREN.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/TAMAGOTC.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/TAMAGOTC.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/TENCHIRE.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/TENCHIRE.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/ZENNIC01.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/ZENNIC01.BUP -------------------------------------------------------------------------------- /cd/SATSAVES/ZENNIC02.BUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/cd/SATSAVES/ZENNIC02.BUP -------------------------------------------------------------------------------- /clean.bat: -------------------------------------------------------------------------------- 1 | @ECHO Off 2 | SET COMPILER_DIR=..\..\Compiler 3 | SET JO_ENGINE_SRC_DIR=../../jo_engine 4 | SET PATH=%COMPILER_DIR%\WINDOWS\Other Utilities;%PATH% 5 | 6 | rm -f ./cd/0.bin 7 | rm -f *.o 8 | rm -f %JO_ENGINE_SRC_DIR%/*.o 9 | rm -f ./*.bin 10 | rm -f ./*.coff 11 | rm -f ./*.elf 12 | rm -f ./*.map 13 | rm -f ./*.iso 14 | rm -f ./*.cue 15 | 16 | ECHO Done. 17 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -f ./cd/0.bin 3 | rm -f *.o 4 | rm -f ../../jo_engine/*.o 5 | rm -f ./*.bin 6 | rm -f ./*.coff 7 | rm -f ./*.elf 8 | rm -f ./*.map 9 | rm -f ./*.iso 10 | rm -f ./*.cue 11 | echo "Done." 12 | exit 0 13 | -------------------------------------------------------------------------------- /compile.bat: -------------------------------------------------------------------------------- 1 | @ECHO Off 2 | SET COMPILER_DIR=..\..\Compiler 3 | SET PATH=%COMPILER_DIR%\WINDOWS\Other Utilities;%COMPILER_DIR%\WINDOWS\bin;%PATH% 4 | make re -------------------------------------------------------------------------------- /compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export NCPU=`nproc` 3 | make clean && make -j${NCPU} all 4 | exit 0 5 | 6 | -------------------------------------------------------------------------------- /game_cue_for_mode.cue: -------------------------------------------------------------------------------- 1 | FILE "game.iso" BINARY 2 | TRACK 01 MODE1/2048 3 | INDEX 01 00:00:00 4 | TRACK 02 AUDIO 5 | PREGAP 00:02:00 6 | INDEX 01 60:33:35 7 | -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | // Save Game Copier - a utility that copies Sega Saturn save game files 2 | 3 | /* 4 | ** Jo Sega Saturn Engine 5 | ** Copyright (c) 2012-2017, Johannes Fetz (johannesfetz@gmail.com) 6 | ** All rights reserved. 7 | ** 8 | ** Redistribution and use in source and binary forms, with or without 9 | ** modification, are permitted provided that the following conditions are met: 10 | ** * Redistributions of source code must retain the above copyright 11 | ** notice, this list of conditions and the following disclaimer. 12 | ** * Redistributions in binary form must reproduce the above copyright 13 | ** notice, this list of conditions and the following disclaimer in the 14 | ** documentation and/or other materials provided with the distribution. 15 | ** * Neither the name of the Johannes Fetz nor the 16 | ** names of its contributors may be used to endorse or promote products 17 | ** derived from this software without specific prior written permission. 18 | ** 19 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | ** DISCLAIMED. IN NO EVENT SHALL Johannes Fetz BE LIABLE FOR ANY 23 | ** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | ** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | ** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #pragma once 31 | #include "bup_header.h" 32 | 33 | // program version, keep this length to avoid having to resize strings 34 | #define VERSION "3.6.18" 35 | 36 | // program states 37 | #define STATE_UNINITIALIZED 0 38 | #define STATE_MAIN 1 39 | #define STATE_LIST_SAVES 2 40 | #define STATE_DISPLAY_SAVE 3 41 | #define STATE_DISPLAY_MEMORY 4 42 | #define STATE_DUMP_MEMORY 5 43 | #define STATE_WRITE_MEMORY 6 44 | #define STATE_FORMAT 7 45 | #define STATE_FORMAT_VERIFY 8 46 | #define STATE_COLLECT 9 47 | #define STATE_CREDITS 10 48 | #define STATE_PREVIOUS -1 // go to the previous state 49 | 50 | #define MAX_STATES 16 // how many states to record 51 | 52 | // option selected on the main screen 53 | #define MAIN_OPTION_INTERNAL 0 54 | #define MAIN_OPTION_CARTRIDGE 1 55 | #define MAIN_OPTION_EXTERNAL 2 56 | #define MAIN_OPTION_SATIATOR 3 57 | #define MAIN_OPTION_MODE 4 58 | #define MAIN_OPTION_CD 5 59 | #define MAIN_OPTION_DUMP_MEMORY 6 60 | #define MAIN_OPTION_FORMAT 7 61 | #define MAIN_OPTION_COLLECT 8 62 | #define MAIN_OPTION_CREDITS 9 63 | #define MAIN_OPTION_EXIT 10 64 | #define MAIN_OPTION_REBOOT 11 65 | #define MAIN_OPTION_EXIT_SATIATOR 12 66 | #define MAIN_OPTION_ACTION_REPLAY 13 67 | 68 | #define SAVE_OPTION_INTERNAL 0 69 | #define SAVE_OPTION_CARTRIDGE 1 70 | #define SAVE_OPTION_EXTERNAL 2 71 | #define SAVE_OPTION_SATIATOR 3 72 | #define SAVE_OPTION_WRITE_MEMORY 4 73 | #define SAVE_OPTION_DELETE 5 74 | #define SAVE_OPTION_MODE 6 75 | #define SAVE_OPTION_BACK 7 76 | 77 | #define VERIFY_YES 0 78 | #define VERIFY_NO 1 79 | 80 | #define OPERATION_UNINIT 0 81 | #define OPERATION_SUCCESS 1 82 | #define OPERATION_FAIL 2 83 | #define OPERATION_FAIL_DELETE 3 84 | 85 | // position of the heading text 86 | #define HEADING_X 2 87 | #define HEADING_Y 2 88 | 89 | #define OPTIONS_X HEADING_X + 3 90 | #define OPTIONS_Y HEADING_Y + 4 91 | 92 | #define VERIFY_X HEADING_X + 3 93 | #define VERIFY_Y HEADING_Y + 7 94 | 95 | #define SAVES_X HEADING_X + 3 96 | #define SAVES_Y HEADING_Y + 16 97 | 98 | #define CURSOR_X HEADING_X 99 | 100 | #define MAIN_NUM_OPTIONS 10 101 | #define FORMAT_NUM_OPTIONS 3 102 | #define FORMAT_VERIFY_NUM_OPTIONS 2 103 | #define SAVES_NUM_OPTIONS 6 104 | 105 | #define HEADING_UNDERSCORE "___________________________________" 106 | 107 | #define MAX_SAVE_SIZE (256 * 1024) // according to Cafe-Alpha this is the maximum size supported by the BIOS 108 | #define MAX_SAVE_FILENAME 12 109 | #define MAX_SAVE_COMMENT 11 110 | #define MAX_SAVES 255 111 | #define MAX_SAVES_PER_PAGE 15 112 | #define MAX_FILENAME 32 // maximum length of the filename on the backup medium 113 | // internal memory - this will be 11 characters 114 | // cd - 8.3 115 | // satiator, ode - 255? hopefully most people will keep the save filenames small 116 | 117 | #define MD5_HASH_SIZE 16 118 | 119 | // set this to 1 to skip device checks at boot. This will show the full menu 120 | // set to 1 to skip 121 | // BUGBUG: this should be a compile option,not a #define 122 | #define SKIP_DEVICE_CHECKS 0 123 | 124 | #define MAX_MENU_OPTIONS 20 125 | 126 | // records whether or not an input has been pressed that frame 127 | typedef struct _INPUTCACHE 128 | { 129 | bool pressedUp; 130 | bool pressedDown; 131 | bool pressedLeft; 132 | bool pressedRight; 133 | bool pressedB; 134 | bool pressedStartAC; 135 | bool pressedLT; 136 | bool pressedRT; 137 | } INPUTCACHE, *PINPUTCACHE; 138 | 139 | // dynamic menu options 140 | typedef struct _MENUOPTIONS 141 | { 142 | char* optionText; // what to print to the user 143 | unsigned int option; // value of the option, used to detect which option was selected 144 | } MENUOPTIONS, *PMENUOPTIONS; 145 | 146 | typedef struct _GAME 147 | { 148 | // game state variables 149 | int state; // current state of the program i.e. what screen it is in 150 | int previousStates[MAX_STATES]; // useful when going backwards from certain states 151 | int numStates; 152 | 153 | // number of options in the current state 154 | int numStateOptions; 155 | int numSaves; // number of saves on the device 156 | 157 | MENUOPTIONS menuOptions[MAX_MENU_OPTIONS]; 158 | unsigned int numMenuOptions; 159 | 160 | // the position of the cursor in the 0 position 161 | // we add cursorOffset to get the correct position 162 | int cursorPosX; 163 | int cursorPosY; 164 | int cursorOffset; 165 | int listSavesCursorOffset; 166 | 167 | jo_backup_device backupDevice; // JoInternalMemoryBackup, JoCartridgeMemoryBackup, JoExternalDeviceBackup 168 | char* backupDeviceName; 169 | 170 | // flags for whether or not we found the specified backup device 171 | // this will help with our dynamic menu 172 | bool deviceInternalMemoryBackup; 173 | bool deviceCartridgeMemoryBackup; 174 | bool deviceExternalDeviceBackup; 175 | bool deviceActionReplayBackup; 176 | bool deviceSatiatorBackup; 177 | bool deviceCdMemoryBackup; 178 | bool deviceModeBackup; 179 | 180 | bool listedSaves; // set to true if we already queried the saves from the backup device 181 | 182 | int operationStatus; // status of the operation 183 | 184 | // save name and savefilename are not the same thing 185 | char saveFilename[MAX_FILENAME]; // file name 186 | char saveName[MAX_SAVE_FILENAME]; // selected save file name 187 | char saveComment[MAX_SAVE_COMMENT]; // selected save comment 188 | unsigned char saveLanguage; // selected save language 189 | unsigned int saveDate; // selected save date; 190 | unsigned int saveFileSize; // selected save file size 191 | 192 | PBUP_HEADER saveBupHeader; // the bup header. Immediately following is the saveFileData 193 | unsigned char* saveFileData; // the raw save data 194 | 195 | unsigned int dumpMemoryAddress; 196 | unsigned int dumpMemorySize; 197 | 198 | bool md5Calculated; // set to true if we have calculated the md5 MD5_HASH_SIZE 199 | unsigned char md5Hash[MD5_HASH_SIZE]; 200 | 201 | // hack to cache controller inputs 202 | INPUTCACHE input; 203 | 204 | } GAME, *PGAME; 205 | 206 | extern GAME g_Game; 207 | 208 | // common functions 209 | void jo_main(void); 210 | void abcStartHandler(void); 211 | void clearScreen(void); 212 | int copyBIOS(unsigned int segment); 213 | int copySaveFile(void); 214 | void moveCursor(int* cursorOffset, bool savesPage); 215 | void moveDigitCursor(void); 216 | void adjustHexValue(unsigned int* value, unsigned int digit, bool add); 217 | void queryBackupDevices(void); 218 | 219 | // menu options helpers 220 | unsigned int initMenuOptions(int newState); 221 | 222 | // state helper functions 223 | void transitionToState(int newState); 224 | void resetState(void); 225 | int popState(void); 226 | int pushState(int newState); 227 | 228 | // main screen 229 | void main_draw(void); 230 | void main_input(void); 231 | 232 | // list saves screen 233 | void listSaves_draw(void); 234 | void listSaves_input(void); 235 | 236 | // playing save screen 237 | void displaySave_draw(void); 238 | void displaySave_input(void); 239 | 240 | // dump bios screen 241 | void dumpBios_draw(void); 242 | void dumpBios_input(void); 243 | 244 | // format screen 245 | void format_draw(void); 246 | void format_input(void); 247 | 248 | // format verify screen 249 | void formatVerify_draw(void); 250 | void formatVerify_input(void); 251 | 252 | // dump memory screen 253 | void dumpMemory_draw(void); 254 | void dumpMemory_input(void); 255 | 256 | // save games collect projects screen 257 | void collect_draw(void); 258 | void collect_input(void); 259 | 260 | // credits screen 261 | void credits_draw(void); 262 | void credits_input(void); 263 | 264 | // debug output 265 | void debugOutput_draw(void); 266 | 267 | // function prototypes to suppress compiler warnings 268 | void *memcpy(void *dest, const void *src, unsigned int n); 269 | char *strncpy(char *dest, const char *src, unsigned int n); 270 | int snprintf(char *str, unsigned int size, const char *format, ...); 271 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | JO_COMPILE_WITH_VIDEO_MODULE = 0 2 | JO_COMPILE_WITH_BACKUP_MODULE = 1 3 | JO_COMPILE_WITH_TGA_MODULE = 0 4 | JO_COMPILE_WITH_AUDIO_MODULE = 1 5 | JO_COMPILE_WITH_3D_MODULE = 0 6 | JO_COMPILE_WITH_PSEUDO_MODE7_MODULE = 0 7 | JO_COMPILE_WITH_EFFECTS_MODULE = 0 8 | JO_PSEUDO_SATURN_KAI_SUPPORT = 1 9 | JO_COMPILE_WITH_DUAL_CPU_MODULE = 0 10 | JO_DEBUG = 0 11 | JO_NTSC = 1 12 | JO_COMPILE_USING_SGL = 1 13 | SRCS=main.c bup_header.c util.c backends/backend.c backends/saturn.c backends/satiator.c backends/cd.c backends/actionreplay.c backends/sat.c md5/md5.c backends/satiator/satiator.c backends/satiator/cd.c backends/mode.c 14 | LIBS=backends/mode/mode_intf.a 15 | JO_ENGINE_SRC_DIR=../../jo_engine 16 | COMPILER_DIR=../../Compiler 17 | include $(COMPILER_DIR)/COMMON/jo_engine_makefile 18 | -------------------------------------------------------------------------------- /md5/md5.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. 3 | * MD5 Message-Digest Algorithm (RFC 1321). 4 | * 5 | * Homepage: 6 | * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 7 | * 8 | * Author: 9 | * Alexander Peslyak, better known as Solar Designer 10 | * 11 | * This software was written by Alexander Peslyak in 2001. No copyright is 12 | * claimed, and the software is hereby placed in the public domain. 13 | * In case this attempt to disclaim copyright and place the software in the 14 | * public domain is deemed null and void, then the software is 15 | * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the 16 | * general public under the following terms: 17 | * 18 | * Redistribution and use in source and binary forms, with or without 19 | * modification, are permitted. 20 | * 21 | * There's ABSOLUTELY NO WARRANTY, express or implied. 22 | * 23 | * (This is a heavily cut-down "BSD license".) 24 | * 25 | * This differs from Colin Plumb's older public domain implementation in that 26 | * no exactly 32-bit integer data type is required (any 32-bit or wider 27 | * unsigned integer data type will do), there's no compile-time endianness 28 | * configuration, and the function prototypes match OpenSSL's. No code from 29 | * Colin Plumb's implementation has been reused; this comment merely compares 30 | * the properties of the two independent implementations. 31 | * 32 | * The primary goals of this implementation are portability and ease of use. 33 | * It is meant to be fast, but not as fast as possible. Some known 34 | * optimizations are not included to reduce source code size and avoid 35 | * compile-time configuration. 36 | */ 37 | 38 | #ifndef HAVE_OPENSSL 39 | 40 | //#include 41 | 42 | #include "md5.h" 43 | 44 | /* 45 | * The basic MD5 functions. 46 | * 47 | * F and G are optimized compared to their RFC 1321 definitions for 48 | * architectures that lack an AND-NOT instruction, just like in Colin Plumb's 49 | * implementation. 50 | */ 51 | #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) 52 | #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) 53 | #define H(x, y, z) (((x) ^ (y)) ^ (z)) 54 | #define H2(x, y, z) ((x) ^ ((y) ^ (z))) 55 | #define I(x, y, z) ((y) ^ ((x) | ~(z))) 56 | 57 | /* 58 | * The MD5 transformation for all four rounds. 59 | */ 60 | #define STEP(f, a, b, c, d, x, t, s) \ 61 | (a) += f((b), (c), (d)) + (x) + (t); \ 62 | (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ 63 | (a) += (b); 64 | 65 | /* 66 | * SET reads 4 input bytes in little-endian byte order and stores them in a 67 | * properly aligned word in host byte order. 68 | * 69 | * The check for little-endian architectures that tolerate unaligned memory 70 | * accesses is just an optimization. Nothing will break if it fails to detect 71 | * a suitable architecture. 72 | * 73 | * Unfortunately, this optimization may be a C strict aliasing rules violation 74 | * if the caller's data buffer has effective type that cannot be aliased by 75 | * MD5_u32plus. In practice, this problem may occur if these MD5 routines are 76 | * inlined into a calling function, or with future and dangerously advanced 77 | * link-time optimizations. For the time being, keeping these MD5 routines in 78 | * their own translation unit avoids the problem. 79 | */ 80 | #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) 81 | #define SET(n) \ 82 | (*(MD5_u32plus *)&ptr[(n) * 4]) 83 | #define GET(n) \ 84 | SET(n) 85 | #else 86 | #define SET(n) \ 87 | (ctx->block[(n)] = \ 88 | (MD5_u32plus)ptr[(n) * 4] | \ 89 | ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ 90 | ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ 91 | ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) 92 | #define GET(n) \ 93 | (ctx->block[(n)]) 94 | #endif 95 | 96 | /* 97 | * This processes one or more 64-byte data blocks, but does NOT update the bit 98 | * counters. There are no alignment requirements. 99 | */ 100 | static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) 101 | { 102 | const unsigned char *ptr; 103 | MD5_u32plus a, b, c, d; 104 | MD5_u32plus saved_a, saved_b, saved_c, saved_d; 105 | 106 | ptr = (const unsigned char *)data; 107 | 108 | a = ctx->a; 109 | b = ctx->b; 110 | c = ctx->c; 111 | d = ctx->d; 112 | 113 | do { 114 | saved_a = a; 115 | saved_b = b; 116 | saved_c = c; 117 | saved_d = d; 118 | 119 | /* Round 1 */ 120 | STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) 121 | STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) 122 | STEP(F, c, d, a, b, SET(2), 0x242070db, 17) 123 | STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) 124 | STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) 125 | STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) 126 | STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) 127 | STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) 128 | STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) 129 | STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) 130 | STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) 131 | STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) 132 | STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) 133 | STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) 134 | STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) 135 | STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) 136 | 137 | /* Round 2 */ 138 | STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) 139 | STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) 140 | STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) 141 | STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) 142 | STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) 143 | STEP(G, d, a, b, c, GET(10), 0x02441453, 9) 144 | STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) 145 | STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) 146 | STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) 147 | STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) 148 | STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) 149 | STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) 150 | STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) 151 | STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) 152 | STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) 153 | STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) 154 | 155 | /* Round 3 */ 156 | STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) 157 | STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) 158 | STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) 159 | STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) 160 | STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) 161 | STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) 162 | STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) 163 | STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) 164 | STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) 165 | STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) 166 | STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) 167 | STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) 168 | STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) 169 | STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) 170 | STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) 171 | STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) 172 | 173 | /* Round 4 */ 174 | STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) 175 | STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) 176 | STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) 177 | STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) 178 | STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) 179 | STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) 180 | STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) 181 | STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) 182 | STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) 183 | STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) 184 | STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) 185 | STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) 186 | STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) 187 | STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) 188 | STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) 189 | STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) 190 | 191 | a += saved_a; 192 | b += saved_b; 193 | c += saved_c; 194 | d += saved_d; 195 | 196 | ptr += 64; 197 | } while (size -= 64); 198 | 199 | ctx->a = a; 200 | ctx->b = b; 201 | ctx->c = c; 202 | ctx->d = d; 203 | 204 | return ptr; 205 | } 206 | 207 | void MD5_Init(MD5_CTX *ctx) 208 | { 209 | ctx->a = 0x67452301; 210 | ctx->b = 0xefcdab89; 211 | ctx->c = 0x98badcfe; 212 | ctx->d = 0x10325476; 213 | 214 | ctx->lo = 0; 215 | ctx->hi = 0; 216 | } 217 | 218 | void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) 219 | { 220 | MD5_u32plus saved_lo; 221 | unsigned long used, available; 222 | 223 | saved_lo = ctx->lo; 224 | if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) 225 | ctx->hi++; 226 | ctx->hi += size >> 29; 227 | 228 | used = saved_lo & 0x3f; 229 | 230 | if (used) { 231 | available = 64 - used; 232 | 233 | if (size < available) { 234 | memcpy(&ctx->buffer[used], data, size); 235 | return; 236 | } 237 | 238 | memcpy(&ctx->buffer[used], data, available); 239 | data = (const unsigned char *)data + available; 240 | size -= available; 241 | body(ctx, ctx->buffer, 64); 242 | } 243 | 244 | if (size >= 64) { 245 | data = body(ctx, data, size & ~(unsigned long)0x3f); 246 | size &= 0x3f; 247 | } 248 | 249 | memcpy(ctx->buffer, data, size); 250 | } 251 | 252 | #define OUT(dst, src) \ 253 | (dst)[0] = (unsigned char)(src); \ 254 | (dst)[1] = (unsigned char)((src) >> 8); \ 255 | (dst)[2] = (unsigned char)((src) >> 16); \ 256 | (dst)[3] = (unsigned char)((src) >> 24); 257 | 258 | void MD5_Final(unsigned char *result, MD5_CTX *ctx) 259 | { 260 | unsigned long used, available; 261 | 262 | used = ctx->lo & 0x3f; 263 | 264 | ctx->buffer[used++] = 0x80; 265 | 266 | available = 64 - used; 267 | 268 | if (available < 8) { 269 | memset(&ctx->buffer[used], 0, available); 270 | body(ctx, ctx->buffer, 64); 271 | used = 0; 272 | available = 64; 273 | } 274 | 275 | memset(&ctx->buffer[used], 0, available - 8); 276 | 277 | ctx->lo <<= 3; 278 | OUT(&ctx->buffer[56], ctx->lo) 279 | OUT(&ctx->buffer[60], ctx->hi) 280 | 281 | body(ctx, ctx->buffer, 64); 282 | 283 | OUT(&result[0], ctx->a) 284 | OUT(&result[4], ctx->b) 285 | OUT(&result[8], ctx->c) 286 | OUT(&result[12], ctx->d) 287 | 288 | memset(ctx, 0, sizeof(*ctx)); 289 | } 290 | 291 | #endif 292 | -------------------------------------------------------------------------------- /md5/md5.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. 3 | * MD5 Message-Digest Algorithm (RFC 1321). 4 | * 5 | * Homepage: 6 | * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 7 | * 8 | * Author: 9 | * Alexander Peslyak, better known as Solar Designer 10 | * 11 | * This software was written by Alexander Peslyak in 2001. No copyright is 12 | * claimed, and the software is hereby placed in the public domain. 13 | * In case this attempt to disclaim copyright and place the software in the 14 | * public domain is deemed null and void, then the software is 15 | * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the 16 | * general public under the following terms: 17 | * 18 | * Redistribution and use in source and binary forms, with or without 19 | * modification, are permitted. 20 | * 21 | * There's ABSOLUTELY NO WARRANTY, express or implied. 22 | * 23 | * See md5.c for more information. 24 | */ 25 | 26 | #ifdef HAVE_OPENSSL 27 | #include 28 | #elif !defined(_MD5_H) 29 | #define _MD5_H 30 | 31 | /* Any 32-bit or wider unsigned integer data type will do */ 32 | typedef unsigned int MD5_u32plus; 33 | 34 | typedef struct { 35 | MD5_u32plus lo, hi; 36 | MD5_u32plus a, b, c, d; 37 | unsigned char buffer[64]; 38 | MD5_u32plus block[16]; 39 | } MD5_CTX; 40 | 41 | extern void MD5_Init(MD5_CTX *ctx); 42 | extern void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size); 43 | extern void MD5_Final(unsigned char *result, MD5_CTX *ctx); 44 | 45 | // missing function prototypes needed by Jo Engine 46 | void *memcpy(void *dest, const void *src, unsigned int n); 47 | void *memset(void *s, int c, unsigned int n); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /run_with_daemon_tools_and_ssf.bat: -------------------------------------------------------------------------------- 1 | @ECHO Off 2 | 3 | SET EMULATOR_DIR=..\..\Emulators 4 | SET DT_DIR=C:\Program Files (x86)\DAEMON Tools Lite 5 | 6 | if exist game.iso ( 7 | echo Mounting image... 8 | "%DT_DIR%\DTLite.exe" -mount 0,game.cue 9 | cd "%EMULATOR_DIR%\SSF\" 10 | echo Running SSF... 11 | "SSF.exe" 12 | echo Unmounting image... 13 | "%DT_DIR%\DTLite.exe" -unmount 0 14 | ) else ( 15 | echo Please compile first ! 16 | pause 17 | ) 18 | -------------------------------------------------------------------------------- /run_with_mednafen.bat: -------------------------------------------------------------------------------- 1 | @ECHO Off 2 | SET EMULATOR_DIR=..\..\Emulators 3 | SET MEDNAFEN_EXECUTABLE_PATH=%EMULATOR_DIR%\mednafen\mednafen.exe 4 | 5 | if not exist %MEDNAFEN_EXECUTABLE_PATH% ( 6 | echo --- 7 | echo Please install Mednafen here %EMULATOR_DIR% 8 | echo --- 9 | pause 10 | exit 11 | ) 12 | 13 | if exist game.cue ( 14 | "%MEDNAFEN_EXECUTABLE_PATH%" "%cd%\game.cue" -sound.volume "150" 15 | ) else ( 16 | echo Please compile first ! 17 | ) 18 | -------------------------------------------------------------------------------- /run_with_nova.bat: -------------------------------------------------------------------------------- 1 | @ECHO Off 2 | SET EMULATOR_DIR=..\..\Emulators 3 | SET NOVA_BIOS_PATH=%EMULATOR_DIR%\nova\bios\bios.bin 4 | 5 | if not exist %NOVA_BIOS_PATH% ( 6 | echo --- 7 | echo Nova doesn't support HLE bios today. 8 | echo Therefore, please put Sega Saturn bios here %NOVA_BIOS_PATH% 9 | echo --- 10 | pause 11 | exit 12 | ) 13 | 14 | if exist game.iso ( 15 | "%EMULATOR_DIR%\nova\nova.exe" "%cd%\game.cue" 16 | ) else ( 17 | echo Please compile first ! 18 | ) 19 | -------------------------------------------------------------------------------- /run_with_powershell_and_ssf.ps1: -------------------------------------------------------------------------------- 1 | # /!\ THIS SCRIPT DOESN'T WORK TODAY BECAUSE OF SSF /!\ 2 | $isoPath = Join-Path $pwd.Path game.iso 3 | $ssfDirectory = Join-Path $pwd.Path "..\..\Emulators\SSF\" 4 | Write-Host "Mounting image..." 5 | Mount-DiskImage -StorageType ISO -ImagePath $isoPath 6 | Write-Host "Running SSF..." 7 | cd "$ssfDirectory" 8 | Start-Process -FilePath "SSF.exe" -Wait 9 | Write-Host "Unmounting image..." 10 | Get-DiskImage $isoPath | Dismount-DiskImage 11 | -------------------------------------------------------------------------------- /run_with_virtual_clone_drive_and_ssf.bat: -------------------------------------------------------------------------------- 1 | @ECHO Off 2 | SET EMULATOR_DIR=..\..\Emulators 3 | SET VCD_DIR=C:\Program Files (x86)\Elaborate Bytes\VirtualCloneDrive 4 | 5 | if exist game.iso ( 6 | echo Mounting image... 7 | "%VCD_DIR%\vcdmount.exe" game.iso 8 | cd "%EMULATOR_DIR%\SSF\" 9 | echo Running SSF... 10 | "SSF.exe" 11 | echo Unmounting image... 12 | "%VCD_DIR%\vcdmount.exe" /u 13 | ) else ( 14 | echo Please compile first ! 15 | pause 16 | ) 17 | -------------------------------------------------------------------------------- /run_with_yabaSanshiro.bat: -------------------------------------------------------------------------------- 1 | @ECHO Off 2 | SET EMULATOR_DIR=..\..\Emulators 3 | 4 | if exist game.iso ( 5 | echo Please wait, it's very slow, but I don't know why... 6 | "%EMULATOR_DIR%\YabaSanshiro\yabasanshiro.exe" -a -i game.iso 7 | ) else ( 8 | echo Please compile first ! 9 | ) 10 | -------------------------------------------------------------------------------- /run_with_yabause.bat: -------------------------------------------------------------------------------- 1 | @ECHO Off 2 | SET EMULATOR_DIR=..\..\Emulators 3 | 4 | if exist game.iso ( 5 | "%EMULATOR_DIR%\yabause\yabause.exe" -a -i game.iso 6 | ) else ( 7 | echo Please compile first ! 8 | ) 9 | -------------------------------------------------------------------------------- /run_with_yabause.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | command -v yabause >/dev/null 2>&1 || { echo "yabause is not installed.\ 3 | Aborting." >&2; exit 1; } 4 | 5 | if [ -f game.iso ]; 6 | then 7 | yabause -a -i game.cue 8 | else 9 | echo "Please compile first !" >&2 10 | fi 11 | -------------------------------------------------------------------------------- /screenshots/cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/screenshots/cd.png -------------------------------------------------------------------------------- /screenshots/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/screenshots/copy.png -------------------------------------------------------------------------------- /screenshots/dump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/screenshots/dump.png -------------------------------------------------------------------------------- /screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/screenshots/main.png -------------------------------------------------------------------------------- /screenshots/mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/screenshots/mode.png -------------------------------------------------------------------------------- /screenshots/satiator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/screenshots/satiator.png -------------------------------------------------------------------------------- /screenshots/saves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slinga-homebrew/Save-Game-Copier/13d5ffb46cfcae870967d4de9097a003b72593cf/screenshots/saves.png -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | void jo_core_suspend(void); 4 | 5 | char __sgc_last_error[JO_PRINTF_BUF_SIZE] = {0}; 6 | 7 | // equivalent to __jo_core_error but works in release mode 8 | // prints an error message to the screen and waits for further 9 | // input 10 | void __sgc_core_error(char *message, const char *function) 11 | { 12 | jo_vdp2_clear_bitmap_nbg1(JO_COLOR_Blue); 13 | jo_set_printf_color_index(0); 14 | jo_printf(0, 13, " >>> Jo Engine error handler <<<"); 15 | 16 | jo_printf_with_color(0, 21, JO_COLOR_INDEX_Red, "In %s():", function); 17 | jo_printf_with_color(0, 23, JO_COLOR_INDEX_Red, message); 18 | jo_set_printf_color_index(0); 19 | jo_printf(2, 27, "Press [START] to continue..."); 20 | jo_core_suspend(); 21 | jo_clear_screen(); 22 | 23 | jo_vdp2_clear_bitmap_nbg1(JO_COLOR_Black); 24 | } 25 | 26 | void jo_core_suspend(void) 27 | { 28 | int wait_cursor; 29 | int frame; 30 | 31 | for (JO_ZERO(frame), JO_ZERO(wait_cursor);; ++frame) 32 | { 33 | #ifdef JO_COMPILE_WITH_PRINTF_SUPPORT 34 | if (frame > 4) 35 | { 36 | switch (wait_cursor) 37 | { 38 | case 0: 39 | jo_printf(0, 27, "-"); 40 | break; 41 | case 1: 42 | jo_printf(0, 27, "/"); 43 | break; 44 | case 2: 45 | jo_printf(0, 27, "I"); 46 | break; 47 | } 48 | if (wait_cursor == 2) 49 | JO_ZERO(wait_cursor); 50 | else 51 | ++wait_cursor; 52 | JO_ZERO(frame); 53 | } 54 | #endif // JO_COMPILE_WITH_PRINTF_SUPPORT 55 | #if JO_COMPILE_USING_SGL 56 | slSynch(); 57 | #else 58 | jo_input_update(); 59 | jo_wait_vblank_out(); 60 | jo_wait_vblank_in(); 61 | #endif 62 | if (jo_is_pad1_available() && (jo_is_pad1_key_down(JO_KEY_START) 63 | #ifdef JO_COMPILE_WITH_KEYBOARD_SUPPORT 64 | || (jo_keyboard_get_special_key() == JO_KEYBOARD_ENTER) 65 | #endif // JO_COMPILE_WITH_KEYBOARD_SUPPORT 66 | )) 67 | { 68 | #ifdef JO_COMPILE_WITH_PRINTF_SUPPORT 69 | jo_printf(0, 27, " "); 70 | #endif // JO_COMPILE_WITH_PRINTF_SUPPORT 71 | return ; 72 | } 73 | } 74 | } 75 | 76 | // hackish way to to clear the screen 77 | // Need to find API way 78 | void clearScreen(void) 79 | { 80 | for(int i = 0; i < 32; i++) 81 | { 82 | jo_printf(0, i, " "); 83 | } 84 | } 85 | 86 | // calculates the MD5 hash of buffer 87 | // md5Hash is an out parameter that must be at least MD5_HASH_SIZE (16) long 88 | // returns 0 on success 89 | int calculateMD5Hash(unsigned char* buffer, unsigned int bufferSize, unsigned char* md5Hash) 90 | { 91 | MD5_CTX ctx = {0}; 92 | 93 | if(buffer == NULL || md5Hash == NULL) 94 | { 95 | sgc_core_error("Invalid parameters to calculateMD5Hash!!"); 96 | return -1; 97 | } 98 | 99 | if(bufferSize == 0) 100 | { 101 | jo_memset(md5Hash, 0, bufferSize); 102 | return 0; 103 | } 104 | 105 | MD5_Init(&ctx); 106 | MD5_Update(&ctx, buffer, bufferSize); 107 | MD5_Final(md5Hash, &ctx); 108 | 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "md5/md5.h" 5 | 6 | #define COUNTOF(x) sizeof(x)/sizeof(x[0]) 7 | 8 | #define LWRAM 0x00200000 // start of LWRAM memory. Doesn't appear to be used 9 | #define LWRAM_HEAP_SIZE 0x100000 // number of bytes to extend heap by 10 | 11 | #define JO_PRINTF_BUF_SIZE (64) 12 | 13 | #define UNUSED_ARG(x) (void)(x) 14 | 15 | // taken from Jo Engine core.h 16 | // used to display an error message to the user 17 | extern char __sgc_last_error[JO_PRINTF_BUF_SIZE]; 18 | void __sgc_core_error(char *message, const char *function); 19 | # define sgc_core_error(...) do {sprintf(__sgc_last_error, __VA_ARGS__); __sgc_core_error(__sgc_last_error, __FUNCTION__);} while(0) 20 | 21 | // This function prototype is not in jo/malloc.h 22 | // Extend the heap 23 | void jo_add_memory_zone(unsigned char *ptr, const unsigned int size_in_bytes); 24 | 25 | // clears all the text on the screen 26 | void clearScreen(void); 27 | 28 | // calculates the MD5 hash of buffer 29 | // md5Hash is an out parameter that must be at least MD5_HASH_SIZE (16) long 30 | // returns 0 on success 31 | int calculateMD5Hash(unsigned char* buffer, unsigned int bufferSize, unsigned char* md5Hash); 32 | --------------------------------------------------------------------------------