code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

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

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#define MAIN
#define QUIET
#include "build.h"

#include "keys.h"
#include "names2.h"
#include "panel.h"
#include "game.h"
#include "tags.h"
#include "lists.h"
#include "interp.h"
#include "interpso.h"
#include "text.h"

#include "network.h"
//#include "save.h"
#include "savedef.h"
#include "jsector.h"
#include "parent.h"
#include "reserve.h"

//#define FILE_TYPE 1
#include "mfile.h"

#include "fx_man.h"
#include "music.h"

#include "weapon.h"
#include "cache.h"
#include "colormap.h"
#include "player.h"

#include "saveable.h"

//void TimerFunc(task * Task);

/*
//////////////////////////////////////////////////////////////////////////////
TO DO

//////////////////////////////////////////////////////////////////////////////
*/

extern int lastUpdate;
extern uint8_t RedBookSong[40];
extern char UserMapName[80];
extern char LevelSong[16];
extern char SaveGameDescr[10][80];
extern int PlayClock;
extern short TotalKillable;
extern short LevelSecrets;
extern short Bunny_Count;
extern SWBOOL NewGame;
extern char CacheLastLevel[];
extern short PlayingLevel;
extern int GodMode;
extern int FinishTimer;
extern SWBOOL FinishAnim;
extern int GameVersion;
//extern short Zombies;

extern SWBOOL serpwasseen;
extern SWBOOL sumowasseen;
extern SWBOOL zillawasseen;
extern short BossSpriteNum[3];

void ScreenTileLock(void);
void ScreenTileUnLock(void);

int ScreenSaveSetup(void);
void ScreenSave(MFILE_WRITE fout);

int ScreenLoadSaveSetup(void);
void ScreenLoad(MFILE_READ fin);

#define PANEL_SAVE 1
#define ANIM_SAVE 1

extern SW_PACKET loc;
extern char LevelName[20];
extern STATE s_NotRestored[];

OrgTileListP otlist[] = {&orgwalllist, &orgwalloverlist, &orgsectorceilinglist, &orgsectorfloorlist};

int
PanelSpriteToNdx(PLAYERp pp, PANEL_SPRITEp psprite)
{
    short ndx = 0;
    PANEL_SPRITEp psp=NULL, next=NULL;

    TRAVERSE(&pp->PanelSpriteList, psp, next)
    {
        if (psp == psprite)
            return ndx;

        ndx++;
    }

    // special case for pointing to the list head
    if ((LIST)psprite == (LIST)&pp->PanelSpriteList)
        return 9999;

    return -1;
}

PANEL_SPRITEp
PanelNdxToSprite(PLAYERp pp, int ndx)
{
    short count = 0;
    PANEL_SPRITEp psp, next;

    if (ndx == -1)
        return NULL;

    if (ndx == 9999)
        return (PANEL_SPRITEp)&pp->PanelSpriteList;

    TRAVERSE(&pp->PanelSpriteList, psp, next)
    {
        if (count == ndx)
            return psp;

        count++;
    }

    return NULL;
}

int SaveSymDataInfo(MFILE_WRITE fil, void *ptr)
{
    saveddatasym sym;

    if (Saveable_FindDataSym(ptr, &sym))
    {
        FILE *fp;

        fp = fopen("savegame symbols missing.txt", "a");
        if (fp)
        {
            fprintf(fp,"data %p\n",ptr);
            fclose(fp);
        }
        return 1;
    }

    MWRITE(&sym, sizeof(sym), 1, fil);

    return 0;
}
static int SaveSymCodeInfo_raw(MFILE_WRITE fil, void *ptr)
{
    savedcodesym sym;

    if (Saveable_FindCodeSym(ptr, &sym))
    {
        FILE *fp;

        fp = fopen("savegame symbols missing.txt", "a");
        if (fp)
        {
            fprintf(fp,"code %p\n",ptr);
            fclose(fp);
        }
        return 1;
    }

    MWRITE(&sym, sizeof(sym), 1, fil);

    return 0;
}
template <typename T>
static int SaveSymCodeInfo(MFILE_WRITE fil, T * ptr)
{
    return SaveSymCodeInfo_raw(fil, (void *)ptr);
}

int LoadSymDataInfo(MFILE_READ fil, void **ptr)
{
    saveddatasym sym;

    MREAD(&sym, sizeof(sym), 1, fil);

    return Saveable_RestoreDataSym(&sym, ptr);
}
int LoadSymCodeInfo(MFILE_READ fil, void **ptr)
{
    savedcodesym sym;

    MREAD(&sym, sizeof(sym), 1, fil);

    return Saveable_RestoreCodeSym(&sym, ptr);
}

int SaveGame(short save_num)
{
    MFILE_WRITE fil;
    int i,j;
    short ndx;
    PLAYER tp;
    PLAYERp pp;
    SECT_USERp sectu;
    USER tu;
    USERp u;
    ANIM tanim;
    ANIMp a;
    PANEL_SPRITE tpanel_sprite;
    PANEL_SPRITEp psp,cur,next;
    SECTOR_OBJECTp sop;
    char game_name[80];
    int saveisshot=0;
    OrgTileP otp, next_otp;

    Saveable_Init();

    sprintf(game_name,"game%d.sav",save_num);
    if ((fil = MOPEN_WRITE(game_name)) == MOPEN_WRITE_ERR)
        return -1;

    MWRITE(&GameVersion,sizeof(GameVersion),1,fil);

    MWRITE(SaveGameDescr[save_num],sizeof(SaveGameDescr[save_num]),1,fil);

    MWRITE(&Level,sizeof(Level),1,fil);
    MWRITE(&Skill,sizeof(Skill),1,fil);

    ScreenSaveSetup();

    ScreenSave(fil);

    ScreenTileUnLock();

    MWRITE(&numplayers,sizeof(numplayers),1,fil);
    MWRITE(&myconnectindex,sizeof(myconnectindex),1,fil);
    MWRITE(&connecthead,sizeof(connecthead),1,fil);
    MWRITE(connectpoint2,sizeof(connectpoint2),1,fil);

    //save players info
    pp = &tp;
    for (i = 0; i < numplayers; i++)
    {
        memcpy(&tp, &Player[i], sizeof(PLAYER));

        // this does not point to global data - this is allocated link list based
        // save this inside the structure
#if PANEL_SAVE
        pp->CurWpn = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(&Player[i], pp->CurWpn);
        for (ndx = 0; ndx < MAX_WEAPONS; ndx++)
            pp->Wpn[ndx] = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(&Player[i], pp->Wpn[ndx]);
        pp->Chops = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(&Player[i], pp->Chops);
        for (ndx = 0; ndx < MAX_INVENTORY; ndx++)
            pp->InventorySprite[ndx] = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(&Player[i], pp->InventorySprite[ndx]);
        pp->InventorySelectionBox = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(&Player[i], pp->InventorySelectionBox);
        pp->MiniBarHealthBox = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(&Player[i], pp->MiniBarHealthBox);
        pp->MiniBarAmmo = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(&Player[i], pp->MiniBarAmmo);
        for (ndx = 0; ndx < (short)SIZ(pp->MiniBarHealthBoxDigit); ndx++)
            pp->MiniBarHealthBoxDigit[ndx] = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(&Player[i], pp->MiniBarHealthBoxDigit[ndx]);
        for (ndx = 0; ndx < (short)SIZ(pp->MiniBarAmmoDigit); ndx++)
            pp->MiniBarAmmoDigit[ndx] = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(&Player[i], pp->MiniBarAmmoDigit[ndx]);
#endif

        MWRITE(&tp, sizeof(PLAYER),1,fil);

        //////

        saveisshot |= SaveSymDataInfo(fil, pp->remote_sprite);
        saveisshot |= SaveSymDataInfo(fil, pp->remote.sop_control);
        saveisshot |= SaveSymDataInfo(fil, pp->sop_remote);
        saveisshot |= SaveSymDataInfo(fil, pp->sop);
        saveisshot |= SaveSymDataInfo(fil, pp->hi_sectp);
        saveisshot |= SaveSymDataInfo(fil, pp->lo_sectp);
        saveisshot |= SaveSymDataInfo(fil, pp->hi_sp);
        saveisshot |= SaveSymDataInfo(fil, pp->lo_sp);

        saveisshot |= SaveSymDataInfo(fil, pp->last_camera_sp);
        saveisshot |= SaveSymDataInfo(fil, pp->SpriteP);
        saveisshot |= SaveSymDataInfo(fil, pp->UnderSpriteP);

        saveisshot |= SaveSymCodeInfo(fil, pp->DoPlayerAction);

        saveisshot |= SaveSymDataInfo(fil, pp->sop_control);
        saveisshot |= SaveSymDataInfo(fil, pp->sop_riding);
    }

#if PANEL_SAVE
    // local copy
    psp = &tpanel_sprite;
    for (i = 0; i < numplayers; i++)
    {
        unsigned j;
        pp = &Player[i];
        ndx = 0;

        TRAVERSE(&pp->PanelSpriteList, cur, next)
        {
            // this is a HEADER
            MWRITE(&ndx, sizeof(ndx),1,fil);

            memcpy(psp, cur, sizeof(PANEL_SPRITE));

            // Panel Sprite - save in structure
            psp->sibling = (PANEL_SPRITEp)(intptr_t)PanelSpriteToNdx(pp, cur->sibling);
            MWRITE(psp, sizeof(PANEL_SPRITE),1,fil);

            saveisshot |= SaveSymDataInfo(fil, psp->PlayerP);
            saveisshot |= SaveSymDataInfo(fil, psp->State);
            saveisshot |= SaveSymDataInfo(fil, psp->RetractState);
            saveisshot |= SaveSymDataInfo(fil, psp->PresentState);
            saveisshot |= SaveSymDataInfo(fil, psp->ActionState);
            saveisshot |= SaveSymDataInfo(fil, psp->RestState);
            saveisshot |= SaveSymCodeInfo(fil, psp->PanelSpriteFunc);

            for (j = 0; j < SIZ(psp->over); j++)
            {
                saveisshot |= SaveSymDataInfo(fil, psp->over[j].State);
            }

            ndx++;
        }

        // store -1 when done for player
        ndx = -1;
        MWRITE(&ndx, sizeof(ndx),1,fil);
    }
#endif

    MWRITE(&numsectors,sizeof(numsectors),1,fil);
    MWRITE(sector,sizeof(SECTOR), numsectors, fil);

    //Sector User information
    for (i = 0; i < numsectors; i++)
    {
        sectu = SectUser[i];
        ndx = i;
        if (sectu)
        {
            // write header
            MWRITE(&ndx,sizeof(ndx),1,fil);

            MWRITE(sectu,sizeof(SECT_USER),1,fil);
        }
        else
        {
            // write trailer
            ndx = -1;
            MWRITE(&ndx,sizeof(ndx),1,fil);
        }
    }

    MWRITE(&numwalls,sizeof(numwalls),1,fil);
    MWRITE(wall,sizeof(WALL),numwalls,fil);

    MWRITE(&Numsprites,sizeof(Numsprites),1,fil);
    //Preserve sprite indices
    for (i = 0; i < MAXSPRITES; i++)
    {
        if (sprite[i].statnum != MAXSTATUS)
        {
            MWRITE(&i,sizeof(i),1,fil);

            MWRITE(&sprite[i],sizeof(SPRITE),1,fil);
        }
    }
    i = -1;
    MWRITE(&i,sizeof(i),1,fil);

    MWRITE(headspritesect,sizeof(headspritesect),1,fil);
    MWRITE(prevspritesect,sizeof(prevspritesect),1,fil);
    MWRITE(nextspritesect,sizeof(nextspritesect),1,fil);
    MWRITE(headspritestat,sizeof(headspritestat),1,fil);
    MWRITE(prevspritestat,sizeof(prevspritestat),1,fil);
    MWRITE(nextspritestat,sizeof(nextspritestat),1,fil);
    MWRITE(&tailspritefree,sizeof(tailspritefree),1,fil);

    //User information
    for (i = 0; i < MAXSPRITES; i++)
    {
        ndx = i;
        if (User[i])
        {
            // write header
            MWRITE(&ndx,sizeof(ndx),1,fil);

            memcpy(&tu, User[i], sizeof(USER));
            u = &tu;

            MWRITE(u,sizeof(USER),1,fil);

            if (u->WallShade)
            {
                MWRITE(u->WallShade,sizeof(*u->WallShade)*u->WallCount,1,fil);
            }

            if (u->rotator)
            {
                MWRITE(u->rotator,sizeof(*u->rotator),1,fil);
                if (u->rotator->origx)
                    MWRITE(u->rotator->origx,sizeof(*u->rotator->origx)*u->rotator->num_walls,1,fil);
                if (u->rotator->origy)
                    MWRITE(u->rotator->origy,sizeof(*u->rotator->origy)*u->rotator->num_walls,1,fil);
            }

            saveisshot |= SaveSymDataInfo(fil, u->WallP);
            saveisshot |= SaveSymDataInfo(fil, u->State);
            saveisshot |= SaveSymDataInfo(fil, u->Rot);
            saveisshot |= SaveSymDataInfo(fil, u->StateStart);
            saveisshot |= SaveSymDataInfo(fil, u->StateEnd);
            saveisshot |= SaveSymDataInfo(fil, u->StateFallOverride);
            saveisshot |= SaveSymCodeInfo(fil, u->ActorActionFunc);
            saveisshot |= SaveSymDataInfo(fil, u->ActorActionSet);
            saveisshot |= SaveSymDataInfo(fil, u->Personality);
            saveisshot |= SaveSymDataInfo(fil, u->Attrib);
            saveisshot |= SaveSymDataInfo(fil, u->sop_parent);
            saveisshot |= SaveSymDataInfo(fil, u->hi_sectp);
            saveisshot |= SaveSymDataInfo(fil, u->lo_sectp);
            saveisshot |= SaveSymDataInfo(fil, u->hi_sp);
            saveisshot |= SaveSymDataInfo(fil, u->lo_sp);
            saveisshot |= SaveSymDataInfo(fil, u->SpriteP);
            saveisshot |= SaveSymDataInfo(fil, u->PlayerP);
            saveisshot |= SaveSymDataInfo(fil, u->tgt_sp);
        }
    }
    ndx = -1;
    MWRITE(&ndx,sizeof(ndx),1,fil);

    //
    // Sector object
    //

    MWRITE(SectorObject, sizeof(SectorObject),1,fil);

    for (ndx = 0; ndx < (short)SIZ(SectorObject); ndx++)
    {
        sop = &SectorObject[ndx];

        saveisshot |= SaveSymCodeInfo(fil, sop->PreMoveAnimator);
        saveisshot |= SaveSymCodeInfo(fil, sop->PostMoveAnimator);
        saveisshot |= SaveSymCodeInfo(fil, sop->Animator);
        saveisshot |= SaveSymDataInfo(fil, sop->controller);
        saveisshot |= SaveSymDataInfo(fil, sop->sp_child);
    }

    MWRITE(SineWaveFloor, sizeof(SineWaveFloor),1,fil);
    MWRITE(SineWall, sizeof(SineWall),1,fil);
    MWRITE(SpringBoard, sizeof(SpringBoard),1,fil);
    //MWRITE(Rotate, sizeof(Rotate),1,fil);
    //MWRITE(DoorAutoClose, sizeof(DoorAutoClose),1,fil);
    MWRITE(&x_min_bound, sizeof(x_min_bound),1,fil);
    MWRITE(&y_min_bound, sizeof(y_min_bound),1,fil);
    MWRITE(&x_max_bound, sizeof(x_max_bound),1,fil);
    MWRITE(&y_max_bound, sizeof(y_max_bound),1,fil);

    MWRITE(Track, sizeof(Track),1,fil);
    for (i = 0; i < MAX_TRACKS; i++)
    {
        ASSERT(Track[i].TrackPoint);
        if (Track[i].NumPoints == 0)
            MWRITE(Track[i].TrackPoint, sizeof(TRACK_POINT),1,fil);
        else
            MWRITE(Track[i].TrackPoint, Track[i].NumPoints * sizeof(TRACK_POINT),1,fil);
    }

    MWRITE(&loc,sizeof(loc),1,fil);
    //MWRITE(&oloc,sizeof(oloc),1,fil);
    //MWRITE(&fsync,sizeof(fsync),1,fil);

    MWRITE(LevelName,sizeof(LevelName),1,fil);
    MWRITE(&screenpeek,sizeof(screenpeek),1,fil);
    MWRITE(&totalsynctics,sizeof(totalsynctics),1,fil);

    // do all sector manipulation structures

#if ANIM_SAVE
#if 1
    MWRITE(&AnimCnt,sizeof(AnimCnt),1,fil);

    for (i = 0, a = &tanim; i < AnimCnt; i++)
    {
        intptr_t offset;
        memcpy(a,&Anim[i],sizeof(ANIM));

        // maintain compatibility with sinking boat which points to user data
        for (j=0; j<MAXSPRITES; j++)
        {
            if (User[j])
            {
                uint8_t* bp = (uint8_t*)User[j];

                if ((uint8_t*)a->ptr >= bp && (uint8_t*)a->ptr < bp + sizeof(USER))
                {
                    offset = (intptr_t)((uint8_t*)a->ptr - bp); // offset from user data
                    a->ptr = (int *)-2;
                    break;
                }
            }
        }

        if ((intptr_t)a->ptr != -2)
        {
            for (j=0; j<numsectors; j++)
            {
                if (SectUser[j])
                {
                    uint8_t* bp = (uint8_t*)SectUser[j];

                    if ((uint8_t*)a->ptr >= bp && (uint8_t*)a->ptr < bp + sizeof(SECT_USER))
                    {
                        offset = (intptr_t)((uint8_t*)a->ptr - bp); // offset from user data
                        a->ptr = (int *)-3;
                        break;
                    }
                }
            }
        }
        MWRITE(a,sizeof(ANIM),1,fil);

        if ((intptr_t)a->ptr == -2 || (intptr_t)a->ptr == -3)
        {
            MWRITE(&j, sizeof(j),1,fil);
            MWRITE(&offset, sizeof(offset),1,fil);
        }
        else
        {
            saveisshot |= SaveSymDataInfo(fil, a->ptr);
        }

        saveisshot |= SaveSymCodeInfo(fil, a->callback);
        saveisshot |= SaveSymDataInfo(fil, a->callbackdata);
    }

#else
    ndx = 0;
    for (i = AnimCnt - 1, a = &tanim; i >= 0; i--)
    {
        // write header
        MWRITE(&ndx,sizeof(ndx),1,fil);

        memcpy(a,&Anim[i],sizeof(ANIM));
        MWRITE(a,sizeof(ANIM),1,fil);

        saveisshot |= SaveSymDataInfo(fil, a->ptr);
        saveisshot |= SaveSymCodeInfo(fil, a->callback);
        saveisshot |= SaveSymDataInfo(fil, a->callbackdata);

        ndx++;
    }

    // write trailer
    ndx = -1;
    MWRITE(&ndx,sizeof(ndx),1,fil);
#endif
#endif

    MWRITE(&totalclock,sizeof(totalclock),1,fil);
    MWRITE(&numframes,sizeof(numframes),1,fil);
    MWRITE(&randomseed,sizeof(randomseed),1,fil);
    MWRITE(&numshades,sizeof(numshades),1,fil);

    MWRITE(&NormalVisibility,sizeof(NormalVisibility),1,fil);
    MWRITE(&g_visibility,sizeof(g_visibility),1,fil);
    MWRITE(&parallaxtype,sizeof(parallaxtype),1,fil);
    MWRITE(&parallaxyoffs_override,sizeof(parallaxyoffs_override),1,fil);
    MWRITE(&parallaxyscale_override,sizeof(parallaxyscale_override),1,fil);
    MWRITE(&pskybits_override,sizeof(pskybits_override),1,fil);

    MWRITE(&BorderInfo,sizeof(BorderInfo),1,fil);
    MWRITE(&MoveSkip2,sizeof(MoveSkip2),1,fil);
    MWRITE(&MoveSkip4,sizeof(MoveSkip4),1,fil);
    MWRITE(&MoveSkip8,sizeof(MoveSkip8),1,fil);

    // long interpolations
    MWRITE(&numinterpolations,sizeof(numinterpolations),1,fil);
    MWRITE(oldipos,sizeof(oldipos),1,fil);
    MWRITE(bakipos,sizeof(bakipos),1,fil);
    for (i = numinterpolations - 1; i >= 0; i--)
        saveisshot |= SaveSymDataInfo(fil, curipos[i]);

    // short interpolations
    MWRITE(&short_numinterpolations,sizeof(short_numinterpolations),1,fil);
    MWRITE(short_oldipos,sizeof(short_oldipos),1,fil);
    MWRITE(short_bakipos,sizeof(short_bakipos),1,fil);
    for (i = short_numinterpolations - 1; i >= 0; i--)
        saveisshot |= SaveSymDataInfo(fil, short_curipos[i]);

    // SO interpolations
    saveisshot |= so_writeinterpolations(fil);

    // parental lock
    for (i = 0; i < (int)SIZ(otlist); i++)
    {
        ndx = 0;
        TRAVERSE(otlist[i], otp, next_otp)
        {
            MWRITE(&ndx,sizeof(ndx),1,fil);
            MWRITE(&otp,sizeof(*otp),1,fil);
            ndx++;
        }
        ndx = -1;
        MWRITE(&ndx, sizeof(ndx),1,fil);
    }

    // mirror
    MWRITE(mirror,sizeof(mirror),1,fil);
    MWRITE(&mirrorcnt,sizeof(mirrorcnt),1,fil);
    MWRITE(&mirrorinview,sizeof(mirrorinview),1,fil);

    // queue
    MWRITE(&StarQueueHead,sizeof(StarQueueHead),1,fil);
    MWRITE(StarQueue,sizeof(StarQueue),1,fil);
    MWRITE(&HoleQueueHead,sizeof(HoleQueueHead),1,fil);
    MWRITE(HoleQueue,sizeof(HoleQueue),1,fil);
    MWRITE(&WallBloodQueueHead,sizeof(WallBloodQueueHead),1,fil);
    MWRITE(WallBloodQueue,sizeof(WallBloodQueue),1,fil);
    MWRITE(&FloorBloodQueueHead,sizeof(FloorBloodQueueHead),1,fil);
    MWRITE(FloorBloodQueue,sizeof(FloorBloodQueue),1,fil);
    MWRITE(&GenericQueueHead,sizeof(GenericQueueHead),1,fil);
    MWRITE(GenericQueue,sizeof(GenericQueue),1,fil);
    MWRITE(&LoWangsQueueHead,sizeof(LoWangsQueueHead),1,fil);
    MWRITE(LoWangsQueue,sizeof(LoWangsQueue),1,fil);

    MWRITE(&PlayClock,sizeof(PlayClock),1,fil);
    MWRITE(&TotalKillable,sizeof(TotalKillable),1,fil);

    // game settings
    MWRITE(&gNet,sizeof(gNet),1,fil);

    MWRITE(LevelSong,sizeof(LevelSong),1,fil);
    MWRITE(&SongTrack,sizeof(SongTrack),1,fil);

    MWRITE(palette,sizeof(palette),1,fil);
    MWRITE(palette_data,sizeof(palette_data),1,fil);
    MWRITE(&gs,sizeof(gs),1,fil);
    MWRITE(picanm,sizeof(picanm),1,fil);

    MWRITE(&LevelSecrets,sizeof(LevelSecrets),1,fil);

    MWRITE(show2dwall,sizeof(show2dwall),1,fil);
    MWRITE(show2dsprite,sizeof(show2dsprite),1,fil);
    MWRITE(show2dsector,sizeof(show2dsector),1,fil);

    MWRITE(&Bunny_Count,sizeof(Bunny_Count),1,fil);

    MWRITE(UserMapName,sizeof(UserMapName),1,fil);
    MWRITE(&GodMode,sizeof(GodMode),1,fil);

    MWRITE(&FinishTimer,sizeof(FinishTimer),1,fil);
    MWRITE(&FinishAnim,sizeof(FinishAnim),1,fil);

    MWRITE(&serpwasseen, sizeof(serpwasseen), 1, fil);
    MWRITE(&sumowasseen, sizeof(sumowasseen), 1, fil);
    MWRITE(&zillawasseen, sizeof(zillawasseen), 1, fil);
    MWRITE(BossSpriteNum, sizeof(BossSpriteNum), 1, fil);
    //MWRITE(&Zombies, sizeof(Zombies), 1, fil);

    MCLOSE_WRITE(fil);

    ////DSPRINTF(ds, "done saving");
    //MONO_PRINT(ds);

    if (saveisshot)
        CON_Message("There was a problem saving. See \"Save Help\" section of release notes.");
    else
        PutStringInfo(&Player[myconnectindex], "Game Saved");

    return saveisshot ? -1 : 0;
}

int LoadGameFullHeader(short save_num, char *descr, short *level, short *skill)
{
    MFILE_READ fil;
    char game_name[80];
    short tile;
    int ver;

    sprintf(game_name,"game%d.sav",save_num);
    if ((fil = MOPEN_READ(game_name)) == MOPEN_READ_ERR)
        return -1;

    MREAD(&ver,sizeof(ver),1,fil);
    if (ver != GameVersion)
    {
        MCLOSE_READ(fil);
        return -1;
    }

    MREAD(descr, sizeof(SaveGameDescr[0]), 1,fil);

    MREAD(level,sizeof(*level),1,fil);
    MREAD(skill,sizeof(*skill),1,fil);

    tile = ScreenLoadSaveSetup();
    ScreenLoad(fil);

    MCLOSE_READ(fil);

    return tile;
}

void LoadGameDescr(short save_num, char *descr)
{
    MFILE_READ fil;
    char game_name[80];
    int ver;

    sprintf(game_name,"game%d.sav",save_num);
    if ((fil = MOPEN_READ(game_name)) == MOPEN_READ_ERR)
        return;

    MREAD(&ver,sizeof(ver),1,fil);
    if (ver != GameVersion)
    {
        MCLOSE_READ(fil);
        return;
    }

    MREAD(descr, sizeof(SaveGameDescr[0]),1,fil);

    MCLOSE_READ(fil);
}

int LoadGame(short save_num)
{
    MFILE_READ fil;
    int i,j,saveisshot=0;
    short ndx,SpriteNum,sectnum;
    PLAYERp pp = NULL;
    USERp u;
    SECTOR_OBJECTp sop;
    SECT_USERp sectu;
    ANIMp a;
    PANEL_SPRITEp psp,next;
    char game_name[80];
    OrgTileP otp;

    extern SWBOOL InMenuLevel;

    Saveable_Init();

    sprintf(game_name,"game%d.sav",save_num);
    if ((fil = MOPEN_READ(game_name)) == MOPEN_READ_ERR)
        return -1;

    MREAD(&i,sizeof(i),1,fil);
    if (i != GameVersion)
    {
        MCLOSE_READ(fil);
        return -1;
    }

    // Don't terminate until you've made sure conditions are valid for loading.
    if (InMenuLevel)
        StopSong();
    else
        TerminateLevel();
    Terminate3DSounds();

    Terminate3DSounds();

    MREAD(SaveGameDescr[save_num], sizeof(SaveGameDescr[save_num]),1,fil);

    MREAD(&Level,sizeof(Level),1,fil);
    MREAD(&Skill,sizeof(Skill),1,fil);

    ScreenLoadSaveSetup();
    ScreenLoad(fil);
    ScreenTileUnLock();

    MREAD(&numplayers, sizeof(numplayers),1,fil);
    MREAD(&myconnectindex,sizeof(myconnectindex),1,fil);
    MREAD(&connecthead,sizeof(connecthead),1,fil);
    MREAD(connectpoint2,sizeof(connectpoint2),1,fil);

    //save players
    //MREAD(Player,sizeof(PLAYER), numplayers,fil);

    //save players info
    for (i = 0; i < numplayers; i++)
    {
        pp = &Player[i];

        MREAD(pp, sizeof(*pp), 1, fil);

        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->remote_sprite);
        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->remote.sop_control);
        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->sop_remote);
        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->sop);

        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->hi_sectp);
        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->lo_sectp);

        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->hi_sp);
        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->lo_sp);

        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->last_camera_sp);
        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->SpriteP);
        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->UnderSpriteP);
        saveisshot |= LoadSymCodeInfo(fil, (void **)&pp->DoPlayerAction);
        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->sop_control);
        saveisshot |= LoadSymDataInfo(fil, (void **)&pp->sop_riding);
        if (saveisshot) { MCLOSE_READ(fil); return -1; }
    }

#if PANEL_SAVE
    for (i = 0; i < numplayers; i++)
    {
        int j;
        pp = &Player[i];

        INITLIST(&pp->PanelSpriteList);

        while (TRUE)
        {
            MREAD(&ndx, sizeof(ndx),1,fil);

            if (ndx == -1)
                break;

            psp = (PANEL_SPRITEp)CallocMem(sizeof(PANEL_SPRITE), 1);
            ASSERT(psp);

            MREAD(psp, sizeof(PANEL_SPRITE),1,fil);
            INSERT_TAIL(&pp->PanelSpriteList,psp);

            saveisshot |= LoadSymDataInfo(fil, (void **)&psp->PlayerP);
            saveisshot |= LoadSymDataInfo(fil, (void **)&psp->State);
            saveisshot |= LoadSymDataInfo(fil, (void **)&psp->RetractState);
            saveisshot |= LoadSymDataInfo(fil, (void **)&psp->PresentState);
            saveisshot |= LoadSymDataInfo(fil, (void **)&psp->ActionState);
            saveisshot |= LoadSymDataInfo(fil, (void **)&psp->RestState);
            saveisshot |= LoadSymCodeInfo(fil, (void **)&psp->PanelSpriteFunc);
            if (saveisshot) { MCLOSE_READ(fil); return -1; }

            for (j = 0; j < (int)SIZ(psp->over); j++)
            {
                saveisshot |= LoadSymDataInfo(fil, (void **)&psp->over[j].State);
                if (saveisshot) { MCLOSE_READ(fil); return -1; }
            }

        }
    }
#endif

    MREAD(&numsectors,sizeof(numsectors),1,fil);
    MREAD(sector,sizeof(SECTOR),numsectors,fil);

    //Sector User information
    for (i = 0; i < numsectors; i++)
    {
        MREAD(&sectnum,sizeof(sectnum),1,fil);
        if (sectnum != -1)
        {
            SectUser[sectnum] = sectu = (SECT_USERp)CallocMem(sizeof(SECT_USER), 1);
            MREAD(sectu,sizeof(SECT_USER),1,fil);
        }
    }

    MREAD(&numwalls,sizeof(numwalls),1,fil);
    MREAD(wall,sizeof(WALL),numwalls,fil);

    MREAD(&Numsprites,sizeof(Numsprites),1,fil);
    //Preserve sprite indices
    MREAD(&i, sizeof(i),1,fil);
    while (i != -1)
    {
        MREAD(&sprite[i], sizeof(SPRITE),1,fil);
        MREAD(&i, sizeof(i),1,fil);
    }

    MREAD(headspritesect,sizeof(headspritesect),1,fil);
    MREAD(prevspritesect,sizeof(prevspritesect),1,fil);
    MREAD(nextspritesect,sizeof(nextspritesect),1,fil);
    MREAD(headspritestat,sizeof(headspritestat),1,fil);
    MREAD(prevspritestat,sizeof(prevspritestat),1,fil);
    MREAD(nextspritestat,sizeof(nextspritestat),1,fil);
    MREAD(&tailspritefree,sizeof(tailspritefree),1,fil);

    //User information
    memset(User, 0, sizeof(User));

    MREAD(&SpriteNum, sizeof(SpriteNum),1,fil);
    while (SpriteNum != -1)
    {
        User[SpriteNum] = u = (USERp)CallocMem(sizeof(USER), 1);
        MREAD(u,sizeof(USER),1,fil);

        if (u->WallShade)
        {
            u->WallShade = (int8_t*)CallocMem(u->WallCount * sizeof(*u->WallShade), 1);
            MREAD(u->WallShade,sizeof(*u->WallShade)*u->WallCount,1,fil);
        }

        if (u->rotator)
        {
            u->rotator = (ROTATORp)CallocMem(sizeof(*u->rotator), 1);
            MREAD(u->rotator,sizeof(*u->rotator),1,fil);

            if (u->rotator->origx)
            {
                u->rotator->origx = (int*)CallocMem(u->rotator->num_walls * sizeof(*u->rotator->origx), 1);
                MREAD(u->rotator->origx,sizeof(*u->rotator->origx)*u->rotator->num_walls,1,fil);
            }
            if (u->rotator->origy)
            {
                u->rotator->origy = (int*)CallocMem(u->rotator->num_walls * sizeof(*u->rotator->origy), 1);
                MREAD(u->rotator->origy,sizeof(*u->rotator->origy)*u->rotator->num_walls,1,fil);
            }
        }

        saveisshot |= LoadSymDataInfo(fil, (void **)&u->WallP);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->State);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->Rot);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->StateStart);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->StateEnd);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->StateFallOverride);
        saveisshot |= LoadSymCodeInfo(fil, (void **)&u->ActorActionFunc);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->ActorActionSet);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->Personality);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->Attrib);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->sop_parent);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->hi_sectp);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->lo_sectp);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->hi_sp);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->lo_sp);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->SpriteP);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->PlayerP);
        saveisshot |= LoadSymDataInfo(fil, (void **)&u->tgt_sp);
        if (saveisshot) { MCLOSE_READ(fil); return -1; }

        MREAD(&SpriteNum,sizeof(SpriteNum),1,fil);
    }

    MREAD(SectorObject, sizeof(SectorObject),1,fil);

    for (ndx = 0; ndx < (short)SIZ(SectorObject); ndx++)
    {
        sop = &SectorObject[ndx];

        saveisshot |= LoadSymCodeInfo(fil, (void **)&sop->PreMoveAnimator);
        saveisshot |= LoadSymCodeInfo(fil, (void **)&sop->PostMoveAnimator);
        saveisshot |= LoadSymCodeInfo(fil, (void **)&sop->Animator);
        saveisshot |= LoadSymDataInfo(fil, (void **)&sop->controller);
        saveisshot |= LoadSymDataInfo(fil, (void **)&sop->sp_child);
        if (saveisshot) { MCLOSE_READ(fil); return -1; }
    }

    MREAD(SineWaveFloor, sizeof(SineWaveFloor),1,fil);
    MREAD(SineWall, sizeof(SineWall),1,fil);
    MREAD(SpringBoard, sizeof(SpringBoard),1,fil);
    //MREAD(Rotate, sizeof(Rotate),1,fil);
    //MREAD(DoorAutoClose, sizeof(DoorAutoClose),1,fil);
    MREAD(&x_min_bound, sizeof(x_min_bound),1,fil);
    MREAD(&y_min_bound, sizeof(y_min_bound),1,fil);
    MREAD(&x_max_bound, sizeof(x_max_bound),1,fil);
    MREAD(&y_max_bound, sizeof(y_max_bound),1,fil);

    MREAD(Track, sizeof(Track),1,fil);
    for (i = 0; i < MAX_TRACKS; i++)
    {
        if (Track[i].NumPoints == 0)
        {
            Track[i].TrackPoint = (TRACK_POINTp)CallocMem(sizeof(TRACK_POINT), 1);
            MREAD(Track[i].TrackPoint, sizeof(TRACK_POINT),1,fil);
        }
        else
        {
            Track[i].TrackPoint = (TRACK_POINTp)CallocMem(Track[i].NumPoints * sizeof(TRACK_POINT), 1);
            MREAD(Track[i].TrackPoint, Track[i].NumPoints * sizeof(TRACK_POINT),1,fil);
        }
    }

    MREAD(&loc,sizeof(loc),1,fil);

    MREAD(LevelName,sizeof(LevelName),1,fil);
    MREAD(&screenpeek,sizeof(screenpeek),1,fil);
    MREAD(&totalsynctics,sizeof(totalsynctics),1,fil);  // same as kens lockclock

    // do all sector manipulation structures

#if ANIM_SAVE
#if 1
    MREAD(&AnimCnt,sizeof(AnimCnt),1,fil);

    for (i = 0; i < AnimCnt; i++)
    {
        a = &Anim[i];
        MREAD(a,sizeof(ANIM),1,fil);

        if ((intptr_t)a->ptr == -2)
        {
            // maintain compatibility with sinking boat which points to user data
            int offset;
            MREAD(&j, sizeof(j),1,fil);
            MREAD(&offset, sizeof(offset),1,fil);
            a->ptr = (int *)(((char *)User[j]) + offset);
        }
        else if ((intptr_t)a->ptr == -3)
        {
            // maintain compatibility with sinking boat which points to user data
            int offset;
            MREAD(&j, sizeof(j),1,fil);
            MREAD(&offset, sizeof(offset),1,fil);
            a->ptr = (int *)(((char *)SectUser[j]) + offset);
        }
        else
        {
            saveisshot |= LoadSymDataInfo(fil, (void **)&a->ptr);
        }

        saveisshot |= LoadSymCodeInfo(fil, (void **)&a->callback);
        saveisshot |= LoadSymDataInfo(fil, (void **)&a->callbackdata);
        if (saveisshot) { MCLOSE_READ(fil); return -1; }
    }
#else
    AnimCnt = 0;
    for (i = MAXANIM - 1; i >= 0; i--)
    {
        a = &Anim[i];

        MREAD(&ndx,sizeof(ndx),1,fil);

        if (ndx == -1)
            break;

        AnimCnt++;

        MREAD(a,sizeof(ANIM),1,fil);

        saveisshot |= LoadSymDataInfo(fil, (void **)&a->ptr);
        saveisshot |= LoadSymCodeInfo(fil, (void **)&a->callback);
        saveisshot |= LoadSymDataInfo(fil, (void **)&a->callbackdata);
        if (saveisshot) { MCLOSE_READ(fil); return -1; }
    }
#endif
#endif

    MREAD(&totalclock,sizeof(totalclock),1,fil);
    MREAD(&numframes,sizeof(numframes),1,fil);
    MREAD(&randomseed,sizeof(randomseed),1,fil);
    MREAD(&numshades,sizeof(numshades),1,fil);

    MREAD(&NormalVisibility,sizeof(NormalVisibility),1,fil);
    MREAD(&g_visibility,sizeof(g_visibility),1,fil);
    MREAD(&parallaxtype,sizeof(parallaxtype),1,fil);
    MREAD(&parallaxyoffs_override,sizeof(parallaxyoffs_override),1,fil);
    MREAD(&parallaxyscale_override,sizeof(parallaxyscale_override),1,fil);
    MREAD(&pskybits_override,sizeof(pskybits_override),1,fil);

    MREAD(&BorderInfo,sizeof(BorderInfo),1,fil);
    MREAD(&MoveSkip2,sizeof(MoveSkip2),1,fil);
    MREAD(&MoveSkip4,sizeof(MoveSkip4),1,fil);
    MREAD(&MoveSkip8,sizeof(MoveSkip8),1,fil);

    // long interpolations
    MREAD(&numinterpolations,sizeof(numinterpolations),1,fil);
    MREAD(oldipos,sizeof(oldipos),1,fil);
    MREAD(bakipos,sizeof(bakipos),1,fil);
    for (i = numinterpolations - 1; i >= 0; i--)
        saveisshot |= LoadSymDataInfo(fil, (void **)&curipos[i]);
    if (saveisshot) { MCLOSE_READ(fil); return -1; }

    // short interpolations
    MREAD(&short_numinterpolations,sizeof(short_numinterpolations),1,fil);
    MREAD(short_oldipos,sizeof(short_oldipos),1,fil);
    MREAD(short_bakipos,sizeof(short_bakipos),1,fil);
    for (i = short_numinterpolations - 1; i >= 0; i--)
        saveisshot |= LoadSymDataInfo(fil, (void **)&short_curipos[i]);
    if (saveisshot) { MCLOSE_READ(fil); return -1; }

    // SO interpolations
    saveisshot |= so_readinterpolations(fil);
    if (saveisshot) { MCLOSE_READ(fil); return -1; }

    // parental lock
    for (i = 0; i < (int)SIZ(otlist); i++)
    {
        INITLIST(otlist[i]);

        while (TRUE)
        {
            MREAD(&ndx, sizeof(ndx),1,fil);

            if (ndx == -1)
                break;

            otp = (OrgTileP)CallocMem(sizeof(*otp), 1);
            ASSERT(otp);

            MREAD(otp, sizeof(*otp),1,fil);
            INSERT_TAIL(otlist[i],otp);
        }
    }

    // mirror
    MREAD(mirror,sizeof(mirror),1,fil);
    MREAD(&mirrorcnt,sizeof(mirrorcnt),1,fil);
    MREAD(&mirrorinview,sizeof(mirrorinview),1,fil);

    // queue
    MREAD(&StarQueueHead,sizeof(StarQueueHead),1,fil);
    MREAD(StarQueue,sizeof(StarQueue),1,fil);
    MREAD(&HoleQueueHead,sizeof(HoleQueueHead),1,fil);
    MREAD(HoleQueue,sizeof(HoleQueue),1,fil);
    MREAD(&WallBloodQueueHead,sizeof(WallBloodQueueHead),1,fil);
    MREAD(WallBloodQueue,sizeof(WallBloodQueue),1,fil);
    MREAD(&FloorBloodQueueHead,sizeof(FloorBloodQueueHead),1,fil);
    MREAD(FloorBloodQueue,sizeof(FloorBloodQueue),1,fil);
    MREAD(&GenericQueueHead,sizeof(GenericQueueHead),1,fil);
    MREAD(GenericQueue,sizeof(GenericQueue),1,fil);
    MREAD(&LoWangsQueueHead,sizeof(LoWangsQueueHead),1,fil);
    MREAD(LoWangsQueue,sizeof(LoWangsQueue),1,fil);

    // init timing vars before PlayClock is read
    MREAD(&PlayClock,sizeof(PlayClock),1,fil);
    MREAD(&TotalKillable,sizeof(TotalKillable),1,fil);

    // game settings
    MREAD(&gNet,sizeof(gNet),1,fil);

    MREAD(LevelSong,sizeof(LevelSong),1,fil);
    decltype(SongTrack) SavedSongTrack;
    MREAD(&SavedSongTrack,sizeof(SongTrack),1,fil);

    MREAD(palette,sizeof(palette),1,fil);
    MREAD(palette_data,sizeof(palette_data),1,fil);

    {
        SWBOOL AmbBak = gs.Ambient;
        SWBOOL MusicBak = gs.MusicOn;
        SWBOOL FxBak = gs.FxOn;
        short SndVolBak = gs.SoundVolume;
        short MusVolBak = gs.MusicVolume;
        MREAD(&gs,sizeof(gs),1,fil);
        gs.MusicOn = MusicBak;
        gs.FxOn = FxBak;
        gs.Ambient = AmbBak;
        gs.SoundVolume = SndVolBak;
        gs.MusicVolume = MusVolBak;
    }

    //COVERsetbrightness(gs.Brightness,(char *)palette_data);

    MREAD(picanm,sizeof(picanm),1,fil);

    MREAD(&LevelSecrets,sizeof(LevelSecrets),1,fil);

    MREAD(show2dwall,sizeof(show2dwall),1,fil);
    MREAD(show2dsprite,sizeof(show2dsprite),1,fil);
    MREAD(show2dsector,sizeof(show2dsector),1,fil);

    MREAD(&Bunny_Count,sizeof(Bunny_Count),1,fil);

    MREAD(UserMapName,sizeof(UserMapName),1,fil);
    MREAD(&GodMode,sizeof(GodMode),1,fil);

    MREAD(&FinishTimer,sizeof(FinishTimer),1,fil);
    MREAD(&FinishAnim,sizeof(FinishAnim),1,fil);

    MREAD(&serpwasseen, sizeof(serpwasseen), 1, fil);
    MREAD(&sumowasseen, sizeof(sumowasseen), 1, fil);
    MREAD(&zillawasseen, sizeof(zillawasseen), 1, fil);
    MREAD(BossSpriteNum, sizeof(BossSpriteNum), 1, fil);
    //MREAD(&Zombies, sizeof(Zombies), 1, fil);

    MCLOSE_READ(fil);

    //!!IMPORTANT - this POST stuff will not work here now becaus it does actual reads

    //
    // POST processing of info MREAD in
    //

#if PANEL_SAVE
    for (i = 0; i < numplayers; i++)
    {
        pp = &Player[i];
        TRAVERSE(&pp->PanelSpriteList, psp, next)
        {
            // dont need to set Next and Prev this was done
            // when sprites were inserted

            // sibling is the only PanelSprite (malloced ptr) in the PanelSprite struct
            psp->sibling = PanelNdxToSprite(pp, (int)(intptr_t)psp->sibling);
        }
    }
#endif

    if (Bstrcasecmp(CacheLastLevel, LevelName) != 0)
    {
        SetupPreCache();
        DoTheCache();
    }

    calc_sector_reachability();

    // what is this for? don't remember
    totalclock = totalsynctics;
    ototalclock = totalsynctics;

    // this is ok - just duplicating sector list with pointers
    for (sop = SectorObject; sop < &SectorObject[SIZ(SectorObject)]; sop++)
    {
        for (i = 0; i < sop->num_sectors; i++)
            sop->sectp[i] = &sector[sop->sector[i]];
    }

    //!!Again this will not work here
    //restore players info
    for (i = 0; i < numplayers; i++)
    {
#if PANEL_SAVE
        pp->CurWpn = PanelNdxToSprite(pp, (int)(intptr_t)pp->CurWpn);

        for (ndx = 0; ndx < MAX_WEAPONS; ndx++)
            pp->Wpn[ndx] = PanelNdxToSprite(pp, (int)(intptr_t)pp->Wpn[ndx]);

        for (ndx = 0; ndx < MAX_INVENTORY; ndx++)
            pp->InventorySprite[ndx] = PanelNdxToSprite(pp, (int)(intptr_t)pp->InventorySprite[ndx]);

        pp->Chops = PanelNdxToSprite(pp, (int)(intptr_t)pp->Chops);
        pp->InventorySelectionBox = PanelNdxToSprite(pp, (int)(intptr_t)pp->InventorySelectionBox);
        pp->MiniBarHealthBox = PanelNdxToSprite(pp, (int)(intptr_t)pp->MiniBarHealthBox);
        pp->MiniBarAmmo = PanelNdxToSprite(pp, (int)(intptr_t)pp->MiniBarAmmo);

        for (ndx = 0; ndx < (short)SIZ(pp->MiniBarHealthBoxDigit); ndx++)
            pp->MiniBarHealthBoxDigit[ndx] = PanelNdxToSprite(pp, (int)(intptr_t)pp->MiniBarHealthBoxDigit[ndx]);

        for (ndx = 0; ndx < (short)SIZ(pp->MiniBarAmmoDigit); ndx++)
            pp->MiniBarAmmoDigit[ndx] = PanelNdxToSprite(pp, (int)(intptr_t)pp->MiniBarAmmoDigit[ndx]);

#endif
    }

    {
        int SavePlayClock = PlayClock;
        InitTimingVars();
        PlayClock = SavePlayClock;
    }
    InitNetVars();

    SetupAspectRatio();
    SetRedrawScreen(Player + myconnectindex);

    COVERsetbrightness(gs.Brightness,&palette_data[0][0]);

    screenpeek = myconnectindex;
    PlayingLevel = Level;

    PlaySong(LevelSong, SavedSongTrack, TRUE, FALSE);
    if (gs.Ambient)
        StartAmbientSound();
    FX_SetVolume(gs.SoundVolume);
    SetSongVolume(gs.MusicVolume);

    TRAVERSE_CONNECT(i)
    {
        Player[i].PlayerTalking = FALSE;
        Player[i].TalkVocnum = -1;
        Player[i].TalkVocHandle = -1;
        Player[i].StartColor = 0;
    }

    // this is not a new game
    NewGame = FALSE;

    DoPlayerDivePalette(Player+myconnectindex);
    DoPlayerNightVisionPalette(Player+myconnectindex);

    return 0;
}

void
ScreenSave(MFILE_WRITE fout)
{
    // int num;
    MWRITE((void *)waloff[SAVE_SCREEN_TILE], SAVE_SCREEN_XSIZE * SAVE_SCREEN_YSIZE, 1, fout);
    // ASSERT(num == 1);
}

void
ScreenLoad(MFILE_READ fin)
{
    renderSetTarget(SAVE_SCREEN_TILE, SAVE_SCREEN_YSIZE, SAVE_SCREEN_XSIZE);

    MREAD((void *)waloff[SAVE_SCREEN_TILE], SAVE_SCREEN_XSIZE * SAVE_SCREEN_YSIZE, 1, fin);

    renderRestoreTarget();
}


code:
//-------------------------------------------------------------------------
/*
 Copyright (C) 2005 Jonathon Fowler <jf@jonof.id.au>

 This file is part of JFShadowWarrior

 Shadow Warrior is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

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

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
//-------------------------------------------------------------------------

#include "compat.h"
#include "saveable.h"

#define maxModules 35

static saveable_module *saveablemodules[maxModules];
static unsigned nummodules = 0;

void Saveable_Init(void)
{
    if (nummodules > 0) return;

    Saveable_Init_Dynamic();

#define MODULE(x) { \
        extern saveable_module saveable_ ## x; \
        saveablemodules[nummodules++] = &saveable_ ## x; \
}

    MODULE(actor)
    MODULE(ai)
    MODULE(build)
    MODULE(bunny)
    MODULE(coolg)
    MODULE(coolie)
    MODULE(eel)
    MODULE(girlninj)
    MODULE(goro)
    MODULE(hornet)
    MODULE(jweapon)
    MODULE(lava)
    MODULE(miscactr)
    MODULE(morph)
    MODULE(ninja)
    MODULE(panel)
    MODULE(player)
    MODULE(quake)
    MODULE(ripper)
    MODULE(ripper2)
    MODULE(rotator)
    MODULE(serp)
    MODULE(skel)
    MODULE(skull)
    MODULE(slidor)
    MODULE(spike)
    MODULE(sprite)
    MODULE(sumo)
    MODULE(track)
    MODULE(vator)
    MODULE(wallmove)
    MODULE(weapon)
    MODULE(zilla)
    MODULE(zombie)

    MODULE(sector)
}

int Saveable_FindCodeSym(void *ptr, savedcodesym *sym)
{
    unsigned m,i;

    if (!ptr)
    {
        sym->module = 0;    // module 0 is the "null module" for null pointers
        sym->index  = 0;
        return 0;
    }

    for (m=0; m<nummodules; m++)
    {
        for (i=0; i<saveablemodules[m]->numcode; i++)
        {
            if (ptr != saveablemodules[m]->code[i]) continue;

            sym->module = 1+m;
            sym->index  = i;

            return 0;
        }
    }

    debug_break();
    return -1;
}

int Saveable_FindDataSym(void *ptr, saveddatasym *sym)
{
    unsigned m,i;

    if (!ptr)
    {
        sym->module = 0;
        sym->index  = 0;
        sym->offset = 0;
        return 0;
    }

    for (m=0; m<nummodules; m++)
    {
        for (i=0; i<saveablemodules[m]->numdata; i++)
        {
            if (ptr < saveablemodules[m]->data[i].base) continue;
            if (ptr >= (void *)((intptr_t)saveablemodules[m]->data[i].base +
                                saveablemodules[m]->data[i].size)) continue;

            sym->module = 1+m;
            sym->index  = i;
            sym->offset = (intptr_t)ptr - (intptr_t)saveablemodules[m]->data[i].base;

            return 0;
        }
    }

    debug_break();
    return -1;
}

int Saveable_RestoreCodeSym(savedcodesym *sym, void **ptr)
{
    if (sym->module == 0)
    {
        *ptr = NULL;
        return 0;
    }

    if (sym->module > nummodules)
    {
        debug_break();
        return -1;
    }
    if (sym->index  >= saveablemodules[sym->module-1]->numcode)
    {
        debug_break();
        return -1;
    }

    *ptr = saveablemodules[sym->module-1]->code[sym->index];

    return 0;
}

int Saveable_RestoreDataSym(saveddatasym *sym, void **ptr)
{
    if (sym->module == 0)
    {
        *ptr = NULL;
        return 0;
    }

    if (sym->module > nummodules)
    {
        debug_break();
        return -1;
    }
    if (sym->index  >= saveablemodules[sym->module-1]->numdata)
    {
        debug_break();
        return -1;
    }
    if (sym->offset >= saveablemodules[sym->module-1]->data[sym->index].size)
    {
        debug_break();
        return -1;
    }

    *ptr = (void *)((intptr_t)saveablemodules[sym->module-1]->data[sym->index].base + sym->offset);

    return 0;
}

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
 Copyright (C) 2005 Jonathon Fowler <jf@jonof.id.au>

 This file is part of JFShadowWarrior

 Shadow Warrior is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

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

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
//-------------------------------------------------------------------------

#ifndef SAVEABLE_H
#define SAVEABLE_H

#include "compat.h"

typedef void *saveable_code;

typedef struct
{
    void *base;
    unsigned int size;
} saveable_data;

typedef struct
{
    saveable_code *code;
    unsigned int numcode;

    saveable_data *data;
    unsigned int numdata;
} saveable_module;

template <typename T>
static FORCE_INLINE constexpr enable_if_t<!std::is_pointer<T>::value, size_t> SAVE_SIZEOF(T const & obj) noexcept
{
    return sizeof(obj);
}

#define SAVE_CODE(s) (void*)(s)
#define SAVE_DATA(s) { (void*)&(s), SAVE_SIZEOF(s) }

#define NUM_SAVEABLE_ITEMS(x) ARRAY_SIZE(x)

typedef struct
{
    unsigned int module;
    unsigned int index;
} savedcodesym;

typedef struct
{
    unsigned int module;
    unsigned int index;
    unsigned int offset;
} saveddatasym;

void Saveable_Init(void);
void Saveable_Init_Dynamic(void);

int Saveable_FindCodeSym(void *ptr, savedcodesym *sym);
int Saveable_FindDataSym(void *ptr, saveddatasym *sym);

int Saveable_RestoreCodeSym(savedcodesym *sym, void **ptr);
int Saveable_RestoreDataSym(saveddatasym *sym, void **ptr);

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

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

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#define SAVE_VALIDATE FALSE


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

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

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

// scriplib.c
#include "build.h"
#include "cache1d.h"

#include "keys.h"
#include "names2.h"
#include "panel.h"
#include "game.h"

#include "parse.h"
#include "sprite.h"
#include "jsector.h"
#include "parent.h"

#define PATHSEPERATOR   '\\'

//#define COMPUTE_TOTALS    1

ParentalStruct aVoxelArray[MAXTILES];

/*
=============================================================================

                        PARSING STUFF

=============================================================================
*/

char    token[MAXTOKEN];
char    *scriptbuffer,*script_p,*scriptend_p;
int     grabbed;
int     scriptline;
SWBOOL    endofscript;
SWBOOL    tokenready;                     // only TRUE if UnGetToken was just called

/*
==============
=
= LoadScriptFile
=
==============
*/

SWBOOL LoadScriptFile(const char *filename)
{
    int size, readsize;
    int fp;

    if ((fp=kopen4load(filename,0)) == -1)
    {
        // If there's no script file, forget it.
        return FALSE;
    }

    size = kfilelength(fp);

    scriptbuffer = (char *)AllocMem(size+1);

    ASSERT(scriptbuffer != NULL);

    readsize = kread(fp, scriptbuffer, size);

    kclose(fp);

    ASSERT(readsize == size);

    scriptbuffer[readsize] = '\0';

    // Convert filebuffer to all upper case
    //Bstrupr(scriptbuffer);

    script_p = scriptbuffer;
    scriptend_p = script_p + size;
    scriptline = 1;
    endofscript = FALSE;
    tokenready = FALSE;
    return TRUE;
}

/*
==============
=
= UnGetToken
=
= Signals that the current token was not used, and should be reported
= for the next GetToken.  Note that

GetToken (TRUE);
UnGetToken ();
GetToken (FALSE);

= could cross a line boundary.
=
==============
*/

void UnGetToken(void)
{
    tokenready = TRUE;
}

/*
==============
=
= GetToken
=
==============
*/

void GetToken(SWBOOL crossline)
{
    char    *token_p;

    if (tokenready)                         // is a token already waiting?
    {
        tokenready = FALSE;
        return;
    }

    if (script_p >= scriptend_p)
    {
        if (!crossline)
            buildprintf("Error: Line %i is incomplete\n",scriptline);
        endofscript = TRUE;
        return;
    }

//
// skip space
//
skipspace:
    while (*script_p <= 32)
    {
        if (script_p >= scriptend_p)
        {
            if (!crossline)
                buildprintf("Error: Line %i is incomplete\n",scriptline);
            endofscript = TRUE;
            return;
        }
        if (*script_p++ == '\n')
        {
            if (!crossline)
                buildprintf("Error: Line %i is incomplete\n",scriptline);
            scriptline++;
        }
    }

    if (script_p >= scriptend_p)
    {
        if (!crossline)
            buildprintf("Error: Line %i is incomplete\n",scriptline);
        endofscript = TRUE;
        return;
    }

    if (*script_p == '#')   // # is comment field
    {
        if (!crossline)
            buildprintf("Error: Line %i is incomplete\n",scriptline);
        while (*script_p++ != '\n')
            if (script_p >= scriptend_p)
            {
                endofscript = TRUE;
                return;
            }
        goto skipspace;
    }

//
// copy token
//
    token_p = token;

    while (*script_p > 32 && *script_p != '#')
    {
        *token_p++ = *script_p++;
        if (script_p == scriptend_p)
            break;
        ASSERT(token_p != &token[MAXTOKEN]);
//          buildprintf("Error: Token too large on line %i\n",scriptline);
    }

    *token_p = 0;
}

/*
==============
=
= TokenAvailable
=
= Returns true if there is another token on the line
=
==============
*/

SWBOOL TokenAvailable(void)
{
    char    *search_p;

    search_p = script_p;

    if (search_p >= scriptend_p)
        return FALSE;

    while (*search_p <= 32)
    {
        if (*search_p == '\n')
            return FALSE;
        search_p++;
        if (search_p == scriptend_p)
            return FALSE;

    }

    if (*search_p == '#')
        return FALSE;

    return TRUE;
}

// Load all the voxel files using swvoxfil.txt script file
// Script file format:

//          # - Comment
//          spritenumber (in artfile), voxel number, filename
//          Ex. 1803 0 medkit2.kvx
//              1804 1 shotgun.kvx
//              etc....

void LoadKVXFromScript(const char *filename)
{
    int lNumber=0,lTile=0; // lNumber is the voxel no. and lTile is the editart tile being
    // replaced.
    char *sName;            // KVS file being loaded in.

    int grabbed=0;          // Number of lines parsed

    sName = (char *)AllocMem(256);    // Up to 256 bytes for path
    ASSERT(sName != NULL);

    // zero out the array memory with -1's for pics not being voxelized
    memset(&aVoxelArray[0],-1,sizeof(struct TILE_INFO_TYPE)*MAXTILES);
    for (grabbed = 0; grabbed < MAXTILES; grabbed++)
    {
        aVoxelArray[grabbed].Voxel = -1;
        aVoxelArray[grabbed].Parental = -1;
    }

    grabbed = 0;

    // Load the file
    if (!LoadScriptFile(filename))
        ASSERT(TRUE==FALSE);

    do
    {
        GetToken(TRUE);     // Crossing a line boundary on the end of line to first token
        // of a new line is permitted (and expected)
        if (endofscript)
            break;

        lTile = atol(token);

        GetToken(FALSE);
        lNumber = atol(token);

        GetToken(FALSE);
        strcpy(sName,token);            // Copy the whole token as a file name and path

        // Load the voxel file into memory
        if (!qloadkvx(lNumber,sName))
        {
            // Store the sprite and voxel numbers for later use
            aVoxelArray[lTile].Voxel = lNumber; // Voxel num
        }

        if (lNumber >= nextvoxid)   // JBF: so voxels in the def file append to the list
            nextvoxid = lNumber + 1;

        grabbed++;
        ASSERT(grabbed < MAXSPRITES);

    }
    while (script_p < scriptend_p);

    FreeMem(scriptbuffer);
    FreeMem(sName);
    script_p = NULL;
}

// Load in info for all Parental lock tile targets
//          # - Comment
//          tilenumber (in artfile), replacement tile offset (if any)
//          Ex. 1803 -1       -1 = No tile replacement
//              1804 2000
//              etc....
void LoadPLockFromScript(const char *filename)
{
    int lNumber=0,lTile=0; // lNumber is the voxel no. and lTile is the editart tile being
    // replaced.
    char *sName;            // KVS file being loaded in.

    int grabbed=0;          // Number of lines parsed

    sName = (char *)AllocMem(256);    // Up to 256 bytes for path
    ASSERT(sName != NULL);

    // Load the file
    if (!LoadScriptFile(filename))
        ASSERT(TRUE==FALSE);

    do
    {
        GetToken(TRUE);     // Crossing a line boundary on the end of line to first token
        // of a new line is permitted (and expected)
        if (endofscript)
            break;

        lTile = atoi(token);

        GetToken(FALSE);
        lNumber = atoi(token);

        // Store the sprite and voxel numbers for later use
        aVoxelArray[lTile].Parental = lNumber;  // Replacement to tile, -1 for none

        grabbed++;
        ASSERT(grabbed < MAXSPRITES);

    }
    while (script_p < scriptend_p);

    FreeMem(scriptbuffer);
    FreeMem(sName);
    script_p = NULL;
}

/*
 * Here begins JonoF's modding enhancement stuff
 */

#include "scriptfile.h"

enum
{
    CM_MAP,
    CM_EPISODE,
    CM_TITLE,
    CM_FILENAME,
    CM_SONG,
    CM_CDATRACK,
    CM_BESTTIME,
    CM_PARTIME,
    CM_SUBTITLE,
    CM_SKILL,
    CM_TEXT,
    CM_COOKIE,
    CM_GOTKEY,
    CM_NEEDKEY,
    CM_INVENTORY,
    CM_AMOUNT,
    CM_WEAPON,
    CM_AMMONAME,
    CM_MAXAMMO,
    CM_DAMAGEMIN,
    CM_DAMAGEMAX,
    CM_SECRET,
    CM_QUIT,
};

static const struct _tokset
{
    const char *str;
    int tokn;
} cm_tokens[] =
{
    { "map",         CM_MAP       },
    { "level",       CM_MAP       },
    { "episode",     CM_EPISODE   },
    { "skill",       CM_SKILL     },
    { "cookie",      CM_COOKIE    },
    { "fortune",     CM_COOKIE    },
    { "gotkey",      CM_GOTKEY    },
    { "inventory",   CM_INVENTORY },
    { "weapon",      CM_WEAPON    },
    { "needkey",     CM_NEEDKEY   },
    { "secret",      CM_SECRET    },
    { "quit",        CM_QUIT      },
},
  cm_map_tokens[] =
{
    { "title",       CM_TITLE     },
    { "name",        CM_TITLE     },
    { "description", CM_TITLE     },
    { "filename",    CM_FILENAME  },
    { "file",        CM_FILENAME  },
    { "fn",          CM_FILENAME  },
    { "levelname",   CM_FILENAME  },
    { "song",        CM_SONG      },
    { "music",       CM_SONG      },
    { "songname",    CM_SONG      },
    { "cdatrack",    CM_CDATRACK  },
    { "cdtrack",     CM_CDATRACK  },
    { "besttime",    CM_BESTTIME  },
    { "partime",     CM_PARTIME   },
},
  cm_episode_tokens[] =
{
    { "title",       CM_TITLE     },
    { "name",        CM_TITLE     },
    { "description", CM_TITLE     },
    { "subtitle",    CM_SUBTITLE  },
},
  cm_skill_tokens[] =
{
    { "title",       CM_TITLE     },
    { "name",        CM_TITLE     },
    { "description", CM_TITLE     },
},
  cm_inventory_tokens[] =
{
    { "title",       CM_TITLE     },
    { "name",        CM_TITLE     },
    { "description", CM_TITLE     },
    { "amount",      CM_AMOUNT    },
},
  cm_weapons_tokens[] =
{
    { "title",       CM_TITLE     },
    { "name",        CM_TITLE     },
    { "description", CM_TITLE     },
    { "ammoname",    CM_AMMONAME  },
    { "maxammo",     CM_MAXAMMO   },
    { "mindamage",   CM_DAMAGEMIN },
    { "maxdamage",   CM_DAMAGEMAX },
    { "pickup",      CM_AMOUNT    },
    { "weaponpickup",CM_WEAPON    },
}
;
#define cm_numtokens           (sizeof(cm_tokens)/sizeof(cm_tokens[0]))
#define cm_map_numtokens       (sizeof(cm_map_tokens)/sizeof(cm_map_tokens[0]))
#define cm_episode_numtokens   (sizeof(cm_episode_tokens)/sizeof(cm_episode_tokens[0]))
#define cm_skill_numtokens     (sizeof(cm_skill_tokens)/sizeof(cm_skill_tokens[0]))
#define cm_inventory_numtokens (sizeof(cm_inventory_tokens)/sizeof(cm_inventory_tokens[0]))
#define cm_weapons_numtokens   (sizeof(cm_weapons_tokens)/sizeof(cm_weapons_tokens[0]))

static int cm_transtok(const char *tok, const struct _tokset *set, const unsigned num)
{
    unsigned i;

    for (i=0; i<num; i++)
    {
        if (!Bstrcasecmp(tok, set[i].str))
            return set[i].tokn;
    }

    return -1;
}

// Load custom map and episode information
//   level # {
//      title    "Map Name"
//      filename "filename.map"
//      song     "filename.mid"
//      cdatrack n
//      besttime secs
//      partime  secs
//   }
//
//   episode # {
//      title    "Episode Name"
//      subtitle "Caption text"
//   }
//
//   skill # {
//      name "Tiny grasshopper"
//   }
//
//   fortune {
//      "You never going to score."
//      "26-31-43-82-16-29"
//      "Sorry, you no win this time, try again."
//   }
//   gotkey {
//      "Got the RED key!"
//      "Got the BLUE key!"
//      "Got the GREEN key!"
//      ...
//   }
//   needkey {
//      "You need a RED key for this door."
//      "You need a BLUE key for this door."
//      "You need a GREEN key for this door."
//      ...
//   }
//   inventory # { name "Armour" amount 50 }
//   weapon # { name "Uzi Submachine Gun" ammoname "Uzi Clip" maxammo 200 mindamage 5 maxdamage 7 pickup 50 }
//   secret  "You found a secret area!"
//   quit    "PRESS (Y) TO QUIT, (N) TO FIGHT ON."

static LEVEL_INFO custommaps[MAX_LEVELS_REG];
static char *customfortune[MAX_FORTUNES];
static char *customkeymsg[MAX_KEYS];
static char *customkeydoormsg[MAX_KEYS];
static char *custominventoryname[InvDecl_TOTAL];
static char *customweaponname[2][MAX_WEAPONS];  // weapon, ammo

#define WM_DAMAGE  1
#define WM_WEAP   2
#define WM_AMMO   4
static struct
{
    const char *sym;
    int dmgid;
    int editable;
} weaponmap[] =
{
    { "WPN_FIST",       WPN_FIST,        WM_DAMAGE },
    { "WPN_SWORD",      WPN_SWORD,       WM_DAMAGE },
    { "WPN_SHURIKEN",   WPN_STAR,        WM_DAMAGE|WM_WEAP },
    { "WPN_STICKYBOMB", WPN_MINE,        WM_DAMAGE|WM_WEAP },
    { "WPN_UZI",        WPN_UZI,         WM_DAMAGE|WM_WEAP|WM_AMMO },
    { "WPN_MISSILE",    WPN_MICRO,       WM_DAMAGE|WM_WEAP|WM_AMMO },
    { "WPN_NUKE",       DMG_NUCLEAR_EXP, WM_DAMAGE|WM_WEAP|WM_AMMO },
    { "WPN_GRENADE",    WPN_GRENADE,     WM_DAMAGE|WM_WEAP|WM_AMMO },
    { "WPN_RAILGUN",    WPN_RAIL,        WM_DAMAGE|WM_WEAP|WM_AMMO },
    { "WPN_SHOTGUN",    WPN_SHOTGUN,     WM_DAMAGE|WM_WEAP|WM_AMMO },
    { "WPN_HOTHEAD",    WPN_HOTHEAD,     WM_DAMAGE|WM_WEAP },
    { "WPN_HEART",      WPN_HEART,       WM_DAMAGE|WM_WEAP },
    { "WPN_HOTHEAD_NAPALM", WPN_NAPALM,  WM_DAMAGE },
    { "WPN_HOTHEAD_RING",   WPN_RING,    WM_DAMAGE },
};

// FIXME: yes, we are leaking memory here at the end of the program by not freeing anything
void LoadCustomInfoFromScript(const char *filename)
{
    scriptfile *script;
    char *token;
    char *braceend;
    int curmap = -1;

    script = scriptfile_fromfile(filename);
    if (!script) return;

    // predefine constants for some stuff to give convenience and eliminate the need for a 'define' directive
    scriptfile_addsymbolvalue("INV_ARMOR",      1+InvDecl_Armor);
    scriptfile_addsymbolvalue("INV_KEVLAR",     1+InvDecl_Kevlar);
    scriptfile_addsymbolvalue("INV_SM_MEDKIT",  1+InvDecl_SmMedkit);
    scriptfile_addsymbolvalue("INV_FORTUNE",    1+InvDecl_Booster);
    scriptfile_addsymbolvalue("INV_MEDKIT",     1+InvDecl_Medkit);
    scriptfile_addsymbolvalue("INV_GAS_BOMB",   1+InvDecl_ChemBomb);
    scriptfile_addsymbolvalue("INV_FLASH_BOMB", 1+InvDecl_FlashBomb);
    scriptfile_addsymbolvalue("INV_CALTROPS",   1+InvDecl_Caltrops);
    scriptfile_addsymbolvalue("INV_NIGHT_VIS",  1+InvDecl_NightVision);
    scriptfile_addsymbolvalue("INV_REPAIR_KIT", 1+InvDecl_RepairKit);
    scriptfile_addsymbolvalue("INV_SMOKE_BOMB", 1+InvDecl_Cloak);

    {
        unsigned i;
        for (i=0; i<SIZ(weaponmap); i++)
            scriptfile_addsymbolvalue(weaponmap[i].sym, 1+i);
    }

    while ((token = scriptfile_gettoken(script)))
    {
        switch (cm_transtok(token, cm_tokens, cm_numtokens))
        {
        case CM_MAP:
        {
            char *mapnumptr;
            if (scriptfile_getnumber(script, &curmap)) break;
            mapnumptr = script->ltextptr;
            if (scriptfile_getbraces(script, &braceend)) break;

            // first map file in LevelInfo[] is bogus, last map file is NULL
            if (curmap < 1 || curmap > MAX_LEVELS_REG)
            {
                buildprintf("Error: map number %d not in range 1-%d on line %s:%d\n",
                            curmap, MAX_LEVELS_REG, script->filename,
                            scriptfile_getlinum(script,mapnumptr));
                script->textptr = braceend;
                break;
            }

            while (script->textptr < braceend)
            {
                if (!(token = scriptfile_gettoken(script))) break;
                if (token == braceend) break;
                switch (cm_transtok(token, cm_map_tokens, cm_map_numtokens))
                {
                case CM_FILENAME:
                {
                    char *t;
                    if (scriptfile_getstring(script, &t)) break;

                    //Xfree(custommaps[curmap].LevelName);
                    custommaps[curmap].LevelName = Xstrdup(t);
                    LevelInfo[curmap].LevelName = custommaps[curmap].LevelName;
                    break;
                }
                case CM_SONG:
                {
                    char *t;
                    if (scriptfile_getstring(script, &t)) break;

                    //Xfree(custommaps[curmap].SongName);
                    custommaps[curmap].SongName = Xstrdup(t);
                    LevelInfo[curmap].SongName = custommaps[curmap].SongName;
                    break;
                }
                case CM_TITLE:
                {
                    char *t;
                    if (scriptfile_getstring(script, &t)) break;

                    //Xfree(custommaps[curmap].Description);
                    custommaps[curmap].Description = Xstrdup(t);
                    LevelInfo[curmap].Description = custommaps[curmap].Description;
                    break;
                }
                case CM_BESTTIME:
                {
                    int n;
                    char s[10];
                    if (scriptfile_getnumber(script, &n)) break;

                    Bsnprintf(s, 10, "%d : %02d", n/60, n%60);
                    //Xfree(custommaps[curmap].BestTime);
                    custommaps[curmap].BestTime = Xstrdup(s);
                    LevelInfo[curmap].BestTime = custommaps[curmap].BestTime;
                    break;
                }
                case CM_PARTIME:
                {
                    int n;
                    char s[10];
                    if (scriptfile_getnumber(script, &n)) break;

                    Bsnprintf(s, 10, "%d : %02d", n/60, n%60);
                    //Xfree(custommaps[curmap].ParTime);
                    custommaps[curmap].ParTime = Xstrdup(s);
                    LevelInfo[curmap].ParTime = custommaps[curmap].ParTime;
                    break;
                }
                case CM_CDATRACK:
                {
                    int n;
                    if (scriptfile_getnumber(script, &n)) break;
                    break;
                }
                default:
                    buildprintf("Error on line %s:%d\n",
                                script->filename,
                                scriptfile_getlinum(script,script->ltextptr));
                    break;
                }
            }
            break;
        }

        case CM_EPISODE:
        {
            char *epnumptr;
            if (scriptfile_getnumber(script, &curmap)) break;
            epnumptr = script->ltextptr;
            if (scriptfile_getbraces(script, &braceend)) break;

            // first map file in LevelInfo[] is bogus, last map file is NULL
            if ((unsigned)--curmap >= 2u)
            {
                buildprintf("Error: episode number %d not in range 1-2 on line %s:%d\n",
                            curmap, script->filename,
                            scriptfile_getlinum(script,epnumptr));
                script->textptr = braceend;
                break;
            }

            while (script->textptr < braceend)
            {
                if (!(token = scriptfile_gettoken(script))) break;
                if (token == braceend) break;
                switch (cm_transtok(token, cm_episode_tokens, cm_episode_numtokens))
                {
                case CM_TITLE:
                {
                    char *t;
                    if (scriptfile_getstring(script, &t)) break;

                    strncpy(&EpisodeNames[curmap][1], t, MAX_EPISODE_NAME_LEN);
                    EpisodeNames[curmap][MAX_EPISODE_NAME_LEN+1] = 0;
                    break;
                }
                case CM_SUBTITLE:
                {
                    char *t;
                    if (scriptfile_getstring(script, &t)) break;

                    strncpy(EpisodeSubtitles[curmap], t, MAX_EPISODE_SUBTITLE_LEN);
                    EpisodeSubtitles[curmap][MAX_EPISODE_SUBTITLE_LEN] = 0;
                    break;
                }
                default:
                    buildprintf("Error on line %s:%d\n",
                                script->filename,
                                scriptfile_getlinum(script,script->ltextptr));
                    break;
                }
            }
            break;
        }

        case CM_SKILL:
        {
            char *epnumptr;
            if (scriptfile_getnumber(script, &curmap)) break;
            epnumptr = script->ltextptr;
            if (scriptfile_getbraces(script, &braceend)) break;

            // first map file in LevelInfo[] is bogus, last map file is NULL
            if ((unsigned)--curmap >= 4u)
            {
                buildprintf("Error: skill number %d not in range 1-4 on line %s:%d\n",
                            curmap, script->filename,
                            scriptfile_getlinum(script,epnumptr));
                script->textptr = braceend;
                break;
            }

            while (script->textptr < braceend)
            {
                if (!(token = scriptfile_gettoken(script))) break;
                if (token == braceend) break;
                switch (cm_transtok(token, cm_skill_tokens, cm_skill_numtokens))
                {
                case CM_TITLE:
                {
                    char *t;
                    if (scriptfile_getstring(script, &t)) break;

                    strncpy(&SkillNames[curmap][1], t, MAX_SKILL_NAME_LEN);
                    SkillNames[curmap][MAX_SKILL_NAME_LEN+1] = 0;
                    break;
                }
                default:
                    buildprintf("Error on line %s:%d\n",
                                script->filename,
                                scriptfile_getlinum(script,script->ltextptr));
                    break;
                }
            }
            break;
        }

        case CM_COOKIE:
        {
            char *t;
            int fc = 0;

            if (scriptfile_getbraces(script, &braceend)) break;

            while (script->textptr < braceend)
            {
                if (scriptfile_getstring(script, &t)) break;

                if (fc == MAX_FORTUNES) continue;

                customfortune[fc] = Xstrdup(t);
                if (customfortune[fc]) ReadFortune[fc] = customfortune[fc];
                fc++;
            }
            break;
        }
        case CM_GOTKEY:
        {
            char *t;
            int fc = 0;

            if (scriptfile_getbraces(script, &braceend)) break;

            while (script->textptr < braceend)
            {
                if (scriptfile_getstring(script, &t)) break;

                if (fc == MAX_KEYS) continue;

                customkeymsg[fc] = Xstrdup(t);
                if (customkeymsg[fc]) KeyMsg[fc] = customkeymsg[fc];
                fc++;
            }
            break;
        }
        case CM_NEEDKEY:
        {
            char *t;
            int fc = 0;

            if (scriptfile_getbraces(script, &braceend)) break;

            while (script->textptr < braceend)
            {
                if (scriptfile_getstring(script, &t)) break;

                if (fc == MAX_KEYS) continue;

                customkeydoormsg[fc] = Xstrdup(t);
                if (customkeydoormsg[fc]) KeyDoorMessage[fc] = customkeydoormsg[fc];
                fc++;
            }
            break;
        }
        case CM_INVENTORY:
        {
            char *invnumptr;
            int in;
            char *name = NULL;
            int amt = -1;

            if (scriptfile_getsymbol(script, &in)) break;
            invnumptr = script->ltextptr;
            if (scriptfile_getbraces(script, &braceend)) break;

            if ((unsigned)--in >= (unsigned)InvDecl_TOTAL)
            {
                buildprintf("Error: inventory item number not in range 1-%d on line %s:%d\n",
                            InvDecl_TOTAL, script->filename,
                            scriptfile_getlinum(script,invnumptr));
                script->textptr = braceend;
                break;
            }

            while (script->textptr < braceend)
            {
                if (!(token = scriptfile_gettoken(script))) break;
                if (token == braceend) break;
                switch (cm_transtok(token, cm_inventory_tokens, cm_inventory_numtokens))
                {
                case CM_TITLE:
                    if (scriptfile_getstring(script, &name)) break;
                    break;
                case CM_AMOUNT:
                    if (scriptfile_getnumber(script, &amt)) break;
                    break;
                default:
                    buildprintf("Error on line %s:%d\n",
                                script->filename,
                                scriptfile_getlinum(script,script->ltextptr));
                    break;
                }
            }

            if (name)
            {
                Xfree(custominventoryname[in]);
                custominventoryname[in] = Xstrdup(name);
                InventoryDecls[in].name = custominventoryname[in];
            }
            if (amt >= 0)
            {
                InventoryDecls[in].amount = amt;
            }
            break;
        }
        case CM_WEAPON:
        {
            char *wpnnumptr;
            char *name = NULL, *ammo = NULL;
            int maxammo = -1, damagemin = -1, damagemax = -1, pickup = -1, wpickup = -1;
            int in,id;

            if (scriptfile_getsymbol(script, &in)) break;
            wpnnumptr = script->ltextptr;
            if (scriptfile_getbraces(script, &braceend)) break;

            if ((unsigned)--in >= (unsigned)SIZ(weaponmap))
            {
                buildprintf("Error: weapon number not in range 1-%d on line %s:%d\n",
                            (int)SIZ(weaponmap), script->filename,
                            scriptfile_getlinum(script,wpnnumptr));
                script->textptr = braceend;
                break;
            }

            while (script->textptr < braceend)
            {
                if (!(token = scriptfile_gettoken(script))) break;
                if (token == braceend) break;
                switch (cm_transtok(token, cm_weapons_tokens, cm_weapons_numtokens))
                {
                case CM_TITLE:
                    if (scriptfile_getstring(script, &name)) break;
                    break;
                case CM_AMMONAME:
                    if (scriptfile_getstring(script, &ammo)) break;
                    break;
                case CM_MAXAMMO:
                    if (scriptfile_getnumber(script, &maxammo)) break;
                    break;
                case CM_DAMAGEMIN:
                    if (scriptfile_getnumber(script, &damagemin)) break;
                    break;
                case CM_DAMAGEMAX:
                    if (scriptfile_getnumber(script, &damagemax)) break;
                    break;
                case CM_AMOUNT:
                    if (scriptfile_getnumber(script, &pickup)) break;
                    break;
                case CM_WEAPON:
                    if (scriptfile_getnumber(script, &wpickup)) break;
                    break;
                default:
                    buildprintf("Error on line %s:%d\n",
                                script->filename,
                                scriptfile_getlinum(script,script->ltextptr));
                    break;
                }
            }
            id = weaponmap[in].dmgid;
            if (weaponmap[in].editable & WM_DAMAGE)
            {
                if (damagemin >= 0) DamageData[id].damage_lo = damagemin;
                if (damagemax >= 0) DamageData[id].damage_hi = damagemax;
            }
            if (weaponmap[in].editable & WM_WEAP)
            {
                if (maxammo >= 0) DamageData[id].max_ammo = maxammo;
                if (name)
                {
                    Xfree(customweaponname[0][id]);
                    customweaponname[0][id] = Xstrdup(name);
                    DamageData[id].weapon_name = customweaponname[0][id];
                }
                if (wpickup >= 0) DamageData[id].weapon_pickup = wpickup;
            }
            if (weaponmap[in].editable & WM_AMMO)
            {
                if (ammo)
                {
                    Xfree(customweaponname[1][id]);
                    customweaponname[1][id] = Xstrdup(ammo);
                    DamageData[id].ammo_name = customweaponname[1][id];
                }
                if (pickup >= 0) DamageData[id].ammo_pickup = pickup;
            }
            break;
        }
        case CM_SECRET:
        case CM_QUIT:
        default:
            buildprintf("Error on line %s:%d\n",
                        script->filename,
                        scriptfile_getlinum(script,script->ltextptr));
            break;
        }
    }

    scriptfile_close(script);
    scriptfile_clearsymbols();
}