code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#include "build.h"
#include "editor.h"
#ifdef POLYMER
#include "polymer.h"
#endif
#include "compat.h"
#include "common_game.h"
#include "crc32.h"
#include "md4.h"

//#include "actor.h"
#include "globals.h"
#include "db.h"
#include "iob.h"
#include "eventq.h"
#ifdef NOONE_EXTENSIONS
#include "nnexts.h"
#endif

#ifdef NOONE_EXTENSIONS
bool gModernMap = false;
#endif // !NOONE_EXTENSIONS

unsigned short gStatCount[kMaxStatus + 1];

XSPRITE xsprite[kMaxXSprites];
XSECTOR xsector[kMaxXSectors];
XWALL xwall[kMaxXWalls];

SPRITEHIT gSpriteHit[kMaxXSprites];

int xvel[kMaxSprites], yvel[kMaxSprites], zvel[kMaxSprites];

#ifdef POLYMER
PolymerLight_t gPolymerLight[kMaxSprites];
#endif

char qsprite_filler[kMaxSprites], qsector_filler[kMaxSectors];

int gVisibility;
const char *gItemText[] = {
    "Skull Key",
    "Eye Key",
    "Fire Key",
    "Dagger Key",
    "Spider Key",
    "Moon Key",
    "Key 7",
    "Doctor's Bag",
    "Medicine Pouch",
    "Life Essence",
    "Life Seed",
    "Red Potion",
    "Feather Fall",
    "Limited Invisibility",
    "INVULNERABILITY",
    "Boots of Jumping",
    "Raven Flight",
    "Guns Akimbo",
    "Diving Suit",
    "Gas mask",
    "Clone",
    "Crystal Ball",
    "Decoy",
    "Doppleganger",
    "Reflective shots",
    "Beast Vision",
    "ShadowCloak",
    "Rage shroom",
    "Delirium Shroom",
    "Grow shroom",
    "Shrink shroom",
    "Death mask",
    "Wine Goblet",
    "Wine Bottle",
    "Skull Grail",
    "Silver Grail",
    "Tome",
    "Black Chest",
    "Wooden Chest",
    "Asbestos Armor",
    "Basic Armor",
    "Body Armor",
    "Fire Armor",
    "Spirit Armor",
    "Super Armor",
    "Blue Team Base",
    "Red Team Base",
    "Blue Flag",
    "Red Flag",
    "DUMMY",
    "Level map",
};

const char *gAmmoText[] = {
    "Spray can",
    "Bundle of TNT*",
    "Bundle of TNT",
    "Case of TNT",
    "Proximity Detonator",
    "Remote Detonator",
    "Trapped Soul",
    "4 shotgun shells",
    "Box of shotgun shells",
    "A few bullets",
    "Voodoo Doll",
    "OBSOLETE",
    "Full drum of bullets",
    "Tesla Charge",
    "OBSOLETE",
    "OBSOLETE",
    "Flares",
    "OBSOLETE",
    "OBSOLETE",
    "Gasoline Can",
    NULL,
};

const char *gWeaponText[] = {
    "RANDOM",
    "Sawed-off",
    "Tommy Gun",
    "Flare Pistol",
    "Voodoo Doll",
    "Tesla Cannon",
    "Napalm Launcher",
    "Pitchfork",
    "Spray Can",
    "Dynamite",
    "Life Leech",
};

void dbCrypt(char *pPtr, int nLength, int nKey)
{
    for (int i = 0; i < nLength; i++)
    {
        pPtr[i] = pPtr[i] ^ nKey;
        nKey++;
    }
}

#ifdef POLYMER

void DeleteLight(int32_t s)
{
    if (gPolymerLight[s].lightId >= 0)
        polymer_deletelight(gPolymerLight[s].lightId);
    gPolymerLight[s].lightId = -1;
    gPolymerLight[s].lightptr = NULL;
}

#endif

void InsertSpriteSect(int nSprite, int nSector)
{
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    dassert(nSector >= 0 && nSector < kMaxSectors);
    int nOther = headspritesect[nSector];
    if (nOther >= 0)
    {
        prevspritesect[nSprite] = prevspritesect[nOther];
        nextspritesect[nSprite] = -1;
        nextspritesect[prevspritesect[nOther]] = nSprite;
        prevspritesect[nOther] = nSprite;
    }
    else
    {
        prevspritesect[nSprite] = nSprite;
        nextspritesect[nSprite] = -1;
        headspritesect[nSector] = nSprite;
    }
    sprite[nSprite].sectnum = nSector;
}

void RemoveSpriteSect(int nSprite)
{
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    int nSector = sprite[nSprite].sectnum;
    dassert(nSector >= 0 && nSector < kMaxSectors);
    int nOther = nextspritesect[nSprite];
    if (nOther < 0)
    {
        nOther = headspritesect[nSector];
    }
    prevspritesect[nOther] = prevspritesect[nSprite];
    if (headspritesect[nSector] != nSprite)
    {
        nextspritesect[prevspritesect[nSprite]] = nextspritesect[nSprite];
    }
    else
    {
        headspritesect[nSector] = nextspritesect[nSprite];
    }
    sprite[nSprite].sectnum = -1;
}

void InsertSpriteStat(int nSprite, int nStat)
{
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    dassert(nStat >= 0 && nStat <= kMaxStatus);
    int nOther = headspritestat[nStat];
    if (nOther >= 0)
    {
        prevspritestat[nSprite] = prevspritestat[nOther];
        nextspritestat[nSprite] = -1;
        nextspritestat[prevspritestat[nOther]] = nSprite;
        prevspritestat[nOther] = nSprite;
    }
    else
    {
        prevspritestat[nSprite] = nSprite;
        nextspritestat[nSprite] = -1;
        headspritestat[nStat] = nSprite;
    }
    sprite[nSprite].statnum = nStat;
    gStatCount[nStat]++;
}

void RemoveSpriteStat(int nSprite)
{
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    int nStat = sprite[nSprite].statnum;
    dassert(nStat >= 0 && nStat <= kMaxStatus);
    int nOther = nextspritestat[nSprite];
    if (nOther < 0)
    {
        nOther = headspritestat[nStat];
    }
    prevspritestat[nOther] = prevspritestat[nSprite];
    if (headspritestat[nStat] != nSprite)
    {
        nextspritestat[prevspritestat[nSprite]] = nextspritestat[nSprite];
    }
    else
    {
        headspritestat[nStat] = nextspritestat[nSprite];
    }
    sprite[nSprite].statnum = kStatNothing;
    gStatCount[nStat]--;
}

void qinitspritelists(void) // Replace
{
    for (short i = 0; i <= kMaxSectors; i++)
    {
        headspritesect[i] = -1;
    }
    for (short i = 0; i <= kMaxStatus; i++)
    {
        headspritestat[i] = -1;
    }
    int const nMaxSprites = bVanilla ? 4096 : kMaxSprites;
    for (short i = 0; i < nMaxSprites; i++)
    {
        sprite[i].sectnum = -1;
        sprite[i].index = -1;
        InsertSpriteStat(i, kMaxStatus);
    }
    memset(gStatCount, 0, sizeof(gStatCount));
    Numsprites = 0;
}

int InsertSprite(int nSector, int nStat)
{
    int nSprite = headspritestat[kMaxStatus];
    dassert(nSprite < kMaxSprites);
    if (nSprite < 0)
    {
        return nSprite;
    }
    RemoveSpriteStat(nSprite);
    spritetype *pSprite = &sprite[nSprite];
    memset(&sprite[nSprite], 0, sizeof(spritetype));
    InsertSpriteStat(nSprite, nStat);
    InsertSpriteSect(nSprite, nSector);
    pSprite->cstat = 128;
    pSprite->clipdist = 32;
    pSprite->xrepeat = pSprite->yrepeat = 64;
    pSprite->owner = -1;
    pSprite->extra = -1;
    pSprite->index = nSprite;
    xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;

#ifdef POLYMER
    gPolymerLight[nSprite].lightId = -1;
#endif

    Numsprites++;

    return nSprite;
}

int qinsertsprite(short nSector, short nStat) // Replace
{
    return InsertSprite(nSector, nStat);
}

int DeleteSprite(int nSprite)
{
#ifdef POLYMER
    if (gPolymerLight[nSprite].lightptr != NULL && videoGetRenderMode() == REND_POLYMER)
        DeleteLight(nSprite);
#endif
    if (sprite[nSprite].extra > 0)
    {
        dbDeleteXSprite(sprite[nSprite].extra);
    }
    dassert(sprite[nSprite].statnum >= 0 && sprite[nSprite].statnum < kMaxStatus);
    RemoveSpriteStat(nSprite);
    dassert(sprite[nSprite].sectnum >= 0 && sprite[nSprite].sectnum < kMaxSectors);
    RemoveSpriteSect(nSprite);
    InsertSpriteStat(nSprite, kMaxStatus);

    Numsprites--;

    return nSprite;
}

int qdeletesprite(short nSprite) // Replace
{
    return DeleteSprite(nSprite);
}

int ChangeSpriteSect(int nSprite, int nSector)
{
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    dassert(nSector >= 0 && nSector < kMaxSectors);
    dassert(sprite[nSprite].sectnum >= 0 && sprite[nSprite].sectnum < kMaxSectors);
    RemoveSpriteSect(nSprite);
    InsertSpriteSect(nSprite, nSector);
    return 0;
}

int qchangespritesect(short nSprite, short nSector)
{
    return ChangeSpriteSect(nSprite, nSector);
}

int ChangeSpriteStat(int nSprite, int nStatus)
{
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    dassert(nStatus >= 0 && nStatus < kMaxStatus);
    dassert(sprite[nSprite].statnum >= 0 && sprite[nSprite].statnum < kMaxStatus);
    dassert(sprite[nSprite].sectnum >= 0 && sprite[nSprite].sectnum < kMaxSectors);
    RemoveSpriteStat(nSprite);
    InsertSpriteStat(nSprite, nStatus);
    return 0;
}

int qchangespritestat(short nSprite, short nStatus)
{
    return ChangeSpriteStat(nSprite, nStatus);
}

unsigned short nextXSprite[kMaxXSprites];
unsigned short nextXWall[kMaxXWalls];
unsigned short nextXSector[kMaxXSectors];

void InitFreeList(unsigned short *pList, int nCount)
{
    for (int i = 1; i < nCount; i++)
    {
        pList[i] = i-1;
    }
    pList[0] = nCount - 1;
}

void InsertFree(unsigned short *pList, int nIndex)
{
    pList[nIndex] = pList[0];
    pList[0] = nIndex;
}

unsigned short dbInsertXSprite(int nSprite)
{
    int nXSprite = nextXSprite[0];
    nextXSprite[0] = nextXSprite[nXSprite];
    if (nXSprite == 0)
    {
        ThrowError("Out of free XSprites");
    }
    memset(&xsprite[nXSprite], 0, sizeof(XSPRITE));
    if (!bVanilla)
        memset(&gSpriteHit[nXSprite], 0, sizeof(SPRITEHIT));
    xsprite[nXSprite].reference = nSprite;
    sprite[nSprite].extra = nXSprite;
    return nXSprite;
}

void dbDeleteXSprite(int nXSprite)
{
    dassert(xsprite[nXSprite].reference >= 0);
    dassert(sprite[xsprite[nXSprite].reference].extra == nXSprite);
    InsertFree(nextXSprite, nXSprite);
    sprite[xsprite[nXSprite].reference].extra = -1;
    xsprite[nXSprite].reference = -1;
}

unsigned short dbInsertXWall(int nWall)
{
    int nXWall = nextXWall[0];
    nextXWall[0] = nextXWall[nXWall];
    if (nXWall == 0)
    {
        ThrowError("Out of free XWalls");
    }
    memset(&xwall[nXWall], 0, sizeof(XWALL));
    xwall[nXWall].reference = nWall;
    wall[nWall].extra = nXWall;
    return nXWall;
}

void dbDeleteXWall(int nXWall)
{
    dassert(xwall[nXWall].reference >= 0);
    InsertFree(nextXWall, nXWall);
    wall[xwall[nXWall].reference].extra = -1;
    xwall[nXWall].reference = -1;
}

unsigned short dbInsertXSector(int nSector)
{
    int nXSector = nextXSector[0];
    nextXSector[0] = nextXSector[nXSector];
    if (nXSector == 0)
    {
        ThrowError("Out of free XSectors");
    }
    memset(&xsector[nXSector], 0, sizeof(XSECTOR));
    xsector[nXSector].reference = nSector;
    sector[nSector].extra = nXSector;
    return nXSector;
}

void dbDeleteXSector(int nXSector)
{
    dassert(xsector[nXSector].reference >= 0);
    InsertFree(nextXSector, nXSector);
    sector[xsector[nXSector].reference].extra = -1;
    xsector[nXSector].reference = -1;
}

void dbXSpriteClean(void)
{
    for (int i = 0; i < kMaxSprites; i++)
    {
        int nXSprite = sprite[i].extra;
        if (nXSprite == 0)
        {
            sprite[i].extra = -1;
        }
        if (sprite[i].statnum < kMaxStatus && nXSprite > 0)
        {
            dassert(nXSprite < kMaxXSprites);
            if (xsprite[nXSprite].reference != i)
            {
                int nXSprite2 = dbInsertXSprite(i);
                memcpy(&xsprite[nXSprite2], &xsprite[nXSprite], sizeof(XSPRITE));
                xsprite[nXSprite2].reference = i;
            }
        }
    }
    for (int i = 1; i < kMaxXSprites; i++)
    {
        int nSprite = xsprite[i].reference;
        if (nSprite >= 0)
        {
            dassert(nSprite < kMaxSprites);
            if (sprite[nSprite].statnum >= kMaxStatus || sprite[nSprite].extra != i)
            {
                InsertFree(nextXSprite, i);
                xsprite[i].reference = -1;
            }
        }
    }
}

void dbXWallClean(void)
{
    for (int i = 0; i < numwalls; i++)
    {
        int nXWall = wall[i].extra;
        if (nXWall == 0)
        {
            wall[i].extra = -1;
        }
        if (nXWall > 0)
        {
            dassert(nXWall < kMaxXWalls);
            if (xwall[nXWall].reference == -1)
            {
                wall[i].extra = -1;
            }
            else
            {
                xwall[nXWall].reference = i;
            }
        }
    }
    for (int i = 0; i < numwalls; i++)
    {
        int nXWall = wall[i].extra;
        if (nXWall > 0)
        {
            dassert(nXWall < kMaxXWalls);
            if (xwall[nXWall].reference != i)
            {
                int nXWall2 = dbInsertXWall(i);
                memcpy(&xwall[nXWall2], &xwall[nXWall], sizeof(XWALL));
                xwall[nXWall2].reference = i;
            }
        }
    }
    for (int i = 1; i < kMaxXWalls; i++)
    {
        int nWall = xwall[i].reference;
        if (nWall >= 0)
        {
            dassert(nWall < kMaxWalls);
            if (nWall >= numwalls || wall[nWall].extra != i)
            {
                InsertFree(nextXWall, i);
                xwall[i].reference = -1;
            }
        }
    }
}

void dbXSectorClean(void)
{

    for (int i = 0; i < numsectors; i++)
    {
        int nXSector = sector[i].extra;
        if (nXSector == 0)
        {
            sector[i].extra = -1;
        }
        if (nXSector > 0)
        {
            dassert(nXSector < kMaxXSectors);
            if (xsector[nXSector].reference == -1)
            {
                sector[i].extra = -1;
            }
            else
            {
                xsector[nXSector].reference = i;
            }
        }
    }
    for (int i = 0; i < numsectors; i++)
    {
        int nXSector = sector[i].extra;
        if (nXSector > 0)
        {
            dassert(nXSector < kMaxXSectors);
            if (xsector[nXSector].reference != i)
            {
                int nXSector2 = dbInsertXSector(i);
                memcpy(&xsector[nXSector2], &xsector[nXSector], sizeof(XSECTOR));
                xsector[nXSector2].reference = i;
            }
        }
    }
    for (int i = 1; i < kMaxXSectors; i++)
    {
        int nSector = xsector[i].reference;
        if (nSector >= 0)
        {
            dassert(nSector < kMaxSectors);
            if (nSector >= numsectors || sector[nSector].extra != i)
            {
                InsertFree(nextXSector, i);
                xsector[i].reference = -1;
            }
        }
    }
}

void dbInit(void)
{
    InitFreeList(nextXSprite, kMaxXSprites);
    for (int i = 1; i < kMaxXSprites; i++)
    {
        xsprite[i].reference = -1;
    }
    InitFreeList(nextXWall, kMaxXWalls);
    for (int i = 1; i < kMaxXWalls; i++)
    {
        xwall[i].reference = -1;
    }
    InitFreeList(nextXSector, kMaxXSectors);
    for (int i = 1; i < kMaxXSectors; i++)
    {
        xsector[i].reference = -1;
    }
    initspritelists();
    for (int i = 0; i < kMaxSprites; i++)
    {
        sprite[i].cstat = 128;
    }
}

void PropagateMarkerReferences(void)
{
    int nSprite, nNextSprite;
    for (nSprite = headspritestat[kStatMarker]; nSprite != -1; nSprite = nNextSprite) {

        nNextSprite = nextspritestat[nSprite];

        switch (sprite[nSprite].type)  {
            case kMarkerOff:
            case kMarkerAxis:
            case kMarkerWarpDest: {
                int nOwner = sprite[nSprite].owner;
                if (nOwner >= 0 && nOwner < numsectors) {
                    int nXSector = sector[nOwner].extra;
                    if (nXSector > 0 && nXSector < kMaxXSectors) {
                        xsector[nXSector].marker0 = nSprite;
                        continue;
                    }
                }
            }
            break;
            case kMarkerOn: {
                int nOwner = sprite[nSprite].owner;
                if (nOwner >= 0 && nOwner < numsectors) {
                    int nXSector = sector[nOwner].extra;
                    if (nXSector > 0 && nXSector < kMaxXSectors) {
                        xsector[nXSector].marker1 = nSprite;
                        continue;
                    }
                }
            }
            break;
        }

        DeleteSprite(nSprite);
    }
}

bool byte_1A76C6, byte_1A76C7, byte_1A76C8;

MAPHEADER2 byte_19AE44;

unsigned int dbReadMapCRC(const char *pPath)
{
    char name2[BMAX_PATH];
    byte_1A76C7 = 0;
    byte_1A76C8 = 0;

    int const bakpathsearchmode = pathsearchmode;
    pathsearchmode = 1;

    Bstrncpy(name2, pPath, BMAX_PATH);
    Bstrupr(name2);
    DICTNODE* pNode = *gSysRes.Probe(name2, "MAP");
    if (pNode && pNode->flags & DICT_EXTERNAL)
    {
        gSysRes.RemoveNode(pNode);
    }
    pNode = gSysRes.Lookup(pPath, "MAP");
    if (!pNode)
    {
        char name2[BMAX_PATH];
        Bstrncpy(name2, pPath, BMAX_PATH);
        ChangeExtension(name2, "");
        pNode = gSysRes.Lookup(name2, "MAP");
    }

    if (!pNode)
    {
        initprintf("Error opening map file %s", pPath);
        pathsearchmode = bakpathsearchmode;
        return -1;
    }
    char *pData = (char*)gSysRes.Lock(pNode);
    pathsearchmode = bakpathsearchmode;

    int nSize = pNode->size;
    MAPSIGNATURE header;
    IOBuffer(nSize, pData).Read(&header, 6);
#if B_BIG_ENDIAN == 1
    header.version = B_LITTLE16(header.version);
#endif
    if (memcmp(header.signature, "BLM\x1a", 4))
    {
        ThrowError("Map file corrupted");
    }
    if ((header.version & 0xff00) == 0x600)
    {
    }
    else if ((header.version & 0xff00) == 0x700)
    {
        byte_1A76C8 = 1;
    }
    else
    {
        ThrowError("Map file is wrong version");
    }
    unsigned int nCRC = *(unsigned int*)(pData+nSize-4);
    gSysRes.Unlock(pNode);
    return nCRC;
}

int gMapRev, gSongId, gSkyCount;
//char byte_19AE44[128];
const int nXSectorSize = 60;
const int nXSpriteSize = 56;
const int nXWallSize = 24;

int dbLoadMap(const char *pPath, int *pX, int *pY, int *pZ, short *pAngle, short *pSector, unsigned int *pCRC) {
    char name2[BMAX_PATH]; int16_t tpskyoff[256];

    memset(show2dsector,0,sizeof(show2dsector));
    memset(show2dwall,0,sizeof(show2dwall));
    memset(show2dsprite,0,sizeof(show2dsprite));
    memset(spriteext,0,kMaxSprites*sizeof(spriteext_t));

    memset(xvel,0,sizeof(xvel));
    memset(yvel,0,sizeof(yvel));
    memset(zvel,0,sizeof(zvel));
    memset(xsprite,0,kMaxXSprites*sizeof(XSPRITE));
    memset(sprite,0,kMaxSprites*sizeof(spritetype));

    #ifdef NOONE_EXTENSIONS
    gModernMap = false;
    #endif

#ifdef USE_OPENGL
    Polymost_prepare_loadboard();
#endif

    int const bakpathsearchmode = pathsearchmode;
    pathsearchmode = 1;

    Bstrncpy(name2, pPath, BMAX_PATH);
    Bstrupr(name2);
    DICTNODE* pNode = *gSysRes.Probe(name2, "MAP");
    if (pNode && pNode->flags & DICT_EXTERNAL)
    {
        gSysRes.RemoveNode(pNode);
    }

    pNode = gSysRes.Lookup(pPath, "MAP");
    if (!pNode)
    {
        char name2[BMAX_PATH];
        Bstrncpy(name2, pPath, BMAX_PATH);
        ChangeExtension(name2, "");
        pNode = gSysRes.Lookup(name2, "MAP");
    }

    if (!pNode)
    {
        initprintf("Error opening map file %s", pPath);
        pathsearchmode = bakpathsearchmode;
        return -1;
    }
    char *pData = (char*)gSysRes.Lock(pNode);
    pathsearchmode = bakpathsearchmode;
    int nSize = pNode->size;
    MAPSIGNATURE header;
    IOBuffer IOBuffer1 = IOBuffer(nSize, pData);
    IOBuffer1.Read(&header, 6);
#if B_BIG_ENDIAN == 1
    header.version = B_LITTLE16(header.version);
#endif
    if (memcmp(header.signature, "BLM\x1a", 4))
    {
        initprintf("Map file corrupted");
        gSysRes.Unlock(pNode);
        return -1;
    }
    byte_1A76C8 = 0;
    if ((header.version & 0xff00) == 0x700) {
        byte_1A76C8 = 1;

        #ifdef NOONE_EXTENSIONS
        // indicate if the map requires modern features to work properly
        // for maps wich created in PMAPEDIT BETA13 or higher versions. Since only minor version changed,
        // the map is still can be loaded with vanilla BLOOD / MAPEDIT and should work in other ports too.
        if ((header.version & 0x00ff) == 0x001) gModernMap = true;
        #endif

    } else {
        initprintf("Map file is wrong version");
        gSysRes.Unlock(pNode);
        return -1;
    }

    MAPHEADER mapHeader;
    IOBuffer1.Read(&mapHeader,37/* sizeof(mapHeader)*/);
    if (mapHeader.at16 != 0 && mapHeader.at16 != 0x7474614d && mapHeader.at16 != 0x4d617474) {
        dbCrypt((char*)&mapHeader, sizeof(mapHeader), 0x7474614d);
        byte_1A76C7 = 1;
    }

#if B_BIG_ENDIAN == 1
    mapHeader.at0 = B_LITTLE32(mapHeader.at0);
    mapHeader.at4 = B_LITTLE32(mapHeader.at4);
    mapHeader.at8 = B_LITTLE32(mapHeader.at8);
    mapHeader.atc = B_LITTLE16(mapHeader.atc);
    mapHeader.ate = B_LITTLE16(mapHeader.ate);
    mapHeader.at10 = B_LITTLE16(mapHeader.at10);
    mapHeader.at12 = B_LITTLE32(mapHeader.at12);
    mapHeader.at16 = B_LITTLE32(mapHeader.at16);
    mapHeader.at1b = B_LITTLE32(mapHeader.at1b);
    mapHeader.at1f = B_LITTLE16(mapHeader.at1f);
    mapHeader.at21 = B_LITTLE16(mapHeader.at21);
    mapHeader.at23 = B_LITTLE16(mapHeader.at23);
#endif

    psky_t *pSky = tileSetupSky(0);
    pSky->horizfrac = 65536;

    *pX = mapHeader.at0;
    *pY = mapHeader.at4;
    *pZ = mapHeader.at8;
    *pAngle = mapHeader.atc;
    *pSector = mapHeader.ate;
    pSky->lognumtiles = mapHeader.at10;
    gVisibility = g_visibility = mapHeader.at12;
    gSongId = mapHeader.at16;
    if (byte_1A76C8)
    {
        if (mapHeader.at16 == 0x7474614d || mapHeader.at16 == 0x4d617474)
        {
            byte_1A76C6 = 1;
        }
        else if (!mapHeader.at16)
        {
            byte_1A76C6 = 0;
        }
        else
        {
            initprintf("Corrupted Map file");
            gSysRes.Unlock(pNode);
            return -1;
        }
    }
    else if (mapHeader.at16)
    {
        initprintf("Corrupted Map file");
        gSysRes.Unlock(pNode);
        return -1;
    }
    parallaxtype = mapHeader.at1a;
    gMapRev = mapHeader.at1b;
    numsectors = mapHeader.at1f;
    numwalls = mapHeader.at21;
    dbInit();
    if (byte_1A76C8)
    {
        IOBuffer1.Read(&byte_19AE44, 128);
        dbCrypt((char*)&byte_19AE44, 128, numwalls);
#if B_BIG_ENDIAN == 1
        byte_19AE44.at40 = B_LITTLE32(byte_19AE44.at40);
        byte_19AE44.at44 = B_LITTLE32(byte_19AE44.at44);
        byte_19AE44.at48 = B_LITTLE32(byte_19AE44.at48);
#endif
    }
    else
    {
        memset(&byte_19AE44, 0, 128);
    }
    gSkyCount = 1<<pSky->lognumtiles;
    IOBuffer1.Read(tpskyoff, gSkyCount*sizeof(tpskyoff[0]));
    if (byte_1A76C8)
    {
        dbCrypt((char*)tpskyoff, gSkyCount*sizeof(tpskyoff[0]), gSkyCount*2);
    }
    for (int i = 0; i < ClipHigh(gSkyCount, MAXPSKYTILES); i++)
    {
        pSky->tileofs[i] = B_LITTLE16(tpskyoff[i]);
    }
    for (int i = 0; i < numsectors; i++)
    {
        sectortype *pSector = &sector[i];
        IOBuffer1.Read(pSector, sizeof(sectortype));
        if (byte_1A76C8)
        {
            dbCrypt((char*)pSector, sizeof(sectortype), gMapRev*sizeof(sectortype));
        }
#if B_BIG_ENDIAN == 1
        pSector->wallptr = B_LITTLE16(pSector->wallptr);
        pSector->wallnum = B_LITTLE16(pSector->wallnum);
        pSector->ceilingz = B_LITTLE32(pSector->ceilingz);
        pSector->floorz = B_LITTLE32(pSector->floorz);
        pSector->ceilingstat = B_LITTLE16(pSector->ceilingstat);
        pSector->floorstat = B_LITTLE16(pSector->floorstat);
        pSector->ceilingpicnum = B_LITTLE16(pSector->ceilingpicnum);
        pSector->ceilingheinum = B_LITTLE16(pSector->ceilingheinum);
        pSector->floorpicnum = B_LITTLE16(pSector->floorpicnum);
        pSector->floorheinum = B_LITTLE16(pSector->floorheinum);
        pSector->type = B_LITTLE16(pSector->type);
        pSector->hitag = B_LITTLE16(pSector->hitag);
        pSector->extra = B_LITTLE16(pSector->extra);
#endif
        qsector_filler[i] = pSector->fogpal;
        pSector->fogpal = 0;
        if (sector[i].extra > 0)
        {
            char pBuffer[nXSectorSize];
            int nXSector = dbInsertXSector(i);
            XSECTOR *pXSector = &xsector[nXSector];
            memset(pXSector, 0, sizeof(XSECTOR));
            int nCount;
            if (!byte_1A76C8)
            {
                nCount = nXSectorSize;
            }
            else
            {
                nCount = byte_19AE44.at48;
            }
            dassert(nCount <= nXSectorSize);
            IOBuffer1.Read(pBuffer, nCount);
            BitReader bitReader(pBuffer, nCount);
            pXSector->reference = bitReader.readSigned(14);
            pXSector->state = bitReader.readUnsigned(1);
            pXSector->busy = bitReader.readUnsigned(17);
            pXSector->data = bitReader.readUnsigned(16);
            pXSector->txID = bitReader.readUnsigned(10);
            pXSector->busyWaveA = bitReader.readUnsigned(3);
            pXSector->busyWaveB = bitReader.readUnsigned(3);
            pXSector->rxID = bitReader.readUnsigned(10);
            pXSector->command = bitReader.readUnsigned(8);
            pXSector->triggerOn = bitReader.readUnsigned(1);
            pXSector->triggerOff = bitReader.readUnsigned(1);
            pXSector->busyTimeA = bitReader.readUnsigned(12);
            pXSector->waitTimeA = bitReader.readUnsigned(12);
            pXSector->restState = bitReader.readUnsigned(1);
            pXSector->interruptable = bitReader.readUnsigned(1);
            pXSector->amplitude = bitReader.readSigned(8);
            pXSector->freq = bitReader.readUnsigned(8);
            pXSector->reTriggerA = bitReader.readUnsigned(1);
            pXSector->reTriggerB = bitReader.readUnsigned(1);
            pXSector->phase = bitReader.readUnsigned(8);
            pXSector->wave = bitReader.readUnsigned(4);
            pXSector->shadeAlways = bitReader.readUnsigned(1);
            pXSector->shadeFloor = bitReader.readUnsigned(1);
            pXSector->shadeCeiling = bitReader.readUnsigned(1);
            pXSector->shadeWalls = bitReader.readUnsigned(1);
            pXSector->shade = bitReader.readSigned(8);
            pXSector->panAlways = bitReader.readUnsigned(1);
            pXSector->panFloor = bitReader.readUnsigned(1);
            pXSector->panCeiling = bitReader.readUnsigned(1);
            pXSector->Drag = bitReader.readUnsigned(1);
            pXSector->Underwater = bitReader.readUnsigned(1);
            pXSector->Depth = bitReader.readUnsigned(3);
            pXSector->panVel = bitReader.readUnsigned(8);
            pXSector->panAngle = bitReader.readUnsigned(11);
            pXSector->unused1 = bitReader.readUnsigned(1);
            pXSector->decoupled = bitReader.readUnsigned(1);
            pXSector->triggerOnce = bitReader.readUnsigned(1);
            pXSector->isTriggered = bitReader.readUnsigned(1);
            pXSector->Key = bitReader.readUnsigned(3);
            pXSector->Push = bitReader.readUnsigned(1);
            pXSector->Vector = bitReader.readUnsigned(1);
            pXSector->Reserved = bitReader.readUnsigned(1);
            pXSector->Enter = bitReader.readUnsigned(1);
            pXSector->Exit = bitReader.readUnsigned(1);
            pXSector->Wallpush = bitReader.readUnsigned(1);
            pXSector->color = bitReader.readUnsigned(1);
            pXSector->unused2 = bitReader.readUnsigned(1);
            pXSector->busyTimeB = bitReader.readUnsigned(12);
            pXSector->waitTimeB = bitReader.readUnsigned(12);
            pXSector->stopOn = bitReader.readUnsigned(1);
            pXSector->stopOff = bitReader.readUnsigned(1);
            pXSector->ceilpal = bitReader.readUnsigned(4);
            pXSector->offCeilZ = bitReader.readSigned(32);
            pXSector->onCeilZ = bitReader.readSigned(32);
            pXSector->offFloorZ = bitReader.readSigned(32);
            pXSector->onFloorZ = bitReader.readSigned(32);
            pXSector->marker0 = bitReader.readUnsigned(16);
            pXSector->marker1 = bitReader.readUnsigned(16);
            pXSector->Crush = bitReader.readUnsigned(1);
            pXSector->ceilXPanFrac = bitReader.readUnsigned(8);
            pXSector->ceilYPanFrac = bitReader.readUnsigned(8);
            pXSector->floorXPanFrac = bitReader.readUnsigned(8);
            pXSector->damageType = bitReader.readUnsigned(3);
            pXSector->floorpal = bitReader.readUnsigned(4);
            pXSector->floorYPanFrac = bitReader.readUnsigned(8);
            pXSector->locked = bitReader.readUnsigned(1);
            pXSector->windVel = bitReader.readUnsigned(10);
            pXSector->windAng = bitReader.readUnsigned(11);
            pXSector->windAlways = bitReader.readUnsigned(1);
            pXSector->dudeLockout = bitReader.readUnsigned(1);
            pXSector->bobTheta = bitReader.readUnsigned(11);
            pXSector->bobZRange = bitReader.readUnsigned(5);
            pXSector->bobSpeed = bitReader.readSigned(12);
            pXSector->bobAlways = bitReader.readUnsigned(1);
            pXSector->bobFloor = bitReader.readUnsigned(1);
            pXSector->bobCeiling = bitReader.readUnsigned(1);
            pXSector->bobRotate = bitReader.readUnsigned(1);
            xsector[sector[i].extra].reference = i;
            xsector[sector[i].extra].busy = xsector[sector[i].extra].state<<16;

        }
    }
    for (int i = 0; i < numwalls; i++)
    {
        walltype *pWall = &wall[i];
        IOBuffer1.Read(pWall, sizeof(walltype));
        if (byte_1A76C8)
        {
            dbCrypt((char*)pWall, sizeof(walltype), (gMapRev*sizeof(sectortype)) | 0x7474614d);
        }
#if B_BIG_ENDIAN == 1
        pWall->x = B_LITTLE32(pWall->x);
        pWall->y = B_LITTLE32(pWall->y);
        pWall->point2 = B_LITTLE16(pWall->point2);
        pWall->nextwall = B_LITTLE16(pWall->nextwall);
        pWall->nextsector = B_LITTLE16(pWall->nextsector);
        pWall->cstat = B_LITTLE16(pWall->cstat);
        pWall->picnum = B_LITTLE16(pWall->picnum);
        pWall->overpicnum = B_LITTLE16(pWall->overpicnum);
        pWall->type = B_LITTLE16(pWall->type);
        pWall->hitag = B_LITTLE16(pWall->hitag);
        pWall->extra = B_LITTLE16(pWall->extra);
#endif
        if (wall[i].extra > 0)
        {
            char pBuffer[nXWallSize];
            int nXWall = dbInsertXWall(i);
            XWALL *pXWall = &xwall[nXWall];
            memset(pXWall, 0, sizeof(XWALL));
            int nCount;
            if (!byte_1A76C8)
            {
                nCount = nXWallSize;
            }
            else
            {
                nCount = byte_19AE44.at44;
            }
            dassert(nCount <= nXWallSize);
            IOBuffer1.Read(pBuffer, nCount);
            BitReader bitReader(pBuffer, nCount);
            pXWall->reference = bitReader.readSigned(14);
            pXWall->state = bitReader.readUnsigned(1);
            pXWall->busy = bitReader.readUnsigned(17);
            pXWall->data = bitReader.readSigned(16);
            pXWall->txID = bitReader.readUnsigned(10);
            pXWall->unused1 = bitReader.readUnsigned(6);
            pXWall->rxID = bitReader.readUnsigned(10);
            pXWall->command = bitReader.readUnsigned(8);
            pXWall->triggerOn = bitReader.readUnsigned(1);
            pXWall->triggerOff = bitReader.readUnsigned(1);
            pXWall->busyTime = bitReader.readUnsigned(12);
            pXWall->waitTime = bitReader.readUnsigned(12);
            pXWall->restState = bitReader.readUnsigned(1);
            pXWall->interruptable = bitReader.readUnsigned(1);
            pXWall->panAlways = bitReader.readUnsigned(1);
            pXWall->panXVel = bitReader.readSigned(8);
            pXWall->panYVel = bitReader.readSigned(8);
            pXWall->decoupled = bitReader.readUnsigned(1);
            pXWall->triggerOnce = bitReader.readUnsigned(1);
            pXWall->isTriggered = bitReader.readUnsigned(1);
            pXWall->key = bitReader.readUnsigned(3);
            pXWall->triggerPush = bitReader.readUnsigned(1);
            pXWall->triggerVector = bitReader.readUnsigned(1);
            pXWall->triggerTouch = bitReader.readUnsigned(1);
            pXWall->unused2 = bitReader.readUnsigned(2);
            pXWall->xpanFrac = bitReader.readUnsigned(8);
            pXWall->ypanFrac = bitReader.readUnsigned(8);
            pXWall->locked = bitReader.readUnsigned(1);
            pXWall->dudeLockout = bitReader.readUnsigned(1);
            pXWall->unused3 = bitReader.readUnsigned(4);
            pXWall->unused4 = bitReader.readUnsigned(32);
            xwall[wall[i].extra].reference = i;
            xwall[wall[i].extra].busy = xwall[wall[i].extra].state << 16;

        }
    }
    initspritelists();
    for (int i = 0; i < mapHeader.at23; i++)
    {
        RemoveSpriteStat(i);
        spritetype *pSprite = &sprite[i];
        IOBuffer1.Read(pSprite, sizeof(spritetype));
        if (byte_1A76C8)
        {
            dbCrypt((char*)pSprite, sizeof(spritetype), (gMapRev*sizeof(spritetype)) | 0x7474614d);
        }
#if B_BIG_ENDIAN == 1
        pSprite->x = B_LITTLE32(pSprite->x);
        pSprite->y = B_LITTLE32(pSprite->y);
        pSprite->z = B_LITTLE32(pSprite->z);
        pSprite->cstat = B_LITTLE16(pSprite->cstat);
        pSprite->picnum = B_LITTLE16(pSprite->picnum);
        pSprite->sectnum = B_LITTLE16(pSprite->sectnum);
        pSprite->statnum = B_LITTLE16(pSprite->statnum);
        pSprite->ang = B_LITTLE16(pSprite->ang);
        pSprite->owner = B_LITTLE16(pSprite->owner);
        pSprite->index = B_LITTLE16(pSprite->index);
        pSprite->yvel = B_LITTLE16(pSprite->yvel);
        pSprite->inittype = B_LITTLE16(pSprite->inittype);
        pSprite->type = B_LITTLE16(pSprite->type);
        pSprite->flags = B_LITTLE16(pSprite->hitag);
        pSprite->extra = B_LITTLE16(pSprite->extra);
#endif
        InsertSpriteSect(i, sprite[i].sectnum);
        InsertSpriteStat(i, sprite[i].statnum);
        Numsprites++;
        sprite[i].index = i;
        qsprite_filler[i] = pSprite->blend;
        pSprite->blend = 0;
        if (sprite[i].extra > 0)
        {
            char pBuffer[nXSpriteSize];
            int nXSprite = dbInsertXSprite(i);
            XSPRITE *pXSprite = &xsprite[nXSprite];
            memset(pXSprite, 0, sizeof(XSPRITE));
            int nCount;
            if (!byte_1A76C8)
            {
                nCount = nXSpriteSize;
            }
            else
            {
                nCount = byte_19AE44.at40;
            }
            dassert(nCount <= nXSpriteSize);
            IOBuffer1.Read(pBuffer, nCount);
            BitReader bitReader(pBuffer, nCount);
            pXSprite->reference = bitReader.readSigned(14);
            pXSprite->state = bitReader.readUnsigned(1);
            pXSprite->busy = bitReader.readUnsigned(17);
            pXSprite->txID = bitReader.readUnsigned(10);
            pXSprite->rxID = bitReader.readUnsigned(10);
            pXSprite->command = bitReader.readUnsigned(8);
            pXSprite->triggerOn = bitReader.readUnsigned(1);
            pXSprite->triggerOff = bitReader.readUnsigned(1);
            pXSprite->wave = bitReader.readUnsigned(2);
            pXSprite->busyTime = bitReader.readUnsigned(12);
            pXSprite->waitTime = bitReader.readUnsigned(12);
            pXSprite->restState = bitReader.readUnsigned(1);
            pXSprite->Interrutable = bitReader.readUnsigned(1);
            pXSprite->unused1 = bitReader.readUnsigned(2);
            pXSprite->respawnPending = bitReader.readUnsigned(2);
            pXSprite->unused2 = bitReader.readUnsigned(1);
            pXSprite->lT = bitReader.readUnsigned(1);
            pXSprite->dropMsg = bitReader.readUnsigned(8);
            pXSprite->Decoupled = bitReader.readUnsigned(1);
            pXSprite->triggerOnce = bitReader.readUnsigned(1);
            pXSprite->isTriggered = bitReader.readUnsigned(1);
            pXSprite->key = bitReader.readUnsigned(3);
            pXSprite->Push = bitReader.readUnsigned(1);
            pXSprite->Vector = bitReader.readUnsigned(1);
            pXSprite->Impact = bitReader.readUnsigned(1);
            pXSprite->Pickup = bitReader.readUnsigned(1);
            pXSprite->Touch = bitReader.readUnsigned(1);
            pXSprite->Sight = bitReader.readUnsigned(1);
            pXSprite->Proximity = bitReader.readUnsigned(1);
            pXSprite->unused3 = bitReader.readUnsigned(2);
            pXSprite->lSkill = bitReader.readUnsigned(5);
            pXSprite->lS = bitReader.readUnsigned(1);
            pXSprite->lB = bitReader.readUnsigned(1);
            pXSprite->lC = bitReader.readUnsigned(1);
            pXSprite->DudeLockout = bitReader.readUnsigned(1);
            pXSprite->data1 = bitReader.readSigned(16);
            pXSprite->data2 = bitReader.readSigned(16);
            pXSprite->data3 = bitReader.readSigned(16);
            pXSprite->goalAng = bitReader.readUnsigned(11);
            pXSprite->dodgeDir = bitReader.readSigned(2);
            pXSprite->locked = bitReader.readUnsigned(1);
            pXSprite->medium = bitReader.readUnsigned(2);
            pXSprite->respawn = bitReader.readUnsigned(2);
            pXSprite->data4 = bitReader.readUnsigned(16);
            pXSprite->unused4 = bitReader.readUnsigned(6);
            pXSprite->lockMsg = bitReader.readUnsigned(8);
            pXSprite->health = bitReader.readUnsigned(12);
            pXSprite->dudeDeaf = bitReader.readUnsigned(1);
            pXSprite->dudeAmbush = bitReader.readUnsigned(1);
            pXSprite->dudeGuard = bitReader.readUnsigned(1);
            pXSprite->dudeFlag4 = bitReader.readUnsigned(1);
            pXSprite->target = bitReader.readSigned(16);
            pXSprite->targetX = bitReader.readSigned(32);
            pXSprite->targetY = bitReader.readSigned(32);
            pXSprite->targetZ = bitReader.readSigned(32);
            pXSprite->burnTime = bitReader.readUnsigned(16);
            pXSprite->burnSource = bitReader.readSigned(16);
            pXSprite->height = bitReader.readUnsigned(16);
            pXSprite->stateTimer = bitReader.readUnsigned(16);
            pXSprite->aiState = NULL;
            bitReader.skipBits(32);
            xsprite[sprite[i].extra].reference = i;
            xsprite[sprite[i].extra].busy = xsprite[sprite[i].extra].state << 16;
            if (!byte_1A76C8) {
                xsprite[sprite[i].extra].lT |= xsprite[sprite[i].extra].lB;
            }

            #ifdef NOONE_EXTENSIONS
            // indicate if the map requires modern features to work properly
            // for maps wich created in different editors (include vanilla MAPEDIT) or in PMAPEDIT version below than BETA13
            if (!gModernMap && pXSprite->rxID == kChannelMapModernize && pXSprite->rxID == pXSprite->txID && pXSprite->command == kCmdModernFeaturesEnable)
                gModernMap = true;
            #endif
        }
#if 0
        if ((sprite[i].cstat & 0x30) == 0x30)
        {
            sprite[i].cstat &= ~0x30;
        }
#endif
    }
    unsigned int nCRC;
    IOBuffer1.Read(&nCRC, 4);
#if B_BIG_ENDIAN == 1
    nCRC = B_LITTLE32(nCRC);
#endif
    md4once((unsigned char*)pData, nSize, g_loadedMapHack.md4);
    if (Bcrc32(pData, nSize-4, 0) != nCRC)
    {
        initprintf("Map File does not match CRC");
        gSysRes.Unlock(pNode);
        return -1;
    }
    if (pCRC)
        *pCRC = nCRC;
    gSysRes.Unlock(pNode);
    PropagateMarkerReferences();
    if (byte_1A76C8)
    {
        if (gSongId == 0x7474614d || gSongId == 0x4d617474)
        {
            byte_1A76C6 = 1;
        }
        else if (!gSongId)
        {
            byte_1A76C6 = 0;
        }
        else
        {
            initprintf("Corrupted Map file");
            return -1;
        }
    }
    else if (gSongId != 0)
    {
        initprintf("Corrupted Map file");
        return -1;
    }

#ifdef POLYMER
    if (videoGetRenderMode() == REND_POLYMER)
        polymer_loadboard();
#endif

    if ((header.version & 0xff00) == 0x600)
    {
        switch (header.version&0xff)
        {
        case 0:
            for (int i = 0; i < numsectors; i++)
            {
                sectortype *pSector = &sector[i];
                if (pSector->extra > 0)
                {
                    XSECTOR *pXSector = &xsector[pSector->extra];
                    pXSector->busyTimeB = pXSector->busyTimeA;
                    if (pXSector->busyTimeA > 0)
                    {
                        if (!pXSector->restState)
                        {
                            pXSector->reTriggerA = 1;
                        }
                        else
                        {
                            pXSector->waitTimeB = pXSector->busyTimeA;
                            pXSector->waitTimeA = 0;
                            pXSector->reTriggerB = 1;
                        }
                    }
                }
            }
            fallthrough__;
        case 1:
            for (int i = 0; i < numsectors; i++)
            {
                sectortype *pSector = &sector[i];
                if (pSector->extra > 0)
                {
                    XSECTOR *pXSector = &xsector[pSector->extra];
                    pXSector->freq >>= 1;
                }
            }
            fallthrough__;
        case 2:
            for (int i = 0; i < kMaxSprites; i++)
            {
            }
            break;

        }
    }

#ifdef YAX_ENABLE
    yax_update((header.version & 0xff00) > 0x700 ? 0 : 1);
    if (editstatus)
        yax_updategrays(*pZ);
#endif

    calc_sector_reachability();

    g_loadedMapVersion = 7;

    return 0;
}

int dbSaveMap(const char *pPath, int nX, int nY, int nZ, short nAngle, short nSector)
{
    char sMapExt[BMAX_PATH];
    //char sBakExt[BMAX_PATH];
    int16_t tpskyoff[256];
    int nSpriteNum;
    psky_t *pSky = tileSetupSky(0);
    gSkyCount = 1<<pSky->lognumtiles;
    gMapRev++;
    nSpriteNum = 0;
    strcpy(sMapExt, pPath);
    ChangeExtension(sMapExt, ".MAP");
    int nSize = sizeof(MAPSIGNATURE)+sizeof(MAPHEADER);
    if (byte_1A76C8)
    {
        nSize += sizeof(MAPHEADER2);
    }
    for (int i = 0; i < gSkyCount; i++)
        tpskyoff[i] = pSky->tileofs[i];
    nSize += gSkyCount*sizeof(tpskyoff[0]);
    nSize += sizeof(sectortype)*numsectors;
    for (int i = 0; i < numsectors; i++)
    {
        if (sector[i].extra > 0)
        {
            nSize += nXSectorSize;
        }
    }
    nSize += sizeof(walltype)*numwalls;
    for (int i = 0; i < numwalls; i++)
    {
        if (wall[i].extra > 0)
        {
            nSize += nXWallSize;
        }
    }
    for (int i = 0; i < kMaxSprites; i++)
    {
        if (sprite[i].statnum < kMaxStatus)
        {
            nSpriteNum++;
            if (sprite[i].extra > 0)
            {
                nSize += nXSpriteSize;
            }
        }
    }
    nSize += sizeof(spritetype)*nSpriteNum;
    nSize += 4;
    char *pData = (char*)Xmalloc(nSize);
    IOBuffer IOBuffer1 = IOBuffer(nSize, pData);
    MAPSIGNATURE header;
    memcpy(&header, "BLM\x1a", 4);
    if (byte_1A76C8)
    {
        header.version = 0x700;
        byte_1A76C7 = 1;
    }
    else
    {
        header.version = 0x603;
        byte_1A76C7 = 0;
    }
    IOBuffer1.Write(&header, sizeof(header));
    MAPHEADER mapheader;
    mapheader.at0 = B_LITTLE32(nX);
    mapheader.at4 = B_LITTLE32(nY);
    mapheader.at8 = B_LITTLE32(nZ);
    mapheader.atc = B_LITTLE16(nAngle);
    mapheader.ate = B_LITTLE16(nSector);
    mapheader.at10 = B_LITTLE16(pSky->lognumtiles);
    mapheader.at12 = B_LITTLE32(gVisibility);
    if (byte_1A76C6)
    {
        gSongId = 0x7474614d;
    }
    else
    {
        gSongId = 0;
    }
    mapheader.at16 = B_LITTLE32(gSongId);
    mapheader.at1a = parallaxtype;
    mapheader.at1b = gMapRev;
    mapheader.at1f = B_LITTLE16(numsectors);
    mapheader.at21 = B_LITTLE16(numwalls);
    mapheader.at23 = B_LITTLE16(nSpriteNum);
    if (byte_1A76C7)
    {
        dbCrypt((char*)&mapheader, sizeof(MAPHEADER), 'ttaM');
    }
    IOBuffer1.Write(&mapheader, sizeof(MAPHEADER));
    if (byte_1A76C8)
    {
        Bstrcpy(byte_19AE44.at0, AppProperName);
        byte_19AE44.at48 = nXSectorSize;
        byte_19AE44.at44 = nXWallSize;
        byte_19AE44.at40 = nXSpriteSize;
        dbCrypt((char*)&byte_19AE44, sizeof(MAPHEADER2), numwalls);
        IOBuffer1.Write(&byte_19AE44, sizeof(MAPHEADER2));
        dbCrypt((char*)&byte_19AE44, sizeof(MAPHEADER2), numwalls);
    }
    if (byte_1A76C8)
    {
        dbCrypt((char*)tpskyoff, gSkyCount*sizeof(tpskyoff[0]), gSkyCount*sizeof(tpskyoff[0]));
    }
    IOBuffer1.Write(tpskyoff, gSkyCount*sizeof(tpskyoff[0]));
    if (byte_1A76C8)
    {
        dbCrypt((char*)tpskyoff, gSkyCount*sizeof(tpskyoff[0]), gSkyCount*sizeof(tpskyoff[0]));
    }
    for (int i = 0; i < numsectors; i++)
    {
        if (byte_1A76C8)
        {
            dbCrypt((char*)&sector[i], sizeof(sectortype), gMapRev*sizeof(sectortype));
        }
        IOBuffer1.Write(&sector[i], sizeof(sectortype));
        if (byte_1A76C8)
        {
            dbCrypt((char*)&sector[i], sizeof(sectortype), gMapRev*sizeof(sectortype));
        }
        if (sector[i].extra > 0)
        {
            char pBuffer[nXSectorSize];
            BitWriter bitWriter(pBuffer, nXSectorSize);
            XSECTOR* pXSector = &xsector[sector[i].extra];
            bitWriter.write(pXSector->reference, 14);
            bitWriter.write(pXSector->state, 1);
            bitWriter.write(pXSector->busy, 17);
            bitWriter.write(pXSector->data, 16);
            bitWriter.write(pXSector->txID, 10);
            bitWriter.write(pXSector->busyWaveA, 3);
            bitWriter.write(pXSector->busyWaveB, 3);
            bitWriter.write(pXSector->rxID, 10);
            bitWriter.write(pXSector->command, 8);
            bitWriter.write(pXSector->triggerOn, 1);
            bitWriter.write(pXSector->triggerOff, 1);
            bitWriter.write(pXSector->busyTimeA, 12);
            bitWriter.write(pXSector->waitTimeA, 12);
            bitWriter.write(pXSector->restState, 1);
            bitWriter.write(pXSector->interruptable, 1);
            bitWriter.write(pXSector->amplitude, 8);
            bitWriter.write(pXSector->freq, 8);
            bitWriter.write(pXSector->reTriggerA, 1);
            bitWriter.write(pXSector->reTriggerB, 1);
            bitWriter.write(pXSector->phase, 8);
            bitWriter.write(pXSector->wave, 4);
            bitWriter.write(pXSector->shadeAlways, 1);
            bitWriter.write(pXSector->shadeFloor, 1);
            bitWriter.write(pXSector->shadeCeiling, 1);
            bitWriter.write(pXSector->shadeWalls, 1);
            bitWriter.write(pXSector->shade, 8);
            bitWriter.write(pXSector->panAlways, 1);
            bitWriter.write(pXSector->panFloor, 1);
            bitWriter.write(pXSector->panCeiling, 1);
            bitWriter.write(pXSector->Drag, 1);
            bitWriter.write(pXSector->Underwater, 1);
            bitWriter.write(pXSector->Depth, 3);
            bitWriter.write(pXSector->panVel, 8);
            bitWriter.write(pXSector->panAngle, 11);
            bitWriter.write(pXSector->unused1, 1);
            bitWriter.write(pXSector->decoupled, 1);
            bitWriter.write(pXSector->triggerOnce, 1);
            bitWriter.write(pXSector->isTriggered, 1);
            bitWriter.write(pXSector->Key, 3);
            bitWriter.write(pXSector->Push, 1);
            bitWriter.write(pXSector->Vector, 1);
            bitWriter.write(pXSector->Reserved, 1);
            bitWriter.write(pXSector->Enter, 1);
            bitWriter.write(pXSector->Exit, 1);
            bitWriter.write(pXSector->Wallpush, 1);
            bitWriter.write(pXSector->color, 1);
            bitWriter.write(pXSector->unused2, 1);
            bitWriter.write(pXSector->busyTimeB, 12);
            bitWriter.write(pXSector->waitTimeB, 12);
            bitWriter.write(pXSector->stopOn, 1);
            bitWriter.write(pXSector->stopOff, 1);
            bitWriter.write(pXSector->ceilpal, 4);
            bitWriter.write(pXSector->offCeilZ, 32);
            bitWriter.write(pXSector->onCeilZ, 32);
            bitWriter.write(pXSector->offFloorZ, 32);
            bitWriter.write(pXSector->onFloorZ, 32);
            bitWriter.write(pXSector->marker0, 16);
            bitWriter.write(pXSector->marker1, 16);
            bitWriter.write(pXSector->Crush, 1);
            bitWriter.write(pXSector->ceilXPanFrac, 8);
            bitWriter.write(pXSector->ceilYPanFrac, 8);
            bitWriter.write(pXSector->floorXPanFrac, 8);
            bitWriter.write(pXSector->damageType, 3);
            bitWriter.write(pXSector->floorpal, 4);
            bitWriter.write(pXSector->floorYPanFrac, 8);
            bitWriter.write(pXSector->locked, 1);
            bitWriter.write(pXSector->windVel, 10);
            bitWriter.write(pXSector->windAng, 11);
            bitWriter.write(pXSector->windAlways, 1);
            bitWriter.write(pXSector->dudeLockout, 1);
            bitWriter.write(pXSector->bobTheta, 11);
            bitWriter.write(pXSector->bobZRange, 5);
            bitWriter.write(pXSector->bobSpeed, 12);
            bitWriter.write(pXSector->bobAlways, 1);
            bitWriter.write(pXSector->bobFloor, 1);
            bitWriter.write(pXSector->bobCeiling, 1);
            bitWriter.write(pXSector->bobRotate, 1);
            IOBuffer1.Write(pBuffer, nXSectorSize);
        }
    }
    for (int i = 0; i < numwalls; i++)
    {
        if (byte_1A76C8)
        {
            dbCrypt((char*)&wall[i], sizeof(walltype), gMapRev*sizeof(sectortype) | 0x7474614d);
        }
        IOBuffer1.Write(&wall[i], sizeof(walltype));
        if (byte_1A76C8)
        {
            dbCrypt((char*)&wall[i], sizeof(walltype), gMapRev*sizeof(sectortype) | 0x7474614d);
        }
        if (wall[i].extra > 0)
        {
            char pBuffer[nXWallSize];
            BitWriter bitWriter(pBuffer, nXWallSize);
            XWALL* pXWall = &xwall[wall[i].extra];
            bitWriter.write(pXWall->reference, 14);
            bitWriter.write(pXWall->state, 1);
            bitWriter.write(pXWall->busy, 17);
            bitWriter.write(pXWall->data, 16);
            bitWriter.write(pXWall->txID, 10);
            bitWriter.write(pXWall->unused1, 6);
            bitWriter.write(pXWall->rxID, 10);
            bitWriter.write(pXWall->command, 8);
            bitWriter.write(pXWall->triggerOn, 1);
            bitWriter.write(pXWall->triggerOff, 1);
            bitWriter.write(pXWall->busyTime, 12);
            bitWriter.write(pXWall->waitTime, 12);
            bitWriter.write(pXWall->restState, 1);
            bitWriter.write(pXWall->interruptable, 1);
            bitWriter.write(pXWall->panAlways, 1);
            bitWriter.write(pXWall->panXVel, 8);
            bitWriter.write(pXWall->panYVel, 8);
            bitWriter.write(pXWall->decoupled, 1);
            bitWriter.write(pXWall->triggerOnce, 1);
            bitWriter.write(pXWall->isTriggered, 1);
            bitWriter.write(pXWall->key, 3);
            bitWriter.write(pXWall->triggerPush, 1);
            bitWriter.write(pXWall->triggerVector, 1);
            bitWriter.write(pXWall->triggerTouch, 1);
            bitWriter.write(pXWall->unused2, 2);
            bitWriter.write(pXWall->xpanFrac, 8);
            bitWriter.write(pXWall->ypanFrac, 8);
            bitWriter.write(pXWall->locked, 1);
            bitWriter.write(pXWall->dudeLockout, 1);
            bitWriter.write(pXWall->unused3, 4);
            bitWriter.write(pXWall->unused4, 32);
            IOBuffer1.Write(pBuffer, nXWallSize);
        }
    }
    for (int i = 0; i < kMaxSprites; i++)
    {
        if (sprite[i].statnum < kMaxStatus)
        {
            if (byte_1A76C8)
            {
                dbCrypt((char*)&sprite[i], sizeof(spritetype), gMapRev*sizeof(spritetype) | 'ttaM');
            }
            IOBuffer1.Write(&sprite[i], sizeof(spritetype));
            if (byte_1A76C8)
            {
                dbCrypt((char*)&sprite[i], sizeof(spritetype), gMapRev*sizeof(spritetype) | 'ttaM');
            }
            if (sprite[i].extra > 0)
            {
                char pBuffer[nXSpriteSize];
                BitWriter bitWriter(pBuffer, nXSpriteSize);
                XSPRITE* pXSprite = &xsprite[sprite[i].extra];
                bitWriter.write(pXSprite->reference, 14);
                bitWriter.write(pXSprite->state, 1);
                bitWriter.write(pXSprite->busy, 17);
                bitWriter.write(pXSprite->txID, 10);
                bitWriter.write(pXSprite->rxID, 10);
                bitWriter.write(pXSprite->command, 8);
                bitWriter.write(pXSprite->triggerOn, 1);
                bitWriter.write(pXSprite->triggerOff, 1);
                bitWriter.write(pXSprite->wave, 2);
                bitWriter.write(pXSprite->busyTime, 12);
                bitWriter.write(pXSprite->waitTime, 12);
                bitWriter.write(pXSprite->restState, 1);
                bitWriter.write(pXSprite->Interrutable, 1);
                bitWriter.write(pXSprite->unused1, 2);
                bitWriter.write(pXSprite->respawnPending, 2);
                bitWriter.write(pXSprite->unused2, 1);
                bitWriter.write(pXSprite->lT, 1);
                bitWriter.write(pXSprite->dropMsg, 8);
                bitWriter.write(pXSprite->Decoupled, 1);
                bitWriter.write(pXSprite->triggerOnce, 1);
                bitWriter.write(pXSprite->isTriggered, 1);
                bitWriter.write(pXSprite->key, 3);
                bitWriter.write(pXSprite->Push, 1);
                bitWriter.write(pXSprite->Vector, 1);
                bitWriter.write(pXSprite->Impact, 1);
                bitWriter.write(pXSprite->Pickup, 1);
                bitWriter.write(pXSprite->Touch, 1);
                bitWriter.write(pXSprite->Sight, 1);
                bitWriter.write(pXSprite->Proximity, 1);
                bitWriter.write(pXSprite->unused3, 2);
                bitWriter.write(pXSprite->lSkill, 5);
                bitWriter.write(pXSprite->lS, 1);
                bitWriter.write(pXSprite->lB, 1);
                bitWriter.write(pXSprite->lC, 1);
                bitWriter.write(pXSprite->DudeLockout, 1);
                bitWriter.write(pXSprite->data1, 16);
                bitWriter.write(pXSprite->data2, 16);
                bitWriter.write(pXSprite->data3, 16);
                bitWriter.write(pXSprite->goalAng, 11);
                bitWriter.write(pXSprite->dodgeDir, 2);
                bitWriter.write(pXSprite->locked, 1);
                bitWriter.write(pXSprite->medium, 2);
                bitWriter.write(pXSprite->respawn, 2);
                bitWriter.write(pXSprite->data4, 16);
                bitWriter.write(pXSprite->unused4, 6);
                bitWriter.write(pXSprite->lockMsg, 8);
                bitWriter.write(pXSprite->health, 12);
                bitWriter.write(pXSprite->dudeDeaf, 1);
                bitWriter.write(pXSprite->dudeAmbush, 1);
                bitWriter.write(pXSprite->dudeGuard, 1);
                bitWriter.write(pXSprite->dudeFlag4, 1);
                bitWriter.write(pXSprite->target, 16);
                bitWriter.write(pXSprite->targetX, 32);
                bitWriter.write(pXSprite->targetY, 32);
                bitWriter.write(pXSprite->targetZ, 32);
                bitWriter.write(pXSprite->burnTime, 16);
                bitWriter.write(pXSprite->burnSource, 16);
                bitWriter.write(pXSprite->height, 16);
                bitWriter.write(pXSprite->stateTimer, 16);
                IOBuffer1.Write(pBuffer, nXSpriteSize);
            }
        }
    }
    unsigned int nCRC = Bcrc32(pData, nSize-4, 0);
    IOBuffer1.Write(&nCRC, 4);
    int nHandle = Bopen(sMapExt, BO_BINARY|BO_TRUNC|BO_CREAT|BO_WRONLY, BS_IREAD|BS_IWRITE);
    if (nHandle == -1)
    {
        initprintf("Couldn't open \"%s\" for writing: %s\n", sMapExt, strerror(errno));
        Xfree(pData);
        return -1;
    }
    if (Bwrite(nHandle, pData, nSize) != nSize)
    {
        initprintf("Couldn't write to \"%s\": %s\n", sMapExt, strerror(errno));
        Bclose(nHandle);
        Xfree(pData);
        return -1;
    }
    Bclose(nHandle);
    Xfree(pData);
    return 0;
#if 0
    char *pExt = strchr(sMapExt, '.');
    if (pExt)
    {
        *pExt = 0;
    }
    gSysRes.AddExternalResource(sMapExt, "MAP", nSize);
    DICTNODE *hMap = gSysRes.Lookup(sMapExt, "MAP");
    dassert(hMap != NULL);
#endif
}

int32_t qloadboard(const char* filename, char flags, vec3_t* dapos, int16_t* daang, int16_t* dacursectnum)
{
    // NUKE-TODO: implement flags, see mapedit.cpp
    UNREFERENCED_PARAMETER(flags);
    return dbLoadMap(filename, &dapos->x, &dapos->y, &dapos->z, (short*)daang, (short*)dacursectnum, NULL);
}

int32_t qsaveboard(const char* filename, const vec3_t* dapos, int16_t daang, int16_t dacursectnum)
{
    // NUKE-TODO: see mapedit.cpp
    byte_1A76C6 = byte_1A76C8 = byte_1A76C7 = 1;
    return dbSaveMap(filename, dapos->x, dapos->y, dapos->z, daang, dacursectnum);
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#pragma once

#define kMaxXSprites 16384
#define kMaxXWalls 16384
#define kMaxXSectors 4096

#ifdef NOONE_EXTENSIONS
extern bool gModernMap;
#endif // NOONE_EXTENSIONS

// by NoOne: functions to quckly check range of specifical arrays
inline bool xspriRangeIsFine(int nXindex) {
    return (nXindex > 0 && nXindex < kMaxXSprites);
}

inline bool xsectRangeIsFine(int nXindex) {
    return (nXindex > 0 && nXindex < kMaxXSectors);
}

inline bool xwallRangeIsFine(int nXindex) {
    return (nXindex > 0 && nXindex < kMaxXWalls);
}
#pragma pack(push, 1)

struct AISTATE;

struct XSPRITE {
    unsigned int unused1 : 2;           // additional dude flags in modern maps
    unsigned int unused2 : 1;           // unused
    unsigned int unused3 : 2;           // unused
    unsigned int unused4 : 6;           // unused

    signed   int reference : 15;
    unsigned int state : 1;             // State 0
    unsigned int busy : 17;
    unsigned int txID : 10;             // TX ID
    unsigned int rxID : 10;             // RX ID
    unsigned int command : 8;           // Cmd
    unsigned int triggerOn : 1;         // going ON
    unsigned int triggerOff : 1;        // going OFF
    unsigned int busyTime : 12;         // busyTime
    unsigned int waitTime : 12;         // waitTime
    unsigned int restState : 1;         // restState
    unsigned int Interrutable : 1;      // Interruptable

    unsigned int respawnPending : 2;    // respawnPending

    unsigned int dropMsg : 8;           // Drop Item
    unsigned int Decoupled : 1;         // Decoupled
    unsigned int triggerOnce : 1;       // 1-shot
    unsigned int isTriggered : 1;       // works in case if triggerOnce selected

    unsigned int key : 3;               // Key
    unsigned int wave : 2;              // Wave
    unsigned int Push : 1;              // Push
    unsigned int Vector : 1;            // Vector
    unsigned int Impact : 1;            // Impact
    unsigned int Pickup : 1;            // Pickup
    unsigned int Touch : 1;             // Touch
    unsigned int Sight : 1;             // Sight
    unsigned int Proximity : 1;         // Proximity
    unsigned int lSkill : 5;            // Launch 12345
    unsigned int lS : 1;                // Single
    unsigned int lB : 1;                // Bloodbath
    unsigned int lT : 1;                // Launch Team
    unsigned int lC : 1;                // Coop
    unsigned int DudeLockout : 1;       // DudeLockout
    signed   int data1 : 16;            // Data 1
    signed   int data2 : 16;            // Data 2
    signed   int data3 : 16;            // Data 3
    unsigned int data4 : 16;            // Data 4
    unsigned int locked : 1;            // Locked
    unsigned int medium : 2;            // medium
    unsigned int respawn : 2;           // Respawn option
    unsigned int lockMsg : 8;           // Lock msg
    unsigned int health : 20;
    unsigned int dudeDeaf : 1;          // dudeDeaf
    unsigned int dudeAmbush : 1;        // dudeAmbush
    unsigned int dudeGuard : 1;         // dudeGuard
    unsigned int dudeFlag4 : 1;         // unused
    signed   int target : 16;           // target sprite
    signed   int targetX : 32;          // target x
    signed   int targetY : 32;          // target y
    signed   int targetZ : 32;          // target z
    unsigned int goalAng : 11;          // Dude goal ang
    signed   int dodgeDir : 2;          // Dude dodge direction
    unsigned int burnTime : 16;
    signed   int burnSource : 16;
    unsigned int height : 16;
    unsigned int stateTimer : 16;       // ai timer
    AISTATE* aiState;                   // ai
    #ifdef NOONE_EXTENSIONS
    signed int sysData1: 32;            // used to keep here various system data, so user can't change it in map editor
    signed int sysData2: 32;            //
    unsigned int physAttr : 32;         // currently used by additional physics sprites to keep it's attributes.
    #endif
    signed int scale;                   // used for scaling SEQ size on sprites

};

struct XSECTOR {
    signed int reference : 14;
    unsigned int state : 1;             // State
    unsigned int busy : 17;
    unsigned int data : 16;             // Data
    unsigned int txID : 10;             // TX ID
    unsigned int rxID : 10;             // RX ID
    unsigned int busyWaveA : 3;         // OFF->ON wave
    unsigned int busyWaveB : 3;         // ON->OFF wave

    unsigned int command : 8;           // Cmd
    unsigned int triggerOn : 1;         // Send at ON
    unsigned int triggerOff : 1;        // Send at OFF
    unsigned int busyTimeA : 12;        // OFF->ON busyTime
    unsigned int waitTimeA : 12;        // OFF->ON waitTime
    unsigned int restState : 1;
    unsigned int interruptable : 1;     // Interruptable

    unsigned int reTriggerA : 1;        // OFF->ON wait
    unsigned int reTriggerB : 1;        // ON->OFF wait
    signed int amplitude : 8;           // Lighting amplitude
    unsigned int freq : 8;              // Lighting freq
    unsigned int phase : 8;             // Lighting phase
    unsigned int wave : 4;              // Lighting wave
    unsigned int shadeAlways : 1;       // Lighting shadeAlways
    unsigned int shadeFloor : 1;        // Lighting floor
    unsigned int shadeCeiling : 1;      // Lighting ceiling
    unsigned int shadeWalls : 1;        // Lighting walls
    signed int shade : 8;               // Lighting value
    unsigned int panAlways : 1;         // Pan always
    unsigned int panFloor : 1;          // Pan floor
    unsigned int panCeiling : 1;        // Pan ceiling
    unsigned int Drag : 1;              // Pan drag
    unsigned int panVel : 8;            // Motion speed
    unsigned int panAngle : 11;         // Motion angle
    unsigned int Underwater : 1;        // Underwater
    unsigned int Depth : 3;             // Depth
    unsigned int unused1 : 1;
    unsigned int decoupled : 1;         // Decoupled
    unsigned int triggerOnce : 1;       // 1-shot
    unsigned int isTriggered : 1;
    unsigned int Key : 3;               // Key
    unsigned int Push : 1;              // Push
    unsigned int Vector : 1;            // Vector
    unsigned int Reserved : 1;          // Reserved
    unsigned int Enter : 1;             // Enter
    unsigned int Exit : 1;              // Exit
    unsigned int Wallpush : 1;          // WallPush
    unsigned int color : 1;             // Color Lights
    unsigned int unused2 : 1;
    unsigned int busyTimeB : 12;        // ON->OFF busyTime
    unsigned int waitTimeB : 12;        // ON->OFF waitTime
    unsigned int stopOn : 1;
    unsigned int stopOff : 1;
    unsigned int ceilpal : 4;           // Ceil pal2
    signed int offCeilZ : 32;
    signed int onCeilZ : 32;
    signed int offFloorZ : 32;
    signed int onFloorZ : 32;
    unsigned int marker0 : 16;
    unsigned int marker1 : 16;
    unsigned int Crush : 1;             // Crush
    unsigned int ceilXPanFrac : 8;      // Ceiling x panning frac
    unsigned int ceilYPanFrac : 8;      // Ceiling y panning frac
    unsigned int floorXPanFrac : 8;     // Floor x panning frac
    unsigned int damageType : 3;        // DamageType
    unsigned int floorpal : 4;          // Floor pal2
    unsigned int floorYPanFrac : 8;     // Floor y panning frac
    unsigned int locked : 1;            // Locked
    #ifdef NOONE_EXTENSIONS
    unsigned int windVel : 32;          // Wind vel (changed from 10 bit to use higher velocity values)
    #else
    unsigned int windVel : 10;
    #endif
    unsigned int windAng : 11;          // Wind ang
    unsigned int windAlways : 1;        // Wind always
    unsigned int dudeLockout : 1;
    unsigned int bobTheta : 11;         // Motion Theta
    unsigned int bobZRange : 5;         // Motion Z range
    signed int bobSpeed : 12;           // Motion speed
    unsigned int bobAlways : 1;         // Motion always
    unsigned int bobFloor : 1;          // Motion bob floor
    unsigned int bobCeiling : 1;        // Motion bob ceiling
    unsigned int bobRotate : 1;         // Motion rotate
};

struct XWALL {
    signed int reference : 15;
    unsigned int state : 1;             // State
    unsigned int busy : 17;
    signed int data : 16;               // Data
    unsigned int txID : 10;             // TX ID
    unsigned int unused1 : 6;           // unused
    unsigned int rxID : 10;             // RX ID
    unsigned int command : 8;           // Cmd
    unsigned int triggerOn : 1;         // going ON
    unsigned int triggerOff : 1;        // going OFF
    unsigned int busyTime : 12;         // busyTime
    unsigned int waitTime : 12;         // waitTime
    unsigned int restState : 1;         // restState
    unsigned int interruptable : 1;     // Interruptable
    unsigned int panAlways : 1;         // panAlways
    signed   int panXVel : 8;           // panX
    signed   int panYVel : 8;           // panY
    unsigned int decoupled : 1;         // Decoupled
    unsigned int triggerOnce : 1;       // 1-shot
    unsigned int isTriggered : 1;
    unsigned int key : 3;               // Key
    unsigned int triggerPush : 1;       // Push
    unsigned int triggerVector : 1;     // Vector
    unsigned int triggerTouch : 1;      // by NoOne: renamed from Reserved to Touch as it works with Touch now.
    unsigned int unused2 : 2;           // unused
    unsigned int xpanFrac : 8;          // x panning frac
    unsigned int ypanFrac : 8;          // y panning frac
    unsigned int locked : 1;            // Locked
    unsigned int dudeLockout : 1;       // DudeLockout
    unsigned int unused3 : 4;           // unused;
    unsigned int unused4 : 32;          // unused
};

struct MAPSIGNATURE {
    char signature[4];
    short version;
};

struct MAPHEADER  {
    int at0; // x
    int at4; // y
    int at8; // z
    short atc; // ang
    short ate; // sect
    short at10; // pskybits
    int at12; // visibility
    int at16; // song id, Matt
    char at1a; // parallaxtype
    int at1b; // map revision
    short at1f; // numsectors
    short at21; // numwalls
    short at23; // numsprites
};

struct MAPHEADER2 {
    char at0[64];
    int at40; // xsprite size
    int at44; // xwall size
    int at48; // xsector size
    char pad[52];
};

struct SPRITEHIT {
    int hit, ceilhit, florhit;
};

#pragma pack(pop)

extern unsigned short gStatCount[kMaxStatus + 1];

extern bool byte_1A76C6, byte_1A76C7, byte_1A76C8;
extern MAPHEADER2 byte_19AE44;

extern XSPRITE xsprite[kMaxXSprites];
extern XSECTOR xsector[kMaxXSectors];
extern XWALL xwall[kMaxXWalls];

extern SPRITEHIT gSpriteHit[kMaxXSprites];

extern char qsprite_filler[kMaxSprites], qsector_filler[kMaxSectors];

extern int xvel[kMaxSprites], yvel[kMaxSprites], zvel[kMaxSprites];

extern int gVisibility;
extern int gMapRev, gSongId, gSkyCount;
extern const char *gItemText[];
extern const char *gAmmoText[];
extern const char *gWeaponText[];

extern unsigned short nextXSprite[kMaxXSprites];
extern unsigned short nextXWall[kMaxXWalls];
extern unsigned short nextXSector[kMaxXSectors];

#ifdef YAX_ENABLE
static inline bool yax_hasnextwall(int nWall)
{
    return yax_getnextwall(nWall, YAX_CEILING) >= 0 || yax_getnextwall(nWall, YAX_FLOOR) >= 0;
}
#endif

static inline int GetWallType(int nWall)
{
#ifdef YAX_ENABLE
    if (yax_hasnextwall(nWall))
        return 0;
#endif
    return wall[nWall].type;
}

template<typename T> void GetSpriteExtents(T const * const pSprite, int *top, int *bottom)
{
    *top = *bottom = pSprite->z;
    if ((pSprite->cstat & 0x30) != 0x20)
    {
        int height = tilesiz[pSprite->picnum].y;
        int center = height / 2 + picanm[pSprite->picnum].yofs;
        *top -= (pSprite->yrepeat << 2)*center;
        *bottom += (pSprite->yrepeat << 2)*(height - center);
    }
}

#ifdef POLYMER
#pragma pack(push, 1)
struct PolymerLight_t {
    int16_t lightId, lightmaxrange;
    _prlight* lightptr;
    uint8_t lightcount;
};
#pragma pack(pop)

extern PolymerLight_t gPolymerLight[kMaxSprites];

void DeleteLight(int32_t s);

#endif

void InsertSpriteSect(int nSprite, int nSector);
void RemoveSpriteSect(int nSprite);
void InsertSpriteStat(int nSprite, int nStat);
void RemoveSpriteStat(int nSprite);
void qinitspritelists(void);
int InsertSprite(int nSector, int nStat);
int qinsertsprite(short nSector, short nStat);
int DeleteSprite(int nSprite);
int qdeletesprite(short nSprite);
int ChangeSpriteSect(int nSprite, int nSector);
int qchangespritesect(short nSprite, short nSector);
int ChangeSpriteStat(int nSprite, int nStatus);
int qchangespritestat(short nSprite, short nStatus);
void InitFreeList(unsigned short *pList, int nCount);
void InsertFree(unsigned short *pList, int nIndex);
unsigned short dbInsertXSprite(int nSprite);
void dbDeleteXSprite(int nXSprite);
unsigned short dbInsertXWall(int nWall);
void dbDeleteXWall(int nXWall);
unsigned short dbInsertXSector(int nSector);
void dbDeleteXSector(int nXSector);
void dbXSpriteClean(void);
void dbXWallClean(void);
void dbXSectorClean(void);
void dbInit(void);
void PropagateMarkerReferences(void);
unsigned int dbReadMapCRC(const char *pPath);
int dbLoadMap(const char *pPath, int *pX, int *pY, int *pZ, short *pAngle, short *pSector, unsigned int *pCRC);

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include "common.h"
#include "common_game.h"
#include "keyboard.h"
#include "control.h"
#include "osd.h"
#include "mmulti.h"

#include "blood.h"
#include "controls.h"
#include "demo.h"
#include "fire.h"
#include "gamemenu.h"
#include "globals.h"
#include "levels.h"
#include "menu.h"
#include "messages.h"
#include "misc.h"
#include "music.h"
#include "network.h"
#include "player.h"
#include "screen.h"
#include "view.h"

int nBuild = 0;

void ReadGameOptionsLegacy(GAMEOPTIONS &gameOptions, GAMEOPTIONSLEGACY &gameOptionsLegacy)
{
    gameOptions.nGameType = gameOptionsLegacy.nGameType;
    gameOptions.nDifficulty = gameOptionsLegacy.nDifficulty;
    gameOptions.nEpisode = gameOptionsLegacy.nEpisode;
    gameOptions.nLevel = gameOptionsLegacy.nLevel;
    strcpy(gameOptions.zLevelName, gameOptionsLegacy.zLevelName);
    strcpy(gameOptions.zLevelSong, gameOptionsLegacy.zLevelSong);
    gameOptions.nTrackNumber = gameOptionsLegacy.nTrackNumber;
    strcpy(gameOptions.szSaveGameName, gameOptionsLegacy.szSaveGameName);
    strcpy(gameOptions.szUserGameName, gameOptionsLegacy.szUserGameName);
    gameOptions.nSaveGameSlot = gameOptionsLegacy.nSaveGameSlot;
    gameOptions.picEntry = gameOptionsLegacy.picEntry;
    gameOptions.uMapCRC = gameOptionsLegacy.uMapCRC;
    gameOptions.nMonsterSettings = gameOptionsLegacy.nMonsterSettings;
    gameOptions.uGameFlags = gameOptionsLegacy.uGameFlags;
    gameOptions.uNetGameFlags = gameOptionsLegacy.uNetGameFlags;
    gameOptions.nWeaponSettings = gameOptionsLegacy.nWeaponSettings;
    gameOptions.nItemSettings = gameOptionsLegacy.nItemSettings;
    gameOptions.nRespawnSettings = gameOptionsLegacy.nRespawnSettings;
    gameOptions.nTeamSettings = gameOptionsLegacy.nTeamSettings;
    gameOptions.nMonsterRespawnTime = gameOptionsLegacy.nMonsterRespawnTime;
    gameOptions.nWeaponRespawnTime = gameOptionsLegacy.nWeaponRespawnTime;
    gameOptions.nItemRespawnTime = gameOptionsLegacy.nItemRespawnTime;
    gameOptions.nSpecialRespawnTime = gameOptionsLegacy.nSpecialRespawnTime;
}

CDemo gDemo;

CDemo::CDemo()
{
    nBuild = 4;
    at0 = 0;
    at1 = 0;
    at3 = 0;
    hPFile = -1;
    hRFile = NULL;
    atb = 0;
    pFirstDemo = NULL;
    pCurrentDemo = NULL;
    nDemosFound = 0;
    at2 = 0;
    memset(&atf, 0, sizeof(atf));
    m_bLegacy = false;
}

CDemo::~CDemo()
{
    at0 = 0;
    at1 = 0;
    at3 = 0;
    atb = 0;
    memset(&atf, 0, sizeof(atf));
    if (hPFile >= 0)
    {
        kclose(hPFile);
        hPFile = -1;
    }
    if (hRFile != NULL)
    {
        fclose(hRFile);
        hRFile = NULL;
    }
    auto pNextDemo = pFirstDemo;
    for (auto pDemo = pFirstDemo; pDemo != NULL; pDemo = pNextDemo)
    {
        pNextDemo = pDemo->pNext;
        delete pDemo;
    }
    pFirstDemo = NULL;
    pCurrentDemo = NULL;
    nDemosFound = 0;
    m_bLegacy = false;
}

bool CDemo::Create(const char *pzFile)
{
    char buffer[BMAX_PATH];
    char vc = 0;
    if (at0 || at1)
        ThrowError("CDemo::Create called during demo record/playback process.");
    if (!pzFile)
    {
        for (int i = 0; i < 8 && !vc; i++)
        {
            G_ModDirSnprintf(buffer, BMAX_PATH, "%s0%02d.dem", BloodIniPre, i);
            if (access(buffer, F_OK) != -1)
                vc = 1;
        }
        if (vc == 1)
        {
            hRFile = fopen(buffer, "wb");
            if (hRFile == NULL)
                return false;
        }
    }
    else
    {
        G_ModDirSnprintfLite(buffer, BMAX_PATH, pzFile);
        hRFile = fopen(buffer, "wb");
        if (hRFile == NULL)
            return false;
    }
    at0 = 1;
    atb = 0;
    return true;
}

void CDemo::Write(GINPUT *pPlayerInputs)
{
    dassert(pPlayerInputs != NULL);
    if (!at0)
        return;
    if (atb == 0)
    {
        atf.signature = 0x1a4d4445; // '\x1aMDE';
        atf.nVersion = BYTEVERSION;
        atf.nBuild = nBuild;
        atf.nInputCount = 0;
        atf.nNetPlayers = gNetPlayers;
        atf.nMyConnectIndex = myconnectindex;
        atf.nConnectHead = connecthead;
        memcpy(atf.connectPoints, connectpoint2, sizeof(atf.connectPoints));
        memcpy(&m_gameOptions, &gGameOptions, sizeof(gGameOptions));
#if B_BIG_ENDIAN == 1
        atf.signature = B_LITTLE32(atf.signature);
        atf.nVersion = B_LITTLE16(atf.nVersion);
        atf.nBuild = B_LITTLE32(atf.nBuild);
        atf.nInputCount = B_LITTLE32(atf.nInputCount);
        atf.nNetPlayers = B_LITTLE32(atf.nNetPlayers);
        atf.nMyConnectIndex = B_LITTLE16(atf.nMyConnectIndex);
        atf.nConnectHead = B_LITTLE16(atf.nConnectHead);
        for (int i = 0; i < 8; i++)
            atf.connectPoints[i] = B_LITTLE16(atf.connectPoints[i]);
#endif
        fwrite(&atf, sizeof(DEMOHEADER), 1, hRFile);
#if B_BIG_ENDIAN == 1
        m_gameOptions.nEpisode = B_LITTLE32(m_gameOptions.nEpisode);
        m_gameOptions.nLevel = B_LITTLE32(m_gameOptions.nLevel);
        m_gameOptions.nTrackNumber = B_LITTLE32(m_gameOptions.nTrackNumber);
        m_gameOptions.nSaveGameSlot = B_LITTLE16(m_gameOptions.nSaveGameSlot);
        m_gameOptions.picEntry = B_LITTLE32(m_gameOptions.picEntry);
        m_gameOptions.uMapCRC = B_LITTLE32(m_gameOptions.uMapCRC);
        m_gameOptions.uGameFlags = B_LITTLE32(m_gameOptions.uGameFlags);
        m_gameOptions.uNetGameFlags = B_LITTLE32(m_gameOptions.uNetGameFlags);
        m_gameOptions.nMonsterRespawnTime = B_LITTLE32(m_gameOptions.nMonsterRespawnTime);
        m_gameOptions.nWeaponRespawnTime = B_LITTLE32(m_gameOptions.nWeaponRespawnTime);
        m_gameOptions.nItemRespawnTime = B_LITTLE32(m_gameOptions.nItemRespawnTime);
        m_gameOptions.nSpecialRespawnTime = B_LITTLE32(m_gameOptions.nSpecialRespawnTime);
#endif
        fwrite(&m_gameOptions, sizeof(GAMEOPTIONS), 1, hRFile);
    }
    for (int p = connecthead; p >= 0; p = connectpoint2[p])
    {
        memcpy(&at1aa[atb&1023], &pPlayerInputs[p], sizeof(GINPUT));
        atb++;
        if((atb&(kInputBufferSize-1))==0)
            FlushInput(kInputBufferSize);
    }
}

void CDemo::Close(void)
{
    if (at0)
    {
        if (atb&(kInputBufferSize-1))
            FlushInput(atb&(kInputBufferSize-1));
        atf.nInputCount = atb;
#if B_BIG_ENDIAN == 1
        atf.nInputCount = B_LITTLE32(atf.nInputCount);
#endif
        fseek(hRFile, 0, SEEK_SET);
        fwrite(&atf, sizeof(DEMOHEADER), 1, hRFile);
        fwrite(&m_gameOptions, sizeof(GAMEOPTIONS), 1, hRFile);
    }
    if (hPFile >= 0)
    {
        kclose(hPFile);
        hPFile = -1;
    }
    if (hRFile != NULL)
    {
        fclose(hRFile);
        hRFile = NULL;
    }
    at0 = 0;
    at1 = 0;
}

bool CDemo::SetupPlayback(const char *pzFile)
{
    at0 = 0;
    at1 = 0;
    if (pzFile)
    {
        hPFile = kopen4loadfrommod(pzFile, 0);
        if (hPFile == -1)
            return false;
    }
    else
    {
        if (!pCurrentDemo)
            return false;
        hPFile = kopen4loadfrommod(pCurrentDemo->zName, 0);
        if (hPFile == -1)
            return false;
    }
    kread(hPFile, &atf, sizeof(DEMOHEADER));
#if B_BIG_ENDIAN == 1
    atf.signature = B_LITTLE32(atf.signature);
    atf.nVersion = B_LITTLE16(atf.nVersion);
    atf.nBuild = B_LITTLE32(atf.nBuild);
    atf.nInputCount = B_LITTLE32(atf.nInputCount);
    atf.nNetPlayers = B_LITTLE32(atf.nNetPlayers);
    atf.nMyConnectIndex = B_LITTLE16(atf.nMyConnectIndex);
    atf.nConnectHead = B_LITTLE16(atf.nConnectHead);
    for (int i = 0; i < 8; i++)
        atf.connectPoints[i] = B_LITTLE16(atf.connectPoints[i]);
#endif
    // if (aimHeight.signature != '\x1aMED' && aimHeight.signature != '\x1aMDE')
    if (atf.signature != 0x1a4d4544 && atf.signature != 0x1a4d4445)
        return 0;
    m_bLegacy = atf.signature == 0x1a4d4544;
    if (m_bLegacy)
    {
        GAMEOPTIONSLEGACY gameOptions;
        if (BloodVersion != atf.nVersion)
            return 0;
        kread(hPFile, &gameOptions, sizeof(GAMEOPTIONSLEGACY));
        ReadGameOptionsLegacy(m_gameOptions, gameOptions);
    }
    else
    {
        if (BYTEVERSION != atf.nVersion)
            return 0;
        kread(hPFile, &m_gameOptions, sizeof(GAMEOPTIONS));
    }
#if B_BIG_ENDIAN == 1
    m_gameOptions.nEpisode = B_LITTLE32(m_gameOptions.nEpisode);
    m_gameOptions.nLevel = B_LITTLE32(m_gameOptions.nLevel);
    m_gameOptions.nTrackNumber = B_LITTLE32(m_gameOptions.nTrackNumber);
    m_gameOptions.nSaveGameSlot = B_LITTLE16(m_gameOptions.nSaveGameSlot);
    m_gameOptions.picEntry = B_LITTLE32(m_gameOptions.picEntry);
    m_gameOptions.uMapCRC = B_LITTLE32(m_gameOptions.uMapCRC);
    m_gameOptions.uGameFlags = B_LITTLE32(m_gameOptions.uGameFlags);
    m_gameOptions.uNetGameFlags = B_LITTLE32(m_gameOptions.uNetGameFlags);
    m_gameOptions.nMonsterRespawnTime = B_LITTLE32(m_gameOptions.nMonsterRespawnTime);
    m_gameOptions.nWeaponRespawnTime = B_LITTLE32(m_gameOptions.nWeaponRespawnTime);
    m_gameOptions.nItemRespawnTime = B_LITTLE32(m_gameOptions.nItemRespawnTime);
    m_gameOptions.nSpecialRespawnTime = B_LITTLE32(m_gameOptions.nSpecialRespawnTime);
#endif
    at0 = 0;
    at1 = 1;
    return 1;
}

void CDemo::ProcessKeys(void)
{
    switch (gInputMode)
    {
    case INPUT_MODE_1:
        gGameMenuMgr.Process();
        break;
    case INPUT_MODE_2:
        gPlayerMsg.ProcessKeys();
        break;
    case INPUT_MODE_0:
    {
        char nKey;
        while ((nKey = keyGetScan()) != 0)
        {
	        char UNUSED(alt) = keystatus[sc_LeftAlt] | keystatus[sc_RightAlt];
	        char UNUSED(ctrl) = keystatus[sc_LeftControl] | keystatus[sc_RightControl];
            switch (nKey)
            {
            case sc_Escape:
                if (!CGameMenuMgr::m_bActive)
                {
                    gGameMenuMgr.Push(&menuMain, -1);
                    at2 = 1;
                }
                break;
            case sc_F12:
                gViewIndex = connectpoint2[gViewIndex];
                if (gViewIndex == -1)
                    gViewIndex = connecthead;
                gView = &gPlayer[gViewIndex];
                break;
            }
        }
        if (!nKey && CONTROL_JoystickEnabled)
        {
            static int32_t joyold = 0;
            int32_t joy = JOYSTICK_GetControllerButtons() == (1 << CONTROLLER_BUTTON_START);
            if (joy && !joyold)
            {
                JOYSTICK_ClearAllButtons();
                if (!CGameMenuMgr::m_bActive)
                {
                    gGameMenuMgr.Push(&menuMain, -1);
                    at2 = 1;
                }
            }
            joyold = joy;
        }
        break;
    default:
        gInputMode = INPUT_MODE_0;
        break;
    }
    }
}

void CDemo::Playback(void)
{
    CONTROL_BindsEnabled = false;
    ready2send = 0;
    int v4 = 0;
    if (!CGameMenuMgr::m_bActive)
    {
        gGameMenuMgr.Push(&menuMain, -1);
        at2 = 1;
    }
    gNetFifoClock = totalclock;
    gViewMode = 3;
_DEMOPLAYBACK:
    while (at1 && !gQuitGame)
    {
        while (totalclock >= gNetFifoClock && !gQuitGame)
        {
            if (!v4)
            {
                viewResizeView(gViewSize);
                viewSetMessage("");
                gNetPlayers = atf.nNetPlayers;
                atb = atf.nInputCount;
                myconnectindex = atf.nMyConnectIndex;
                connecthead = atf.nConnectHead;
                for (int i = 0; i < 8; i++)
                    connectpoint2[i] = atf.connectPoints[i];
                memset(gNetFifoHead, 0, sizeof(gNetFifoHead));
                gNetFifoTail = 0;
                //memcpy(connectpoint2, aimHeight.connectPoints, sizeof(aimHeight.connectPoints));
                memcpy(&gGameOptions, &m_gameOptions, sizeof(GAMEOPTIONS));
                gSkill = gGameOptions.nDifficulty;
                for (int i = 0; i < 8; i++)
                    playerInit(i, 0);
                StartLevel(&gGameOptions);
                for (int i = 0; i < 8; i++)
                {
                    gProfile[i].nAutoAim = 1;
                    gProfile[i].nWeaponSwitch = 1;
                }
            }
            ready2send = 0;
            OSD_DispatchQueued();
            if (!gDemo.at1)
                break;
            ProcessKeys();
            for (int p = connecthead; p >= 0; p = connectpoint2[p])
            {
                if ((v4&1023) == 0)
                {
                    unsigned int nSize = atb-v4;
                    if (nSize > kInputBufferSize)
                        nSize = kInputBufferSize;
                    ReadInput(nSize);
                }
                memcpy(&gFifoInput[gNetFifoHead[p]&255], &at1aa[v4&1023], sizeof(GINPUT));
                gNetFifoHead[p]++;
                v4++;
                if (v4 >= atf.nInputCount)
                {
                    ready2send = 0;
                    if (nDemosFound > 1)
                    {
                        v4 = 0;
                        Close();
                        NextDemo();
                        gNetFifoClock = totalclock;
                        goto _DEMOPLAYBACK;
                    }
                    else
                    {
                        int const nOffset = sizeof(DEMOHEADER)+(m_bLegacy ? sizeof(GAMEOPTIONSLEGACY) : sizeof(GAMEOPTIONS));
                        klseek(hPFile, nOffset, SEEK_SET);
                        v4 = 0;
                    }
                }
            }
            gNetFifoClock += 4;
            if (!gQuitGame)
                ProcessFrame();
            ready2send = 0;
        }
        if (engineFPSLimit())
        {
            if (handleevents() && quitevent)
            {
                KB_KeyDown[sc_Escape] = 1;
                quitevent = 0;
            }
            MUSIC_Update();
            viewDrawScreen();
            if (gInputMode == INPUT_MODE_1 && CGameMenuMgr::m_bActive)
                gGameMenuMgr.Draw();
            videoNextPage();
        }
        if (TestBitString(gotpic, 2342))
        {
            FireProcess();
            ClearBitString(gotpic, 2342);
        }
    }
    Close();
}

void CDemo::StopPlayback(void)
{
    at1 = 0;
}

void CDemo::LoadDemoInfo(void)
{
    auto pDemo = &pFirstDemo;
    const int opsm = pathsearchmode;
    nDemosFound = 0;
    pathsearchmode = 0;
    char zFN[BMAX_PATH];
    Bsnprintf(zFN, BMAX_PATH, "%s*.dem", BloodIniPre);
    auto pList = klistpath("/", zFN, BUILDVFS_FIND_FILE);
    auto pIterator = pList;
    while (pIterator != NULL)
    {
        int hFile = kopen4loadfrommod(pIterator->name, 0);
        if (hFile == -1)
            ThrowError("Error loading demo file header.");
        kread(hFile, &atf, sizeof(atf));
        kclose(hFile);
#if B_BIG_ENDIAN == 1
        atf.signature = B_LITTLE32(atf.signature);
        atf.nVersion = B_LITTLE16(atf.nVersion);
#endif
        if ((atf.signature == 0x1a4d4544 /* '\x1aMED' */&& atf.nVersion == BloodVersion)
            || (atf.signature == 0x1a4d4445 /* '\x1aMDE' */ && atf.nVersion == BYTEVERSION))
        {
            *pDemo = new DEMOCHAIN;
            (*pDemo)->pNext = NULL;
            Bstrncpy((*pDemo)->zName, pIterator->name, BMAX_PATH);
            nDemosFound++;
            pDemo = &(*pDemo)->pNext;
        }
        pIterator = pIterator->next;
    }
    klistfree(pList);
    pathsearchmode = opsm;
    pCurrentDemo = pFirstDemo;
}

void CDemo::NextDemo(void)
{
    pCurrentDemo = pCurrentDemo->pNext ? pCurrentDemo->pNext : pFirstDemo;
    SetupPlayback(NULL);
}

const int nInputSize = 17;
const int nInputSizeLegacy = 22;

void CDemo::FlushInput(int nCount)
{
    char pBuffer[nInputSize*kInputBufferSize];
    BitWriter bitWriter(pBuffer, sizeof(pBuffer));
    for (int i = 0; i < nCount; i++)
    {
        GINPUT *pInput = &at1aa[i];
        bitWriter.writeBit(pInput->syncFlags.buttonChange);
        bitWriter.writeBit(pInput->syncFlags.keyChange);
        bitWriter.writeBit(pInput->syncFlags.useChange);
        bitWriter.writeBit(pInput->syncFlags.weaponChange);
        bitWriter.writeBit(pInput->syncFlags.mlookChange);
        bitWriter.writeBit(pInput->syncFlags.run);
        bitWriter.write(pInput->forward, 16);
        bitWriter.write(pInput->q16turn, 32);
        bitWriter.write(pInput->strafe, 16);
        bitWriter.writeBit(pInput->buttonFlags.jump);
        bitWriter.writeBit(pInput->buttonFlags.crouch);
        bitWriter.writeBit(pInput->buttonFlags.shoot);
        bitWriter.writeBit(pInput->buttonFlags.shoot2);
        bitWriter.writeBit(pInput->buttonFlags.lookUp);
        bitWriter.writeBit(pInput->buttonFlags.lookDown);
        bitWriter.writeBit(pInput->keyFlags.action);
        bitWriter.writeBit(pInput->keyFlags.jab);
        bitWriter.writeBit(pInput->keyFlags.prevItem);
        bitWriter.writeBit(pInput->keyFlags.nextItem);
        bitWriter.writeBit(pInput->keyFlags.useItem);
        bitWriter.writeBit(pInput->keyFlags.prevWeapon);
        bitWriter.writeBit(pInput->keyFlags.nextWeapon);
        bitWriter.writeBit(pInput->keyFlags.holsterWeapon);
        bitWriter.writeBit(pInput->keyFlags.lookCenter);
        bitWriter.writeBit(pInput->keyFlags.lookLeft);
        bitWriter.writeBit(pInput->keyFlags.lookRight);
        bitWriter.writeBit(pInput->keyFlags.spin180);
        bitWriter.writeBit(pInput->keyFlags.pause);
        bitWriter.writeBit(pInput->keyFlags.quit);
        bitWriter.writeBit(pInput->keyFlags.restart);
        bitWriter.writeBit(pInput->useFlags.useBeastVision);
        bitWriter.writeBit(pInput->useFlags.useCrystalBall);
        bitWriter.writeBit(pInput->useFlags.useJumpBoots);
        bitWriter.writeBit(pInput->useFlags.useMedKit);
        bitWriter.write(pInput->newWeapon, 8);
        bitWriter.write(pInput->q16mlook, 32);
        bitWriter.skipBits(1);
    }
    fwrite(pBuffer, 1, nInputSize*nCount, hRFile);
}

void CDemo::ReadInput(int nCount)
{
    if (m_bLegacy)
    {
        char pBuffer[nInputSizeLegacy*kInputBufferSize];
        kread(hPFile, pBuffer, nInputSizeLegacy*nCount);
        BitReader bitReader(pBuffer, sizeof(pBuffer));
        memset(at1aa, 0, nCount * sizeof(GINPUT));
        for (int i = 0; i < nCount; i++)
        {
            GINPUT *pInput = &at1aa[i];
            pInput->syncFlags.buttonChange = bitReader.readBit();
            pInput->syncFlags.keyChange = bitReader.readBit();
            pInput->syncFlags.useChange = bitReader.readBit();
            pInput->syncFlags.weaponChange = bitReader.readBit();
            pInput->syncFlags.mlookChange = bitReader.readBit();
            pInput->syncFlags.run = bitReader.readBit();
            bitReader.skipBits(26);
            pInput->forward = bitReader.readSigned(8) << 8;
            pInput->q16turn = fix16_from_int(bitReader.readSigned(16) >> 2);
            pInput->strafe = bitReader.readSigned(8) << 8;
            pInput->buttonFlags.jump = bitReader.readBit();
            pInput->buttonFlags.crouch = bitReader.readBit();
            pInput->buttonFlags.shoot = bitReader.readBit();
            pInput->buttonFlags.shoot2 = bitReader.readBit();
            pInput->buttonFlags.lookUp = bitReader.readBit();
            pInput->buttonFlags.lookDown = bitReader.readBit();
            bitReader.skipBits(26);
            pInput->keyFlags.action = bitReader.readBit();
            pInput->keyFlags.jab = bitReader.readBit();
            pInput->keyFlags.prevItem = bitReader.readBit();
            pInput->keyFlags.nextItem = bitReader.readBit();
            pInput->keyFlags.useItem = bitReader.readBit();
            pInput->keyFlags.prevWeapon = bitReader.readBit();
            pInput->keyFlags.nextWeapon = bitReader.readBit();
            pInput->keyFlags.holsterWeapon = bitReader.readBit();
            pInput->keyFlags.lookCenter = bitReader.readBit();
            pInput->keyFlags.lookLeft = bitReader.readBit();
            pInput->keyFlags.lookRight = bitReader.readBit();
            pInput->keyFlags.spin180 = bitReader.readBit();
            pInput->keyFlags.pause = bitReader.readBit();
            pInput->keyFlags.quit = bitReader.readBit();
            pInput->keyFlags.restart = bitReader.readBit();
            bitReader.skipBits(17);
            pInput->useFlags.useBeastVision = bitReader.readBit();
            pInput->useFlags.useCrystalBall = bitReader.readBit();
            pInput->useFlags.useJumpBoots = bitReader.readBit();
            pInput->useFlags.useMedKit = bitReader.readBit();
            bitReader.skipBits(28);
            pInput->newWeapon = bitReader.readUnsigned(8);
            int mlook = bitReader.readSigned(8);
            pInput->q16mlook = fix16_from_int(mlook / 4);
        }
    }
    else
    {
        char pBuffer[nInputSize*kInputBufferSize];
        kread(hPFile, pBuffer, nInputSize*nCount);
        BitReader bitReader(pBuffer, sizeof(pBuffer));
        memset(at1aa, 0, nCount * sizeof(GINPUT));
        for (int i = 0; i < nCount; i++)
        {
            GINPUT *pInput = &at1aa[i];
            pInput->syncFlags.buttonChange = bitReader.readBit();
            pInput->syncFlags.keyChange = bitReader.readBit();
            pInput->syncFlags.useChange = bitReader.readBit();
            pInput->syncFlags.weaponChange = bitReader.readBit();
            pInput->syncFlags.mlookChange = bitReader.readBit();
            pInput->syncFlags.run = bitReader.readBit();
            pInput->forward = bitReader.readSigned(16);
            pInput->q16turn = bitReader.readSigned(32);
            pInput->strafe = bitReader.readSigned(16);
            pInput->buttonFlags.jump = bitReader.readBit();
            pInput->buttonFlags.crouch = bitReader.readBit();
            pInput->buttonFlags.shoot = bitReader.readBit();
            pInput->buttonFlags.shoot2 = bitReader.readBit();
            pInput->buttonFlags.lookUp = bitReader.readBit();
            pInput->buttonFlags.lookDown = bitReader.readBit();
            pInput->keyFlags.action = bitReader.readBit();
            pInput->keyFlags.jab = bitReader.readBit();
            pInput->keyFlags.prevItem = bitReader.readBit();
            pInput->keyFlags.nextItem = bitReader.readBit();
            pInput->keyFlags.useItem = bitReader.readBit();
            pInput->keyFlags.prevWeapon = bitReader.readBit();
            pInput->keyFlags.nextWeapon = bitReader.readBit();
            pInput->keyFlags.holsterWeapon = bitReader.readBit();
            pInput->keyFlags.lookCenter = bitReader.readBit();
            pInput->keyFlags.lookLeft = bitReader.readBit();
            pInput->keyFlags.lookRight = bitReader.readBit();
            pInput->keyFlags.spin180 = bitReader.readBit();
            pInput->keyFlags.pause = bitReader.readBit();
            pInput->keyFlags.quit = bitReader.readBit();
            pInput->keyFlags.restart = bitReader.readBit();
            pInput->useFlags.useBeastVision = bitReader.readBit();
            pInput->useFlags.useCrystalBall = bitReader.readBit();
            pInput->useFlags.useJumpBoots = bitReader.readBit();
            pInput->useFlags.useMedKit = bitReader.readBit();
            pInput->newWeapon = bitReader.readUnsigned(8);
            pInput->q16mlook = bitReader.readSigned(32);
            bitReader.skipBits(1);
        }
    }
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#pragma once

#include "controls.h"
#include "levels.h"

#define kInputBufferSize 1024

#pragma pack(push, 1)

struct GAMEOPTIONSLEGACY {
    char nGameType;
    char nDifficulty;
    int nEpisode;
    int nLevel;
    char zLevelName[144];
    char zLevelSong[144];
    int nTrackNumber; //at12a;
    char szSaveGameName[16];
    char szUserGameName[16];
    short nSaveGameSlot;
    int picEntry;
    unsigned int uMapCRC;
    char nMonsterSettings;
    int uGameFlags;
    int uNetGameFlags;
    char nWeaponSettings;
    char nItemSettings;
    char nRespawnSettings;
    char nTeamSettings;
    int nMonsterRespawnTime;
    int nWeaponRespawnTime;
    int nItemRespawnTime;
    int nSpecialRespawnTime;
};

struct DEMOHEADER
{
    int signature;
    short nVersion;
    int nBuild;
    int nInputCount;
    int nNetPlayers;
    short nMyConnectIndex;
    short nConnectHead;
    short connectPoints[8];
};

#pragma pack(pop)

struct DEMOCHAIN
{
    DEMOCHAIN *pNext;
    char zName[BMAX_PATH];
};

class CDemo {
public:
    CDemo();
    ~CDemo();
    bool Create(const char *);
    void Write(GINPUT *);
    void Close(void);
    bool SetupPlayback(const char *);
    void ProcessKeys(void);
    void Playback(void);
    void StopPlayback(void);
    void LoadDemoInfo(void);
    void NextDemo(void);
    void FlushInput(int nCount);
    void ReadInput(int nCount);
    bool at0; // record
    bool at1; // playback
    bool m_bLegacy;
    char at2;
    int at3;
    int hPFile;
    FILE *hRFile;
    int atb;
    DEMOHEADER atf;
    GAMEOPTIONS m_gameOptions;
    GINPUT at1aa[kInputBufferSize];
    const char **pzDemoFile;
    DEMOCHAIN *pFirstDemo;
    DEMOCHAIN *pCurrentDemo;
    int nDemosFound;
};

extern CDemo gDemo;