code:
// game.h

void    operatesector(short dasector);
void    operatesprite(short dasprite);
int changehealth(short snum, short deltahealth);
void    changenumbombs(short snum, short deltanumbombs);
void    changenummissiles(short snum, short deltanummissiles);
void    changenumgrabbers(short snum, short deltanumgrabbers);
void    drawstatusflytime(short snum);
void    drawstatusbar(short snum);
void    prepareboard(char *daboardfilename);
void    checktouchsprite(short snum, short sectnum);
void    checkgrabbertouchsprite(short snum, short sectnum);
void    shootgun(short snum, const vec3_t *vector, short daang, int dahoriz, short dasectnum, char guntype);
void    analyzesprites(int dax, int day);
void    tagcode(void);
void    statuslistcode(void);
void    activatehitag(short dahitag);
void    bombexplode(int i);
void    processinput(short snum);
void    view(short snum, vec3_t *v, short *vsectnum, short ang, int horiz);
void    drawscreen(short snum, int dasmoothratio);
void    movethings(void);
void    fakedomovethings(void);
void    fakedomovethingscorrect(void);
void    domovethings(void);
void    getinput(void);
void    initplayersprite(short snum);
void    playback(void);
void    setup3dscreen(void);
void    findrandomspot(int *x, int *y, short *sectnum);
void    warp(int *x, int *y, int *z, short *daang, short *dasector);
void    warpsprite(short spritenum);
void    initlava(void);
void    movelava(char *dapic);
void    doanimations(void);
int getanimationgoal(int *animptr);
int setanimation(int *animptr, int thegoal, int thevel, int theacc);
void    checkmasterslaveswitch(void);
int testneighborsectors(short sect1, short sect2);
int loadgame(void);
int savegame(void);
void    faketimerhandler(void);
void    getpackets(void);
void    drawoverheadmap(int cposx, int cposy, int czoom, short cang);
int movesprite(short spritenum, int dx, int dy, int dz, int ceildist, int flordist, int clipmask);
void    waitforeverybody(void);
void    searchmap(short startsector);
void    setinterpolation(int *posptr);
void    stopinterpolation(int *posptr);
void    updateinterpolations(void);
void    dointerpolations(void);
void    restoreinterpolations(void);
void    printext(int x, int y, char *buffer, short tilenum /*, char invisiblecol*/);
void    drawtilebackground(/*int thex, int they,*/ short tilenum, signed char shade, int cx1, int cy1, int cx2, int cy2, char dapalnum);

extern short brightness;
#define NUMOPTIONS 8
#define NUMGAMEKEYS 20
extern unsigned char option[NUMOPTIONS];
extern unsigned char keys[NUMGAMEKEYS];

repo_name: orochi/nblood

license: mit


code:
// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman
// Ken Silverman's official web site: "http://www.advsys.net/ken"
// See the included license file "BUILDLIC.TXT" for license info.
// This file has been modified from Ken Silverman's original release

#include "compat.h"
#include "build.h"
#include "fx_man.h"
#include "renderlayer.h" // for win_gethwnd()

void initsb(char,char,int,char,char,char,char);
void uninitsb(void);
void setears(int,int,int,int);
void wsayfollow(char const *,int,int,int *,int *,char);
void wsay(char const *,int,int,int);
void loadwaves(char const *);
int loadsong(char const *);
void musicon(void);
void musicoff(void);
void refreshaudio(void) { };
void preparesndbuf();

static mutex_t m_callback;

#define NUMCHANNELS 16
#define MAXWAVES 256
#define MAXTRACKS 256
#define MAXNOTES 8192
#define MAXEFFECTS 16

    //Actual sound parameters after initsb was called
int samplerate, numspeakers, bytespersample, intspersec, kdmqual;

    //KWV wave variables
int numwaves;
char instname[MAXWAVES][16];
int wavleng[MAXWAVES];
int repstart[MAXWAVES], repleng[MAXWAVES];
int finetune[MAXWAVES];

    //Other useful wave variables
int totsndbytes, totsndmem;
intptr_t wavoffs[MAXWAVES];

    //Effects array
int eff[MAXEFFECTS][256];

    //KDM song variables:
int kdmversionum, numnotes, numtracks;
char trinst[MAXTRACKS], trquant[MAXTRACKS];
char trvol1[MAXTRACKS], trvol2[MAXTRACKS];
int nttime[MAXNOTES];
char nttrack[MAXNOTES], ntfreq[MAXNOTES];
char ntvol1[MAXNOTES], ntvol2[MAXNOTES];
char ntfrqeff[MAXNOTES], ntvoleff[MAXNOTES], ntpaneff[MAXNOTES];

    //Other useful song variables:
int timecount, notecnt, musicstatus, musicrepeat;

int kdmasm1, kdmasm3;
uint8_t *kdmasm2, *kdmasm4;

static char digistat = 0, musistat = 0;

char *snd = NULL, kwvname[20] = {""};

#define MAXBYTESPERTIC 1024+128
static int stemp[MAXBYTESPERTIC];

    //Sound reading information
int splc[NUMCHANNELS], sinc[NUMCHANNELS];
intptr_t soff[NUMCHANNELS];
int svol1[NUMCHANNELS], svol2[NUMCHANNELS];
int volookup[NUMCHANNELS<<9];
int swavenum[NUMCHANNELS];
int frqeff[NUMCHANNELS], frqoff[NUMCHANNELS];
int voleff[NUMCHANNELS], voloff[NUMCHANNELS];
int paneff[NUMCHANNELS], panoff[NUMCHANNELS];

static int globposx, globposy, globxvect, globyvect;
static intptr_t xplc[NUMCHANNELS], yplc[NUMCHANNELS];
static int vol[NUMCHANNELS];
static int vdist[NUMCHANNELS], sincoffs[NUMCHANNELS];
static char chanstat[NUMCHANNELS];

int frqtable[256];

int samplediv, oldtimerfreq, chainbackcnt, chainbackstart;
char *pcsndptr, pcsndlookup[256], bufferside;
int samplecount, pcsndbufsiz, pcrealmodeint;

#if 0
static short kdmvect = 0x8;
static unsigned short kdmpsel, kdmrseg, kdmroff;
static unsigned int kdmpoff;

#define KDMCODEBYTES 256
static char pcrealbuffer[KDMCODEBYTES] =        //See pckdm.asm
{
    0x50,0x53,0x51,0x52,0x32,0xC0,0xE6,0x42,0xB0,0x20,
    0xE6,0x20,0x5A,0x59,0x5B,0x58,0xCF,
};
#endif

static int bytespertic, sndsamplesleft;

char qualookup[512*16];
int ramplookup[64];

unsigned short sndseg = 0;

int monolocomb(int zero, int *vol, int cnt, int dat, int dasplc, int *stemp)
{
    UNREFERENCED_PARAMETER(zero);
    do
    {
        *stemp++ += vol[kdmasm4[dasplc>>12]];
        dasplc += dat;
        cnt--;
        if (dasplc >= 0 && cnt) // loop
        {
            if (!kdmasm1)
                break;
            kdmasm4 = kdmasm2;
            dasplc -= kdmasm3;
        }
    } while (cnt);
    return dasplc;
}

int monohicomb(int zero, int *vol, int cnt, int dat, int dasplc, int *stemp)
{
    UNREFERENCED_PARAMETER(zero);
    do
    {
        uint8_t s0 = kdmasm4[dasplc>>12];
        uint8_t s1 = kdmasm4[(dasplc>>12)+1];
        uint8_t si = qualookup[((dasplc<<1)&0x1e00)+((s0-s1)&0x1ff)]+s0;
        *stemp++ += vol[si];
        dasplc += dat;
        cnt--;
        if (dasplc >= 0 && cnt) // loop
        {
            if (!kdmasm1)
                break;
            kdmasm4 = kdmasm2;
            dasplc -= kdmasm3;
        }
    } while (cnt);
    return dasplc;
}

int stereolocomb(int zero, int *vol, int cnt, int dat, int dasplc, int *stemp)
{
    UNREFERENCED_PARAMETER(zero);
    do
    {
        *stemp++ += vol[kdmasm4[dasplc>>12]*2+0];
        *stemp++ += vol[kdmasm4[dasplc>>12]*2+1];
        dasplc += dat;
        cnt--;
        if (dasplc >= 0 && cnt) // loop
        {
            if (!kdmasm1)
                break;
            kdmasm4 = kdmasm2;
            dasplc -= kdmasm3;
        }
    } while (cnt);
    return dasplc;
}

int stereohicomb(int zero, int *vol, int cnt, int dat, int dasplc, int *stemp)
{
    UNREFERENCED_PARAMETER(zero);
    do
    {
        uint8_t s0 = kdmasm4[dasplc>>12];
        uint8_t s1 = kdmasm4[(dasplc>>12)+1];
        uint8_t si = qualookup[((dasplc<<1)&0x1e00)+((s0-s1)&0x1ff)]+s0;
        *stemp++ += vol[si*2+0];
        *stemp++ += vol[si*2+1];
        dasplc += dat;
        cnt--;
        if (dasplc >= 0 && cnt) // loop
        {
            if (!kdmasm1)
                break;
            kdmasm4 = kdmasm2;
            dasplc -= kdmasm3;
        }
    } while (cnt);
    return dasplc;
}

void bound2short(int bytespertic, int *stemp, short *shortptr)
{
    for (int i = 0; i < (bytespertic<<1); i++)
    {
        int j = stemp[i];
        stemp[i] = 32768;
        if (j < 0) j = 0;
        if (j > 65535) j = 65535;
        *shortptr++ = (short)(j^0x8000);
    }
}

void fsin(int *eax)
{
    *eax = (int)sin((*eax)*M_PI*(1.0/1024.0))*16384.0;
}

#if 0

#pragma aux fsin =\
	"fldpi",\
	"fimul dword ptr [eax]",\
	"fmul dword ptr [oneshr10]",\
	"fsin",\
	"fmul dword ptr [oneshl14]",\
	"fistp dword ptr [eax]",\
	parm [eax]\

#endif

void calcvolookupmono(int *edi, int eax, int ebx)
{
    for (int i = 0; i < 256; i++)
    {
        edi[i] = eax;
        eax += ebx;
    }
}

void calcvolookupstereo(int *edi, int eax, int ebx, int ecx, int edx)
{
    for (int i = 0; i < 256; i++)
    {
        edi[i*2+0] = eax;
        edi[i*2+1] = ecx;
        eax += ebx;
        ecx += edx;
    }
}

#if 0

#pragma aux calcvolookupmono =\
	"mov ecx, 64",\
	"lea edx, [eax+ebx]",\
	"add ebx, ebx",\
	"begit: mov dword ptr [edi], eax",\
	"mov dword ptr [edi+4], edx",\
	"add eax, ebx",\
	"add edx, ebx",\
	"mov dword ptr [edi+8], eax",\
	"mov dword ptr [edi+12], edx",\
	"add eax, ebx",\
	"add edx, ebx",\
	"add edi, 16",\
	"dec ecx",\
	"jnz begit",\
	parm [edi][eax][ebx]\
	modify [ecx edx]\

#pragma aux calcvolookupstereo =\
	"mov esi, 128",\
	"begit: mov dword ptr [edi], eax",\
	"mov dword ptr [edi+4], ecx",\
	"add eax, ebx",\
	"add ecx, edx",\
	"mov dword ptr [edi+8], eax",\
	"mov dword ptr [edi+12], ecx",\
	"add eax, ebx",\
	"add ecx, edx",\
	"add edi, 16",\
	"dec esi",\
	"jnz begit",\
	parm [edi][eax][ebx][ecx][edx]\
	modify [esi]\

#endif

void initsb(char dadigistat, char damusistat, int dasamplerate, char danumspeakers, char dabytespersample, char daintspersec, char daquality)
{
    int i, j;

    digistat = dadigistat;
    musistat = damusistat;

    if ((digistat == 0) && (musistat != 1))
        return;

#ifdef MIXERTYPEWIN
    void *initdata = (void *) win_gethwnd(); // used for DirectSound
#else
    void *initdata = NULL;
#endif

    if (FX_Init(1, danumspeakers, dasamplerate, initdata))
        return;

    MV_HookMusicRoutine(preparesndbuf);

    mutex_init(&m_callback);

    samplerate = dasamplerate;
    //if (samplerate < 6000) samplerate = 6000;
    //if (samplerate > 48000) samplerate = 48000;
    numspeakers = 2;//  danumspeakers;
    if (numspeakers < 1) numspeakers = 1;
    if (numspeakers > 2) numspeakers = 2;
    bytespersample = dabytespersample;
    if (bytespersample < 1) bytespersample = 1;
    if (bytespersample > 2) bytespersample = 2;
    intspersec = daintspersec;
    if (intspersec < 1) intspersec = 1;
    if (intspersec > 120) intspersec = 120;
    kdmqual = daquality;
    if (kdmqual < 0) kdmqual = 0;
    if (kdmqual > 1) kdmqual = 1;

    bytespertic = (((samplerate/120)+1)&~1);
    sndsamplesleft = 0;

    j = (((11025L*2093)/samplerate)<<13);
    for(i=1;i<76;i++)
    {
        frqtable[i] = j;
        j = mulscale30(j,1137589835);  //(pow(2,1/12)<<30) = 1137589835
    }
    for(i=0;i>=-14;i--) frqtable[i&255] = (frqtable[(i+12)&255]>>1);

    loadwaves("WAVES.KWV");

    timecount = notecnt = musicstatus = musicrepeat = 0;

    clearbuf(stemp,sizeof(stemp)>>2,32768L);
    for(i=0;i<256;i++)
        for(j=0;j<16;j++)
        {
            qualookup[(j<<9)+i] = (((-i)*j+8)>>4);
            qualookup[(j<<9)+i+256] = (((256-i)*j+8)>>4);
        }
    for(i=0;i<(samplerate>>11);i++)
    {
        j = 1536 - (i<<10)/(samplerate>>11);
        fsin(&j);
        ramplookup[i] = ((16384-j)<<1);
    }

    for(i=0;i<256;i++)
    {
        j = i*90; fsin(&j);
        eff[0][i] = 65536+j/9;
        eff[1][i] = min(58386+((i*(65536-58386))/30),65536);
        eff[2][i] = max(69433+((i*(65536-69433))/30),65536);
        j = (i*2048)/120; fsin(&j);
        eff[3][i] = 65536+(j<<2);
        j = (i*2048)/30; fsin(&j);
        eff[4][i] = 65536+j;
        switch((i>>1)%3)
        {
            case 0: eff[5][i] = 65536; break;
            case 1: eff[5][i] = 65536*330/262; break;
            case 2: eff[5][i] = 65536*392/262; break;
        }
        eff[6][i] = min((i<<16)/120,65536);
        eff[7][i] = max(65536-(i<<16)/120,0);
    }

    musicoff();
}

void uninitsb()
{
    if ((digistat == 0) && (musistat != 1))
        return;

    if (snd != 0) free(snd), snd = 0;
}

static void startwave_internal(int wavnum, int dafreq, int davolume1, int davolume2, int dafrqeff, int davoleff, int dapaneff)
{
    int i, chanum;

    chanum = 0;
    for(i=NUMCHANNELS-1;i>0;i--)
        if (splc[i] > splc[chanum])
            chanum = i;

    splc[chanum] = 0;     //Disable channel temporarily for clean switch

    if (numspeakers == 1)
        calcvolookupmono(&volookup[chanum<<9],-(davolume1+davolume2)<<6,(davolume1+davolume2)>>1);
    else
        calcvolookupstereo(&volookup[chanum<<9],-(davolume1<<7),davolume1,-(davolume2<<7),davolume2);

    sinc[chanum] = dafreq;
    svol1[chanum] = davolume1;
    svol2[chanum] = davolume2;
    soff[chanum] = wavoffs[wavnum]+wavleng[wavnum];
    splc[chanum] = -(wavleng[wavnum]<<12);              //splc's modified last
    swavenum[chanum] = wavnum;
    frqeff[chanum] = dafrqeff; frqoff[chanum] = 0;
    voleff[chanum] = davoleff; voloff[chanum] = 0;
    paneff[chanum] = dapaneff; panoff[chanum] = 0;
    chanstat[chanum] = 0; sincoffs[chanum] = 0;
}

static inline void startwave(int wavnum, int dafreq, int davolume1, int davolume2, int dafrqeff, int davoleff, int dapaneff)
{
    if ((davolume1|davolume2) == 0) return;

    mutex_lock(&m_callback);
    startwave_internal(wavnum, dafreq, davolume1, davolume2, dafrqeff, davoleff, dapaneff);
    mutex_unlock(&m_callback);
}

void setears(int daposx, int daposy, int daxvect, int dayvect)
{
    mutex_lock(&m_callback);
    globposx = daposx;
    globposy = daposy;
    globxvect = daxvect;
    globyvect = dayvect;
    mutex_unlock(&m_callback);
}

void wsayfollow(const char *dafilename, int dafreq, int davol, int *daxplc, int *dayplc, char followstat)
{
    char ch1, ch2, bad;
    int i, wavnum, chanum;

    if (digistat == 0) return;
    if (davol <= 0) return;

    mutex_lock(&m_callback);
    for(wavnum=numwaves-1;wavnum>=0;wavnum--)
    {
        bad = 0;

        i = 0;
        while ((dafilename[i] > 0) && (i < 16))
        {
            ch1 = dafilename[i]; if ((ch1 >= 97) && (ch1 <= 123)) ch1 -= 32;
            ch2 = instname[wavnum][i]; if ((ch2 >= 97) && (ch2 <= 123)) ch2 -= 32;
            if (ch1 != ch2) {bad = 1; break;}
            i++;
        }
        if (bad != 0) continue;

        chanum = 0;
        for(i=NUMCHANNELS-1;i>0;i--) if (splc[i] > splc[chanum]) chanum = i;

        splc[chanum] = 0;     //Disable channel temporarily for clean switch

        if (followstat == 0)
        {
            xplc[chanum] = *daxplc;
            yplc[chanum] = *dayplc;
        }
        else
        {
            xplc[chanum] = ((intptr_t)daxplc);
            yplc[chanum] = ((intptr_t)dayplc);
        }
        vol[chanum] = davol;
        vdist[chanum] = 0;
        sinc[chanum] = (dafreq*11025)/samplerate;
        svol1[chanum] = davol;
        svol2[chanum] = davol;
        sincoffs[chanum] = 0;
        soff[chanum] = wavoffs[wavnum]+wavleng[wavnum];
        splc[chanum] = -(wavleng[wavnum]<<12);              //splc's modified last
        swavenum[chanum] = wavnum;
        chanstat[chanum] = followstat+1;
        frqeff[chanum] = 0; frqoff[chanum] = 0;
        voleff[chanum] = 0; voloff[chanum] = 0;
        paneff[chanum] = 0; panoff[chanum] = 0;
        break;
    }
    mutex_unlock(&m_callback);
}

void preparesndbuf()
{
    int i, j, k, voloffs1, voloffs2, *stempptr;
    int *volptr;
    int daswave, dasinc;
    int ox, oy, x, y;

    auto routinebuf = MV_GetMusicRoutineBuffer();
    auto sndbuf = (short *)routinebuf.buffer;
    int32_t sndsamples = routinebuf.size / 4;

    mutex_lock(&m_callback);
    for (i=NUMCHANNELS-1;i>=0;i--)
        if ((splc[i] < 0) && (chanstat[i] > 0))
        {
            if (chanstat[i] == 1)
            {
                ox = xplc[i];
                oy = yplc[i];
            }
            else
            {
                stempptr = (int *)xplc[i]; ox = *stempptr;
                stempptr = (int *)yplc[i]; oy = *stempptr;
            }
            ox -= globposx; oy -= globposy;
            x = dmulscale28(oy,globxvect,-ox,globyvect);
            y = dmulscale28(ox,globxvect,oy,globyvect);

            if ((klabs(x) >= 32768) || (klabs(y) >= 32768))
                { splc[i] = 0; continue; }

            j = vdist[i];
            vdist[i] = ksqrt(x*x+y*y);
            if (j)
            {
                j = (sinc[i]<<10)/(min(max(vdist[i]-j,-768),768)+1024)-sinc[i];
                sincoffs[i] = ((sincoffs[i]*7+j)>>3);
            }

            voloffs1 = min((vol[i]<<22)/(((x+1536)*(x+1536)+y*y)+1),255);
            voloffs2 = min((vol[i]<<22)/(((x-1536)*(x-1536)+y*y)+1),255);

            if (numspeakers == 1)
                calcvolookupmono(&volookup[i<<9],-(voloffs1+voloffs2)<<6,(voloffs1+voloffs2)>>1);
            else
                calcvolookupstereo(&volookup[i<<9],-(voloffs1<<7),voloffs1,-(voloffs2<<7),voloffs2);
        }

    int left = min(sndsamplesleft, sndsamples);
    if (left)
    {
        bound2short(left, &stemp[(bytespertic-sndsamplesleft)<<1], sndbuf);
        sndbuf += left<<1;
        sndsamples -= left;
        sndsamplesleft -= left;
    }
    while (sndsamples > 0)
    {
        if (musicstatus > 0)    //Gets here 120 times/second
        {
            while ((notecnt < numnotes) && (timecount >= nttime[notecnt]))
            {
                j = trinst[nttrack[notecnt]];
                k = mulscale24(frqtable[ntfreq[notecnt]],finetune[j]+748);

                if (ntvol1[notecnt] == 0)   //Note off
                {
                    for(i=NUMCHANNELS-1;i>=0;i--)
                        if (splc[i] < 0)
                            if (swavenum[i] == j)
                                if (sinc[i] == k)
                                    splc[i] = 0;
                }
                else                        //Note on
                    startwave_internal(j,k,ntvol1[notecnt],ntvol2[notecnt],ntfrqeff[notecnt],ntvoleff[notecnt],ntpaneff[notecnt]);

                notecnt++;
                if (notecnt >= numnotes)
                    if (musicrepeat > 0)
                        notecnt = 0, timecount = nttime[0];
            }
            timecount++;
        }

        for(i=NUMCHANNELS-1;i>=0;i--)
        {
            if (splc[i] >= 0) continue;

            dasinc = sinc[i]+sincoffs[i];

            if (frqeff[i] != 0)
            {
                dasinc = mulscale16(dasinc,eff[frqeff[i]-1][frqoff[i]]);
                frqoff[i]++; if (frqoff[i] >= 256) frqeff[i] = 0;
            }
            if ((voleff[i]) || (paneff[i]))
            {
                voloffs1 = svol1[i];
                voloffs2 = svol2[i];
                if (voleff[i])
                {
                    voloffs1 = mulscale16(voloffs1,eff[voleff[i]-1][voloff[i]]);
                    voloffs2 = mulscale16(voloffs2,eff[voleff[i]-1][voloff[i]]);
                    voloff[i]++; if (voloff[i] >= 256) voleff[i] = 0;
                }

                if (numspeakers == 1)
                    calcvolookupmono(&volookup[i<<9],-(voloffs1+voloffs2)<<6,(voloffs1+voloffs2)>>1);
                else
                {
                    if (paneff[i])
                    {
                        voloffs1 = mulscale16(voloffs1,131072-eff[paneff[i]-1][panoff[i]]);
                        voloffs2 = mulscale16(voloffs2,eff[paneff[i]-1][panoff[i]]);
                        panoff[i]++; if (panoff[i] >= 256) paneff[i] = 0;
                    }
                    calcvolookupstereo(&volookup[i<<9],-(voloffs1<<7),voloffs1,-(voloffs2<<7),voloffs2);
                }
            }

            daswave = swavenum[i];
            volptr = &volookup[i<<9];

            kdmasm1 = repleng[daswave];
            kdmasm2 = (uint8_t*)wavoffs[daswave]+repstart[daswave]+repleng[daswave];
            kdmasm3 = (repleng[daswave]<<12); //repsplcoff
            kdmasm4 = (uint8_t*)soff[i];
            if (numspeakers == 1)
            {
                if (kdmqual == 0) splc[i] = monolocomb(0L,volptr,bytespertic,dasinc,splc[i],stemp);
                                    else splc[i] = monohicomb(0L,volptr,bytespertic,dasinc,splc[i],stemp);
            }
            else
            {
                if (kdmqual == 0) splc[i] = stereolocomb(0L,volptr,bytespertic,dasinc,splc[i],stemp);
                                    else splc[i] = stereohicomb(0L,volptr,bytespertic,dasinc,splc[i],stemp);
            }
            soff[i] = (intptr_t)kdmasm4;

            if ((kdmqual == 0) || (splc[i] >= 0)) continue;
            if (numspeakers == 1)
            {
                if (kdmqual == 0) monolocomb(0L,volptr,samplerate>>11,dasinc,splc[i],&stemp[bytespertic]);
                                    else monohicomb(0L,volptr,samplerate>>11,dasinc,splc[i],&stemp[bytespertic]);
            }
            else
            {
                if (kdmqual == 0) stereolocomb(0L,volptr,samplerate>>11,dasinc,splc[i],&stemp[bytespertic<<1]);
                                    else stereohicomb(0L,volptr,samplerate>>11,dasinc,splc[i],&stemp[bytespertic<<1]);
            }
        }

        if (kdmqual)
        {
            if (numspeakers == 1)
            {
                for(i=(samplerate>>11)-1;i>=0;i--)
                    stemp[i] += mulscale16(stemp[i+1024]-stemp[i],ramplookup[i]);
                j = bytespertic; k = (samplerate>>11);
                copybuf(&stemp[j],&stemp[1024],k);
                clearbuf(&stemp[j],k,32768);
            }
            else
            {
                for(i=(samplerate>>11)-1;i>=0;i--)
                {
                    j = (i<<1);
                    stemp[j+0] += mulscale16(stemp[j+1024]-stemp[j+0],ramplookup[i]);
                    stemp[j+1] += mulscale16(stemp[j+1025]-stemp[j+1],ramplookup[i]);
                }
                j = (bytespertic<<1); k = ((samplerate>>11)<<1);
                copybuf(&stemp[j],&stemp[1024],k);
                clearbuf(&stemp[j],k,32768);
            }
        }

        sndsamplesleft = bytespertic;
        left = min(sndsamplesleft, sndsamples);
        bound2short(left, stemp, sndbuf);
        sndbuf += left<<1;
        sndsamples -= left;
        sndsamplesleft -= left;
    }
    mutex_unlock(&m_callback);
}

void wsay(const char *dafilename, int dafreq, int volume1, int volume2)
{
    unsigned char ch1, ch2;
    int i, j, bad;

    if (digistat == 0) return;

    i = numwaves-1;
    do
    {
        bad = 0;

        j = 0;
        while ((dafilename[j] > 0) && (j < 16))
        {
            ch1 = dafilename[j]; if ((ch1 >= 97) && (ch1 <= 123)) ch1 -= 32;
            ch2 = instname[i][j]; if ((ch2 >= 97) && (ch2 <= 123)) ch2 -= 32;
            if (ch1 != ch2) {bad = 1; break;}
            j++;
        }
        if (bad == 0)
        {
            startwave(i,(dafreq*11025)/samplerate,volume1,volume2,0L,0L,0L);
            return;
        }

        i--;
    } while (i >= 0);
}

void loadwaves(const char *wavename)
{
    int fil, i, j, dawaversionum;
    char filename[80];

    strcpy(filename,wavename);
    if (strstr(filename,".KWV") == 0) strcat(filename,".KWV");
    if ((fil = kopen4load(filename,0)) == -1)
        if (strcmp(filename,"WAVES.KWV") != 0)
        {
            strcpy(filename,"WAVES.KWV");
            fil = kopen4load(filename,0);
        }

    totsndbytes = 0;

    if (fil != -1)
    {
        if (strcmp(kwvname,filename) == 0) { kclose(fil); return; }
        strcpy(kwvname,filename);

        kread(fil,&dawaversionum,4);
        if (dawaversionum != 0) { kclose(fil); return; }

        kread(fil,&numwaves,4);
        for(i=0;i<numwaves;i++)
        {
            kread(fil,&instname[i][0],16);
            kread(fil,&wavleng[i],4);
            kread(fil,&repstart[i],4);
            kread(fil,&repleng[i],4);
            kread(fil,&finetune[i],4);
            wavoffs[i] = totsndbytes;
            totsndbytes += wavleng[i];
        }
    }
    else
    {
        dawaversionum = 0;
        numwaves = 0;
    }

    for(i=numwaves;i<MAXWAVES;i++)
    {
        for(j=0;j<16;j++) instname[i][j] = 0;
        wavoffs[i] = totsndbytes;
        wavleng[i] = 0L;
        repstart[i] = 0L;
        repleng[i] = 0L;
        finetune[i] = 0L;
    }

    if (snd == 0)
    {
        if ((snd = (char *)malloc(totsndbytes+2)) == 0)
            { printf("Not enough memory for digital music!\n"); exit(0); }
    }
    for(i=0;i<MAXWAVES;i++) wavoffs[i] += (intptr_t)snd;
    if (fil != -1)
    {
        kread(fil,snd,totsndbytes);
        kclose(fil);
    }
    snd[totsndbytes] = snd[totsndbytes+1] = 128;
}

int loadsong(const char *_filename)
{
    int fil;
    char filename[256];

    Bstrcpy(filename, _filename);

    if (musistat != 1) return(0);
    musicoff();
    Bstrupr(filename);
    if (strstr(filename,".KDM") == 0) strcat(filename,".KDM");
    if ((fil = kopen4load(filename,0)) == -1)
    {
        printf("I cannot load %s.\n",filename);
        uninitsb();
        return(-1);
    }
    kread(fil,&kdmversionum,4);
    if (kdmversionum != 0) return(-2);
    kread(fil,&numnotes,4);
    kread(fil,&numtracks,4);
    kread(fil,trinst,numtracks);
    kread(fil,trquant,numtracks);
    kread(fil,trvol1,numtracks);
    kread(fil,trvol2,numtracks);
    kread(fil,nttime,numnotes<<2);
    kread(fil,nttrack,numnotes);
    kread(fil,ntfreq,numnotes);
    kread(fil,ntvol1,numnotes);
    kread(fil,ntvol2,numnotes);
    kread(fil,ntfrqeff,numnotes);
    kread(fil,ntvoleff,numnotes);
    kread(fil,ntpaneff,numnotes);
    kclose(fil);
    return(0);
}

void musicon()
{
    if (musistat != 1)
        return;

    mutex_lock(&m_callback);
    notecnt = 0;
    timecount = nttime[notecnt];
    musicrepeat = 1;
    musicstatus = 1;
    mutex_unlock(&m_callback);
}

void musicoff()
{
    int i;

    mutex_lock(&m_callback);
    musicstatus = 0;
    for(i=0;i<NUMCHANNELS;i++)
        splc[i] = 0;
    musicrepeat = 0;
    timecount = 0;
    notecnt = 0;
    mutex_unlock(&m_callback);
}


code:
#ifndef KEYS_H_
#define KEYS_H_

#define ESC    0x1B
#define ENTER  0x0D

#define KEYSC_ESC      0x01
#define KEYSC_1        0x02
#define KEYSC_2        0x03
#define KEYSC_3        0x04
#define KEYSC_4        0x05
#define KEYSC_5        0x06
#define KEYSC_6        0x07
#define KEYSC_7        0x08
#define KEYSC_8        0x09
#define KEYSC_9        0x0a
#define KEYSC_0        0x0b
#define KEYSC_DASH     0x0c
#define KEYSC_EQUAL    0x0d

#define KEYSC_BS       0x0e
#define KEYSC_TAB      0x0f
#define KEYSC_Q        0x10
#define KEYSC_W        0x11
#define KEYSC_E        0x12
#define KEYSC_R        0x13
#define KEYSC_T        0x14
#define KEYSC_Y        0x15
#define KEYSC_U        0x16
#define KEYSC_I        0x17
#define KEYSC_O        0x18
#define KEYSC_P        0x19
#define KEYSC_LBRACK   0x1a
#define KEYSC_RBRACK   0x1b
#define KEYSC_ENTER    0x1c

#define KEYSC_LCTRL    0x1d
#define KEYSC_A        0x1e
#define KEYSC_S        0x1f
#define KEYSC_D        0x20
#define KEYSC_F        0x21
#define KEYSC_G        0x22
#define KEYSC_H        0x23
#define KEYSC_J        0x24
#define KEYSC_K        0x25
#define KEYSC_L        0x26
#define KEYSC_SEMI     0x27
#define KEYSC_QUOTE    0x28
#define KEYSC_BQUOTE   0x29
#define KEYSC_TILDE    0x29

#define KEYSC_LSHIFT   0x2a
#define KEYSC_BSLASH   0x2b
#define KEYSC_Z        0x2c
#define KEYSC_X        0x2d
#define KEYSC_C        0x2e
#define KEYSC_V        0x2f
#define KEYSC_B        0x30
#define KEYSC_N        0x31
#define KEYSC_M        0x32
#define KEYSC_COMMA    0x33
#define KEYSC_PERIOD   0x34
#define KEYSC_SLASH    0x35
#define KEYSC_RSHIFT   0x36
#define KEYSC_gSTAR    0x37

#define KEYSC_LALT     0x38
#define KEYSC_SPACE    0x39
#define KEYSC_CAPS     0x3a

#define KEYSC_F1       0x3b
#define KEYSC_F2       0x3c
#define KEYSC_F3       0x3d
#define KEYSC_F4       0x3e
#define KEYSC_F5       0x3f
#define KEYSC_F6       0x40
#define KEYSC_F7       0x41
#define KEYSC_F8       0x42
#define KEYSC_F9       0x43
#define KEYSC_F10      0x44

#define KEYSC_gNUM     0x45
#define KEYSC_SCROLL   0x46

#define KEYSC_gHOME    0x47
#define KEYSC_gUP      0x48
#define KEYSC_gPGUP    0x49
#define KEYSC_gMINUS   0x4a
#define KEYSC_gLEFT    0x4b
#define KEYSC_gKP5     0x4c
#define KEYSC_gRIGHT   0x4d
#define KEYSC_gPLUS    0x4e
#define KEYSC_gEND     0x4f
#define KEYSC_gDOWN    0x50
#define KEYSC_gPGDN    0x51
#define KEYSC_gINS     0x52
#define KEYSC_gDEL     0x53

#define KEYSC_F11      0x57
#define KEYSC_F12      0x58

#define KEYSC_gENTER   0x9C
#define KEYSC_RCTRL    0x9D
#define KEYSC_gSLASH   0xB5
#define KEYSC_RALT     0xB8
#define KEYSC_PRTSCN   0xB7
#define KEYSC_PAUSE    0xC5
#define KEYSC_HOME     0xC7
#define KEYSC_UP       0xC8
#define KEYSC_PGUP     0xC9
#define KEYSC_LEFT     0xCB
#define KEYSC_RIGHT    0xCD
#define KEYSC_END      0xCF
#define KEYSC_DOWN     0xD0
#define KEYSC_PGDN     0xD1
#define KEYSC_INSERT   0xD2
#define KEYSC_DELETE   0xD3

#define asc_Esc         27
#define asc_Enter       13
#define asc_Space       32

#endif


code:
//Be careful when changing this file - it is parsed by Editart and Build.
#define SWITCH1ON 15
#define SLIME 34
#define BACKGROUND 37
#define KENPICTURE 48
#define BUILDDISK 49
#define SWITCH2ON 66
#define SWITCH2OFF 69
#define ALPHABET 73
#define NO 74
#define DEMOSIGN 75
#define COIN 76
#define COINSTACK 77
#define GIFTBOX 78
#define DIAMONDS 79
#define EVILALGRAVE 83
#define STATUSBAR 87
#define DAYSKY 89
#define WATERFOUNTAIN 90
#define USEWATERFOUNTAIN 91
#define NIGHTSKY 93
#define BULLET 98
#define BOMB 100
#define CANNON 101
#define GUNONBOTTOM 102
#define BOMBEMITTER 103
#define EXPLOSION 105
#define SPLASH 106
#define BROWNMONSTER 110
#define SKELETON 113
#define AL 114
#define EVILAL 115
#define PLAYER 120
#define SWITCH3OFF 146
#define SWITCH3ON 147
#define AIRPLANE 148
#define SPIRAL 149
#define COMPASS 150
#define FOOTPRINT 156
#define STATUSBARFILL8 160
#define STATUSBARFILL4 161
#define BOUNCYMAT 162
#define MIRROR 165
#define FLOORMIRROR 166
#define GRABBER 167
#define GRABCANNON 168
#define MISSILE 169
#define LAUNCHER 171
#define MIRRORLABEL 4000


code:
#include "compat.h"

void initsb(char dadigistat, char damusistat, int dasamplerate, char danumspeakers, char dabytespersample, char daintspersec, char daquality)
{
    UNREFERENCED_PARAMETER(dadigistat);
    UNREFERENCED_PARAMETER(damusistat);
    UNREFERENCED_PARAMETER(dasamplerate);
    UNREFERENCED_PARAMETER(danumspeakers);
    UNREFERENCED_PARAMETER(dabytespersample);
    UNREFERENCED_PARAMETER(daintspersec);
    UNREFERENCED_PARAMETER(daquality);
}

void uninitsb(void)
{
}

void setears(int daposx, int daposy, int daxvect, int dayvect)
{
    UNREFERENCED_PARAMETER(daposx);
    UNREFERENCED_PARAMETER(daposy);
    UNREFERENCED_PARAMETER(daxvect);
    UNREFERENCED_PARAMETER(dayvect);
}

void wsayfollow(char const *dafilename, int dafreq, int davol, int *daxplc, int *dayplc, char followstat)
{
    UNREFERENCED_PARAMETER(dafilename);
    UNREFERENCED_PARAMETER(dafreq);
    UNREFERENCED_PARAMETER(davol);
    UNREFERENCED_PARAMETER(daxplc);
    UNREFERENCED_PARAMETER(dayplc);
    UNREFERENCED_PARAMETER(followstat);
}

void wsay(char const *dafilename, int dafreq, int volume1, int volume2)
{
    UNREFERENCED_PARAMETER(dafilename);
    UNREFERENCED_PARAMETER(dafreq);
    UNREFERENCED_PARAMETER(volume1);
    UNREFERENCED_PARAMETER(volume2);
}

void loadwaves(const char *wavename)
{
    UNREFERENCED_PARAMETER(wavename);
}

void loadsong(char const *filename)
{
    UNREFERENCED_PARAMETER(filename);
}

void musicon(void)
{
}

void musicoff(void)
{
}

void refreshaudio(void)
{
}


code:
/* NOTE: Glade will generate code for a dialogue box which you should
 * then patch into this file whenever you make a change to the Glade
 * template.
 */

#include "compat.h"

#include "gtkpixdata.h"
#include <gdk-pixbuf/gdk-pixdata.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>

#include "dynamicgtk.h"

#include "baselayer.h"
#include "compat.h"
#include "build.h"
#include "editor.h"

#define TAB_CONFIG 0
#define TAB_MESSAGES 1

static struct
{
    int fullscreen;
    int xdim3d, ydim3d, bpp3d;
    int forcesetup;
} settings;

extern int gtkenabled;

static GtkWidget *startwin = NULL;
static int retval = -1, mode = TAB_MESSAGES;

// -- SUPPORT FUNCTIONS -------------------------------------------------------

#define GLADE_HOOKUP_OBJECT(component,widget,name) \
    g_object_set_data_full (G_OBJECT (component), name, \
                            gtk_widget_ref (widget), (GDestroyNotify) gtk_widget_unref)

#define GLADE_HOOKUP_OBJECT_NO_REF(component,widget,name) \
    g_object_set_data (G_OBJECT (component), name, widget)

#define lookup_widget(x,w) \
    (GtkWidget*) g_object_get_data(G_OBJECT(x), w)

static GdkPixbuf *load_banner(void)
{
    return gdk_pixbuf_from_pixdata((GdkPixdata const *)&startbanner_pixdata, FALSE, NULL);
}

static void SetPage(int n)
{
    if (!gtkenabled || !startwin) return;
    mode = n;
    gtk_notebook_set_current_page(GTK_NOTEBOOK(lookup_widget(startwin,"tabs")), n);

    // each control in the config page vertical layout plus the start button should be made (in)sensitive
    if (n == TAB_CONFIG) n = TRUE; else n = FALSE;
    gtk_widget_set_sensitive(lookup_widget(startwin,"startbutton"), n);
    gtk_container_foreach(GTK_CONTAINER(lookup_widget(startwin,"configvlayout")),
                          (GtkCallback)gtk_widget_set_sensitive, (gpointer)n);
}

static void on_vmode3dcombo_changed(GtkComboBox *, gpointer);
static void PopulateForm(void)
{
    int mode3d, i;
    GtkListStore *modes3d;
    GtkTreeIter iter;
    GtkComboBox *box3d;
    char buf[64];

    mode3d = videoCheckMode(&settings.xdim3d, &settings.ydim3d, settings.bpp3d, settings.fullscreen, 1);
    if (mode3d < 0)
    {
        int i, cd[] = { 32, 24, 16, 15, 8, 0 };
        for (i=0; cd[i]; ) { if (cd[i] >= settings.bpp3d) i++; else break; }
        for (; cd[i]; i++)
        {
            mode3d = videoCheckMode(&settings.xdim3d, &settings.ydim3d, cd[i], settings.fullscreen, 1);
            if (mode3d < 0) continue;
            settings.bpp3d = cd[i];
            break;
        }
    }

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lookup_widget(startwin,"fullscreencheck")), settings.fullscreen);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lookup_widget(startwin,"alwaysshowcheck")), settings.forcesetup);

    box3d = GTK_COMBO_BOX(lookup_widget(startwin,"vmode3dcombo"));
    modes3d = GTK_LIST_STORE(gtk_combo_box_get_model(box3d));
    gtk_list_store_clear(modes3d);

    for (i=0; i<validmodecnt; i++)
    {
        if (validmode[i].fs != settings.fullscreen) continue;

        // all modes get added to the 3D mode list
        Bsprintf(buf, "%ld x %ld %dbpp", validmode[i].xdim, validmode[i].ydim, validmode[i].bpp);
        gtk_list_store_append(modes3d, &iter);
        gtk_list_store_set(modes3d, &iter, 0,buf, 1,i, -1);
        if (i == mode3d)
        {
            g_signal_handlers_block_by_func(box3d, (gpointer) on_vmode3dcombo_changed, NULL);
            gtk_combo_box_set_active_iter(box3d, &iter);
            g_signal_handlers_unblock_by_func(box3d, (gpointer) on_vmode3dcombo_changed, NULL);
        }
    }
}

// -- EVENT CALLBACKS AND CREATION STUFF --------------------------------------

static void on_vmode3dcombo_changed(GtkComboBox *combobox, gpointer user_data)
{
    GtkTreeModel *data;
    GtkTreeIter iter;
    int val;
    if (!gtk_combo_box_get_active_iter(combobox, &iter)) return;
    if (!(data = gtk_combo_box_get_model(combobox))) return;
    gtk_tree_model_get(data, &iter, 1, &val, -1);
    settings.xdim3d = validmode[val].xdim;
    settings.ydim3d = validmode[val].ydim;
}

static void on_fullscreencheck_toggled(GtkToggleButton *togglebutton, gpointer user_data)
{
    settings.fullscreen = (gtk_toggle_button_get_active(togglebutton) == TRUE);
    PopulateForm();
}

static void on_alwaysshowcheck_toggled(GtkToggleButton *togglebutton, gpointer user_data)
{
    settings.forcesetup = (gtk_toggle_button_get_active(togglebutton) == TRUE);
}

static void on_cancelbutton_clicked(GtkButton *button, gpointer user_data)
{
    if (mode == TAB_CONFIG) { retval = 0; gtk_main_quit(); }
    else quitevent++;
}

static void on_startbutton_clicked(GtkButton *button, gpointer user_data)
{
    retval = 1;
    gtk_main_quit();
}

static gboolean on_startwin_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
    if (mode == TAB_CONFIG) { retval = 0; gtk_main_quit(); }
    else quitevent++;
    return TRUE;    // FALSE would let the event go through. we want the game to decide when to close
}

static GtkWidget *create_window(void)
{
    GtkWidget *startwin;
    GtkWidget *hlayout;
    GtkWidget *banner;
    GtkWidget *vlayout;
    GtkWidget *tabs;
    GtkWidget *configvlayout;
    GtkWidget *configlayout;
    GtkWidget *vmode3dlabel;
    GtkWidget *vmode3dcombo;
    GtkWidget *fullscreencheck;
    GtkWidget *alwaysshowcheck;
    GtkWidget *configtab;
    GtkWidget *messagesscroll;
    GtkWidget *messagestext;
    GtkWidget *messagestab;
    GtkWidget *buttons;
    GtkWidget *cancelbutton;
    GtkWidget *cancelbuttonalign;
    GtkWidget *cancelbuttonlayout;
    GtkWidget *cancelbuttonicon;
    GtkWidget *cancelbuttonlabel;
    GtkWidget *startbutton;
    GtkWidget *startbuttonalign;
    GtkWidget *startbuttonlayout;
    GtkWidget *startbuttonicon;
    GtkWidget *startbuttonlabel;
    GtkAccelGroup *accel_group;

    accel_group = gtk_accel_group_new();

    // Basic window
    startwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(startwin), apptitle);     // NOTE: use global app title
    gtk_window_set_position(GTK_WINDOW(startwin), GTK_WIN_POS_CENTER);
    gtk_window_set_resizable(GTK_WINDOW(startwin), FALSE);
    gtk_window_set_type_hint(GTK_WINDOW(startwin), GDK_WINDOW_TYPE_HINT_DIALOG);

    // Horizontal layout of banner and controls
    hlayout = gtk_hbox_new(FALSE, 0);
    gtk_widget_show(hlayout);
    gtk_container_add(GTK_CONTAINER(startwin), hlayout);

    // Banner
    {
        GdkPixbuf *pixbuf = load_banner();
        banner = gtk_image_new_from_pixbuf(pixbuf);
        g_object_unref((gpointer)pixbuf);
    }
    gtk_widget_show(banner);
    gtk_box_pack_start(GTK_BOX(hlayout), banner, FALSE, FALSE, 0);
    gtk_misc_set_alignment(GTK_MISC(banner), 0.5, 0);

    // Vertical layout of tab control and start+cancel buttons
    vlayout = gtk_vbox_new(FALSE, 0);
    gtk_widget_show(vlayout);
    gtk_box_pack_start(GTK_BOX(hlayout), vlayout, TRUE, TRUE, 0);

    // Tab control
    tabs = gtk_notebook_new();
    gtk_widget_show(tabs);
    gtk_box_pack_start(GTK_BOX(vlayout), tabs, TRUE, TRUE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(tabs), 4);

    // Vertical layout of config page main body
    configvlayout = gtk_vbox_new(FALSE, 0);
    gtk_widget_show(configvlayout);
    gtk_container_add(GTK_CONTAINER(tabs), configvlayout);

    // Fixed-position layout of config page controls
    configlayout = gtk_fixed_new();
    gtk_widget_show(configlayout);
    gtk_box_pack_start(GTK_BOX(configvlayout), configlayout, TRUE, TRUE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(configlayout), 6);

    // 3D video mode label
    vmode3dlabel = gtk_label_new_with_mnemonic("_Video mode:");
    gtk_widget_show(vmode3dlabel);
    gtk_fixed_put(GTK_FIXED(configlayout), vmode3dlabel, 0, 0);
    gtk_widget_set_size_request(vmode3dlabel, 88, 29);
    gtk_misc_set_alignment(GTK_MISC(vmode3dlabel), 0, 0.5);

    // 3D video mode combo
    {
        GtkListStore *list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
        GtkCellRenderer *cell;

        vmode3dcombo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
        g_object_unref(G_OBJECT(list));

        cell = gtk_cell_renderer_text_new();
        gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(vmode3dcombo), cell, FALSE);
        gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(vmode3dcombo), cell, "text", 0, NULL);
    }
    gtk_widget_show(vmode3dcombo);
    gtk_fixed_put(GTK_FIXED(configlayout), vmode3dcombo, 88, 0);
    gtk_widget_set_size_request(vmode3dcombo, 150, 29);
    gtk_widget_add_accelerator(vmode3dcombo, "grab_focus", accel_group,
                               GDK_V, GDK_MOD1_MASK,
                               GTK_ACCEL_VISIBLE);

    // Fullscreen checkbox
    fullscreencheck = gtk_check_button_new_with_mnemonic("_Fullscreen");
    gtk_widget_show(fullscreencheck);
    gtk_fixed_put(GTK_FIXED(configlayout), fullscreencheck, 248, 0);
    gtk_widget_set_size_request(fullscreencheck, 85, 29);
    gtk_widget_add_accelerator(fullscreencheck, "grab_focus", accel_group,
                               GDK_F, GDK_MOD1_MASK,
                               GTK_ACCEL_VISIBLE);

    // Always show config checkbox
    alwaysshowcheck = gtk_check_button_new_with_mnemonic("_Always show configuration on start");
    gtk_widget_show(alwaysshowcheck);
    gtk_box_pack_start(GTK_BOX(configvlayout), alwaysshowcheck, FALSE, FALSE, 0);
    gtk_widget_add_accelerator(alwaysshowcheck, "grab_focus", accel_group,
                               GDK_A, GDK_MOD1_MASK,
                               GTK_ACCEL_VISIBLE);

    // Configuration tab
    configtab = gtk_label_new("Configuration");
    gtk_widget_show(configtab);
    gtk_notebook_set_tab_label(GTK_NOTEBOOK(tabs), gtk_notebook_get_nth_page(GTK_NOTEBOOK(tabs), 0), configtab);

    // Messages scrollable area
    messagesscroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_show(messagesscroll);
    gtk_container_add(GTK_CONTAINER(tabs), messagesscroll);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(messagesscroll), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);

    // Messages text area
    messagestext = gtk_text_view_new();
    gtk_widget_show(messagestext);
    gtk_container_add(GTK_CONTAINER(messagesscroll), messagestext);
    gtk_text_view_set_editable(GTK_TEXT_VIEW(messagestext), FALSE);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(messagestext), GTK_WRAP_WORD);
    gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(messagestext), FALSE);
    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(messagestext), 2);
    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(messagestext), 2);

    // Messages tab
    messagestab = gtk_label_new("Messages");
    gtk_widget_show(messagestab);
    gtk_notebook_set_tab_label(GTK_NOTEBOOK(tabs), gtk_notebook_get_nth_page(GTK_NOTEBOOK(tabs), 1), messagestab);

    // Dialogue box buttons layout
    buttons = gtk_hbutton_box_new();
    gtk_widget_show(buttons);
    gtk_box_pack_start(GTK_BOX(vlayout), buttons, FALSE, TRUE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(buttons), 3);
    gtk_button_box_set_layout(GTK_BUTTON_BOX(buttons), GTK_BUTTONBOX_END);

    // Cancel button
    cancelbutton = gtk_button_new();
    gtk_widget_show(cancelbutton);
    gtk_container_add(GTK_CONTAINER(buttons), cancelbutton);
    GTK_WIDGET_SET_FLAGS(cancelbutton, GTK_CAN_DEFAULT);
    gtk_widget_add_accelerator(cancelbutton, "grab_focus", accel_group,
                               GDK_C, GDK_MOD1_MASK,
                               GTK_ACCEL_VISIBLE);
    gtk_widget_add_accelerator(cancelbutton, "clicked", accel_group,
                               GDK_Escape, (GdkModifierType)0,
                               GTK_ACCEL_VISIBLE);

    cancelbuttonalign = gtk_alignment_new(0.5, 0.5, 0, 0);
    gtk_widget_show(cancelbuttonalign);
    gtk_container_add(GTK_CONTAINER(cancelbutton), cancelbuttonalign);

    cancelbuttonlayout = gtk_hbox_new(FALSE, 2);
    gtk_widget_show(cancelbuttonlayout);
    gtk_container_add(GTK_CONTAINER(cancelbuttonalign), cancelbuttonlayout);

    cancelbuttonicon = gtk_image_new_from_stock("gtk-cancel", GTK_ICON_SIZE_BUTTON);
    gtk_widget_show(cancelbuttonicon);
    gtk_box_pack_start(GTK_BOX(cancelbuttonlayout), cancelbuttonicon, FALSE, FALSE, 0);

    cancelbuttonlabel = gtk_label_new_with_mnemonic("_Cancel");
    gtk_widget_show(cancelbuttonlabel);
    gtk_box_pack_start(GTK_BOX(cancelbuttonlayout), cancelbuttonlabel, FALSE, FALSE, 0);

    // Start button
    startbutton = gtk_button_new();
    gtk_widget_show(startbutton);
    gtk_container_add(GTK_CONTAINER(buttons), startbutton);
    GTK_WIDGET_SET_FLAGS(startbutton, GTK_CAN_DEFAULT);
    gtk_widget_add_accelerator(startbutton, "grab_focus", accel_group,
                               GDK_S, GDK_MOD1_MASK,
                               GTK_ACCEL_VISIBLE);
    gtk_widget_add_accelerator(startbutton, "clicked", accel_group,
                               GDK_Return, (GdkModifierType)0,
                               GTK_ACCEL_VISIBLE);

    startbuttonalign = gtk_alignment_new(0.5, 0.5, 0, 0);
    gtk_widget_show(startbuttonalign);
    gtk_container_add(GTK_CONTAINER(startbutton), startbuttonalign);

    startbuttonlayout = gtk_hbox_new(FALSE, 2);
    gtk_widget_show(startbuttonlayout);
    gtk_container_add(GTK_CONTAINER(startbuttonalign), startbuttonlayout);

    startbuttonicon = gtk_image_new_from_stock("gtk-execute", GTK_ICON_SIZE_BUTTON);
    gtk_widget_show(startbuttonicon);
    gtk_box_pack_start(GTK_BOX(startbuttonlayout), startbuttonicon, FALSE, FALSE, 0);

    startbuttonlabel = gtk_label_new_with_mnemonic("_Start");
    gtk_widget_show(startbuttonlabel);
    gtk_box_pack_start(GTK_BOX(startbuttonlayout), startbuttonlabel, FALSE, FALSE, 0);

    // Wire up the signals
    g_signal_connect((gpointer) startwin, "delete_event",
                     G_CALLBACK(on_startwin_delete_event),
                     NULL);
    g_signal_connect((gpointer) vmode3dcombo, "changed",
                     G_CALLBACK(on_vmode3dcombo_changed),
                     NULL);
    g_signal_connect((gpointer) fullscreencheck, "toggled",
                     G_CALLBACK(on_fullscreencheck_toggled),
                     NULL);
    g_signal_connect((gpointer) alwaysshowcheck, "toggled",
                     G_CALLBACK(on_alwaysshowcheck_toggled),
                     NULL);
    g_signal_connect((gpointer) cancelbutton, "clicked",
                     G_CALLBACK(on_cancelbutton_clicked),
                     NULL);
    g_signal_connect((gpointer) startbutton, "clicked",
                     G_CALLBACK(on_startbutton_clicked),
                     NULL);

    // Associate labels with their controls
    gtk_label_set_mnemonic_widget(GTK_LABEL(vmode3dlabel), vmode3dcombo);

    /* Store pointers to all widgets, for use by lookup_widget(). */
    GLADE_HOOKUP_OBJECT_NO_REF(startwin, startwin, "startwin");
    GLADE_HOOKUP_OBJECT(startwin, hlayout, "hlayout");
    GLADE_HOOKUP_OBJECT(startwin, banner, "banner");
    GLADE_HOOKUP_OBJECT(startwin, vlayout, "vlayout");
    GLADE_HOOKUP_OBJECT(startwin, tabs, "tabs");
    GLADE_HOOKUP_OBJECT(startwin, configvlayout, "configvlayout");
    GLADE_HOOKUP_OBJECT(startwin, configlayout, "configlayout");
    GLADE_HOOKUP_OBJECT(startwin, vmode3dlabel, "vmode3dlabel");
    GLADE_HOOKUP_OBJECT(startwin, vmode3dcombo, "vmode3dcombo");
    GLADE_HOOKUP_OBJECT(startwin, fullscreencheck, "fullscreencheck");
    GLADE_HOOKUP_OBJECT(startwin, alwaysshowcheck, "alwaysshowcheck");
    GLADE_HOOKUP_OBJECT(startwin, configtab, "configtab");
    GLADE_HOOKUP_OBJECT(startwin, messagesscroll, "messagesscroll");
    GLADE_HOOKUP_OBJECT(startwin, messagestext, "messagestext");
    GLADE_HOOKUP_OBJECT(startwin, messagestab, "messagestab");
    GLADE_HOOKUP_OBJECT(startwin, buttons, "buttons");
    GLADE_HOOKUP_OBJECT(startwin, cancelbutton, "cancelbutton");
    GLADE_HOOKUP_OBJECT(startwin, cancelbuttonalign, "cancelbuttonalign");
    GLADE_HOOKUP_OBJECT(startwin, cancelbuttonlayout, "cancelbuttonlayout");
    GLADE_HOOKUP_OBJECT(startwin, cancelbuttonicon, "cancelbuttonicon");
    GLADE_HOOKUP_OBJECT(startwin, cancelbuttonlabel, "cancelbuttonlabel");
    GLADE_HOOKUP_OBJECT(startwin, startbutton, "startbutton");
    GLADE_HOOKUP_OBJECT(startwin, startbuttonalign, "startbuttonalign");
    GLADE_HOOKUP_OBJECT(startwin, startbuttonlayout, "startbuttonlayout");
    GLADE_HOOKUP_OBJECT(startwin, startbuttonicon, "startbuttonicon");
    GLADE_HOOKUP_OBJECT(startwin, startbuttonlabel, "startbuttonlabel");

    gtk_window_add_accel_group(GTK_WINDOW(startwin), accel_group);

    return startwin;
}

// -- BUILD ENTRY POINTS ------------------------------------------------------
bool startwin_isopen(void)
{
    return gtkenabled && startwin;
}

int startwin_open(void)
{
    if (!gtkenabled) return 0;
    if (startwin) return 1;

    startwin = create_window();
    if (startwin)
    {
        SetPage(TAB_MESSAGES);
        gtk_widget_show(startwin);
        gtk_main_iteration_do(FALSE);
        return 0;
    }
    return -1;
}

int startwin_close(void)
{
    if (!gtkenabled) return 0;
    if (!startwin) return 1;
    gtk_widget_destroy(startwin);
    startwin = NULL;
    return 0;
}

int startwin_puts(const char *str)
{
    GtkWidget *textview;
    GtkTextBuffer *textbuffer;
    GtkTextIter enditer;
    GtkTextMark *mark;
    const char *aptr, *bptr;

    if (!gtkenabled || !str) return 0;
    if (!startwin) return 1;
    if (!(textview = lookup_widget(startwin, "messagestext"))) return -1;
    textbuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));

    gtk_text_buffer_get_end_iter(textbuffer, &enditer);
    for (aptr = bptr = str; *aptr != 0; )
    {
        switch (*bptr)
        {
        case '\b':
            if (bptr > aptr)
                gtk_text_buffer_insert(textbuffer, &enditer, (const gchar *)aptr, (gint)(bptr-aptr)-1);
#if GTK_CHECK_VERSION(2,6,0)
            gtk_text_buffer_backspace(textbuffer, &enditer, FALSE, TRUE);
#else
            {
                GtkTextIter iter2 = enditer;
                gtk_text_iter_backward_cursor_position(&iter2);
                //FIXME: this seems be deleting one too many chars somewhere!
                if (!gtk_text_iter_equal(&iter2, &enditer))
                    gtk_text_buffer_delete_interactive(textbuffer, &iter2, &enditer, TRUE);
            }
#endif
            aptr = ++bptr;
            break;
        case 0:
            if (bptr > aptr)
                gtk_text_buffer_insert(textbuffer, &enditer, (const gchar *)aptr, (gint)(bptr-aptr));
            aptr = bptr;
            break;
        case '\r':  // FIXME
        default:
            bptr++;
            break;
        }
    }

    mark = gtk_text_buffer_create_mark(textbuffer, NULL, &enditer, 1);
    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(textview), mark, 0.0, FALSE, 0.0, 1.0);
    gtk_text_buffer_delete_mark(textbuffer, mark);

    return 0;
}

int startwin_settitle(const char *title)
{
    if (!gtkenabled) return 0;
    if (!startwin) return 1;
    gtk_window_set_title(GTK_WINDOW(startwin), title);
    return 0;
}

int startwin_idle(void *s)
{
    if (!gtkenabled) return 0;
    //if (!startwin) return 1;
    gtk_main_iteration_do(FALSE);
    return 0;
}

int startwin_run(void)
{
    if (!gtkenabled) return 0;
    if (!startwin) return 1;

    SetPage(TAB_CONFIG);

    settings.fullscreen = fullscreen;
    settings.xdim3d = xdim;
    settings.ydim3d = ydim;
    settings.bpp3d = bppgame;
    settings.forcesetup = forcesetup;
    PopulateForm();

    gtk_main();

    SetPage(TAB_MESSAGES);
    if (retval)
    {
        fullscreen = settings.fullscreen;
        xdim = settings.xdim3d;
        ydim = settings.ydim3d;
        bppgame = settings.bpp3d;
        forcesetup = settings.forcesetup;
    }

    return retval;
}


code:
#ifndef _WIN32
#error Only for Windows
#endif

#include "compat.h"
#include "build.h"
#include "editor.h"

#define NEED_WINDOWSX_H
#define NEED_COMMCTRL_H
#include "windows_inc.h"

#include "winlayer.h"

#include "startwin.game.h"

#define TAB_CONFIG 0
#define TAB_MESSAGES 1

static struct
{
    int fullscreen;
    int xdim3d, ydim3d, bpp3d;
    int forcesetup;
} settings;

static HWND startupdlg = NULL;
static HWND pages[2] = { NULL, NULL};
static int done = -1, mode = TAB_CONFIG;

static void PopulateForm(void)
{
    int i,j;
    char buf[64];
    int mode3d;
    HWND hwnd3d;

    hwnd3d = GetDlgItem(pages[TAB_CONFIG], IDC3DVMODE);

    mode3d = videoCheckMode(&settings.xdim3d, &settings.ydim3d, settings.bpp3d, settings.fullscreen, 1);
    if (mode3d < 0)
    {
        int cd[] = { 32, 24, 16, 15, 8, 0 };
        for (i=0; cd[i]; ) { if (cd[i] >= settings.bpp3d) i++; else break; }
        for (; cd[i]; i++)
        {
            mode3d = videoCheckMode(&settings.xdim3d, &settings.ydim3d, cd[i], settings.fullscreen, 1);
            if (mode3d < 0) continue;
            settings.bpp3d = cd[i];
            break;
        }
    }

    Button_SetCheck(GetDlgItem(pages[TAB_CONFIG], IDCFULLSCREEN), (settings.fullscreen ? BST_CHECKED : BST_UNCHECKED));
    Button_SetCheck(GetDlgItem(pages[TAB_CONFIG], IDCALWAYSSHOW), (settings.forcesetup ? BST_CHECKED : BST_UNCHECKED));

    ComboBox_ResetContent(hwnd3d);
    for (i=0; i<validmodecnt; i++)
    {
        if (validmode[i].fs != settings.fullscreen) continue;

        // all modes get added to the 3D mode list
        Bsprintf(buf, "%d x %d %dbpp", validmode[i].xdim, validmode[i].ydim, validmode[i].bpp);
        j = ComboBox_AddString(hwnd3d, buf);
        ComboBox_SetItemData(hwnd3d, j, i);
        if (i == mode3d) ComboBox_SetCurSel(hwnd3d, j);
    }
}

static INT_PTR CALLBACK ConfigPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDCFULLSCREEN:
            settings.fullscreen = !settings.fullscreen;
            PopulateForm();
            return TRUE;
        case IDC3DVMODE:
            if (HIWORD(wParam) == CBN_SELCHANGE)
            {
                int i;
                i = ComboBox_GetCurSel((HWND)lParam);
                if (i != CB_ERR) i = ComboBox_GetItemData((HWND)lParam, i);
                if (i != CB_ERR)
                {
                    settings.xdim3d = validmode[i].xdim;
                    settings.ydim3d = validmode[i].ydim;
                    settings.bpp3d  = validmode[i].bpp;
                }
            }
            return TRUE;
        case IDCALWAYSSHOW:
            settings.forcesetup = IsDlgButtonChecked(hwndDlg, IDCALWAYSSHOW) == BST_CHECKED;
            return TRUE;
        default: break;
        }
        break;
    default: break;
    }
    return FALSE;
}

static void SetPage(int n)
{
    HWND tab;
    int cur;
    tab = GetDlgItem(startupdlg, WIN_STARTWIN_TABCTL);
    cur = (int)SendMessage(tab, TCM_GETCURSEL,0,0);
    ShowWindow(pages[cur],SW_HIDE);
    SendMessage(tab, TCM_SETCURSEL, n, 0);
    ShowWindow(pages[n],SW_SHOW);
    mode = n;

    SetFocus(GetDlgItem(startupdlg, WIN_STARTWIN_TABCTL));
}

static void EnableConfig(int n)
{
    //EnableWindow(GetDlgItem(startupdlg, WIN_STARTWIN_CANCEL), n);
    EnableWindow(GetDlgItem(startupdlg, WIN_STARTWIN_START), n);
    EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCFULLSCREEN), n);
    EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDC3DVMODE), n);
}

static INT_PTR CALLBACK startup_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HBITMAP hbmp = NULL;
    HDC hdc;

    switch (uMsg)
    {
    case WM_INITDIALOG:
    {
        HWND hwnd;
        RECT r, rdlg, chrome, rtab, rcancel, rstart;
        int xoffset = 0, yoffset = 0;

        // Fetch the positions (in screen coordinates) of all the windows we need to tweak
        ZeroMemory(&chrome, sizeof(chrome));
        AdjustWindowRect(&chrome, GetWindowLongPtr(hwndDlg, GWL_STYLE), FALSE);
        GetWindowRect(hwndDlg, &rdlg);
        GetWindowRect(GetDlgItem(hwndDlg, WIN_STARTWIN_TABCTL), &rtab);
        GetWindowRect(GetDlgItem(hwndDlg, WIN_STARTWIN_CANCEL), &rcancel);
        GetWindowRect(GetDlgItem(hwndDlg, WIN_STARTWIN_START), &rstart);

        // Knock off the non-client area of the main dialogue to give just the client area
        rdlg.left -= chrome.left; rdlg.top -= chrome.top;
        rdlg.right -= chrome.right; rdlg.bottom -= chrome.bottom;

        // Translate them to client-relative coordinates wrt the main dialogue window
        rtab.right -= rtab.left - 1; rtab.bottom -= rtab.top - 1;
        rtab.left  -= rdlg.left; rtab.top -= rdlg.top;

        rcancel.right -= rcancel.left - 1; rcancel.bottom -= rcancel.top - 1;
        rcancel.left -= rdlg.left; rcancel.top -= rdlg.top;

        rstart.right -= rstart.left - 1; rstart.bottom -= rstart.top - 1;
        rstart.left -= rdlg.left; rstart.top -= rdlg.top;

        // And then convert the main dialogue coordinates to just width/length
        rdlg.right -= rdlg.left - 1; rdlg.bottom -= rdlg.top - 1;
        rdlg.left = 0; rdlg.top = 0;

        // Load the bitmap into the bitmap control and fetch its dimensions
        hbmp = LoadBitmap((HINSTANCE)win_gethinstance(), MAKEINTRESOURCE(RSRC_BMP));
        hwnd = GetDlgItem(hwndDlg,WIN_STARTWIN_BITMAP);
        SendMessage(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbmp);
        GetClientRect(hwnd, &r);
        xoffset = r.right;
        yoffset = r.bottom - rdlg.bottom;

        // Shift and resize the controls that require it
        rtab.left += xoffset; rtab.bottom += yoffset;
        rcancel.left += xoffset; rcancel.top += yoffset;
        rstart.left += xoffset; rstart.top += yoffset;
        rdlg.right += xoffset;
        rdlg.bottom += yoffset;

        // Move the controls to their new positions
        MoveWindow(GetDlgItem(hwndDlg, WIN_STARTWIN_TABCTL), rtab.left, rtab.top, rtab.right, rtab.bottom, FALSE);
        MoveWindow(GetDlgItem(hwndDlg, WIN_STARTWIN_CANCEL), rcancel.left, rcancel.top, rcancel.right, rcancel.bottom, FALSE);
        MoveWindow(GetDlgItem(hwndDlg, WIN_STARTWIN_START), rstart.left, rstart.top, rstart.right, rstart.bottom, FALSE);

        // Move the main dialogue to the centre of the screen
        hdc = GetDC(NULL);
        rdlg.left = (GetDeviceCaps(hdc, HORZRES) - rdlg.right) / 2;
        rdlg.top = (GetDeviceCaps(hdc, VERTRES) - rdlg.bottom) / 2;
        ReleaseDC(NULL, hdc);
        MoveWindow(hwndDlg, rdlg.left + chrome.left, rdlg.top + chrome.left,
                   rdlg.right + (-chrome.left+chrome.right), rdlg.bottom + (-chrome.top+chrome.bottom), TRUE);

        // Add tabs to the tab control
        {
            TCITEM tab;

            hwnd = GetDlgItem(hwndDlg, WIN_STARTWIN_TABCTL);

            ZeroMemory(&tab, sizeof(tab));
            tab.mask = TCIF_TEXT;
            static char textConfiguration[] = TEXT("Configuration");
            tab.pszText = textConfiguration;
            SendMessage(hwnd, TCM_INSERTITEM, (WPARAM)0, (LPARAM)&tab);
            tab.mask = TCIF_TEXT;
            static char textMessages[] = TEXT("Messages");
            tab.pszText = textMessages;
            SendMessage(hwnd, TCM_INSERTITEM, (WPARAM)1, (LPARAM)&tab);

            // Work out the position and size of the area inside the tab control for the pages
            ZeroMemory(&r, sizeof(r));
            GetClientRect(hwnd, &r);
            SendMessage(hwnd, TCM_ADJUSTRECT, FALSE, (LPARAM)&r);
            r.right -= r.left-1;
            r.bottom -= r.top-1;
            r.top += rtab.top;
            r.left += rtab.left;

            // Create the pages and position them in the tab control, but hide them
            pages[TAB_CONFIG] = CreateDialog((HINSTANCE)win_gethinstance(),
                                             MAKEINTRESOURCE(WIN_STARTWINPAGE_CONFIG), hwndDlg, ConfigPageProc);
            pages[TAB_MESSAGES] = GetDlgItem(hwndDlg, WIN_STARTWIN_MESSAGES);
            SetWindowPos(pages[TAB_CONFIG], hwnd,r.left,r.top,r.right,r.bottom,SWP_HIDEWINDOW);
            SetWindowPos(pages[TAB_MESSAGES], hwnd,r.left,r.top,r.right,r.bottom,SWP_HIDEWINDOW);

            // Tell the editfield acting as the console to exclude the width of the scrollbar
            GetClientRect(pages[TAB_MESSAGES],&r);
            r.right -= GetSystemMetrics(SM_CXVSCROLL)+4;
            r.left = r.top = 0;
            SendMessage(pages[TAB_MESSAGES], EM_SETRECTNP,0,(LPARAM)&r);

            SetFocus(GetDlgItem(hwndDlg, WIN_STARTWIN_START));
        }
        return FALSE;
    }

    case WM_NOTIFY:
    {
        LPNMHDR nmhdr = (LPNMHDR)lParam;
        int cur;
        if (nmhdr->idFrom != WIN_STARTWIN_TABCTL) break;
        cur = (int)SendMessage(nmhdr->hwndFrom, TCM_GETCURSEL,0,0);
        switch (nmhdr->code)
        {
        case TCN_SELCHANGING:
        {
            if (cur < 0 || !pages[cur]) break;
            ShowWindow(pages[cur],SW_HIDE);
            return TRUE;
        }
        case TCN_SELCHANGE:
        {
            if (cur < 0 || !pages[cur]) break;
            ShowWindow(pages[cur],SW_SHOW);
            return TRUE;
        }
        }
        break;
    }

    case WM_CLOSE:
        if (mode == TAB_CONFIG) done = 0;
        else quitevent++;
        return TRUE;

    case WM_DESTROY:
        if (hbmp)
        {
            DeleteObject(hbmp);
            hbmp = NULL;
        }

        if (pages[TAB_CONFIG])
        {
            DestroyWindow(pages[TAB_CONFIG]);
            pages[TAB_CONFIG] = NULL;
        }

        startupdlg = NULL;
        return TRUE;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case WIN_STARTWIN_CANCEL:
            if (mode == TAB_CONFIG) done = 0;
            else quitevent++;
            return TRUE;
        case WIN_STARTWIN_START: done = 1; return TRUE;
        }
        return FALSE;

    case WM_CTLCOLORSTATIC:
        if ((HWND)lParam == pages[TAB_MESSAGES])
            return (BOOL)(intptr_t)GetSysColorBrush(COLOR_WINDOW);
        break;

    default: break;
    }

    return FALSE;
}

bool startwin_isopen(void)
{
    return !!startupdlg;
}

int startwin_open(void)
{
    INITCOMMONCONTROLSEX icc;
    if (startupdlg) return 1;
    icc.dwSize = sizeof(icc);
    icc.dwICC = ICC_TAB_CLASSES;
    InitCommonControlsEx(&icc);
    startupdlg = CreateDialog((HINSTANCE)win_gethinstance(), MAKEINTRESOURCE(WIN_STARTWIN), NULL, startup_dlgproc);
    if (startupdlg)
    {
        SetPage(TAB_MESSAGES);
        EnableConfig(0);
        return 0;
    }
    return -1;
}

int startwin_close(void)
{
    if (!startupdlg) return 1;
    DestroyWindow(startupdlg);
    startupdlg = NULL;
    return 0;
}

int startwin_puts(const char *buf)
{
    const char *p = NULL, *q = NULL;
    char workbuf[1024];
    static int newline = 0;
    int curlen, linesbefore, linesafter;
    HWND edctl;
    int vis;

    if (!startupdlg) return 1;

    edctl = pages[TAB_MESSAGES];
    if (!edctl) return -1;

    vis = ((int)SendMessage(GetDlgItem(startupdlg, WIN_STARTWIN_TABCTL), TCM_GETCURSEL,0,0) == TAB_MESSAGES);

    if (vis) SendMessage(edctl, WM_SETREDRAW, FALSE,0);
    curlen = SendMessage(edctl, WM_GETTEXTLENGTH, 0,0);
    SendMessage(edctl, EM_SETSEL, (WPARAM)curlen, (LPARAM)curlen);
    linesbefore = SendMessage(edctl, EM_GETLINECOUNT, 0,0);
    p = buf;
    while (*p)
    {
        if (newline)
        {
            SendMessage(edctl, EM_REPLACESEL, 0, (LPARAM)"\r\n");
            newline = 0;
        }
        q = p;
        while (*q && *q != '\n') q++;
        memcpy(workbuf, p, q-p);
        if (*q == '\n')
        {
            if (!q[1])
            {
                newline = 1;
                workbuf[q-p] = 0;
            }
            else
            {
                workbuf[q-p] = '\r';
                workbuf[q-p+1] = '\n';
                workbuf[q-p+2] = 0;
            }
            p = q+1;
        }
        else
        {
            workbuf[q-p] = 0;
            p = q;
        }
        SendMessage(edctl, EM_REPLACESEL, 0, (LPARAM)workbuf);
    }
    linesafter = SendMessage(edctl, EM_GETLINECOUNT, 0,0);
    SendMessage(edctl, EM_LINESCROLL, 0, linesafter-linesbefore);
    if (vis) SendMessage(edctl, WM_SETREDRAW, TRUE,0);
    return 0;
}

int startwin_settitle(const char *str)
{
    if (!startupdlg) return 1;
    SetWindowText(startupdlg, str);
    return 0;
}

int startwin_idle(void *v)
{
    if (!startupdlg || !IsWindow(startupdlg)) return 0;
    if (IsDialogMessage(startupdlg, (MSG *)v)) return 1;
    return 0;
}

int startwin_run(void)
{
    MSG msg;
    if (!startupdlg) return 1;

    done = -1;

    SetPage(TAB_CONFIG);
    EnableConfig(1);

    settings.fullscreen = fullscreen;
    settings.xdim3d = xdim;
    settings.ydim3d = ydim;
    settings.bpp3d = bppgame;
    settings.forcesetup = forcesetup;
    PopulateForm();

    while (done < 0)
    {
        switch (GetMessage(&msg, NULL, 0,0))
        {
        case 0: done = 1; break;
        case -1: return -1;
        default:
            if (IsWindow(startupdlg) && IsDialogMessage(startupdlg, &msg)) break;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            break;
        }
    }

    SetPage(TAB_MESSAGES);
    EnableConfig(0);
    if (done)
    {
        fullscreen = settings.fullscreen;
        xdim = settings.xdim3d;
        ydim = settings.ydim3d;
        bppgame = settings.bpp3d;
        forcesetup = settings.forcesetup;
    }

    return done;
}


code:
// resource ids
#define WIN_STARTWIN        1000
#define WIN_STARTWINPAGE_CONFIG 2000
#define WIN_STARTWIN_BITMAP 100 // banner bitmap
#define WIN_STARTWIN_TABCTL 101
#define WIN_STARTWIN_CANCEL IDCANCEL
#define WIN_STARTWIN_START  IDOK

#define WIN_STARTWIN_MESSAGES   104 // output list box

#define RSRC_ICON       100
#define RSRC_BMP        200

// config page
#define IDCFULLSCREEN   100
#define IDC3DVMODE  101
#define IDCALWAYSSHOW   102


code:
/*
 * libsmackerdec - Smacker video decoder
 * Copyright (C) 2011 Barry Duncan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifndef _SmackerBitReader_h_
#define _SmackerBitReader_h_

#include <stdint.h>
#include "FileStream.h"

namespace SmackerCommon {

class BitReader
{
	public:
		BitReader(SmackerCommon::FileStream &file, uint32_t size);
		~BitReader();
		uint32_t GetBit();
		uint32_t GetBits(uint32_t n);
		void SkipBits(uint32_t n);

		uint32_t GetSize();
		uint32_t GetPosition();

	private:
		uint32_t totalSize;
		uint32_t currentOffset;
		uint32_t bytesRead;

		SmackerCommon::FileStream *file;

		uint8_t *cache;

		void FillCache();
};

} // close namespace SmackerCommon

#endif


code:
/*
 * libsmackerdec - Smacker video decoder
 * Copyright (C) 2011 Barry Duncan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifndef _SmackerFileStream_h_
#define _SmackerFileStream_h_

#include "vfs.h"
#include "compat.h"
#include <stdint.h>

namespace SmackerCommon {

class FileStream
{
	public:

		bool Open(const char *fileName);
		bool Is_Open();
		void Close();

		int32_t ReadBytes(uint8_t *data, uint32_t nBytes);

		uint32_t ReadUint32LE();
		uint32_t ReadUint32BE();

		uint16_t ReadUint16LE();
		uint16_t ReadUint16BE();

		uint8_t ReadByte();

		enum SeekDirection{
			kSeekCurrent = 0,
			kSeekStart   = 1,
			kSeekEnd     = 2
		};

        int32_t Seek(int32_t offset, SeekDirection = kSeekStart);
        int32_t Skip(int32_t offset);

		int32_t GetPosition();

	private:
		int file;
};

} // close namespace SmackerCommon

#endif


code:
/*
 * libsmackerdec - Smacker video decoder
 * Copyright (C) 2011 Barry Duncan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifndef _SmackerHuffmanVLC_h_
#define _SmackerHuffmanVLC_h_

#include <stdint.h>
#include "BitReader.h"
#include <vector>

namespace SmackerCommon {

struct VLC
{
    uint32_t symbol;
    uint32_t code;
};

typedef std::vector< std::vector<VLC> > VLCtable;

uint16_t VLC_GetCodeBits(BitReader &bits, VLCtable &table);
void     VLC_InitTable  (VLCtable &table, uint32_t maxLength, uint32_t size, int *lengths, uint32_t *bits);
uint32_t VLC_GetSize    (VLCtable &table);

} // close namespace SmackerCommon

#endif


code:
/*
 * libsmackerdec - Smacker video decoder
 * Copyright (C) 2011 Barry Duncan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifndef _SmackerLogError_h_
#define _SmackerLogError_h_

//#include <string>

namespace SmackerCommon {

//void LogError(const std::string &error);

} // close namespace SmackerCommon

#endif


code:
/*
 * libsmackerdec - Smacker video decoder
 * Copyright (C) 2011 Barry Duncan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/* This code is based on smacker.c from the FFmpeg project which can be obtained from http://www.ffmpeg.org/
 * below is the license from smacker.c
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * Smacker decoder
 * Copyright (c) 2006 Konstantin Shishkov
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef _SmackerDecoder_h_
#define _SmackerDecoder_h_

#include <stdint.h>
#include "FileStream.h"
#include "BitReader.h"
#include <vector>

// exportable interface
struct SmackerHandle
{
	bool isValid;
	int instanceIndex;
};

struct SmackerAudioInfo
{
	uint32_t sampleRate;
	uint8_t  nChannels;
	uint8_t  bitsPerSample;

	uint32_t idealBufferSize;
};

SmackerHandle     Smacker_Open                 (const char *fileName);
void              Smacker_Close                (SmackerHandle &handle);
uint32_t          Smacker_GetNumAudioTracks    (SmackerHandle &handle);
SmackerAudioInfo  Smacker_GetAudioTrackDetails (SmackerHandle &handle, uint32_t trackIndex);
uint32_t          Smacker_GetAudioData         (SmackerHandle &handle, uint32_t trackIndex, int16_t *data);
uint32_t          Smacker_GetNumFrames         (SmackerHandle &handle);
void              Smacker_GetFrameSize         (SmackerHandle &handle, uint32_t &width, uint32_t &height);
uint32_t          Smacker_GetCurrentFrameNum   (SmackerHandle &handle);
uint32_t          Smacker_GetNextFrame         (SmackerHandle &handle);
float             Smacker_GetFrameRate         (SmackerHandle &handle);
void              Smacker_GetPalette           (SmackerHandle &handle, uint8_t *palette);
void              Smacker_GetFrame             (SmackerHandle &handle, uint8_t *frame);
void              Smacker_GotoFrame            (SmackerHandle &handle, uint32_t frameNum);

const int kMaxAudioTracks = 7;

// forward declare
struct HuffContext;
struct DBCtx;

struct SmackerAudioTrack
{
	uint32_t sizeInBytes;
	uint32_t flags;
	uint32_t sampleRate;
	uint8_t  nChannels;
	uint8_t  bitsPerSample;
//	int	     compressionType;

	uint8_t  *buffer;
	uint32_t bufferSize;

	uint32_t bytesReadThisFrame;
};

class SmackerDecoder
{
	public:
		uint32_t frameWidth;
		uint32_t frameHeight;

		SmackerDecoder();
		~SmackerDecoder();

		bool Open(const char *fileName);
		void GetPalette(uint8_t *palette);
		void GetFrame(uint8_t *frame);

		SmackerAudioInfo GetAudioTrackDetails(uint32_t trackIndex);
		uint32_t GetAudioData(uint32_t trackIndex, int16_t *audioBuffer);
		uint32_t GetNumFrames();
		uint32_t GetCurrentFrameNum();
		float GetFrameRate();
		void GetNextFrame();
		void GotoFrame(uint32_t frameNum);

	private:
		SmackerCommon::FileStream file;
		char signature[4];

		// video related members
		uint32_t nFrames;
		uint32_t fps; // frames per second

		uint8_t palette[768];
		uint8_t *picture;

		bool isVer4;

		SmackerAudioTrack audioTracks[kMaxAudioTracks];

		uint32_t treeSize;
		uint32_t mMapSize, MClrSize, fullSize, typeSize;

		std::vector<int> mmap_tbl;
		std::vector<int> mclr_tbl;
		std::vector<int> full_tbl;
		std::vector<int> type_tbl;

		int mmap_last[3], mclr_last[3], full_last[3], type_last[3];

		std::vector<uint32_t> frameSizes;
		std::vector<uint8_t> frameFlags;

		uint32_t currentFrame;
		int32_t nextPos;
        int32_t firstFrameFilePos;

		bool DecodeHeaderTrees();
		int DecodeHeaderTree(SmackerCommon::BitReader &bits, std::vector<int> &recodes, int *last, int size);
		int DecodeTree(SmackerCommon::BitReader &bits, HuffContext *hc, uint32_t prefix, int length);
		int DecodeBigTree(SmackerCommon::BitReader &bits, HuffContext *hc, DBCtx *ctx);
		int GetCode(SmackerCommon::BitReader &bits, std::vector<int> &recode, int *last);
		int ReadPacket();
		int DecodeFrame(uint32_t frameSize);
		void GetFrameSize(uint32_t &width, uint32_t &height);
		int DecodeAudio(uint32_t size, SmackerAudioTrack &track);
};

#endif


code:
/*
 * libsmackerdec - Smacker video decoder
 * Copyright (C) 2011 Barry Duncan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "BitReader.h"
#include <assert.h>

namespace SmackerCommon {

BitReader::BitReader(SmackerCommon::FileStream &file, uint32_t size)
{
	this->file = &file;
	this->totalSize = size;
	this->currentOffset = 0;
	this->bytesRead = 0;

    this->cache = (uint8_t*)Xmalloc(size);
    file.ReadBytes(this->cache, size);
}

BitReader::~BitReader()
{
	Xfree(this->cache);
}

void BitReader::FillCache()
{
}

uint32_t BitReader::GetSize()
{
	return totalSize * 8;
}

uint32_t BitReader::GetPosition()
{
	return currentOffset;
}

uint32_t BitReader::GetBit()
{
	uint32_t ret = (cache[currentOffset>>3]>>(currentOffset&7))&1;
    currentOffset++;
	return ret;
}

uint32_t BitReader::GetBits(uint32_t n)
{
	uint32_t ret = 0;

	int bitsTodo = n;

	uint32_t theShift = 0;

	while (bitsTodo)
	{
		uint32_t bit = GetBit();
		bit <<= theShift;

		theShift++;

		ret |= bit;

		bitsTodo--;
	}

	return ret;
}

void BitReader::SkipBits(uint32_t n)
{
	GetBits(n);
}

} // close namespace SmackerCommon


code:
/*
 * libsmackerdec - Smacker video decoder
 * Copyright (C) 2011 Barry Duncan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "FileStream.h"
#include <stdlib.h>

namespace SmackerCommon {

bool FileStream::Open(const char *fileName)
{
    file = kopen4loadfrommod(fileName, 0);
	if (file == -1)
	{
		// log error
		return false;
	}

	return true;
}

bool FileStream::Is_Open()
{
	return file != -1;
}

void FileStream::Close()
{
    kclose(file);
    file = -1;
}

int32_t FileStream::ReadBytes(uint8_t *data, uint32_t nBytes)
{
	uint32_t nCount = (uint32_t)kread(file, data, static_cast<int32_t>(nBytes));

	if (nCount != nBytes)
	{
		return 0;
	}

	return (int32_t)nCount;
}

uint32_t FileStream::ReadUint32LE()
{
	uint32_t value;
	kread(file, &value, 4);
	return B_LITTLE32(value);
}

uint32_t FileStream::ReadUint32BE()
{
	uint32_t value;
    kread(file, &value, 4);
	return B_BIG32(value);
}

uint16_t FileStream::ReadUint16LE()
{
	uint16_t value;
    kread(file, &value, 2);
	return B_LITTLE16(value);
}

uint16_t FileStream::ReadUint16BE()
{
	uint16_t value;
    kread(file, &value, 2);
	return B_BIG16(value);
}

uint8_t FileStream::ReadByte()
{
	uint8_t value;
    kread(file, &value, 1);
	return value;
}

int32_t FileStream::Seek(int32_t offset, SeekDirection direction)
{
    int32_t nStatus = -1;
	if (kSeekStart == direction) {
        nStatus = klseek(file, offset, SEEK_SET);
	}
	else if (kSeekCurrent == direction) {
        nStatus = klseek(file, offset, SEEK_CUR);
	}
    else if (kSeekEnd == direction) {
        nStatus = klseek(file, offset, SEEK_END);
    }

    return nStatus;
}

int32_t FileStream::Skip(int32_t offset)
{
	return Seek(offset, kSeekCurrent);
}

int32_t FileStream::GetPosition()
{
    return ktell(file);
}

} // close namespace SmackerCommon


code:
/*
 * libsmackerdec - Smacker video decoder
 * Copyright (C) 2011 Barry Duncan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <HuffmanVLC.h>

namespace SmackerCommon {

uint16_t VLC_GetCodeBits(BitReader &bits, VLCtable &table)
{
	uint32_t codeBits = 0;

	// search each length array
	for (uint32_t i = 0; i < table.size(); i++)
	{
		// get and add a new bit to codeBits
		uint32_t theBit = bits.GetBit() << i;
		codeBits |= theBit;

		// search for a code match
		for (uint32_t j = 0; j < table[i].size(); j++)
		{
			if (codeBits == table[i][j].code)
			{
				return table[i][j].symbol;
			}
		}
	}

	// shouldn't get here..
	return 0;
}

void VLC_InitTable(VLCtable &table, uint32_t maxLength, uint32_t size, int *lengths, uint32_t *bits)
{
	table.resize(maxLength);

	for (uint32_t i = 0; i < size; i++)
	{
		VLC newCode;
		newCode.symbol = i;
		newCode.code   = bits[i];

		uint32_t codeLength = lengths[i];

		if (codeLength)
			table[codeLength - 1].push_back(newCode);
	}
}

uint32_t VLC_GetSize(VLCtable &table)
{
	return table.size();
}

} // close namespace SmackerCommon


code:
/*
 * libsmackerdec - Smacker video decoder
 * Copyright (C) 2011 Barry Duncan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "LogError.h"

namespace SmackerCommon {

#if 0
static std::string LastError;

void LogError(const std::string &error)
{
	LastError = error;
}
#endif

} // close namespace SmackerCommon