123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- /* This file is part of JJSave.
- *
- * JJSave is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * JJSave is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- #include <stdio.h>
- #include <stdint.h>
- #include <string.h>
- #include <getopt.h>
- #include <stdlib.h> // strtoul
- #include <errno.h>
- #include "version.h"
- #define DATA_MULTIPLIER 0x0100 // 256 (16 * 16) | (8 * 32) 00100000000
- #define OFFSET_MULTIPLIER 0x07D0 // 2000 (16 * 125) | (8 * 250) 11111010000
- enum Operation {
- OP_NONE = 0,
- OP_READ = 1,
- OP_WRITE = 2
- };
- enum Difficulty {
- DIFFICULTY_EASY = 0,
- DIFFICULTY_MEDIUM = 1,
- DIFFICULTY_HARD = 2,
- DIFFICULTY_TURBO = 3
- };
- struct CipherState {
- uint16_t key1, key2;
- };
- // This will generate a new keys state, what the code original looked like is
- // hard to guess but this is doing the same as the original, it is translated
- // from assembly. I failed to recognize any existing cipher, but I'm also no
- // expert. (Did Borland products have compiler optimisations in 1994?)
- //
- // However the basic idea is to multiply the first key with a constant, do
- // some mangling/math on high parts and low parts to calculate the next keys
- // (random bytes). During the calculations bytes will overflow, this is part
- // of the process.
- void gen_new_keys(struct CipherState *result, uint16_t key1, uint16_t key2) {
- uint16_t val;
- uint32_t buf;
- #ifdef JJSAVE_DEBUG
- printf(" --> result->key1: %04X\n", result->key1);
- printf(" --> result->key2: %04X\n", result->key2);
- #endif
- buf = key1 * 0x8405;
- #ifdef JJSAVE_DEBUG
- printf(" buf : %08X\n", buf);
- #endif
- val = (uint16_t)(buf >> 16) + (key1 * 2056) + (key2 * 5);
- result->key1 = ((val >> 8) + ((uint8_t)key2 + (key2 << 5)) * 4) * 256 | (uint8_t)val;
- result->key2 = (uint16_t)buf + 1;
- #ifdef JJSAVE_DEBUG
- printf(" <-- result->key1: %04X\n", result->key1);
- printf(" <-- result->key2: %04X\n", result->key2);
- #endif
- return;
- }
- uint16_t finalize_key(uint16_t key, uint16_t multiplier) {
- uint32_t buf = key * multiplier;
- return (uint16_t)(buf >> 16);
- }
- struct SaveGame {
- unsigned char name[16];
- uint16_t level;
- uint16_t planet;
- uint16_t difficulty;
- uint16_t unknown_1;
- uint16_t unknown_2;
- uint16_t unknown_3;
- uint16_t unknown_4;
- uint16_t unknown_5;
- uint16_t unknown_6;
- };
- void savegame_init(struct SaveGame *sg) {
- // init name with all spaces
- memset(sg->name, 0x20, 16);
- sg->level = 0;
- sg->planet = 0;
- sg->difficulty = 0;
- sg->unknown_1 = 0;
- sg->unknown_2 = 0;
- sg->unknown_3 = 0;
- sg->unknown_4 = 0;
- sg->unknown_5 = 0;
- sg->unknown_6 = 0;
- }
- void savegame_print(struct SaveGame *sg) {
- printf("Name : \"%.16s\"\n", sg->name);
- printf("Planet : %5d\n", sg->planet);
- printf("Level : %5d\n", sg->level);
- printf("Difficulty: %5d\n", sg->difficulty);
- printf("Unknown 1 : %5d 0x%04X\n", sg->unknown_1, sg->unknown_1);
- printf("Unknown 2 : %5d 0x%04X\n", sg->unknown_2, sg->unknown_2);
- printf("Unknown 3 : %5d 0x%04X\n", sg->unknown_3, sg->unknown_3);
- printf("Unknown 4 : %5d 0x%04X\n", sg->unknown_4, sg->unknown_4);
- printf("Unknown 5 : %5d 0x%04X\n", sg->unknown_5, sg->unknown_5);
- printf("Unknown 6 : %5d 0x%04X\n", sg->unknown_6, sg->unknown_6);
- }
- // Reads next value from file and returns the deciphered value, updating the
- // keys state while doing so.
- uint16_t read_value(struct CipherState *keys, FILE *fp, uint16_t *to) {
- struct CipherState result;
- uint16_t enc_value;
- uint16_t key;
- gen_new_keys(&result, keys->key2, keys->key1);
- keys->key1 = result.key1;
- keys->key2 = result.key2;
- key = finalize_key(result.key1, DATA_MULTIPLIER);
- if (fread(&enc_value, sizeof(enc_value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- *to = enc_value ^ key;
- return 0;
- }
- // Read from SAVE file and decipher save game into struct SaveGame.
- // Returns:
- // 0 ok success
- // 1 on file errors
- // 2 on magic error
- uint8_t savegame_read(struct SaveGame *sg, const char *savefile) {
- uint8_t magic;
- uint16_t data_offset;
- uint16_t key;
- FILE *fp;
- struct CipherState next_keys;
- fp = fopen(savefile, "rb");
- if (!fp) {
- return 1;
- }
- // Read save name
- if (fread(sg->name, sizeof(unsigned char) * 16, 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // Read and check magic
- if (fread(&magic, sizeof(magic), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- if (magic != 0x1A) {
- fclose(fp);
- return 2;
- }
- // Read keys
- if (fread(&next_keys.key2, sizeof(next_keys.key2), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- if (fread(&next_keys.key1, sizeof(next_keys.key1), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- #ifdef JJSAVE_DEBUG
- printf("Keys : 0x%04X 0x%04X\n", next_keys.key1, next_keys.key2);
- #endif
- gen_new_keys(&next_keys, next_keys.key2, next_keys.key1);
- key = finalize_key(next_keys.key1, OFFSET_MULTIPLIER);
- data_offset = key + 21;
- #ifdef JJSAVE_DEBUG
- printf("Data offset: %d = 0x%04X\n", data_offset, data_offset);
- #endif
- // Seek to data offset
- if (fseek(fp, (long)data_offset, SEEK_SET) != 0) {
- fclose(fp);
- return 1;
- }
- // Read keys
- if (fread(&next_keys.key2, sizeof(next_keys.key2), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- if (fread(&next_keys.key1, sizeof(next_keys.key1), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- #ifdef JJSAVE_DEBUG
- printf("Keys : 0x%04X 0x%04X\n", next_keys.key1, next_keys.key2);
- #endif
- // NOTE: read_value() does call fclose(fp) on error.
- if (read_value(&next_keys, fp, &sg->level) != 0) {
- return 1;
- }
- if (read_value(&next_keys, fp, &sg->planet) != 0) {
- return 1;
- }
- if (read_value(&next_keys, fp, &sg->difficulty) != 0) {
- return 1;
- }
- if (read_value(&next_keys, fp, &sg->unknown_1) != 0) {
- return 1;
- }
- if (read_value(&next_keys, fp, &sg->unknown_2) != 0) {
- return 1;
- }
- if (read_value(&next_keys, fp, &sg->unknown_3) != 0) {
- return 1;
- }
- if (read_value(&next_keys, fp, &sg->unknown_4) != 0) {
- return 1;
- }
- if (read_value(&next_keys, fp, &sg->unknown_5) != 0) {
- return 1;
- }
- if (read_value(&next_keys, fp, &sg->unknown_6) != 0) {
- return 1;
- }
- fclose(fp);
- return 0;
- }
- // Used for value generation based of current keys state.
- uint16_t read_next_value(struct CipherState *next_keys) {
- gen_new_keys(next_keys, next_keys->key2, next_keys->key1);
- return finalize_key(next_keys->key1, DATA_MULTIPLIER);
- }
- // Write SAVE file
- uint8_t savegame_write(struct SaveGame *sg, uint32_t systime, const char *savefile) {
- FILE *fp;
- uint16_t value;
- uint16_t key;
- struct CipherState next_keys;
- next_keys.key2 = (uint16_t)(systime >> 16);
- next_keys.key1 = (uint16_t)systime;
- fp = fopen(savefile, "wb");
- if (!fp) {
- return 1;
- }
- // write savegame name
- if (fwrite(sg->name, 16, 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // write magic
- if (putc(0x1A, fp) != 0x1A) {
- fclose(fp);
- return 1;
- }
- // write system time (the secret start keys)
- if (fwrite(&next_keys.key2, sizeof(next_keys.key2), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- if (fwrite(&next_keys.key1, sizeof(next_keys.key1), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // generate the number of bytes to skip (garbage bytes)
- gen_new_keys(&next_keys, next_keys.key2, next_keys.key1);
- key = finalize_key(next_keys.key1, OFFSET_MULTIPLIER);
- #ifdef JJSAVE_DEBUG
- printf("Garbage bytes: %d %04X\n", key, key);
- #endif
- uint16_t nogarbage = key;
- // generate/write garbage bytes
- for (uint16_t i=0; i<nogarbage; ++i) {
- gen_new_keys(&next_keys, next_keys.key2, next_keys.key1);
- key = finalize_key(next_keys.key1, DATA_MULTIPLIER);
- value = (uint8_t)(key >> 8);
- if (fwrite(&value, sizeof(uint8_t), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- }
- #ifdef JJSAVE_DEBUG
- printf("----------------------------\n");
- printf("New keys: %04X %04X\n", next_keys.key2, next_keys.key1);
- #endif
- // write keys (so on read we can skip the garbage and start of with the
- // values we left with)
- if (fwrite(&next_keys.key2, sizeof(next_keys.key2), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- if (fwrite(&next_keys.key1, sizeof(next_keys.key1), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // level
- value = read_next_value(&next_keys)^ sg->level;
- if (fwrite(&value, sizeof(value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // planet
- value = read_next_value(&next_keys)^ sg->planet;
- if (fwrite(&value, sizeof(value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // difficulty
- value = read_next_value(&next_keys)^ sg->difficulty;
- if (fwrite(&value, sizeof(value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // unknown 1
- value = read_next_value(&next_keys)^ sg->unknown_1;
- if (fwrite(&value, sizeof(value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // unknown 2
- value = read_next_value(&next_keys)^ sg->unknown_2;
- if (fwrite(&value, sizeof(value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // unknown 3
- value = read_next_value(&next_keys)^ sg->unknown_3;
- if (fwrite(&value, sizeof(value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // unknown 4
- value = read_next_value(&next_keys)^ sg->unknown_4;
- if (fwrite(&value, sizeof(value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // unknown 5
- value = read_next_value(&next_keys)^ sg->unknown_5;
- if (fwrite(&value, sizeof(value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- // unknown 6
- value = read_next_value(&next_keys)^ sg->unknown_6;
- if (fwrite(&value, sizeof(value), 1, fp) != 1) {
- fclose(fp);
- return 1;
- }
- fclose(fp);
- return 0;
- }
- void print_help(void) {
- printf("==============================================================\n");
- printf(" Welcome to JJ Save version %s\n", JJSAVE_VERSION_STR);
- printf("==============================================================\n\n");
- printf(" Usage: jjsave [OPERATION]\n\n");
- printf(" OPERATIONS\n\n");
- printf(" -r --read INPUT_FILE Read SAVE file. (default behaviour)\n\n");
- printf(" -w --write Create new SAVE file.\n");
- printf(" NAME Save game name, max 16 characters.\n");
- printf(" PLANET Planet ID.\n");
- printf(" LEVEL Level number.\n");
- printf(" DIFFICULTY Difficulty (0-3)\n");
- printf(" OUTPUT_FILE File to write the output to.\n\n");
- printf(" -v --version Print version and exit.\n");
- printf(" -h --help Display this HELP.\n");
- printf("\n");
- }
- int main(int argc, char *argv[]) {
- enum Operation operation = OP_READ;
- struct SaveGame sg;
- savegame_init(&sg);
- // https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Options.html
- // https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Option-Example.html
- struct option long_options[] = {
- // OPERATIONS
- {"write" , no_argument , NULL, 'w'},
- {"version", no_argument , NULL, 'v'},
- {"help" , no_argument , NULL, 'h'},
- {NULL , 0 , NULL, 0}
- };
- int option_index = 0;
- for (;;) {
- int opt = getopt_long(argc, argv, "rwhv",
- long_options, &option_index);
- if (opt == -1) {
- break;
- }
- switch (opt) {
- // OPERATIONS
- /*case 'r':
- operation = OP_READ;
- break;*/
- case 'w':
- operation = OP_WRITE;
- break;
- case 'v':
- printf("JJ Save v%s\n", JJSAVE_VERSION_STR);
- return 0;
- case 'h':
- print_help();
- return 0;
- case '?':
- // invalid option
- printf("Invalid operation or option\n");
- return 1;
- default:
- printf("default\n");
- break;
- }
- }
- // Read
- if (operation == OP_READ) {
- if ((argc - 1 ) < optind) {
- fprintf(stderr, "Please supply an input file\n");
- return 1;
- }
- if ((argc - 1 ) > optind) {
- fprintf(stderr, "Please supply only one input file\n");
- return 1;
- }
- // 0 ok success
- // 1 on file errors
- // 2 on magic error
- uint8_t result = savegame_read(&sg, (const char *)argv[optind]);
- if (result == 1) {
- perror("Error reading file");
- return 1;
- }
- else
- if (result == 2) {
- fprintf(stderr, "Not a valid SAVE file (magic is not 0x1A)\n");
- return 2;
- }
- savegame_print(&sg);
- }
- // Write
- else
- if (operation == OP_WRITE) {
- if ((argc - 5 ) < optind) {
- printf("Please supply NAME PLANET LEVEL DIFFICULTY OUTPUT_FILE\n");
- return 1;
- }
- if ((argc - 5 ) > optind) {
- printf("Invalid arguments\n");
- return 1;
- }
- // name
- size_t namelen = strlen(argv[optind]);
- if (namelen > 16) {
- printf("Given save name is to long, it may one by 16 characters long.\n");
- return 1;
- }
- memcpy(sg.name, argv[optind], namelen);
- char *str;
- unsigned long val;
- // planet
- val = strtoul(argv[optind + 1], &str, 10);
- if (val > 0xffff) {
- printf("Invalid planet: number to large\n");
- return 1;
- }
- if (strlen(str)) {
- printf("Invalid planet: string not allowed\n");
- return 1;
- }
- sg.planet = (uint16_t)val;
- // level
- val = strtoul(argv[optind + 2], &str, 10);
- if (val > 0xffff) {
- printf("Invalid level: number to large\n");
- return 1;
- }
- if (strlen(str)) {
- printf("Invalid level: string not allowed\n");
- return 1;
- }
- sg.level = (uint16_t)val;
- // level
- val = strtoul(argv[optind + 3], &str, 10);
- if (val > 3) {
- printf("Invalid difficulty: number to large\n");
- return 1;
- }
- if (strlen(str)) {
- printf("Invalid difficulty: string not allowed\n");
- return 1;
- }
- sg.difficulty = (uint16_t)val;
- savegame_print(&sg);
- if (savegame_write(&sg, 0x00000000, argv[optind + 4]) != 0) {
- fprintf(stderr, "Failed to write save game to \"%s\"\n", argv[optind + 4]);
- return 1;
- }
- printf("Wrote to %s\n", argv[optind + 4]);
- }
- return 0;
- }
|