/* * OpenTyrian: A modern cross-platform port of Tyrian * Copyright (C) 2007-2009 The OpenTyrian Development Team * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include "episodes.h" #include "file.h" #include "joystick.h" #include "loudness.h" #include "mtrand.h" #include "nortsong.h" #include "opentyr.h" #include "player.h" #include "varz.h" #include "vga256d.h" #include "video.h" #include "video_scale.h" #include #include /* Configuration Load/Save handler */ const JE_byte cryptKey[10] = /* [1..10] */ { 15, 50, 89, 240, 147, 34, 86, 9, 32, 208 }; const JE_KeySettingType defaultKeySettings = { SDLK_UP, SDLK_DOWN, SDLK_LEFT, SDLK_RIGHT, SDLK_SPACE, SDLK_RETURN, SDLK_LCTRL, SDLK_LALT /* 72, 80, 75, 77, 57, 28, 29, 56*/ }; const char defaultHighScoreNames[34][23] = /* [1..34] of string [22] */ {/*1P*/ /*TYR*/ "The Prime Chair", /*13*/ "Transon Lohk", "Javi Onukala", "Mantori", "Nortaneous", "Dougan", "Reid", "General Zinglon", "Late Gyges Phildren", "Vykromod", "Beppo", "Borogar", "ShipMaster Carlos", /*OTHER*/ "Jill", /*5*/ "Darcy", "Jake Stone", "Malvineous Havershim", "Marta Louise Velasquez", /*JAZZ*/ "Jazz Jackrabbit", /*3*/ "Eva Earlong", "Devan Shell", /*OMF*/ "Crystal Devroe", /*11*/ "Steffan Tommas", "Milano Angston", "Christian", "Shirro", "Jean-Paul", "Ibrahim Hothe", "Angel", "Cossette Akira", "Raven", "Hans Kreissack", /*DARE*/ "Tyler", /*2*/ "Rennis the Rat Guard" }; const char defaultTeamNames[22][25] = /* [1..22] of string [24] */ { "Jackrabbits", "Team Tyrian", "The Elam Brothers", "Dare to Dream Team", "Pinball Freaks", "Extreme Pinball Freaks", "Team Vykromod", "Epic All-Stars", "Hans Keissack's WARriors", "Team Overkill", "Pied Pipers", "Gencore Growlers", "Microsol Masters", "Beta Warriors", "Team Loco", "The Shellians", "Jungle Jills", "Murderous Malvineous", "The Traffic Department", "Clan Mikal", "Clan Patrok", "Carlos' Crawlers" }; const JE_EditorItemAvailType initialItemAvail = { 1,1,1,0,0,1,1,0,1,1,1,1,1,0,1,0,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, /* Front/Rear Weapons 1-38 */ 0,0,0,0,0,0,0,0,0,0,1, /* Fill */ 1,0,0,0,0,1,0,0,0,1,1,0,1,0,0,0,0,0, /* Sidekicks 51-68 */ 0,0,0,0,0,0,0,0,0,0,0, /* Fill */ 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* Special Weapons 81-93 */ 0,0,0,0,0 /* Fill */ }; /* Last 2 bytes = Word * * Max Value = 1680 * X div 60 = Armor (1-28) * X div 168 = Shield (1-12) * X div 280 = Engine (1-06) */ JE_boolean smoothies[9] = /* [1..9] */ { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; JE_byte starShowVGASpecialCode; /* CubeData */ JE_word lastCubeMax, cubeMax; JE_word cubeList[4]; /* [1..4] */ /* High-Score Stuff */ JE_boolean gameHasRepeated; // can only get highscore on first play-through /* Difficulty */ JE_shortint difficultyLevel, oldDifficultyLevel, initialDifficulty; // can only get highscore on initial episode /* Player Stuff */ uint power, lastPower, powerAdd; JE_byte shieldWait, shieldT; JE_byte shotRepeat[11], shotMultiPos[11]; JE_boolean portConfigChange, portConfigDone; /* Level Data */ char lastLevelName[11], levelName[11]; /* string [10] */ JE_byte mainLevel, nextLevel, saveLevel; /*Current Level #*/ /* Keyboard Junk */ JE_KeySettingType keySettings; /* Configuration */ JE_shortint levelFilter, levelFilterNew, levelBrightness, levelBrightnessChg; JE_boolean filtrationAvail, filterActive, filterFade, filterFadeStart; JE_boolean gameJustLoaded; JE_boolean galagaMode; JE_boolean extraGame; JE_boolean twoPlayerMode, twoPlayerLinked, onePlayerAction, superTyrian; JE_boolean trentWin = false; JE_byte superArcadeMode; JE_byte superArcadePowerUp; JE_real linkGunDirec; JE_byte inputDevice[2] = { 1, 2 }; // 0:any 1:keyboard 2:mouse 3+:joystick JE_byte secretHint; JE_byte background3over; JE_byte background2over; JE_byte gammaCorrection; JE_boolean superPause = false; JE_boolean explosionTransparent, youAreCheating, displayScore, background2, smoothScroll, wild, superWild, starActive, topEnemyOver, skyEnemyOverAll, background2notTransparent; JE_byte soundEffects; // dummy value for config JE_byte versionNum; /* SW 1.0 and SW/Reg 1.1 = 0 or 1 * EA 1.2 = 2 */ JE_byte fastPlay; JE_boolean pentiumMode; /* Savegame files */ JE_byte gameSpeed; JE_byte processorType; /* 1=386 2=486 3=Pentium Hyper */ JE_SaveFilesType saveFiles; /*array[1..saveLevelnum] of savefiletype;*/ JE_SaveGameTemp saveTemp; JE_word editorLevel; /*Initial value 800*/ TouchscreenControlMode_t touchscreenControlMode = TOUCHSCREEN_CONTROL_FINGER; cJSON *load_json( const char *filename ) { FILE *f = dir_fopen_warn(get_user_directory(), filename, "rb"); if (f == NULL) return NULL; size_t buffer_len = ftell_eof(f); char *buffer = (char *)malloc(buffer_len + 1); fread(buffer, 1, buffer_len, f); buffer[buffer_len] = '\0'; fclose(f); cJSON *root = cJSON_Parse(buffer); free(buffer); return root; } void save_json( cJSON *root, const char *filename ) { FILE *f = dir_fopen_warn(get_user_directory(), filename, "w+"); if (f == NULL) return; char *buffer = cJSON_Print(root); if (buffer != NULL) { fputs(buffer, f); free(buffer); } fclose(f); } bool load_opentyrian_config( void ) { // defaults fullscreen_enabled = false; set_scaler_by_name("Scale2x"); cJSON *root = load_json("opentyrian.conf"); if (root == NULL) return false; cJSON *section = cJSON_GetObjectItem(root, "video"); if (section != NULL) { cJSON *setting; if ((setting = cJSON_GetObjectItem(section, "fullscreen"))) fullscreen_enabled = (setting->type == cJSON_True); if ((setting = cJSON_GetObjectItem(section, "scaler"))) set_scaler_by_name(setting->valuestring); if ((setting = cJSON_GetObjectItem(section, "touchscreenmode"))) touchscreenControlMode = setting->valueint; } cJSON_Delete(root); return true; } bool save_opentyrian_config( void ) { cJSON *root = load_json("opentyrian.conf"); if (root == NULL) root = cJSON_CreateObject(); cJSON *section; section = cJSON_CreateOrGetObjectItem(root, "video"); cJSON_ForceType(section, cJSON_Object); { cJSON *setting; setting = cJSON_CreateOrGetObjectItem(section, "fullscreen"); cJSON_SetBoolean(setting, fullscreen_enabled); setting = cJSON_CreateOrGetObjectItem(section, "scaler"); cJSON_SetString(setting, scalers[scaler].name); setting = cJSON_CreateOrGetObjectItem(section, "touchscreenmode"); cJSON_SetNumber(setting, touchscreenControlMode); } save_json(root, "opentyrian.conf"); cJSON_Delete(root); return true; } static void playeritems_to_pitems( JE_PItemsType pItems, PlayerItems *items, JE_byte initial_episode_num ) { pItems[0] = items->weapon[FRONT_WEAPON].id; pItems[1] = items->weapon[REAR_WEAPON].id; pItems[2] = items->super_arcade_mode; pItems[3] = items->sidekick[LEFT_SIDEKICK]; pItems[4] = items->sidekick[RIGHT_SIDEKICK]; pItems[5] = items->generator; pItems[6] = items->sidekick_level; pItems[7] = items->sidekick_series; pItems[8] = initial_episode_num; pItems[9] = items->shield; pItems[10] = items->special; pItems[11] = items->ship; } static void pitems_to_playeritems( PlayerItems *items, JE_PItemsType pItems, JE_byte *initial_episode_num ) { items->weapon[FRONT_WEAPON].id = pItems[0]; items->weapon[REAR_WEAPON].id = pItems[1]; items->super_arcade_mode = pItems[2]; items->sidekick[LEFT_SIDEKICK] = pItems[3]; items->sidekick[RIGHT_SIDEKICK] = pItems[4]; items->generator = pItems[5]; items->sidekick_level = pItems[6]; items->sidekick_series = pItems[7]; if (initial_episode_num != NULL) *initial_episode_num = pItems[8]; items->shield = pItems[9]; items->special = pItems[10]; items->ship = pItems[11]; } void JE_saveGame( JE_byte slot, const char *name ) { saveFiles[slot-1].initialDifficulty = initialDifficulty; saveFiles[slot-1].gameHasRepeated = gameHasRepeated; saveFiles[slot-1].level = saveLevel; if (superTyrian) player[0].items.super_arcade_mode = SA_SUPERTYRIAN; else if (superArcadeMode == SA_NONE && onePlayerAction) player[0].items.super_arcade_mode = SA_ARCADE; else player[0].items.super_arcade_mode = superArcadeMode; playeritems_to_pitems(saveFiles[slot-1].items, &player[0].items, initial_episode_num); if (twoPlayerMode) playeritems_to_pitems(saveFiles[slot-1].lastItems, &player[1].items, 0); else playeritems_to_pitems(saveFiles[slot-1].lastItems, &player[0].last_items, 0); saveFiles[slot-1].score = player[0].cash; saveFiles[slot-1].score2 = player[1].cash; memcpy(&saveFiles[slot-1].levelName, &lastLevelName, sizeof(lastLevelName)); saveFiles[slot-1].cubes = lastCubeMax; if (strcmp(lastLevelName, "Completed") == 0) { temp = episodeNum - 1; if (temp < 1) { temp = EPISODE_AVAILABLE; /* JE: {Episodemax is 4 for completion purposes} */ } saveFiles[slot-1].episode = temp; } else { saveFiles[slot-1].episode = episodeNum; } saveFiles[slot-1].difficulty = difficultyLevel; saveFiles[slot-1].secretHint = secretHint; saveFiles[slot-1].input1 = inputDevice[0]; saveFiles[slot-1].input2 = inputDevice[1]; strcpy(saveFiles[slot-1].name, name); for (uint port = 0; port < 2; ++port) { // if two-player, use first player's front and second player's rear weapon saveFiles[slot-1].power[port] = player[twoPlayerMode ? port : 0].items.weapon[port].power; } JE_saveConfiguration(); } void JE_loadGame( JE_byte slot ) { superTyrian = false; onePlayerAction = false; twoPlayerMode = false; extraGame = false; galagaMode = false; initialDifficulty = saveFiles[slot-1].initialDifficulty; gameHasRepeated = saveFiles[slot-1].gameHasRepeated; twoPlayerMode = (slot-1) > 10; difficultyLevel = saveFiles[slot-1].difficulty; pitems_to_playeritems(&player[0].items, saveFiles[slot-1].items, &initial_episode_num); superArcadeMode = player[0].items.super_arcade_mode; if (superArcadeMode == SA_SUPERTYRIAN) superTyrian = true; if (superArcadeMode != SA_NONE) onePlayerAction = true; if (superArcadeMode > SA_NORTSHIPZ) superArcadeMode = SA_NONE; if (twoPlayerMode) { onePlayerAction = false; pitems_to_playeritems(&player[1].items, saveFiles[slot-1].lastItems, NULL); } else { pitems_to_playeritems(&player[0].last_items, saveFiles[slot-1].lastItems, NULL); } /* Compatibility with old version */ if (player[1].items.sidekick_level < 101) { player[1].items.sidekick_level = 101; player[1].items.sidekick_series = player[1].items.sidekick[LEFT_SIDEKICK]; } player[0].cash = saveFiles[slot-1].score; player[1].cash = saveFiles[slot-1].score2; mainLevel = saveFiles[slot-1].level; cubeMax = saveFiles[slot-1].cubes; lastCubeMax = cubeMax; secretHint = saveFiles[slot-1].secretHint; inputDevice[0] = saveFiles[slot-1].input1; inputDevice[1] = saveFiles[slot-1].input2; for (uint port = 0; port < 2; ++port) { // if two-player, use first player's front and second player's rear weapon player[twoPlayerMode ? port : 0].items.weapon[port].power = saveFiles[slot-1].power[port]; } int episode = saveFiles[slot-1].episode; memcpy(&levelName, &saveFiles[slot-1].levelName, sizeof(levelName)); if (strcmp(levelName, "Completed") == 0) { if (episode == EPISODE_AVAILABLE) { episode = 1; } else if (episode < EPISODE_AVAILABLE) { episode++; } /* Increment episode. Episode EPISODE_AVAILABLE goes to 1. */ } JE_initEpisode(episode); saveLevel = mainLevel; memcpy(&lastLevelName, &levelName, sizeof(levelName)); } void JE_initProcessorType( void ) { /* SYN: Originally this proc looked at your hardware specs and chose appropriate options. We don't care, so I'll just set decent defaults here. */ wild = false; superWild = false; smoothScroll = true; explosionTransparent = true; filtrationAvail = false; background2 = true; displayScore = true; switch (processorType) { case 1: /* 386 */ background2 = false; displayScore = false; explosionTransparent = false; break; case 2: /* 486 - Default */ break; case 3: /* High Detail */ smoothScroll = false; break; case 4: /* Pentium */ wild = true; filtrationAvail = true; break; case 5: /* Nonstandard VGA */ smoothScroll = false; break; case 6: /* SuperWild */ wild = true; superWild = true; filtrationAvail = true; break; } switch (gameSpeed) { case 1: /* Slug Mode */ fastPlay = 3; break; case 2: /* Slower */ fastPlay = 4; break; case 3: /* Slow */ fastPlay = 5; break; case 4: /* Normal */ fastPlay = 0; break; case 5: /* Pentium Hyper */ fastPlay = 1; break; } } void JE_setNewGameSpeed( void ) { pentiumMode = false; switch (fastPlay) { case 0: speed = 0x4300; smoothScroll = true; frameCountMax = 2; break; case 1: speed = 0x3000; smoothScroll = true; frameCountMax = 2; break; case 2: speed = 0x2000; smoothScroll = false; frameCountMax = 2; break; case 3: speed = 0x5300; smoothScroll = true; frameCountMax = 4; break; case 4: speed = 0x4300; smoothScroll = true; frameCountMax = 3; break; case 5: speed = 0x4300; smoothScroll = true; frameCountMax = 2; pentiumMode = true; break; } frameCount = frameCountMax; JE_resetTimerInt(); JE_setTimerInt(); } void JE_encryptSaveTemp( void ) { JE_SaveGameTemp s3; JE_word x; JE_byte y; memcpy(&s3, &saveTemp, sizeof(s3)); y = 0; for (x = 0; x < SAVE_FILE_SIZE; x++) { y += s3[x]; } saveTemp[SAVE_FILE_SIZE] = y; y = 0; for (x = 0; x < SAVE_FILE_SIZE; x++) { y -= s3[x]; } saveTemp[SAVE_FILE_SIZE+1] = y; y = 1; for (x = 0; x < SAVE_FILE_SIZE; x++) { y = (y * s3[x]) + 1; } saveTemp[SAVE_FILE_SIZE+2] = y; y = 0; for (x = 0; x < SAVE_FILE_SIZE; x++) { y = y ^ s3[x]; } saveTemp[SAVE_FILE_SIZE+3] = y; for (x = 0; x < SAVE_FILE_SIZE; x++) { saveTemp[x] = saveTemp[x] ^ cryptKey[(x+1) % 10]; if (x > 0) { saveTemp[x] = saveTemp[x] ^ saveTemp[x - 1]; } } } void JE_decryptSaveTemp( void ) { JE_boolean correct = true; JE_SaveGameTemp s2; int x; JE_byte y; /* Decrypt save game file */ for (x = (SAVE_FILE_SIZE - 1); x >= 0; x--) { s2[x] = (JE_byte)saveTemp[x] ^ (JE_byte)(cryptKey[(x+1) % 10]); if (x > 0) { s2[x] ^= (JE_byte)saveTemp[x - 1]; } } /* for (x = 0; x < SAVE_FILE_SIZE; x++) printf("%c", s2[x]); */ /* Check save file for correctitude */ y = 0; for (x = 0; x < SAVE_FILE_SIZE; x++) { y += s2[x]; } if (saveTemp[SAVE_FILE_SIZE] != y) { correct = false; printf("Failed additive checksum: %d vs %d\n", saveTemp[SAVE_FILE_SIZE], y); } y = 0; for (x = 0; x < SAVE_FILE_SIZE; x++) { y -= s2[x]; } if (saveTemp[SAVE_FILE_SIZE+1] != y) { correct = false; printf("Failed subtractive checksum: %d vs %d\n", saveTemp[SAVE_FILE_SIZE+1], y); } y = 1; for (x = 0; x < SAVE_FILE_SIZE; x++) { y = (y * s2[x]) + 1; } if (saveTemp[SAVE_FILE_SIZE+2] != y) { correct = false; printf("Failed multiplicative checksum: %d vs %d\n", saveTemp[SAVE_FILE_SIZE+2], y); } y = 0; for (x = 0; x < SAVE_FILE_SIZE; x++) { y = y ^ s2[x]; } if (saveTemp[SAVE_FILE_SIZE+3] != y) { correct = false; printf("Failed XOR'd checksum: %d vs %d\n", saveTemp[SAVE_FILE_SIZE+3], y); } /* Barf and die if save file doesn't validate */ if (!correct) { fprintf(stderr, "Error reading save file!\n"); exit(255); } /* Keep decrypted version plz */ memcpy(&saveTemp, &s2, sizeof(s2)); } #ifndef TARGET_MACOSX const char *get_user_directory( void ) { static char user_dir[500] = ""; if (strlen(user_dir) == 0) { #ifdef TARGET_UNIX if (getenv("HOME")) snprintf(user_dir, sizeof(user_dir), "%s/.opentyrian", getenv("HOME")); #else strcpy(user_dir, "."); #endif // TARGET_UNIX } return user_dir; } #endif // TARGET_MACOSX // for compatibility Uint8 joyButtonAssign[4] = {1, 4, 5, 5}; Uint8 inputDevice_ = 0, jConfigure = 0, midiPort = 1; void JE_loadConfiguration( void ) { FILE *fi; int z; JE_byte *p; int y; fi = dir_fopen_warn(get_user_directory(), "tyrian.cfg", "rb"); if (fi && ftell_eof(fi) == 20 + sizeof(keySettings)) { /* SYN: I've hardcoded the sizes here because the .CFG file format is fixed anyways, so it's not like they'll change. */ background2 = 0; efread(&background2, 1, 1, fi); efread(&gameSpeed, 1, 1, fi); efread(&inputDevice_, 1, 1, fi); efread(&jConfigure, 1, 1, fi); efread(&versionNum, 1, 1, fi); efread(&processorType, 1, 1, fi); efread(&midiPort, 1, 1, fi); efread(&soundEffects, 1, 1, fi); efread(&gammaCorrection, 1, 1, fi); efread(&difficultyLevel, 1, 1, fi); efread(joyButtonAssign, 1, 4, fi); efread(&tyrMusicVolume, 2, 1, fi); efread(&fxVolume, 2, 1, fi); efread(inputDevice, 1, 2, fi); efread(keySettings, sizeof(*keySettings), COUNTOF(keySettings), fi); fclose(fi); } else { printf("\nInvalid or missing TYRIAN.CFG! Continuing using defaults.\n\n"); soundEffects = 1; memcpy(&keySettings, &defaultKeySettings, sizeof(keySettings)); background2 = true; tyrMusicVolume = fxVolume = 128; gammaCorrection = 0; processorType = 3; gameSpeed = 4; } load_opentyrian_config(); if (tyrMusicVolume > 255) tyrMusicVolume = 255; if (fxVolume > 255) fxVolume = 255; JE_calcFXVol(); set_volume(tyrMusicVolume, fxVolume); fi = dir_fopen_warn(get_user_directory(), "tyrian.sav", "rb"); if (fi) { fseek(fi, 0, SEEK_SET); efread(saveTemp, 1, sizeof(saveTemp), fi); JE_decryptSaveTemp(); /* SYN: The original mostly blasted the save file into raw memory. However, our lives are not so easy, because the C struct is necessarily a different size. So instead we have to loop through each record and load fields manually. *emo tear* :'( */ p = saveTemp; for (z = 0; z < SAVE_FILES_NUM; z++) { memcpy(&saveFiles[z].encode, p, sizeof(JE_word)); p += 2; saveFiles[z].encode = SDL_SwapLE16(saveFiles[z].encode); memcpy(&saveFiles[z].level, p, sizeof(JE_word)); p += 2; saveFiles[z].level = SDL_SwapLE16(saveFiles[z].level); memcpy(&saveFiles[z].items, p, sizeof(JE_PItemsType)); p += sizeof(JE_PItemsType); memcpy(&saveFiles[z].score, p, sizeof(JE_longint)); p += 4; saveFiles[z].score = SDL_SwapLE32(saveFiles[z].score); memcpy(&saveFiles[z].score2, p, sizeof(JE_longint)); p += 4; saveFiles[z].score2 = SDL_SwapLE32(saveFiles[z].score2); /* SYN: Pascal strings are prefixed by a byte holding the length! */ memset(&saveFiles[z].levelName, 0, sizeof(saveFiles[z].levelName)); memcpy(&saveFiles[z].levelName, &p[1], *p); p += 10; /* This was a BYTE array, not a STRING, in the original. Go fig. */ memcpy(&saveFiles[z].name, p, 14); p += 14; memcpy(&saveFiles[z].cubes, p, sizeof(JE_byte)); p++; memcpy(&saveFiles[z].power, p, sizeof(JE_byte) * 2); p += 2; memcpy(&saveFiles[z].episode, p, sizeof(JE_byte)); p++; memcpy(&saveFiles[z].lastItems, p, sizeof(JE_PItemsType)); p += sizeof(JE_PItemsType); memcpy(&saveFiles[z].difficulty, p, sizeof(JE_byte)); p++; memcpy(&saveFiles[z].secretHint, p, sizeof(JE_byte)); p++; memcpy(&saveFiles[z].input1, p, sizeof(JE_byte)); p++; memcpy(&saveFiles[z].input2, p, sizeof(JE_byte)); p++; /* booleans were 1 byte in pascal -- working around it */ Uint8 temp; memcpy(&temp, p, 1); p++; saveFiles[z].gameHasRepeated = temp != 0; memcpy(&saveFiles[z].initialDifficulty, p, sizeof(JE_byte)); p++; memcpy(&saveFiles[z].highScore1, p, sizeof(JE_longint)); p += 4; saveFiles[z].highScore1 = SDL_SwapLE32(saveFiles[z].highScore1); memcpy(&saveFiles[z].highScore2, p, sizeof(JE_longint)); p += 4; saveFiles[z].highScore2 = SDL_SwapLE32(saveFiles[z].highScore2); memset(&saveFiles[z].highScoreName, 0, sizeof(saveFiles[z].highScoreName)); memcpy(&saveFiles[z].highScoreName, &p[1], *p); p += 30; memcpy(&saveFiles[z].highScoreDiff, p, sizeof(JE_byte)); p++; } /* SYN: This is truncating to bytes. I have no idea what this is doing or why. */ /* TODO: Figure out what this is about and make sure it isn't broked. */ editorLevel = (saveTemp[SIZEOF_SAVEGAMETEMP - 5] << 8) | saveTemp[SIZEOF_SAVEGAMETEMP - 6]; fclose(fi); } else { /* We didn't have a save file! Let's make up random stuff! */ editorLevel = 800; for (z = 0; z < 100; z++) { saveTemp[SAVE_FILES_SIZE + z] = initialItemAvail[z]; } for (z = 0; z < SAVE_FILES_NUM; z++) { saveFiles[z].level = 0; for (y = 0; y < 14; y++) { saveFiles[z].name[y] = ' '; } saveFiles[z].name[14] = 0; saveFiles[z].highScore1 = ((mt_rand() % 20) + 1) * 1000; if (z % 6 > 2) { saveFiles[z].highScore2 = ((mt_rand() % 20) + 1) * 1000; strcpy(saveFiles[z].highScoreName, defaultTeamNames[mt_rand() % 22]); } else { strcpy(saveFiles[z].highScoreName, defaultHighScoreNames[mt_rand() % 34]); } } } JE_initProcessorType(); } void JE_saveConfiguration( void ) { #ifdef TARGET_UNIX if (getenv("HOME")) { char dir[1000]; snprintf(dir, sizeof(dir), "%s/.opentyrian", getenv("HOME")); mkdir(dir, 0755); } #endif /* HOME */ FILE *f; JE_byte *p; int z; p = saveTemp; for (z = 0; z < SAVE_FILES_NUM; z++) { JE_SaveFileType tempSaveFile; memcpy(&tempSaveFile, &saveFiles[z], sizeof(tempSaveFile)); tempSaveFile.encode = SDL_SwapLE16(tempSaveFile.encode); memcpy(p, &tempSaveFile.encode, sizeof(JE_word)); p += 2; tempSaveFile.level = SDL_SwapLE16(tempSaveFile.level); memcpy(p, &tempSaveFile.level, sizeof(JE_word)); p += 2; memcpy(p, &tempSaveFile.items, sizeof(JE_PItemsType)); p += sizeof(JE_PItemsType); tempSaveFile.score = SDL_SwapLE32(tempSaveFile.score); memcpy(p, &tempSaveFile.score, sizeof(JE_longint)); p += 4; tempSaveFile.score2 = SDL_SwapLE32(tempSaveFile.score2); memcpy(p, &tempSaveFile.score2, sizeof(JE_longint)); p += 4; /* SYN: Pascal strings are prefixed by a byte holding the length! */ memset(p, 0, sizeof(tempSaveFile.levelName)); *p = strlen(tempSaveFile.levelName); memcpy(&p[1], &tempSaveFile.levelName, *p); p += 10; /* This was a BYTE array, not a STRING, in the original. Go fig. */ memcpy(p, &tempSaveFile.name, 14); p += 14; memcpy(p, &tempSaveFile.cubes, sizeof(JE_byte)); p++; memcpy(p, &tempSaveFile.power, sizeof(JE_byte) * 2); p += 2; memcpy(p, &tempSaveFile.episode, sizeof(JE_byte)); p++; memcpy(p, &tempSaveFile.lastItems, sizeof(JE_PItemsType)); p += sizeof(JE_PItemsType); memcpy(p, &tempSaveFile.difficulty, sizeof(JE_byte)); p++; memcpy(p, &tempSaveFile.secretHint, sizeof(JE_byte)); p++; memcpy(p, &tempSaveFile.input1, sizeof(JE_byte)); p++; memcpy(p, &tempSaveFile.input2, sizeof(JE_byte)); p++; /* booleans were 1 byte in pascal -- working around it */ Uint8 temp = tempSaveFile.gameHasRepeated != false; memcpy(p, &temp, 1); p++; memcpy(p, &tempSaveFile.initialDifficulty, sizeof(JE_byte)); p++; tempSaveFile.highScore1 = SDL_SwapLE32(tempSaveFile.highScore1); memcpy(p, &tempSaveFile.highScore1, sizeof(JE_longint)); p += 4; tempSaveFile.highScore2 = SDL_SwapLE32(tempSaveFile.highScore2); memcpy(p, &tempSaveFile.highScore2, sizeof(JE_longint)); p += 4; memset(p, 0, sizeof(tempSaveFile.highScoreName)); *p = strlen(tempSaveFile.highScoreName); memcpy(&p[1], &tempSaveFile.highScoreName, *p); p += 30; memcpy(p, &tempSaveFile.highScoreDiff, sizeof(JE_byte)); p++; } saveTemp[SIZEOF_SAVEGAMETEMP - 6] = editorLevel >> 8; saveTemp[SIZEOF_SAVEGAMETEMP - 5] = editorLevel; JE_encryptSaveTemp(); f = dir_fopen_warn(get_user_directory(), "tyrian.sav", "wb"); if (f) { efwrite(saveTemp, 1, sizeof(saveTemp), f); fclose(f); #if (_BSD_SOURCE || _XOPEN_SOURCE >= 500) sync(); #endif } JE_decryptSaveTemp(); f = dir_fopen_warn(get_user_directory(), "tyrian.cfg", "wb"); if (f) { efwrite(&background2, 1, 1, f); efwrite(&gameSpeed, 1, 1, f); efwrite(&inputDevice_, 1, 1, f); efwrite(&jConfigure, 1, 1, f); efwrite(&versionNum, 1, 1, f); efwrite(&processorType, 1, 1, f); efwrite(&midiPort, 1, 1, f); efwrite(&soundEffects, 1, 1, f); efwrite(&gammaCorrection, 1, 1, f); efwrite(&difficultyLevel, 1, 1, f); efwrite(joyButtonAssign, 1, 4, f); efwrite(&tyrMusicVolume, 2, 1, f); efwrite(&fxVolume, 2, 1, f); efwrite(inputDevice, 1, 2, f); efwrite(keySettings, sizeof(*keySettings), COUNTOF(keySettings), f); fclose(f); } save_opentyrian_config(); #if (_BSD_SOURCE || _XOPEN_SOURCE >= 500) sync(); #endif }