/ source / blood / src / loadsave.cpp
loadsave.cpp
  1  //-------------------------------------------------------------------------
  2  /*
  3  Copyright (C) 2010-2019 EDuke32 developers and contributors
  4  Copyright (C) 2019 Nuke.YKT
  5  
  6  This file is part of NBlood.
  7  
  8  NBlood is free software; you can redistribute it and/or
  9  modify it under the terms of the GNU General Public License version 2
 10  as published by the Free Software Foundation.
 11  
 12  This program is distributed in the hope that it will be useful,
 13  but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 15  
 16  See the GNU General Public License for more details.
 17  
 18  You should have received a copy of the GNU General Public License
 19  along with this program; if not, write to the Free Software
 20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 21  */
 22  //-------------------------------------------------------------------------
 23  #include <stdio.h>
 24  #include "build.h"
 25  #include "compat.h"
 26  #include "lz4.h"
 27  #include "mmulti.h"
 28  #include "common_game.h"
 29  #include "config.h"
 30  #include "ai.h"
 31  #include "asound.h"
 32  #include "blood.h"
 33  #include "demo.h"
 34  #include "globals.h"
 35  #include "db.h"
 36  #include "messages.h"
 37  #include "menu.h"
 38  #include "network.h"
 39  #include "loadsave.h"
 40  #include "resource.h"
 41  #include "screen.h"
 42  #include "sectorfx.h"
 43  #include "seq.h"
 44  #include "sfx.h"
 45  #include "sound.h"
 46  #include "view.h"
 47  #ifdef NOONE_EXTENSIONS
 48  #include "nnexts.h"
 49  #endif
 50  
 51  #define LZ4_THRESHOLD_SIZE (0x8000)
 52  
 53  GAMEOPTIONS gSaveGameOptions[kMaxLoadSaveSlot];
 54  int gSaveGameProfileSkill[kMaxLoadSaveSlot];
 55  char *gSaveGamePic[kMaxLoadSaveSlot];
 56  unsigned int gSavedOffset = 0;
 57  
 58  LoadSave LoadSave::head(123);
 59  FILE *LoadSave::hSFile = NULL;
 60  int LoadSave::hLFile = -1;
 61  
 62  void LoadSave::Save(void)
 63  {
 64      ThrowError("Pure virtual function called");
 65  }
 66  
 67  void LoadSave::Load(void)
 68  {
 69      ThrowError("Pure virtual function called");
 70  }
 71  
 72  void LoadSave::Read(void *pData, int nSize)
 73  {
 74      dassert(hLFile != -1);
 75      while (nSize >= LZ4_THRESHOLD_SIZE)
 76      {
 77          int nCompSize;
 78          if (kread(hLFile, (void *)&nCompSize, sizeof(nCompSize)) != sizeof(nCompSize))
 79              ThrowError("Error reading save file.");
 80          nCompSize = B_LITTLE32(nCompSize);
 81          if (nCompSize >= nSize)
 82              ThrowError("Error reading size file compress offset.");
 83          if (nCompSize == 0) // uncompressed data
 84              break;
 85  
 86          void *pIn = Xaligned_alloc(16, nCompSize);
 87          if (!pIn)
 88          {
 89              ThrowError("File error #%d could not allocate %d bytes.", errno, nCompSize);
 90              return;
 91          }
 92          if (kread(hLFile, pIn, nCompSize) != nCompSize)
 93              ThrowError("Error reading save file.");
 94          LZ4_decompress_safe((const char *)pIn, (char *)pData, nCompSize, nSize);
 95          Xaligned_free(pIn);
 96          return;
 97      }
 98      if (kread(hLFile, pData, nSize) != nSize)
 99          ThrowError("Error reading save file.");
100  }
101  
102  void LoadSave::Write(void const *pData, int nSize)
103  {
104      dassert(hSFile != NULL);
105      while (nSize >= LZ4_THRESHOLD_SIZE)
106      {
107          int nCompSize = LZ4_compressBound(nSize);
108          void *pOut = Xaligned_alloc(16, nCompSize);
109          if (!pOut)
110          {
111              nCompSize = 0;
112              if (fwrite((void *)&nCompSize, 1, sizeof(nCompSize), hSFile) != sizeof(nCompSize))
113                  ThrowError("File error #%d writing save file.", errno);
114              break;
115          }
116          nCompSize = LZ4_compress_fast((const char *)pData, (char *)pOut, nSize, nCompSize, lz4CompressionLevel);
117          const char bBadRatio = nCompSize >= nSize; // compressed size is bigger than uncompressed, store as uncompressed data
118          if (bBadRatio)
119          {
120              nCompSize = 0;
121              if (fwrite((void *)&nCompSize, 1, sizeof(nCompSize), hSFile) != sizeof(nCompSize))
122                  ThrowError("File error #%d writing save file.", errno);
123              Xaligned_free(pOut);
124              break;
125          }
126  
127          int nCompSizeBSwap = B_LITTLE32(nCompSize);
128          if (fwrite((void *)&nCompSizeBSwap, 1, sizeof(nCompSizeBSwap), hSFile) != sizeof(nCompSizeBSwap))
129              ThrowError("File error #%d writing save file.", errno);
130          if (fwrite(pOut, 1, nCompSize, hSFile) != (size_t)nCompSize)
131              ThrowError("File error #%d writing save file.", errno);
132          Xaligned_free(pOut);
133          return;
134      }
135      if (fwrite(pData, 1, nSize, hSFile) != (size_t)nSize)
136          ThrowError("File error #%d writing save file.", errno);
137  }
138  
139  void LoadSave::LoadGame(char *pzFile)
140  {
141      const char bDemoWasPlayed = gDemo.bPlaying;
142      const char bGameWasStarted = gGameStarted;
143      if (gDemo.bPlaying || gDemo.bRecording)
144          gDemo.Close();
145  
146      gViewPos = VIEWPOS_0;
147      gViewIndex = myconnectindex;
148      sndKillAllSounds();
149      sfxKillAllSounds();
150      ambKillAll();
151      seqKillAll();
152      if (!gGameStarted)
153      {
154          memset(xvel,0,sizeof(xvel[0])*kMaxSprites);
155          memset(yvel,0,sizeof(yvel[0])*kMaxSprites);
156          memset(zvel,0,sizeof(zvel[0])*kMaxSprites);
157          memset(xsprite, 0, sizeof(XSPRITE)*kMaxXSprites);
158          memset(sprite, 0, sizeof(spritetype)*kMaxSprites);
159          memset(qsprite_filler,0,sizeof(qsprite_filler[0])*kMaxSprites);
160          memset(qsector_filler,0,sizeof(qsector_filler[0])*kMaxSectors);
161          automapping = 1;
162      }
163      hLFile = kopen4load(pzFile, 0);
164      if (hLFile == -1)
165          ThrowError("Error loading save file.");
166      LoadSave *rover = head.next;
167      while (rover != &head)
168      {
169          rover->Load();
170          rover = rover->next;
171      }
172      kclose(hLFile);
173      hLFile = -1;
174      if (!gGameStarted)
175          scrLoadPLUs();
176      InitSectorFX();
177      viewInitializePrediction();
178      PreloadCache();
179      if (!VanillaMode()) // set reverb sound effect state
180          sfxSetReverb(packItemActive(gMe, kPackDivingSuit) || powerupCheck(gMe, kPwUpReflectShots));
181      ambInit();
182  #ifdef YAX_ENABLE
183      yax_update(numyaxbunches > 0 ? 2 : 1);
184  #endif
185      calc_sector_reachability();
186      memset(myMinLag, 0, sizeof(myMinLag));
187      otherMinLag = 0;
188      myMaxLag = 0;
189      gNetFifoClock = 0;
190      gNetFifoTail = 0;
191      memset(gNetFifoHead, 0, sizeof(gNetFifoHead));
192      gPredictTail = 0;
193      gNetFifoMasterTail = 0;
194      memset(gFifoInput, 0, sizeof(gFifoInput));
195      memset(gChecksum, 0, sizeof(gChecksum));
196      memset(gCheckFifo, 0, sizeof(gCheckFifo));
197      memset(gCheckHead, 0, sizeof(gCheckHead));
198      gSendCheckTail = 0;
199      gCheckTail = 0;
200      gBufferJitter = 0;
201      bOutOfSync = 0;
202      for (int i = 0; i < gNetPlayers; i++)
203          playerSetRace(&gPlayer[i], gPlayer[i].lifeMode);
204      if (VanillaMode())
205          viewSetMessage("");
206      else
207          gGameMessageMgr.Clear();
208      viewSetErrorMessage("");
209      viewResizeView(gViewSize);
210      if (!gGameStarted)
211      {
212          netWaitForEveryone(0);
213          memset(gPlayerReady, 0, sizeof(gPlayerReady));
214      }
215      gFrameTicks = 0;
216      gFrame = 0;
217      gCacheMiss = 0;
218      gFrameRate = 0;
219      totalclock = 0;
220      gPaused = 0;
221      gGameStarted = 1;
222  
223  #ifdef USE_STRUCT_TRACKERS
224      Bmemset(sectorchanged, 0, sizeof(sectorchanged));
225      Bmemset(spritechanged, 0, sizeof(spritechanged));
226      Bmemset(wallchanged, 0, sizeof(wallchanged));
227  #endif
228  
229  #ifdef USE_OPENGL
230      Polymost_prepare_loadboard();
231  #endif
232  
233  #ifdef POLYMER
234      if (videoGetRenderMode() == REND_POLYMER)
235          polymer_loadboard();
236  
237      // this light pointer nulling needs to be outside the videoGetRenderMode check
238      // because we might be loading the savegame using another renderer but
239      // change to Polymer later
240      for (int i=0; i<kMaxSprites; i++)
241      {
242          gPolymerLight[i].lightptr = NULL;
243          gPolymerLight[i].lightId = -1;
244      }
245  #endif
246  
247      if (gGameOptions.nEpisode >= gEpisodeCount || gGameOptions.nLevel >= gEpisodeInfo[gGameOptions.nEpisode].nLevels
248          || Bstrcasecmp(gEpisodeInfo[gGameOptions.nEpisode].levelsInfo[gGameOptions.nLevel].Filename, gGameOptions.zLevelName) != 0)
249      {
250          if (!gSysRes.Lookup(gGameOptions.zLevelName, "MAP"))
251          {
252              gGameOptions.nEpisode = 0;
253              gGameOptions.nLevel = 0;
254          }
255          else
256          {
257              levelAddUserMap(gGameOptions.zLevelName);
258          }
259      }
260  
261      if (MusicRestartsOnLoadToggle
262          || bDemoWasPlayed
263          || !bGameWasStarted
264          || (gMusicPrevLoadedEpisode != gGameOptions.nEpisode || gMusicPrevLoadedLevel != gGameOptions.nLevel))
265      {
266          levelTryPlayMusicOrNothing(gGameOptions.nEpisode, gGameOptions.nLevel);
267      }
268      gMusicPrevLoadedEpisode = gGameOptions.nEpisode;
269      gMusicPrevLoadedLevel = gGameOptions.nLevel;
270  
271      netBroadcastPlayerInfo(myconnectindex);
272      //sndPlaySong(gGameOptions.zLevelSong, 1);
273  
274      if ((gGameOptions.nGameType == kGameTypeSinglePlayer) && (numplayers == 1)) // if single-player, update the game options/player profile by loading the current set settings
275      {
276          gGameOptions.bQuadDamagePowerup = gQuadDamagePowerup;
277          gGameOptions.nDamageInvul = gDamageInvul;
278          gGameOptions.nProjectileBehavior = gProjectileBehavior;
279          gGameOptions.bNapalmFalloff = gNapalmFalloff;
280          gGameOptions.nEnemyBehavior = gEnemyBehavior;
281          gGameOptions.bEnemyRandomTNT = gEnemyRandomTNT;
282          gGameOptions.nWeaponsVer = gWeaponsVer;
283          gGameOptions.nAmmoScale = gAmmoScale;
284          gGameOptions.bSectorBehavior = gSectorBehavior;
285          gGameOptions.nHitscanProjectiles = gHitscanProjectiles;
286          gGameOptions.nGoreBehavior = gGoreBehavior;
287          gGameOptions.nPlayerSpeed = gPlayerModSpeed;
288          gGameOptions.nRandomizerMode = gRandomizerMode;
289          Bmemcpy(gGameOptions.szRandomizerSeed, gzRandomizerSeed, sizeof(gGameOptions.szRandomizerSeed));
290      }
291  }
292  
293  // TODO: when starting to use buildvfs in SaveGame(), remove this function and use buildvfs_exists() instead
294  static int file_exists(char const* path)
295  {
296  #ifdef _WIN32
297      return GetFileAttributes(path) != INVALID_FILE_ATTRIBUTES;
298  #else
299      struct Bstat st;
300      return !Bstat(path, &st);
301  #endif
302  }
303  
304  void LoadSave::SaveGame(char *pzFile)
305  {
306      // TODO: use buildvfs_open_write() etc (probably in the whole file?)
307      char fileNameTmp[BMAX_PATH+4];
308      const char* saveFileName = pzFile;
309      bool saveFileExists = file_exists(saveFileName);
310      if (saveFileExists)
311      {
312          // write to a different file first, so in case the game crashes while saving
313          // (which would result in a corrupted savegame) at least the old savegame is preserved
314          strcpy(fileNameTmp, pzFile);
315          strcat(fileNameTmp, "_tmp");
316          saveFileName = fileNameTmp;
317      }
318  
319      hSFile = fopen(saveFileName, "wb");
320      if (hSFile == NULL)
321          ThrowError("File error #%d creating save file.", errno);
322      LoadSave *rover = head.next;
323      while (rover != &head)
324      {
325          rover->Save();
326          rover = rover->next;
327      }
328      fclose(hSFile);
329      hSFile = NULL;
330      if (saveFileExists)
331      {
332          // the savegame was written successfully, so we can rename the saved file
333          // to the requested name (from gameXXX.sav_tmp to gameXXX.sav)
334  #ifdef _WIN32
335          _unlink(pzFile);
336  #endif
337          rename(saveFileName, pzFile);
338      }
339  }
340  
341  class MyLoadSave : public LoadSave
342  {
343  public:
344      virtual void Load(void);
345      virtual void Save(void);
346  };
347  
348  void MyLoadSave::Load(void)
349  {
350      psky_t *pSky = tileSetupSky(0);
351      int id;
352      Read(&id, sizeof(id));
353      if (id != 0x5653424e/*'VSBN'*/)
354          ThrowError("Old saved game found");
355      short version;
356      Read(&version, sizeof(version));
357      if (version != BYTEVERSION)
358          ThrowError("Incompatible version of saved game found!");
359      short nSkill;
360      Read(&nSkill, sizeof(nSkill));
361      Read(&gGameOptions, sizeof(gGameOptions));
362      Read(&numsectors, sizeof(numsectors));
363      Read(&numwalls, sizeof(numwalls));
364      Read(&numsectors, sizeof(numsectors));
365      int nNumSprites;
366      Read(&nNumSprites, sizeof(nNumSprites));
367      memset(sector, 0, sizeof(sector[0])*kMaxSectors);
368      memset(wall, 0, sizeof(wall[0])*kMaxWalls);
369      memset(sprite, 0, sizeof(sprite[0])*kMaxSprites);
370      memset(spriteext, 0, sizeof(spriteext[0])*kMaxSprites);
371      memset(qsprite_filler,0,sizeof(qsprite_filler[0])*kMaxSprites);
372      memset(qsector_filler,0,sizeof(qsector_filler[0])*kMaxSectors);
373      Read(sector, sizeof(sector[0])*numsectors);
374      Read(wall, sizeof(wall[0])*numwalls);
375      Read(sprite, sizeof(sprite[0])*kMaxSprites);
376      Read(spriteext, sizeof(spriteext[0])*kMaxSprites);
377      Read(qsector_filler, sizeof(qsector_filler[0])*numsectors);
378      Read(qsprite_filler, sizeof(qsprite_filler[0])*kMaxSprites);
379      Read(&randomseed, sizeof(randomseed));
380      Read(&parallaxtype, sizeof(parallaxtype));
381      char showinvisibilitytemp; // don't set to current showinvisibility var
382      Read(&showinvisibilitytemp, sizeof(showinvisibilitytemp));
383      Read(&pSky->horizfrac, sizeof(pSky->horizfrac));
384      Read(&pSky->yoffs, sizeof(pSky->yoffs));
385      Read(&pSky->yscale, sizeof(pSky->yscale));
386      Read(&gVisibility, sizeof(gVisibility));
387      Read(&g_visibility, sizeof(g_visibility));
388      Read(&parallaxvisibility, sizeof(parallaxvisibility));
389      Read(pSky->tileofs, sizeof(pSky->tileofs));
390      Read(&pSky->lognumtiles, sizeof(pSky->lognumtiles));
391      Read(headspritesect, sizeof(headspritesect));
392      Read(headspritestat, sizeof(headspritestat));
393      Read(prevspritesect, sizeof(prevspritesect));
394      Read(prevspritestat, sizeof(prevspritestat));
395      Read(nextspritesect, sizeof(nextspritesect));
396      Read(nextspritestat, sizeof(nextspritestat));
397      Read(show2dsector, sizeof(show2dsector));
398      Read(show2dwall, sizeof(show2dwall));
399      Read(show2dsprite, sizeof(show2dsprite));
400      Read(&automapping, sizeof(automapping));
401      Read(gotpic, sizeof(gotpic));
402      Read(gotsector, sizeof(gotsector));
403      Read(&gFrameClock, sizeof(gFrameClock));
404      Read(&gFrameTicks, sizeof(gFrameTicks));
405      Read(&gFrame, sizeof(gFrame));
406      ClockTicks nGameClock;
407      Read(&totalclock, sizeof(totalclock));
408      totalclock = nGameClock;
409      Read(&gLevelTime, sizeof(gLevelTime));
410      Read(&gPaused, sizeof(gPaused));
411      Read(baseWall, sizeof(baseWall[0])*numwalls);
412      Read(baseSprite, sizeof(baseSprite[0])*nNumSprites);
413      Read(baseFloor, sizeof(baseFloor[0])*numsectors);
414      Read(baseCeil, sizeof(baseCeil[0])*numsectors);
415      Read(velFloor, sizeof(velFloor[0])*numsectors);
416      Read(velCeil, sizeof(velCeil[0])*numsectors);
417      Read(&gHitInfo, sizeof(gHitInfo));
418      Read(&byte_1A76C6, sizeof(byte_1A76C6));
419      Read(&byte_1A76C8, sizeof(byte_1A76C8));
420      Read(&byte_1A76C7, sizeof(byte_1A76C7));
421      Read(&byte_19AE44, sizeof(byte_19AE44));
422      Read(gStatCount, sizeof(gStatCount));
423      Read(nextXSprite, sizeof(nextXSprite));
424      Read(nextXWall, sizeof(nextXWall));
425      Read(nextXSector, sizeof(nextXSector));
426      memset(xsprite, 0, sizeof(xsprite[0])*kMaxXSprites);
427      for (int nSprite = 0; nSprite < kMaxSprites; nSprite++)
428      {
429          int nXSprite = sprite[nSprite].extra;
430          if (nXSprite > 0)
431              Read(&xsprite[nXSprite], sizeof(XSPRITE));
432      }
433      memset(xwall, 0, sizeof(xwall));
434      for (int nWall = 0; nWall < numwalls; nWall++)
435      {
436          int nXWall = wall[nWall].extra;
437          if (nXWall > 0)
438              Read(&xwall[nXWall], sizeof(XWALL));
439      }
440      memset(xsector, 0, sizeof(xsector));
441      for (int nSector = 0; nSector < numsectors; nSector++)
442      {
443          int nXSector = sector[nSector].extra;
444          if (nXSector > 0)
445              Read(&xsector[nXSector], sizeof(XSECTOR));
446      }
447      Read(xvel, nNumSprites*sizeof(xvel[0]));
448      Read(yvel, nNumSprites*sizeof(yvel[0]));
449      Read(zvel, nNumSprites*sizeof(zvel[0]));
450      Read(&gMapRev, sizeof(gMapRev));
451      Read(&gSongId, sizeof(gSongId));
452      Read(&gSkyCount, sizeof(gSkyCount));
453      Read(&gFogMode, sizeof(gFogMode));
454  #ifdef NOONE_EXTENSIONS
455      Read(&gModernMap, sizeof(gModernMap));
456  #endif
457  #ifdef YAX_ENABLE
458      Read(&numyaxbunches, sizeof(numyaxbunches));
459  #endif
460      psky_t skyInfo;
461      Read(&skyInfo, sizeof(skyInfo));
462  
463      *tileSetupSky(0) = skyInfo;
464      gCheatMgr.ResetCheats();
465  
466  }
467  
468  void MyLoadSave::Save(void)
469  {
470      psky_t *pSky = tileSetupSky(0);
471      int nNumSprites = 0;
472      int id = 0x5653424e/*'VSBN'*/;
473      Write(&id, sizeof(id));
474      short version = BYTEVERSION;
475      Write(&version, sizeof(version));
476      short nSkill = (short)gProfile[myconnectindex].skill;
477      Write(&nSkill, sizeof(nSkill));
478      for (int nSprite = 0; nSprite < kMaxSprites; nSprite++)
479      {
480          if (sprite[nSprite].statnum < kMaxStatus && nSprite > nNumSprites)
481              nNumSprites = nSprite;
482      }
483      nNumSprites++;
484      Write(&gGameOptions, sizeof(gGameOptions));
485      Write(&numsectors, sizeof(numsectors));
486      Write(&numwalls, sizeof(numwalls));
487      Write(&numsectors, sizeof(numsectors));
488      Write(&nNumSprites, sizeof(nNumSprites));
489      Write(sector, sizeof(sector[0])*numsectors);
490      Write(wall, sizeof(wall[0])*numwalls);
491      Write(sprite, sizeof(sprite[0])*kMaxSprites);
492      Write(spriteext, sizeof(spriteext[0])*kMaxSprites);
493      Write(qsector_filler, sizeof(qsector_filler[0])*numsectors);
494      Write(qsprite_filler, sizeof(qsprite_filler[0])*kMaxSprites);
495      Write(&randomseed, sizeof(randomseed));
496      Write(&parallaxtype, sizeof(parallaxtype));
497      char showinvisibilitytemp; // don't set to current showinvisibility var
498      Write(&showinvisibilitytemp, sizeof(showinvisibilitytemp));
499      Write(&pSky->horizfrac, sizeof(pSky->horizfrac));
500      Write(&pSky->yoffs, sizeof(pSky->yoffs));
501      Write(&pSky->yscale, sizeof(pSky->yscale));
502      Write(&gVisibility, sizeof(gVisibility));
503      Write(&g_visibility, sizeof(g_visibility));
504      Write(&parallaxvisibility, sizeof(parallaxvisibility));
505      Write(pSky->tileofs, sizeof(pSky->tileofs));
506      Write(&pSky->lognumtiles, sizeof(pSky->lognumtiles));
507      Write(headspritesect, sizeof(headspritesect));
508      Write(headspritestat, sizeof(headspritestat));
509      Write(prevspritesect, sizeof(prevspritesect));
510      Write(prevspritestat, sizeof(prevspritestat));
511      Write(nextspritesect, sizeof(nextspritesect));
512      Write(nextspritestat, sizeof(nextspritestat));
513      Write(show2dsector, sizeof(show2dsector));
514      Write(show2dwall, sizeof(show2dwall));
515      Write(show2dsprite, sizeof(show2dsprite));
516      Write(&automapping, sizeof(automapping));
517      Write(gotpic, sizeof(gotpic));
518      Write(gotsector, sizeof(gotsector));
519      Write(&gFrameClock, sizeof(gFrameClock));
520      Write(&gFrameTicks, sizeof(gFrameTicks));
521      Write(&gFrame, sizeof(gFrame));
522      ClockTicks nGameClock = totalclock;
523      Write(&nGameClock, sizeof(nGameClock));
524      Write(&gLevelTime, sizeof(gLevelTime));
525      Write(&gPaused, sizeof(gPaused));
526      Write(baseWall, sizeof(baseWall[0])*numwalls);
527      Write(baseSprite, sizeof(baseSprite[0])*nNumSprites);
528      Write(baseFloor, sizeof(baseFloor[0])*numsectors);
529      Write(baseCeil, sizeof(baseCeil[0])*numsectors);
530      Write(velFloor, sizeof(velFloor[0])*numsectors);
531      Write(velCeil, sizeof(velCeil[0])*numsectors);
532      Write(&gHitInfo, sizeof(gHitInfo));
533      Write(&byte_1A76C6, sizeof(byte_1A76C6));
534      Write(&byte_1A76C8, sizeof(byte_1A76C8));
535      Write(&byte_1A76C7, sizeof(byte_1A76C7));
536      Write(&byte_19AE44, sizeof(byte_19AE44));
537      Write(gStatCount, sizeof(gStatCount));
538      Write(nextXSprite, sizeof(nextXSprite));
539      Write(nextXWall, sizeof(nextXWall));
540      Write(nextXSector, sizeof(nextXSector));
541      for (int nSprite = 0; nSprite < kMaxSprites; nSprite++)
542      {
543          int nXSprite = sprite[nSprite].extra;
544          if (nXSprite > 0)
545              Write(&xsprite[nXSprite], sizeof(XSPRITE));
546      }
547      for (int nWall = 0; nWall < numwalls; nWall++)
548      {
549          int nXWall = wall[nWall].extra;
550          if (nXWall > 0)
551              Write(&xwall[nXWall], sizeof(XWALL));
552      }
553      for (int nSector = 0; nSector < numsectors; nSector++)
554      {
555          int nXSector = sector[nSector].extra;
556          if (nXSector > 0)
557              Write(&xsector[nXSector], sizeof(XSECTOR));
558      }
559      Write(xvel, nNumSprites*sizeof(xvel[0]));
560      Write(yvel, nNumSprites*sizeof(yvel[0]));
561      Write(zvel, nNumSprites*sizeof(zvel[0]));
562      Write(&gMapRev, sizeof(gMapRev));
563      Write(&gSongId, sizeof(gSongId));
564      Write(&gSkyCount, sizeof(gSkyCount));
565      Write(&gFogMode, sizeof(gFogMode));
566  #ifdef NOONE_EXTENSIONS
567      Write(&gModernMap, sizeof(gModernMap));
568  #endif
569  #ifdef YAX_ENABLE
570      Write(&numyaxbunches, sizeof(numyaxbunches));
571  #endif
572      psky_t skyInfo = *tileSetupSky(0);
573      Write(&skyInfo, sizeof(skyInfo));
574  }
575  
576  void LoadSavedInfo(void)
577  {
578      int const bakpathsearchmode = pathsearchmode;
579      pathsearchmode = 1;
580      const int nNameMin = strlen("##.sav"); // length offset for string numbers
581      auto pList = klistpath((g_modDir[0] != '/') ? g_modDir : "./", "game00*.sav", BUILDVFS_FIND_FILE);
582      for (auto pIterator = pList; pIterator != NULL; pIterator = pIterator->next)
583      {
584          int hFile = kopen4loadfrommod(pIterator->name, 0);
585          if (hFile == -1)
586              ThrowError("Error loading save file header.");
587          int id = 0;
588          short version = 0;
589          short nSkill = 0, nSlot = 0;
590          if ((uint32_t)kread(hFile, &id, sizeof(id)) != sizeof(id))
591          {
592              kclose(hFile);
593              continue;
594          }
595          if (id != 0x5653424e/*'VSBN'*/)
596          {
597              kclose(hFile);
598              continue;
599          }
600          kread(hFile, &version, sizeof(version));
601          if (version != BYTEVERSION)
602          {
603              kclose(hFile);
604              continue;
605          }
606          nSlot = strlen(pIterator->name);
607          if (nSlot < nNameMin) // unexpected size, abort
608          {
609              kclose(hFile);
610              continue;
611          }
612          nSlot = Batoi(&pIterator->name[nSlot-nNameMin]);
613          if (nSlot > kLoadSaveSlot10) // slot id too big, skip
614          {
615              kclose(hFile);
616              continue;
617          }
618          kread(hFile, &nSkill, sizeof(nSkill));
619          if ((uint32_t)kread(hFile, &gSaveGameOptions[nSlot], sizeof(gSaveGameOptions[0])) != sizeof(gSaveGameOptions[0]))
620              ThrowError("Error reading save file.");
621          LoadUpdateSaveGame(nSlot, nSkill);
622          kclose(hFile);
623      }
624      klistfree(pList);
625      pathsearchmode = bakpathsearchmode;
626  }
627  
628  void LoadAutosavedInfo(void)
629  {
630      int const bakpathsearchmode = pathsearchmode;
631      pathsearchmode = 1;
632      const int nNameMin = strlen("#.sav"); // length offset for string numbers
633      auto pList = klistpath((g_modDir[0] != '/') ? g_modDir : "./", "gameautosave*.sav", BUILDVFS_FIND_FILE);
634      for (auto pIterator = pList; pIterator != NULL; pIterator = pIterator->next)
635      {
636          int hFile = kopen4loadfrommod(pIterator->name, 0);
637          if (hFile == -1)
638              ThrowError("Error loading save file header.");
639          int id = 0;
640          short version = 0;
641          short nSkill = 0, nSlot = 0;
642          if ((uint32_t)kread(hFile, &id, sizeof(id)) != sizeof(id))
643          {
644              kclose(hFile);
645              continue;
646          }
647          if (id != 0x5653424e/*'VSBN'*/)
648          {
649              kclose(hFile);
650              continue;
651          }
652          kread(hFile, &version, sizeof(version));
653          if (version != BYTEVERSION)
654          {
655              kclose(hFile);
656              continue;
657          }
658          nSlot = strlen(pIterator->name);
659          if (nSlot < nNameMin) // unexpected size, abort
660          {
661              kclose(hFile);
662              continue;
663          }
664          nSlot = kLoadSaveSlotAutosave + Batoi(&pIterator->name[nSlot-nNameMin]);
665          if (nSlot < kLoadSaveSlotAutosave || nSlot > kLoadSaveSlotKey) // slot id too small/big, skip
666          {
667              kclose(hFile);
668              continue;
669          }
670          kread(hFile, &nSkill, sizeof(nSkill));
671          if ((uint32_t)kread(hFile, &gSaveGameOptions[nSlot], sizeof(gSaveGameOptions[0])) != sizeof(gSaveGameOptions[0]))
672              ThrowError("Error reading save file.");
673          LoadUpdateSaveGame(nSlot, nSkill);
674          kclose(hFile);
675      }
676      klistfree(pList);
677      pathsearchmode = bakpathsearchmode;
678  }
679  
680  bool LoadSavedInCurrentSession(int nSlot)
681  {
682      if ((nSlot < kLoadSaveSlot0) || (nSlot >= kMaxLoadSaveSlot))
683          return false;
684      if (gSaveGameOptions[nSlot].nEpisode != gGameOptions.nEpisode)
685          return false;
686      if (gSaveGameOptions[nSlot].nLevel != gGameOptions.nLevel)
687          return false;
688      if (gSaveGameOptions[nSlot].nDifficulty != gGameOptions.nDifficulty)
689          return false;
690      if (gSaveGameOptions[nSlot].nEnemyHealth != gGameOptions.nEnemyHealth)
691          return false;
692      if (gSaveGameOptions[nSlot].nEnemyQuantity != gGameOptions.nEnemyQuantity)
693          return false;
694      if (gSaveGameProfileSkill[nSlot] != gProfile[myconnectindex].skill)
695          return false;
696      if (gSaveGameOptions[nSlot].nEnemySpeed != gGameOptions.nEnemySpeed)
697          return false;
698      if (gSaveGameOptions[nSlot].bEnemyShuffle != gGameOptions.bEnemyShuffle)
699          return false;
700      if (gSaveGameOptions[nSlot].bPitchforkOnly != gGameOptions.bPitchforkOnly)
701          return false;
702      if (gSaveGameOptions[nSlot].bPermaDeath != gGameOptions.bPermaDeath)
703          return false;
704      if (gSaveGameOptions[nSlot].uSpriteBannedFlags != gGameOptions.uSpriteBannedFlags)
705          return false;
706      if (gSaveGameOptions[nSlot].nRandomizerCheat != gGameOptions.nRandomizerCheat)
707          return false;
708      if (Bstrncmp(gSaveGameOptions[nSlot].zLevelName, gGameOptions.zLevelName, sizeof(gSaveGameOptions[nSlot].zLevelName)))
709          return false;
710      return true;
711  }
712  
713  void LoadUpdateSaveGame(int nSlot, int nSkill)
714  {
715      gSaveGameProfileSkill[nSlot] = nSkill;
716      Bstrncpyz(strRestoreGameStrings[gSaveGameOptions[nSlot].nSaveGameSlot], gSaveGameOptions[nSlot].szUserGameName, sizeof(strRestoreGameStrings[gSaveGameOptions[nSlot].nSaveGameSlot]));
717  
718      char nDifficulty = gSaveGameOptions[nSlot].nDifficulty;
719      if (gSaveGameOptions[nSlot].nDifficulty != gSaveGameOptions[nSlot].nEnemyHealth)
720          nDifficulty = 5;
721      else if (gSaveGameOptions[nSlot].nDifficulty != gSaveGameOptions[nSlot].nEnemyQuantity)
722          nDifficulty = 5;
723      else if (gSaveGameOptions[nSlot].nDifficulty != (char)gSaveGameProfileSkill[nSlot])
724          nDifficulty = 5;
725      else if (gSaveGameOptions[nSlot].nEnemySpeed)
726          nDifficulty = 5;
727      else if (gSaveGameOptions[nSlot].bEnemyShuffle)
728          nDifficulty = 5;
729      else if (gSaveGameOptions[nSlot].bPitchforkOnly)
730          nDifficulty = 5;
731      else if (gSaveGameOptions[nSlot].bPermaDeath)
732          nDifficulty = 5;
733      else if (gSaveGameOptions[nSlot].uSpriteBannedFlags)
734          nDifficulty = 5;
735      restoreGameDifficulty[gSaveGameOptions[nSlot].nSaveGameSlot] = nDifficulty;
736  }
737  
738  static MyLoadSave *myLoadSave;
739  
740  
741  void LoadSaveSetup(void)
742  {
743      void ActorLoadSaveConstruct(void);
744      void AILoadSaveConstruct(void);
745      void EndGameLoadSaveConstruct(void);
746      void EventQLoadSaveConstruct(void);
747      void LevelsLoadSaveConstruct(void);
748      void MessagesLoadSaveConstruct(void);
749      void MirrorLoadSaveConstruct(void);
750      void PlayerLoadSaveConstruct(void);
751      void SeqLoadSaveConstruct(void);
752      void TriggersLoadSaveConstruct(void);
753      void ViewLoadSaveConstruct(void);
754      void WarpLoadSaveConstruct(void);
755      void WeaponLoadSaveConstruct(void);
756  #ifdef NOONE_EXTENSIONS
757      void nnExtLoadSaveConstruct(void);
758  #endif
759      myLoadSave = new MyLoadSave();
760      
761      // NoOne: Seq must be in top of AI because of AISTATE callbacks (and seq callbacks in general).
762      // Ex: cultist throwing TNT - if you saved the game while it plays animation
763      // before fire trigger seq flag, you will get assertion fail on load since
764      // target == -1 after aiInitSprite() call.
765  
766      // Another reason is that it just spawns the wrong animation.
767      // Ex: save the game when some dude moves, load it and quckly
768      // use ONERING cheat. You will see that dude plays moving
769      // animation, but have idle AISTATE after aiInitSprite()
770      // call. In vanilla they just stand still.
771  
772      SeqLoadSaveConstruct();
773      ActorLoadSaveConstruct();
774      AILoadSaveConstruct();
775      EndGameLoadSaveConstruct();
776      EventQLoadSaveConstruct();
777      LevelsLoadSaveConstruct();
778      MessagesLoadSaveConstruct();
779      MirrorLoadSaveConstruct();
780      PlayerLoadSaveConstruct();
781      TriggersLoadSaveConstruct();
782      ViewLoadSaveConstruct();
783      WarpLoadSaveConstruct();
784      WeaponLoadSaveConstruct();
785  #ifdef NOONE_EXTENSIONS
786      nnExtLoadSaveConstruct();
787  #endif
788  }