//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
//-------------------------------------------------------------------------

#include "duke3d.h"
#include "gamedef.h"
#include "premap.h"
#include "menus.h"
#include "prlights.h"

extern char *bitptr;

#define BITPTR_POINTER 1

void ReadSaveGameHeaders(void)
{
    int32_t dummy,j;
    int32_t i;
    char fn[13];
    int32_t fil;

    Bstrcpy(fn,"egam_.sav");

    for (i=0; i<10; i++)
    {
        fn[4] = i+'0';
        if ((fil = kopen4loadfrommod(fn,0)) == -1) continue;
        if (kdfread(&j,sizeof(int32_t),1,fil) != 1)
        {
            kclose(fil);
            continue;
        }
        if (kdfread(g_szBuf,j,1,fil) != 1)
        {
            kclose(fil);
            continue;
        }
        if (kdfread(&dummy,sizeof(dummy),1,fil) != 1)
        {
            kclose(fil);
            continue;
        }
        if (dummy != BYTEVERSION)
        {
            kclose(fil);
            continue;
        }
        if (kdfread(&dummy,sizeof(dummy),1,fil) != 1)
        {
            kclose(fil);
            continue;
        }
        if (kdfread(&ud.savegame[i][0],21,1,fil) != 1)
        {
            ud.savegame[i][0] = 0;
        }
        else ud.savegame[i][19] = 0;

        kclose(fil);
    }
}

int32_t G_LoadSaveHeader(char spot,struct savehead *saveh)
{
    char fn[13];
    int32_t fil;
    int32_t bv;

    strcpy(fn, "egam0.sav");
    fn[4] = spot+'0';

    if ((fil = kopen4loadfrommod(fn,0)) == -1) return(-1);

    walock[TILE_LOADSHOT] = 255;

    if (kdfread(&bv,sizeof(bv),1,fil) != 1) goto corrupt;
    if (kdfread(g_szBuf,bv,1,fil) != 1) goto corrupt;
    g_szBuf[bv]=0;
    //    AddLog(g_szBuf);

    if (kdfread(&bv,sizeof(bv),1,fil) != 1) goto corrupt;
    /*    if (bv != BYTEVERSION)
        {
            P_DoQuote(QUOTE_SAVE_BAD_VERSION,g_player[myconnectindex].ps);
            kclose(fil);
            return 1;
        }*/

    if (kdfread(&saveh->numplr,sizeof(int32_t),1,fil) != 1) goto corrupt;

    if (kdfread(saveh->name,21,1,fil) != 1) goto corrupt;
    if (kdfread(&saveh->volnum,sizeof(int32_t),1,fil) != 1) goto corrupt;
    if (kdfread(&saveh->levnum,sizeof(int32_t),1,fil) != 1) goto corrupt;
    if (kdfread(&saveh->plrskl,sizeof(int32_t),1,fil) != 1) goto corrupt;
    if (kdfread(saveh->boardfn,BMAX_PATH,1,fil) != 1) goto corrupt;

    if (waloff[TILE_LOADSHOT] == 0) allocache(&waloff[TILE_LOADSHOT],320*200,&walock[TILE_LOADSHOT]);
    tilesizx[TILE_LOADSHOT] = 200;
    tilesizy[TILE_LOADSHOT] = 320;
    if (kdfread((char *)waloff[TILE_LOADSHOT],320,200,fil) != 200) goto corrupt;
    invalidatetile(TILE_LOADSHOT,0,255);

    kclose(fil);

    return(0);
corrupt:
    kclose(fil);
    return 1;
}

int32_t G_LoadPlayer(int32_t spot)
{
    int32_t k;
    char fn[13];
    char mpfn[13];
    char *fnptr, *scriptptrs;
    int32_t fil, bv, i, x;
    intptr_t j;
    int32_t nump;

    strcpy(fn, "egam0.sav");
    strcpy(mpfn, "egamA_00.sav");

    fnptr = fn;
    fn[4] = spot + '0';

    if ((fil = kopen4loadfrommod(fnptr,0)) == -1) return(-1);

    ready2send = 0;

    if (kdfread(&bv,sizeof(bv),1,fil) != 1) goto corrupt;
    if (kdfread(g_szBuf,bv,1,fil) != 1) goto corrupt;
    g_szBuf[bv]=0;
    //    AddLog(g_szBuf);

    if (kdfread(&bv,sizeof(bv),1,fil) != 1) return -1;
    if (bv != BYTEVERSION)
    {
        P_DoQuote(QUOTE_SAVE_BAD_VERSION,g_player[myconnectindex].ps);
        kclose(fil);
        ototalclock = totalclock;
        ready2send = 1;
        return 1;
    }

    if (kdfread(&nump,sizeof(nump),1,fil) != 1) return -1;
    if (nump != ud.multimode)
    {
        kclose(fil);
        ototalclock = totalclock;
        ready2send = 1;
        P_DoQuote(QUOTE_SAVE_BAD_PLAYERS,g_player[myconnectindex].ps);
        return 1;
    }
    else ud.multimode = nump;

    if (numplayers > 1)
    {
        pub = NUMPAGES;
        pus = NUMPAGES;
        G_UpdateScreenArea();
        G_DrawBackground();
        menutext(160,100,0,0,"LOADING...");
        nextpage();
    }

    Net_WaitForServer();

    FX_StopAllSounds();
    S_ClearSoundLocks();

    if (numplayers > 1)
    {
        if (kdfread(&buf,21,1,fil) != 1) goto corrupt;
    }
    else
    {
        if (kdfread(&ud.savegame[spot][0],21,1,fil) != 1) goto corrupt;
        ud.savegame[spot][19] = 0;
    }


    if (kdfread(&ud.volume_number,sizeof(ud.volume_number),1,fil) != 1) goto corrupt;
    if (kdfread(&ud.level_number,sizeof(ud.level_number),1,fil) != 1) goto corrupt;
    if (kdfread(&ud.player_skill,sizeof(ud.player_skill),1,fil) != 1) goto corrupt;
    if (kdfread(&boardfilename[0],BMAX_PATH,1,fil) != 1) goto corrupt;

    currentboardfilename[0] = 0;

    if (boardfilename[0])
        strcpy(currentboardfilename, boardfilename);
    else if (MapInfo[(ud.volume_number * MAXLEVELS) + ud.level_number].filename)
        strcpy(currentboardfilename, MapInfo[(ud.volume_number * MAXLEVELS) + ud.level_number].filename);

    if (currentboardfilename[0])
    {
        char *p;

        p = Bstrrchr(currentboardfilename,'.');
        if (!p) strcat(currentboardfilename,".mhk");
        else
        {
            p[1]='m';
            p[2]='h';
            p[3]='k';
            p[4]=0;
        }

        loadmaphack(currentboardfilename);
    }

    Bmemcpy(&currentboardfilename[0],&boardfilename[0],BMAX_PATH);

    ud.m_level_number = ud.level_number;
    ud.m_volume_number = ud.volume_number;
    ud.m_player_skill = ud.player_skill;

    //Fake read because lseek won't work with compression
    walock[TILE_LOADSHOT] = 1;
    if (waloff[TILE_LOADSHOT] == 0) allocache(&waloff[TILE_LOADSHOT],320*200,&walock[TILE_LOADSHOT]);
    tilesizx[TILE_LOADSHOT] = 200;
    tilesizy[TILE_LOADSHOT] = 320;
    if (kdfread((char *)waloff[TILE_LOADSHOT],320,200,fil) != 200) goto corrupt;
    invalidatetile(TILE_LOADSHOT,0,255);

    if (kdfread(&numwalls,sizeof(numwalls),1,fil) != 1) goto corrupt;
    if (kdfread(&wall[0],sizeof(walltype),MAXWALLS,fil) != MAXWALLS) goto corrupt;
    if (kdfread(&numsectors,sizeof(numsectors),1,fil) != 1) goto corrupt;
    if (kdfread(&sector[0],sizeof(sectortype),MAXSECTORS,fil) != MAXSECTORS) goto corrupt;
    if (kdfread(&sprite[0],sizeof(spritetype),MAXSPRITES,fil) != MAXSPRITES) goto corrupt;
    if (kdfread(&spriteext[0],sizeof(spriteext_t),MAXSPRITES,fil) != MAXSPRITES) goto corrupt;

#ifdef POLYMER
    if (kdfread(&lightcount,sizeof(lightcount),1,fil) != 1) goto corrupt;
    if (kdfread(&prlights[0],sizeof(_prlight),lightcount,fil) != lightcount) goto corrupt;
#else
    if (kdfread(&i,sizeof(int32_t),1,fil) != 1) goto corrupt;
#endif // POLYMER

#ifdef USE_OPENGL
    for (i=0; i<MAXSPRITES; i++)
        if (spriteext[i].mdanimtims)
            spriteext[i].mdanimtims+=mdtims;
#endif
    if (kdfread(&headspritesect[0],sizeof(headspritesect[0]),MAXSECTORS+1,fil) != MAXSECTORS+1) goto corrupt;
    if (kdfread(&prevspritesect[0],sizeof(prevspritesect[0]),MAXSPRITES,fil) != MAXSPRITES) goto corrupt;
    if (kdfread(&nextspritesect[0],sizeof(nextspritesect[0]),MAXSPRITES,fil) != MAXSPRITES) goto corrupt;
    if (kdfread(&headspritestat[STAT_DEFAULT],sizeof(headspritestat[STAT_DEFAULT]),MAXSTATUS+1,fil) != MAXSTATUS+1) goto corrupt;
    if (kdfread(&prevspritestat[STAT_DEFAULT],sizeof(prevspritestat[STAT_DEFAULT]),MAXSPRITES,fil) != MAXSPRITES) goto corrupt;
    if (kdfread(&nextspritestat[STAT_DEFAULT],sizeof(nextspritestat[STAT_DEFAULT]),MAXSPRITES,fil) != MAXSPRITES) goto corrupt;
    if (kdfread(&g_numCyclers,sizeof(g_numCyclers),1,fil) != 1) goto corrupt;
    if (kdfread(&cyclers[0][0],sizeof(cyclers[0][0])*6,MAXCYCLERS,fil) != MAXCYCLERS) goto corrupt;
    for (i=0; i<nump; i++)
        if (kdfread(g_player[i].ps,sizeof(DukePlayer_t),1,fil) != 1) goto corrupt;
    if (kdfread(&g_playerSpawnPoints,sizeof(g_playerSpawnPoints),1,fil) != 1) goto corrupt;
    if (kdfread(&g_numAnimWalls,sizeof(g_numAnimWalls),1,fil) != 1) goto corrupt;
    if (kdfread(&animwall,sizeof(animwall),1,fil) != 1) goto corrupt;
    if (kdfread(&msx[0],sizeof(int32_t),sizeof(msx)/sizeof(int32_t),fil) != sizeof(msx)/sizeof(int32_t)) goto corrupt;
    if (kdfread(&msy[0],sizeof(int32_t),sizeof(msy)/sizeof(int32_t),fil) != sizeof(msy)/sizeof(int32_t)) goto corrupt;
    if (kdfread((int16_t *)&g_spriteDeleteQueuePos,sizeof(int16_t),1,fil) != 1) goto corrupt;
    if (kdfread((int16_t *)&g_spriteDeleteQueueSize,sizeof(int16_t),1,fil) != 1) goto corrupt;
    if (kdfread((int16_t *)&SpriteDeletionQueue[0],sizeof(int16_t),g_spriteDeleteQueueSize,fil) != g_spriteDeleteQueueSize) goto corrupt;
    if (kdfread(&g_mirrorCount,sizeof(int16_t),1,fil) != 1) goto corrupt;
    if (kdfread(&g_mirrorWall[0],sizeof(int16_t),64,fil) != 64) goto corrupt;
    if (kdfread(&g_mirrorSector[0],sizeof(int16_t),64,fil) != 64) goto corrupt;
    if (kdfread(&show2dsector[0],sizeof(uint8_t),MAXSECTORS>>3,fil) != (MAXSECTORS>>3)) goto corrupt;
    if (kdfread(&ActorType[0],sizeof(uint8_t),MAXTILES,fil) != MAXTILES) goto corrupt;

    if (kdfread(&g_numClouds,sizeof(g_numClouds),1,fil) != 1) goto corrupt;
    if (kdfread(&clouds[0],sizeof(int16_t)<<7,1,fil) != 1) goto corrupt;
    if (kdfread(&cloudx[0],sizeof(int16_t)<<7,1,fil) != 1) goto corrupt;
    if (kdfread(&cloudy[0],sizeof(int16_t)<<7,1,fil) != 1) goto corrupt;

    if (kdfread(&g_scriptSize,sizeof(g_scriptSize),1,fil) != 1) goto corrupt;
    if (!g_scriptSize) goto corrupt;
    scriptptrs = Bcalloc(1,g_scriptSize * sizeof(scriptptrs));
    Bfree(bitptr);
    bitptr = Bcalloc(1,(((g_scriptSize+7)>>3)+1) * sizeof(uint8_t));
    if (kdfread(&bitptr[0],sizeof(uint8_t),(g_scriptSize+7)>>3,fil) != ((g_scriptSize+7)>>3)) goto corrupt;
    if (script != NULL)
        Bfree(script);
    script = Bcalloc(1,g_scriptSize * sizeof(intptr_t));
    if (kdfread(&script[0],sizeof(script),g_scriptSize,fil) != g_scriptSize) goto corrupt;
    for (i=0; i<g_scriptSize; i++)
        if (bitptr[i>>3]&(BITPTR_POINTER<<(i&7)))
        {
            j = (intptr_t)script[i]+(intptr_t)&script[0];
            script[i] = j;
        }

    if (kdfread(&actorscrptr[0],sizeof(actorscrptr[0]),MAXTILES,fil) != MAXTILES) goto corrupt;
    for (i=0; i<MAXTILES; i++)
        if (actorscrptr[i])
        {
            j = (intptr_t)actorscrptr[i]+(intptr_t)&script[0];
            actorscrptr[i] = (intptr_t *)j;
        }
    if (kdfread(&actorLoadEventScrptr[0],sizeof(&actorLoadEventScrptr[0]),MAXTILES,fil) != MAXTILES) goto corrupt;
    for (i=0; i<MAXTILES; i++)
        if (actorLoadEventScrptr[i])
        {
            j = (intptr_t)actorLoadEventScrptr[i]+(intptr_t)&script[0];
            actorLoadEventScrptr[i] = (intptr_t *)j;
        }

    scriptptrs = Brealloc(scriptptrs, MAXSPRITES * sizeof(scriptptrs));

    if (kdfread(&scriptptrs[0],sizeof(scriptptrs),MAXSPRITES,fil) != MAXSPRITES) goto corrupt;
    if (kdfread(&actor[0],sizeof(actor_t),MAXSPRITES,fil) != MAXSPRITES) goto corrupt;

    for (i=0; i<MAXSPRITES; i++)
    {
        j = (intptr_t)(&script[0]);
        if (scriptptrs[i]&1) T2 += j;
        if (scriptptrs[i]&2) T5 += j;
        if (scriptptrs[i]&4) T6 += j;
        actor[i].projectile = &SpriteProjectile[i];
    }

    if (kdfread(&lockclock,sizeof(lockclock),1,fil) != 1) goto corrupt;
    if (kdfread(&pskybits,sizeof(pskybits),1,fil) != 1) goto corrupt;
    if (kdfread(&pskyoff[0],sizeof(pskyoff[0]),MAXPSKYTILES,fil) != MAXPSKYTILES) goto corrupt;

    if (kdfread(&g_animateCount,sizeof(g_animateCount),1,fil) != 1) goto corrupt;
    if (kdfread(&animatesect[0],sizeof(animatesect[0]),MAXANIMATES,fil) != MAXANIMATES) goto corrupt;
    if (kdfread(&animateptr[0],sizeof(animateptr[0]),MAXANIMATES,fil) != MAXANIMATES) goto corrupt;
    for (i = g_animateCount-1; i>=0; i--) animateptr[i] = (int32_t *)((intptr_t)animateptr[i]+(intptr_t)(&sector[0]));
    if (kdfread(&animategoal[0],sizeof(animategoal[0]),MAXANIMATES,fil) != MAXANIMATES) goto corrupt;
    if (kdfread(&animatevel[0],sizeof(animatevel[0]),MAXANIMATES,fil) != MAXANIMATES) goto corrupt;

    if (kdfread(&g_earthquakeTime,sizeof(g_earthquakeTime),1,fil) != 1) goto corrupt;
    if (kdfread(&ud.from_bonus,sizeof(ud.from_bonus),1,fil) != 1) goto corrupt;
    if (kdfread(&ud.secretlevel,sizeof(ud.secretlevel),1,fil) != 1) goto corrupt;
    if (kdfread(&ud.respawn_monsters,sizeof(ud.respawn_monsters),1,fil) != 1) goto corrupt;
    ud.m_respawn_monsters = ud.respawn_monsters;
    if (kdfread(&ud.respawn_items,sizeof(ud.respawn_items),1,fil) != 1) goto corrupt;
    ud.m_respawn_items = ud.respawn_items;
    if (kdfread(&ud.respawn_inventory,sizeof(ud.respawn_inventory),1,fil) != 1) goto corrupt;
    ud.m_respawn_inventory = ud.respawn_inventory;

    if (kdfread(&ud.god,sizeof(ud.god),1,fil) != 1) goto corrupt;
    if (kdfread(&ud.auto_run,sizeof(ud.auto_run),1,fil) != 1) goto corrupt;
    if (kdfread(&ud.crosshair,sizeof(ud.crosshair),1,fil) != 1) goto corrupt;
    if (kdfread(&ud.monsters_off,sizeof(ud.monsters_off),1,fil) != 1) goto corrupt;
    ud.m_monsters_off = ud.monsters_off;
    if (kdfread(&ud.last_level,sizeof(ud.last_level),1,fil) != 1) goto corrupt;
    if (kdfread(&ud.eog,sizeof(ud.eog),1,fil) != 1) goto corrupt;

    if (kdfread(&ud.coop,sizeof(ud.coop),1,fil) != 1) goto corrupt;
    ud.m_coop = ud.coop;
    if (kdfread(&ud.marker,sizeof(ud.marker),1,fil) != 1) goto corrupt;
    ud.m_marker = ud.marker;
    if (kdfread(&ud.ffire,sizeof(ud.ffire),1,fil) != 1) goto corrupt;
    ud.m_ffire = ud.ffire;

    if (kdfread(&camsprite,sizeof(camsprite),1,fil) != 1) goto corrupt;
    if (kdfread(&connecthead,sizeof(connecthead),1,fil) != 1) goto corrupt;
    if (kdfread(connectpoint2,sizeof(connectpoint2),1,fil) != 1) goto corrupt;
    if (kdfread(&g_numPlayerSprites,sizeof(g_numPlayerSprites),1,fil) != 1) goto corrupt;
    for (i=0; i<MAXPLAYERS; i++)
        if (kdfread((int16_t *)&g_player[i].frags[0],sizeof(g_player[i].frags),1,fil) != 1) goto corrupt;

    if (kdfread(&randomseed,sizeof(randomseed),1,fil) != 1) goto corrupt;
    if (kdfread(&g_globalRandom,sizeof(g_globalRandom),1,fil) != 1) goto corrupt;
    if (kdfread(&parallaxyscale,sizeof(parallaxyscale),1,fil) != 1) goto corrupt;

    if (kdfread(&SpriteProjectile[0],sizeof(projectile_t),MAXSPRITES,fil) != MAXSPRITES) goto corrupt;
    if (kdfread(&ProjectileData[0],sizeof(projectile_t),MAXTILES,fil) != MAXTILES) goto corrupt;
    if (kdfread(&DefaultProjectileData[0],sizeof(projectile_t),MAXTILES,fil) != MAXTILES) goto corrupt;

    if (kdfread(&SpriteFlags[0],sizeof(SpriteFlags[0]),MAXTILES,fil) != MAXTILES) goto corrupt;

    if (kdfread(&SpriteCacheList[0],sizeof(SpriteCacheList[0]),MAXTILES,fil) != MAXTILES) goto corrupt;

    if (kdfread(&i,sizeof(int32_t),1,fil) != 1) goto corrupt;

    while (i != MAXQUOTES)
    {
        if (ScriptQuotes[i] != NULL)
            Bfree(ScriptQuotes[i]);

        ScriptQuotes[i] = Bcalloc(MAXQUOTELEN,sizeof(uint8_t));

        if (kdfread((char *)ScriptQuotes[i],MAXQUOTELEN,1,fil) != 1) goto corrupt;
        if (kdfread(&i,sizeof(int32_t),1,fil) != 1) goto corrupt;
    }

    if (kdfread(&g_numQuoteRedefinitions,sizeof(g_numQuoteRedefinitions),1,fil) != 1) goto corrupt;

    for (i=0; i<g_numQuoteRedefinitions; i++)
    {
        if (ScriptQuoteRedefinitions[i] != NULL)
            Bfree(ScriptQuoteRedefinitions[i]);

        ScriptQuoteRedefinitions[i] = Bcalloc(MAXQUOTELEN,sizeof(uint8_t));

        if (kdfread((char *)ScriptQuoteRedefinitions[i],MAXQUOTELEN,1,fil) != 1) goto corrupt;
    }

    if (kdfread(&DynamicTileMap[0],sizeof(DynamicTileMap[0]),MAXTILES,fil) != MAXTILES) goto corrupt;

    if (kdfread(&ud.noexits,sizeof(ud.noexits),1,fil) != 1) goto corrupt;
    ud.m_noexits = ud.noexits;


    if (Gv_ReadSave(fil, 0)) goto corrupt;

    kclose(fil);

    if (g_player[myconnectindex].ps->over_shoulder_on != 0)
    {
        g_cameraDistance = 0;
        g_cameraClock = 0;
        g_player[myconnectindex].ps->over_shoulder_on = 1;
    }

    screenpeek = myconnectindex;

    clearbufbyte(gotpic,sizeof(gotpic),0L);
    S_ClearSoundLocks();
    G_CacheMapData();

    i = g_musicIndex;
    g_musicIndex = (ud.volume_number*MAXLEVELS) + ud.level_number;

    if (boardfilename[0] != 0 && ud.level_number == 7 && ud.volume_number == 0)
    {
        char *p;
        char levname[BMAX_PATH];
        int32_t fil;

        strcpy(levname, boardfilename);
        // usermap music based on map filename
        Bcorrectfilename(levname,0);
        p = Bstrrchr(levname,'.');
        if (!p) strcat(levname,".ogg");
        else
        {
            p[1]='o';
            p[2]='g';
            p[3]='g';
            p[4]=0;
        }

        fil = kopen4loadfrommod(levname,0);

        if (fil > -1)
        {
            kclose(fil);
            if (MapInfo[ud.level_number].alt_musicfn == NULL)
                MapInfo[ud.level_number].alt_musicfn = Bcalloc(Bstrlen(levname)+1,sizeof(uint8_t));
            else if ((Bstrlen(levname)+1) > sizeof(MapInfo[ud.level_number].alt_musicfn))
                MapInfo[ud.level_number].alt_musicfn = Brealloc(MapInfo[ud.level_number].alt_musicfn,(Bstrlen(levname)+1));
            Bstrcpy(MapInfo[ud.level_number].alt_musicfn,levname);
        }
        else if (MapInfo[ud.level_number].alt_musicfn != NULL)
        {
            Bfree(MapInfo[ud.level_number].alt_musicfn);
            MapInfo[ud.level_number].alt_musicfn = NULL;
        }

        p[1]='m';
        p[2]='i';
        p[3]='d';
        p[4]=0;

        fil = kopen4loadfrommod(levname,0);

        if (fil == -1)
            Bsprintf(levname,"dethtoll.mid");
        else kclose(fil);

        if (MapInfo[ud.level_number].musicfn == NULL)
            MapInfo[ud.level_number].musicfn = Bcalloc(Bstrlen(levname)+1,sizeof(uint8_t));
        else if ((Bstrlen(levname)+1) > sizeof(MapInfo[ud.level_number].musicfn))
            MapInfo[ud.level_number].musicfn = Brealloc(MapInfo[ud.level_number].musicfn,(Bstrlen(levname)+1));
        Bstrcpy(MapInfo[ud.level_number].musicfn,levname);
    }

    if (ud.config.MusicToggle)
    {
        if (MapInfo[(uint8_t)g_musicIndex].musicfn != NULL &&
            (i != g_musicIndex || MapInfo[MAXVOLUMES*MAXLEVELS+2].alt_musicfn))
        {
            S_StopMusic();
            S_PlayMusic(&MapInfo[(uint8_t)g_musicIndex].musicfn[0],g_musicIndex);
        }

        S_PauseMusic(0);
    }

    g_player[myconnectindex].ps->gm = MODE_GAME;
    ud.recstat = 0;

    if (g_player[myconnectindex].ps->jetpack_on)
        A_PlaySound(DUKE_JETPACK_IDLE,g_player[myconnectindex].ps->i);

    g_restorePalette = 1;
    P_UpdateScreenPal(g_player[myconnectindex].ps);
    G_UpdateScreenArea();

    FX_SetReverb(0);

    if (ud.lockout == 0)
    {
        for (x=0; x<g_numAnimWalls; x++)
            if (wall[animwall[x].wallnum].extra >= 0)
                wall[animwall[x].wallnum].picnum = wall[animwall[x].wallnum].extra;
    }
    else
    {
        for (x=0; x<g_numAnimWalls; x++)
            switch (DynamicTileMap[wall[animwall[x].wallnum].picnum])
            {
            case FEMPIC1__STATIC:
                wall[animwall[x].wallnum].picnum = BLANKSCREEN;
                break;
            case FEMPIC2__STATIC:
            case FEMPIC3__STATIC:
                wall[animwall[x].wallnum].picnum = SCREENBREAK6;
                break;
            }
    }

    g_numInterpolations = 0;
    startofdynamicinterpolations = 0;

    k = headspritestat[STAT_EFFECTOR];
    while (k >= 0)
    {
        switch (sprite[k].lotag)
        {
        case 31:
            G_SetInterpolation(&sector[sprite[k].sectnum].floorz);
            break;
        case 32:
            G_SetInterpolation(&sector[sprite[k].sectnum].ceilingz);
            break;
        case 25:
            G_SetInterpolation(&sector[sprite[k].sectnum].floorz);
            G_SetInterpolation(&sector[sprite[k].sectnum].ceilingz);
            break;
        case 17:
            G_SetInterpolation(&sector[sprite[k].sectnum].floorz);
            G_SetInterpolation(&sector[sprite[k].sectnum].ceilingz);
            break;
        case 0:
        case 5:
        case 6:
        case 11:
        case 14:
        case 15:
        case 16:
        case 26:
        case 30:
            Sect_SetInterpolation(k);
            break;
        }

        k = nextspritestat[k];
    }

    for (i=g_numInterpolations-1; i>=0; i--) bakipos[i] = *curipos[i];
    for (i = g_animateCount-1; i>=0; i--)
        G_SetInterpolation(animateptr[i]);

    g_showShareware = 0;
    everyothertime = 0;

//    clearbufbyte(playerquitflag,MAXPLAYERS,0x01010101);

    for (i=0; i<MAXPLAYERS; i++)
        clearbufbyte(&g_player[i].playerquitflag,1,0x01010101);

    Net_ResetPrediction();

    ready2send = 1;

    G_ClearFIFO();
    Net_WaitForServer();

    G_ResetTimers();

#ifdef POLYMER
    if (getrendermode() == 4)
    {
        int32_t i = 0;

        polymer_loadboard();

        while (i < MAXSPRITES)
        {
            if (actor[i].lightptr)
            {
                actor[i].lightptr = NULL;
                actor[i].lightId = -1;
            }
            i++;
        }
    }
#endif

    return(0);
corrupt:
    Bsprintf(tempbuf,"Save game file \"%s\" is corrupt or of the wrong version.",fnptr);
    G_GameExit(tempbuf);
    return -1;
}

int32_t G_SavePlayer(int32_t spot)
{
    int32_t i;
    intptr_t j;
    char fn[13];
    char mpfn[13];
    char *fnptr, *scriptptrs;
    FILE *fil;
    int32_t bv = BYTEVERSION;

    strcpy(fn, "egam0.sav");
    strcpy(mpfn, "egamA_00.sav");

    Net_WaitForServer();

    fnptr = fn;
    fn[4] = spot + '0';

    {
        char temp[BMAX_PATH];
        if (g_modDir[0] != '/')
            Bsprintf(temp,"%s/%s",g_modDir,fnptr);
        else Bsprintf(temp,"%s",fnptr);
        if ((fil = fopen(temp,"wb")) == 0) return(-1);
    }

    ready2send = 0;

    Bsprintf(g_szBuf,"EDuke32");
    i=strlen(g_szBuf);
    dfwrite(&i,sizeof(i),1,fil);
    dfwrite(g_szBuf,i,1,fil);

    dfwrite(&bv,sizeof(bv),1,fil);
    dfwrite(&ud.multimode,sizeof(ud.multimode),1,fil);

    dfwrite(&ud.savegame[spot][0],21,1,fil);
    dfwrite(&ud.volume_number,sizeof(ud.volume_number),1,fil);
    dfwrite(&ud.level_number,sizeof(ud.level_number),1,fil);
    dfwrite(&ud.player_skill,sizeof(ud.player_skill),1,fil);
    dfwrite(&currentboardfilename[0],BMAX_PATH,1,fil);
    if (!waloff[TILE_SAVESHOT])
    {
        walock[TILE_SAVESHOT] = 254;
        allocache(&waloff[TILE_SAVESHOT],200*320,&walock[TILE_SAVESHOT]);
        clearbuf((void *)waloff[TILE_SAVESHOT],(200*320)/4,0);
        walock[TILE_SAVESHOT] = 1;
    }
    dfwrite((char *)waloff[TILE_SAVESHOT],320,200,fil);

    dfwrite(&numwalls,sizeof(numwalls),1,fil);
    dfwrite(&wall[0],sizeof(walltype),MAXWALLS,fil);
    dfwrite(&numsectors,sizeof(numsectors),1,fil);
    dfwrite(&sector[0],sizeof(sectortype),MAXSECTORS,fil);
    dfwrite(&sprite[0],sizeof(spritetype),MAXSPRITES,fil);
#ifdef USE_OPENGL
    for (i=0; i<MAXSPRITES; i++)
        if (spriteext[i].mdanimtims)
        {
            spriteext[i].mdanimtims=spriteext[i].mdanimtims-mdtims;
            if (!spriteext[i].mdanimtims)
                spriteext[i].mdanimtims++;
        }
#endif
    dfwrite(&spriteext[0],sizeof(spriteext_t),MAXSPRITES,fil);

#ifdef POLYMER
    dfwrite(&lightcount,sizeof(lightcount),1,fil);
    dfwrite(&prlights[0],sizeof(_prlight),lightcount,fil);
#else
    i = 0;
    dfwrite(&i,sizeof(int32_t),1,fil);
#endif // POLYMER

#ifdef USE_OPENGL
    for (i=0; i<MAXSPRITES; i++)if (spriteext[i].mdanimtims)spriteext[i].mdanimtims+=mdtims;
#endif
    dfwrite(&headspritesect[0],sizeof(headspritesect[0]),MAXSECTORS+1,fil);
    dfwrite(&prevspritesect[0],sizeof(prevspritesect[0]),MAXSPRITES,fil);
    dfwrite(&nextspritesect[0],sizeof(nextspritesect[0]),MAXSPRITES,fil);
    dfwrite(&headspritestat[STAT_DEFAULT],sizeof(headspritestat[0]),MAXSTATUS+1,fil);
    dfwrite(&prevspritestat[STAT_DEFAULT],sizeof(prevspritestat[0]),MAXSPRITES,fil);
    dfwrite(&nextspritestat[STAT_DEFAULT],sizeof(nextspritestat[0]),MAXSPRITES,fil);
    dfwrite(&g_numCyclers,sizeof(g_numCyclers),1,fil);
    dfwrite(&cyclers[0][0],sizeof(cyclers[0][0])*6,MAXCYCLERS,fil);
    for (i=0; i<ud.multimode; i++)
        dfwrite(g_player[i].ps,sizeof(DukePlayer_t),1,fil);
    dfwrite(&g_playerSpawnPoints,sizeof(g_playerSpawnPoints),1,fil);
    dfwrite(&g_numAnimWalls,sizeof(g_numAnimWalls),1,fil);
    dfwrite(&animwall,sizeof(animwall),1,fil);
    dfwrite(&msx[0],sizeof(int32_t),sizeof(msx)/sizeof(int32_t),fil);
    dfwrite(&msy[0],sizeof(int32_t),sizeof(msy)/sizeof(int32_t),fil);
    dfwrite(&g_spriteDeleteQueuePos,sizeof(int16_t),1,fil);
    dfwrite(&g_spriteDeleteQueueSize,sizeof(int16_t),1,fil);
    dfwrite(&SpriteDeletionQueue[0],sizeof(int16_t),g_spriteDeleteQueueSize,fil);
    dfwrite(&g_mirrorCount,sizeof(int16_t),1,fil);
    dfwrite(&g_mirrorWall[0],sizeof(int16_t),64,fil);
    dfwrite(&g_mirrorSector[0],sizeof(int16_t),64,fil);
    dfwrite(&show2dsector[0],sizeof(uint8_t),MAXSECTORS>>3,fil);
    dfwrite(&ActorType[0],sizeof(uint8_t),MAXTILES,fil);

    dfwrite(&g_numClouds,sizeof(g_numClouds),1,fil);
    dfwrite(&clouds[0],sizeof(int16_t)<<7,1,fil);
    dfwrite(&cloudx[0],sizeof(int16_t)<<7,1,fil);
    dfwrite(&cloudy[0],sizeof(int16_t)<<7,1,fil);

    dfwrite(&g_scriptSize,sizeof(g_scriptSize),1,fil);
    scriptptrs = Bcalloc(1, g_scriptSize * sizeof(scriptptrs));
    for (i=0; i<g_scriptSize; i++)
    {
        if (bitptr[i>>3]&(BITPTR_POINTER<<(i&7)))
        {
//            scriptptrs[i] = 1;
            j = (intptr_t)script[i] - (intptr_t)&script[0];
            script[i] = j;
        }
        //      else scriptptrs[i] = 0;
    }

//    dfwrite(&scriptptrs[0],sizeof(scriptptrs),g_scriptSize,fil);
    dfwrite(&bitptr[0],sizeof(bitptr[0]),(g_scriptSize+7)>>3,fil);
    dfwrite(&script[0],sizeof(script[0]),g_scriptSize,fil);

    for (i=0; i<g_scriptSize; i++)
        if (bitptr[i>>3]&(BITPTR_POINTER<<(i&7)))
        {
            j = script[i]+(intptr_t)&script[0];
            script[i] = j;
        }

    for (i=0; i<MAXTILES; i++)
        if (actorscrptr[i])
        {
            j = (intptr_t)actorscrptr[i]-(intptr_t)&script[0];
            actorscrptr[i] = (intptr_t *)j;
        }
    dfwrite(&actorscrptr[0],sizeof(actorscrptr[0]),MAXTILES,fil);
    for (i=0; i<MAXTILES; i++)
        if (actorscrptr[i])
        {
            j = (intptr_t)actorscrptr[i]+(intptr_t)&script[0];
            actorscrptr[i] = (intptr_t *)j;
        }

    for (i=0; i<MAXTILES; i++)
        if (actorLoadEventScrptr[i])
        {
            j = (intptr_t)actorLoadEventScrptr[i]-(intptr_t)&script[0];
            actorLoadEventScrptr[i] = (intptr_t *)j;
        }
    dfwrite(&actorLoadEventScrptr[0],sizeof(actorLoadEventScrptr[0]),MAXTILES,fil);
    for (i=0; i<MAXTILES; i++)
        if (actorLoadEventScrptr[i])
        {
            j = (intptr_t)actorLoadEventScrptr[i]+(intptr_t)&script[0];
            actorLoadEventScrptr[i] = (intptr_t *)j;
        }

    Bfree(scriptptrs);
    scriptptrs = Bcalloc(1, MAXSPRITES * sizeof(scriptptrs));

    for (i=0; i<MAXSPRITES; i++)
    {
        scriptptrs[i] = 0;

        if (actorscrptr[PN] == 0) continue;

        j = (intptr_t)&script[0];

        if (T2 >= j && T2 < (intptr_t)(&script[g_scriptSize]))
        {
            scriptptrs[i] |= 1;
            T2 -= j;
        }
        if (T5 >= j && T5 < (intptr_t)(&script[g_scriptSize]))
        {
            scriptptrs[i] |= 2;
            T5 -= j;
        }
        if (T6 >= j && T6 < (intptr_t)(&script[g_scriptSize]))
        {
            scriptptrs[i] |= 4;
            T6 -= j;
        }
    }

    dfwrite(&scriptptrs[0],sizeof(scriptptrs),MAXSPRITES,fil);
    dfwrite(&actor[0],sizeof(actor_t),MAXSPRITES,fil);

    for (i=0; i<MAXSPRITES; i++)
    {
        if (actorscrptr[PN] == 0) continue;
        j = (intptr_t)&script[0];

        if (scriptptrs[i]&1)
            T2 += j;
        if (scriptptrs[i]&2)
            T5 += j;
        if (scriptptrs[i]&4)
            T6 += j;
    }

    dfwrite(&lockclock,sizeof(lockclock),1,fil);
    dfwrite(&pskybits,sizeof(pskybits),1,fil);
    dfwrite(&pskyoff[0],sizeof(pskyoff[0]),MAXPSKYTILES,fil);
    dfwrite(&g_animateCount,sizeof(g_animateCount),1,fil);
    dfwrite(&animatesect[0],sizeof(animatesect[0]),MAXANIMATES,fil);
    for (i = g_animateCount-1; i>=0; i--) animateptr[i] = (int32_t *)((intptr_t)animateptr[i]-(intptr_t)(&sector[0]));
    dfwrite(&animateptr[0],sizeof(animateptr[0]),MAXANIMATES,fil);
    for (i = g_animateCount-1; i>=0; i--) animateptr[i] = (int32_t *)((intptr_t)animateptr[i]+(intptr_t)(&sector[0]));
    dfwrite(&animategoal[0],sizeof(animategoal[0]),MAXANIMATES,fil);
    dfwrite(&animatevel[0],sizeof(animatevel[0]),MAXANIMATES,fil);

    dfwrite(&g_earthquakeTime,sizeof(g_earthquakeTime),1,fil);
    dfwrite(&ud.from_bonus,sizeof(ud.from_bonus),1,fil);
    dfwrite(&ud.secretlevel,sizeof(ud.secretlevel),1,fil);
    dfwrite(&ud.respawn_monsters,sizeof(ud.respawn_monsters),1,fil);
    dfwrite(&ud.respawn_items,sizeof(ud.respawn_items),1,fil);
    dfwrite(&ud.respawn_inventory,sizeof(ud.respawn_inventory),1,fil);
    dfwrite(&ud.god,sizeof(ud.god),1,fil);
    dfwrite(&ud.auto_run,sizeof(ud.auto_run),1,fil);
    dfwrite(&ud.crosshair,sizeof(ud.crosshair),1,fil);
    dfwrite(&ud.monsters_off,sizeof(ud.monsters_off),1,fil);
    dfwrite(&ud.last_level,sizeof(ud.last_level),1,fil);
    dfwrite(&ud.eog,sizeof(ud.eog),1,fil);
    dfwrite(&ud.coop,sizeof(ud.coop),1,fil);
    dfwrite(&ud.marker,sizeof(ud.marker),1,fil);
    dfwrite(&ud.ffire,sizeof(ud.ffire),1,fil);
    dfwrite(&camsprite,sizeof(camsprite),1,fil);
    dfwrite(&connecthead,sizeof(connecthead),1,fil);
    dfwrite(connectpoint2,sizeof(connectpoint2),1,fil);
    dfwrite(&g_numPlayerSprites,sizeof(g_numPlayerSprites),1,fil);
    for (i=0; i<MAXPLAYERS; i++)
        dfwrite((int16_t *)&g_player[i].frags[0],sizeof(g_player[i].frags),1,fil);

    dfwrite(&randomseed,sizeof(randomseed),1,fil);
    dfwrite(&g_globalRandom,sizeof(g_globalRandom),1,fil);
    dfwrite(&parallaxyscale,sizeof(parallaxyscale),1,fil);

    dfwrite(&SpriteProjectile[0],sizeof(projectile_t),MAXSPRITES,fil);
    dfwrite(&ProjectileData[0],sizeof(projectile_t),MAXTILES,fil);
    dfwrite(&DefaultProjectileData[0],sizeof(projectile_t),MAXTILES,fil);

    dfwrite(&SpriteFlags[0],sizeof(SpriteFlags[0]),MAXTILES,fil);

    dfwrite(&SpriteCacheList[0],sizeof(SpriteCacheList[0]),MAXTILES,fil);

    for (i=0; i<MAXQUOTES; i++)
    {
        if (ScriptQuotes[i] != NULL)
        {
            dfwrite(&i,sizeof(int32_t),1,fil);
            dfwrite(ScriptQuotes[i],MAXQUOTELEN, 1, fil);
        }
    }
    dfwrite(&i,sizeof(int32_t),1,fil);

    dfwrite(&g_numQuoteRedefinitions,sizeof(g_numQuoteRedefinitions),1,fil);
    for (i=0; i<g_numQuoteRedefinitions; i++)
    {
        if (ScriptQuoteRedefinitions[i] != NULL)
            dfwrite(ScriptQuoteRedefinitions[i],MAXQUOTELEN, 1, fil);
    }

    dfwrite(&DynamicTileMap[0],sizeof(DynamicTileMap[0]),MAXTILES,fil);

    dfwrite(&ud.noexits,sizeof(ud.noexits),1,fil);

    Gv_WriteSave(fil, 0);

    fclose(fil);

    if ((!g_netServer && ud.multimode < 2))
    {
        strcpy(ScriptQuotes[QUOTE_RESERVED4],"GAME SAVED");
        P_DoQuote(QUOTE_RESERVED4,g_player[myconnectindex].ps);
    }

    ready2send = 1;

    Net_WaitForServer();

    ototalclock = totalclock;

    return(0);
}


////////// GENERIC SAVING/LOADING SYSTEM //////////

typedef struct dataspec_
{
    uint32_t flags;
    void *ptr;
    uint32_t size;
    intptr_t cnt;
} dataspec_t;

#define SV_MAJOR_VER 0
#define SV_MINOR_VER 2
#define SV_DEFAULTCOMPRTHRES 8
static uint8_t savegame_diffcompress;  // 0:none, 1:Ken's LZW in cache1d.c
static uint8_t savegame_comprthres;


#define DS_DYNAMIC 1  // dereference .ptr one more time
#define DS_STRING 2
#define DS_CMP 4
// 8
#define DS_CNT(x) ((sizeof(x))<<3)  // .cnt is pointer to...
#define DS_CNT16 16
#define DS_CNT32 32
#define DS_CNTMASK (8|DS_CNT16|DS_CNT32|64)
// 64
#define DS_LOADFN 128  // .ptr is function that is run when loading
#define DS_SAVEFN 256  // .ptr is function that is run when saving
#define DS_NOCHK 1024  // don't check for diffs (and don't write out in dump) since assumed constant throughout demo
#define DS_END (0x70000000)

static int32_t ds_getcnt(const dataspec_t *sp)
{
    switch (sp->flags&DS_CNTMASK)
    {
    case 0: return sp->cnt;
    case DS_CNT16: return *((int16_t *)sp->cnt);
    case DS_CNT32: return *((int32_t *)sp->cnt);
    default: return -1;
    }
}

static void ds_get(const dataspec_t *sp, const void **ptr, int32_t *cnt)
{
    *cnt = ds_getcnt(sp);

    if (sp->flags&DS_DYNAMIC)
        *ptr = *((void **)sp->ptr);
    else
        *ptr = sp->ptr;
}

// write state to file and/or to dump
static uint8_t *writespecdata(const dataspec_t *spec, FILE *fil, uint8_t *dump)
{
    int32_t cnt;
    const void *ptr;
    const dataspec_t *sp=spec;

    for (; sp->flags!=DS_END; sp++)
    {
        if (sp->flags&(DS_SAVEFN|DS_LOADFN))
        {
            if (sp->flags&DS_SAVEFN)
                (*(void ( *)(void))sp->ptr)();
            continue;
        }

        if (!fil && (sp->flags&(DS_NOCHK|DS_CMP|DS_STRING)))
            continue;

        if (sp->flags&DS_STRING)
        {
            fwrite(sp->ptr, Bstrlen(sp->ptr), 1, fil);  // not null-terminated!
            continue;
        }

        ds_get(sp, &ptr, &cnt);
        if (cnt < 0) { OSD_Printf("wsd: cnt=%d, f=0x%x.\n",cnt,sp->flags); continue; }

        if (fil)
        {
            if (((sp->flags&DS_CNTMASK)==0 && sp->size *cnt<=(int32_t)savegame_comprthres)
                    || (sp->flags&DS_CMP))
                fwrite(ptr, sp->size, cnt, fil);
            else
                dfwrite((void *)ptr, sp->size, cnt, fil);
        }

        if (dump && (sp->flags&(DS_NOCHK|DS_CMP))==0)
        {
            Bmemcpy(dump, ptr, sp->size*cnt);
            dump += sp->size*cnt;
        }
    }
    return dump;
}

// let havedump=dumpvar&&*dumpvar
// (fil>=0 && havedump): first restore dump from file, then restore state from dump
// (fil<0 && havedump): only restore state from dump
// (fil>=0 && !havedump): only restore state from file
static int32_t readspecdata(const dataspec_t *spec, int32_t fil, uint8_t **dumpvar)
{
    int32_t cnt, i, j;
    void *ptr;
    uint8_t *dump=dumpvar?*dumpvar:NULL, *mem;
    const dataspec_t *sp=spec;
    static char cmpstrbuf[32];

    for (; sp->flags!=DS_END; sp++)
    {
        if (fil < 0 && sp->flags&(DS_NOCHK|DS_STRING|DS_CMP))  // we're updating
            continue;

        if (sp->flags&(DS_LOADFN|DS_SAVEFN))
        {
            if (sp->flags&DS_LOADFN)
                (*(void ( *)())sp->ptr)();
            continue;
        }

        if (sp->flags&(DS_STRING|DS_CMP))  // DS_STRING and DS_CMP is for static data only
        {
            if (sp->flags&(DS_STRING))
                i = Bstrlen(sp->ptr);
            else
                i = sp->size*sp->cnt;

            j=kread(fil, cmpstrbuf, i);
            if (j!=i || Bmemcmp(sp->ptr, cmpstrbuf, i))
            {
                OSD_Printf("rds: spec=%p, sp=%p ", spec, sp);
                if (j!=i)
                    OSD_Printf("kread returned %d, expected %d.\n", j, i);
                else
                    OSD_Printf("sp->ptr and cmpstrbuf not identical!\n");
//                *(int32_t *)0 = 1;
                return -1;
            }
            continue;
        }

        ds_get(sp, (const void **)&ptr, &cnt);
        if (cnt < 0) { OSD_Printf("rsd: cnt<0... wtf?\n"); return -1; }

        if (fil>=0)
        {
            mem = (dump && (sp->flags&DS_NOCHK)==0) ? dump : ptr;

            if ((sp->flags&DS_CNTMASK)==0 && sp->size *cnt<=(int32_t)savegame_comprthres)
            {
                i = kread(fil, mem, cnt*sp->size);
                j = cnt*sp->size;
            }
            else
            {
                i = kdfread(mem, sp->size, cnt, fil);
                j = cnt;
            }
            if (i!=j)
            {
                OSD_Printf("rsd: spec=%p, sp=%p, mem=%p ", spec, sp, mem);
                OSD_Printf("rsd: %s: read %d, expected %d!\n",
                           ((sp->flags&DS_CNTMASK)==0 && sp->size *cnt<=(int32_t)savegame_comprthres)?
                           "UNCOMP":"COMPR", i, j);

                if (i==-1)
                    perror("read");
//            *(int32_t *)0 = 1;
                return -1;
            }
        }

        if (dump && (sp->flags&DS_NOCHK)==0)
        {
            Bmemcpy(ptr, dump, sp->size*cnt);
            dump += sp->size*cnt;
        }
    }

    if (dumpvar)
        *dumpvar = dump;
    return 0;
}

#define UINT(bits) uint##bits##_t
#define BYTES(bits) (bits>>3)
#define VAL(bits,p) (*(UINT(bits) *)(p))

static void docmpsd(const void *ptr, void *dump, uint32_t size, uint32_t cnt, uint8_t **diffvar)
{
    uint8_t *retdiff = *diffvar;

    // Hail to the C preprocessor, baby!
#define CPSINGLEVAL(Datbits) \
        if (VAL(Datbits, ptr) != VAL(Datbits, dump))  \
        {                                             \
            VAL(Datbits, retdiff) = VAL(Datbits, dump) = VAL(Datbits, ptr); \
            *diffvar = retdiff+BYTES(Datbits);        \
        }

    if (cnt==1)
        switch (size)
        {
        case 8: CPSINGLEVAL(64); return;
        case 4: CPSINGLEVAL(32); return;
        case 2: CPSINGLEVAL(16); return;
        case 1: CPSINGLEVAL(8); return;
        }

#define CPELTS(Idxbits, Datbits) do \
    {                                         \
        for (i=0; i<nelts; i++)               \
        {                                     \
            if (*p!=*op)                      \
            {                                 \
                *op = *p;                     \
                VAL(Idxbits, retdiff) = i;    \
                retdiff += BYTES(Idxbits);    \
                VAL(Datbits, retdiff) = *p;   \
                retdiff += BYTES(Datbits);    \
            }                                 \
            p++;                              \
            op++;                             \
        }                                     \
        VAL(Idxbits, retdiff) = -1;           \
        retdiff += BYTES(Idxbits);            \
    } while (0)

#define CPDATA(Datbits) do \
    { \
        const UINT(Datbits) *p=ptr;  \
        UINT(Datbits) *op=dump;      \
        uint32_t i, nelts=(size*cnt)/BYTES(Datbits); \
        if (nelts>65536)          \
            CPELTS(32,Datbits);   \
        else if (nelts>256)       \
            CPELTS(16,Datbits);   \
        else                      \
            CPELTS(8,Datbits);    \
    } while (0)

    if (size==8)
        CPDATA(64);
    else if ((size&3)==0)
        CPDATA(32);
    else if ((size&1)==0)
        CPDATA(16);
    else
        CPDATA(8);

    *diffvar = retdiff;
    return;

#undef CPELTS
#undef CPSINGLEVAL
#undef CPDATA
}

// get the number of elements to be monitored for changes
static int32_t getnumvar(const dataspec_t *spec)
{
    int32_t n=0;
    for (; spec->flags!=DS_END; spec++)
        n += (spec->flags&(DS_STRING|DS_CMP|DS_NOCHK|DS_SAVEFN|DS_LOADFN) ? 0 : 1);
    return n;
}

// update dump at *dumpvar with new state and write diff to *diffvar
static void cmpspecdata(const dataspec_t *spec, uint8_t **dumpvar, uint8_t **diffvar)
{
    const void *ptr;
    uint8_t *dump=*dumpvar, *diff=*diffvar, *tmptr;
    const dataspec_t *sp=spec;
    int32_t cnt, eltnum=0, nbytes=(getnumvar(spec)+7)>>3, l=Bstrlen(spec->ptr);

    Bmemcpy(diff, spec->ptr, l);
    diff+=l;

    while (nbytes--)
        *(diff++) = 0;  // the bitmap of indices which elements of spec have changed go here

    for (sp++; sp->flags!=DS_END; sp++)
    {
        if ((sp->flags&(DS_NOCHK|DS_STRING|DS_CMP)))
            continue;

        if (sp->flags&(DS_LOADFN|DS_SAVEFN))
        {
            (*(void ( *)())sp->ptr)();
            continue;
        }

        ds_get(sp, &ptr, &cnt);
        if (cnt < 0) { OSD_Printf("csd: cnt=%d, f=0x%x\n", cnt, sp->flags); continue; }

        tmptr = diff;
        docmpsd(ptr, dump, sp->size, cnt, &diff);
        if (diff != tmptr)
            (*diffvar + l)[eltnum>>3] |= 1<<(eltnum&7);
        dump += sp->size*cnt;
        eltnum++;
    }

    *diffvar = diff;
    *dumpvar = dump;
    return;
}

#define VALOFS(bits,p,ofs) (*(((UINT(bits) *)(p)) + (ofs)))

// apply diff to dump, not to state! state is restored from dump afterwards.
static int32_t applydiff(const dataspec_t *spec, uint8_t **dumpvar, uint8_t **diffvar)
{
    uint8_t *dumptr=*dumpvar, *diffptr=*diffvar;
    const dataspec_t *sp=spec;
    int32_t cnt, eltnum=-1, nbytes=(getnumvar(spec)+7)>>3, l=Bstrlen(spec->ptr);

    if (Bmemcmp(diffptr, spec->ptr, l))  // check STRING magic (sync check)
    {
//        *(int32_t *)0 = 1;
        return 1;
    }
    diffptr += l+nbytes;

    for (sp++; sp->flags!=DS_END; sp++)
    {
        if ((sp->flags&(DS_NOCHK|DS_STRING|DS_CMP|DS_LOADFN|DS_SAVEFN)))
            continue;

        cnt = ds_getcnt(sp);
        if (cnt < 0) return 1;

        eltnum++;
        if (((*diffvar + l)[eltnum>>3]&(1<<(eltnum&7))) == 0)
        {
            dumptr += sp->size*cnt;
            continue;
        }

// ----------
#define CPSINGLEVAL(Datbits) \
            VAL(Datbits, dumptr) = VAL(Datbits, diffptr); \
            diffptr += BYTES(Datbits); \
            dumptr += BYTES(Datbits)

        if (cnt==1)
        {
            switch (sp->size)
            {
            case 8: CPSINGLEVAL(64); continue;
            case 4: CPSINGLEVAL(32); continue;
            case 2: CPSINGLEVAL(16); continue;
            case 1: CPSINGLEVAL(8); continue;
            }
        }

#define CPELTS(Idxbits, Datbits) do \
        {                                    \
            UINT(Idxbits) idx;               \
            goto readidx_##Idxbits##_##Datbits; \
            do                               \
            {                                \
                VALOFS(Datbits, dumptr, idx) = VAL(Datbits, diffptr); \
                diffptr += BYTES(Datbits);   \
readidx_##Idxbits##_##Datbits:               \
                idx = VAL(Idxbits, diffptr); \
                diffptr += BYTES(Idxbits);   \
            } while ((int##Idxbits##_t)idx != -1);  \
        } while (0)

#define CPDATA(Datbits) do \
        {                             \
            uint32_t nelts=(sp->size*cnt)/BYTES(Datbits); \
            if (nelts>65536)          \
                CPELTS(32,Datbits);   \
            else if (nelts>256)       \
                CPELTS(16,Datbits);   \
            else                      \
                CPELTS(8,Datbits);    \
        } while (0)

        if (sp->size==8)
            CPDATA(64);
        else if ((sp->size&3)==0)
            CPDATA(32);
        else if ((sp->size&1)==0)
            CPDATA(16);
        else
            CPDATA(8);
        dumptr += sp->size*cnt;
// ----------

#undef CPELTS
#undef CPSINGLEVAL
#undef CPDATA
    }

    *diffvar = diffptr;
    *dumpvar = dumptr;
    return 0;
}

#undef VAL
#undef VALOFS
#undef BYTES
#undef UINT

// calculate size needed for dump
static uint32_t calcsz(const dataspec_t *spec)
{
    const dataspec_t *sp=spec;
    int32_t cnt;
    uint32_t dasiz=0;

    for (; sp->flags!=DS_END; sp++)
    {
        // DS_STRINGs are used as sync checks in the diffs but not in the dump
        if ((sp->flags&(DS_CMP|DS_NOCHK|DS_SAVEFN|DS_LOADFN|DS_STRING)))
            continue;

        cnt = ds_getcnt(sp);
        if (cnt<=0) continue;

        dasiz += cnt*sp->size;
    }

    return dasiz;
}

static void sv_postudload();
#ifdef USE_OPENGL
static void sv_prespriteextsave();
static void sv_postspriteext();
#endif
static void sv_calcbitptrsize();
static void sv_prescriptsave_once();
static void sv_prescriptload_once();
static void sv_postscript_once();
static void sv_preactordatasave();
static void sv_postactordata();
static void sv_preanimateptrsave();
static void sv_postanimateptr();
static void sv_prequote();
static void sv_quotesave();
static void sv_quoteload();
static void sv_prequoteredef();
static void sv_quoteredefsave();
static void sv_quoteredefload();
static void sv_postquoteredef();
static void sv_restsave();
static void sv_restload();

#define SVARDATALEN \
    ((sizeof(g_player[0].user_name)+sizeof(g_player[0].pcolor)+sizeof(g_player[0].pteam) \
      +sizeof(g_player[0].frags)+sizeof(DukePlayer_t))*MAXPLAYERS + sizeof(_prlight)*PR_MAXLIGHTS + sizeof(lightcount))

static uint8_t savegame_bitmap[MAXSPRITES>>3][3];
static uint32_t savegame_bitptrsize;
static uint8_t savegame_quotedef[MAXQUOTES>>3];
static char(*savegame_quotes)[MAXQUOTELEN];
static char(*savegame_quoteredefs)[MAXQUOTELEN];
static uint8_t savegame_restdata[SVARDATALEN];

static const dataspec_t svgm_udnetw[] =
{
    { DS_STRING, "blK:udnt", 0, 1 },
    { 0, &ud.multimode, sizeof(ud.multimode), 1 },
    { 0, &g_numPlayerSprites, sizeof(g_numPlayerSprites), 1 },
    { 0, &g_playerSpawnPoints, sizeof(g_playerSpawnPoints), 1 },

    { DS_NOCHK, &ud.volume_number, sizeof(ud.volume_number), 1 },
    { DS_NOCHK, &ud.level_number, sizeof(ud.level_number), 1 },
    { DS_NOCHK, &ud.player_skill, sizeof(ud.player_skill), 1 },

    { DS_NOCHK, &ud.from_bonus, sizeof(ud.from_bonus), 1 },
    { DS_NOCHK, &ud.secretlevel, sizeof(ud.secretlevel), 1 },
    { DS_NOCHK, &ud.respawn_monsters, sizeof(ud.respawn_monsters), 1 },
    { DS_NOCHK, &ud.respawn_items, sizeof(ud.respawn_items), 1 },
    { DS_NOCHK, &ud.respawn_inventory, sizeof(ud.respawn_inventory), 1 },
    { 0, &ud.god, sizeof(ud.god), 1 },
    { 0, &ud.auto_run, sizeof(ud.auto_run), 1 },
//    { DS_NOCHK, &ud.crosshair, sizeof(ud.crosshair), 1 },
    { DS_NOCHK, &ud.monsters_off, sizeof(ud.monsters_off), 1 },
    { DS_NOCHK, &ud.last_level, sizeof(ud.last_level), 1 },
    { 0, &ud.eog, sizeof(ud.eog), 1 },
    { DS_NOCHK, &ud.coop, sizeof(ud.coop), 1 },
    { DS_NOCHK, &ud.marker, sizeof(ud.marker), 1 },
    { DS_NOCHK, &ud.ffire, sizeof(ud.ffire), 1 },
    { DS_NOCHK, &ud.noexits, sizeof(ud.noexits), 1 },
    { DS_NOCHK, &ud.playerai, sizeof(ud.playerai), 1 },
    { 0, &ud.pause_on, sizeof(ud.pause_on), 1 },
    { DS_NOCHK, &currentboardfilename[0], BMAX_PATH, 1 },
    { DS_LOADFN, (void *)&sv_postudload, 0, 1 },
    { 0, &connecthead, sizeof(connecthead), 1 },
    { 0, connectpoint2, sizeof(connectpoint2), 1 },
    { 0, &randomseed, sizeof(randomseed), 1 },
    { 0, &g_globalRandom, sizeof(g_globalRandom), 1 },
//    { 0, &lockclock_dummy, sizeof(lockclock), 1 },
    { DS_END, 0, 0, 0 }
};

static const dataspec_t svgm_secwsp[] =
{
    { DS_STRING, "blK:swsp", 0, 1 },
    { DS_NOCHK, &numwalls, sizeof(numwalls), 1 },
    { DS_DYNAMIC|DS_CNT(numwalls), &wall, sizeof(walltype), (intptr_t)&numwalls },
    { DS_NOCHK, &numsectors, sizeof(numsectors), 1 },
    { DS_DYNAMIC|DS_CNT(numsectors), &sector, sizeof(sectortype), (intptr_t)&numsectors },
    { DS_DYNAMIC, &sprite, sizeof(spritetype), MAXSPRITES },
    { 0, &headspritesect[0], sizeof(headspritesect[0]), MAXSECTORS+1 },
    { 0, &prevspritesect[0], sizeof(prevspritesect[0]), MAXSPRITES },
    { 0, &nextspritesect[0], sizeof(nextspritesect[0]), MAXSPRITES },
    { 0, &headspritestat[0], sizeof(headspritestat[0]), MAXSTATUS+1 },
    { 0, &prevspritestat[0], sizeof(prevspritestat[0]), MAXSPRITES },
    { 0, &nextspritestat[0], sizeof(nextspritestat[0]), MAXSPRITES },
#ifdef USE_OPENGL
    { DS_SAVEFN, (void *)&sv_prespriteextsave, 0, 1 },
#endif
    { DS_DYNAMIC, &spriteext, sizeof(spriteext_t), MAXSPRITES },
#ifdef USE_OPENGL
    { DS_SAVEFN|DS_LOADFN, (void *)&sv_postspriteext, 0, 1 },
#endif
    { DS_NOCHK, &SpriteFlags[0], sizeof(SpriteFlags[0]), MAXTILES },
    { DS_NOCHK, &SpriteCacheList[0], sizeof(SpriteCacheList[0]), MAXTILES },
    { 0, &DynamicTileMap[0], sizeof(DynamicTileMap[0]), MAXTILES },  // NOCHK?
    { DS_NOCHK, &ActorType[0], sizeof(uint8_t), MAXTILES },
    { DS_NOCHK, &g_numCyclers, sizeof(g_numCyclers), 1 },
    { DS_CNT(g_numCyclers), &cyclers[0][0], sizeof(cyclers[0]), (intptr_t)&g_numCyclers },
    { DS_NOCHK, &g_numAnimWalls, sizeof(g_numAnimWalls), 1 },
    { DS_CNT(g_numAnimWalls), &animwall, sizeof(animwall[0]), (intptr_t)&g_numAnimWalls },
    { DS_NOCHK, &g_mirrorCount, sizeof(g_mirrorCount), 1 },
    { DS_NOCHK, &g_mirrorWall[0], sizeof(g_mirrorWall[0]), sizeof(g_mirrorWall)/sizeof(g_mirrorWall[0]) },
    { DS_NOCHK, &g_mirrorSector[0], sizeof(g_mirrorSector[0]), sizeof(g_mirrorSector)/sizeof(g_mirrorSector[0]) },
// projectiles
    { 0, &SpriteProjectile[0], sizeof(projectile_t), MAXSPRITES },
    { 0, &ProjectileData[0], sizeof(projectile_t), MAXTILES },
    { DS_NOCHK, &DefaultProjectileData[0], sizeof(projectile_t), MAXTILES },
    { 0, &everyothertime, sizeof(everyothertime), 1 },
    { DS_END, 0, 0, 0 }
};

static const dataspec_t svgm_script[] =
{
    { DS_STRING, "blK:scri", 0, 1 },
    { DS_NOCHK, &g_scriptSize, sizeof(g_scriptSize), 1 },
    { DS_SAVEFN|DS_LOADFN|DS_NOCHK, (void *)&sv_calcbitptrsize, 0, 1 },
    { DS_DYNAMIC|DS_CNT(savegame_bitptrsize)|DS_NOCHK, &bitptr, sizeof(bitptr[0]), (intptr_t)&savegame_bitptrsize },

    { DS_SAVEFN|DS_NOCHK, (void *)&sv_prescriptsave_once, 0, 1 },
    { DS_NOCHK, &actorscrptr[0], sizeof(actorscrptr[0]), MAXTILES },
    { DS_LOADFN|DS_NOCHK, (void *)&sv_prescriptload_once, 0, 1 },
    { DS_DYNAMIC|DS_CNT(g_scriptSize)|DS_NOCHK, &script, sizeof(script[0]), (intptr_t)&g_scriptSize },
    { DS_NOCHK, &actorLoadEventScrptr[0], sizeof(actorLoadEventScrptr[0]), MAXTILES },
//    { DS_NOCHK, &apScriptGameEvent[0], sizeof(apScriptGameEvent[0]), MAXGAMEEVENTS },
    { DS_SAVEFN|DS_LOADFN|DS_NOCHK, (void *)&sv_postscript_once, 0, 1 },

    { DS_SAVEFN, (void *)&sv_preactordatasave, 0, 1 },
    { 0, &savegame_bitmap, sizeof(savegame_bitmap), 1 },
    { 0, &actor[0], sizeof(actor_t), MAXSPRITES },
    { DS_SAVEFN|DS_LOADFN, (void *)&sv_postactordata, 0, 1 },

    { DS_END, 0, 0, 0 }
};

static const dataspec_t svgm_anmisc[] =
{
    { DS_STRING, "blK:anms", 0, 1 },
    { 0, &g_animateCount, sizeof(g_animateCount), 1 },
    { 0, &animatesect[0], sizeof(animatesect[0]), MAXANIMATES },
    { 0, &animategoal[0], sizeof(animategoal[0]), MAXANIMATES },
    { 0, &animatevel[0], sizeof(animatevel[0]), MAXANIMATES },
    { DS_SAVEFN, (void *)&sv_preanimateptrsave, 0, 1 },
    { 0, &animateptr[0], sizeof(animateptr[0]), MAXANIMATES },
    { DS_SAVEFN|DS_LOADFN , (void *)&sv_postanimateptr, 0, 1 },
    { 0, &camsprite, sizeof(camsprite), 1 },
    { 0, &msx[0], sizeof(msx[0]), sizeof(msx)/sizeof(msx[0]) },
    { 0, &msy[0], sizeof(msy[0]), sizeof(msy)/sizeof(msy[0]) },
    { 0, &g_spriteDeleteQueuePos, sizeof(g_spriteDeleteQueuePos), 1 },
    { DS_NOCHK, &g_spriteDeleteQueueSize, sizeof(g_spriteDeleteQueueSize), 1 },
    { DS_CNT(g_spriteDeleteQueueSize), &SpriteDeletionQueue[0], sizeof(int16_t), (intptr_t)&g_spriteDeleteQueueSize },
    { 0, &show2dsector[0], sizeof(uint8_t), MAXSECTORS>>3 },
    { DS_NOCHK, &g_numClouds, sizeof(g_numClouds), 1 },
    { 0, &clouds[0], sizeof(clouds), 1 },
    { 0, &cloudx[0], sizeof(cloudx), 1 },
    { 0, &cloudy[0], sizeof(cloudy), 1 },
    { DS_NOCHK, &parallaxyscale, sizeof(parallaxyscale), 1 },
    { 0, &pskybits, sizeof(pskybits), 1 },
    { 0, &pskyoff[0], sizeof(pskyoff[0]), MAXPSKYTILES },
    { 0, &g_earthquakeTime, sizeof(g_earthquakeTime), 1 },

    { DS_SAVEFN|DS_LOADFN|DS_NOCHK, (void *)sv_prequote, 0, 1 },
    { DS_SAVEFN, (void *)&sv_quotesave, 0, 1 },
    { DS_NOCHK, &savegame_quotedef, sizeof(savegame_quotedef), 1 },  // quotes can change during runtime, but new quote numbers cannot be allocated
    { DS_DYNAMIC, &savegame_quotes, MAXQUOTELEN, MAXQUOTES },
    { DS_LOADFN, (void *)&sv_quoteload, 0, 1 },

    { DS_NOCHK, &g_numQuoteRedefinitions, sizeof(g_numQuoteRedefinitions), 1 },
    { DS_NOCHK|DS_SAVEFN|DS_LOADFN, (void *)&sv_prequoteredef, 0, 1 },
    { DS_NOCHK|DS_SAVEFN, (void *)&sv_quoteredefsave, 0, 1 },  // quote redefinitions replace quotes at runtime, but cannot be changed after CON compilation
    { DS_NOCHK|DS_DYNAMIC|DS_CNT(g_numQuoteRedefinitions), &savegame_quoteredefs, MAXQUOTELEN, (intptr_t)&g_numQuoteRedefinitions },
    { DS_NOCHK|DS_LOADFN, (void *)&sv_quoteredefload, 0, 1 },
    { DS_NOCHK|DS_SAVEFN|DS_LOADFN, (void *)&sv_postquoteredef, 0, 1 },

    { DS_SAVEFN, (void *)&sv_restsave, 0, 1 },
    { 0, savegame_restdata, 1, sizeof(savegame_restdata) },  // sz/cnt swapped for kdfread
    { DS_LOADFN, (void *)&sv_restload, 0, 1 },

    { DS_STRING, "savegame_end", 0, 1 },
    { DS_END, 0, 0, 0 }
};

static dataspec_t *svgm_vars=NULL;
static uint8_t *dosaveplayer2(int32_t spot, FILE *fil, uint8_t *mem);
static int32_t doloadplayer2(int32_t spot, int32_t fil, uint8_t **memptr);
static void postloadplayer1();
static void postloadplayer2();

// SVGM snapshot system
static uint32_t svsnapsiz;
static uint8_t *svsnapshot, *svinitsnap;
static uint32_t svdiffsiz;
static uint8_t *svdiff;

#include "gamedef.h"

#define SV_SKIPMASK (/*GAMEVAR_SYSTEM|*/GAMEVAR_READONLY|GAMEVAR_INTPTR|    \
                     GAMEVAR_SHORTPTR|GAMEVAR_CHARPTR /*|GAMEVAR_NORESET*/ |GAMEVAR_SPECIAL)
// setup gamevar data spec for snapshotting and diffing... gamevars must be loaded when called
static void sv_makevarspec()
{
    static char *magic = "blK:vars";
    int32_t i, j, numsavedvars=0, per;

    if (svgm_vars)
        Bfree(svgm_vars);

    for (i=0; i<g_gameVarCount; i++)
        numsavedvars += (aGameVars[i].dwFlags&SV_SKIPMASK) ? 0 : 1;

    svgm_vars = Bmalloc((numsavedvars+g_gameArrayCount+2)*sizeof(dataspec_t));

    svgm_vars[0].flags = DS_STRING;
    svgm_vars[0].ptr = magic;
    svgm_vars[0].cnt = 1;

    j=1;
    for (i=0; i<g_gameVarCount; i++)
    {
        if (aGameVars[i].dwFlags&SV_SKIPMASK)
            continue;

        per = aGameVars[i].dwFlags&GAMEVAR_USER_MASK;

        svgm_vars[j].flags = 0;
        svgm_vars[j].ptr = (per==0) ? &aGameVars[i].val.lValue : aGameVars[i].val.plValues;
        svgm_vars[j].size = sizeof(intptr_t);
        svgm_vars[j].cnt = (per==0) ? 1 : (per==GAMEVAR_PERPLAYER ? MAXPLAYERS : MAXSPRITES);
        j++;
    }

    for (i=0; i<g_gameArrayCount; i++)
    {
        svgm_vars[j].flags = 0;
        svgm_vars[j].ptr = aGameArrays[i].plValues;
        svgm_vars[j].size = sizeof(aGameArrays[0].plValues[0]);
        svgm_vars[j].cnt = aGameArrays[i].size;  // assumed constant throughout demo, i.e. no RESIZEARRAY
        j++;
    }

    svgm_vars[j].flags = DS_END;
}

void sv_freemem()
{
    if (svsnapshot)
        Bfree(svsnapshot), svsnapshot=NULL;
    if (svinitsnap)
        Bfree(svinitsnap), svinitsnap=NULL;
    if (svdiff)
        Bfree(svdiff), svdiff=NULL;
//    if (svgm_vars)
//        Bfree(svgm_vars), svgm_vars=NULL;
}

static int32_t doallocsnap(int32_t allocinit)
{
    sv_freemem();

    svsnapshot = Bmalloc(svsnapsiz);
    if (allocinit)
        svinitsnap = Bmalloc(svsnapsiz);
    svdiffsiz = svsnapsiz;  // theoretically it's less than could be needed in the worst case, but practically it's overkill
    svdiff = Bmalloc(svdiffsiz);
    if (svsnapshot==NULL || (allocinit && svinitsnap==0) || svdiff==NULL)
    {
        sv_freemem();
        return 1;
    }
    return 0;
}

int32_t sv_saveandmakesnapshot(FILE *fil, int32_t recdiffs, int32_t diffcompress, int32_t synccompress)
{
    uint8_t *p, tb;
    uint16_t ts;

    fwrite("EDuke32demo", 11, 1, fil);//0 11b
    tb = SV_MAJOR_VER;
    fwrite(&tb, sizeof(tb), 1, fil);  //11 1b
    tb = SV_MINOR_VER;
    fwrite(&tb, sizeof(tb), 1, fil);  //12 1b
    tb = sizeof(intptr_t);
    fwrite(&tb, sizeof(tb), 1, fil);  //13 1b

    ts = BYTEVERSION;
    fwrite(&ts, sizeof(ts), 1, fil);  //14 2b

    savegame_comprthres = SV_DEFAULTCOMPRTHRES;
    fwrite(&savegame_comprthres, sizeof(savegame_comprthres), 1, fil);  //16 1b

    tb = recdiffs;
    fwrite(&tb, sizeof(tb), 1, fil);  //17 1b

    savegame_diffcompress = (uint8_t)diffcompress;
    fwrite(&savegame_diffcompress, sizeof(savegame_diffcompress), 1, fil);  //18 1b

    tb = (uint8_t)synccompress;
    fwrite(&tb, sizeof(tb), 1, fil);  //19 1b

    sv_makevarspec();
    svsnapsiz = 0;
    fwrite(&svsnapsiz, sizeof(svsnapsiz), 1, fil);  // 20 4b record count for demos
    svsnapsiz = calcsz(svgm_vars);
    svsnapsiz += calcsz(svgm_udnetw) + calcsz(svgm_secwsp) + calcsz(svgm_script) + calcsz(svgm_anmisc);
    fwrite(&svsnapsiz, sizeof(svsnapsiz), 1, fil);  // 24 4b

    OSD_Printf("sv_saveandmakesnapshot: size: %d bytes.\n", svsnapsiz);

    if (doallocsnap(0))
    {
        OSD_Printf("sv_saveandmakesnapshot: failed allocating memory.\n");
        return 1;
    }

    p = dosaveplayer2(-1, fil, svsnapshot);
    if (p != svsnapshot+svsnapsiz)
        OSD_Printf("sv_saveandmakesnapshot: ptr-(snapshot end)=%d!\n", (int32_t)(p-(svsnapshot+svsnapsiz)));

    return 0;
}

int32_t sv_loadsnapshot(int32_t fil, int32_t *ret_hasdiffs, int32_t *ret_demoticcnt, int32_t *ret_synccompress)
{
    uint8_t *p, tmpbuf[11];
    int32_t i;

    if (kread(fil, tmpbuf, 11) != 11) goto corrupt;
    if (Bmemcmp(tmpbuf, "EDuke32demo", 11))
    {
        OSD_Printf("Missing demo header.\n");
        return 1;
    }

    if (kread(fil, tmpbuf, 9) != 9) goto corrupt;
    if (tmpbuf[0] != SV_MAJOR_VER || tmpbuf[1] != SV_MINOR_VER || *(uint16_t *)&tmpbuf[3] != BYTEVERSION)
    {
        OSD_Printf("Incompatible demo version. Expected %d.%d.%d, found %d.%d.%d\n",
                   SV_MAJOR_VER, SV_MINOR_VER, BYTEVERSION,
                   (int)tmpbuf[0], (int)tmpbuf[1], (int)(*(uint16_t *)&tmpbuf[3]));
        return 2;
    }
    if (tmpbuf[2] != sizeof(intptr_t))
    {
        OSD_Printf("Demo incompatible. Expected pointer size %d, found %d.\n",
                   (int32_t)sizeof(intptr_t), (int32_t)tmpbuf[2]);
        return 3;
    }

    savegame_comprthres = tmpbuf[5];
    *ret_hasdiffs = (int32_t)tmpbuf[6];
    savegame_diffcompress = tmpbuf[7];
    *ret_synccompress = (int32_t)tmpbuf[8];

    if (kread(fil, ret_demoticcnt, sizeof(ret_demoticcnt)) != sizeof(ret_demoticcnt)) goto corrupt;
    if (kread(fil, &svsnapsiz, sizeof(svsnapsiz)) != sizeof(svsnapsiz)) goto corrupt;


    OSD_Printf("sv_loadsnapshot: size: %d bytes.\n", svsnapsiz);

    if (doallocsnap(1))
    {
        OSD_Printf("sv_loadsnapshot: failed allocating memory.\n");
        return 4;
    }

    p = svsnapshot;
    i = doloadplayer2(-1, fil, &p);
    if (i)
    {
        OSD_Printf("sv_loadsnapshot: doloadplayer2() returned %d.\n", i);
        sv_freemem();
        return 5;
    }

    Bmemcpy(svinitsnap, svsnapshot, svsnapsiz);

    postloadplayer1();
    postloadplayer2();

    if (p != svsnapshot+svsnapsiz)
    {
        OSD_Printf("sv_loadsnapshot: internal error: p-(snapshot end)=%d!\n",
                   (int32_t)(p-(svsnapshot+svsnapsiz)));
        sv_freemem();
        return 6;
    }

    return 0;
corrupt:
    OSD_Printf("Demo header corrupt.\n");
    return 8;
}

uint32_t sv_writediff(FILE *fil)
{
    uint8_t *p=svsnapshot, *d=svdiff;
    uint32_t diffsiz;

    cmpspecdata(svgm_udnetw, &p, &d);
    cmpspecdata(svgm_secwsp, &p, &d);
    cmpspecdata(svgm_script, &p, &d);
    cmpspecdata(svgm_anmisc, &p, &d);
    cmpspecdata(svgm_vars, &p, &d);

    if (p != svsnapshot+svsnapsiz)
        OSD_Printf("sv_writediff: dump+siz=%p, p=%p!\n", svsnapshot+svsnapsiz, p);

    diffsiz = d-svdiff;

    fwrite("dIfF",4,1,fil);
    fwrite(&diffsiz, sizeof(diffsiz), 1, fil);
    if (savegame_diffcompress)
        dfwrite(svdiff, 1, diffsiz, fil);  // cnt and sz swapped
    else
        fwrite(svdiff, 1, diffsiz, fil);

    return diffsiz;
}

int32_t sv_readdiff(int32_t fil)
{
    uint8_t *p=svsnapshot, *d=svdiff, i=0; //, tbuf[4];
    int32_t diffsiz;

#if 0  // handled by the caller
    if (kread(fil, tbuf, 4)!=4)
        return -1;
    if (Bmemcmp(tbuf, "dIfF", 4))
        return 4;
#endif
    if (kread(fil, &diffsiz, sizeof(uint32_t))!=sizeof(uint32_t))
        return -1;
    if (savegame_diffcompress)
    {
        if (kdfread(svdiff, 1, diffsiz, fil) != diffsiz)  // cnt and sz swapped
            return -2;
    }
    else
    {
        if (kread(fil, svdiff, diffsiz) != diffsiz)
            return -2;
    }

    if (applydiff(svgm_udnetw, &p, &d)) return -3;
    if (applydiff(svgm_secwsp, &p, &d)) return -4;
    if (applydiff(svgm_script, &p, &d)) return -5;
    if (applydiff(svgm_anmisc, &p, &d)) return -6;
    if (applydiff(svgm_vars, &p, &d)) return -7;

    if (p!=svsnapshot+svsnapsiz)
        i|=1;
    if (d!=svdiff+diffsiz)
        i|=2;
    if (i)
        OSD_Printf("sv_readdiff: p=%p, svsnapshot+svsnapsiz=%p; d=%p, svdiff+diffsiz=%p",
                   p, svsnapshot+svsnapsiz, d, svdiff+diffsiz);
    return i;
}

// SVGM data description
static void sv_postudload()
{
    Bmemcpy(&boardfilename[0], &currentboardfilename[0], BMAX_PATH);
#if 0
    ud.m_level_number = ud.level_number;
    ud.m_volume_number = ud.volume_number;
    ud.m_player_skill = ud.player_skill;
    ud.m_respawn_monsters = ud.respawn_monsters;
    ud.m_respawn_items = ud.respawn_items;
    ud.m_respawn_inventory = ud.respawn_inventory;
    ud.m_monsters_off = ud.monsters_off;
    ud.m_coop = ud.coop;
    ud.m_marker = ud.marker;
    ud.m_ffire = ud.ffire;
    ud.m_noexits = ud.noexits;
#endif
}
//static int32_t lockclock_dummy;

#ifdef USE_OPENGL
static void sv_prespriteextsave()
{
    int32_t i;
    for (i=0; i<MAXSPRITES; i++)
        if (spriteext[i].mdanimtims)
        {
            spriteext[i].mdanimtims -= mdtims;
            if (spriteext[i].mdanimtims==0)
                spriteext[i].mdanimtims++;
        }
}
static void sv_postspriteext()
{
    int32_t i;
    for (i=0; i<MAXSPRITES; i++)
        if (spriteext[i].mdanimtims)
            spriteext[i].mdanimtims += mdtims;
}
#endif

static void sv_calcbitptrsize()
{
    savegame_bitptrsize = (g_scriptSize+7)>>3;
}
static void sv_prescriptsave_once()
{
    int32_t i;
    for (i=0; i<g_scriptSize; i++)
        if (bitptr[i>>3]&(BITPTR_POINTER<<(i&7)))
            script[i] = (intptr_t)((intptr_t *)script[i] - &script[0]);
    for (i=0; i<MAXTILES; i++)
        if (actorscrptr[i])
            actorscrptr[i] = (intptr_t *)(actorscrptr[i]-&script[0]);
    for (i=0; i<MAXTILES; i++)
        if (actorLoadEventScrptr[i])
            actorLoadEventScrptr[i] = (intptr_t *)(actorLoadEventScrptr[i]-&script[0]);
}
static void sv_prescriptload_once()
{
    if (script)
        Bfree(script);
    script = Bmalloc(g_scriptSize * sizeof(script[0]));
}
static void sv_postscript_once()
{
    int32_t i;
    for (i=0; i<MAXTILES; i++)
        if (actorLoadEventScrptr[i])
            actorLoadEventScrptr[i] = (intptr_t)actorLoadEventScrptr[i] + &script[0];
    for (i=0; i<MAXTILES; i++)
        if (actorscrptr[i])
            actorscrptr[i] = (intptr_t)actorscrptr[i] + &script[0];
    for (i=0; i<g_scriptSize; i++)
        if (bitptr[i>>3]&(BITPTR_POINTER<<(i&7)))
            script[i] = (intptr_t)(script[i] + &script[0]);
}
static void sv_preactordatasave()
{
    int32_t i;
    intptr_t j=(intptr_t)&script[0], k=(intptr_t)&script[g_scriptSize];

    Bmemset(savegame_bitmap, 0, sizeof(savegame_bitmap));
    for (i=0; i<MAXSPRITES; i++)
    {
//        Actor[i].lightptr = NULL;
//        Actor[i].lightId = -1;

        if (sprite[i].statnum==MAXSTATUS || actorscrptr[PN]==NULL) continue;
        if (T2 >= j && T2 < k) savegame_bitmap[i>>3][0] |= 1<<(i&7), T2 -= j;
        if (T5 >= j && T5 < k) savegame_bitmap[i>>3][1] |= 1<<(i&7), T5 -= j;
        if (T6 >= j && T6 < k) savegame_bitmap[i>>3][2] |= 1<<(i&7), T6 -= j;
    }
}
static void sv_postactordata()
{
    int32_t i;
    intptr_t j=(intptr_t)&script[0];

#if POLYMER
    if (getrendermode() == 4)
        polymer_resetlights();
#endif

    for (i=0; i<MAXSPRITES; i++)
    {
//        Actor[i].lightptr = NULL;
//        Actor[i].lightId = -1;
        actor[i].projectile = &SpriteProjectile[i];

        if (sprite[i].statnum==MAXSTATUS || actorscrptr[PN]==NULL) continue;
        if (savegame_bitmap[i>>3][0]&(1<<(i&7))) T2 += j;
        if (savegame_bitmap[i>>3][1]&(1<<(i&7))) T5 += j;
        if (savegame_bitmap[i>>3][2]&(1<<(i&7))) T6 += j;
    }
}

static void sv_preanimateptrsave()
{
    int32_t i;
    for (i=g_animateCount-1; i>=0; i--)
        animateptr[i] = (int32_t *)((intptr_t)animateptr[i]-(intptr_t)&sector[0]);
}
static void sv_postanimateptr()
{
    int32_t i;
    for (i=g_animateCount-1; i>=0; i--)
        animateptr[i] = (int32_t *)((intptr_t)animateptr[i]+(intptr_t)&sector[0]);
}
static void sv_prequote()
{
    if (!savegame_quotes)
        savegame_quotes = Bcalloc(MAXQUOTES, MAXQUOTELEN);
}
static void sv_quotesave()
{
    int32_t i;
    Bmemset(savegame_quotedef, 0, sizeof(savegame_quotedef));
    for (i=0; i<MAXQUOTES; i++)
        if (ScriptQuotes[i])
        {
            savegame_quotedef[i>>3] |= 1<<(i&7);
            Bmemcpy(savegame_quotes[i], ScriptQuotes[i], MAXQUOTELEN);
        }
}
static void sv_quoteload()
{
    int32_t i;
    for (i=0; i<MAXQUOTES; i++)
    {
        if (savegame_quotedef[i>>3]&(1<<(i&7)))
        {
            if (!ScriptQuotes[i])
                ScriptQuotes[i] = Bcalloc(1,MAXQUOTELEN);
            Bmemcpy(ScriptQuotes[i], savegame_quotes[i], MAXQUOTELEN);
        }
    }
}
static void sv_prequoteredef()
{
    // "+1" needed for dfwrite which doesn't handle the src==NULL && cnt==0 case
    savegame_quoteredefs = Bcalloc(g_numQuoteRedefinitions+1, MAXQUOTELEN);
}
static void sv_quoteredefsave()
{
    int32_t i;
    for (i=0; i<g_numQuoteRedefinitions; i++)
        if (ScriptQuoteRedefinitions[i])
            Bmemcpy(savegame_quoteredefs[i], ScriptQuoteRedefinitions[i], MAXQUOTELEN);
}
static void sv_quoteredefload()
{
    int32_t i;
    for (i=0; i<g_numQuoteRedefinitions; i++)
    {
        if (!ScriptQuoteRedefinitions[i])
            ScriptQuoteRedefinitions[i] = Bcalloc(1,MAXQUOTELEN);
        Bmemcpy(ScriptQuoteRedefinitions[i], savegame_quoteredefs[i], MAXQUOTELEN);
    }
}
static void sv_postquoteredef()
{
    Bfree(savegame_quoteredefs), savegame_quoteredefs=NULL;
}
static void sv_restsave()
{
    int32_t i;
    uint8_t *mem = savegame_restdata;
    DukePlayer_t dummy_ps;

    Bmemset(&dummy_ps, 0, sizeof(DukePlayer_t));

#define CPDAT(ptr,sz) Bmemcpy(mem, ptr, sz), mem+=sz
    for (i=0; i<MAXPLAYERS; i++)
    {
        CPDAT(g_player[i].user_name, 32);
        CPDAT(&g_player[i].pcolor, sizeof(g_player[0].pcolor));
        CPDAT(&g_player[i].pteam, sizeof(g_player[0].pteam));
        CPDAT(&g_player[i].frags[0], sizeof(g_player[0].frags));
        if (g_player[i].ps)
            CPDAT(g_player[i].ps, sizeof(DukePlayer_t));
        else
            CPDAT(&dummy_ps, sizeof(DukePlayer_t));
    }
#if 0  // POLYMER  this will have to wait...
    CPDAT(&lightcount, sizeof(lightcount));
    for (i=0; i<lightcount; i++)
    {
        CPDAT(&prlights[i], sizeof(_prlight));
        ((prlight_ *)(mem-sizeof(_prlight)))->planelist = NULL;
    }
#endif
    Bmemset(mem, 0, (savegame_restdata+SVARDATALEN)-mem);
#undef CPDAT
}
static void sv_restload()
{
    int32_t i;
    uint8_t *mem = savegame_restdata;
    DukePlayer_t dummy_ps;

#define CPDAT(ptr,sz) Bmemcpy(ptr, mem, sz), mem+=sz
    for (i=0; i<MAXPLAYERS; i++)
    {
        CPDAT(g_player[i].user_name, 32);
        CPDAT(&g_player[i].pcolor, sizeof(g_player[0].pcolor));
        CPDAT(&g_player[i].pteam, sizeof(g_player[0].pteam));
        CPDAT(&g_player[i].frags[0], sizeof(g_player[0].frags));
        if (g_player[i].ps)
            CPDAT(g_player[i].ps, sizeof(DukePlayer_t));
        else
            CPDAT(&dummy_ps, sizeof(DukePlayer_t));
    }
#if 0  // POLYMER
    CPDAT(&lightcount, sizeof(lightcount));
    for (i=0; i<lightcount; i++)
        CPDAT(&prlights[i], sizeof(_prlight));
#endif
#undef CPDAT
}

#define SAVEWR(ptr, sz, cnt) do { if (fil) dfwrite(ptr,sz,cnt,fil); } while (0)
#define SAVEWRU(ptr, sz, cnt) do { if (fil) fwrite(ptr,sz,cnt,fil); } while (0)

#define PRINTSIZE(name) OSD_Printf(#name ": %d\n", (int32_t)(mem-tmem)), tmem=mem

static uint8_t *dosaveplayer2(int32_t spot, FILE *fil, uint8_t *mem)
{
    uint8_t *tmem = mem;
    mem=writespecdata(svgm_udnetw, fil, mem);  // user settings, players & net
    PRINTSIZE(ud);

    if (spot>=0)
    {
        SAVEWRU(&ud.savegame[spot][0], 21, 1);
        SAVEWRU("1", 1, 1);
        if (!waloff[TILE_SAVESHOT])
        {
            walock[TILE_SAVESHOT] = 254;
            allocache(&waloff[TILE_SAVESHOT],200*320,&walock[TILE_SAVESHOT]);
            clearbuf((void *)waloff[TILE_SAVESHOT],(200*320)/4,0);
            walock[TILE_SAVESHOT] = 1;
        }
        SAVEWR((char *)waloff[TILE_SAVESHOT], 320, 200);
    }
    else
    {
        char buf[21];
        const time_t t=time(NULL);
        struct tm *st;
        Bsprintf(buf, "Eduke32 demo");
        if (t>=0 && (st = localtime(&t)))
            Bsprintf(buf, "Edemo32 %04d%02d%02d", st->tm_year+1900, st->tm_mon+1, st->tm_mday);
        SAVEWRU(&buf, 21, 1);
        SAVEWRU("\0", 1, 1);  // demos don't save screenshot
    }

    mem=writespecdata(svgm_secwsp, fil, mem);  // sector, wall, sprite
    PRINTSIZE(sws);
    mem=writespecdata(svgm_script, fil, mem);  // script
    PRINTSIZE(script);
    mem=writespecdata(svgm_anmisc, fil, mem);  // animates, quotes & misc.
    PRINTSIZE(animisc);

    Gv_WriteSave(fil, 1);  // gamevars
    mem=writespecdata(svgm_vars, 0, mem);
    PRINTSIZE(vars);

    return mem;
}

#define LOADRD(ptr, sz, cnt) (kdfread(ptr,sz,cnt,fil)!=(cnt))
#define LOADRDU(ptr, sz, cnt) (kread(fil,ptr,(sz)*(cnt))!=(sz)*(cnt))

static int32_t doloadplayer2(int32_t spot, int32_t fil, uint8_t **memptr)
{
    uint8_t *mem = memptr ? *memptr : NULL, *tmem=mem;
    char tbuf[21];
    int32_t i;

    if (readspecdata(svgm_udnetw, fil, &mem))
        return -2;
    PRINTSIZE(ud);
    if (spot >= 0 && ud.multimode!=numplayers)
        return 2;

    if (spot<0 || numplayers > 1)
    {
        if (LOADRDU(&tbuf, 21, 1)) return -3;
    }
    else if (LOADRDU(&ud.savegame[spot][0], 21, 1)) return -3;
    else ud.savegame[spot][19] = 0;

    if (LOADRDU(tbuf, 1, 1)) return -3;
    if (tbuf[0])
    {
        //Fake read because lseek won't work with compression
        walock[TILE_LOADSHOT] = 1;
        if (waloff[TILE_LOADSHOT] == 0) allocache(&waloff[TILE_LOADSHOT],320*200,&walock[TILE_LOADSHOT]);
        tilesizx[TILE_LOADSHOT] = 200;
        tilesizy[TILE_LOADSHOT] = 320;
        if (LOADRD((char *)waloff[TILE_LOADSHOT], 320, 200)) return -3;
        invalidatetile(TILE_LOADSHOT,0,255);
    }

    if (readspecdata(svgm_secwsp, fil, &mem)) return -4;
    PRINTSIZE(sws);
    if (readspecdata(svgm_script, fil, &mem)) return -5;
    PRINTSIZE(script);
    if (readspecdata(svgm_anmisc, fil, &mem)) return -6;
    PRINTSIZE(animisc);

    if (Gv_ReadSave(fil, 1)) return -7;
    sv_makevarspec();

    for (i=1; svgm_vars[i].flags!=DS_END; i++)
    {
        Bmemcpy(mem, svgm_vars[i].ptr, svgm_vars[i].size*svgm_vars[i].cnt);  // careful! works because there are no DS_DYNAMIC's!
        mem += svgm_vars[i].size*svgm_vars[i].cnt;
    }
    PRINTSIZE(vars);

    if (memptr)
        *memptr = mem;
    return 0;
}

int32_t sv_updatestate(int32_t frominit)
{
    uint8_t *p = svsnapshot, *pbeg=p;

    if (frominit)
        Bmemcpy(svsnapshot, svinitsnap, svsnapsiz);

    if (readspecdata(svgm_udnetw, -1, &p)) return -2;
    if (readspecdata(svgm_secwsp, -1, &p)) return -4;
    if (readspecdata(svgm_script, -1, &p)) return -5;
    if (readspecdata(svgm_anmisc, -1, &p)) return -6;

    if (readspecdata(svgm_vars, -1, &p)) return -8;

    if (p != pbeg+svsnapsiz)
        OSD_Printf("sv_updatestate: ptr-(snapshot end)=%d\n", (int32_t)(p-(pbeg+svsnapsiz)));

    if (frominit)
    {
        postloadplayer1();
        postloadplayer2();
    }

    return 0;
}

static void postloadplayer1()
{
    int32_t i;
    //1
    if (g_player[myconnectindex].ps->over_shoulder_on != 0)
    {
        g_cameraDistance = 0;
        g_cameraClock = 0;
        g_player[myconnectindex].ps->over_shoulder_on = 1;
    }

    screenpeek = myconnectindex;
    //2
    //3
    P_UpdateScreenPal(g_player[myconnectindex].ps);
    //4
#if 0
    if (ud.lockout == 0)
    {
        for (i=0; i<g_numAnimWalls; i++)
            if (wall[animwall[i].wallnum].extra >= 0)
                wall[animwall[i].wallnum].picnum = wall[animwall[i].wallnum].extra;
    }
    else
    {
        for (i=0; i<g_numAnimWalls; i++)
            switch (DynamicTileMap[wall[animwall[i].wallnum].picnum])
            {
            case FEMPIC1__STATIC:
                wall[animwall[i].wallnum].picnum = BLANKSCREEN;
                break;
            case FEMPIC2__STATIC:
            case FEMPIC3__STATIC:
                wall[animwall[i].wallnum].picnum = SCREENBREAK6;
                break;
            }
    }
#endif

    //5
    g_numInterpolations = 0;
    startofdynamicinterpolations = 0;

    i = headspritestat[STAT_EFFECTOR];
    while (i >= 0)
    {
        switch (sprite[i].lotag)
        {
        case 31:
            G_SetInterpolation(&sector[sprite[i].sectnum].floorz);
            break;
        case 32:
            G_SetInterpolation(&sector[sprite[i].sectnum].ceilingz);
            break;
        case 25:
            G_SetInterpolation(&sector[sprite[i].sectnum].floorz);
            G_SetInterpolation(&sector[sprite[i].sectnum].ceilingz);
            break;
        case 17:
            G_SetInterpolation(&sector[sprite[i].sectnum].floorz);
            G_SetInterpolation(&sector[sprite[i].sectnum].ceilingz);
            break;
        case 0:
        case 5:
        case 6:
        case 11:
        case 14:
        case 15:
        case 16:
        case 26:
        case 30:
            Sect_SetInterpolation(i);
            break;
        }

        i = nextspritestat[i];
    }

    for (i=g_numInterpolations-1; i>=0; i--) bakipos[i] = *curipos[i];
    for (i = g_animateCount-1; i>=0; i--)
        G_SetInterpolation(animateptr[i]);

    //6
    g_showShareware = 0;
//    everyothertime = 0;
//    clearbufbyte(playerquitflag,MAXPLAYERS,0x01010101);
    for (i=0; i<MAXPLAYERS; i++)
        clearbufbyte(&g_player[i].playerquitflag,1,0x01010101);
}

static void postloadplayer2()
{
    //7
    G_ResetTimers();

#ifdef POLYMER
    if (getrendermode() == 4)
    {
        polymer_loadboard();
        polymer_resetlights();
    }
#elif 0
    if (getrendermode() == 4)
    {
        int32_t i = 0;

        polymer_loadboard();
        while (i < MAXSPRITES)
        {
            if (actor[i].lightptr)
            {
                polymer_deletelight(actor[i].lightId);
                actor[i].lightptr = NULL;
                actor[i].lightId = -1;
            }
            i++;
        }
    }
#endif
}

////////// END GENERIC SAVING/LOADING SYSTEM //////////