├── N64SaveConverterGui └── src │ ├── N64Logo.png │ ├── Readme.txt │ ├── Lookup.txt │ └── N64SaveConverter.java └── Readme.txt /N64SaveConverterGui/src/N64Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ninjiteu/N64SaveConverter/HEAD/N64SaveConverterGui/src/N64Logo.png -------------------------------------------------------------------------------- /Readme.txt: -------------------------------------------------------------------------------- 1 | ===================== 2 | N64SaveConverterGui 3 | ===================== 4 | 5 | Author: Daniel Falk 6 | Version: 1.0 7 | 8 | =========================== 9 | About N64SaveConverterGui 10 | =========================== 11 | 12 | N64SaveConverterGui is a tool to convert N64 saves files for transfer between emulators and/or real N64 hardware. 13 | 14 | =========================== 15 | N64SaveConverterGui Usage 16 | =========================== 17 | 18 | To convert: 19 | * Select a save file to convert 20 | * Select an input source (from where did this save file originate?) 21 | * Select an input type 22 | * Select an output target (what emulator/platform do you want to convert this file to?) 23 | * Select an output type 24 | * Click Convert button. If conversion succeeded, a success messsage will appear. 25 | * Some save files do not need to be converted (i.e. Wii .eep/.mpk to PJ64 .eep/.mpk and vice versa). These scenarios are therefore not a selectable option. But it is still recommended to do a resize to ensure compatibility between emulators 26 | 27 | To resize: 28 | * You can pad/trim your save file to the standard file type size (see below) in order to increase compatibility across emulators 29 | * Click the pad/trim checkbox 30 | * Select an input type 31 | * Click Resize button. If resize succeeded, a success messsage will appear. 32 | 33 | Lookup list: 34 | * Check the file extension of the file you selected to determine input/output type. Retroarch is always (.srm) 35 | * Otherwise, use the Lookup list under the Help menu. 36 | 37 | ============================ 38 | Info about N64 Saves Files 39 | ============================ 40 | 41 | * The Nintendo 64 has 5 save formats: 4Kbit EEPROM (.eep), 16Kbit EEPROM (.eep), SRAM (.sra), FlashRAM (.fla), and Controller Pak (.mpk) 42 | * The exact file sizes that N64 hardware generates for these save types are as follows: 43 | - 4Kbit EEPROM: 512 bytes (.5 kilobytes) 44 | - 16Kbit EEPROM: 2048 bytes (2 kilobytes) 45 | - SRAM: 32,768 bytes (32 kilobytes) 46 | - FlashRAM: 131,072 bytes (128 kilobytes) 47 | - Controller Pak: 32,768 bytes (32 kilobytes) 48 | * Different emulators and hardware (Wii64/WiiVC/WiiUVC/PJ64/Mupern64/Retroarch/Everdrive64/etc) all have slightly different format requirements for these save files to be compatible including byteswapping and size requirements. 49 | * The Retroarch emulator has a unique size for all its games saves (regardless of console): 290 kilobytes as far as I can tell. Native N64 save files are padded to this size. And some of them store the actual save content at strange offsets. (Both front and back padding) 50 | * This is why I have created a "Standard Size" for these save types. The standard size is set to be the smallest file size to be compatible across all emulators/hardware. 51 | 52 | =============================== 53 | Standard Save File Type Sizes 54 | =============================== 55 | 56 | * All N64 Save file conversions will output the converted save file to the standard file type size for maximum compatibility 57 | * Here are the Standard Save File Sizes: 58 | - 4Kbit EEPROM (.eep): 2048 bytes (padded to 16Kbit EEPROM size) 59 | - 16Kbit EEPROM (.eep): 2048 bytes (Same as actual N64 hardware size. Therefore, 4Kbit and 16Kbit EEPs are indistinguishable for the purposes of this application) 60 | - SRAM (.sra): 32,768 bytes (Same as actual N64 hardware size) 61 | - FlashRAM (.fla): 131,072 bytes (Same as actual N64 hardware size) 62 | - Controller Pak (.mpk): 131,072 bytes (Padded to 4x the actual N64 hardware size to simulate 4 paks for the 4 controllers) 63 | - Retroarch Save (.srm): 290 kilobytes (Same as the real Retroarch save) 64 | 65 | ================== 66 | Save File Notes 67 | ================== 68 | 69 | * SRAM and FLA saves need to be byteswapped when converting between PC emulators and Wii/WiiU/N64 70 | * Controller Pak and EEPROM saves do NOT need to be byteswapped when converting between PC Emulators and Wii/WiiU/N64. But good practice to at least do a resize to ensure compatibility. 71 | * Retroarch sets all save files to 290 kilobytes. The save file content is front-padded at strange offsets for SRAM, FLA, and Controller Pak. EEPROM has no offset, just back-padding only. No worries, this app takes care of all that for you. 72 | * For Wii Virtual console games: 73 | - Use Savegame Manager GX or FE100 import/export saves (restore/backup) 74 | - The save file extracted with Savegame Manager GX and FE100 have no file extension. But that's ok, file extension isn't needed with this app. 75 | - To import a save to Wii, first use this app and convert the save file (set output target to Wii/WiiU/Everdrive64). Rename to [Wii file name] with no extension. Then use Savegame Manager GX or FE100 to import to Wii 76 | - To export a save from Wii, first use Savegame Manager GX or FE100 to export the save. Then use this app and convert the save file (set input source to Wii/WiiU/Everdrive64) 77 | after a save file has been converted with this program (output target set to Wii/WiiU/Everdrive64), you must use Savegame Manager GX or FE100 to pack the save to the data.bin to be read by the Wii virtual console games. 78 | * For Wii U Virtual console games: 79 | - Use SaveMii Mod to import/export saves (restore/backup) 80 | - First start the vc game on the Wii U to create the necessary folder structure on your SD card. 81 | - Then turn off the WiiU while still in the VC game. (This is needed in order to delete the restore point save state .rs2 file) 82 | - Put SD card into PC and find the .sav file. This is the real save file (delete any .rs2 file if it appears there). 83 | - To import a save to WiiU, first use this app and convert the save file (set output target to Wii/WiiU/Everdrive64). Rename to [WiiU file name].sav. Then use SaveMii Mod to import to WiiU 84 | - To export a save from WiiU, first use SaveMii Mod to export the save. Then use this app and convert the save file (set input source to Wii/WiiU/Everdrive64) 85 | * I noticed exported SRAM saves from WiiU are 128 kilobytes. Just ignore it. Importing SRAM saves like LoZ:OoT at the standard size of 32 kilobytes works fine. 86 | * I noticed that SRAM saves created by Everdrive64 use the extention (.srm) instead of (.sra). No big deal. Just don't confuse those (.srm) saves with Retroarch saves which also use the (.srm) extension. 87 | * Wii64/not64 cannot read 4Kbit EEPROM saves at .5 kilobytes. The 4Kbit EEPROM must be padded to 2 kilobytes (i.e. the same size as 16Kbit EEPROM). Also Wii64 cannot read Controller Pak saves at 32 kilobytes. They must be padded to 128 kilobytes (i.e. 4x the size. It assumes a controller pak for each of the 4 controllers? Just my guess). This app takes care of all that for you. 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /N64SaveConverterGui/src/Readme.txt: -------------------------------------------------------------------------------- 1 | ===================== 2 | N64SaveConverterGui 3 | ===================== 4 | 5 | Author: Daniel Falk 6 | Version: 1.0 7 | 8 | =========================== 9 | About N64SaveConverterGui 10 | =========================== 11 | 12 | N64SaveConverterGui is a tool to convert N64 saves files for transfer between emulators and/or real N64 hardware. 13 | 14 | =========================== 15 | N64SaveConverterGui Usage 16 | =========================== 17 | 18 | To convert: 19 | * Select a save file to convert 20 | * Select an input source (from where did this save file originate?) 21 | * Select an input type 22 | * Select an output target (what emulator/platform do you want to convert this file to?) 23 | * Select an output type 24 | * Click Convert button. If conversion succeeded, a success messsage will appear. 25 | * Some save files do not need to be converted (i.e. Wii .eep/.mpk to PJ64 .eep/.mpk and vice versa). These scenarios are therefore not a selectable option. But it is still recommended to do a resize to ensure compatibility between emulators 26 | 27 | To resize: 28 | * You can pad/trim your save file to the standard file type size (see below) in order to increase compatibility across emulators 29 | * Click the pad/trim checkbox 30 | * Select an input type 31 | * Click Resize button. If resize succeeded, a success messsage will appear. 32 | 33 | Lookup list: 34 | * Check the file extension of the file you selected to determine input/output type. Retroarch is always (.srm) 35 | * Otherwise, use the Lookup list under the Help menu. 36 | 37 | ============================ 38 | Info about N64 Saves Files 39 | ============================ 40 | 41 | * The Nintendo 64 has 5 save formats: 4Kbit EEPROM (.eep), 16Kbit EEPROM (.eep), SRAM (.sra), FlashRAM (.fla), and Controller Pak (.mpk) 42 | * The exact file sizes that N64 hardware generates for these save types are as follows: 43 | - 4Kbit EEPROM: 512 bytes (.5 kilobytes) 44 | - 16Kbit EEPROM: 2048 bytes (2 kilobytes) 45 | - SRAM: 32,768 bytes (32 kilobytes) 46 | - FlashRAM: 131,072 bytes (128 kilobytes) 47 | - Controller Pak: 32,768 bytes (32 kilobytes) 48 | * Different emulators and hardware (Wii64/WiiVC/WiiUVC/PJ64/Mupern64/Retroarch/Everdrive64/etc) all have slightly different format requirements for these save files to be compatible including byteswapping and size requirements. 49 | * The Retroarch emulator has a unique size for all its games saves (regardless of console): 290 kilobytes as far as I can tell. Native N64 save files are padded to this size. And some of them store the actual save content at strange offsets. (Both front and back padding) 50 | * This is why I have created a "Standard Size" for these save types. The standard size is set to be the smallest file size to be compatible across all emulators/hardware. 51 | 52 | =============================== 53 | Standard Save File Type Sizes 54 | =============================== 55 | 56 | * All N64 Save file conversions will output the converted save file to the standard file type size for maximum compatibility 57 | * Here are the Standard Save File Sizes: 58 | - 4Kbit EEPROM (.eep): 2048 bytes (padded to 16Kbit EEPROM size) 59 | - 16Kbit EEPROM (.eep): 2048 bytes (Same as actual N64 hardware size. Therefore, 4Kbit and 16Kbit EEPs are indistinguishable for the purposes of this application) 60 | - SRAM (.sra): 32,768 bytes (Same as actual N64 hardware size) 61 | - FlashRAM (.fla): 131,072 bytes (Same as actual N64 hardware size) 62 | - Controller Pak (.mpk): 131,072 bytes (Padded to 4x the actual N64 hardware size to simulate 4 paks for the 4 controllers) 63 | - Retroarch Save (.srm): 290 kilobytes (Same as the real Retroarch save) 64 | 65 | ================== 66 | Save File Notes 67 | ================== 68 | 69 | * SRAM and FLA saves need to be byteswapped when converting between PC emulators and Wii/WiiU/N64 70 | * Controller Pak and EEPROM saves do NOT need to be byteswapped when converting between PC Emulators and Wii/WiiU/N64. But good practice to at least do a resize to ensure compatibility. 71 | * Retroarch sets all save files to 290 kilobytes. The save file content is front-padded at strange offsets for SRAM, FLA, and Controller Pak. EEPROM has no offset, just back-padding only. No worries, this app takes care of all that for you. 72 | * For Wii Virtual console games: 73 | - Use Savegame Manager GX or FE100 import/export saves (restore/backup) 74 | - The save file extracted with Savegame Manager GX and FE100 have no file extension. But that's ok, file extension isn't needed with this app. 75 | - To import a save to Wii, first use this app and convert the save file (set output target to Wii/WiiU/Everdrive64). Rename to [Wii file name] with no extension. Then use Savegame Manager GX or FE100 to import to Wii 76 | - To export a save from Wii, first use Savegame Manager GX or FE100 to export the save. Then use this app and convert the save file (set input source to Wii/WiiU/Everdrive64) 77 | after a save file has been converted with this program (output target set to Wii/WiiU/Everdrive64), you must use Savegame Manager GX or FE100 to pack the save to the data.bin to be read by the Wii virtual console games. 78 | * For Wii U Virtual console games: 79 | - Use SaveMii Mod to import/export saves (restore/backup) 80 | - First start the vc game on the Wii U to create the necessary folder structure on your SD card. 81 | - Then turn off the WiiU while still in the VC game. (This is needed in order to delete the restore point save state .rs2 file) 82 | - Put SD card into PC and find the .sav file. This is the real save file (delete any .rs2 file if it appears there). 83 | - To import a save to WiiU, first use this app and convert the save file (set output target to Wii/WiiU/Everdrive64). Rename to [WiiU file name].sav. Then use SaveMii Mod to import to WiiU 84 | - To export a save from WiiU, first use SaveMii Mod to export the save. Then use this app and convert the save file (set input source to Wii/WiiU/Everdrive64) 85 | * I noticed exported SRAM saves from WiiU are 128 kilobytes. Just ignore it. Importing SRAM saves like LoZ:OoT at the standard size of 32 kilobytes works fine. 86 | * I noticed that SRAM saves created by Everdrive64 use the extention (.srm) instead of (.sra). No big deal. Just don't confuse those (.srm) saves with Retroarch saves which also use the (.srm) extension. 87 | * Wii64/not64 cannot read 4Kbit EEPROM saves at .5 kilobytes. The 4Kbit EEPROM must be padded to 2 kilobytes (i.e. the same size as 16Kbit EEPROM). Also Wii64 cannot read Controller Pak saves at 32 kilobytes. They must be padded to 128 kilobytes (i.e. 4x the size. It assumes a controller pak for each of the 4 controllers? Just my guess). This app takes care of all that for you. 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /N64SaveConverterGui/src/Lookup.txt: -------------------------------------------------------------------------------- 1 | This is a list of N64 games grouped by save type. Use this list to determine what save type to use when converting or resizing your game saves. 2 | 3 | ======================= 4 | Controller Pak (.mpk) 5 | ======================= 6 | 7 | Aero Gauge 8 | Aidyn Chronicles: The First Mage 9 | All-Star Baseball 2000 10 | All-Star Baseball 2001 11 | All-Star Baseball '99 12 | Armorines: Project S.W.A.R.M. 13 | Army Men: Air Combat 14 | Army Men: Sarge's Heroes 15 | Army Men: Sarge's Heroes 2 16 | Asteroids Hyper 64 17 | Automobili Lamborghini 18 | Bassmaster 2000 19 | Battle Tanx 20 | Battle Tanx: Global Assault 21 | Battlezone: Rise of the Black Dogs 22 | Beetle Adventure Racing 23 | Big Mountain 2000 24 | Bio F.R.E.A.K.S. 25 | Blast Corps 26 | Blues Brothers 2000 27 | Bomberman 64 28 | Bottom of the 9th 29 | Brunswick Circuit Pro Bowling 30 | Buck Bumble 31 | Bug's Life, A 32 | Bust-A-Move 2: Arcade Edition 33 | Bust-A-Move '99 34 | California Speed 35 | Carmageddon 64 36 | Castlevania 37 | Castlevania: Legacy of Darkness 38 | Centre Court Tennis (PAL) 39 | Chameleon Twist 2 40 | Cruis'n USA 41 | CyberTiger 42 | Daikatana 43 | Deadly Arts 44 | Destruction Derby 45 | Diddy Kong Racing 46 | Doom 64 47 | Dual Heroes 48 | Duke Nukem 64 49 | Duke Nukem: Zero Hour 50 | ECW: Hardcore Revolution 51 | Excitebike 64 52 | Extreme-G 53 | Extreme-G 2 54 | F1 Pole Position 64 55 | F1 Racing Championship (PAL) 56 | FIFA '99 57 | FIFA Soccer 64 58 | FIFA: Road to World Cup 98 59 | Fighters Destiny 60 | Fighter Destiny 2 61 | Fighting Force 64 62 | Flying Dragon 63 | Forsaken 64 64 | Fox Sports College Hoops '99 65 | Gauntlet Legends 66 | Gex 3: Deep Cover Gecko 67 | Gex 64: Enter the Gecko 68 | Goemon's Great Adventure 69 | Golden Nugget 64 70 | GT64 Championship Edition 71 | Hercules: The Legendary Journeys 72 | Hexen 73 | Hot Wheels Turbo Racing 74 | Hybrid Heaven 75 | Hydro Thunder 76 | Iggy's Reckin' Balls 77 | International Superstar Soccer 64 78 | International Superstar Soccer '98 79 | International Superstar Soccer 2000 80 | International Track & Field 2000 81 | Jeremy McGrath Supercross 2000 82 | Killer Instinct Gold 83 | Knockout Kings 2000 84 | Kobe Bryant in NBA Courtside 85 | Lego Racers 86 | Madden 2000 87 | Madden 2001 88 | Madden 2002 89 | Madden 64 90 | Madden '99 91 | Mario Kart 64 92 | Mia Hamm Soccer 64 93 | Micro Machines 64 Turbo 94 | Midway's Greatest Arcade Hits Volume 1 95 | Mike Piazza's Strike Zone 96 | Milo's Astro Lanes 97 | Monaco Grand Prix 98 | Mortal Kombat 4 99 | Mortal Kombat Mythologies: Sub-Zero 100 | Ms. Pac-Man Maze Madness 101 | Multi-Racing Championship 102 | Mystical Ninja Starring Goemon 103 | Nagano Winter Olympics '98 104 | Namco Museum 64 105 | Nascar 2000 106 | Nascar '99 107 | NBA Hang Time 108 | NBA In the Zone 2000 109 | NBA In the Zone '98 110 | NBA In the Zone '99 111 | NBA Jam 2000 112 | NBA Jam '99 113 | NBA Live 2000 114 | NBA Live '99 115 | NBA Show Time: NBA on NBC 116 | New Tetris, The 117 | NFL Blitz 118 | NFL Blitz 2000 119 | NFL Blitz 2001 120 | NFL Blitz Special Edition 121 | NFL Quarterback Club 2000 122 | NFL Quarterback Club 2001 123 | NFL Quarterback Club '98 124 | NFL Quarterback Club '99 125 | NHL '99 126 | NHL Blades of Steel '99 127 | NHL Breakaway Hockey '98 128 | NHL Breakaway Hockey '99 129 | Nightmare Creatures 130 | Nuclear Strike 64 131 | Off Road Challenge 132 | Olympic Hockey '98 133 | Paperboy 134 | Penny Racers 135 | Perfect Dark 136 | Polaris Snocross 137 | Power Rangers: Lightspeed Rescue 138 | Premier Manager 64 (PAL) 139 | Quake 140 | Quake II 141 | Quest 64 142 | Rainbow Six 143 | Rakuga Kids (PAL) 144 | Rally Challenge 2000 145 | Rampage 2: Universal Tour 146 | Rampage: World Tour 147 | Rat Attack 148 | Rayman 2: The Great Escape 149 | Razor Freestyle Scooter 150 | Ready 2 Rumble Boxing 151 | Ready 2 Rumble Boxing Round 2 152 | Re-Volt 153 | Road Rash 64 154 | Roadsters 155 | Robotron 64 156 | Rugrats in Paris: The Movie 157 | Rush 2: Extreme Racing USA 158 | S.C.A.R.S. 159 | San Francisco Rush: 2049 160 | San Francisco Rush: Extreme Racing 161 | Scooby-Doo: Classic Creep Capers 162 | Shadow Man 163 | Shadowgate 64: Trials of the Four Towers 164 | Snowboard Kids 165 | South Park 166 | South Park Rally 167 | Space Invaders 168 | Spider-Man 169 | Stunt Racer 64 170 | Supercross 2000 171 | Superman 172 | Tarzan 173 | The World Is Not Enough 174 | Tonic Trouble 175 | Tony Hawk's Pro Skater 176 | Tony Hawk's Pro Skater 2 177 | Tony Hawk's Pro Skater 3 178 | Top Gear Hyper Bike 179 | Top Gear Rally 180 | Top Gear Rally 2 181 | Toy Story 2 182 | Triple Play 2000 183 | Turok: Dinosaur Hunter 184 | Turok 2: Seeds of Evil 185 | Turok 3: Shadow of Oblivion 186 | Turok: Rage Wars 187 | Twisted Edge Extreme Snowboarding 188 | Vigilante 8 189 | Vigilante 8: 2nd Offense 190 | Virtual Chess 64 191 | Virtual Pool 64 192 | Wave Race 64 193 | Wayne Gretzky's 3D Hockey 194 | Wayne Gretzky's 3D Hockey '98 195 | WCW Backstage Assault 196 | WCW Mayhem 197 | WCW Nitro 198 | WCW vs. NWO: World Tour 199 | Wetrix 200 | WinBack: Covert Operations 201 | Wipeout 64 202 | World Cup '98 203 | World Driver Championship 204 | Wrestlemania 2000 205 | WWF: Attitude 206 | WWF: Warzone 207 | Xena: Talisman of Fate 208 | 209 | ======================= 210 | EEPROM (.eep) 211 | ======================= 212 | 213 | AeroFighters Assault 214 | Air Boarder 64 (PAL) 215 | All-Star Tennis '99 216 | Banjo-Kazooie 217 | Banjo-Tooie 218 | Bass Hunter 64 219 | Big Mountain 2000 220 | Blast Corps 221 | Body Harvest 222 | Bomberman 64 223 | Bomberman 64: Second Attack 224 | Bomberman Hero 225 | Chameleon Twist 226 | Chopper Attack 227 | Conker's Bad Fur Day 228 | Cruis'n Exotica 229 | Cruis'n USA 230 | Cruis'n World 231 | Diddy Kong Racing 232 | Donald Duck: Goin' Quackers 233 | Donkey Kong 64 234 | Dr. Mario 64 235 | Earthworm Jim 3D 236 | Excitebike 64 237 | F-1 World Grand Prix 238 | F-1 World Grand Prix II (PAL) 239 | Fighter Destiny 2 240 | Fighters Destiny 241 | GT64 Championship Edition 242 | Glover 243 | GoldenEye 007 244 | Hey you, Pikachu! 245 | Indiana Jones and the Infernal Machine 246 | Indy Racing 2000 247 | Killer Instinct Gold 248 | Kirby 64: The Crystal Shards 249 | Kobe Bryant in NBA Courtside 250 | Lode Runner 3D 251 | Loony Toons: Duck Dodgers 252 | Mario Kart 64 253 | Mario Party 254 | Mario Party 2 255 | Mario Party 3 256 | Mario Tennis 257 | Mickey's Speedway USA 258 | Mischief Makers 259 | Mission: Impossible 260 | Monopoly 261 | Multi-Racing Championship 262 | PGA European Tour 263 | Penny Racers 264 | Perfect Dark 265 | Pilotwings 64 266 | Premier Manager 64 (PAL) 267 | Ridge Racer 64 268 | Rocket: Robot on Wheels 269 | Snowboard Kids 2 270 | Space Station Silicon Valley 271 | Star Fox 64 272 | Star Soldier: Vanishing Earth 273 | Star Wars Episode 1 Racer 274 | Star Wars Episode 1: Battle for Naboo 275 | Star Wars: Rogue Squadron 276 | Star Wars: Shadows of the Empire 277 | Starshot Space Circus 278 | Super Mario 64 279 | Taz Express (PAL) 280 | Tetrisphere 281 | Tom & Jerry in Fists of Furry 282 | Top Gear Overdrive 283 | V-Rally Edition '99 284 | Waialae Country Club: True Golf Classics 285 | Wave Race 64 286 | Worms Armageddon 287 | Yoshi's Story 288 | 289 | ======================= 290 | SRAM (.sra) 291 | ======================= 292 | 293 | 1080 Snowboarding 294 | Dezaemon 3D (JPN, 768Kbit) 295 | F-Zero X 296 | Harvest Moon 64 297 | Legend of Zelda: Ocarina of Time, The 298 | Major League Baseball featuring Ken Griffey Jr. 299 | Mario Golf 300 | New Tetris, The 301 | Ogre Battle 64: Person of Lordly Caliber 302 | Pocket Monsters Stadium (JPN) 303 | Resident Evil 2 304 | Super Smash Bros. 305 | WCW/NWO Revenge 306 | WWF: Wrestlemania 2000 307 | Animal Crossing (JPN) uses a battery for the real time clock (RTC) chip. 308 | 309 | ======================= 310 | FlashRAM (.fla) 311 | ======================= 312 | 313 | Command & Conquer 314 | Jet Force Gemini 315 | Ken Griffey Jr's Slugfest 316 | Legend of Zelda: Majora's Mask, The 317 | Megaman 64 318 | NBA Courtside 2 featuring Kobe Bryant 319 | Paper Mario 320 | Pokemon Puzzle League 321 | Pokemon Snap 322 | Pokemon Stadium 323 | Pokemon Stadium 2 324 | Starcraft 64 325 | Tigger's Honey Hunt 326 | WWF: No Mercy 327 | 328 | ======================= 329 | No Save 330 | ======================= 331 | 332 | Batman Beyond: Return of the Joker 333 | Charlie Blast's Territory (Password) 334 | Clayfighter 63 1/3 335 | Clayfighter: Sculptor's Cut 336 | Dark Rift 337 | Elmo's Letter Adventure 338 | Elmo's Number Journey 339 | Jeopardy! 340 | Knife Edge: Nosegunner 341 | Mace: The Dark Age 342 | Magical Tetris Challenge 343 | Monster Truck Madness (Password) 344 | Mortal Kombat Trilogy 345 | Powerpuff Girls: Chemical X Traction (Password) 346 | Rugrats: Scavenger Hunt 347 | South Park: Chef's Luv Shack 348 | Transformers Beast Wars: Transmetals 349 | War Gods 350 | Wheel of Fortune -------------------------------------------------------------------------------- /N64SaveConverterGui/src/N64SaveConverter.java: -------------------------------------------------------------------------------- 1 | import java.io.File; 2 | import java.nio.file.Files; 3 | import java.nio.file.Path; 4 | import java.nio.file.Paths; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.Set; 13 | import java.awt.*; 14 | import java.awt.event.*; 15 | 16 | import javax.imageio.ImageIO; 17 | import javax.swing.BoxLayout; 18 | import javax.swing.GroupLayout; 19 | import javax.swing.JButton; 20 | import javax.swing.JCheckBox; 21 | import javax.swing.JComboBox; 22 | import javax.swing.JFileChooser; 23 | import javax.swing.JFrame; 24 | import javax.swing.JLabel; 25 | import javax.swing.JMenu; 26 | import javax.swing.JMenuBar; 27 | import javax.swing.JMenuItem; 28 | import javax.swing.JOptionPane; 29 | import javax.swing.JPanel; 30 | import javax.swing.JScrollPane; 31 | import javax.swing.JTextArea; 32 | import javax.swing.JTextField; 33 | import javax.swing.GroupLayout.Alignment; 34 | import javax.swing.event.DocumentEvent; 35 | import javax.swing.event.DocumentListener; 36 | import javax.swing.filechooser.FileNameExtensionFilter; 37 | import javax.swing.text.DefaultHighlighter; 38 | import javax.swing.text.Highlighter.HighlightPainter; 39 | 40 | public class N64SaveConverter { 41 | 42 | private static final int SIZE_EEP = 2048; 43 | private static final int SIZE_SRA = 32768; 44 | private static final int SIZE_FLA = 131072; 45 | private static final int SIZE_MPK = 131072; 46 | private static final int SIZE_SRM = 296960; 47 | private static final int SIZE_SRA_SRM_OFFSET = 133120; 48 | private static final int SIZE_FLA_SRM_OFFSET = SIZE_SRM - SIZE_FLA; 49 | private static final int SIZE_MPK_SRM_OFFSET = 2048; 50 | private static final String EEP_EXT = ".eep"; 51 | private static final String SRA_EXT = ".sra"; 52 | private static final String FLA_EXT = ".fla"; 53 | private static final String MPK_EXT = ".mpk"; 54 | private static final String SRM_EXT = ".srm"; 55 | private static final String EEP_LABEL = "EEPROM (.eep)"; 56 | private static final String SRA_LABEL = "SRAM (.sra)"; 57 | private static final String FLA_LABEL = "FlashRAM (.fla)"; 58 | private static final String MPK_LABEL = "Controller Pak (.mpk)"; 59 | private static final String SRM_LABEL = "Retroarch Save (.srm)"; 60 | private static final String WII_LABEL = "Wii/WiiU/Everdrive64"; 61 | private static final String PJ64_LABEL = "Project64/Mupen64"; 62 | private static final String RA_LABEL = "Retroarch"; 63 | private static final String[] inputTypeList = {"", EEP_LABEL, SRA_LABEL, FLA_LABEL, MPK_LABEL, SRM_LABEL}; 64 | private static final String[] outputTypeList = {"", EEP_LABEL, SRA_LABEL, FLA_LABEL, MPK_LABEL, SRM_LABEL}; 65 | private static final String[] inputSourceList = {"", WII_LABEL, PJ64_LABEL, RA_LABEL}; 66 | private static final String[] outputTargetList = {"", WII_LABEL, PJ64_LABEL, RA_LABEL}; 67 | private static final Map standardSizeTable; 68 | private static final Map conversionTable; 69 | 70 | static { 71 | standardSizeTable = new HashMap<>(); 72 | standardSizeTable.put(EEP_LABEL, new StandardSizeTableEntry(SIZE_EEP, EEP_EXT)); 73 | standardSizeTable.put(SRA_LABEL, new StandardSizeTableEntry(SIZE_SRA, SRA_EXT)); 74 | standardSizeTable.put(FLA_LABEL, new StandardSizeTableEntry(SIZE_FLA, FLA_EXT)); 75 | standardSizeTable.put(MPK_LABEL, new StandardSizeTableEntry(SIZE_MPK, MPK_EXT)); 76 | standardSizeTable.put(SRM_LABEL, new StandardSizeTableEntry(SIZE_SRM, SRM_EXT)); 77 | 78 | conversionTable = new HashMap<>(); 79 | conversionTable.put(WII_LABEL+"-"+EEP_LABEL+"-"+RA_LABEL+"-"+SRM_LABEL, new ConversionTableEntry(SIZE_EEP, SIZE_SRM, 0, false, SRM_EXT)); 80 | conversionTable.put(WII_LABEL+"-"+SRA_LABEL+"-"+PJ64_LABEL+"-"+SRA_LABEL, new ConversionTableEntry(SIZE_SRA, SIZE_SRA, 0, true, SRA_EXT)); 81 | conversionTable.put(WII_LABEL+"-"+SRA_LABEL+"-"+RA_LABEL+"-"+SRM_LABEL, new ConversionTableEntry(SIZE_SRA, SIZE_SRM, SIZE_SRA_SRM_OFFSET, true, SRM_EXT)); 82 | conversionTable.put(WII_LABEL+"-"+FLA_LABEL+"-"+PJ64_LABEL+"-"+FLA_LABEL, new ConversionTableEntry(SIZE_FLA, SIZE_FLA, 0, true, FLA_EXT)); 83 | conversionTable.put(WII_LABEL+"-"+FLA_LABEL+"-"+RA_LABEL+"-"+SRM_LABEL, new ConversionTableEntry(SIZE_FLA, SIZE_SRM, SIZE_FLA_SRM_OFFSET, true, SRM_EXT)); 84 | conversionTable.put(WII_LABEL+"-"+MPK_LABEL+"-"+RA_LABEL+"-"+SRM_LABEL, new ConversionTableEntry(SIZE_MPK, SIZE_SRM, SIZE_MPK_SRM_OFFSET, false, SRM_EXT)); 85 | conversionTable.put(PJ64_LABEL+"-"+EEP_LABEL+"-"+RA_LABEL+"-"+SRM_LABEL, new ConversionTableEntry(SIZE_EEP, SIZE_SRM, 0, false, SRM_EXT)); 86 | conversionTable.put(PJ64_LABEL+"-"+SRA_LABEL+"-"+WII_LABEL+"-"+SRA_LABEL, new ConversionTableEntry(SIZE_SRA, SIZE_SRA, 0, true, SRA_EXT)); 87 | conversionTable.put(PJ64_LABEL+"-"+SRA_LABEL+"-"+RA_LABEL+"-"+SRM_LABEL, new ConversionTableEntry(SIZE_SRA, SIZE_SRM, SIZE_SRA_SRM_OFFSET, false, SRM_EXT)); 88 | conversionTable.put(PJ64_LABEL+"-"+FLA_LABEL+"-"+WII_LABEL+"-"+FLA_LABEL, new ConversionTableEntry(SIZE_FLA, SIZE_FLA, 0, true, FLA_EXT)); 89 | conversionTable.put(PJ64_LABEL+"-"+FLA_LABEL+"-"+RA_LABEL+"-"+SRM_LABEL, new ConversionTableEntry(SIZE_FLA, SIZE_SRM, SIZE_FLA_SRM_OFFSET, false, SRM_EXT)); 90 | conversionTable.put(PJ64_LABEL+"-"+MPK_LABEL+"-"+RA_LABEL+"-"+SRM_LABEL, new ConversionTableEntry(SIZE_MPK, SIZE_SRM, SIZE_MPK_SRM_OFFSET, false, SRM_EXT)); 91 | conversionTable.put(RA_LABEL+"-"+SRM_LABEL+"-"+WII_LABEL+"-"+EEP_LABEL, new ConversionTableEntry(SIZE_SRM, SIZE_EEP, 0, false, EEP_EXT)); 92 | conversionTable.put(RA_LABEL+"-"+SRM_LABEL+"-"+WII_LABEL+"-"+SRA_LABEL, new ConversionTableEntry(SIZE_SRM, SIZE_SRA, -SIZE_SRA_SRM_OFFSET, true, SRA_EXT)); 93 | conversionTable.put(RA_LABEL+"-"+SRM_LABEL+"-"+WII_LABEL+"-"+FLA_LABEL, new ConversionTableEntry(SIZE_SRM, SIZE_FLA, -SIZE_FLA_SRM_OFFSET, true, FLA_EXT)); 94 | conversionTable.put(RA_LABEL+"-"+SRM_LABEL+"-"+WII_LABEL+"-"+MPK_LABEL, new ConversionTableEntry(SIZE_SRM, SIZE_MPK, -SIZE_MPK_SRM_OFFSET, false, MPK_EXT)); 95 | conversionTable.put(RA_LABEL+"-"+SRM_LABEL+"-"+PJ64_LABEL+"-"+EEP_LABEL, new ConversionTableEntry(SIZE_SRM, SIZE_EEP, 0, false, EEP_EXT)); 96 | conversionTable.put(RA_LABEL+"-"+SRM_LABEL+"-"+PJ64_LABEL+"-"+SRA_LABEL, new ConversionTableEntry(SIZE_SRM, SIZE_SRA, -SIZE_SRA_SRM_OFFSET, false, SRA_EXT)); 97 | conversionTable.put(RA_LABEL+"-"+SRM_LABEL+"-"+PJ64_LABEL+"-"+FLA_LABEL, new ConversionTableEntry(SIZE_SRM, SIZE_FLA, -SIZE_FLA_SRM_OFFSET, false, FLA_EXT)); 98 | conversionTable.put(RA_LABEL+"-"+SRM_LABEL+"-"+PJ64_LABEL+"-"+MPK_LABEL, new ConversionTableEntry(SIZE_SRM, SIZE_MPK, -SIZE_MPK_SRM_OFFSET, false, MPK_EXT)); 99 | } 100 | 101 | private static JFrame mainFrame; 102 | private static JFrame readmeFrame; 103 | private static JFrame lookupFrame; 104 | private static Image appIcon; 105 | private static HighlightPainter painter; 106 | private static JButton inputPathButton; 107 | private static JTextField inputPathTextField; 108 | private static JCheckBox standardSizeCheckbox; 109 | private static JLabel inputSourceLabel; 110 | private static JComboBox inputSource; 111 | private static JLabel inputTypeLabel; 112 | private static JComboBox inputType; 113 | private static JLabel outputTargetLabel; 114 | private static JComboBox outputTarget; 115 | private static JLabel outputTypeLabel; 116 | private static JComboBox outputType; 117 | private static JButton convertButton; 118 | 119 | private static String prevSearchText = ""; 120 | private static int prevSearchTextIndex = 0; 121 | 122 | public static void main(String[] args) throws Exception { 123 | appIcon = ImageIO.read(N64SaveConverter.class.getResourceAsStream("N64Logo.png")); 124 | painter = new DefaultHighlighter.DefaultHighlightPainter(Color.yellow); 125 | 126 | inputPathButton = new JButton("Select an N64 save file"); 127 | inputPathButton.setFocusable(false); 128 | inputPathButton.addActionListener(inputPathButtonListener()); 129 | inputPathTextField = new JTextField(); 130 | inputPathTextField.setPreferredSize(new Dimension(500, 30)); 131 | inputPathTextField.getDocument().addDocumentListener(inputPathTextFieldListener()); 132 | standardSizeCheckbox = new JCheckBox(); 133 | standardSizeCheckbox.setText("Pad/trim to standard file type size. (No conversion)"); 134 | standardSizeCheckbox.setFocusable(false); 135 | standardSizeCheckbox.addActionListener(standardSizeCheckboxListener()); 136 | inputSourceLabel = new JLabel("Save file source:"); 137 | inputSource = new JComboBox(inputSourceList); 138 | inputSource.setFocusable(false); 139 | inputSource.addActionListener(inputSourceListener()); 140 | inputTypeLabel = new JLabel("Save file source type:"); 141 | inputType = new JComboBox(new String[0]); 142 | inputType.setFocusable(false); 143 | inputType.setEnabled(false); 144 | inputType.addActionListener(inputTypeListener()); 145 | outputTargetLabel = new JLabel("Save file target:"); 146 | outputTarget = new JComboBox(new String[0]); 147 | outputTarget.setFocusable(false); 148 | outputTarget.setEnabled(false); 149 | outputTarget.addActionListener(outputTargetListener()); 150 | outputTypeLabel = new JLabel("Save file target type:"); 151 | outputType = new JComboBox(new String[0]); 152 | outputType.setFocusable(false); 153 | outputType.setEnabled(false); 154 | convertButton = new JButton("Convert save file"); 155 | convertButton.setFocusable(false); 156 | convertButton.setEnabled(false); 157 | convertButton.addActionListener(convertButtonListener()); 158 | 159 | JPanel groupPanel = new JPanel(); 160 | GroupLayout groupLayout = new GroupLayout(groupPanel); 161 | groupPanel.setLayout(groupLayout); 162 | groupLayout.setAutoCreateGaps(true); 163 | groupLayout.setHorizontalGroup( 164 | groupLayout.createSequentialGroup() 165 | .addGroup(groupLayout.createParallelGroup(Alignment.TRAILING) 166 | .addComponent(inputSourceLabel) 167 | .addComponent(inputTypeLabel) 168 | .addComponent(outputTargetLabel) 169 | .addComponent(outputTypeLabel)) 170 | .addGroup(groupLayout.createParallelGroup(Alignment.LEADING) 171 | .addComponent(inputSource) 172 | .addComponent(inputType) 173 | .addComponent(outputTarget) 174 | .addComponent(outputType)) 175 | .addComponent(standardSizeCheckbox) 176 | ); 177 | groupLayout.setVerticalGroup( 178 | groupLayout.createSequentialGroup() 179 | .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE) 180 | .addComponent(inputSourceLabel) 181 | .addComponent(inputSource)) 182 | .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE) 183 | .addComponent(inputTypeLabel) 184 | .addComponent(inputType) 185 | .addComponent(standardSizeCheckbox)) 186 | .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE) 187 | .addComponent(outputTargetLabel) 188 | .addComponent(outputTarget)) 189 | .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE) 190 | .addComponent(outputTypeLabel) 191 | .addComponent(outputType)) 192 | ); 193 | 194 | JMenuBar menuBar = new JMenuBar(); 195 | JMenu help = new JMenu("Help"); 196 | JMenuItem readme = new JMenuItem("Readme"); 197 | readme.addActionListener(readmeListener()); 198 | JMenuItem lookup = new JMenuItem("Lookup"); 199 | lookup.addActionListener(lookupListener()); 200 | help.add(readme); 201 | help.add(lookup); 202 | menuBar.add(help); 203 | 204 | JPanel inputPathPanel = new JPanel(); 205 | inputPathPanel.setLayout(new FlowLayout(FlowLayout.LEADING)); 206 | inputPathPanel.add(inputPathButton); 207 | inputPathPanel.add(inputPathTextField); 208 | JPanel selectionPanel = new JPanel(); 209 | selectionPanel.setLayout(new FlowLayout(FlowLayout.LEADING)); 210 | selectionPanel.add(groupPanel); 211 | JPanel convertPanel = new JPanel(); 212 | convertPanel.setLayout(new FlowLayout(FlowLayout.LEADING)); 213 | convertPanel.add(convertButton); 214 | 215 | mainFrame = new JFrame(); 216 | mainFrame.setIconImage(appIcon); 217 | mainFrame.setTitle("N64 Save File Converter"); 218 | mainFrame.setResizable(false); 219 | mainFrame.setLayout(new BoxLayout(mainFrame.getContentPane(), BoxLayout.Y_AXIS)); 220 | mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 221 | mainFrame.setJMenuBar(menuBar); 222 | mainFrame.add(inputPathPanel); 223 | mainFrame.add(selectionPanel); 224 | mainFrame.add(convertPanel); 225 | mainFrame.setVisible(true); 226 | mainFrame.pack(); 227 | mainFrame.setLocationRelativeTo(null); 228 | } 229 | 230 | public static ActionListener readmeListener() { 231 | return new ActionListener() { 232 | public void actionPerformed(ActionEvent event) { 233 | try { 234 | JTextArea readmeText = new JTextArea(new String(N64SaveConverter.class.getResourceAsStream("Readme.txt").readAllBytes())); 235 | readmeText.setMargin(new Insets(10,10,10,10)); 236 | readmeText.setLineWrap(true); 237 | readmeText.setEditable(false); 238 | JScrollPane readmeScrollPane = new JScrollPane(readmeText); 239 | readmeFrame = new JFrame(); 240 | readmeFrame.setIconImage(appIcon); 241 | readmeFrame.setTitle("N64 Save Converter Readme"); 242 | readmeFrame.setSize(1000, 1000); 243 | readmeFrame.add(readmeScrollPane); 244 | readmeFrame.setVisible(true); 245 | readmeFrame.addWindowListener(new WindowAdapter() { 246 | public void windowClosing(WindowEvent e) { 247 | mainFrame.setEnabled(true); 248 | readmeFrame.dispose(); 249 | } 250 | }); 251 | mainFrame.setEnabled(false); 252 | } catch (Exception e) { 253 | e.printStackTrace(); 254 | } 255 | } 256 | }; 257 | } 258 | 259 | public static ActionListener lookupListener() { 260 | return new ActionListener() { 261 | public void actionPerformed(ActionEvent event) { 262 | try { 263 | JTextArea lookupText = new JTextArea(new String(N64SaveConverter.class.getResourceAsStream("Lookup.txt").readAllBytes())); 264 | lookupText.setMargin(new Insets(10,10,10,10)); 265 | lookupText.setLineWrap(true); 266 | lookupText.setEditable(false); 267 | JScrollPane lookupScrollPane = new JScrollPane(lookupText); 268 | 269 | JTextField searchTextField = new JTextField(); 270 | searchTextField.setPreferredSize(new Dimension(500, 30)); 271 | JButton searchTextButton = new JButton("Search for text"); 272 | searchTextButton.setFocusable(false); 273 | searchTextButton.addActionListener(new ActionListener() { 274 | public void actionPerformed(ActionEvent event) { 275 | try { 276 | String currSearchText = searchTextField.getText(); 277 | int firstSearchTextIndex = lookupText.getText().toUpperCase().indexOf(currSearchText.toUpperCase()); 278 | if(firstSearchTextIndex == -1) { 279 | JOptionPane.showMessageDialog(null, "Search Value Not Found"); 280 | } else { 281 | lookupText.getHighlighter().removeAllHighlights(); 282 | int currSearchTextIndex = 0; 283 | if(prevSearchText.equals(currSearchText)) { 284 | currSearchTextIndex = lookupText.getText().toUpperCase().indexOf(currSearchText.toUpperCase(), prevSearchTextIndex + prevSearchText.length()); 285 | if(currSearchTextIndex == -1) { 286 | currSearchTextIndex = firstSearchTextIndex; 287 | } 288 | } else { 289 | currSearchTextIndex = firstSearchTextIndex; 290 | } 291 | lookupText.select(currSearchTextIndex, currSearchTextIndex + currSearchText.length()); 292 | lookupText.getHighlighter().addHighlight(currSearchTextIndex, currSearchTextIndex + currSearchText.length(), painter); 293 | prevSearchText = currSearchText; 294 | prevSearchTextIndex = currSearchTextIndex; 295 | } 296 | } catch (Exception e) { 297 | e.printStackTrace(); 298 | } 299 | } 300 | }); 301 | 302 | JPanel searchPanel = new JPanel(); 303 | searchPanel.setLayout(new FlowLayout(FlowLayout.LEADING)); 304 | searchPanel.add(searchTextField); 305 | searchPanel.add(searchTextButton); 306 | 307 | lookupFrame = new JFrame(); 308 | lookupFrame.setIconImage(appIcon); 309 | lookupFrame.setTitle("N64 Save Type Lookup"); 310 | lookupFrame.setSize(1000, 1000); 311 | lookupFrame.setLayout(new BoxLayout(lookupFrame.getContentPane(), BoxLayout.Y_AXIS)); 312 | lookupFrame.add(searchPanel); 313 | lookupFrame.add(lookupScrollPane); 314 | lookupFrame.setVisible(true); 315 | lookupFrame.addWindowListener(new WindowAdapter() { 316 | public void windowClosing(WindowEvent e) { 317 | mainFrame.setEnabled(true); 318 | lookupFrame.dispose(); 319 | prevSearchText = ""; 320 | prevSearchTextIndex = 0; 321 | } 322 | }); 323 | mainFrame.setEnabled(false); 324 | } catch (Exception e) { 325 | e.printStackTrace(); 326 | } 327 | } 328 | }; 329 | } 330 | 331 | public static ActionListener inputPathButtonListener() { 332 | return new ActionListener() { 333 | public void actionPerformed(ActionEvent event) { 334 | JFileChooser fileChooser = new JFileChooser(); 335 | fileChooser.setCurrentDirectory(new File(System.getProperty("user.dir"))); 336 | fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("N64 Save Files (*.eep; *.sra; *.fla; *.mpk; *.srm)", "eep", "sra", "fla", "mpk", "srm")); 337 | fileChooser.setAcceptAllFileFilterUsed(true); 338 | fileChooser.setDialogTitle("Select a save file"); 339 | if(fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { 340 | inputPathTextField.setText(fileChooser.getSelectedFile().getAbsolutePath()); 341 | } 342 | convertButtonCheck(); 343 | } 344 | }; 345 | } 346 | 347 | public static DocumentListener inputPathTextFieldListener() { 348 | return new DocumentListener() { 349 | public void changedUpdate(DocumentEvent e) { 350 | } 351 | public void removeUpdate(DocumentEvent e) { 352 | convertButtonCheck(); 353 | } 354 | public void insertUpdate(DocumentEvent e) { 355 | convertButtonCheck(); 356 | } 357 | }; 358 | } 359 | 360 | public static ActionListener standardSizeCheckboxListener() { 361 | return new ActionListener() { 362 | public void actionPerformed(ActionEvent event) { 363 | if(standardSizeCheckbox.isSelected()) { 364 | inputSource.setSelectedItem(""); 365 | inputType.removeAllItems(); 366 | for(String newInputTypeEntry : Arrays.asList(inputTypeList)) { 367 | if(!newInputTypeEntry.isEmpty()) { 368 | inputType.addItem(newInputTypeEntry); 369 | } 370 | } 371 | inputSource.setEnabled(false); 372 | inputType.setEnabled(true); 373 | outputTarget.setEnabled(false); 374 | outputType.setEnabled(false); 375 | convertButton.setText("Resize save file"); 376 | } else { 377 | inputType.removeAllItems(); 378 | inputSource.setEnabled(true); 379 | inputType.setEnabled(false); 380 | convertButton.setText("Convert save file"); 381 | } 382 | convertButtonCheck(); 383 | } 384 | }; 385 | } 386 | 387 | public static ActionListener inputSourceListener() { 388 | return new ActionListener() { 389 | public void actionPerformed(ActionEvent event) { 390 | Set validInputTypeOptions = new HashSet<>(); 391 | for(String key : conversionTable.keySet()) { 392 | String[] keyParts = key.split("-"); 393 | if(inputSource.getSelectedItem() != null && inputSource.getSelectedItem().equals(keyParts[0])) { 394 | validInputTypeOptions.add(keyParts[1]); 395 | } 396 | } 397 | List newInputTypeList = new ArrayList<>(Arrays.asList(inputTypeList)); 398 | newInputTypeList.retainAll(validInputTypeOptions); 399 | inputType.removeAllItems(); 400 | for(String newInputTypeEntry : newInputTypeList) { 401 | inputType.addItem(newInputTypeEntry); 402 | } 403 | convertButtonCheck(); 404 | if(inputSource.getSelectedItem() != null && !inputSource.getSelectedItem().equals("")) { 405 | inputType.setEnabled(true); 406 | outputTarget.setEnabled(true); 407 | outputType.setEnabled(true); 408 | } else { 409 | inputType.setEnabled(false); 410 | outputTarget.setEnabled(false); 411 | outputType.setEnabled(false); 412 | } 413 | } 414 | }; 415 | } 416 | 417 | public static ActionListener inputTypeListener() { 418 | return new ActionListener() { 419 | public void actionPerformed(ActionEvent event) { 420 | Set validOutputTargetOptions = new HashSet<>(); 421 | for(String key : conversionTable.keySet()) { 422 | String[] keyParts = key.split("-"); 423 | if(inputSource.getSelectedItem() != null && inputSource.getSelectedItem().equals(keyParts[0]) 424 | && inputType.getSelectedItem() != null && inputType.getSelectedItem().equals(keyParts[1])) { 425 | validOutputTargetOptions.add(keyParts[2]); 426 | } 427 | } 428 | List newOutputTargetList = new ArrayList<>(Arrays.asList(outputTargetList)); 429 | newOutputTargetList.retainAll(validOutputTargetOptions); 430 | outputTarget.removeAllItems(); 431 | for(String newOutputTargetEntry : newOutputTargetList) { 432 | outputTarget.addItem(newOutputTargetEntry); 433 | } 434 | } 435 | }; 436 | } 437 | 438 | public static ActionListener outputTargetListener() { 439 | return new ActionListener() { 440 | public void actionPerformed(ActionEvent event) { 441 | Set validOutputTypeOptions = new HashSet<>(); 442 | for(String key : conversionTable.keySet()) { 443 | String[] keyParts = key.split("-"); 444 | if(inputSource.getSelectedItem() != null && inputSource.getSelectedItem().equals(keyParts[0]) 445 | && inputType.getSelectedItem() != null && inputType.getSelectedItem().equals(keyParts[1]) 446 | && outputTarget.getSelectedItem() != null && outputTarget.getSelectedItem().equals(keyParts[2])) { 447 | validOutputTypeOptions.add(keyParts[3]); 448 | } 449 | } 450 | List newOutputTypeList = new ArrayList<>(Arrays.asList(outputTypeList)); 451 | newOutputTypeList.retainAll(validOutputTypeOptions); 452 | outputType.removeAllItems(); 453 | for(String newOutputTypeEntry : newOutputTypeList) { 454 | outputType.addItem(newOutputTypeEntry); 455 | } 456 | } 457 | }; 458 | } 459 | 460 | public static ActionListener convertButtonListener() { 461 | return new ActionListener() { 462 | public void actionPerformed(ActionEvent event) { 463 | byte[] inputSaveFile = readBytes(inputPathTextField.getText()); 464 | if (inputSaveFile != null){ 465 | if(standardSizeCheckbox.isSelected()) { 466 | StandardSizeTableEntry sizeInfo = standardSizeTable.get(inputType.getSelectedItem()); 467 | byte[] outputSaveFile = byteResize(inputSaveFile, sizeInfo.getStandardSize(), 0); 468 | String outputFileName = newFilename(inputPathTextField.getText(), sizeInfo.getExtension()); 469 | if(writeBytes(outputSaveFile, outputFileName)) { 470 | JOptionPane.showMessageDialog(null, "Created new N64 save file: " + outputFileName, "Save resize success!", JOptionPane.INFORMATION_MESSAGE); 471 | } 472 | } else { 473 | String conversionKey = inputSource.getSelectedItem()+"-"+inputType.getSelectedItem()+"-"+outputTarget.getSelectedItem()+"-"+outputType.getSelectedItem(); 474 | ConversionTableEntry conversionInfo = conversionTable.get(conversionKey); 475 | byte[] outputSaveFile = byteResize(inputSaveFile, conversionInfo.getInputSize(), 0); 476 | if(!SRM_LABEL.equals(inputType.getSelectedItem()) && conversionInfo.isByteswap()) { 477 | outputSaveFile = byteSwap(outputSaveFile); 478 | } 479 | if(!Objects.equals(inputType.getSelectedItem(), outputType.getSelectedItem())) { 480 | outputSaveFile = byteResize(outputSaveFile, conversionInfo.getOutputSize(), conversionInfo.getOffset()); 481 | } 482 | if(SRM_LABEL.equals(inputType.getSelectedItem()) && conversionInfo.isByteswap()) { 483 | outputSaveFile = byteSwap(outputSaveFile); 484 | } 485 | String outputFileName = newFilename(inputPathTextField.getText(), conversionInfo.getExtension()); 486 | if(writeBytes(outputSaveFile, outputFileName)) { 487 | JOptionPane.showMessageDialog(null, "Created new N64 save file: " + outputFileName, "Save conversion success!", JOptionPane.INFORMATION_MESSAGE); 488 | } 489 | } 490 | } 491 | } 492 | }; 493 | } 494 | 495 | private static void convertButtonCheck() { 496 | if((inputPathTextField.getText() != null && !inputPathTextField.getText().isBlank()) 497 | && ((inputSource.getSelectedItem() != null && !inputSource.getSelectedItem().equals("")) 498 | || standardSizeCheckbox.isSelected())) { 499 | convertButton.setEnabled(true); 500 | } else { 501 | convertButton.setEnabled(false); 502 | } 503 | } 504 | 505 | private static byte[] readBytes(String location) { 506 | byte[] fileBytes = null; 507 | try { 508 | File file = new File(location); 509 | fileBytes = Files.readAllBytes(file.toPath()); 510 | } catch (Exception e) { 511 | e.printStackTrace(); 512 | JOptionPane.showMessageDialog(null, "Could not read file: " + location, "File read error", JOptionPane.ERROR_MESSAGE); 513 | } 514 | return fileBytes; 515 | } 516 | 517 | private static boolean writeBytes(byte[] output, String location) { 518 | boolean success = false; 519 | if (output != null) { 520 | try { 521 | Path path = Paths.get(location); 522 | Files.write(path, output); 523 | success = true; 524 | } catch (Exception e) { 525 | e.printStackTrace(); 526 | JOptionPane.showMessageDialog(null, "Could not write file: " + location, "File write error", JOptionPane.ERROR_MESSAGE); 527 | } 528 | } 529 | return success; 530 | } 531 | 532 | private static String newFilename(String filename, String extension) { 533 | int lastIndexOfDot = filename.lastIndexOf("."); 534 | if(lastIndexOfDot == -1) { 535 | return filename + extension; 536 | } else { 537 | String currentExtention = filename.substring(lastIndexOfDot); 538 | return filename.substring(0, lastIndexOfDot) + (Objects.equals(extension, currentExtention) ? "#" : "") + extension; 539 | } 540 | } 541 | 542 | private static byte[] byteResize(byte[] input, int toSize, int offset) { 543 | byte[] output = new byte[toSize]; 544 | for(int i = 0; i < output.length; i++) { 545 | output[i] = (i >= offset && i - offset < input.length) ? input[i - offset] : 0; 546 | } 547 | return output; 548 | } 549 | 550 | private static byte[] byteSwap(byte[] input) { 551 | byte[] output = new byte[input.length]; 552 | for(int i = 0; i < input.length; i++) { 553 | if(i < input.length) { 554 | if(i % 4 == 0) { 555 | if(i + 3 < input.length) { 556 | output[i] = input[i + 3]; 557 | output[i + 1] = input[i + 2]; 558 | output[i + 2] = input[i + 1]; 559 | output[i + 3] = input[i]; 560 | } else if(i + 2 < input.length) { 561 | output[i] = input[i + 2]; 562 | output[i + 1] = input[i + 1]; 563 | output[i + 2] = input[i]; 564 | } else if(i + 1 < input.length) { 565 | output[i] = input[i + 1]; 566 | output[i + 1] = input[i]; 567 | } else { 568 | output[i] = input[i]; 569 | } 570 | } 571 | } 572 | } 573 | return output; 574 | } 575 | 576 | private static class StandardSizeTableEntry { 577 | private int standardSize; 578 | private String extension; 579 | 580 | public StandardSizeTableEntry(int standardSize, String extension) { 581 | this.standardSize = standardSize; 582 | this.extension = extension; 583 | } 584 | public int getStandardSize() { 585 | return standardSize; 586 | } 587 | public String getExtension() { 588 | return extension; 589 | } 590 | } 591 | 592 | private static class ConversionTableEntry { 593 | private int inputSize; 594 | private int outputSize; 595 | private int offset; 596 | private boolean byteswap; 597 | private String extension; 598 | 599 | public ConversionTableEntry(int inputSize, int outputSize, int offset, boolean byteswap, String extension) { 600 | this.inputSize = inputSize; 601 | this.outputSize = outputSize; 602 | this.offset = offset; 603 | this.byteswap = byteswap; 604 | this.extension = extension; 605 | } 606 | public int getInputSize() { 607 | return inputSize; 608 | } 609 | public int getOutputSize() { 610 | return outputSize; 611 | } 612 | public int getOffset() { 613 | return offset; 614 | } 615 | public boolean isByteswap() { 616 | return byteswap; 617 | } 618 | public String getExtension() { 619 | return extension; 620 | } 621 | } 622 | } 623 | 624 | 625 | --------------------------------------------------------------------------------