]> icculus.org git repositories - theoddone33/hhexen.git/blob - base/sv_save.c
Add alt-enter fullscreen toggle
[theoddone33/hhexen.git] / base / sv_save.c
1
2 //**************************************************************************
3 //**
4 //** sv_save.c : Heretic 2 : Raven Software, Corp.
5 //**
6 //** $RCSfile$
7 //** $Revision$
8 //** $Date$
9 //** $Author$
10 //**
11 //**************************************************************************
12
13 // HEADER FILES ------------------------------------------------------------
14
15 #include "h2def.h"
16 #include "p_local.h"
17
18 // MACROS ------------------------------------------------------------------
19
20 #define MAX_TARGET_PLAYERS 512
21 #define MOBJ_NULL -1
22 #define MOBJ_XX_PLAYER -2
23 #define GET_BYTE (*SavePtr.b++)
24 #define GET_WORD (*SavePtr.w++)
25 #define GET_LONG (*SavePtr.l++)
26 #define MAX_MAPS 99
27 #define BASE_SLOT 6
28 #define REBORN_SLOT 7
29 #define REBORN_DESCRIPTION "TEMP GAME"
30 #define MAX_THINKER_SIZE 256
31
32 // TYPES -------------------------------------------------------------------
33
34 typedef enum
35 {
36         ASEG_GAME_HEADER = 101,
37         ASEG_MAP_HEADER,
38         ASEG_WORLD,
39         ASEG_POLYOBJS,
40         ASEG_MOBJS,
41         ASEG_THINKERS,
42         ASEG_SCRIPTS,
43         ASEG_PLAYERS,
44         ASEG_SOUNDS,
45         ASEG_MISC,
46         ASEG_END
47 } gameArchiveSegment_t;
48
49 typedef enum
50 {
51         TC_NULL,
52         TC_MOVE_CEILING,
53         TC_VERTICAL_DOOR,
54         TC_MOVE_FLOOR,
55         TC_PLAT_RAISE,
56         TC_INTERPRET_ACS,
57         TC_FLOOR_WAGGLE,
58         TC_LIGHT,
59         TC_PHASE,
60         TC_BUILD_PILLAR,
61         TC_ROTATE_POLY,
62         TC_MOVE_POLY,
63         TC_POLY_DOOR
64 } thinkClass_t;
65
66 typedef struct
67 {
68         thinkClass_t tClass;
69         think_t thinkerFunc;
70         void (*mangleFunc)();
71         void (*restoreFunc)();
72         size_t size;
73 } thinkInfo_t;
74
75 typedef struct
76 {
77         thinker_t thinker;
78         sector_t *sector;
79 } ssthinker_t;
80
81 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
82
83 void P_SpawnPlayer(mapthing_t *mthing);
84
85 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
86
87 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
88
89 static void ArchiveWorld(void);
90 static void UnarchiveWorld(void);
91 static void ArchivePolyobjs(void);
92 static void UnarchivePolyobjs(void);
93 static void ArchiveMobjs(void);
94 static void UnarchiveMobjs(void);
95 static void ArchiveThinkers(void);
96 static void UnarchiveThinkers(void);
97 static void ArchiveScripts(void);
98 static void UnarchiveScripts(void);
99 static void ArchivePlayers(void);
100 static void UnarchivePlayers(void);
101 static void ArchiveSounds(void);
102 static void UnarchiveSounds(void);
103 static void ArchiveMisc(void);
104 static void UnarchiveMisc(void);
105 static void SetMobjArchiveNums(void);
106 static void RemoveAllThinkers(void);
107 static void MangleMobj(mobj_t *mobj);
108 static void RestoreMobj(mobj_t *mobj);
109 static int GetMobjNum(mobj_t *mobj);
110 static void SetMobjPtr(int *archiveNum);
111 static void MangleSSThinker(ssthinker_t *sst);
112 static void RestoreSSThinker(ssthinker_t *sst);
113 static void RestoreSSThinkerNoSD(ssthinker_t *sst);
114 static void MangleScript(acs_t *script);
115 static void RestoreScript(acs_t *script);
116 static void RestorePlatRaise(plat_t *plat);
117 static void RestoreMoveCeiling(ceiling_t *ceiling);
118 static void AssertSegment(gameArchiveSegment_t segType);
119 static void ClearSaveSlot(int slot);
120 static void CopySaveSlot(int sourceSlot, int destSlot);
121 static void CopyFile(char *sourceName, char *destName);
122 static boolean ExistingFile(char *name);
123 static void OpenStreamOut(char *fileName);
124 static void CloseStreamOut(void);
125 static void StreamOutBuffer(void *buffer, int size);
126 static void StreamOutByte(byte val);
127 static void StreamOutWord(unsigned short val);
128 static void StreamOutLong(unsigned int val);
129
130 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
131
132 extern int ACScriptCount;
133 extern byte *ActionCodeBase;
134 extern acsInfo_t *ACSInfo;
135
136 // PUBLIC DATA DEFINITIONS -------------------------------------------------
137
138 extern char *basePath;
139
140 // PRIVATE DATA DEFINITIONS ------------------------------------------------
141
142 static int MobjCount;
143 static mobj_t **MobjList;
144 static int **TargetPlayerAddrs;
145 static int TargetPlayerCount;
146 static byte *SaveBuffer;
147 static boolean SavingPlayers;
148 static union
149 {
150         byte *b;
151         short *w;
152         int *l;
153 } SavePtr;
154 static FILE *SavingFP;
155
156 // This list has been prioritized using frequency estimates
157 static thinkInfo_t ThinkerInfo[] =
158 {
159         {
160                 TC_MOVE_FLOOR,
161                 T_MoveFloor,
162                 MangleSSThinker,
163                 RestoreSSThinker,
164                 sizeof(floormove_t)
165         },
166         {
167                 TC_PLAT_RAISE,
168                 T_PlatRaise,
169                 MangleSSThinker,
170                 RestorePlatRaise,
171                 sizeof(plat_t)
172         },
173         {
174                 TC_MOVE_CEILING,
175                 T_MoveCeiling,
176                 MangleSSThinker,
177                 RestoreMoveCeiling,
178                 sizeof(ceiling_t)
179         },
180         {
181                 TC_LIGHT,
182                 T_Light,
183                 MangleSSThinker,
184                 RestoreSSThinkerNoSD,
185                 sizeof(light_t)
186         },
187         {
188                 TC_VERTICAL_DOOR,
189                 T_VerticalDoor,
190                 MangleSSThinker,
191                 RestoreSSThinker,
192                 sizeof(vldoor_t)
193         },
194         {
195                 TC_PHASE,
196                 T_Phase,
197                 MangleSSThinker,
198                 RestoreSSThinkerNoSD,
199                 sizeof(phase_t)
200         },
201         {
202                 TC_INTERPRET_ACS,
203                 T_InterpretACS,
204                 MangleScript,
205                 RestoreScript,
206                 sizeof(acs_t)
207         },
208         {
209                 TC_ROTATE_POLY,
210                 T_RotatePoly,
211                 NULL,
212                 NULL,
213                 sizeof(polyevent_t)
214         },
215         {
216                 TC_BUILD_PILLAR,
217                 T_BuildPillar,
218                 MangleSSThinker,
219                 RestoreSSThinker,
220                 sizeof(pillar_t)
221         },
222         {
223                 TC_MOVE_POLY,
224                 T_MovePoly,
225                 NULL,
226                 NULL,
227                 sizeof(polyevent_t)
228         },
229         {
230                 TC_POLY_DOOR,
231                 T_PolyDoor,
232                 NULL,
233                 NULL,
234                 sizeof(polydoor_t)
235         },
236         {
237                 TC_FLOOR_WAGGLE,
238                 T_FloorWaggle,
239                 MangleSSThinker,
240                 RestoreSSThinker,
241                 sizeof(floorWaggle_t)
242         },
243         { // Terminator
244                 TC_NULL, NULL, NULL, NULL, 0
245         }
246 };
247
248 // CODE --------------------------------------------------------------------
249
250 //==========================================================================
251 //
252 // SV_SaveGame
253 //
254 //==========================================================================
255
256 void SV_SaveGame(int slot, char *description)
257 {
258         char fileName[100];
259         char versionText[HXS_VERSION_TEXT_LENGTH];
260
261         // Open the output file
262         snprintf(fileName, 100, "%shex6.hxs", basePath);
263         OpenStreamOut(fileName);
264
265         // Write game save description
266         StreamOutBuffer(description, HXS_DESCRIPTION_LENGTH);
267
268         // Write version info
269         memset(versionText, 0, HXS_VERSION_TEXT_LENGTH);
270         strcpy(versionText, HXS_VERSION_TEXT);
271         StreamOutBuffer(versionText, HXS_VERSION_TEXT_LENGTH);
272
273         // Place a header marker
274         StreamOutLong(ASEG_GAME_HEADER);
275
276         // Write current map and difficulty
277         StreamOutByte(gamemap);
278         StreamOutByte(gameskill);
279
280         // Write global script info
281         StreamOutBuffer(WorldVars, sizeof(WorldVars));
282         StreamOutBuffer(ACSStore, sizeof(ACSStore));
283
284         ArchivePlayers();
285
286         // Place a termination marker
287         StreamOutLong(ASEG_END);
288
289         // Close the output file
290         CloseStreamOut();
291
292         // Save out the current map
293         SV_SaveMap(true); // true = save player info
294
295         // Clear all save files at destination slot
296         ClearSaveSlot(slot);
297
298         // Copy base slot to destination slot
299         CopySaveSlot(BASE_SLOT, slot);
300 }
301
302 //==========================================================================
303 //
304 // SV_SaveMap
305 //
306 //==========================================================================
307
308 void SV_SaveMap(boolean savePlayers)
309 {
310         char fileName[100];
311
312         SavingPlayers = savePlayers;
313
314         // Open the output file
315         snprintf(fileName, 100, "%shex6%02d.hxs", basePath, gamemap);
316         OpenStreamOut(fileName);
317
318         // Place a header marker
319         StreamOutLong(ASEG_MAP_HEADER);
320
321         // Write the level timer
322         StreamOutLong(leveltime);
323
324         // Set the mobj archive numbers
325         SetMobjArchiveNums();
326
327         ArchiveWorld();
328         ArchivePolyobjs();
329         ArchiveMobjs();
330         ArchiveThinkers();
331         ArchiveScripts();
332         ArchiveSounds();
333         ArchiveMisc();
334
335         // Place a termination marker
336         StreamOutLong(ASEG_END);
337
338         // Close the output file
339         CloseStreamOut();
340 }
341
342 //==========================================================================
343 //
344 // SV_LoadGame
345 //
346 //==========================================================================
347
348 void SV_LoadGame(int slot)
349 {
350         int i;
351         char fileName[100];
352         player_t playerBackup[MAXPLAYERS];
353         mobj_t *mobj;
354
355         // Copy all needed save files to the base slot
356         if(slot != BASE_SLOT)
357         {
358                 ClearSaveSlot(BASE_SLOT);
359                 CopySaveSlot(slot, BASE_SLOT);
360         }
361
362         // Create the name
363         snprintf(fileName, 100, "%shex6.hxs", basePath);
364
365         // Load the file
366         M_ReadFile(fileName, &SaveBuffer);
367
368         // Set the save pointer and skip the description field
369         SavePtr.b = SaveBuffer+HXS_DESCRIPTION_LENGTH;
370
371         // Check the version text
372         if(strcmp(SavePtr.b, HXS_VERSION_TEXT))
373         { // Bad version
374                 return;
375         }
376         SavePtr.b += HXS_VERSION_TEXT_LENGTH;
377
378         AssertSegment(ASEG_GAME_HEADER);
379
380         gameepisode = 1;
381         gamemap = GET_BYTE;
382         gameskill = GET_BYTE;
383
384         // Read global script info
385         memcpy(WorldVars, SavePtr.b, sizeof(WorldVars));
386         SavePtr.b += sizeof(WorldVars);
387         memcpy(ACSStore, SavePtr.b, sizeof(ACSStore));
388         SavePtr.b += sizeof(ACSStore);
389
390         // Read the player structures
391         UnarchivePlayers();
392
393         AssertSegment(ASEG_END);
394
395         Z_Free(SaveBuffer);
396
397         // Save player structs
398         for(i = 0; i < MAXPLAYERS; i++)
399         {
400                 playerBackup[i] = players[i];
401         }
402
403         // Load the current map
404         SV_LoadMap();
405
406         // Don't need the player mobj relocation info for load game
407         Z_Free(TargetPlayerAddrs);
408
409         // Restore player structs
410         inv_ptr = 0;
411         curpos = 0;
412         for(i = 0; i < MAXPLAYERS; i++)
413         {
414                 mobj = players[i].mo;
415                 players[i] = playerBackup[i];
416                 players[i].mo = mobj;
417                 if(i == consoleplayer)
418                 {
419                         players[i].readyArtifact = players[i].inventory[inv_ptr].type;
420                 }
421         }
422 }
423
424 //==========================================================================
425 //
426 // SV_UpdateRebornSlot
427 //
428 // Copies the base slot to the reborn slot.
429 //
430 //==========================================================================
431
432 void SV_UpdateRebornSlot(void)
433 {
434         ClearSaveSlot(REBORN_SLOT);
435         CopySaveSlot(BASE_SLOT, REBORN_SLOT);
436 }
437
438 //==========================================================================
439 //
440 // SV_ClearRebornSlot
441 //
442 //==========================================================================
443
444 void SV_ClearRebornSlot(void)
445 {
446         ClearSaveSlot(REBORN_SLOT);
447 }
448
449 //==========================================================================
450 //
451 // SV_MapTeleport
452 //
453 //==========================================================================
454
455 void SV_MapTeleport(int map, int position)
456 {
457         int i;
458         int j;
459         char fileName[100];
460         player_t playerBackup[MAXPLAYERS];
461         mobj_t *targetPlayerMobj;
462         mobj_t *mobj;
463         int inventoryPtr;
464         int currentInvPos;
465         boolean rClass;
466         boolean playerWasReborn;
467         boolean oldWeaponowned[NUMWEAPONS];
468         int oldKeys   = 0; /* jim added initialiser */
469         int oldPieces = 0; /* jim added initialiser */
470         int bestWeapon;
471
472         if(!deathmatch)
473         {
474                 if(P_GetMapCluster(gamemap) == P_GetMapCluster(map))
475                 { // Same cluster - save map without saving player mobjs
476                         SV_SaveMap(false);
477                 }
478                 else
479                 { // Entering new cluster - clear base slot
480                         ClearSaveSlot(BASE_SLOT);
481                 }
482         }
483
484         // Store player structs for later
485         rClass = randomclass;
486         randomclass = false;
487         for(i = 0; i < MAXPLAYERS; i++)
488         {
489                 playerBackup[i] = players[i];
490         }
491
492         // Save some globals that get trashed during the load
493         inventoryPtr = inv_ptr;
494         currentInvPos = curpos;
495
496         // Only SV_LoadMap() uses TargetPlayerAddrs, so it's NULLed here
497         // for the following check (player mobj redirection)
498         TargetPlayerAddrs = NULL;
499
500         gamemap = map;
501         snprintf(fileName, 100, "%shex6%02d.hxs", basePath, gamemap);
502         if(!deathmatch && ExistingFile(fileName))
503         { // Unarchive map
504                 SV_LoadMap();
505         }
506         else
507         { // New map
508                 G_InitNew(gameskill, gameepisode, gamemap);
509
510                 // Destroy all freshly spawned players
511                 for(i = 0; i < MAXPLAYERS; i++)
512                 {
513                         if(playeringame[i])
514                         {
515                                 P_RemoveMobj(players[i].mo);
516                         }
517                 }
518         }
519
520         // Restore player structs
521         targetPlayerMobj = NULL;
522         for(i = 0; i < MAXPLAYERS; i++)
523         {
524                 if(!playeringame[i])
525                 {
526                         continue;
527                 }
528                 players[i] = playerBackup[i];
529                 P_ClearMessage(&players[i]);
530                 players[i].attacker = NULL;
531                 players[i].poisoner = NULL;
532
533                 if(netgame)
534                 {
535                         if(players[i].playerstate == PST_DEAD)
536                         { // In a network game, force all players to be alive
537                                 players[i].playerstate = PST_REBORN;
538                         }
539                         if(!deathmatch)
540                         { // Cooperative net-play, retain keys and weapons
541                                 oldKeys = players[i].keys;
542                                 oldPieces = players[i].pieces;
543                                 for(j = 0; j < NUMWEAPONS; j++)
544                                 {
545                                         oldWeaponowned[j] = players[i].weaponowned[j];
546                                 }
547                         }
548                 }
549                 playerWasReborn = (players[i].playerstate == PST_REBORN);
550                 if(deathmatch)
551                 {
552                         memset(players[i].frags, 0, sizeof(players[i].frags));
553                         mobj = P_SpawnMobj(playerstarts[0][i].x<<16,
554                                 playerstarts[0][i].y<<16, 0, MT_PLAYER_FIGHTER);
555                         players[i].mo = mobj;
556                         G_DeathMatchSpawnPlayer(i);
557                         P_RemoveMobj(mobj);
558                 }
559                 else
560                 {
561                         P_SpawnPlayer(&playerstarts[position][i]);
562                 }
563
564                 if(playerWasReborn && netgame && !deathmatch)
565                 { // Restore keys and weapons when reborn in co-op
566                         players[i].keys = oldKeys;
567                         players[i].pieces = oldPieces;
568                         for(bestWeapon = 0, j = 0; j < NUMWEAPONS; j++)
569                         {
570                                 if(oldWeaponowned[j])
571                                 {
572                                         bestWeapon = j;
573                                         players[i].weaponowned[j] = true;
574                                 }
575                         }
576                         players[i].mana[MANA_1] = 25;
577                         players[i].mana[MANA_2] = 25;
578                         if(bestWeapon)
579                         { // Bring up the best weapon
580                                 players[i].pendingweapon = bestWeapon;
581                         }
582                 }
583
584                 if(targetPlayerMobj == NULL)
585                 { // The poor sap
586                         targetPlayerMobj = players[i].mo;
587                 }
588         }
589         randomclass = rClass;
590
591         // Redirect anything targeting a player mobj
592         if(TargetPlayerAddrs)
593         {
594                 for(i = 0; i < TargetPlayerCount; i++)
595                 {
596                         *TargetPlayerAddrs[i] = (int)targetPlayerMobj;
597                 }
598                 Z_Free(TargetPlayerAddrs);
599         }
600
601         // Destroy all things touching players
602         for(i = 0; i < MAXPLAYERS; i++)
603         {
604                 if(playeringame[i])
605                 {
606                         P_TeleportMove(players[i].mo, players[i].mo->x,
607                                 players[i].mo->y);
608                 }
609         }
610
611         // Restore trashed globals
612         inv_ptr = inventoryPtr;
613         curpos = currentInvPos;
614
615         // Launch waiting scripts
616         if(!deathmatch)
617         {
618                 P_CheckACSStore();
619         }
620
621         // For single play, save immediately into the reborn slot
622         if(!netgame)
623         {
624                 SV_SaveGame(REBORN_SLOT, REBORN_DESCRIPTION);
625         }
626 }
627
628 //==========================================================================
629 //
630 // SV_GetRebornSlot
631 //
632 //==========================================================================
633
634 int SV_GetRebornSlot(void)
635 {
636         return(REBORN_SLOT);
637 }
638
639 //==========================================================================
640 //
641 // SV_RebornSlotAvailable
642 //
643 // Returns true if the reborn slot is available.
644 //
645 //==========================================================================
646
647 boolean SV_RebornSlotAvailable(void)
648 {
649         char fileName[100];
650
651         snprintf(fileName, 100, "%shex%d.hxs", basePath, REBORN_SLOT);
652         return ExistingFile(fileName);
653 }
654
655 //==========================================================================
656 //
657 // SV_LoadMap
658 //
659 //==========================================================================
660
661 void SV_LoadMap(void)
662 {
663         char fileName[100];
664
665         // Load a base level
666         G_InitNew(gameskill, gameepisode, gamemap);
667
668         // Remove all thinkers
669         RemoveAllThinkers();
670
671         // Create the name
672         snprintf(fileName, 100, "%shex6%02d.hxs", basePath, gamemap);
673
674         // Load the file
675         M_ReadFile(fileName, &SaveBuffer);
676         SavePtr.b = SaveBuffer;
677
678         AssertSegment(ASEG_MAP_HEADER);
679
680         // Read the level timer
681         leveltime = GET_LONG;
682
683         UnarchiveWorld();
684         UnarchivePolyobjs();
685         UnarchiveMobjs();
686         UnarchiveThinkers();
687         UnarchiveScripts();
688         UnarchiveSounds();
689         UnarchiveMisc();
690
691         AssertSegment(ASEG_END);
692
693         // Free mobj list and save buffer
694         Z_Free(MobjList);
695         Z_Free(SaveBuffer);
696 }
697
698 //==========================================================================
699 //
700 // SV_InitBaseSlot
701 //
702 //==========================================================================
703
704 void SV_InitBaseSlot(void)
705 {
706         ClearSaveSlot(BASE_SLOT);
707 }
708
709 //==========================================================================
710 //
711 // ArchivePlayers
712 //
713 //==========================================================================
714
715 static void ArchivePlayers(void)
716 {
717         int i;
718         int j;
719         player_t tempPlayer;
720
721         StreamOutLong(ASEG_PLAYERS);
722         for(i = 0; i < MAXPLAYERS; i++)
723         {
724                 StreamOutByte(playeringame[i]);
725         }
726         for(i = 0; i < MAXPLAYERS; i++)
727         {
728                 if(!playeringame[i])
729                 {
730                         continue;
731                 }
732                 StreamOutByte(PlayerClass[i]);
733                 tempPlayer = players[i];
734                 for(j = 0; j < NUMPSPRITES; j++)
735                 {
736                         if(tempPlayer.psprites[j].state)
737                         {
738                                 tempPlayer.psprites[j].state =
739                                         (state_t *)(tempPlayer.psprites[j].state-states);
740                         }
741                 }
742                 StreamOutBuffer(&tempPlayer, sizeof(player_t));
743         }
744 }
745
746 //==========================================================================
747 //
748 // UnarchivePlayers
749 //
750 //==========================================================================
751
752 static void UnarchivePlayers(void)
753 {
754         int i, j;
755
756         AssertSegment(ASEG_PLAYERS);
757         for(i = 0; i < MAXPLAYERS; i++)
758         {
759                 playeringame[i] = GET_BYTE;
760         }
761         for(i = 0; i < MAXPLAYERS; i++)
762         {
763                 if(!playeringame[i])
764                 {
765                         continue;
766                 }
767                 PlayerClass[i] = GET_BYTE;
768                 memcpy(&players[i], SavePtr.b, sizeof(player_t));
769                 SavePtr.b += sizeof(player_t);
770                 players[i].mo = NULL; // Will be set when unarc thinker
771                 P_ClearMessage(&players[i]);
772                 players[i].attacker = NULL;
773                 players[i].poisoner = NULL;
774                 for(j = 0; j < NUMPSPRITES; j++)
775                 {
776                         if(players[i].psprites[j].state)
777                         {
778                                 players[i].psprites[j].state =
779                                         &states[(int)players[i].psprites[j].state];
780                         }
781                 }
782         }
783 }
784
785 //==========================================================================
786 //
787 // ArchiveWorld
788 //
789 //==========================================================================
790
791 static void ArchiveWorld(void)
792 {
793         int i;
794         int j;
795         sector_t *sec;
796         line_t *li;
797         side_t *si;
798
799         StreamOutLong(ASEG_WORLD);
800         for(i = 0, sec = sectors; i < numsectors; i++, sec++)
801         {
802                 StreamOutWord(sec->floorheight>>FRACBITS);
803                 StreamOutWord(sec->ceilingheight>>FRACBITS);
804                 StreamOutWord(sec->floorpic);
805                 StreamOutWord(sec->ceilingpic);
806                 StreamOutWord(sec->lightlevel);
807                 StreamOutWord(sec->special);
808                 StreamOutWord(sec->tag);
809                 StreamOutWord(sec->seqType);
810         }
811         for(i = 0, li = lines; i < numlines; i++, li++)
812         {
813                 StreamOutWord(li->flags);
814                 StreamOutByte(li->special);
815                 StreamOutByte(li->arg1);
816                 StreamOutByte(li->arg2);
817                 StreamOutByte(li->arg3);
818                 StreamOutByte(li->arg4);
819                 StreamOutByte(li->arg5);
820                 for(j = 0; j < 2; j++)
821                 {
822                         if(li->sidenum[j] == -1)
823                         {
824                                 continue;
825                         }
826                         si = &sides[li->sidenum[j]];
827                         StreamOutWord(si->textureoffset>>FRACBITS);
828                         StreamOutWord(si->rowoffset>>FRACBITS);
829                         StreamOutWord(si->toptexture);
830                         StreamOutWord(si->bottomtexture);
831                         StreamOutWord(si->midtexture);
832                 }
833         }
834 }
835
836 //==========================================================================
837 //
838 // UnarchiveWorld
839 //
840 //==========================================================================
841
842 static void UnarchiveWorld(void)
843 {
844         int i;
845         int j;
846         sector_t *sec;
847         line_t *li;
848         side_t *si;
849
850         AssertSegment(ASEG_WORLD);
851         for(i = 0, sec = sectors; i < numsectors; i++, sec++)
852         {
853                 sec->floorheight = GET_WORD<<FRACBITS;
854                 sec->ceilingheight = GET_WORD<<FRACBITS;
855                 sec->floorpic = GET_WORD;
856                 sec->ceilingpic = GET_WORD;
857                 sec->lightlevel = GET_WORD;
858                 sec->special = GET_WORD;
859                 sec->tag = GET_WORD;
860                 sec->seqType = GET_WORD;
861                 sec->specialdata = 0;
862                 sec->soundtarget = 0;
863         }
864         for(i = 0, li = lines; i < numlines; i++, li++)
865         {
866                 li->flags = GET_WORD;
867                 li->special = GET_BYTE;
868                 li->arg1 = GET_BYTE;
869                 li->arg2 = GET_BYTE;
870                 li->arg3 = GET_BYTE;
871                 li->arg4 = GET_BYTE;
872                 li->arg5 = GET_BYTE;
873                 for(j = 0; j < 2; j++)
874                 {
875                         if(li->sidenum[j] == -1)
876                         {
877                                 continue;
878                         }
879                         si = &sides[li->sidenum[j]];
880                         si->textureoffset = GET_WORD<<FRACBITS;
881                         si->rowoffset = GET_WORD<<FRACBITS;
882                         si->toptexture = GET_WORD;
883                         si->bottomtexture = GET_WORD;
884                         si->midtexture = GET_WORD;
885                 }
886         }
887 }
888
889 //==========================================================================
890 //
891 // SetMobjArchiveNums
892 //
893 // Sets the archive numbers in all mobj structs.  Also sets the MobjCount
894 // global.  Ignores player mobjs if SavingPlayers is false.
895 //
896 //==========================================================================
897
898 static void SetMobjArchiveNums(void)
899 {
900         mobj_t *mobj;
901         thinker_t *thinker;
902
903         MobjCount = 0;
904         for(thinker = thinkercap.next; thinker != &thinkercap;
905                 thinker = thinker->next)
906         {
907                 if(thinker->function == P_MobjThinker)
908                 {
909                         mobj = (mobj_t *)thinker;
910                         if(mobj->player && !SavingPlayers)
911                         { // Skipping player mobjs
912                                 continue;
913                         }
914                         mobj->archiveNum = MobjCount++;
915                 }
916         }
917 }
918
919 //==========================================================================
920 //
921 // ArchiveMobjs
922 //
923 //==========================================================================
924
925 static void ArchiveMobjs(void)
926 {
927         int count;
928         thinker_t *thinker;
929         mobj_t tempMobj;
930
931         StreamOutLong(ASEG_MOBJS);
932         StreamOutLong(MobjCount);
933         count = 0;
934         for(thinker = thinkercap.next; thinker != &thinkercap;
935                 thinker = thinker->next)
936         {
937                 if(thinker->function != P_MobjThinker)
938                 { // Not a mobj thinker
939                         continue;
940                 }
941                 if(((mobj_t *)thinker)->player && !SavingPlayers)
942                 { // Skipping player mobjs
943                         continue;
944                 }
945                 count++;
946                 memcpy(&tempMobj, thinker, sizeof(mobj_t));
947                 MangleMobj(&tempMobj);
948                 StreamOutBuffer(&tempMobj, sizeof(mobj_t));
949         }
950         if(count != MobjCount)
951         {
952                 I_Error("ArchiveMobjs: bad mobj count");
953         }
954 }
955
956 //==========================================================================
957 //
958 // UnarchiveMobjs
959 //
960 //==========================================================================
961
962 static void UnarchiveMobjs(void)
963 {
964         int i;
965         mobj_t *mobj;
966
967         AssertSegment(ASEG_MOBJS);
968         TargetPlayerAddrs = Z_Malloc(MAX_TARGET_PLAYERS*sizeof(int *),
969                 PU_STATIC, NULL);
970         TargetPlayerCount = 0;
971         MobjCount = GET_LONG;
972         MobjList = Z_Malloc(MobjCount*sizeof(mobj_t *), PU_STATIC, NULL);
973         for(i = 0; i < MobjCount; i++)
974         {
975                 MobjList[i] = Z_Malloc(sizeof(mobj_t), PU_LEVEL, NULL);
976         }
977         for(i = 0; i < MobjCount; i++)
978         {
979                 mobj = MobjList[i];
980                 memcpy(mobj, SavePtr.b, sizeof(mobj_t));
981                 SavePtr.b += sizeof(mobj_t);
982                 mobj->thinker.function = P_MobjThinker;
983                 RestoreMobj(mobj);
984                 P_AddThinker(&mobj->thinker);
985         }
986         P_CreateTIDList();
987         P_InitCreatureCorpseQueue(true); // true = scan for corpses
988 }
989
990 //==========================================================================
991 //
992 // MangleMobj
993 //
994 //==========================================================================
995
996 static void MangleMobj(mobj_t *mobj)
997 {
998         boolean corpse;
999
1000         corpse = mobj->flags&MF_CORPSE;
1001         mobj->state = (state_t *)(mobj->state-states);
1002         if(mobj->player)
1003         {
1004                 mobj->player = (player_t *)((mobj->player-players)+1);
1005         }
1006         if(corpse)
1007         {
1008                 mobj->target = (mobj_t *)MOBJ_NULL;
1009         }
1010         else
1011         {
1012                 mobj->target = (mobj_t *)GetMobjNum(mobj->target);
1013         }
1014         switch(mobj->type)
1015         {
1016                 // Just special1
1017                 case MT_BISH_FX:
1018                 case MT_HOLY_FX:
1019                 case MT_DRAGON:
1020                 case MT_THRUSTFLOOR_UP:
1021                 case MT_THRUSTFLOOR_DOWN:
1022                 case MT_MINOTAUR:
1023                 case MT_SORCFX1:
1024                 case MT_MSTAFF_FX2:
1025                         if(corpse)
1026                         {
1027                                 mobj->special1 = MOBJ_NULL;
1028                         }
1029                         else
1030                         {
1031                                 mobj->special1 = GetMobjNum((mobj_t *)mobj->special1);
1032                         }
1033                         break;
1034
1035                 // Just special2
1036                 case MT_LIGHTNING_FLOOR:
1037                 case MT_LIGHTNING_ZAP:
1038                         if(corpse)
1039                         {
1040                                 mobj->special2 = MOBJ_NULL;
1041                         }
1042                         else
1043                         {
1044                                 mobj->special2 = GetMobjNum((mobj_t *)mobj->special2);
1045                         }
1046                         break;
1047
1048                 // Both special1 and special2
1049                 case MT_HOLY_TAIL:
1050                 case MT_LIGHTNING_CEILING:
1051                         if(corpse)
1052                         {
1053                                 mobj->special1 = MOBJ_NULL;
1054                                 mobj->special2 = MOBJ_NULL;
1055                         }
1056                         else
1057                         {
1058                                 mobj->special1 = GetMobjNum((mobj_t *)mobj->special1);
1059                                 mobj->special2 = GetMobjNum((mobj_t *)mobj->special2);
1060                         }
1061                         break;
1062
1063                 // Miscellaneous
1064                 case MT_KORAX:
1065                         mobj->special1 = 0; // Searching index
1066                         break;
1067
1068                 default:
1069                         break;
1070         }
1071 }
1072
1073 //==========================================================================
1074 //
1075 // GetMobjNum
1076 //
1077 //==========================================================================
1078
1079 static int GetMobjNum(mobj_t *mobj)
1080 {
1081         if(mobj == NULL)
1082         {
1083                 return MOBJ_NULL;
1084         }
1085         if(mobj->player && !SavingPlayers)
1086         {
1087                 return MOBJ_XX_PLAYER;
1088         }
1089         return mobj->archiveNum;
1090 }
1091
1092 //==========================================================================
1093 //
1094 // RestoreMobj
1095 //
1096 //==========================================================================
1097
1098 static void RestoreMobj(mobj_t *mobj)
1099 {
1100         mobj->state = &states[(int)mobj->state];
1101         if(mobj->player)
1102         {
1103                 mobj->player = &players[(int)mobj->player-1];
1104                 mobj->player->mo = mobj;
1105         }
1106         P_SetThingPosition(mobj);
1107         mobj->info = &mobjinfo[mobj->type];
1108         mobj->floorz = mobj->subsector->sector->floorheight;
1109         mobj->ceilingz = mobj->subsector->sector->ceilingheight;
1110         SetMobjPtr((int *)&mobj->target);
1111         switch(mobj->type)
1112         {
1113                 // Just special1
1114                 case MT_BISH_FX:
1115                 case MT_HOLY_FX:
1116                 case MT_DRAGON:
1117                 case MT_THRUSTFLOOR_UP:
1118                 case MT_THRUSTFLOOR_DOWN:
1119                 case MT_MINOTAUR:
1120                 case MT_SORCFX1:
1121                         SetMobjPtr(&mobj->special1);
1122                         break;
1123
1124                 // Just special2
1125                 case MT_LIGHTNING_FLOOR:
1126                 case MT_LIGHTNING_ZAP:
1127                         SetMobjPtr(&mobj->special2);
1128                         break;
1129
1130                 // Both special1 and special2
1131                 case MT_HOLY_TAIL:
1132                 case MT_LIGHTNING_CEILING:
1133                         SetMobjPtr(&mobj->special1);
1134                         SetMobjPtr(&mobj->special2);
1135                         break;
1136
1137                 default:
1138                         break;
1139         }
1140 }
1141
1142 //==========================================================================
1143 //
1144 // SetMobjPtr
1145 //
1146 //==========================================================================
1147
1148 static void SetMobjPtr(int *archiveNum)
1149 {
1150         if(*archiveNum == MOBJ_NULL)
1151         {
1152                 *archiveNum = 0;
1153                 return;
1154         }
1155         if(*archiveNum == MOBJ_XX_PLAYER)
1156         {
1157                 if(TargetPlayerCount == MAX_TARGET_PLAYERS)
1158                 {
1159                         I_Error("RestoreMobj: exceeded MAX_TARGET_PLAYERS");
1160                 }
1161                 TargetPlayerAddrs[TargetPlayerCount++] = archiveNum;
1162                 *archiveNum = 0;
1163                 return;
1164         }
1165         *archiveNum = (int)MobjList[*archiveNum];
1166 }
1167
1168 //==========================================================================
1169 //
1170 // ArchiveThinkers
1171 //
1172 //==========================================================================
1173
1174 static void ArchiveThinkers(void)
1175 {
1176         thinker_t *thinker;
1177         thinkInfo_t *info;
1178         byte buffer[MAX_THINKER_SIZE];
1179
1180         StreamOutLong(ASEG_THINKERS);
1181         for(thinker = thinkercap.next; thinker != &thinkercap;
1182                 thinker = thinker->next)
1183         {
1184                 for(info = ThinkerInfo; info->tClass != TC_NULL; info++)
1185                 {
1186                         if(thinker->function == info->thinkerFunc)
1187                         {
1188                                 StreamOutByte(info->tClass);
1189                                 memcpy(buffer, thinker, info->size);
1190                                 if(info->mangleFunc)
1191                                 {
1192                                         info->mangleFunc(buffer);
1193                                 }
1194                                 StreamOutBuffer(buffer, info->size);
1195                                 break;
1196                         }
1197                 }
1198         }
1199         // Add a termination marker
1200         StreamOutByte(TC_NULL);
1201 }
1202
1203 //==========================================================================
1204 //
1205 // UnarchiveThinkers
1206 //
1207 //==========================================================================
1208
1209 static void UnarchiveThinkers(void)
1210 {
1211         int tClass;
1212         thinker_t *thinker;
1213         thinkInfo_t *info;
1214
1215         AssertSegment(ASEG_THINKERS);
1216         while((tClass = GET_BYTE) != TC_NULL)
1217         {
1218                 for(info = ThinkerInfo; info->tClass != TC_NULL; info++)
1219                 {
1220                         if(tClass == info->tClass)
1221                         {
1222                                 thinker = Z_Malloc(info->size, PU_LEVEL, NULL);
1223                                 memcpy(thinker, SavePtr.b, info->size);
1224                                 SavePtr.b += info->size;
1225                                 thinker->function = info->thinkerFunc;
1226                                 if(info->restoreFunc)
1227                                 {
1228                                         info->restoreFunc(thinker);
1229                                 }
1230                                 P_AddThinker(thinker);
1231                                 break;
1232                         }
1233                 }
1234                 if(info->tClass == TC_NULL)
1235                 {
1236                         I_Error("UnarchiveThinkers: Unknown tClass %d in "
1237                                 "savegame", tClass);
1238                 }
1239         }
1240 }
1241
1242 //==========================================================================
1243 //
1244 // MangleSSThinker
1245 //
1246 //==========================================================================
1247
1248 static void MangleSSThinker(ssthinker_t *sst)
1249 {
1250         sst->sector = (sector_t *)(sst->sector-sectors);
1251 }
1252
1253 //==========================================================================
1254 //
1255 // RestoreSSThinker
1256 //
1257 //==========================================================================
1258
1259 static void RestoreSSThinker(ssthinker_t *sst)
1260 {
1261         sst->sector = &sectors[(int)sst->sector];
1262         sst->sector->specialdata = sst->thinker.function;
1263 }
1264
1265 //==========================================================================
1266 //
1267 // RestoreSSThinkerNoSD
1268 //
1269 //==========================================================================
1270
1271 static void RestoreSSThinkerNoSD(ssthinker_t *sst)
1272 {
1273         sst->sector = &sectors[(int)sst->sector];
1274 }
1275
1276 //==========================================================================
1277 //
1278 // MangleScript
1279 //
1280 //==========================================================================
1281
1282 static void MangleScript(acs_t *script)
1283 {
1284         script->ip = (int *)((int)(script->ip)-(int)ActionCodeBase);
1285         script->line = script->line ?
1286                 (line_t *)(script->line-lines) : (line_t *)-1;
1287         script->activator = (mobj_t *)GetMobjNum(script->activator);
1288 }
1289
1290 //==========================================================================
1291 //
1292 // RestoreScript
1293 //
1294 //==========================================================================
1295
1296 static void RestoreScript(acs_t *script)
1297 {
1298         script->ip = (int *)(ActionCodeBase+(int)script->ip);
1299         if((int)script->line == -1)
1300         {
1301                 script->line = NULL;
1302         }
1303         else
1304         {
1305                 script->line = &lines[(int)script->line];
1306         }
1307         SetMobjPtr((int *)&script->activator);
1308 }
1309
1310 //==========================================================================
1311 //
1312 // RestorePlatRaise
1313 //
1314 //==========================================================================
1315
1316 static void RestorePlatRaise(plat_t *plat)
1317 {
1318         plat->sector = &sectors[(int)plat->sector];
1319         plat->sector->specialdata = T_PlatRaise;
1320         P_AddActivePlat(plat);
1321 }
1322
1323 //==========================================================================
1324 //
1325 // RestoreMoveCeiling
1326 //
1327 //==========================================================================
1328
1329 static void RestoreMoveCeiling(ceiling_t *ceiling)
1330 {
1331         ceiling->sector = &sectors[(int)ceiling->sector];
1332         ceiling->sector->specialdata = T_MoveCeiling;
1333         P_AddActiveCeiling(ceiling);
1334 }
1335
1336 //==========================================================================
1337 //
1338 // ArchiveScripts
1339 //
1340 //==========================================================================
1341
1342 static void ArchiveScripts(void)
1343 {
1344         int i;
1345
1346         StreamOutLong(ASEG_SCRIPTS);
1347         for(i = 0; i < ACScriptCount; i++)
1348         {
1349                 StreamOutWord(ACSInfo[i].state);
1350                 StreamOutWord(ACSInfo[i].waitValue);
1351         }
1352         StreamOutBuffer(MapVars, sizeof(MapVars));
1353 }
1354
1355 //==========================================================================
1356 //
1357 // UnarchiveScripts
1358 //
1359 //==========================================================================
1360
1361 static void UnarchiveScripts(void)
1362 {
1363         int i;
1364
1365         AssertSegment(ASEG_SCRIPTS);
1366         for(i = 0; i < ACScriptCount; i++)
1367         {
1368                 ACSInfo[i].state = GET_WORD;
1369                 ACSInfo[i].waitValue = GET_WORD;
1370         }
1371         memcpy(MapVars, SavePtr.b, sizeof(MapVars));
1372         SavePtr.b += sizeof(MapVars);
1373 }
1374
1375 //==========================================================================
1376 //
1377 // ArchiveMisc
1378 //
1379 //==========================================================================
1380
1381 static void ArchiveMisc(void)
1382 {
1383         int ix;
1384
1385         StreamOutLong(ASEG_MISC);
1386         for (ix=0; ix<MAXPLAYERS; ix++)
1387         {
1388                 StreamOutLong(localQuakeHappening[ix]);
1389         }
1390 }
1391
1392 //==========================================================================
1393 //
1394 // UnarchiveMisc
1395 //
1396 //==========================================================================
1397
1398 static void UnarchiveMisc(void)
1399 {
1400         int ix;
1401
1402         AssertSegment(ASEG_MISC);
1403         for (ix=0; ix<MAXPLAYERS; ix++)
1404         {
1405                 localQuakeHappening[ix] = GET_LONG;
1406         }
1407 }
1408
1409 //==========================================================================
1410 //
1411 // RemoveAllThinkers
1412 //
1413 //==========================================================================
1414
1415 static void RemoveAllThinkers(void)
1416 {
1417         thinker_t *thinker;
1418         thinker_t *nextThinker;
1419
1420         thinker = thinkercap.next;
1421         while(thinker != &thinkercap)
1422         {
1423                 nextThinker = thinker->next;
1424                 if(thinker->function == P_MobjThinker)
1425                 {
1426                         P_RemoveMobj((mobj_t *)thinker);
1427                 }
1428                 else
1429                 {
1430                         Z_Free(thinker);
1431                 }
1432                 thinker = nextThinker;
1433         }
1434         P_InitThinkers();
1435 }
1436
1437 //==========================================================================
1438 //
1439 // ArchiveSounds
1440 //
1441 //==========================================================================
1442
1443 static void ArchiveSounds(void)
1444 {
1445         seqnode_t *node;
1446         sector_t *sec;
1447         int difference;
1448         int i;
1449
1450         StreamOutLong(ASEG_SOUNDS);
1451
1452         // Save the sound sequences
1453         StreamOutLong(ActiveSequences);
1454         for(node = SequenceListHead; node; node = node->next)
1455         {
1456                 StreamOutLong(node->sequence);
1457                 StreamOutLong(node->delayTics);
1458                 StreamOutLong(node->volume);
1459                 StreamOutLong(SN_GetSequenceOffset(node->sequence,
1460                         node->sequencePtr));
1461                 StreamOutLong(node->currentSoundID);
1462                 for(i = 0; i < po_NumPolyobjs; i++)
1463                 {
1464                         if(node->mobj == (mobj_t *)&polyobjs[i].startSpot)
1465                         {
1466                                 break;
1467                         }
1468                 }
1469                 if(i == po_NumPolyobjs)
1470                 { // Sound is attached to a sector, not a polyobj
1471                         sec = R_PointInSubsector(node->mobj->x, node->mobj->y)->sector;
1472                         difference = (int)((byte *)sec
1473                                 -(byte *)&sectors[0])/sizeof(sector_t);
1474                         StreamOutLong(0); // 0 -- sector sound origin
1475                 }
1476                 else
1477                 {
1478                         StreamOutLong(1); // 1 -- polyobj sound origin
1479                         difference = i;
1480                 }
1481                 StreamOutLong(difference);
1482         }
1483 }
1484
1485 //==========================================================================
1486 //
1487 // UnarchiveSounds
1488 //
1489 //==========================================================================
1490
1491 static void UnarchiveSounds(void)
1492 {
1493         int i;
1494         int numSequences;
1495         int sequence;
1496         int delayTics;
1497         int volume;
1498         int seqOffset;
1499         int soundID;
1500         int polySnd;
1501         int secNum;
1502         mobj_t *sndMobj;
1503
1504         AssertSegment(ASEG_SOUNDS);
1505
1506         // Reload and restart all sound sequences
1507         numSequences = GET_LONG;
1508         i = 0;
1509         while(i < numSequences)
1510         {
1511                 sequence = GET_LONG;
1512                 delayTics = GET_LONG;
1513                 volume = GET_LONG;
1514                 seqOffset = GET_LONG;
1515
1516                 soundID = GET_LONG;
1517                 polySnd = GET_LONG;
1518                 secNum = GET_LONG;
1519                 if(!polySnd)
1520                 {
1521                         sndMobj = (mobj_t *)&sectors[secNum].soundorg;
1522                 }
1523                 else
1524                 {
1525                         sndMobj = (mobj_t *)&polyobjs[secNum].startSpot;
1526                 }
1527                 SN_StartSequence(sndMobj, sequence);
1528                 SN_ChangeNodeData(i, seqOffset, delayTics, volume, soundID);
1529                 i++;
1530         }
1531 }
1532
1533 //==========================================================================
1534 //
1535 // ArchivePolyobjs
1536 //
1537 //==========================================================================
1538
1539 static void ArchivePolyobjs(void)
1540 {
1541         int i;
1542
1543         StreamOutLong(ASEG_POLYOBJS);
1544         StreamOutLong(po_NumPolyobjs);
1545         for(i = 0; i < po_NumPolyobjs; i++)
1546         {
1547                 StreamOutLong(polyobjs[i].tag);
1548                 StreamOutLong(polyobjs[i].angle);
1549                 StreamOutLong(polyobjs[i].startSpot.x);
1550                 StreamOutLong(polyobjs[i].startSpot.y);
1551         }
1552 }
1553
1554 //==========================================================================
1555 //
1556 // UnarchivePolyobjs
1557 //
1558 //==========================================================================
1559
1560 static void UnarchivePolyobjs(void)
1561 {
1562         int i;
1563         fixed_t deltaX;
1564         fixed_t deltaY;
1565
1566         AssertSegment(ASEG_POLYOBJS);
1567         if(GET_LONG != po_NumPolyobjs)
1568         {
1569                 I_Error("UnarchivePolyobjs: Bad polyobj count");
1570         }
1571         for(i = 0; i < po_NumPolyobjs; i++)
1572         {
1573                 if(GET_LONG != polyobjs[i].tag)
1574                 {
1575                         I_Error("UnarchivePolyobjs: Invalid polyobj tag");
1576                 }
1577                 PO_RotatePolyobj(polyobjs[i].tag, (angle_t)GET_LONG);
1578                 deltaX = GET_LONG-polyobjs[i].startSpot.x;
1579                 deltaY = GET_LONG-polyobjs[i].startSpot.y;
1580                 PO_MovePolyobj(polyobjs[i].tag, deltaX, deltaY);
1581         }
1582 }
1583
1584 //==========================================================================
1585 //
1586 // AssertSegment
1587 //
1588 //==========================================================================
1589
1590 static void AssertSegment(gameArchiveSegment_t segType)
1591 {
1592         if(GET_LONG != segType)
1593         {
1594                 I_Error("Corrupt save game: Segment [%d] failed alignment check",
1595                         segType);
1596         }
1597 }
1598
1599 //==========================================================================
1600 //
1601 // ClearSaveSlot
1602 //
1603 // Deletes all save game files associated with a slot number.
1604 //
1605 //==========================================================================
1606
1607 static void ClearSaveSlot(int slot)
1608 {
1609         int i;
1610         char fileName[100];
1611
1612         for(i = 0; i < MAX_MAPS; i++)
1613         {
1614                 snprintf(fileName, 100, "%shex%d%02d.hxs", basePath, slot, i);
1615                 remove(fileName);
1616         }
1617         snprintf(fileName, 100, "%shex%d.hxs", basePath, slot);
1618         remove(fileName);
1619 }
1620
1621 //==========================================================================
1622 //
1623 // CopySaveSlot
1624 //
1625 // Copies all the save game files from one slot to another.
1626 //
1627 //==========================================================================
1628
1629 static void CopySaveSlot(int sourceSlot, int destSlot)
1630 {
1631         int i;
1632         char sourceName[100];
1633         char destName[100];
1634
1635         for(i = 0; i < MAX_MAPS; i++)
1636         {
1637                 snprintf(sourceName,100, "%shex%d%02d.hxs", basePath,sourceSlot, i);
1638                 if(ExistingFile(sourceName))
1639                 {
1640                         snprintf(destName,100, "%shex%d%02d.hxs", basePath,destSlot, i);
1641                         CopyFile(sourceName, destName);
1642                 }
1643         }
1644         snprintf(sourceName, 100,"%shex%d.hxs", basePath, sourceSlot);
1645         if(ExistingFile(sourceName))
1646         {
1647                 snprintf(destName, 100,"%shex%d.hxs", basePath, destSlot);
1648                 CopyFile(sourceName, destName);
1649         }
1650 }
1651
1652 //==========================================================================
1653 //
1654 // CopyFile
1655 //
1656 //==========================================================================
1657
1658 static void CopyFile(char *sourceName, char *destName)
1659 {
1660         int length;
1661         byte *buffer;
1662
1663         length = M_ReadFile(sourceName, &buffer);
1664         M_WriteFile(destName, buffer, length);
1665         Z_Free(buffer);
1666 }
1667
1668 //==========================================================================
1669 //
1670 // ExistingFile
1671 //
1672 //==========================================================================
1673
1674 static boolean ExistingFile(char *name)
1675 {
1676         FILE *fp;
1677
1678         if((fp = fopen(name, "rb")) != NULL)
1679         {
1680                 fclose(fp);
1681                 return true;
1682         }
1683         else
1684         {
1685                 return false;
1686         }
1687 }
1688
1689 //==========================================================================
1690 //
1691 // OpenStreamOut
1692 //
1693 //==========================================================================
1694
1695 static void OpenStreamOut(char *fileName)
1696 {
1697         SavingFP = fopen(fileName, "wb");
1698 }
1699
1700 //==========================================================================
1701 //
1702 // CloseStreamOut
1703 //
1704 //==========================================================================
1705
1706 static void CloseStreamOut(void)
1707 {
1708         if(SavingFP)
1709         {
1710                 fclose(SavingFP);
1711         }
1712 }
1713
1714 //==========================================================================
1715 //
1716 // StreamOutBuffer
1717 //
1718 //==========================================================================
1719
1720 static void StreamOutBuffer(void *buffer, int size)
1721 {
1722         fwrite(buffer, size, 1, SavingFP);
1723 }
1724
1725 //==========================================================================
1726 //
1727 // StreamOutByte
1728 //
1729 //==========================================================================
1730
1731 static void StreamOutByte(byte val)
1732 {
1733         fwrite(&val, sizeof(byte), 1, SavingFP);
1734 }
1735
1736 //==========================================================================
1737 //
1738 // StreamOutWord
1739 //
1740 //==========================================================================
1741
1742 static void StreamOutWord(unsigned short val)
1743 {
1744         fwrite(&val, sizeof(unsigned short), 1, SavingFP);
1745 }
1746
1747 //==========================================================================
1748 //
1749 // StreamOutLong
1750 //
1751 //==========================================================================
1752
1753 static void StreamOutLong(unsigned int val)
1754 {
1755         fwrite(&val, sizeof(int), 1, SavingFP);
1756 }
1757