#include "stdafx.h"

#include <stdio.h>

#include "UI.h"

#include "Objects.h"
#include "Dispatch.h"
#include "CSB.h"
#include "Data.h"


void MD5Init(void);
void MD5Update(ui8 *data, ui32 len);
void MD5Final(ui8 *digest);

extern ui32 numRandomCalls;
extern char *PlayfileName;
extern char szCSBVersion[];
extern i32 VBLMultiplier;
extern bool fullscreenRequested;
extern bool fullscreenActive;

#define MaxTraceFiles 100
i16   TraceFile  = -1;
char *folderName = NULL;
char *folderParentName = NULL;
char *root = NULL;
VIDEOMODE videoMode;

ui8 *dataTypeMap = NULL;
ui16 *dataIndexMap = NULL;
i32 dataMapLength = 0;
ui32 graphicSignature1=0, graphicSignature2=0;
ui32 expectedGraphicSignature1=0, expectedGraphicSignature2=0;
ui32 CSBgraphicSignature1=0, CSBgraphicSignature2=0;
ui32 expectedCSBgraphicSignature1=0, expectedCSBgraphicSignature2=0;
bool firstGraphicsWarning = true;
bool firstCSBgraphicsWarning = true;
ui32 dungeonSignature1=0, dungeonSignature2=0;
bool firstDungeonWarning = true;
bool checkDungeonSignature;
ui32 versionSignature = 0;
bool firstVersionWarning = true;
ui32 spellFilterLocation = 0;
ui32 cellflagArraySize;
bool disableSaves = false;
bool bigActuators = false;
OVERLAYDATA currentOverlay;
TEMPORARY_FILE CSBgraphicsFile;
bool overlayActive = false;
EXPOOL expool;

DSAINDEX DSAIndex;
ui16 DSALevelIndex[64][32];

void CloseTraceFile(void)
{
  CLOSE(TraceFile);
  TraceFile = -1;
}

void SmartDiscardTrace(FILE *f);

bool OpenTraceFile(void)
{//Return true if file successfully.
  i32 i;
  bool result = false;
  for (i=0; i<MaxTraceFiles; i++)
  {
    char filename[100];
    sprintf(filename,"TRACE%03d.txt",i);
    TraceFile = OPEN(filename,"r");
    if (TraceFile < 0)
    {
      char  msg[100];;
      TraceFile = CREATE(filename,"w", true);
      if (TraceFile < 0)
      {
        sprintf(msg,"Cannot open file %s for Trace",filename);
        UI_MessageBox(msg,
             "",MESSAGE_OK);
      }
      else
      {
        result = true;
      };
      break;
    }
    else
    {
      CLOSE (TraceFile);
    };
  };
  if (i==MaxTraceFiles)
      UI_MessageBox("Too many Trace files",
              "",MESSAGE_OK);
  return result;
};

i32 millisecondsPerVBL = 17;
i32 VBLperTimer = 10;
bool TimerTraceActive=false;
bool AttackTraceActive = false;
bool AITraceActive = false;
i32 GameMode=0;
//i32 clockSpeed = 10;
bool BeginRecordOK = false;
bool RepeatGame = false;
bool GameIsComplete;
bool ItemsRemainingOK = false;
bool gameFrozen = false;
i32 DrawViewportCount = 0;
DOESNOTEXIST DoesNotExist;
bool DiskMenuNeeded;
i32  MostRecentlyAdjustedSkills[2] = {0,0}; //Just for Player display
i32  LatestSkillValues[2] = {0,0}; //Just for Player display.
i32  AdjustedSkillNumber = 0;
bool AutoEnlarge = false;
bool ThreeDMovements = false; //For 3DMaze
bool GravityGame = false;     //For 3DMaze
unsigned char cipherkey[64];
unsigned char *encipheredDataFile = NULL;//cipherkey;
bool simpleEncipher = false;
i32  nextGravityMoveTime;
char moveHistory[4] = {0,1,2,0};
bool gravityMove = false;
i32  ExtendedFeaturesSize = 0;
char ExtendedFeaturesVersion = '@';
bool NoSound = false;
bool NoSleep = false;
bool DM_rules = false;
bool indirectText = false;
i32  totalMoveCount;
bool version90Compatible = false;
i32 RememberToPutObjectInHand = -1;
i32 segSize[5]   = {0x02,0x00,0x00,0x00,0x00};
i32 segSrcX[5]   = {0x00,0x00,0x00,0x00,0x00};   
i32 segSrcY[5]   = {0x21,0x00,0x00,0x00,0x00};   
i32 segWidth[5]  = {0xe0,0x02,0x02,0x02,0x02};   
i32 segHeight[5] = {0x88,0x02,0x02,0x00,0x02};   
i32 segX[5]      = {0x00,0x00,0xe0,0x00,0x20};   
i32 segY[5]      = {0x00,0x00,0x21,0xa9,0x31};
i32 *videoSegSize   = segSize;
i32 *videoSegSrcX   = segSrcX;
i32 *videoSegSrcY   = segSrcY;
i32 *videoSegWidth  = segWidth;
i32 *videoSegHeight = segHeight;
i32 *videoSegX      = segX;
i32 *videoSegY      = segY;
char *gameInfo = NULL;

SPEEDTABLE speedTable[SPEED_NUMSPEED];
SPEEDS gameSpeed = SPEED_NORMAL;
VOLUMETABLE volumeTable[VOLUME_NUMVOLUME];
VOLUMES gameVolume = VOLUME_FULL;
bool playerClock = false;
bool extraTicks = true;
bool usingDirectX = false;

//Locations in Atari screen pixel units.
// Viewport, Portraits, Spells&Commands, Text
void TranslateFullscreen(i32 a, i32 b, i32& x, i32& y)
{//Translate physical pixel units on actual screen.
 // to pixel units on Atari screen
    i32 i, left, top, right, bottom;
  for (i=0; i<4; i++)
  {
    if (videoSegSize[i] == 0) continue;
    i32 size = videoSegSize[i];
    left   = videoSegSrcX[i];
    top    = videoSegSrcY[i];
    right  = left + videoSegWidth[i]-1;
    bottom = top  + videoSegHeight[i]-1;
    if (a < videoSegX[i]) continue;
    if (a >= videoSegX[i] + size*(videoSegWidth[i])) continue;
    if (b < videoSegY[i])    continue;
    if (b >= videoSegY[i] + size*(videoSegHeight[i])) continue;
    x = (a - videoSegX[i] + size/2)/size + left;
    y = (b - videoSegY[i] + size/2)/size + top;
    if (x > right-1) x = right-1;
    if (y > bottom-1) y = bottom-1;
    return;
  };
}

bool GetVideoRectangle(i32 i, RECT *rect)
{
  i32 j;
  if (i>3) return false;
  for (j=0; j<4; j++)
  {
    if (videoSegSize[j] == 0) continue;
    if (i == 0)
    {
      rect->top = videoSegY[j];
      rect->left = videoSegX[j];
      rect->right = videoSegX[j] + videoSegSize[j]*videoSegWidth[j];
      rect->bottom = videoSegY[j] + videoSegSize[j]*videoSegHeight[j];
      return true;
    };
    i--;
  };
  return false;
}

SDLIST::~SDLIST(void)
{
  SD *temp;
  while (psd != NULL)
  {
    temp = psd;
    psd = psd->pnext;
    delete temp;
  };
}

SD *SDLIST::newSD(char *name,
                  i32 minimum,
                  float level,
                  float y_intercept,
                  float slope)
{
  SD *temp;
  if (strlen(name) > 15) return NULL;
  temp = new SD;
  temp->pnext = psd;
  psd = temp;
  strcpy(temp->name,name);
  temp->minimum = minimum;
  temp->level = level;
  temp->y_intercept = y_intercept;
  temp->slope = slope;
  return temp;
}

SD *SDLIST::First(void)
{
  return psd;
}

SD *SDLIST::Next(SD *ent)
{
  return ent->pnext;
}

i32 AddSD(char *name,
          i32 min,
          float level,
          float y_intercept,
          float slope)
{
  if(sdList.newSD(name,min,level,y_intercept,slope)==NULL) return 1;
  return 0;
}

// The list of Smart Discards from the config.txt file
SDLIST sdList;
SDENT sdTable[199];




DATABASES db;

//*********************************************************
// Object Database class member functions
//*********************************************************

i32 dbEntrySizes[16] =
{
  sizeof (DB0),
  sizeof (DB1),
  sizeof (DB2),
  sizeof (DB3),
  sizeof (DB4),
  sizeof (DB5),
  sizeof (DB6),
  sizeof (DB7),
  sizeof (DB8),
  sizeof (DB9),
  sizeof (DB10),
  sizeof (DB11),
  sizeof (DB12),
  sizeof (DB13),
  sizeof (DB14),
  sizeof (DB15)
};

char *dbNames[16] =
{
  "Doors",        //0
  "Teleporters",  //1
  "Scrolls",      //2
  "Actuators",    //3
  "Monsters",     //4
  "Weapons",      //5
  "Clothes",      //6
  "Scrolls",      //7
  "potions",      //8
  "chests",       //9
  "food, misc",   //10
  "??????",       //11
  "??????",       //12
  "??????",       //13
  "Missiles",     //14
  "Clouds"        //15
};

DATABASES::DATABASES (void)
{
  i32 i;
  for (i=0; i<16; i++)
  {
    m_Address[i] = NULL;
    m_numEnt[i] = 0;
  };
}

DATABASES::~DATABASES (void)
{
  i32 i;
  for (i=0; i<16; i++) DeAllocate(i);
}

RN::RN(ui32,ui32,ui32)
{
  NotImplemented();
}

RN::RN(DBTYPE dbType, ui32 dbIndex)
{
  i32 emptyIndexEntry;
  i32 i, newDML;
  DBCOMMON *pDBC;
  emptyIndexEntry = -1;
  for (i=dataMapLength-1; i>0; i--)
  {
    if (dataIndexMap[i] == 0xffff)
    {
      emptyIndexEntry = i;
      continue;
    };
    if ( (dataTypeMap[i] & 15) != dbType) continue;
    if (dataIndexMap[i] == dbIndex)
    {
      indirectIndex = (ui16)i;
      return; //Already exists
    };
    pDBC = db.GetCommonAddress(dbType, dataIndexMap[i]);
    if (pDBC->link() == RNnul)
    {
      emptyIndexEntry = i;
      continue;
    };
  };
  if (emptyIndexEntry == -1)
  {
    if (dataMapLength > 64000)
    {
      UI_MessageBox("Object Indirect Index Overflow","Error",MB_OK);
      die(0x22dde5);
    };
    newDML = 32*(dataMapLength/32) + 33;
    dataTypeMap = (ui8 *)UI_realloc(dataTypeMap, newDML, MALLOC062);
    dataIndexMap = (ui16 *)UI_realloc(dataIndexMap, 2*newDML, MALLOC063);
    if ( (dataTypeMap == NULL) || (dataIndexMap == NULL))
    {
      UI_MessageBox("Cannot allocate memory for object index",
                    "Error", MB_OK);
      die(0xa1c445);
    };
    emptyIndexEntry = dataMapLength;
    dataMapLength++;
  };
  indirectIndex = (ui16)emptyIndexEntry;
  dataIndexMap[emptyIndexEntry] = (ui16)dbIndex;
  dataTypeMap[emptyIndexEntry] = (ui8)dbType;
  return;
}

RN::RN(RNVAL i)
{
  if (   (i < 0xff80)
      && (i != 0))
  {
    UI_MessageBox("Illegal RNVAL","Sys Error",MB_OK);
    die(0xace321);
  };
  indirectIndex = (ui16)i;
};


bool RN::checkIndirectIndex(void) const
{
  if (    (indirectIndex < dataMapLength)
       && (indirectIndex > 0) )return true;
  UI_MessageBox("Illegal object Indirect Index","error",MB_OK);
#ifdef _DEBUG
  die(0x6ccee);
#endif
  return false;
}

bool RN::checkIndirectIndex(ui32 i) const
{
  if (    (i < (unsigned)dataMapLength)
       && (i > 0) )return true;
  return false;
}


void RN::CreateSpell(i32 n)
{
  ASSERT(n<128);
  indirectIndex = (ui16)(0xff80 + n);
}

ui8 RN::GetSpellType(void)
{
  if (indirectIndex < 0xff80)
  {
    UI_MessageBox("Illegal Spell object","Error",MB_OK);
    die(0xc8d9a3);
  };
  return (ui8)(indirectIndex & 0x7f);
}

bool RN::NonMaterial(void)
{
  DBTYPE dbType;
  DB4 *pDB_4;
  dbType = this->dbType();
  ASSERT(dbType == dbMONSTER);
  if (dbType != dbMONSTER) return false;
  pDB_4 = GetRecordAddressDB4(*this);
  ASSERT(pDB_4->monsterType() < 27);
  return d.MonsterDescriptor[pDB_4->monsterType()].nonMaterial();
}

bool RN::Levitating(void)
{
  DBTYPE dbType;
  DB4 *pDB_4;
  dbType = this->dbType();
  ASSERT(dbType == dbMONSTER);
  if (dbType != dbMONSTER) return false;
  pDB_4 = GetRecordAddressDB4(*this);
  ASSERT(pDB_4->monsterType() < 27);
  return d.MonsterDescriptor[pDB_4->monsterType()].levitating();
}

ui8 RN::VerticalSize(void)
{
  DBTYPE dbType;
  DB4 *pDB_4;
  dbType = this->dbType();
  ASSERT(dbType == dbMONSTER);
  if (dbType != dbMONSTER) return 1;
  pDB_4 = GetRecordAddressDB4(*this);
  ASSERT(pDB_4->monsterType() < 27);
  return d.MonsterDescriptor[pDB_4->monsterType()].verticalSize();
}

const RN DBCOMMON::link(void)
{
  return m_link;
}

void DBCOMMON::link(RN rn)
{
  m_link = rn;
}

DBCOMMON *DATABASES::GetCommonAddress(RN object)
{
  i32 dbNum, index;
  dbNum = object.dbNum();
  index = object.idx();
  ASSERT(index < m_numEnt[dbNum]);
  return (DBCOMMON *)
       (((char *)m_Address[dbNum]) + index * dbEntrySizes[dbNum]);
}

DBCOMMON *DATABASES::GetCommonAddress(DBTYPE dbType, i32 i)
{
  i32 dbNum, index;
  dbNum = dbType;
  index = i;
  ASSERT(index < m_numEnt[dbNum]);
  return (DBCOMMON *)
       (((char *)m_Address[dbNum]) + index * dbEntrySizes[dbType]);
}

void DATABASES::SetAddress(DBTYPE dbType, DBCOMMON *m)
{
  i32 dbNum;
  dbNum = dbType;
  m_Address[dbNum] = m;
}

i32 DATABASES::NumEntry(i32 dbNum)
{
  return m_numEnt[dbNum];
}

void DATABASES::NumEntry(i32 dbNum, i32 n)
{
  m_numEnt[dbNum] = n;
  d.dungeonDatIndex->DBSize(dbNum, (ui16)n);
}

void DATABASES::Allocate(i32 dbNum, i32 numEnt)
{
  DeAllocate(dbNum); // In case something is there already.
  ASSERT((dbNum & 0xfffffff0) == 0);
  dbNum &= 15;
  ASSERT(numEnt <= ((dbNum==15) ? 768 : 8000));
  m_numEnt[dbNum] = numEnt;
  if (numEnt > 0)
  {
    m_Address[dbNum] = (DBCOMMON *)
             UI_malloc(dbEntrySizes[dbNum] * numEnt, MALLOC047);
    if (m_Address[dbNum]==NULL)
    {
      UI_MessageBox("Cannot allocate memory",NULL,MESSAGE_OK);
      die (0xbad);
    };
    pnt p = (pnt)m_Address[dbNum];
    for (i32 i=0; i<m_numEnt[dbNum]; i++)
    {
      *(RN *)p = RNnul; // Mark entry as empty
      p += dbEntrySizes[dbNum];
    }
  }
  else
  {
    m_Address[dbNum] = NULL;
  };
}

void DATABASES::DeAllocate(i32 dbNum)
{
  ASSERT((dbNum & 0xfffffff0) == 0);
  dbNum &= 15;
  if (m_Address[dbNum] != NULL) UI_free(m_Address[dbNum]);
  m_Address[dbNum] = NULL;
  m_numEnt[dbNum] = 0;
}

i32 DATABASES::Enlarge(i32 dbNum)
{ // Returns index of newly available entry or -1
  i32 max, iNew, i;
  i32 result;
  i32 newSize;
  DBCOMMON *pC;
  char msg[1000];
  ASSERT( (dbNum & 0xfffffff0) == 0);
  if (m_noEnlarge & (1 << dbNum)) return RNnul;
  max = (dbNum==15) ? 1000 : 8000;
  if (m_numEnt[dbNum] >= max)
  {
    return RNnul;//Already as big as possible.
  };

  sprintf(msg,
             "Database Number %d (containing %s)\n"
             "has become full.  There is a rather arbitrary\n"
             "limit of %d items in this database.  The\n"
             "original Atari program handled this by\n"
             "discarding items at random.  It is possible\n"
             "to increase the size to %d but of course this\n"
             "will be 'non-standard'.\n"
             "\n"
             "Do you want me to enlarge the database?"
             ,dbNum
             ,dbNames[dbNum]
             ,m_numEnt[dbNum]
             ,max);
        AutoEnlarge = true; //***************** PRS 26 Octover 2002
  if (AutoEnlarge) result = MESSAGE_IDYES;
  else result = UI_MessageBox(msg,NULL,MESSAGE_YESNO);
  if (result == MESSAGE_IDNO)
  {
    m_noEnlarge |= 1 << dbNum;
    return RNnul;
  };
  newSize = m_numEnt[dbNum] + 100;
  m_Address[dbNum] =
     (DBCOMMON *)UI_realloc(m_Address[dbNum],
         newSize*dbEntrySizes[dbNum],
         MALLOC064);
  if (m_Address[dbNum] == NULL)
  {
    UI_MessageBox("Cannot allocate memory",NULL,MESSAGE_OK);
    die(0);
  };
  //d.dungeonDatIndex[dbType+6] = sw(max);
  d.dungeonDatIndex->DBSize(dbNum, uw(newSize));
  // We need to clear all the entries.
  iNew = m_numEnt[dbNum]; // First entry to clear
  m_numEnt[dbNum] = newSize;
  for (i=iNew; i<newSize; i++)
  {
    //pC = GetCommonAddress(RN(0,dbNum,i));
    pC = (DBCOMMON *)((pnt)m_Address[dbNum] + i*dbEntrySizes[dbNum]);
    memset((char *)pC,0,dbEntrySizes[dbNum]);
    pC->link(RNnul);
  };
  //pC = GetCommonAddress(RN(0,dbNum,iNew));
  pC = (DBCOMMON *)((pnt)m_Address[dbNum] + iNew*dbEntrySizes[dbNum]);
  pC->link(RNeof);
  return iNew;
}



DBTYPE DATABASES::GetDBType(DBCOMMON *pDB)
{
  i32 dbNum;
  for (dbNum=0; dbNum<16; dbNum++)
  {
    if (  (pDB >= m_Address[dbNum])
        &&(pDB < m_Address[dbNum] + dbEntrySizes[dbNum]/sizeof(*m_Address[0]) * m_numEnt[dbNum])) return (DBTYPE)dbNum;
  };
  return (DBTYPE)dbNum;
}

bool DATABASES::IsDBType(DBCOMMON *pDB, DBTYPE type)
{
  return GetDBType(pDB) == type;
}

i32 DATABASES::GetDBIndex(DBCOMMON *pDB, DBTYPE dbType)
{
  return ((char *)pDB - (char *)m_Address[dbType])/dbEntrySizes[dbType];
}

RN DATABASES::AllocateSegment(i32 /*dbNum*/, DBTYPE /*dbType*/)
{
  NotImplemented();
  /*
  i32 entrySize;
  ASSERT(m_Address[dbNum] == NULL);
  ASSERT(m_numEnt[dbNum] == 0);
  entrySize = dbEntrySizes[dbType];
  m_Address[dbNum] = (DBCOMMON *)UI_malloc(2*entrySize);
  m_numEnt[dbNum] = 2;
  dataMap[dbNum] = dbType;
  return RN(0, dbNum, 1);
  */
  return RNnul;
}

// TAG00a0b0
RN DATABASES::FindEmptyDBEntry(DBTYPE dbType, bool important)
{//(RN)
  // important means to assign even if database is
  // nearly full.  Only used to assign fallen hero's bones
  // as far as I know.
  RN result;
  i32 newDML, index;
  DBCOMMON *pDBC;
  i32 entrySize;
  i32 i;
  i32 emptyIndexEntry, emptyDBEntry;
  ASSERT((dbType&0x7fff) < 16);
  entrySize = dbEntrySizes[dbType];
  emptyDBEntry = -1;
  emptyIndexEntry = -1;
  for (i=1; i<dataMapLength; i++)
  {
    if (dataIndexMap[i] == 0xffff)
    {
      if (emptyIndexEntry < 0)
      {
        emptyIndexEntry = i;
        if (emptyDBEntry != -1) break;
      };
      continue;
    };
    if ( (dataTypeMap[i] & 15) != dbType) continue;
    pDBC = (DBCOMMON *)((pnt)m_Address[dbType] + entrySize*dataIndexMap[i]);
    if (pDBC->link() == RNnul)
    {
      emptyDBEntry = dataIndexMap[i];
      emptyIndexEntry = i;
      break;
    };
  };
  if (emptyDBEntry == -1)
  {
    pDBC = (DBCOMMON *)((pnt)m_Address[dbType] + entrySize);
    for (i=1; i<m_numEnt[dbType]; i++)
    {
      if (pDBC->link() == RNnul)
      {
        emptyDBEntry = i;
        break;
      };
      pDBC = (DBCOMMON *)((pnt)pDBC + entrySize);
    };
  };
  if (emptyDBEntry == -1)
  {
    index = db.Enlarge(dbType);
    if (index != -1) return RN(dbType,index);
    NotImplemented();
  };
  if (emptyIndexEntry == -1)
  {
    if (dataMapLength > 0xff00 - 100)
    {
      if (!important) return RNnul;
    };
    newDML = 32*(dataMapLength/32) + 33;
    dataIndexMap = (ui16 *)UI_realloc(dataIndexMap, 2*newDML, MALLOC065);
    dataTypeMap  = (ui8  *)UI_realloc(dataTypeMap, newDML, MALLOC066);
    emptyIndexEntry = dataMapLength;
    dataMapLength++;
    dataIndexMap[emptyIndexEntry] = 0xffff;
  };
  dataIndexMap[emptyIndexEntry] = (ui16)emptyDBEntry;
  dataTypeMap[emptyIndexEntry] = (ui8)dbType;
  pDBC = (DBCOMMON *)((pnt)m_Address[dbType]+emptyDBEntry*entrySize);
  ClearMemory((pnt)pDBC, entrySize);
  pDBC->link(RNeof);
  result.ConstructFromInteger(emptyIndexEntry);
  return result;
}

void DeleteDBEntry(DBCOMMON *pDB)
{
  DBTYPE dbType;
  i32 dbIndex;
  i32 i, j;
  dbType = db.GetDBType(pDB);
  if (dbType == dbUNKNOWN) return;
  dbIndex = db.GetDBIndex(pDB, dbType);
  for (i=0, j=-1; i<dataMapLength; i++)
  {
    if ((dataTypeMap[i] & 0xf) != dbType) continue;
    if (dataIndexMap[i] != dbIndex) continue;
    if (j != -1) return; //Cloned!!!
    j = i;
    if ((dataTypeMap[i] & 0x80) != 0) return; //Cloned!!!
  };
  pDB->link(RNnul);
  if (j != -1)
  {
    dataTypeMap[j] = 0xff;
    dataIndexMap[j] = 0xffff;
  };
}

void DATABASES::swapDOOR(i32 dbNum)
{
  DB0 *pDB0;
  pDB0 = m_Address[dbNum]->CastToDB0();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB0[i].swap();
  };
}

void DATABASES::swapTELEPORTER(i32 dbNum)
{
  DB1 *pDB1;
  pDB1 = m_Address[dbNum]->CastToDB1();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB1[i].swap();
  };
}

void DATABASES::swapTEXT(i32 dbNum)
{
  DB2 *pDB2;
  pDB2 = m_Address[dbNum]->CastToDB2();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB2[i].swap();
  };
}

void DATABASES::swapACTUATOR(i32 dbNum)
{
  DB3 *pDB3;
  pDB3 = m_Address[dbNum]->CastToDB3();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB3[i].swap();
  };
}



void DATABASES::swapMONSTER(i32 dbNum)
{
  DB4 *pDB4;
  pDB4 = m_Address[dbNum]->CastToDB4();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB4[i].swap();
  };
}

void DATABASES::swapWEAPON(i32 dbNum)
{
  DB5 *pDB5;
  pDB5 = m_Address[dbNum]->CastToDB5();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB5[i].swap();
  };
}

void DATABASES::swapCLOTHING(i32 dbNum)
{
  DB6 *pDB6;
  pDB6 = m_Address[dbNum]->CastToDB6();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB6[i].swap();
  };
}

void DATABASES::swapSCROLL(i32 dbNum)
{
  DB7 *pDB7;
  pDB7 = m_Address[dbNum]->CastToDB7();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB7[i].swap();
  };
}

void DATABASES::swapPOTION(i32 dbNum)
{
  DB8 *pDB8;
  pDB8 = m_Address[dbNum]->CastToDB8();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB8[i].swap();
  };
}

void DATABASES::swapCHEST(i32 dbNum)
{
  DB9 *pDB9;
  pDB9 = m_Address[dbNum]->CastToDB9();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB9[i].swap();
  };
}

void DATABASES::swapMISC(i32 dbNum)
{
  DB10 *pDB10;
  pDB10 = m_Address[dbNum]->CastToDB10();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB10[i].swap();
  };
}

void DATABASES::swapEXPOOL(i32 dbNum)
{
  DB11 *pDB11;
  pDB11 = m_Address[dbNum]->CastToDB11();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB11[i].swap();
  };
}

void DATABASES::swapMISSILE(i32 dbNum)
{
  DB14 *pDB14;
  pDB14 = m_Address[dbNum]->CastToDB14();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB14[i].swap();
  };
}

void DATABASES::swapCLOUD(i32 dbNum)
{
  DB15 *pDB15;
  pDB15 = m_Address[dbNum]->CastToDB15();
  for (i32 i=0; i<m_numEnt[dbNum]; i++)
  {
    pDB15[i].swap();
  };
}




void DATABASES::swap(i32 dbNum)
{
  switch (dbNum)
  {
    // We need to littleEndian some of the words in these
    // various databases.
  case dbDOOR:       swapDOOR(dbNum);       break;
  case dbTELEPORTER: swapTELEPORTER(dbNum); break;
  case dbTEXT:       swapTEXT(dbNum);       break;
  case dbACTUATOR:   swapACTUATOR(dbNum);   break;
  case dbMONSTER:    swapMONSTER(dbNum);    break;
  case dbWEAPON:     swapWEAPON(dbNum);     break;
  case dbCLOTHING:   swapCLOTHING(dbNum);   break;
  case dbSCROLL:     swapSCROLL(dbNum);     break;
  case dbPOTION:     swapPOTION(dbNum);     break;
  case dbCHEST:      swapCHEST(dbNum);      break;
  case dbMISC:       swapMISC(dbNum);       break;
  case dbEXPOOL:     swapEXPOOL(dbNum);     break;
  case db12:                                break;
  case db13:                                break;
  case dbMISSILE:    swapMISSILE(dbNum);    break;
  case dbCLOUD  :    swapCLOUD(dbNum);      break;
  default: ASSERT(0);
  };
}


bool DBCOMMON::IsDBType(DBTYPE dbtype)
{
  return db.IsDBType(this, dbtype);
}

DB0 *DBCOMMON::CastToDB0(void)
{
  ASSERT(IsDBType(dbDOOR));
  return (DB0 *)this;
}

DB1 *DBCOMMON::CastToDB1(void)
{
  ASSERT(IsDBType(dbTELEPORTER));
  return (DB1 *)this;
}

DB2 *DBCOMMON::CastToDB2(void)
{
  ASSERT(IsDBType(dbTEXT));
  return (DB2 *)this;
}

DB3 *DBCOMMON::CastToDB3(void)
{
  ASSERT(IsDBType(dbACTUATOR));
  return (DB3 *)this;
}

DB4 *DBCOMMON::CastToDB4(void)
{
  ASSERT(IsDBType(dbMONSTER));
  return (DB4 *)this;
}

DB5 *DBCOMMON::CastToDB5(void)
{
  ASSERT(IsDBType(dbWEAPON));
  return (DB5 *)this;
}

DB6 *DBCOMMON::CastToDB6(void)
{
  ASSERT(IsDBType(dbCLOTHING));
  return (DB6 *)this;
}

DB7 *DBCOMMON::CastToDB7(void)
{
  ASSERT(IsDBType(dbSCROLL));
  return (DB7 *)this;
}

DB8 *DBCOMMON::CastToDB8(void)
{
  ASSERT(IsDBType(dbPOTION));
  return (DB8 *)this;
}

DB9 *DBCOMMON::CastToDB9(void)
{
  ASSERT(IsDBType(dbCHEST));
  return (DB9 *)this;
}

DB10 *DBCOMMON::CastToDB10(void)
{
  ASSERT(IsDBType(dbMISC));
  return (DB10 *)this;
}

DB11 *DBCOMMON::CastToDB11(void)
{
  ASSERT(IsDBType(dbEXPOOL));
  return (DB11 *)this;
}

DB14 *DBCOMMON::CastToDB14(void)
{
  ASSERT(IsDBType(dbMISSILE));
  return (DB14 *)this;
}

DB15 *DBCOMMON::CastToDB15(void)
{
  ASSERT(IsDBType(dbCLOUD));
  return (DB15 *)this;
}

void DBCOMMON::swapLink(void)
{
  m_link.swap();
}

void DB0::swap(void)
{
  swapLink();
  word2 = LE16(word2);
}

void DB1::swap(void)
{
  swapLink();
  word2 = LE16(word2); // x, y, flags
  word4 = LE16(word4); // level
}

void DB2::swap(void)
{
  swapLink();
  word2 = LE16(word2); // x, y, flags
}

void DB2::Clear(void)
{
  m_link = RNeof;
  word2 = 0;
}

void DB3::swap(void)
{
  swapLink();
  word2 = LE16(word2); // x, y, flags
  word4 = LE16(word4);
  word6 = LE16(word6);
}

void DB3::MakeBig(void)
{
  word8 = 0;
  if (actuatorType() != 47) return; //If not DSA
  word8 = (ui16)(((word6 >> 12) & 0xc0) | ((word4 >> 14) & 0x03));
  word6 &= 0x3fff;
  word4 &= 0x3fff;
}

ui32 DB3::ParameterA(void)
{
  return word4 | ((word8 & 3) << 16);
}

ui32 DB3::ParameterB(void)
{
  return word6 | ((word8 & 0x0c) << 12);
}

void DB3::ParameterA(ui32 v)
{
  word4 = (ui16)v;
  word8 = (ui16)((word8 & 0xfffc) | ((v >> 16) & 3));
}

void DB3::ParameterB(ui32 v)
{
  word6 = (ui16)v;
  word8 = (ui16)((word8 & 0xfff3) | ((v >> 14) & 0x0c));
}

void DB4::swap(void)
{
  swapLink();
  possession2.swap();
  hitPoints6[0]  = LE16(hitPoints6[0]);
  hitPoints6[1]  = LE16(hitPoints6[1]);
  hitPoints6[2]  = LE16(hitPoints6[2]);
  hitPoints6[3]  = LE16(hitPoints6[3]);
  word14 = LE16(word14);
}

void DB4::groupIndex(ui32 i)
{
  if (TimerTraceActive)
  {
    if ((i32)this == 0x87e03c)
    {
      fprintf(GETFILE(TraceFile),"Set 105f groupIndex to %02x\n",i);
    };
  };
  uByte5 = (ui8)i;
};


void DB4::Clear(void)
{
  m_link = RNeof;
  possession2 = RNeof;
  uByte4 = 0;
  uByte5 = 0;
  hitPoints6[0] = 0;
  hitPoints6[1] = 0;
  hitPoints6[2] = 0;
  hitPoints6[3] = 0;
  word14 = 0;
}

void DB5::swap(void)
{
  swapLink();
  word2  = LE16(word2);
}

void DB5::Clear(void)
{
  m_link = RNeof;
  word2 = 0;
}

void DB6::swap(void)
{
  swapLink();
  word2  = LE16(word2);
}

void DB6::Clear(void)
{
  m_link = RNeof;
  word2 = 0;
}


void DB7::swap(void)
{
  swapLink();
  m_RNtext.LittleEndian();
  m_word4  = LE16(m_word4);
}

void DB8::Clear(void)
{
  m_link = RNeof;
  word2 = 0;
}

void DB8::swap(void)
{
  swapLink();
  word2  = LE16(word2);
}

void DB9::swap(void)
{
  swapLink();
  m_contents.swap();
  m_word4 = LE16(m_word4);
}

void DB10::swap(void)
{
  swapLink();
  word2  = LE16(word2);
}

void DB10::Clear(void)
{
  m_link = RNeof;
  word2 = 0;
}

void DB11::swap(void)
{
  int i;
  swapLink();
  size = LE16(size);
  for (i=0; i<63; i++)
  {
    d[i] = LE32(d[i]);
  };
}

void DB14::swap(void)
{
  swapLink();
  m_obj2.swap();
  m_timerIndex  = LE16(m_timerIndex);
}

void DB14::Clear(void)
{
  m_link = RNeof;
  m_obj2 = RNeof;
  m_energyRemaining = 0;
  m_energyRemaining2 = 0;
  m_timerIndex = 0;
}

void DB15::swap(void)
{
  swapLink();
  word2  = LE16(word2);
}

void DB15::Clear(void)
{
  m_link = RNeof;
  word2 = 0;
}


EXPOOL::EXPOOL(void)
{
  m = NULL;
}

EXPOOL::~EXPOOL(void)
{
}

void EXPOOL::Setup(void)
{
  if (db.NumEntry(dbEXPOOL) == 0)
  {
    m = (ui32 *)UI_malloc(256, MALLOC085);
    memset(m, 0 , 256);
    db.SetAddress(dbEXPOOL, (DBCOMMON *)m);
    ((DB11 *)m)->link(RNeof);
    ((DB11 *)m)->size = 0;
    db.NumEntry(dbEXPOOL, 1);
  }
  else
  {
    m = (ui32 *)db.GetCommonAddress(dbEXPOOL, 0);
  };
}

void EXPOOL::Write(ui32 key, ui32 *record, ui32 size)
{
  ui32 hash = key * 0xbb40e62d;
  ui32 hashi = 32 + (hash >> 27);
  ui32 result;
  ASSERT(size <= 29);
  if (m[size+2] == 0) enlarge(size+2);
  result = m[size+2];
  m[size+2] = m[result];
  if ((m[hashi] & 0x80000000) != 0)
  {
    hashi = (m[hashi] & 0x7fffffff) + ((hash >> 21) & 0x3f);
  };
  m[result] = m[hashi];
  m[hashi] = result;
  m[result+1] = key;
  memcpy(m+result+2, record, 4*size);
}

ui32 EXPOOL::Read(ui32 key, ui32 *record)
{ //Returns size of record (or zero)
  ui32 hash = key * 0xbb40e62d;
  ui32 hashi = 32 + (hash >> 27);
  ui32 p, size;
  if ((m[hashi] & 0x80000000) != 0)
  {
    hashi = (m[hashi] & 0x7fffffff) + ((hash >> 21) & 0x3f);
  };
  while (m[hashi] != 0)
  {
    p = m[hashi];
    if (m[p+1] == key)
    {
      size = ((DB11 *)(m+(p & 0xffffffc0)))->size;
      memcpy(record, m+p+2, 4*(size-2));
      m[hashi] = m[p];
      m[p] = m[size];
      m[size] = p;
      return size-2;
    };
    hashi = m[hashi];
  };
  return 0;
}

ui32 EXPOOL::Locate(ui32 key, ui32 **record)
{ //Returns size of record (or zero)
  ui32 hash = key * 0xbb40e62d;
  ui32 hashi = 32 + (hash >> 27);
  ui32 p, size;
  if ((m[hashi] & 0x80000000) != 0)
  {
    hashi = (m[hashi] & 0x7fffffff) + ((hash >> 21) & 0x3f);
  };
  while (m[hashi] != 0)
  {
    p = m[hashi];
    if (m[p+1] == key)
    {
      size = ((DB11 *)(m+(p & 0xffffffc0)))->size;
      *record = m+p+2;
      //memcpy(record, m+p+2, 4*(size-2));
      //m[hashi] = m[p];
      //m[p] = m[size];
      //m[size] = p;
      return size-2;
    };
    hashi = m[hashi];
  };
  return 0;
}

void EXPOOL::enlarge(ui32 size)
{
  ui32 n, e, p, i;
  ui32 numBlock;
  DB11 *pDB11;
  numBlock = db.NumEntry(dbEXPOOL);
  numBlock++;
  m = (ui32 *)UI_realloc(m, 256*numBlock, MALLOC086);
  p = 64*(numBlock-1) + 1;
  e = p+64;
  n = 63 / size;
  pDB11 = (DB11 *)m + numBlock-1;
  pDB11->link(RNeof);
  pDB11->Size(size);
  for (i=0; i<n; i++, p+=size)
  {
    m[p] = p+size;
  };
  m[p-size] = m[size];
  m[size] = 64*(numBlock-1) + 1;
  db.NumEntry(dbEXPOOL, numBlock);
  db.SetAddress(dbEXPOOL, (DBCOMMON *)m);
}

//*********************************************************
// End of class DATABASES member functions
//*********************************************************

void ATTRIBUTE::Current(i32 value)
{
  ubCurrent = ub(value);
}

void ATTRIBUTE::Maximum(i32 value)
{
  ubMaximum = ub(value);
}

void ATTRIBUTE::Minimum(i32 value)
{
  ubMinimum = ub(value);
}

ui8 ATTRIBUTE::Maximum(void)
{
  if (ubMaximum > 240) return 0; //It went negative
  return ubMaximum;
}

ui8 ATTRIBUTE::Minimum(void)
{
  if (ubMinimum > 240) return 0; //It went negative
  return ubMinimum;
}

ui8 ATTRIBUTE::Current(void)
{
  if (ubCurrent > 240) return 0; //It went negative
  return ubCurrent;
}

void ATTRIBUTE::IncrMinimum(i32 incr)
{
  ubMinimum = (UI8)(ubMinimum + incr);// It can go negative!
}

void ATTRIBUTE::IncrMaximum(i32 incr)
{
  ubMaximum = ub(ubMaximum + incr);
}

void ATTRIBUTE::IncrCurrent(i32 incr)
{
  ubCurrent = ub(ubCurrent + incr);
}


void RECORDFILE::PreOpen(void)
{
  // We write the first lines into memory.  Then later
  // the file may be opened for real and these lines will
  // be put on disk.
  m_lineQueueLen = 0;
};

void RECORDFILE::Open(void)
{
  // We write the first lines into memory.  Then later
  // the file may be opened for real and these lines will
  // be put on disk.
  if (m_fileNum >= 0) return; //Already open
  char folder[10] = "logs\\";
  for (i32 i=0; i<100; i++)
  {
    char filename[100];
    sprintf(filename,"%sRecord%03d.log",folder,i);
    m_fileNum = OPEN(filename,"r");
    if (m_fileNum < 0)
    {
      char msg[300];
      m_fileNum = CREATE(filename,"w", false);
      if (m_fileNum < 0)
      {
        if (folder[0] != 0)
        {
          folder[0] = 0;
          i = -1;
          continue;
        };
        sprintf(msg,"Cannot open file %s for Recording",filename);
        UI_MessageBox(msg,
                      NULL,MESSAGE_OK);
      };
      fprintf(GETFILE(m_fileNum),"#%s\n",szCSBVersion);
      fprintf(GETFILE(m_fileNum),"#                  0093 --> Freeze Game\n");
      fprintf(GETFILE(m_fileNum),"#                  0094 --> un-Freeze Game\n");
      fprintf(GETFILE(m_fileNum),"#                  00c9 --> Resume (at prison door)\n");
      fprintf(GETFILE(m_fileNum),"#                  008c --> Select Disk option from inventory\n");
      fprintf(GETFILE(m_fileNum),"#                  00d2 --> First Menu option\n");
      fprintf(GETFILE(m_fileNum),"#                  00d3 --> Second Menu Option\n");
      fprintf(GETFILE(m_fileNum),"#                  00d4 --> Third Menu Option\n");
      fprintf(GETFILE(m_fileNum),"#                  0003 --> Move Forward\n");
      fprintf(GETFILE(m_fileNum),"#                  0001 --> Turn Left\n");
      fprintf(GETFILE(m_fileNum),"#                  0002 --> Turn Right\n");
      fprintf(GETFILE(m_fileNum),"#                  0006 --> Slide Left\n");
      fprintf(GETFILE(m_fileNum),"#                  0053 --> Select Inventory Screen\n");
      fprintf(GETFILE(m_fileNum),"#                  000b --> Leave Inventory Screen\n");
      fprintf(GETFILE(m_fileNum),"#                  0047 --> Click eye to show skills\n");
      fprintf(GETFILE(m_fileNum),"#                  0046 --> Click mouth to feed\n");
      //Now dump any pre-open lines.
      for (i32 j=0; j<m_lineQueueLen; j++)
      {
        fprintf(GETFILE(m_fileNum),"%s", m_lineQueue[j]);
      };
      m_lineQueueLen = -1;
      return;
    }
    else
    {
      CLOSE(m_fileNum);
    };
  };
  UI_MessageBox("Too many Record files",
             "",MESSAGE_OK);
};

void RECORDFILE::Close(void)
{
  if (m_fileNum >= 0) CLOSE(m_fileNum);
  m_fileNum = -1;
  m_lineQueueLen = -1;
}

void RECORDFILE::CycleRandom(i32 n)
{
  if (m_fileNum >= 0)
  {
    fprintf(GETFILE(m_fileNum), "%08x 0000 %04x 2221 %08x\n",
                   d.Time, n, d.RandomNumber);
  };
  for (i32 i=0; i<=n; i++) STRandom();
}

void RECORDFILE::Signature(ui32 sig, ui32 type)
{
  if (m_fileNum >= 0)
  {
    fprintf(GETFILE(m_fileNum), "%08x %04x %04x %04x %08x\n",
                   d.Time, 
                   (sig >> 16) & 0xffff,
                   sig & 0xffff,
                   type,
                   d.RandomNumber);
  };
  STRandom();
}

void RECORDFILE::Record(i32 x, i32 y, i32 f)
{
  MouseQueueEnt ent;
  ent.x = sw(x);
  ent.y = sw(y);
  ent.num = sw(f);
  Record(&ent);
}

void RECORDFILE::Record(MouseQueueEnt *ent)
{
  if (   (ent->num == 0x64)
       &&(ent->x == 0x114)
       &&(ent->y == 0x2f) ) return; //No operation
  totalMoveCount++;
  if ((m_fileNum < 0)&&(m_lineQueueLen < 0)) return;
  if (m_fileNum >= 0)
  {
    if (!PlayFile.IsOpen())
    {
      if (((d.RandomNumber & 0x3651) == 0) && (d.Time>100))
      {
        CycleRandom(gameSpeed);
      };
      if (   ((d.RandomNumber & 0x351a8) == 0) 
          || (!m_graphicSignature) && (graphicSignature1!=0))
      {
        Signature(graphicSignature1, 0x6666);
        m_graphicSignature = true;
      };
      if (   ((d.RandomNumber & 0x351a4) == 0) 
          || (!m_CSBgraphicSignature) && (CSBgraphicSignature1!=0))
      {
        Signature(CSBgraphicSignature1, 0x666a);
        m_CSBgraphicSignature = true;
      };
      if (   ((d.RandomNumber & 0x8a153) == 0) 
          || (!m_dungeonSignature) && (dungeonSignature1!=0))
      {
        Signature(dungeonSignature1, 0x6667);
        m_dungeonSignature = true;
      };
      if (   ((d.RandomNumber & 0x8a163) == 0) 
          || (!m_versionSignature) && (versionSignature!=0))
      {
        Signature(versionSignature, 0x6665);
        m_versionSignature = true;
      };
    };
    fprintf(GETFILE(m_fileNum), "%08x %04x %04x %04x %08x\n",
                     d.Time, ent->x, ent->y, ent->num, d.RandomNumber);
    fflush(GETFILE(m_fileNum));
  }
  else
  {
    if (m_lineQueueLen > MAXLINEQUEUE)
    {
      UI_MessageBox("Record Line Queue Full","Fatal" ,MESSAGE_OK);
      die(0x3388);
    };
    sprintf(m_lineQueue[m_lineQueueLen++],
            "%08x %04x %04x %04x %08x\n",
            d.Time, ent->x, ent->y, ent->num, d.RandomNumber);
  };
}

RECORDFILE RecordFile;
bool RecordfileIsOpen(void) {return RecordFile.IsOpen();};
void RecordfileOpen(bool open)
{
  if (open) RecordFile.Open();
  else RecordFile.Close();
}

void RecordfilePreOpen(void)
{
  RecordFile.PreOpen();
}

class TABLE
{
  i32 m_entsize;
  i32 *m_table;
  i32 m_used;
  i32 m_allocated;
  void MakeRoom(void);
public:
  TABLE(i32 size);
  ~TABLE(void);
  void Clear(void);
  void Add(i32 a, i32 b);
  void Add(i32 a, i32 b, i32 c);
  void Get(i32 index, i32 *a, i32 *b);
  void Get(i32 index, i32 *a, i32 *b, i32 *c);
};

TABLE::TABLE(i32 size)
{
  m_entsize = size;
  m_table = (i32 *)UI_malloc(3 * m_entsize * sizeof (i32), 0xffff);
  if (m_table == NULL)
  {
    UI_MessageBox("No memory for playfile decompression", "Error", MESSAGE_OK);
    die(0x76d3);
  };
  m_used = 0;
  m_allocated = 3;
}

void TABLE::Clear(void)
{
  m_used = 0;
  m_allocated = 3;
}

TABLE::~TABLE(void)
{
  if (m_table != NULL) UI_free(m_table);
  m_table = NULL;
}

void TABLE::MakeRoom(void)
{ // Ensure at least one entry available.
  if (m_used < m_allocated) return;
  m_allocated = m_allocated * 5 / 4 + 1;
  m_table = (i32 *)UI_realloc(m_table, 
                              m_allocated * m_entsize * sizeof (i32),
                              0xffff);
  if (m_table == NULL)
  {
    UI_MessageBox("Cannot allocate memory for playfile expansion", "error", MESSAGE_OK);
    die(0x57aa);
  };
}

void TABLE::Add(i32 a, i32 b, i32 c)
{
  if (m_entsize!=3)
  {
    UI_MessageBox("Decompressing playfile","Internal Error", MESSAGE_OK);
    die(0x776a);
  };
  MakeRoom();
  m_table[m_entsize*m_used] = a;
  m_table[m_entsize*m_used+1] = b;
  m_table[m_entsize*m_used+2] = c;
  m_used++;
}

void TABLE::Add(i32 a, i32 b)
{
  if (m_entsize!=2)
  {
    UI_MessageBox("Decompressing playfile","Internal Error", MESSAGE_OK);
    die(0x776a);
  };
  MakeRoom();
  m_table[m_entsize*m_used] = a;
  m_table[m_entsize*m_used+1] = b;
  m_used++;
}

void TABLE::Get(i32 index, i32 *a, i32 *b)
{
  ASSERT(m_entsize == 2);
  *a = m_table[index*m_entsize+0];
  *b = m_table[index*m_entsize+1];
}

void TABLE::Get(i32 index, i32 *a, i32 *b, i32 *c)
{
  ASSERT(m_entsize == 3);
  *a = m_table[index*m_entsize+0];
  *b = m_table[index*m_entsize+1];
  *c = m_table[index*m_entsize+2];
}



TABLE tableXYF(3);
TABLE tableTXYF(2);
TABLE tableTXYFR(2);


void PLAYFILE::Open(void)
{
  char versionLine[200];
  m_file = OPEN(PlayfileName, "r");
  if (m_file < 0)
  {
    char msg[200];
    sprintf(msg,"Cannot open Playfile \"%s\"",PlayfileName);
    UI_MessageBox(msg,"",MESSAGE_OK);
    return;
  };
  GETS(versionLine,199,m_file);
  if (strlen(versionLine)!=0) versionLine[strlen(versionLine)-1] = 0;
  if (strcmp(versionLine+1, szCSBVersion) != 0)
  {
    char msg[200];
    if (    (strcmp(szCSBVersion,"CSB for Windows Version 9.03")==0)
         && (strcmp(versionLine+1,"CSB for Windows Version 9.0") == 0)
       )
    {
      version90Compatible = true;
    }
    else
    {
      sprintf(msg,"Playback file is not the correct version");
      if (TimerTraceActive)
      {
        fprintf(GETFILE(TraceFile),"Wrong playback version\n");
        fprintf(GETFILE(TraceFile),"Expected version %s\n", szCSBVersion);
        fprintf(GETFILE(TraceFile),"File version %s\n", versionLine+1);
      };
      UI_MessageBox(msg,"",MESSAGE_OK);
    };
  };
  STHideCursor();
  m_eofEncountered = m_forceClose = false;
  tableXYF.Clear();
  tableTXYF.Clear();
  tableTXYFR.Clear();
};

void PLAYFILE::Close(void)
{
  if (m_file >= 0) CLOSE(m_file);
  m_file = -1;
}

bool PLAYFILE::IsOpen(void)
{
 if (m_file < 0) return false;
 return true;
}

void PLAYFILE::ReadEOF(void)
{
  //Force file close with next Play().
  m_forceClose = true;
}

bool PLAYFILE::Play(MouseQueueEnt *ent)
{
  static char line[80];
  static i32 f1,f2,f3,f4,f5;
  ui32 deltatime;
  i32 numfield;
  if (m_file < 0) return false;
  while ((m_time == -1) || m_forceClose)
  { // Must read another record from file.
    i32 len;
    if (m_forceClose || GETS(line,80,m_file)==NULL)
    {
      m_eofEncountered = true;
      if (!m_forceClose && (d.Time <= m_oldTime)) return false;
      MouseQueueEnt tent;
      tent.num = 0x5555;
      tent.x = 0;
      tent.y = 0;
      d.RandomNumber ^= 0xaa00;
      RecordFile.Record(&tent);
      Close();
      if (!RepeatGame) 
      {
        UI_MessageBox("End Of PlaybackFile","Info",MESSAGE_OK);
      };
      NoSpeedLimit = 0;
      VBLMultiplier = 1;
      STShowCursor();
      return false;
    };
    len = strlen(line);
    if ( (len>0) && (line[len-1] == '\n'))
    {
      len--;
      line[len] = 0;
    };
    if (line[0]== '#')
    {
      if (strcmp(line,"#DMRules")==0)
      {
        DM_rules = true;
        continue;
      };
      if (strcmp(line,"#SpeedLimit") == 0)
      {
        NoSpeedLimit = 0;
        VBLMultiplier = 1;
        continue;
      };
      if (strcmp(line,"#AutoEnlarge") == 0)
      {
        AutoEnlarge = true;
        continue;
      };
      if (strcmp(line,"#NoSound") == 0)
      {
        NoSound = true;
        continue;
      };
      if (strcmp(line,"#Sound") == 0)
      {
        NoSound = false;
        continue;
      };
      if (strcmp(line,"#NoSleep") == 0)
      {
        NoSleep = true;
        continue;
      };
      if (strcmp(line,"#Sleep") == 0)
      {
        NoSleep = false;
        continue;
      };
      if (memcmp(line,"#FastVBL",8) == 0)
      {
        i32 speed;
        VBLMultiplier = 4;
        if (sscanf(line+8,"%d",&speed) == 1)
        {
          if ( (speed>0) && (speed<100))
          {
            VBLMultiplier = speed;
          };
        };
        continue;
      };
      if (strcmp(line,"#TimerTraceOn") == 0)
      {
        if (TimerTraceActive) continue; // Ignore it
        if (AttackTraceActive || AITraceActive || OpenTraceFile())
        {
          TimerTraceActive = true;
          SmartDiscardTrace(GETFILE(TraceFile));
        };
        continue;
      };
      if (strcmp(line,"#TimerTraceOff") == 0)
      {
        SmartDiscardTrace(NULL);
        if (!TimerTraceActive) continue; //Ignore it
        TimerTraceActive = false;
        if (!AttackTraceActive && !AITraceActive)
        {
          CloseTraceFile();
        };
        continue;
      };
      if (strcmp(line,"#AITraceOn") == 0)
      {
        if (AITraceActive) continue; // Ignore it
        if (AttackTraceActive || TimerTraceActive || OpenTraceFile())
        {
          AITraceActive = true;
          SmartDiscardTrace(GETFILE(TraceFile));
        };
        continue;
      };
      if (strcmp(line,"#AITraceOff") == 0)
      {
        SmartDiscardTrace(NULL);
        if (!AITraceActive) continue; //Ignore it
        AITraceActive = false;
        if (!AttackTraceActive && !TimerTraceActive)
        {
          CloseTraceFile();
        };
        continue;
      };
      continue; //ignore it
    };
    if (line[0] == '+') // Increments rather than absolute values
    {
      numfield = sscanf(line+1,"%x %x %x %x %x\n",
                           &deltatime, &m_x, &m_y, &m_num, &m_ran);
      if (numfield != 5)
      {
        UI_MessageBox("Playback Plus line did not have 5 fields","Error",MESSAGE_OK);
        die(0x6656);
      };
      m_time = m_oldTime + deltatime;
      tableXYF.Add(m_x, m_y, m_num);
    }
    else
    {
      numfield = sscanf(line,"%08x %04x %04x %04x %08x\n",
                        &f1, &f2, &f3, &f4, &f5);
      switch (numfield)
      {
      case 1:  // tableTXYFRindex
        tableTXYFR.Get(f1, &f5, (i32 *)&m_ran);
        tableTXYF.Get(f5, (i32 *)&deltatime, &f5);
        tableXYF.Get(f5, &m_x, &m_y, &m_num);
        m_time = m_oldTime + deltatime;
        break;
      case 2:  // tableTXYFindex, random
        tableTXYF.Get(f1, (i32 *)&deltatime, &f5);
        tableXYF.Get(f5, &m_x, &m_y, &m_num);
        m_ran = f2;
        tableTXYFR.Add(f1, m_ran);
        m_time = m_oldTime + deltatime;
        break;
      case 3:  // deltaTime, tableXYFindex, random
        deltatime = f1;
        m_ran = f3;
        tableXYF.Get(f2, &m_x, &m_y, &m_num);
        tableTXYF.Add(deltatime, f2);
        m_time = m_oldTime + deltatime;
        break;
      case 5:
        m_time = f1;
        m_x = f2;
        m_y = f3;
        m_num = f4;
        m_ran = f5;
        break;
      default:
        UI_MessageBox("Improper playfile format","Error",MESSAGE_OK);
        die(0x3342);
      };
    };
  };
  ASSERT(  (m_time >= d.Time)
         ||(m_time == 0)
         ||(m_num == 0xffff)
         ||(m_num == 0x5555) 
         ||(m_num == 0x6668) );
  if (m_time > d.Time) return false;
  if (    (m_time!=0)
       && (m_ran!=d.RandomNumber)
       && (m_ran!=numRandomCalls-m_oldCallCount)
       && (m_num != 0x2222)  //Cycle random number generator
       && (m_num != 0xd7)    //Party died???
       && (m_num != 0x3333)  //Set Random number to xy
       && (m_num != 0x5555)  //Splice
       && (m_num != 0x6668)  //SaveFile signature (a no-op)
       && (m_num != 0xffff)  //Mouse moves from character order area
      )
  {
    i32 answer;
    answer = UI_MessageBox("Sequence/timing error\nShould we quit?",
                           "Playback error",
                           MESSAGE_YESNO);
    if (answer == MESSAGE_IDYES) die(0x7dbc);
    d.RandomNumber = m_ran;
  };
  if (m_num == 0xd7) // Game over???
  {
    m_time = 0x7fffffff;
    return false;
  };
  ent->x = sw(m_x);
  ent->y = sw(m_y);
  ent->num = sw(m_num);
  m_oldTime = m_time;
  m_oldCallCount = numRandomCalls;
  m_time = -1;
  if (m_num == 0x3333)
  {
    d.RandomNumber = (m_x << 16) | (m_y & 0xffff);
    RecordFile.Record(ent);
    return false;
  };
  if (m_num == 0x2222)
  {
    i32 count;
    count = m_ran;
    while (count > 0)
    {
      STRandom();
      count--;
    };
    m_num = 0;
  };
  if (m_num == 0x2221)
  {
    RecordFile.CycleRandom(ent->y);
    return  Play(ent);
  };
  if (m_num == 0x6665)
  {
    if ( (((unsigned)m_x << 16) | m_y) != versionSignature)
    {
      if (firstVersionWarning)
      {
        UI_MessageBox("Recorded with\nDifferent\nCSBwin Version","error",MESSAGE_OK);
        firstVersionWarning = false;
      };
    };
    RecordFile.Signature(versionSignature,0x6665);
    return Play(ent);
  };
  if (m_num == 0x6666)
  {
    if ( (((unsigned)m_x << 16) | m_y) != graphicSignature1)
    {
      if (firstGraphicsWarning)
      {
        UI_MessageBox("Recorded with\nDifferent\nGraphics File","error",MESSAGE_OK);
        firstGraphicsWarning = false;
      };
    };
    RecordFile.Signature(graphicSignature1,0x6666);
    return Play(ent);
  };
  if (m_num == 0x666a)
  {
    if ( (((unsigned)m_x << 16) | m_y) != CSBgraphicSignature1)
    {
      if (firstCSBgraphicsWarning)
      {
        UI_MessageBox("Recorded with\nDifferent\nCSBgraphics File","error",MESSAGE_OK);
        firstCSBgraphicsWarning = false;
      };
    };
    RecordFile.Signature(CSBgraphicSignature1,0x666a);
    return Play(ent);
  };
  if (m_num == 0x6667)
  {
    if (checkDungeonSignature)
    {
      if ( (((unsigned)m_x << 16) | m_y) != dungeonSignature1)
      {
        if (firstDungeonWarning)
        {
          UI_MessageBox("Recorded with\nDifferent\ndungeon File","error",MESSAGE_OK);
          firstDungeonWarning = false;
        };
      };
    };
    RecordFile.Signature(dungeonSignature1,0x6667);
    return Play(ent);
  };
  if (  (m_num == 0x6668) || (m_num == 0x6669) )
  {
    RecordFile.Record(ent);
    return Play(ent);
  };
  if (m_num == 0x5555)
  {
    d.RandomNumber ^= 0xaa00;
    RecordFile.Record(ent);
    return  Play(ent);
  };
  if ((m_num == 0x2223) || (m_num == 0x2224) )
  { // What is this for?  Be patient and I will explain.
    // The recording files often contain game-save operations.
    // Saving a game modifies the game's state.  Unfortunately, that is
    // what the original Atari program did.
    // I want to remove these game-save operations from the recording
    // to make them 'cleaner'.  So I had to provide a substitute
    // operation that would change the game's state in the same way that
    // a save operation would change it.  The 2223 operation is designed
    // to do just that.  See example below.
    //
    // The 2224 function is used to splice two Recordnn.log files
    // together.  Since the save/restore cleared a few things
    // the 2224 function clears the same things.
    RecordFile.Record(ent);
    TAG00bd40(); //Remove the Active Monster (ITEM16) entries
    ProcessMonstersOnLevel();
    SmartDiscard(true); // Initialize Smart Discard Processing.

    if (m_num == 0x2224)
    {
      //d.Long16600 = -505; //No idea!
      checkDungeonSignature = false;
      if (d.AttackingCharacterOrdinal != 0)
      {
        d.AttackingCharacterOrdinal = 0;
        d.Word20252 = 1;
        DrawLegalAttackTypes();
      };
    };

 // Here is an example of two record files pasted together.
 // 000330fe  0136 0037 0002 f2b7762c          file 1
 // 00033127  0065 005c 0053 4bca7f8a          file 1 select inventory
 // ##00033131  00ae 0027 008c 5ac82e95        file 1 select disk icon
 // ##00033131  008c 0070 00d3 5ac82e95        file 1 select save & quit
 // ##00033131  0091 006b 00d3 5ac82e95        file 1 select game2
 // #
 // #
 // #
 // #CSB for Windows Version 7.3
 // #                  0093 --> Freeze Game
 // #                  0094 --> un-Freeze Game
 // #                  00c9 --> Resume (at prison door)
 // #                  008c --> Select Disk option from inventory
 // #                  00d2 --> First Menu option
 // #                  00d3 --> Second Menu Option
 // #                  00d4 --> Third Menu Option
 // #                  0003 --> Move Forward
 // #                  0001 --> Turn Left
 // #                  0002 --> Turn Right
 // #                  0006 --> Slide Left
 // #                  0053 --> Select Inventory Screen
 // #                  000b --> Leave Inventory Screen
 // ##00000000  00fd 0056 00c9 00000cd9      file 2 resume
 // ##00000000  00e9 0071 00d3 00000cd9      file 2 select game 2
 // ##00033131  00c9 006e 00d2 7454166b      file 2 play
 // 00033131  00c9 006e 2224 5ac82e95        I inserted this to do what
 //                                           the save/reload does
 // 00000000  0000 0000 000b 00000000        I inserted this to get out
 //                                           of inventory mode
 // 00033149  00c9 006e 0003 44e9eb83        file 2 move
 // 00033155  00c9 006e 0005 802596e0        file 2 move
 // 0003315e  004f 0021 0053 4626f1c2        etc.
    return Play(ent);

  };
  return true;
}

void PLAYFILE::Backspace(MouseQueueEnt *
#ifdef _DEBUG
                         ent
#endif
                         )
{
  if (m_file < 0) return;
  ASSERT (m_time == -1);
  ASSERT (m_x == ent->x);
  ASSERT (m_y == ent->y);
  ASSERT (m_num == ent->num);
  m_time = d.Time;
}


PLAYFILE PlayFile;
bool PlayfileIsOpen(void) {return PlayFile.IsOpen();};
void PlayfileOpen(bool open)
{
  if (open) 
  {
    PlayFile.Open();
    checkDungeonSignature = true;
  }
  else PlayFile.Close();
}


bool verticalIntEnabled = false;
i32 NoSpeedLimit= 0;
i32 VBLMultiplier = 1;
bool NoClock=false;
i32 NumberFloppyDrives = 1;
i32 dbgcstint;
i32 GameTime;

i32 keyboardMode=1; // 1 while adventuring
                    // 2 while ReIncarnation

i32 data000b2c; // Contains A4 for use by interrupt handler
i32 data000b30; // Contains A5 for use by interrupt handler

DIRECTION dirNORTH = 0;
DIRECTION dirEAST  = 1;
DIRECTION dirSOUTH = 2;
DIRECTION dirWEST  = 3;

char *tracePrefix = "                                      ";
char *initialTracePrefix(bool trace)
{
  return trace?tracePrefix+strlen(tracePrefix)-2:NULL;
}
char *nextTracePrefix(char *trace)
{
  return trace==NULL?NULL:trace-2;
}

void DUNGEONDATINDEX::Swap(void)
{
  i32 i;
  sentinel = LE16(sentinel);
  word2 = LE16(word2);
  word4 = LE16(word4);
  word6 = LE16(word6);
  word8 = LE16(word8);
  word10= LE16(word10);
  for (i=0; i<16; i++)
  {
    dbSizes[i] = LE16(dbSizes[i]);
  };
}



//*********************************************
static i32 WCSRCShandle;
static ui32 WCSRCSchecksum;

ui32 WCS(i32 handle)
{
  ui32 temp;
  WCSRCShandle = handle;
  temp = WCSRCSchecksum;
  WCSRCSchecksum = 0xffff;
  return temp;
}

ui32 RCS(i32 handle)
{
  ui32 temp;
  WCSRCShandle = handle;
  temp = WCSRCSchecksum;
  WCSRCSchecksum = 0xffff;
  return temp;
}

void WCS(ui8 *buf, i32 n)
{
  i32 i;
  for (i=0; i<n; i++)
  {
    WCSRCSchecksum *= (ui32)0xbb40e62d;
    WCSRCSchecksum += 11 + buf[i];
  };
  WRITE((ui16)WCSRCShandle, n, (char *)buf);
}

void RCS(ui8 *buf, i32 n)
{
  i32 i;
  READ(WCSRCShandle, n, (char *)buf);
  for (i=0; i<n; i++)
  {
    WCSRCSchecksum *= (ui32)0xbb40e62d;
    WCSRCSchecksum += 11 + buf[i];
  };
}

void WCS(ui32 *i, i32 n)
{
  WCS((ui8 *)i, 4*n);
}

void RCS(ui32 *i, i32 n)
{
  RCS((ui8 *)i, 4*n);
}

void WCS(i32 *i, i32 n)
{
  WCS((ui8 *)i, 4*n);
}

void RCS(i32 *i, i32 n)
{
  RCS((ui8 *)i, 4*n);
}

void WCS(ui16 *i, i32 n)
{
  WCS((ui8 *)i, 2*n);
}

void RCS(ui16 *i, i32 n)
{
  RCS((ui8 *)i, 2*n);
}

void WCS(char *c, i32 n)
{
  WCS((ui8 *)c, n);
}

void RCS(char *c, i32 n)
{
  RCS((ui8 *)c, n);
}

void CHARDESC::HP(i32 m)
{
  ASSERT((unsigned)m <= MAXHP);
  m_wHP = sw(m);
}

void CHARDESC::MaxHP(i32 m)
{
  ASSERT((unsigned)m <= MAXHP);
  m_wMaxHP = sw(m);
}


void CHARDESC::IncrHP(i32 inc)
{
  m_wHP = sw(m_wHP + inc);
  ASSERT((unsigned)m_wHP <= MAXHP);
}

void CHARDESC::IncrMaxHP(i32 inc)
{
  m_wMaxHP = sw(m_wMaxHP + inc);
  ASSERT((unsigned)m_wMaxHP <= MAXHP);
}

void CHARDESC::Stamina(i32 m)
{
  ASSERT((unsigned)m <= MAXSTAMINA);
  m_wStamina = sw(m);
}

void CHARDESC::MaxStamina(i32 m)
{
  ASSERT((unsigned)m <= MAXSTAMINA);
  m_wMaxStamina = sw(m);
}

void CHARDESC::IncrMaxStamina(i32 inc)
{
  m_wMaxStamina = sw(m_wMaxStamina + inc);
  ASSERT((unsigned)m_wMaxStamina <= MAXSTAMINA);
}

void CHARDESC::Mana(i32 m)
{
  ASSERT((unsigned)m <= MAXMANA);
  m_wMana = sw(m);
}

void CHARDESC::MaxMana(i32 m)
{
  ASSERT((unsigned)m <= MAXMANA);
  m_wMaxMana = sw(m);
}

void CHARDESC::IncrMana(i32 inc)
{
  m_wMana = sw(m_wMana + inc);
  ASSERT((unsigned)m_wMana <= MAXMANA);
}

void CHARDESC::IncrMaxMana(i32 inc)
{
  m_wMaxMana = sw(m_wMaxMana + inc);
  ASSERT((unsigned)m_wMaxMana <= MAXMANA);
}

void Signature(int fileHandle, ui32 *signature1, ui32 *signature2)
{//result will be 8 bytes (6-bytes MD5 + 2 zeroes)
  FILE *f;
  ui32 sum;
  i32 len;
  ui8 buf[1000];
  ui8 result[16];
  f = GETFILE(fileHandle);
  if (f == NULL) return;
  sum = 0;
  MD5Init();
  while ( (len=fread(buf,1,1000,f)) > 0)
  {
//    for (i=0; i<len; i++)
//    {
//      sum = (sum & 0x80000000) ? (sum<<1)^0x8d : sum<<1;
//      sum += buf[i];
//    };
    MD5Update(buf, len);
  };
  fseek(f,0,SEEK_SET);
  MD5Final(result);
  *signature1 =  result[0] 
              | (result[1]<<8)
              | (result[2]<<16)
              | (result[3]<<24);
  *signature2 =  result[4] 
              | (result[5]<<8)
              | (result[6]<<16)
              | (result[7]<<24);
  if (*signature1 == 0) *signature1 = 1; //one in 4 billion
//  return sum == 0 ? 1 : sum;
}

ui32 Signature(char *text)
{
  ui32 sum;
  i32 i;
  sum = 0;
  for (i=0; text[i]!=0; i++)
  {
    sum = (sum & 0x80000000) ? (sum<<1)^0x8d : sum<<1;
    sum += text[i];
  };
  return sum == 0 ? 1 : sum;
}

void FreeIfNonNULL(void **pointer)
{
  if (*pointer != NULL)
  {
    UI_free(*pointer);
    *pointer = NULL;
  };
}

void CleanupGraphics(void);
void Cleanup(bool programTermination)
{
  int i;
  if (programTermination)
  {
    FreeIfNonNULL((void **)&folderParentName);
    FreeIfNonNULL((void **)&folderName);
    FreeIfNonNULL((void **)&root);
  };
  CleanupGraphics();
  DSAIndex.Cleanup();
  FreeIfNonNULL((void **)&d.Item16);
  FreeIfNonNULL((void **)&d.compressedText);
  FreeIfNonNULL((void **)&d.indirectTextIndex);
  FreeIfNonNULL((void **)&gameInfo);
  FreeIfNonNULL((void **)&dataIndexMap);
  FreeIfNonNULL((void **)&dataTypeMap);
  StartSound(NULL,0,0);
  for (i=0; i<16; i++)
  {
    db.DeAllocate(i);
  };
}