//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 1993-2008 Raven Software
// Copyright(C) 2005-2014 Simon Howard
//
// 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.
//


// HEADER FILES ------------------------------------------------------------

#include "h2def.h"
#include "i_system.h"
#include "m_misc.h"
#include "i_swap.h"
#include "p_local.h"
#include "a11y.h"
#include "sv_extsaveg.h" // [crispy] for extended savegame information

// MACROS ------------------------------------------------------------------

#define MAX_TARGET_PLAYERS 512
#define MOBJ_NULL -1
#define MOBJ_XX_PLAYER -2
#define MAX_MAPS 99
#define BASE_SLOT 6
#define REBORN_SLOT 7
#define REBORN_DESCRIPTION "TEMP GAME"
#define MAX_THINKER_SIZE 256

// TYPES -------------------------------------------------------------------

typedef enum
{
    ASEG_GAME_HEADER = 101,
    ASEG_MAP_HEADER,
    ASEG_WORLD,
    ASEG_POLYOBJS,
    ASEG_MOBJS,
    ASEG_THINKERS,
    ASEG_SCRIPTS,
    ASEG_PLAYERS,
    ASEG_SOUNDS,
    ASEG_MISC,
    ASEG_END
} gameArchiveSegment_t;

typedef enum
{
    TC_NULL,
    TC_MOVE_CEILING,
    TC_VERTICAL_DOOR,
    TC_MOVE_FLOOR,
    TC_PLAT_RAISE,
    TC_INTERPRET_ACS,
    TC_FLOOR_WAGGLE,
    TC_LIGHT,
    TC_PHASE,
    TC_BUILD_PILLAR,
    TC_ROTATE_POLY,
    TC_MOVE_POLY,
    TC_POLY_DOOR
} thinkClass_t;

typedef struct
{
    thinkClass_t tClass;
    think_t thinkerFunc;
    void (*writeFunc)(thinker_t *thinker);
    void (*readFunc)(thinker_t *thinker);
    void (*restoreFunc)(thinker_t *thinker);
    size_t size;
} thinkInfo_t;

typedef struct
{
    thinker_t thinker;
    sector_t *sector;
} ssthinker_t;

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

void P_SpawnPlayer(mapthing_t * mthing);

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static void ArchiveWorld(void);
static void UnarchiveWorld(void);
static void ArchivePolyobjs(void);
static void UnarchivePolyobjs(void);
static void ArchiveMobjs(void);
static void UnarchiveMobjs(void);
static void ArchiveThinkers(void);
static void UnarchiveThinkers(void);
static void ArchiveScripts(void);
static void UnarchiveScripts(void);
static void ArchivePlayers(void);
static void UnarchivePlayers(void);
static void ArchiveSounds(void);
static void UnarchiveSounds(void);
static void ArchiveMisc(void);
static void UnarchiveMisc(void);
static void SetMobjArchiveNums(void);
static void RemoveAllThinkers(void);
static int GetMobjNum(mobj_t * mobj);
static void SetMobjPtr(mobj_t **ptr, unsigned int archiveNum);
static void RestoreSSThinker(thinker_t *sst);
static void RestorePlatRaise(thinker_t *thinker);
static void RestoreMoveCeiling(thinker_t *thinker);
static void AssertSegment(gameArchiveSegment_t segType);
static void CopySaveSlot(int sourceSlot, int destSlot);
static void CopyFile(char *sourceName, char *destName);
static boolean ExistingFile(char *name);
static void SV_OpenRead(char *fileName);
static void SV_OpenWrite(char *fileName);
static void SV_Close(void);
static void SV_Read(void *buffer, int size);
static byte SV_ReadByte(void);
static uint16_t SV_ReadWord(void);
static uint32_t SV_ReadLong(void);
static void *SV_ReadPtr(void);
static void SV_Write(const void *buffer, int size);
static void SV_WriteByte(byte val);
static void SV_WriteWord(unsigned short val);
static void SV_WriteLong(unsigned int val);
static void SV_WritePtr(void *ptr);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

#define DEFAULT_SAVEPATH                "hexndata/"

char *SavePath = DEFAULT_SAVEPATH;

int vanilla_savegame_limit = 1;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static int MobjCount;
static mobj_t **MobjList;
static mobj_t ***TargetPlayerAddrs;
static int TargetPlayerCount;
static boolean SavingPlayers;
FILE *SavingFP;

// CODE --------------------------------------------------------------------

// Autogenerated functions for reading/writing structs:

//
// acsstore_t
//

static void StreamIn_acsstore_t(acsstore_t *str)
{
    int i;

    // int map;
    str->map = SV_ReadLong();

    // int script;
    str->script = SV_ReadLong();

    // byte args[4];
    for (i=0; i<4; ++i)
    {
        str->args[i] = SV_ReadByte();
    }
}

static void StreamOut_acsstore_t(acsstore_t *str)
{
    int i;

    // int map;
    SV_WriteLong(str->map);

    // int script;
    SV_WriteLong(str->script);

    // byte args[4];
    for (i=0; i<4; ++i)
    {
        SV_WriteByte(str->args[i]);
    }
}


//
// ticcmd_t
// (this is based on the Vanilla definition of the struct)
//

static void StreamIn_ticcmd_t(ticcmd_t *str)
{
    // char forwardmove;
    str->forwardmove = SV_ReadByte();

    // char sidemove;
    str->sidemove = SV_ReadByte();

    // short angleturn;
    str->angleturn = SV_ReadWord();

    // short consistancy;
    str->consistancy = SV_ReadWord();

    // byte chatchar;
    str->chatchar = SV_ReadByte();

    // byte buttons;
    str->buttons = SV_ReadByte();

    // byte lookfly;
    str->lookfly = SV_ReadByte();

    // byte arti;
    str->arti = SV_ReadByte();
}

static void StreamOut_ticcmd_t(ticcmd_t *str)
{
    // char forwardmove;
    SV_WriteByte(str->forwardmove);

    // char sidemove;
    SV_WriteByte(str->sidemove);

    // short angleturn;
    SV_WriteWord(str->angleturn);

    // short consistancy;
    SV_WriteWord(str->consistancy);

    // byte chatchar;
    SV_WriteByte(str->chatchar);

    // byte buttons;
    SV_WriteByte(str->buttons);

    // byte lookfly;
    SV_WriteByte(str->lookfly);

    // byte arti;
    SV_WriteByte(str->arti);
}



//
// inventory_t
//

static void StreamIn_inventory_t(inventory_t *str)
{
    // int type;
    str->type = SV_ReadLong();

    // int count;
    str->count = SV_ReadLong();
}

static void StreamOut_inventory_t(inventory_t *str)
{
    // int type;
    SV_WriteLong(str->type);

    // int count;
    SV_WriteLong(str->count);
}


//
// pspdef_t
//

static void StreamIn_pspdef_t(pspdef_t *str)
{
    int state_num;

    // state_t *state;

    // This is a pointer; it is stored as an index into the states table.

    state_num = SV_ReadLong();

    if (state_num != 0)
    {
        str->state = states + state_num;
    }
    else
    {
        str->state = NULL;
    }

    // int tics;
    str->tics = SV_ReadLong();

    // fixed_t sx, sy;
    str->sx = SV_ReadLong();
    str->sy = SV_ReadLong();

    // [crispy] variable weapon sprite bob
    str->sx2 = str->sx;
    str->sy2 = str->sy;
}

static void StreamOut_pspdef_t(pspdef_t *str)
{
    // state_t *state;
    // This is a pointer; store the index in the states table,
    // rather than the pointer itself.
    if (str->state != NULL)
    {
        SV_WriteLong(str->state - states);
    }
    else
    {
        SV_WriteLong(0);
    }

    // int tics;
    SV_WriteLong(str->tics);

    // fixed_t sx, sy;
    SV_WriteLong(str->sx);
    SV_WriteLong(str->sy);
}


//
// player_t
//

static void StreamIn_player_t(player_t *str)
{
    int i;

    // mobj_t *mo;
    // Pointer value is reset on load.
    str->mo = SV_ReadPtr();
    str->mo = NULL;

    // playerstate_t playerstate;
    str->playerstate = SV_ReadLong();

    // ticcmd_t cmd;
    StreamIn_ticcmd_t(&str->cmd);

    // pclass_t class;
    str->class = SV_ReadLong();

    // fixed_t viewz;
    str->viewz = SV_ReadLong();

    // fixed_t viewheight;
    str->viewheight = SV_ReadLong();

    // fixed_t deltaviewheight;
    str->deltaviewheight = SV_ReadLong();

    // fixed_t bob;
    str->bob = SV_ReadLong();

    // [crispy] variable player view bob
    str->bob2 = str->bob;

    // int flyheight;
    str->flyheight = SV_ReadLong();

    // int lookdir;
    str->lookdir = SV_ReadLong();

    // boolean centering;
    str->centering = SV_ReadLong();

    // int health;
    str->health = SV_ReadLong();

    // int armorpoints[NUMARMOR];
    for (i=0; i<NUMARMOR; ++i)
    {
        str->armorpoints[i] = SV_ReadLong();
    }

    // inventory_t inventory[NUMINVENTORYSLOTS];
    for (i=0; i<NUMINVENTORYSLOTS; ++i)
    {
        StreamIn_inventory_t(&str->inventory[i]);
    }

    // artitype_t readyArtifact;
    str->readyArtifact = SV_ReadLong();

    // int artifactCount;
    str->artifactCount = SV_ReadLong();

    // int inventorySlotNum;
    str->inventorySlotNum = SV_ReadLong();

    // int powers[NUMPOWERS];
    for (i=0; i<NUMPOWERS; ++i)
    {
        str->powers[i] = SV_ReadLong();
    }

    // int keys;
    str->keys = SV_ReadLong();

    // int pieces;
    str->pieces = SV_ReadLong();

    // signed int frags[MAXPLAYERS];
    for (i=0; i<maxplayers; ++i)
    {
        str->frags[i] = SV_ReadLong();
    }

    // weapontype_t readyweapon;
    str->readyweapon = SV_ReadLong();

    // weapontype_t pendingweapon;
    str->pendingweapon = SV_ReadLong();

    // boolean weaponowned[NUMWEAPONS];
    for (i=0; i<NUMWEAPONS; ++i)
    {
        str->weaponowned[i] = SV_ReadLong();
    }

    // int mana[NUMMANA];
    for (i=0; i<NUMMANA; ++i)
    {
        str->mana[i] = SV_ReadLong();
    }

    // int attackdown, usedown;
    str->attackdown = SV_ReadLong();
    str->usedown = SV_ReadLong();

    // int cheats;
    str->cheats = SV_ReadLong();

    // int refire;
    str->refire = SV_ReadLong();

    // int killcount, itemcount, secretcount;
    str->killcount = SV_ReadLong();
    str->itemcount = SV_ReadLong();
    str->secretcount = SV_ReadLong();

    // char message[80];
    for (i=0; i<80; ++i)
    {
        str->message[i] = SV_ReadByte();
    }

    // int messageTics;
    str->messageTics = SV_ReadLong();

    // short ultimateMessage;
    str->ultimateMessage = SV_ReadWord();

    // short yellowMessage;
    str->yellowMessage = SV_ReadWord();

    // int damagecount, bonuscount;
    str->damagecount = SV_ReadLong();
    str->bonuscount = SV_ReadLong();

    // int poisoncount;
    str->poisoncount = SV_ReadLong();

    // mobj_t *poisoner;
    // Pointer value is reset.
    str->poisoner = SV_ReadPtr();
    str->poisoner = NULL;

    // mobj_t *attacker;
    // Pointer value is reset.
    str->attacker = SV_ReadPtr();
    str->attacker = NULL;

    // int extralight;
    str->extralight = SV_ReadLong();

    // int fixedcolormap;
    str->fixedcolormap = SV_ReadLong();

    // int colormap;
    str->colormap = SV_ReadLong();

    // pspdef_t psprites[NUMPSPRITES];
    for (i=0; i<NUMPSPRITES; ++i)
    {
        StreamIn_pspdef_t(&str->psprites[i]);
    }

    // int morphTics;
    str->morphTics = SV_ReadLong();

    // unsigned int jumpTics;
    str->jumpTics = SV_ReadLong();

    // unsigned int worldTimer;
    str->worldTimer = SV_ReadLong();
}

static void StreamOut_player_t(player_t *str)
{
    int i;

    // mobj_t *mo;
    SV_WritePtr(str->mo);

    // playerstate_t playerstate;
    SV_WriteLong(str->playerstate);

    // ticcmd_t cmd;
    StreamOut_ticcmd_t(&str->cmd);

    // pclass_t class;
    SV_WriteLong(str->class);

    // fixed_t viewz;
    SV_WriteLong(str->viewz);

    // fixed_t viewheight;
    SV_WriteLong(str->viewheight);

    // fixed_t deltaviewheight;
    SV_WriteLong(str->deltaviewheight);

    // fixed_t bob;
    SV_WriteLong(str->bob);

    // int flyheight;
    SV_WriteLong(str->flyheight);

    // int lookdir;
    SV_WriteLong(str->lookdir);

    // boolean centering;
    SV_WriteLong(str->centering);

    // int health;
    SV_WriteLong(str->health);

    // int armorpoints[NUMARMOR];
    for (i=0; i<NUMARMOR; ++i)
    {
        SV_WriteLong(str->armorpoints[i]);
    }

    // inventory_t inventory[NUMINVENTORYSLOTS];
    for (i=0; i<NUMINVENTORYSLOTS; ++i)
    {
        StreamOut_inventory_t(&str->inventory[i]);
    }

    // artitype_t readyArtifact;
    SV_WriteLong(str->readyArtifact);

    // int artifactCount;
    SV_WriteLong(str->artifactCount);

    // int inventorySlotNum;
    SV_WriteLong(str->inventorySlotNum);

    // int powers[NUMPOWERS];
    for (i=0; i<NUMPOWERS; ++i)
    {
        SV_WriteLong(str->powers[i]);
    }

    // int keys;
    SV_WriteLong(str->keys);

    // int pieces;
    SV_WriteLong(str->pieces);

    // signed int frags[MAXPLAYERS];
    for (i=0; i<maxplayers; ++i)
    {
        SV_WriteLong(str->frags[i]);
    }

    // weapontype_t readyweapon;
    SV_WriteLong(str->readyweapon);

    // weapontype_t pendingweapon;
    SV_WriteLong(str->pendingweapon);

    // boolean weaponowned[NUMWEAPONS];
    for (i=0; i<NUMWEAPONS; ++i)
    {
        SV_WriteLong(str->weaponowned[i]);
    }

    // int mana[NUMMANA];
    for (i=0; i<NUMMANA; ++i)
    {
        SV_WriteLong(str->mana[i]);
    }

    // int attackdown, usedown;
    SV_WriteLong(str->attackdown);
    SV_WriteLong(str->usedown);

    // int cheats;
    SV_WriteLong(str->cheats);

    // int refire;
    SV_WriteLong(str->refire);

    // int killcount, itemcount, secretcount;
    SV_WriteLong(str->killcount);
    SV_WriteLong(str->itemcount);
    SV_WriteLong(str->secretcount);

    // char message[80];
    for (i=0; i<80; ++i)
    {
        SV_WriteByte(str->message[i]);
    }

    // int messageTics;
    SV_WriteLong(str->messageTics);

    // short ultimateMessage;
    SV_WriteWord(str->ultimateMessage);

    // short yellowMessage;
    SV_WriteWord(str->yellowMessage);

    // int damagecount, bonuscount;
    SV_WriteLong(str->damagecount);
    SV_WriteLong(str->bonuscount);

    // int poisoncount;
    SV_WriteLong(str->poisoncount);

    // mobj_t *poisoner;
    SV_WritePtr(str->poisoner);

    // mobj_t *attacker;
    SV_WritePtr(str->attacker);

    // int extralight;
    SV_WriteLong(str->extralight);

    // int fixedcolormap;
    SV_WriteLong(str->fixedcolormap);

    // int colormap;
    SV_WriteLong(str->colormap);

    // pspdef_t psprites[NUMPSPRITES];
    for (i=0; i<NUMPSPRITES; ++i)
    {
        StreamOut_pspdef_t(&str->psprites[i]);
    }

    // int morphTics;
    SV_WriteLong(str->morphTics);

    // unsigned int jumpTics;
    SV_WriteLong(str->jumpTics);

    // unsigned int worldTimer;
    SV_WriteLong(str->worldTimer);
}


//
// thinker_t
//

static void StreamIn_thinker_t(thinker_t *str)
{
    // struct thinker_s *prev, *next;
    // Pointers are discarded:
    str->prev = SV_ReadPtr();
    str->prev = NULL;
    str->next = SV_ReadPtr();
    str->next = NULL;

    // think_t function;
    // Function pointer is discarded:
    str->function = SV_ReadPtr();
    str->function = NULL;
}

static void StreamOut_thinker_t(thinker_t *str)
{
    // struct thinker_s *prev, *next;
    SV_WritePtr(str->prev);
    SV_WritePtr(str->next);

    // think_t function;
    SV_WritePtr(&str->function);
}


//
// mobj_t
//

static void StreamInMobjSpecials(mobj_t *mobj)
{
    unsigned int special1, special2;

    special1 = SV_ReadLong();
    special2 = SV_ReadLong();

    mobj->special1.i = special1;
    mobj->special2.i = special2;

    switch (mobj->type)
    {
            // Just special1
        case MT_BISH_FX:
        case MT_HOLY_FX:
        case MT_DRAGON:
        case MT_THRUSTFLOOR_UP:
        case MT_THRUSTFLOOR_DOWN:
        case MT_MINOTAUR:
        case MT_SORCFX1:
            SetMobjPtr(&mobj->special1.m, special1);
            break;

            // Just special2
        case MT_LIGHTNING_FLOOR:
        case MT_LIGHTNING_ZAP:
            SetMobjPtr(&mobj->special2.m, special2);
            break;

            // Both special1 and special2
        case MT_HOLY_TAIL:
        case MT_LIGHTNING_CEILING:
            SetMobjPtr(&mobj->special1.m, special1);
            SetMobjPtr(&mobj->special2.m, special2);
            break;

        default:
            break;
    }
}

static void StreamIn_mobj_t(mobj_t *str)
{
    unsigned int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // fixed_t x, y, z;
    str->x = SV_ReadLong();
    str->y = SV_ReadLong();
    str->z = SV_ReadLong();

    // struct mobj_s *snext, *sprev;
    // Pointer values are discarded:
    str->snext = SV_ReadPtr();
    str->snext = NULL;
    str->sprev = SV_ReadPtr();
    str->sprev = NULL;

    // angle_t angle;
    str->angle = SV_ReadLong();

    // spritenum_t sprite;
    str->sprite = SV_ReadLong();

    // int frame;
    str->frame = SV_ReadLong();

    // struct mobj_s *bnext, *bprev;
    // Values are read but discarded; this will be restored when the thing's
    // position is set.
    str->bnext = SV_ReadPtr();
    str->bnext = NULL;
    str->bprev = SV_ReadPtr();
    str->bprev = NULL;

    // struct subsector_s *subsector;
    // Read but discard: pointer will be restored when thing position is set.
    str->subsector = SV_ReadPtr();
    str->subsector = NULL;

    // fixed_t floorz, ceilingz;
    str->floorz = SV_ReadLong();
    str->ceilingz = SV_ReadLong();

    // fixed_t floorpic;
    str->floorpic = SV_ReadLong();

    // fixed_t radius, height;
    str->radius = SV_ReadLong();
    str->height = SV_ReadLong();

    // fixed_t momx, momy, momz;
    str->momx = SV_ReadLong();
    str->momy = SV_ReadLong();
    str->momz = SV_ReadLong();

    // int validcount;
    str->validcount = SV_ReadLong();

    // mobjtype_t type;
    str->type = SV_ReadLong();

    // mobjinfo_t *info;
    // Pointer value is read but discarded.
    str->info = SV_ReadPtr();
    str->info = NULL;

    // int tics;
    str->tics = SV_ReadLong();

    // state_t *state;
    // Restore as index into states table.
    i = SV_ReadLong();
    str->state = &states[i];

    // int damage;
    str->damage = SV_ReadLong();

    // int flags;
    str->flags = SV_ReadLong();

    // int flags2;
    str->flags2 = SV_ReadLong();

    // specialval_t special1;
    // specialval_t special2;
    // Read in special values: there are special cases to deal with with
    // mobj pointers.
    StreamInMobjSpecials(str);

    // int health;
    str->health = SV_ReadLong();

    // int movedir;
    str->movedir = SV_ReadLong();

    // int movecount;
    str->movecount = SV_ReadLong();

    // struct mobj_s *target;
    i = SV_ReadLong();
    SetMobjPtr(&str->target, i);

    // int reactiontime;
    str->reactiontime = SV_ReadLong();

    // int threshold;
    str->threshold = SV_ReadLong();

    // struct player_s *player;
    // Saved as player number.
    i = SV_ReadLong();
    if (i == 0)
    {
        str->player = NULL;
    }
    else
    {
        str->player = &players[i - 1];
        str->player->mo = str;
    }

    // int lastlook;
    str->lastlook = SV_ReadLong();

    // fixed_t floorclip;
    str->floorclip = SV_ReadLong();

    // int archiveNum;
    str->archiveNum = SV_ReadLong();

    // short tid;
    str->tid = SV_ReadWord();

    // byte special;
    str->special = SV_ReadByte();

    // byte args[5];
    for (i=0; i<5; ++i)
    {
        str->args[i] = SV_ReadByte();
    }
}

static void StreamOutMobjSpecials(mobj_t *mobj)
{
    unsigned int special1, special2;
    boolean corpse;
    
    corpse = (mobj->flags & MF_CORPSE) != 0;
    special1 = mobj->special1.i;
    special2 = mobj->special2.i;

    switch (mobj->type)
    {
            // Just special1
        case MT_BISH_FX:
        case MT_HOLY_FX:
        case MT_DRAGON:
        case MT_THRUSTFLOOR_UP:
        case MT_THRUSTFLOOR_DOWN:
        case MT_MINOTAUR:
        case MT_SORCFX1:
        case MT_MSTAFF_FX2:
            if (corpse)
            {
                special1 = MOBJ_NULL;
            }
            else
            {
                special1 = GetMobjNum(mobj->special1.m);
            }
            break;

            // Just special2
        case MT_LIGHTNING_FLOOR:
        case MT_LIGHTNING_ZAP:
            if (corpse)
            {
                special2 = MOBJ_NULL;
            }
            else
            {
                special2 = GetMobjNum(mobj->special2.m);
            }
            break;

            // Both special1 and special2
        case MT_HOLY_TAIL:
        case MT_LIGHTNING_CEILING:
            if (corpse)
            {
                special1 = MOBJ_NULL;
                special2 = MOBJ_NULL;
            }
            else
            {
                special1 = GetMobjNum(mobj->special1.m);
                special2 = GetMobjNum(mobj->special2.m);
            }
            break;

            // Miscellaneous
        case MT_KORAX:
            special1 = 0; // Searching index
            break;

        default:
            break;
    }

    // Write special values to savegame file.

    SV_WriteLong(special1);
    SV_WriteLong(special2);
}

static void StreamOut_mobj_t(mobj_t *str)
{
    int i;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // fixed_t x, y, z;
    SV_WriteLong(str->x);
    SV_WriteLong(str->y);
    SV_WriteLong(str->z);

    // struct mobj_s *snext, *sprev;
    SV_WritePtr(str->snext);
    SV_WritePtr(str->sprev);

    // angle_t angle;
    SV_WriteLong(str->angle);

    // spritenum_t sprite;
    SV_WriteLong(str->sprite);

    // int frame;
    SV_WriteLong(str->frame);

    // struct mobj_s *bnext, *bprev;
    SV_WritePtr(str->bnext);
    SV_WritePtr(str->bprev);

    // struct subsector_s *subsector;
    SV_WritePtr(str->subsector);

    // fixed_t floorz, ceilingz;
    SV_WriteLong(str->floorz);
    SV_WriteLong(str->ceilingz);

    // fixed_t floorpic;
    SV_WriteLong(str->floorpic);

    // fixed_t radius, height;
    SV_WriteLong(str->radius);
    SV_WriteLong(str->height);

    // fixed_t momx, momy, momz;
    SV_WriteLong(str->momx);
    SV_WriteLong(str->momy);
    SV_WriteLong(str->momz);

    // int validcount;
    SV_WriteLong(str->validcount);

    // mobjtype_t type;
    SV_WriteLong(str->type);

    // mobjinfo_t *info;
    SV_WritePtr(str->info);

    // int tics;
    SV_WriteLong(str->tics);

    // state_t *state;
    // Save as index into the states table.
    SV_WriteLong(str->state - states);

    // int damage;
    SV_WriteLong(str->damage);

    // int flags;
    SV_WriteLong(str->flags);

    // int flags2;
    SV_WriteLong(str->flags2);

    // specialval_t special1;
    // specialval_t special2;
    // There are lots of special cases for the special values:
    StreamOutMobjSpecials(str);

    // int health;
    SV_WriteLong(str->health);

    // int movedir;
    SV_WriteLong(str->movedir);

    // int movecount;
    SV_WriteLong(str->movecount);

    // struct mobj_s *target;
    if ((str->flags & MF_CORPSE) != 0)
    {
        SV_WriteLong(MOBJ_NULL);
    }
    else
    {
        SV_WriteLong(GetMobjNum(str->target));
    }

    // int reactiontime;
    SV_WriteLong(str->reactiontime);

    // int threshold;
    SV_WriteLong(str->threshold);

    // struct player_s *player;
    // Stored as index into players[] array, if there is a player pointer.
    if (str->player != NULL)
    {
        SV_WriteLong(str->player - players + 1);
    }
    else
    {
        SV_WriteLong(0);
    }

    // int lastlook;
    SV_WriteLong(str->lastlook);

    // fixed_t floorclip;
    SV_WriteLong(str->floorclip);

    // int archiveNum;
    SV_WriteLong(str->archiveNum);

    // short tid;
    SV_WriteWord(str->tid);

    // byte special;
    SV_WriteByte(str->special);

    // byte args[5];
    for (i=0; i<5; ++i)
    {
        SV_WriteByte(str->args[i]);
    }
}


//
// floormove_t
//

static void StreamIn_floormove_t(thinker_t *thinker)
{
    floormove_t *str = (floormove_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // sector_t *sector;
    i = SV_ReadLong();
    str->sector = sectors + i;

    // floor_e type;
    str->type = SV_ReadLong();

    // int crush;
    str->crush = SV_ReadLong();

    // int direction;
    str->direction = SV_ReadLong();

    // int newspecial;
    str->newspecial = SV_ReadLong();

    // short texture;
    str->texture = SV_ReadWord();

    // fixed_t floordestheight;
    str->floordestheight = SV_ReadLong();

    // fixed_t speed;
    str->speed = SV_ReadLong();

    // int delayCount;
    str->delayCount = SV_ReadLong();

    // int delayTotal;
    str->delayTotal = SV_ReadLong();

    // fixed_t stairsDelayHeight;
    str->stairsDelayHeight = SV_ReadLong();

    // fixed_t stairsDelayHeightDelta;
    str->stairsDelayHeightDelta = SV_ReadLong();

    // fixed_t resetHeight;
    str->resetHeight = SV_ReadLong();

    // short resetDelay;
    str->resetDelay = SV_ReadWord();

    // short resetDelayCount;
    str->resetDelayCount = SV_ReadWord();

    // byte textureChange;
    str->textureChange = SV_ReadByte();
}

static void StreamOut_floormove_t(thinker_t *thinker)
{
    floormove_t *str = (floormove_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // sector_t *sector;
    SV_WriteLong(str->sector - sectors);

    // floor_e type;
    SV_WriteLong(str->type);

    // int crush;
    SV_WriteLong(str->crush);

    // int direction;
    SV_WriteLong(str->direction);

    // int newspecial;
    SV_WriteLong(str->newspecial);

    // short texture;
    SV_WriteWord(str->texture);

    // fixed_t floordestheight;
    SV_WriteLong(str->floordestheight);

    // fixed_t speed;
    SV_WriteLong(str->speed);

    // int delayCount;
    SV_WriteLong(str->delayCount);

    // int delayTotal;
    SV_WriteLong(str->delayTotal);

    // fixed_t stairsDelayHeight;
    SV_WriteLong(str->stairsDelayHeight);

    // fixed_t stairsDelayHeightDelta;
    SV_WriteLong(str->stairsDelayHeightDelta);

    // fixed_t resetHeight;
    SV_WriteLong(str->resetHeight);

    // short resetDelay;
    SV_WriteWord(str->resetDelay);

    // short resetDelayCount;
    SV_WriteWord(str->resetDelayCount);

    // byte textureChange;
    SV_WriteByte(str->textureChange);
}


//
// plat_t
//

static void StreamIn_plat_t(thinker_t *thinker)
{
    plat_t *str = (plat_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // sector_t *sector;
    i = SV_ReadLong();
    str->sector = sectors + i;

    // fixed_t speed;
    str->speed = SV_ReadLong();

    // fixed_t low;
    str->low = SV_ReadLong();

    // fixed_t high;
    str->high = SV_ReadLong();

    // int wait;
    str->wait = SV_ReadLong();

    // int count;
    str->count = SV_ReadLong();

    // plat_e status;
    str->status = SV_ReadLong();

    // plat_e oldstatus;
    str->oldstatus = SV_ReadLong();

    // int crush;
    str->crush = SV_ReadLong();

    // int tag;
    str->tag = SV_ReadLong();

    // plattype_e type;
    str->type = SV_ReadLong();
}

static void StreamOut_plat_t(thinker_t *thinker)
{
    plat_t *str = (plat_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // sector_t *sector;
    SV_WriteLong(str->sector - sectors);

    // fixed_t speed;
    SV_WriteLong(str->speed);

    // fixed_t low;
    SV_WriteLong(str->low);

    // fixed_t high;
    SV_WriteLong(str->high);

    // int wait;
    SV_WriteLong(str->wait);

    // int count;
    SV_WriteLong(str->count);

    // plat_e status;
    SV_WriteLong(str->status);

    // plat_e oldstatus;
    SV_WriteLong(str->oldstatus);

    // int crush;
    SV_WriteLong(str->crush);

    // int tag;
    SV_WriteLong(str->tag);

    // plattype_e type;
    SV_WriteLong(str->type);
}


//
// ceiling_t
//

static void StreamIn_ceiling_t(thinker_t *thinker)
{
    ceiling_t *str = (ceiling_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // sector_t *sector;
    i = SV_ReadLong();
    str->sector = sectors + i;

    // ceiling_e type;
    str->type = SV_ReadLong();

    // fixed_t bottomheight, topheight;
    str->bottomheight = SV_ReadLong();
    str->topheight = SV_ReadLong();

    // fixed_t speed;
    str->speed = SV_ReadLong();

    // int crush;
    str->crush = SV_ReadLong();

    // int direction;
    str->direction = SV_ReadLong();

    // int tag;
    str->tag = SV_ReadLong();

    // int olddirection;
    str->olddirection = SV_ReadLong();
}

static void StreamOut_ceiling_t(thinker_t *thinker)
{
    ceiling_t *str = (ceiling_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // sector_t *sector;
    SV_WriteLong(str->sector - sectors);

    // ceiling_e type;
    SV_WriteLong(str->type);

    // fixed_t bottomheight, topheight;
    SV_WriteLong(str->bottomheight);
    SV_WriteLong(str->topheight);

    // fixed_t speed;
    SV_WriteLong(str->speed);

    // int crush;
    SV_WriteLong(str->crush);

    // int direction;
    SV_WriteLong(str->direction);

    // int tag;
    SV_WriteLong(str->tag);

    // int olddirection;
    SV_WriteLong(str->olddirection);
}


//
// light_t
//

static void StreamIn_light_t(thinker_t *thinker)
{
    light_t *str = (light_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // sector_t *sector;
    i = SV_ReadLong();
    str->sector = sectors + i;

    // lighttype_t type;
    str->type = SV_ReadLong();

    // int value1;
    str->value1 = SV_ReadLong();

    // int value2;
    str->value2 = SV_ReadLong();

    // int tics1;
    str->tics1 = SV_ReadLong();

    // int tics2;
    str->tics2 = SV_ReadLong();

    // int count;
    str->count = SV_ReadLong();

    if (!a11y_sector_lighting && str->type >= LITE_GLOW) // [crispy] A11Y - maxlight among competing thinkers.
        str->sector->rlightlevel = MAX(str->sector->rlightlevel, str->value1);
}

static void StreamOut_light_t(thinker_t *thinker)
{
    light_t *str = (light_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // sector_t *sector;
    SV_WriteLong(str->sector - sectors);

    // lighttype_t type;
    SV_WriteLong(str->type);

    // int value1;
    SV_WriteLong(str->value1);

    // int value2;
    SV_WriteLong(str->value2);

    // int tics1;
    SV_WriteLong(str->tics1);

    // int tics2;
    SV_WriteLong(str->tics2);

    // int count;
    SV_WriteLong(str->count);
}


//
// vldoor_t
//

static void StreamIn_vldoor_t(thinker_t *thinker)
{
    vldoor_t *str = (vldoor_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // sector_t *sector;
    i = SV_ReadLong();
    str->sector = &sectors[i];

    // vldoor_e type;
    str->type = SV_ReadLong();

    // fixed_t topheight;
    str->topheight = SV_ReadLong();

    // fixed_t speed;
    str->speed = SV_ReadLong();

    // int direction;
    str->direction = SV_ReadLong();

    // int topwait;
    str->topwait = SV_ReadLong();

    // int topcountdown;
    str->topcountdown = SV_ReadLong();
}

static void StreamOut_vldoor_t(thinker_t *thinker)
{
    vldoor_t *str = (vldoor_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // sector_t *sector;
    SV_WriteLong(str->sector - sectors);

    // vldoor_e type;
    SV_WriteLong(str->type);

    // fixed_t topheight;
    SV_WriteLong(str->topheight);

    // fixed_t speed;
    SV_WriteLong(str->speed);

    // int direction;
    SV_WriteLong(str->direction);

    // int topwait;
    SV_WriteLong(str->topwait);

    // int topcountdown;
    SV_WriteLong(str->topcountdown);
}


//
// phase_t
//

static void StreamIn_phase_t(thinker_t *thinker)
{
    phase_t *str = (phase_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // sector_t *sector;
    i = SV_ReadLong();
    str->sector = &sectors[i];

    // int index;
    str->index = SV_ReadLong();

    // int base;
    str->base = SV_ReadLong();

    if (!a11y_sector_lighting) // [crispy] A11Y - maxlight among competing thinkers.
        str->sector->rlightlevel = MAX(str->base + MAXPHASE, str->sector->rlightlevel);
}

static void StreamOut_phase_t(thinker_t *thinker)
{
    phase_t *str = (phase_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // sector_t *sector;
    SV_WriteLong(str->sector - sectors);

    // int index;
    SV_WriteLong(str->index);

    // int base;
    SV_WriteLong(str->base);
}


//
// acs_t
//

static void StreamIn_acs_t(thinker_t *thinker)
{
    acs_t *str = (acs_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // mobj_t *activator;
    i = SV_ReadLong();
    SetMobjPtr(&str->activator, i);

    // line_t *line;
    i = SV_ReadLong();
    if (i != -1)
    {
        str->line = &lines[i];
    }
    else
    {
        str->line = NULL;
    }

    // int side;
    str->side = SV_ReadLong();

    // int number;
    str->number = SV_ReadLong();

    // int infoIndex;
    str->infoIndex = SV_ReadLong();

    // int delayCount;
    str->delayCount = SV_ReadLong();

    // int stack[ACS_STACK_DEPTH];
    for (i=0; i<ACS_STACK_DEPTH; ++i)
    {
        str->stack[i] = SV_ReadLong();
    }

    // int stackPtr;
    str->stackPtr = SV_ReadLong();

    // int vars[MAX_ACS_SCRIPT_VARS];
    for (i=0; i<MAX_ACS_SCRIPT_VARS; ++i)
    {
        str->vars[i] = SV_ReadLong();
    }

    // int *ip;
    str->ip = SV_ReadLong();
}

static void StreamOut_acs_t(thinker_t *thinker)
{
    acs_t *str = (acs_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // mobj_t *activator;
    SV_WriteLong(GetMobjNum(str->activator));

    // line_t *line;
    if (str->line != NULL)
    {
        SV_WriteLong(str->line - lines);
    }
    else
    {
        SV_WriteLong(-1);
    }

    // int side;
    SV_WriteLong(str->side);

    // int number;
    SV_WriteLong(str->number);

    // int infoIndex;
    SV_WriteLong(str->infoIndex);

    // int delayCount;
    SV_WriteLong(str->delayCount);

    // int stack[ACS_STACK_DEPTH];
    for (i=0; i<ACS_STACK_DEPTH; ++i)
    {
        SV_WriteLong(str->stack[i]);
    }

    // int stackPtr;
    SV_WriteLong(str->stackPtr);

    // int vars[MAX_ACS_SCRIPT_VARS];
    for (i=0; i<MAX_ACS_SCRIPT_VARS; ++i)
    {
        SV_WriteLong(str->vars[i]);
    }

    // int *ip;
    SV_WriteLong(str->ip);
}


//
// polyevent_t
//

static void StreamIn_polyevent_t(thinker_t *thinker)
{
    polyevent_t *str = (polyevent_t *) thinker;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // int polyobj;
    str->polyobj = SV_ReadLong();

    // int speed;
    str->speed = SV_ReadLong();

    // unsigned int dist;
    str->dist = SV_ReadLong();

    // int angle;
    str->angle = SV_ReadLong();

    // fixed_t xSpeed;
    str->xSpeed = SV_ReadLong();

    // fixed_t ySpeed;
    str->ySpeed = SV_ReadLong();
}

static void StreamOut_polyevent_t(thinker_t *thinker)
{
    polyevent_t *str = (polyevent_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // int polyobj;
    SV_WriteLong(str->polyobj);

    // int speed;
    SV_WriteLong(str->speed);

    // unsigned int dist;
    SV_WriteLong(str->dist);

    // int angle;
    SV_WriteLong(str->angle);

    // fixed_t xSpeed;
    SV_WriteLong(str->xSpeed);

    // fixed_t ySpeed;
    SV_WriteLong(str->ySpeed);
}


//
// pillar_t
//

static void StreamIn_pillar_t(thinker_t *thinker)
{
    pillar_t *str = (pillar_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // sector_t *sector;
    i = SV_ReadLong();
    str->sector = &sectors[i];

    // int ceilingSpeed;
    str->ceilingSpeed = SV_ReadLong();

    // int floorSpeed;
    str->floorSpeed = SV_ReadLong();

    // int floordest;
    str->floordest = SV_ReadLong();

    // int ceilingdest;
    str->ceilingdest = SV_ReadLong();

    // int direction;
    str->direction = SV_ReadLong();

    // int crush;
    str->crush = SV_ReadLong();
}

static void StreamOut_pillar_t(thinker_t *thinker)
{
    pillar_t *str = (pillar_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // sector_t *sector;
    SV_WriteLong(str->sector - sectors);

    // int ceilingSpeed;
    SV_WriteLong(str->ceilingSpeed);

    // int floorSpeed;
    SV_WriteLong(str->floorSpeed);

    // int floordest;
    SV_WriteLong(str->floordest);

    // int ceilingdest;
    SV_WriteLong(str->ceilingdest);

    // int direction;
    SV_WriteLong(str->direction);

    // int crush;
    SV_WriteLong(str->crush);
}


//
// polydoor_t
//

static void StreamIn_polydoor_t(thinker_t *thinker)
{
    polydoor_t *str = (polydoor_t *) thinker;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // int polyobj;
    str->polyobj = SV_ReadLong();

    // int speed;
    str->speed = SV_ReadLong();

    // int dist;
    str->dist = SV_ReadLong();

    // int totalDist;
    str->totalDist = SV_ReadLong();

    // int direction;
    str->direction = SV_ReadLong();

    // fixed_t xSpeed, ySpeed;
    str->xSpeed = SV_ReadLong();
    str->ySpeed = SV_ReadLong();

    // int tics;
    str->tics = SV_ReadLong();

    // int waitTics;
    str->waitTics = SV_ReadLong();

    // podoortype_t type;
    str->type = SV_ReadLong();

    // boolean close;
    str->close = SV_ReadLong();
}

static void StreamOut_polydoor_t(thinker_t *thinker)
{
    polydoor_t *str = (polydoor_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // int polyobj;
    SV_WriteLong(str->polyobj);

    // int speed;
    SV_WriteLong(str->speed);

    // int dist;
    SV_WriteLong(str->dist);

    // int totalDist;
    SV_WriteLong(str->totalDist);

    // int direction;
    SV_WriteLong(str->direction);

    // fixed_t xSpeed, ySpeed;
    SV_WriteLong(str->xSpeed);
    SV_WriteLong(str->ySpeed);

    // int tics;
    SV_WriteLong(str->tics);

    // int waitTics;
    SV_WriteLong(str->waitTics);

    // podoortype_t type;
    SV_WriteLong(str->type);

    // boolean close;
    SV_WriteLong(str->close);
}


//
// floorWaggle_t
//

static void StreamIn_floorWaggle_t(thinker_t *thinker)
{
    floorWaggle_t *str = (floorWaggle_t *) thinker;

    int i;

    // thinker_t thinker;
    StreamIn_thinker_t(&str->thinker);

    // sector_t *sector;
    i = SV_ReadLong();
    str->sector = &sectors[i];

    // fixed_t originalHeight;
    str->originalHeight = SV_ReadLong();

    // fixed_t accumulator;
    str->accumulator = SV_ReadLong();

    // fixed_t accDelta;
    str->accDelta = SV_ReadLong();

    // fixed_t targetScale;
    str->targetScale = SV_ReadLong();

    // fixed_t scale;
    str->scale = SV_ReadLong();

    // fixed_t scaleDelta;
    str->scaleDelta = SV_ReadLong();

    // int ticker;
    str->ticker = SV_ReadLong();

    // int state;
    str->state = SV_ReadLong();
}

static void StreamOut_floorWaggle_t(thinker_t *thinker)
{
    floorWaggle_t *str = (floorWaggle_t *) thinker;

    // thinker_t thinker;
    StreamOut_thinker_t(&str->thinker);

    // sector_t *sector;
    SV_WriteLong(str->sector - sectors);

    // fixed_t originalHeight;
    SV_WriteLong(str->originalHeight);

    // fixed_t accumulator;
    SV_WriteLong(str->accumulator);

    // fixed_t accDelta;
    SV_WriteLong(str->accDelta);

    // fixed_t targetScale;
    SV_WriteLong(str->targetScale);

    // fixed_t scale;
    SV_WriteLong(str->scale);

    // fixed_t scaleDelta;
    SV_WriteLong(str->scaleDelta);

    // int ticker;
    SV_WriteLong(str->ticker);

    // int state;
    SV_WriteLong(str->state);
}


//==========================================================================
//
// SV_SaveGame
//
//==========================================================================

void SV_SaveGame(int slot, const char *description)
{
    char fileName[100];
    char versionText[HXS_VERSION_TEXT_LENGTH];
    unsigned int i;

    // Open the output file
    M_snprintf(fileName, sizeof(fileName), "%shex%d.hxs", SavePath, BASE_SLOT);
    SV_OpenWrite(fileName);

    // Write game save description
    SV_Write(description, HXS_DESCRIPTION_LENGTH);

    // Write version info
    memset(versionText, 0, HXS_VERSION_TEXT_LENGTH);
    M_StringCopy(versionText, HXS_VERSION_TEXT, HXS_VERSION_TEXT_LENGTH);
    SV_Write(versionText, HXS_VERSION_TEXT_LENGTH);

    // Place a header marker
    SV_WriteLong(ASEG_GAME_HEADER);

    // Write current map and difficulty
    SV_WriteByte(gamemap);
    SV_WriteByte(gameskill);

    // Write global script info
    for (i = 0; i < MAX_ACS_WORLD_VARS; ++i)
    {
        SV_WriteLong(WorldVars[i]);
    }

    for (i = 0; i < MAX_ACS_STORE + 1; ++i)
    {
        StreamOut_acsstore_t(&ACSStore[i]);
    }

    ArchivePlayers();

    // Place a termination marker
    SV_WriteLong(ASEG_END);

    // [crispy] write extended savegame data for game
    SV_WriteExtendedSaveGameData(EXTSAVEG_GAME);

    // Close the output file
    SV_Close();

    // Save out the current map
    SV_SaveMap(true);           // true = save player info

    // Clear all save files at destination slot
    SV_ClearSaveSlot(slot);

    // Copy base slot to destination slot
    CopySaveSlot(BASE_SLOT, slot);
}

//==========================================================================
//
// SV_SaveMap
//
//==========================================================================

void SV_SaveMap(boolean savePlayers)
{
    char fileName[100];

    SavingPlayers = savePlayers;

    // Open the output file
    M_snprintf(fileName, sizeof(fileName), "%shex%d%02d.hxs",
                SavePath, BASE_SLOT, gamemap);
    SV_OpenWrite(fileName);

    // Place a header marker
    SV_WriteLong(ASEG_MAP_HEADER);

    // Write the level timer
    SV_WriteLong(leveltime);

    // Set the mobj archive numbers
    SetMobjArchiveNums();

    ArchiveWorld();
    ArchivePolyobjs();
    ArchiveMobjs();
    ArchiveThinkers();
    ArchiveScripts();
    ArchiveSounds();
    ArchiveMisc();

    // Place a termination marker
    SV_WriteLong(ASEG_END);

    // [crispy] write extended savegame data for map
    SV_WriteExtendedSaveGameData(EXTSAVEG_MAP);

    // Close the output file
    SV_Close();
}

//==========================================================================
//
// SV_LoadGame
//
//==========================================================================

void SV_LoadGame(int slot)
{
    int i;
    char fileName[100];
    char version_text[HXS_VERSION_TEXT_LENGTH];
    player_t playerBackup[MAXPLAYERS];
    mobj_t *mobj;
    player_t *p; // [crispy]

    p = &players[consoleplayer]; // [crispy]

    // Copy all needed save files to the base slot
    if (slot != BASE_SLOT)
    {
        SV_ClearSaveSlot(BASE_SLOT);
        CopySaveSlot(slot, BASE_SLOT);
    }

    // Create the name
    M_snprintf(fileName, sizeof(fileName), "%shex%d.hxs", SavePath, BASE_SLOT);

    // Load the file
    SV_OpenRead(fileName);

    // Set the save pointer and skip the description field
    fseek(SavingFP, HXS_DESCRIPTION_LENGTH, SEEK_CUR);

    // Check the version text

    for (i = 0; i < sizeof(version_text); ++i)
    {
        version_text[i] = SV_ReadByte();
    }
    if (strncmp(version_text, HXS_VERSION_TEXT, HXS_VERSION_TEXT_LENGTH) != 0)
    {                           // Bad version
        return;
    }

    AssertSegment(ASEG_GAME_HEADER);

    gameepisode = 1;
    gamemap = SV_ReadByte();
    gameskill = SV_ReadByte();

    // Read global script info

    for (i = 0; i < MAX_ACS_WORLD_VARS; ++i)
    {
        WorldVars[i] = SV_ReadLong();
    }

    for (i = 0; i < MAX_ACS_STORE + 1; ++i)
    {
        StreamIn_acsstore_t(&ACSStore[i]);
    }

    // Read the player structures
    UnarchivePlayers();

    AssertSegment(ASEG_END);

    // Save player structs
    for (i = 0; i < maxplayers; i++)
    {
        playerBackup[i] = players[i];
    }

    SV_Close();

    // Load the current map
    SV_LoadMap();

    // Don't need the player mobj relocation info for load game
    Z_Free(TargetPlayerAddrs);

    // Restore player structs
    inv_ptr = 0;
    curpos = 0;

    for (i = 0; i < maxplayers; i++)
    {
        mobj = players[i].mo;
        players[i] = playerBackup[i];
        players[i].mo = mobj;
    }

    // [crispy] point to active artifact after load
    for (i = 0; i < p->inventorySlotNum; i++)
    {
        if (p->inventory[i].type == p->readyArtifact)
        {
            curpos = inv_ptr = i;
            curpos = (curpos > CURPOS_MAX) ? CURPOS_MAX : curpos;
            p->readyArtifact = p->inventory[inv_ptr].type;
            break;
        }
    }

    // Set Bestslideline after loading
    P_InitSlideLine();
}

//==========================================================================
//
// SV_UpdateRebornSlot
//
// Copies the base slot to the reborn slot.
//
//==========================================================================

void SV_UpdateRebornSlot(void)
{
    SV_ClearSaveSlot(REBORN_SLOT);
    CopySaveSlot(BASE_SLOT, REBORN_SLOT);
}

//==========================================================================
//
// SV_ClearRebornSlot
//
//==========================================================================

void SV_ClearRebornSlot(void)
{
    SV_ClearSaveSlot(REBORN_SLOT);
}

//==========================================================================
//
// SV_MapTeleport
//
//==========================================================================

void SV_MapTeleport(int map, int position)
{
    int i;
    int j;
    char fileName[100];
    player_t playerBackup[MAXPLAYERS];
    mobj_t *targetPlayerMobj;
    mobj_t *mobj;
    int inventoryPtr;
    int currentInvPos;
    boolean rClass;
    boolean playerWasReborn;
    boolean oldWeaponowned[NUMWEAPONS];
    int oldKeys = 0;
    int oldPieces = 0;
    int bestWeapon;

    if (!deathmatch)
    {
        if (P_GetMapCluster(gamemap) == P_GetMapCluster(map))
        {                       // Same cluster - save map without saving player mobjs
            SV_SaveMap(false);
        }
        else
        {                       // Entering new cluster - clear base slot
            SV_ClearSaveSlot(BASE_SLOT);
        }
    }

    // Store player structs for later
    rClass = randomclass;
    randomclass = false;
    for (i = 0; i < maxplayers; i++)
    {
        playerBackup[i] = players[i];
    }

    // Save some globals that get trashed during the load
    inventoryPtr = inv_ptr;
    currentInvPos = curpos;

    // Only SV_LoadMap() uses TargetPlayerAddrs, so it's NULLed here
    // for the following check (player mobj redirection)
    TargetPlayerAddrs = NULL;

    gamemap = map;
    M_snprintf(fileName, sizeof(fileName),
                "%shex%d%02d.hxs", SavePath, BASE_SLOT, gamemap);
    if (!deathmatch && ExistingFile(fileName))
    {                           // Unarchive map
        SV_LoadMap();
    }
    else
    {                           // New map
        G_InitNew(gameskill, gameepisode, gamemap);

        // Destroy all freshly spawned players
        for (i = 0; i < maxplayers; i++)
        {
            if (playeringame[i])
            {
                P_RemoveMobj(players[i].mo);
            }
        }
    }

    // Restore player structs
    targetPlayerMobj = NULL;
    for (i = 0; i < maxplayers; i++)
    {
        if (!playeringame[i])
        {
            continue;
        }
        players[i] = playerBackup[i];
        P_ClearMessage(&players[i]);
        players[i].attacker = NULL;
        players[i].poisoner = NULL;

        if (netgame)
        {
            if (players[i].playerstate == PST_DEAD)
            {                   // In a network game, force all players to be alive
                players[i].playerstate = PST_REBORN;
            }
            if (!deathmatch)
            {                   // Cooperative net-play, retain keys and weapons
                oldKeys = players[i].keys;
                oldPieces = players[i].pieces;
                for (j = 0; j < NUMWEAPONS; j++)
                {
                    oldWeaponowned[j] = players[i].weaponowned[j];
                }
            }
        }
        playerWasReborn = (players[i].playerstate == PST_REBORN);
        if (deathmatch)
        {
            memset(players[i].frags, 0, sizeof(players[i].frags));
            mobj = P_SpawnMobj(playerstarts[0][i].x << 16,
                               playerstarts[0][i].y << 16, 0,
                               MT_PLAYER_FIGHTER);
            players[i].mo = mobj;
            G_DeathMatchSpawnPlayer(i);
            P_RemoveMobj(mobj);
        }
        else
        {
            P_SpawnPlayer(&playerstarts[position][i]);
        }

        if (playerWasReborn && netgame && !deathmatch)
        {                       // Restore keys and weapons when reborn in co-op
            players[i].keys = oldKeys;
            players[i].pieces = oldPieces;
            for (bestWeapon = 0, j = 0; j < NUMWEAPONS; j++)
            {
                if (oldWeaponowned[j])
                {
                    bestWeapon = j;
                    players[i].weaponowned[j] = true;
                }
            }
            players[i].mana[MANA_1] = 25;
            players[i].mana[MANA_2] = 25;
            if (bestWeapon)
            {                   // Bring up the best weapon
                players[i].pendingweapon = bestWeapon;
            }
        }

        if (targetPlayerMobj == NULL)
        {                       // The poor sap
            targetPlayerMobj = players[i].mo;
        }
    }
    randomclass = rClass;

    // Redirect anything targeting a player mobj
    if (TargetPlayerAddrs)
    {
        for (i = 0; i < TargetPlayerCount; i++)
        {
            *TargetPlayerAddrs[i] = targetPlayerMobj;
        }
        Z_Free(TargetPlayerAddrs);
    }

    // Destroy all things touching players
    for (i = 0; i < maxplayers; i++)
    {
        if (playeringame[i])
        {
            P_TeleportMove(players[i].mo, players[i].mo->x, players[i].mo->y);
        }
    }

    // Restore trashed globals
    inv_ptr = inventoryPtr;
    curpos = currentInvPos;

    // Launch waiting scripts
    if (!deathmatch)
    {
        P_CheckACSStore();
    }

    // For single play, save immediately into the reborn slot
    if (!netgame)
    {
        SV_SaveGame(REBORN_SLOT, REBORN_DESCRIPTION);
    }
}

//==========================================================================
//
// SV_GetRebornSlot
//
//==========================================================================

int SV_GetRebornSlot(void)
{
    return (REBORN_SLOT);
}

//==========================================================================
//
// SV_RebornSlotAvailable
//
// Returns true if the reborn slot is available.
//
//==========================================================================

boolean SV_RebornSlotAvailable(void)
{
    char fileName[100];

    M_snprintf(fileName, sizeof(fileName), "%shex%d.hxs", SavePath, REBORN_SLOT);
    return ExistingFile(fileName);
}

//==========================================================================
//
// SV_LoadMap
//
//==========================================================================

void SV_LoadMap(void)
{
    char fileName[100];

    // Load a base level
    G_InitNew(gameskill, gameepisode, gamemap);

    // Remove all thinkers
    RemoveAllThinkers();

    // Create the name
    M_snprintf(fileName, sizeof(fileName),
                "%shex%d%02d.hxs", SavePath, BASE_SLOT, gamemap);

    // Load the file
    SV_OpenRead(fileName);

    AssertSegment(ASEG_MAP_HEADER);

    // Read the level timer
    leveltime = SV_ReadLong();

    UnarchiveWorld();
    UnarchivePolyobjs();
    UnarchiveMobjs();
    UnarchiveThinkers();
    UnarchiveScripts();
    UnarchiveSounds();
    UnarchiveMisc();

    AssertSegment(ASEG_END);

    // [crispy] read more extended savegame data for map
    SV_ReadExtendedSaveGameData(EXTSAVEG_MAP);

    // Free mobj list and save buffer
    Z_Free(MobjList);
    SV_Close();
}

//==========================================================================
//
// SV_InitBaseSlot
//
//==========================================================================

void SV_InitBaseSlot(void)
{
    SV_ClearSaveSlot(BASE_SLOT);
}

//==========================================================================
//
// ArchivePlayers
//
//==========================================================================

static void ArchivePlayers(void)
{
    int i;

    SV_WriteLong(ASEG_PLAYERS);
    for (i = 0; i < maxplayers; i++)
    {
        SV_WriteByte(playeringame[i]);
    }
    for (i = 0; i < maxplayers; i++)
    {
        if (!playeringame[i])
        {
            continue;
        }
        SV_WriteByte(PlayerClass[i]);
        StreamOut_player_t(&players[i]);
    }
}

//==========================================================================
//
// UnarchivePlayers
//
//==========================================================================

static void UnarchivePlayers(void)
{
    int i;

    AssertSegment(ASEG_PLAYERS);
    for (i = 0; i < maxplayers; i++)
    {
        playeringame[i] = SV_ReadByte();
    }
    for (i = 0; i < maxplayers; i++)
    {
        if (!playeringame[i])
        {
            continue;
        }
        PlayerClass[i] = SV_ReadByte();
        StreamIn_player_t(&players[i]);
        P_ClearMessage(&players[i]);
    }
}

//==========================================================================
//
// ArchiveWorld
//
//==========================================================================

static void ArchiveWorld(void)
{
    int i;
    int j;
    sector_t *sec;
    line_t *li;
    side_t *si;

    SV_WriteLong(ASEG_WORLD);
    for (i = 0, sec = sectors; i < numsectors; i++, sec++)
    {
        SV_WriteWord(sec->floorheight >> FRACBITS);
        SV_WriteWord(sec->ceilingheight >> FRACBITS);
        SV_WriteWord(sec->floorpic);
        SV_WriteWord(sec->ceilingpic);
        SV_WriteWord(sec->lightlevel);
        SV_WriteWord(sec->special);
        SV_WriteWord(sec->tag);
        SV_WriteWord(sec->seqType);
    }
    for (i = 0, li = lines; i < numlines; i++, li++)
    {
        SV_WriteWord(li->flags);
        SV_WriteByte(li->special);
        SV_WriteByte(li->arg1);
        SV_WriteByte(li->arg2);
        SV_WriteByte(li->arg3);
        SV_WriteByte(li->arg4);
        SV_WriteByte(li->arg5);
        for (j = 0; j < 2; j++)
        {
            if (li->sidenum[j] == -1)
            {
                continue;
            }
            si = &sides[li->sidenum[j]];
            SV_WriteWord(si->textureoffset >> FRACBITS);
            SV_WriteWord(si->rowoffset >> FRACBITS);
            SV_WriteWord(si->toptexture);
            SV_WriteWord(si->bottomtexture);
            SV_WriteWord(si->midtexture);
        }
    }
}

//==========================================================================
//
// UnarchiveWorld
//
//==========================================================================

static void UnarchiveWorld(void)
{
    int i;
    int j;
    sector_t *sec;
    line_t *li;
    side_t *si;

    AssertSegment(ASEG_WORLD);
    for (i = 0, sec = sectors; i < numsectors; i++, sec++)
    {
        sec->floorheight = SV_ReadWord() << FRACBITS;
        sec->ceilingheight = SV_ReadWord() << FRACBITS;
        sec->floorpic = SV_ReadWord();
        sec->ceilingpic = SV_ReadWord();
        sec->lightlevel = SV_ReadWord();
        sec->rlightlevel = sec->lightlevel; // [crispy] A11Y
        sec->special = SV_ReadWord();
        sec->tag = SV_ReadWord();
        sec->seqType = SV_ReadWord();
        sec->specialdata = 0;
        sec->soundtarget = 0;
    }
    for (i = 0, li = lines; i < numlines; i++, li++)
    {
        li->flags = SV_ReadWord();
        li->special = SV_ReadByte();
        li->arg1 = SV_ReadByte();
        li->arg2 = SV_ReadByte();
        li->arg3 = SV_ReadByte();
        li->arg4 = SV_ReadByte();
        li->arg5 = SV_ReadByte();
        for (j = 0; j < 2; j++)
        {
            if (li->sidenum[j] == -1)
            {
                continue;
            }
            si = &sides[li->sidenum[j]];
            si->textureoffset = SV_ReadWord() << FRACBITS;
            si->rowoffset = SV_ReadWord() << FRACBITS;
            si->toptexture = SV_ReadWord();
            si->bottomtexture = SV_ReadWord();
            si->midtexture = SV_ReadWord();
        }
    }
}

//==========================================================================
//
// SetMobjArchiveNums
//
// Sets the archive numbers in all mobj structs.  Also sets the MobjCount
// global.  Ignores player mobjs if SavingPlayers is false.
//
//==========================================================================

static void SetMobjArchiveNums(void)
{
    mobj_t *mobj;
    thinker_t *thinker;

    MobjCount = 0;
    for (thinker = thinkercap.next; thinker != &thinkercap;
         thinker = thinker->next)
    {
        if (thinker->function == P_MobjThinker)
        {
            mobj = (mobj_t *) thinker;
            if (mobj->player && !SavingPlayers)
            {                   // Skipping player mobjs
                continue;
            }
            mobj->archiveNum = MobjCount++;
        }
    }
}

//==========================================================================
//
// ArchiveMobjs
//
//==========================================================================

static void ArchiveMobjs(void)
{
    int count;
    thinker_t *thinker;

    SV_WriteLong(ASEG_MOBJS);
    SV_WriteLong(MobjCount);
    count = 0;
    for (thinker = thinkercap.next; thinker != &thinkercap;
         thinker = thinker->next)
    {
        if (thinker->function != P_MobjThinker)
        {                       // Not a mobj thinker
            continue;
        }
        if (((mobj_t *) thinker)->player && !SavingPlayers)
        {                       // Skipping player mobjs
            continue;
        }
        count++;
        StreamOut_mobj_t((mobj_t *) thinker);
    }
    if (count != MobjCount)
    {
        I_Error("ArchiveMobjs: bad mobj count");
    }
}

//==========================================================================
//
// UnarchiveMobjs
//
//==========================================================================

static void UnarchiveMobjs(void)
{
    int i;
    mobj_t *mobj;

    AssertSegment(ASEG_MOBJS);
    TargetPlayerAddrs = Z_Malloc(MAX_TARGET_PLAYERS * sizeof(mobj_t **),
                                 PU_STATIC, NULL);
    TargetPlayerCount = 0;
    MobjCount = SV_ReadLong();
    MobjList = Z_Malloc(MobjCount * sizeof(mobj_t *), PU_STATIC, NULL);
    for (i = 0; i < MobjCount; i++)
    {
        MobjList[i] = Z_Malloc(sizeof(mobj_t), PU_LEVEL, NULL);
    }
    for (i = 0; i < MobjCount; i++)
    {
        mobj = MobjList[i];
        StreamIn_mobj_t(mobj);

        // Restore broken pointers.
        mobj->info = &mobjinfo[mobj->type];
        P_SetThingPosition(mobj);
        mobj->floorz = mobj->subsector->sector->floorheight;
        mobj->ceilingz = mobj->subsector->sector->ceilingheight;

        mobj->thinker.function = P_MobjThinker;
        P_AddThinker(&mobj->thinker);
    }
    P_CreateTIDList();
    P_InitCreatureCorpseQueue(true);    // true = scan for corpses
}

//==========================================================================
//
// GetMobjNum
//
//==========================================================================

static int GetMobjNum(mobj_t * mobj)
{
    if (mobj == NULL)
    {
        return MOBJ_NULL;
    }
    if (mobj->player && !SavingPlayers)
    {
        return MOBJ_XX_PLAYER;
    }
    return mobj->archiveNum;
}

//==========================================================================
//
// SetMobjPtr
//
//==========================================================================

static void SetMobjPtr(mobj_t **ptr, unsigned int archiveNum)
{
    if (archiveNum == MOBJ_NULL)
    {
        *ptr = NULL;
    }
    else if (archiveNum == MOBJ_XX_PLAYER)
    {
        if (TargetPlayerCount == MAX_TARGET_PLAYERS)
        {
            I_Error("RestoreMobj: exceeded MAX_TARGET_PLAYERS");
        }
        TargetPlayerAddrs[TargetPlayerCount++] = ptr;
        *ptr = NULL;
    }
    // [Dasperal] If the save is corrupted and the identity number is higher than the number of objects on the map restore it as a NULL pointer.
    // This still potentially leaves complex objects broken but NULL is safer than garbage value.
    else if(archiveNum < MobjCount)
    {
        *ptr = MobjList[archiveNum];
    }
    else
    {
        *ptr = NULL;
    }
}

//==========================================================================
//
// Thinker types list.
//
// This is used by ArchiveThinkers and UnarchiveThinkers, below.
//
// Original comment:
// "This list has been prioritized using frequency estimates"
//
//==========================================================================

static thinkInfo_t ThinkerInfo[] = {
    {
     TC_MOVE_FLOOR,
     T_MoveFloor,
     StreamOut_floormove_t,
     StreamIn_floormove_t,
     RestoreSSThinker,
     sizeof(floormove_t)
    },
    {
     TC_PLAT_RAISE,
     T_PlatRaise,
     StreamOut_plat_t,
     StreamIn_plat_t,
     RestorePlatRaise,
     sizeof(plat_t)
    },
    {
     TC_MOVE_CEILING,
     T_MoveCeiling,
     StreamOut_ceiling_t,
     StreamIn_ceiling_t,
     RestoreMoveCeiling,
     sizeof(ceiling_t)
    },
    {
     TC_LIGHT,
     T_Light,
     StreamOut_light_t,
     StreamIn_light_t,
     NULL,
     sizeof(light_t)
    },
    {
     TC_VERTICAL_DOOR,
     T_VerticalDoor,
     StreamOut_vldoor_t,
     StreamIn_vldoor_t,
     RestoreSSThinker,
     sizeof(vldoor_t)
    },
    {
     TC_PHASE,
     T_Phase,
     StreamOut_phase_t,
     StreamIn_phase_t,
     NULL,
     sizeof(phase_t)
    },
    {
     TC_INTERPRET_ACS,
     T_InterpretACS,
     StreamOut_acs_t,
     StreamIn_acs_t,
     NULL,
     sizeof(acs_t)
    },
    {
     TC_ROTATE_POLY,
     T_RotatePoly,
     StreamOut_polyevent_t,
     StreamIn_polyevent_t,
     NULL,
     sizeof(polyevent_t)
    },
    {
     TC_BUILD_PILLAR,
     T_BuildPillar,
     StreamOut_pillar_t,
     StreamIn_pillar_t,
     RestoreSSThinker,
     sizeof(pillar_t)
    },
    {
     TC_MOVE_POLY,
     T_MovePoly,
     StreamOut_polyevent_t,
     StreamIn_polyevent_t,
     NULL,
     sizeof(polyevent_t)
    },
    {
     TC_POLY_DOOR,
     T_PolyDoor,
     StreamOut_polydoor_t,
     StreamIn_polydoor_t,
     NULL,
     sizeof(polydoor_t)
    },
    {
     TC_FLOOR_WAGGLE,
     T_FloorWaggle,
     StreamOut_floorWaggle_t,
     StreamIn_floorWaggle_t,
     RestoreSSThinker,
     sizeof(floorWaggle_t)
    },
    { TC_NULL, NULL, NULL, NULL, NULL, 0},
};

//==========================================================================
//
// ArchiveThinkers
//
//==========================================================================

static void ArchiveThinkers(void)
{
    thinker_t *thinker;
    thinkInfo_t *info;

    SV_WriteLong(ASEG_THINKERS);
    for (thinker = thinkercap.next; thinker != &thinkercap;
         thinker = thinker->next)
    {
        for (info = ThinkerInfo; info->tClass != TC_NULL; info++)
        {
            if (thinker->function == info->thinkerFunc)
            {
                SV_WriteByte(info->tClass);
                info->writeFunc(thinker);
                break;
            }
        }
    }
    // Add a termination marker
    SV_WriteByte(TC_NULL);
}

//==========================================================================
//
// UnarchiveThinkers
//
//==========================================================================

static void UnarchiveThinkers(void)
{
    int tClass;
    thinker_t *thinker;
    thinkInfo_t *info;

    AssertSegment(ASEG_THINKERS);
    while ((tClass = SV_ReadByte()) != TC_NULL)
    {
        for (info = ThinkerInfo; info->tClass != TC_NULL; info++)
        {
            if (tClass == info->tClass)
            {
                thinker = Z_Malloc(info->size, PU_LEVEL, NULL);
                info->readFunc(thinker);
                thinker->function = info->thinkerFunc;
                if (info->restoreFunc)
                {
                    info->restoreFunc(thinker);
                }
                P_AddThinker(thinker);
                break;
            }
        }
        if (info->tClass == TC_NULL)
        {
            I_Error("UnarchiveThinkers: Unknown tClass %d in "
                    "savegame", tClass);
        }
    }
}

//==========================================================================
//
// RestoreSSThinker
//
//==========================================================================

static void RestoreSSThinker(thinker_t *thinker)
{
    ssthinker_t *sst = (ssthinker_t *) thinker;
    sst->sector->specialdata = sst->thinker.function;
}

//==========================================================================
//
// RestorePlatRaise
//
//==========================================================================

static void RestorePlatRaise(thinker_t *thinker)
{
    plat_t *plat = (plat_t *) thinker;
    plat->sector->specialdata = T_PlatRaise;
    P_AddActivePlat(plat);
}

//==========================================================================
//
// RestoreMoveCeiling
//
//==========================================================================

static void RestoreMoveCeiling(thinker_t *thinker)
{
    ceiling_t *ceiling = (ceiling_t *) thinker;
    ceiling->sector->specialdata = T_MoveCeiling;
    P_AddActiveCeiling(ceiling);
}

//==========================================================================
//
// ArchiveScripts
//
//==========================================================================

static void ArchiveScripts(void)
{
    int i;

    SV_WriteLong(ASEG_SCRIPTS);
    for (i = 0; i < ACScriptCount; i++)
    {
        SV_WriteWord(ACSInfo[i].state);
        SV_WriteWord(ACSInfo[i].waitValue);
    }

    for (i = 0; i< MAX_ACS_MAP_VARS; ++i)
    {
        SV_WriteLong(MapVars[i]);
    }
}

//==========================================================================
//
// UnarchiveScripts
//
//==========================================================================

static void UnarchiveScripts(void)
{
    int i;

    AssertSegment(ASEG_SCRIPTS);
    for (i = 0; i < ACScriptCount; i++)
    {
        ACSInfo[i].state = SV_ReadWord();
        ACSInfo[i].waitValue = SV_ReadWord();
    }

    for (i = 0; i < MAX_ACS_MAP_VARS; ++i)
    {
        MapVars[i] = SV_ReadLong();
    }
}

//==========================================================================
//
// ArchiveMisc
//
//==========================================================================

static void ArchiveMisc(void)
{
    int ix;

    SV_WriteLong(ASEG_MISC);
    for (ix = 0; ix < maxplayers; ix++)
    {
        SV_WriteLong(localQuakeHappening[ix]);
    }
}

//==========================================================================
//
// UnarchiveMisc
//
//==========================================================================

static void UnarchiveMisc(void)
{
    int ix;

    AssertSegment(ASEG_MISC);
    for (ix = 0; ix < maxplayers; ix++)
    {
        localQuakeHappening[ix] = SV_ReadLong();
    }
}

//==========================================================================
//
// RemoveAllThinkers
//
//==========================================================================

static void RemoveAllThinkers(void)
{
    thinker_t *thinker;
    thinker_t *nextThinker;

    thinker = thinkercap.next;
    while (thinker != &thinkercap)
    {
        nextThinker = thinker->next;
        if (thinker->function == P_MobjThinker)
        {
            P_RemoveMobj((mobj_t *) thinker);
        }
        else
        {
            Z_Free(thinker);
        }
        thinker = nextThinker;
    }
    P_InitThinkers();
}

//==========================================================================
//
// ArchiveSounds
//
//==========================================================================

static void ArchiveSounds(void)
{
    seqnode_t *node;
    sector_t *sec;
    int difference;
    int i;

    SV_WriteLong(ASEG_SOUNDS);

    // Save the sound sequences
    SV_WriteLong(ActiveSequences);
    for (node = SequenceListHead; node; node = node->next)
    {
        SV_WriteLong(node->sequence);
        SV_WriteLong(node->delayTics);
        SV_WriteLong(node->volume);
        SV_WriteLong(SN_GetSequenceOffset(node->sequence,
                                           node->sequencePtr));
        SV_WriteLong(node->currentSoundID);
        for (i = 0; i < po_NumPolyobjs; i++)
        {
            if (node->mobj == (mobj_t *) & polyobjs[i].startSpot)
            {
                break;
            }
        }
        if (i == po_NumPolyobjs)
        {                       // Sound is attached to a sector, not a polyobj
            sec = R_PointInSubsector(node->mobj->x, node->mobj->y)->sector;
            difference = (int) ((byte *) sec
                                - (byte *) & sectors[0]) / sizeof(sector_t);
            SV_WriteLong(0);   // 0 -- sector sound origin
        }
        else
        {
            SV_WriteLong(1);   // 1 -- polyobj sound origin
            difference = i;
        }
        SV_WriteLong(difference);
    }
}

//==========================================================================
//
// UnarchiveSounds
//
//==========================================================================

static void UnarchiveSounds(void)
{
    int i;
    int numSequences;
    int sequence;
    int delayTics;
    int volume;
    int seqOffset;
    int soundID;
    int polySnd;
    int secNum;
    mobj_t *sndMobj;

    AssertSegment(ASEG_SOUNDS);

    // Reload and restart all sound sequences
    numSequences = SV_ReadLong();
    i = 0;
    while (i < numSequences)
    {
        sequence = SV_ReadLong();
        delayTics = SV_ReadLong();
        volume = SV_ReadLong();
        seqOffset = SV_ReadLong();

        soundID = SV_ReadLong();
        polySnd = SV_ReadLong();
        secNum = SV_ReadLong();
        if (!polySnd)
        {
            sndMobj = (mobj_t *) & sectors[secNum].soundorg;
        }
        else
        {
            sndMobj = (mobj_t *) & polyobjs[secNum].startSpot;
        }
        SN_StartSequence(sndMobj, sequence);
        SN_ChangeNodeData(i, seqOffset, delayTics, volume, soundID);
        i++;
    }
}

//==========================================================================
//
// ArchivePolyobjs
//
//==========================================================================

static void ArchivePolyobjs(void)
{
    int i;

    SV_WriteLong(ASEG_POLYOBJS);
    SV_WriteLong(po_NumPolyobjs);
    for (i = 0; i < po_NumPolyobjs; i++)
    {
        SV_WriteLong(polyobjs[i].tag);
        SV_WriteLong(polyobjs[i].angle);
        SV_WriteLong(polyobjs[i].startSpot.x);
        SV_WriteLong(polyobjs[i].startSpot.y);
    }
}

//==========================================================================
//
// UnarchivePolyobjs
//
//==========================================================================

static void UnarchivePolyobjs(void)
{
    int i;
    fixed_t deltaX;
    fixed_t deltaY;

    AssertSegment(ASEG_POLYOBJS);
    if (SV_ReadLong() != po_NumPolyobjs)
    {
        I_Error("UnarchivePolyobjs: Bad polyobj count");
    }
    for (i = 0; i < po_NumPolyobjs; i++)
    {
        if (SV_ReadLong() != polyobjs[i].tag)
        {
            I_Error("UnarchivePolyobjs: Invalid polyobj tag");
        }
        PO_RotatePolyobj(polyobjs[i].tag, (angle_t) SV_ReadLong(), false);
        deltaX = SV_ReadLong() - polyobjs[i].startSpot.x;
        deltaY = SV_ReadLong() - polyobjs[i].startSpot.y;
        PO_MovePolyobj(polyobjs[i].tag, deltaX, deltaY, false);
    }
}

//==========================================================================
//
// AssertSegment
//
//==========================================================================

static void AssertSegment(gameArchiveSegment_t segType)
{
    if (SV_ReadLong() != segType)
    {
        I_Error("Corrupt save game: Segment [%d] failed alignment check",
                segType);
    }
}

//==========================================================================
//
// SV_ClearSaveSlot
//
// Deletes all save game files associated with a slot number.
//
//==========================================================================

void SV_ClearSaveSlot(int slot)
{
    int i;
    char fileName[100];

    for (i = 0; i < MAX_MAPS; i++)
    {
        M_snprintf(fileName, sizeof(fileName),
                   "%shex%d%02d.hxs", SavePath, slot, i);
        M_remove(fileName);
    }
    M_snprintf(fileName, sizeof(fileName), "%shex%d.hxs", SavePath, slot);
    M_remove(fileName);
}

//==========================================================================
//
// CopySaveSlot
//
// Copies all the save game files from one slot to another.
//
//==========================================================================

static void CopySaveSlot(int sourceSlot, int destSlot)
{
    int i;
    char sourceName[100];
    char destName[100];

    for (i = 0; i < MAX_MAPS; i++)
    {
        M_snprintf(sourceName, sizeof(sourceName),
                   "%shex%d%02d.hxs", SavePath, sourceSlot, i);
        if (ExistingFile(sourceName))
        {
            M_snprintf(destName, sizeof(destName),
                       "%shex%d%02d.hxs", SavePath, destSlot, i);
            CopyFile(sourceName, destName);
        }
    }
    M_snprintf(sourceName, sizeof(sourceName),
               "%shex%d.hxs", SavePath, sourceSlot);
    if (ExistingFile(sourceName))
    {
        M_snprintf(destName, sizeof(destName),
                   "%shex%d.hxs", SavePath, destSlot);
        CopyFile(sourceName, destName);
    }
    else
    {
        I_Error("Could not load savegame %s", sourceName);
    }
}

//==========================================================================
//
// CopyFile
//
// This function was rewritten to copy files with minimal strain on zone
// allocation and allow for big maps that technically work in vanilla to
// save without error.
//==========================================================================

static void CopyFile(char *source_name, char *dest_name)
{
    const int BUFFER_CHUNK_SIZE = 0x10000;

    byte *buffer;
    int file_length, file_remaining;
    FILE *read_handle, *write_handle;
    int buf_count, read_count, write_count;

    read_handle = M_fopen(source_name, "rb");
    if (read_handle == NULL)
    {
        I_Error ("Couldn't read file %s", source_name);
    }
    file_length = file_remaining = M_FileLength(read_handle);

    // Vanilla savegame emulation.
    //
    // CopyFile() typically calls M_ReadFile() which stores the entire file
    // in memory: Chocolate Hexen should force an allocation error here
    // whenever it's appropriate.

    if (vanilla_savegame_limit)
    {
        buffer = Z_Malloc(file_length, PU_STATIC, NULL);
        Z_Free(buffer);
    }

    write_handle = M_fopen(dest_name, "wb");
    if (write_handle == NULL)
    {
        I_Error ("Couldn't read file %s", dest_name);
    }

    buffer = Z_Malloc (BUFFER_CHUNK_SIZE, PU_STATIC, NULL);

    do
    {
        buf_count = BUFFER_CHUNK_SIZE;
        if( file_remaining < BUFFER_CHUNK_SIZE)
        {
            buf_count = file_remaining;
        }

        read_count = fread(buffer, 1, buf_count, read_handle);
        if (read_count < buf_count)
        {
            I_Error ("Couldn't read file %s", source_name);
        }

        write_count = fwrite(buffer, 1, buf_count, write_handle);
        if (write_count < buf_count)
        {
            I_Error ("Couldn't write to file %s", dest_name);
        }

        file_remaining -= buf_count;
    } while (file_remaining > 0);

    Z_Free(buffer);
    fclose(read_handle);
    fclose(write_handle);
}

//==========================================================================
//
// ExistingFile
//
//==========================================================================

static boolean ExistingFile(char *name)
{
    FILE *fp;

    if ((fp = M_fopen(name, "rb")) != NULL)
    {
        fclose(fp);
        return true;
    }
    else
    {
        return false;
    }
}

//==========================================================================
//
// SV_Open
//
//==========================================================================

static void SV_OpenRead(char *fileName)
{
    SavingFP = M_fopen(fileName, "rb");

    // Should never happen, only if hex6.hxs cannot ever be created.
    if (SavingFP == NULL)
    {
        I_Error("Could not load savegame %s", fileName);
    }
}

static void SV_OpenWrite(char *fileName)
{
    SavingFP = M_fopen(fileName, "wb");
}

//==========================================================================
//
// SV_Close
//
//==========================================================================

static void SV_Close(void)
{
    if (SavingFP)
    {
        fclose(SavingFP);
        SavingFP = NULL;
    }
}

//==========================================================================
//
// SV_Read
//
//==========================================================================

static void SV_Read(void *buffer, int size)
{
    int retval = fread(buffer, 1, size, SavingFP);
    if (retval != size)
    {
        I_Error("Incomplete read in SV_Read: Expected %d, got %d bytes",
            size, retval);
    }
}

static byte SV_ReadByte(void)
{
    byte result;
    SV_Read(&result, sizeof(byte));
    return result;
}

static uint16_t SV_ReadWord(void)
{
    uint16_t result;
    SV_Read(&result, sizeof(unsigned short));
    return SHORT(result);
}

static uint32_t SV_ReadLong(void)
{
    uint32_t result;
    SV_Read(&result, sizeof(int));
    return LONG(result);
}

static void *SV_ReadPtr(void)
{
    return (void *) (intptr_t) SV_ReadLong();
}

//==========================================================================
//
// SV_Write
//
//==========================================================================

static void SV_Write(const void *buffer, int size)
{
    fwrite(buffer, size, 1, SavingFP);
}

static void SV_WriteByte(byte val)
{
    fwrite(&val, sizeof(byte), 1, SavingFP);
}

static void SV_WriteWord(unsigned short val)
{
    val = SHORT(val);
    fwrite(&val, sizeof(unsigned short), 1, SavingFP);
}

static void SV_WriteLong(unsigned int val)
{
    val = LONG(val);
    fwrite(&val, sizeof(int), 1, SavingFP);
}

static void SV_WritePtr(void *val)
{
    long ptr;

    // Write a pointer value. In Vanilla Hexen pointers are 32-bit but
    // nowadays they might be larger. Whatever value we write here isn't
    // going to be much use when we reload the game.

    ptr = (long)(intptr_t) val;
    SV_WriteLong((unsigned int) (ptr & 0xffffffff));
}
