#include "stdafx.h"

#include <stdio.h>



#include "Dispatch.h"
#include "UI.h"

#include "CSB.h"
#include "Recording.h"
#include "Data.h"

void SmartDiscardTrace(FILE *f);
void DSAInstrumentation_Dump(void);
void CloseTraceFile(void);
void UnlinkFile(const char *name);



extern char *PlayfileName;
//extern TCHAR szCSBVersion[MAX_LOADSTRING];
extern bool version90Compatible;
extern bool TimerTraceActive;
extern i16 TraceFile;
extern bool RepeatGame;
extern i32 VBLMultiplier;
extern bool neophyteSkills;
extern bool RecordCommandOption;
extern bool NoRecordCommandOption;
bool OpenTraceFile(void);
extern ui32 numRandomCalls;
extern bool playback_71;


bool firstVersionWarning = true;
bool firstGraphicsWarning = true;
bool firstCSBgraphicsWarning = true;
bool checkDungeonSignature;
bool firstDungeonWarning = true;

RECORDFILE RecordFile;


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, "suze");
  *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,"size");
  *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);
  {
    int n;
    for (n = strlen(versionLine)-1; n>=0; n--)
    {
      if (versionLine[n] > 32) break;
      versionLine[n] = 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(HC35);
  m_eofEncountered = m_forceClose = false;
  tableXYF.Clear();
  tableTXYF.Clear();
  tableTXYFR.Clear();
  OnMouseSwitchAction(0); // Say that all mouse buttons are up.
};

void PLAYFILE::Close(void)
{
  if (m_file >= 0)
  {
    CLOSE(m_file);
    STShowCursor(HC35);
  };
  m_file = -1;
  m_time = -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;
}


//These are placed outside of PLAYFILE::Play because the debugger
// cannot see static variables within a function.
static char line[80];
static i32 f1,f2,f3,f4,f5;
bool PLAYFILE::Play(MouseQueueEnt *ent)
{
  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;
#ifdef POST_TRANSLATE_CLICK
      // 20230506
      tent.translatedButtonNum = 0x5555;
#else
      // 20230506
      tent.num = 0x5555;
#endif
      tent.x = 0;
      tent.y = 0;
      d.RandomNumber ^= 0xaa00;
      RecordFile.Record(&tent);
      Close();
      if (!RepeatGame)
      {
        UI_MessageBox("End Of PlaybackFile","Info",MESSAGE_OK);
      };
      if (!RepeatGame)
      {
        NoSpeedLimit = 0;
        VBLMultiplier = 1;
      };
      return false;
    };
    len = strlen(line);
    while ( (len>0) && ((line[len-1] == '\n')||(line[len-1] == '\r')))
    {
      len--;
      line[len] = 0;
    };
    if (line[0]== '#')
    {
      int i;
      for (i = 1; i < len; i++)
      {
        if ((line[i] == '/') || (line[i] == ' '))
        {
          line[i] = 0;
          break;
        };
      };
      // This is to make Version 11.002 and later compatible with Version 11.1 playfile.
      if (strcmp(line,"#NeophyteSkills")==0)
      {
        neophyteSkills = true;
        RecordFile.Record("#NeophyteSkills\n");
        continue;
      };
      if (strcmp(line,"#DMRules")==0)
      {
        DM_rules = true;
        RecordFile.Record("#DMRules\n");
        continue;
      };
      if (strcmp(line,"#NoDMRules")==0)
      {
        DM_rules = false;
        RecordFile.Record("#NoDMRules\n");
        continue;
      };
      if (strcmp(line,"#ExtraTicks")==0)
      {
        extraTicks = true;
        continue;
      };
      if (strcmp(line,"#NoExtraTicks")==0)
      {
        extraTicks = false;
        continue;
      };
      if (strcmp(line,"#SpeedLimit") == 0)
      {
        NoSpeedLimit = 0;
        VBLMultiplier = 1;
        continue;
      };
      if (strcmp(line,"#NoSpeedLimit") == 0)
      {
        NoSpeedLimit = 0x20000000;
        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 (strcmp(line,"#NoRecord") == 0)
      {
        NoRecordCommandOption = true;
        RecordCommandOption = 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,"#DSATraceOn") == 0)
      {
#ifdef _MSVC_INTEL
        DSAIndex.TraceOverride(true);
#else
        DSATraceActive = true;
#endif
        continue;
      };
      if (strcmp(line,"#DSATraceOff") == 0)
      {
        DSAInstrumentation_Dump();
#ifdef _MSVC_INTEL
        DSAIndex.TraceOverride(false);
#else
        DSATraceActive = false;
#endif
        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) ,"playback");
  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;
  };
  {
    static i32 annotationFile = -1;
    static bool attempted = false;
    static i32 annotationTime = -1;
    static i32 annotationPlayerCurrent = 0;
    if (!attempted)
    {
      // Open file don't try again.
      attempted = true;
      annotationFile = OPEN("annotation.txt", "r");
    };
    while (annotationFile >= 0)
    {
      if (annotationTime < 0)
      {
        // We want to find a time.
        // Read the annotation file to find an entry for this time (or greater)
        char annotationLine[200];
        if (fgets(annotationLine, 199, GETFILE(annotationFile)) == NULL)
        {
          // EOF
          CLOSE(annotationFile);
          annotationFile = -1;
        }
        else
        {
          i32 len;
          len = strlen(annotationLine);
          if ( (len>0) && (annotationLine[0] == '#'))
          {
            if (sscanf(annotationLine + 1, "%d", &annotationPlayerCurrent) != 1
              || sscanf(annotationLine + 3, "%d", &annotationTime) != 1)
            {
              CLOSE(annotationFile);
              annotationFile = -1;
            };
          };
        };
      }
      else
      {
        break;
      };
    };
    if (annotationTime > 0)
    {
      if (annotationTime <= d.Time)
      {
        // The current annotation time is less than the dungeon time, so print it.
        char annotation[200];
        annotationTime = -1;
        annotation[0] = 0;
        if (fgets(annotation, 198, GETFILE(annotationFile)) != NULL)
        {
          i32 lineLen;
          lineLen = strlen(annotation);
          if (lineLen > 1
            && (annotation[lineLen - 1] == 0x0d
              || annotation[lineLen - 1] == 0x0a)) {
            annotation[lineLen - 1] = 0;
          }
          if (lineLen > 2
            && (annotation[lineLen - 2] == 0x0d
              || annotation[lineLen - 2] == 0x0a)) {
            annotation[lineLen - 2] = 0;
          }
          annotation[lineLen] = 0;
          annotation[lineLen + 1] = 0;
        };

        static i32 annotationColor = 0;
        switch (annotationPlayerCurrent)
        {
        case 0: // The physical player.
          annotationColor = 15;
          break;
        case 1: // Players 1 - 4.
          annotationColor = 7;
          break;
        case 2:
          annotationColor = 11;
          break;
        case 3:
          annotationColor = 8;
          break;
        case 4:
          annotationColor = 14;
          break;
        case 5: // Monster.
          annotationColor = 4;
          break;
        }

        int i = 0;
        while (annotation[i])
        {
          annotation[i] = (char)toupper(annotation[i]);
          i++;
        }

        char result[201];
        strcpy(result, "\n");
        strcat(result, annotation);
        QuePrintLines(annotationColor, result,false);
      };
    };
  };
  if (m_num == 71)
  {
    playback_71 = true;
  };
  if (m_num == 0xd7) // Game over???
  {
    m_time = 0x7fffffff;
    return false;
  };
  ent->x = sw(m_x);
  ent->y = sw(m_y);
#ifdef POST_TRANSLATE_CLICK
  // 20230506
  ent->translatedButtonNum = sw(m_num);
#else
  // 20230506
  ent->num = sw(m_num);
#endif
  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)
      {
        ui32 hash;
        char msg[80];
        hash = versionSignature;
        sprintf(msg, "Recorded with Different\nCSBwin Version\n%08x", hash);
        //UI_MessageBox(msg,"error",MESSAGE_OK);
        firstVersionWarning = false;
      };
    };
    RecordFile.Signature(versionSignature,0x6665);
    return Play(ent);
  };
  if (m_num == 0xdddd)
  {
    // Now here is a hack, first class.
    // We had Playback problems.  This little adjustment allows us to set
    // variables in the game to particular values needed to make the
    // playback work!  Oh, boy.
    *(ui16 *)(((ui8 *)&d)+m_x) = (ui16)m_y;
    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!
      totalMoveCount += 4; // See sample below where 4 commands are commented out before the save.
      checkDungeonSignature = false;
      if (d.AttackingCharacterOrdinal != 0)
      {
        d.AttackingCharacterOrdinal = 0;
        d.Word20252 = 1;
        DrawLegalAttackTypes();
      };
    };




  // Here is an example of removing a save point from a game
  // 000006e2 00a1 0079 0053 dd109237    //Select inventroy
  // 000006e3 0000 0000 0add 7a071db6    //Unclick
  // #000006e6 00b1 0029 008c c001ec2b   //Select disk icon
  // #000006e6 0000 0000 0add c001ec2b   //unclick
  // #000006e6 0069 006c 00d2 c001ec2b   //SAve and play
  // #000006e6 004b 0069 00d2 c001ec2b   //CSBGAME.dat
  // #000006e6 f2e5 643b 6668 c001ec2b   //Signature
  // 000006e6 0000 0000 2223 c001ec2b    //Do what the save would have done
  // 000006e7 0000 0000 0add fb6f9844    //unclick
  // 000006eb 004a 0069 000b 3329834b    //exit inventory






 // 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
 // #                  0001 --> Turn Left
 // #                  0002 --> Turn Right
 // #                  0003 --> Move Forward
 // #                  0004 --> Slide Right
 // #                  0005 --> Move Backward
 // #                  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, "playback");
  ASSERT (m_x == ent->x, "playback");
  ASSERT (m_y == ent->y, "playback");
#ifdef POST_TRANSLATE_CLICK
  // 20230506
  ASSERT(m_num == ent->translatedButtonNum, "playback");
#else
  // 20230506
  ASSERT (m_num == ent->num, "playback");
#endif
  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();
}





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_isQueueingLines = true;
  //Record(DM_rules ? "#DMRules\n" : "#NoDMRules\n");
};

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.
  int tryAgain;
  if (m_fileNum >= 0) return; //Already open
#ifdef _LINUX
  char folder[10] = "logs/";
#else
  char folder[10] = "logs\\";
#endif
  for (tryAgain=0; tryAgain<2; tryAgain++)
  {
    for (i32 i=0; i<10000; i++)
    {
      char filename[100];
      sprintf(filename,"%sRecord%05d.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),"#                  0001 --> Turn Left\n");
        fprintf(GETFILE(m_fileNum),"#                  0002 --> Turn Right\n");
        fprintf(GETFILE(m_fileNum),"#                  0003 --> Move Forward\n");
        fprintf(GETFILE(m_fileNum),"#                  0004 --> Slide Right\n");
        fprintf(GETFILE(m_fileNum),"#                  0005 --> Move Backward\n");
        fprintf(GETFILE(m_fileNum),"#                  0006 --> Slide Left\n");
        fprintf(GETFILE(m_fileNum),"#                  0007 --> Toggle Inventory Screen\n");
        fprintf(GETFILE(m_fileNum),"#                  0008 --> Toggle Inventory Screen\n");
        fprintf(GETFILE(m_fileNum),"#                  0009 --> Toggle Inventory Screen\n");
        fprintf(GETFILE(m_fileNum),"#                  000a --> Toggle Inventory Screen\n");
        fprintf(GETFILE(m_fileNum),"#                  000b --> Toggle Inventory Screen\n");
        fprintf(GETFILE(m_fileNum),"#                  000c --> Click on character 0\n");
        fprintf(GETFILE(m_fileNum),"#                  000d --> Click on character 1\n");
        fprintf(GETFILE(m_fileNum),"#                  000e --> Click on character 2\n");
        fprintf(GETFILE(m_fileNum),"#                  000f --> Click on character 3\n");
        fprintf(GETFILE(m_fileNum),"#                  001c --> Click on character inventory\n");
        fprintf(GETFILE(m_fileNum),"#                  ....  clothing or backback\n");
        fprintf(GETFILE(m_fileNum),"#                  0041 --> Click on character inventory\n");
        fprintf(GETFILE(m_fileNum),"#                  0046 --> Click mouth to feed\n");
        fprintf(GETFILE(m_fileNum),"#                  0047 --> Click eye to show skills\n");
        fprintf(GETFILE(m_fileNum),"#                  0050 --> Click in Viewport\n"),
        fprintf(GETFILE(m_fileNum),"#                  0051 --> Click in Chest contents\n"),
        fprintf(GETFILE(m_fileNum),"#                  0053 --> Select Inventory Screen\n");
        fprintf(GETFILE(m_fileNum),"#                  0064 --> Magic Click\n");
        fprintf(GETFILE(m_fileNum),"#                  006f --> Select Attack Character/option\n");
        fprintf(GETFILE(m_fileNum),"#                  007d --> Change Character 0 Position\n");
        fprintf(GETFILE(m_fileNum),"#                  007e --> Change Character 1 Position\n");
        fprintf(GETFILE(m_fileNum),"#                  007f --> Change Character 2 Position\n");
        fprintf(GETFILE(m_fileNum),"#                  0080 --> Change Character 3 Position\n");
        fprintf(GETFILE(m_fileNum),"#                  008c --> Select Disk option from inventory\n");
        fprintf(GETFILE(m_fileNum),"#                  0091 --> Set Party Sleeping\n"),
        fprintf(GETFILE(m_fileNum),"#                  0050 --> Awaken Party\n"),
        fprintf(GETFILE(m_fileNum),"#                  0093 --> Freeze Game\n");
        fprintf(GETFILE(m_fileNum),"#                  0094 --> un-Freeze Game\n");
        fprintf(GETFILE(m_fileNum),"#                  00c8 --> Enter Prison\n");
        fprintf(GETFILE(m_fileNum),"#                  00c9 --> Resume (at prison door)\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),"#                  00d5 --> Fourth Menu Option\n");
        fprintf(GETFILE(m_fileNum),"#                  00d7 --> Restart after party death\n");
        fprintf(GETFILE(m_fileNum),"#                  0add --> Mouse button up\n");
        fprintf(GETFILE(m_fileNum),"#                  2224 --> Replace side-effects of save/restore\n"),
        fprintf(GETFILE(m_fileNum),"#                  3333 --> Start of game.\n"),
        fprintf(GETFILE(m_fileNum),"#                  4444 --> Alphabetic Charcter.\n"),
        fprintf(GETFILE(m_fileNum),"#                  5555 --> End-of-file; internal use\n"),
        fprintf(GETFILE(m_fileNum),"#                  6665 --> Version Signiture\n");
        fprintf(GETFILE(m_fileNum),"#                  6666 --> Graphics Signiture\n");
        fprintf(GETFILE(m_fileNum),"#                  6667 --> Dungeon Signiture\n");
        fprintf(GETFILE(m_fileNum),"#                  6668 --> SaveGame Signiture\n");
        fprintf(GETFILE(m_fileNum),"#                  6669 --> LoadGame Signiture\n");
        fprintf(GETFILE(m_fileNum),"#                  666a --> CSBGraphic Signiture\n");
        fprintf(GETFILE(m_fileNum),"#                  ffff --> Drag out of chartacter order area\n");
        fprintf(GETFILE(m_fileNum),"## Parameters you can change\n");
        fprintf(GETFILE(m_fileNum),"##   #[No]DMRules\n");
        fprintf(GETFILE(m_fileNum),"##   #[No]ExtraTicks\n");
        fprintf(GETFILE(m_fileNum),"##   #[No]SpeedLimit //Quick Play\n");
        fprintf(GETFILE(m_fileNum),"##   #[No]Sleep //Skip over any delays for flashing buttons, etc\n");
        fprintf(GETFILE(m_fileNum),"##   #FastVBLnn // Set VBL multiplier (go fast); If not equal one then\n");
        fprintf(GETFILE(m_fileNum),"##              // Only display every third frame.  99 is a special case\n");
        fprintf(GETFILE(m_fileNum),"##              // that goes fast AND displays every frame\n");
        fprintf(GETFILE(m_fileNum),"##   #TimerTrace<On/Off>\n");
        fprintf(GETFILE(m_fileNum),"##   #AITrace<On/Off>\n");
        fprintf(GETFILE(m_fileNum),"##   #DSATrace<On/Off>\n");
        //Now dump any pre-open lines.
#ifdef MSVC_QUEUE
        for (std::list<std::string>::const_iterator j=m_lineQueue.begin(); j!=m_lineQueue.end(); ++j)
        {
          fputs(j->c_str(), GETFILE(m_fileNum));
        };
        m_lineQueue.clear();
#else
        {
          LQENTRY *pos;
          pos = m_lineQueue.GetHeadPosition(); 
          while (pos != NULL)
          {
            fputs(m_lineQueue.GetNext(pos), GETFILE(m_fileNum));
          };
        };
        m_lineQueue.clear();
#endif
        m_isQueueingLines = false;
        return;
      }
      else
      {
        CLOSE(m_fileNum);
      };
    };
    if (tryAgain == 0)
    {
      //Ask if we should delete files.
      tryAgain = false;
      int answer;
      answer = UI_MessageBox("Too many logs\nShould we delete the existing logs\nand start a new set of logs",
                             "Log file problem",
                             MESSAGE_YESNO);
      if (answer == MESSAGE_IDYES)
      {
        for (i32 i=0; i<100; i++)
        {
          char filename[100];
          sprintf(filename,"%sRecord%03d.log",folder,i);
#ifndef _LINUX
          UnlinkFile(filename);
#endif
        };
        continue;
      };
    };
    UI_MessageBox("Too many Record files",
              "",MESSAGE_OK);
  };
};

void RECORDFILE::Close(void)
{
  if (m_fileNum >= 0) CLOSE(m_fileNum);
  m_fileNum = -1;
  m_lineQueue.clear();
  m_isQueueingLines = false;
}

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);
#ifdef POST_TRANSLATE_CLICK
  // 20230506
  if (f == UNTRANSLATED_CLICK)
  {
    die(0x9bc3, "Untranslated Button Code");
  };
  ent.translatedButtonNum = sw(f);
#else
  // 20230506
  ent.num = sw(f);
#endif
  Record(&ent);
}


void RECORDFILE::Record(const char *l)
{
  if (m_fileNum >= 0)
  {
    fprintf(GETFILE(m_fileNum), "%s", l);
    fflush(GETFILE(m_fileNum));
  }
  else
  {
    if (!m_isQueueingLines) return;
    m_lineQueue.push_back(l);
  };
}

void RECORDFILE::Record(MouseQueueEnt *ent)
{
#ifdef POST_TRANSLATE_CLICK
  // 20230506
  if (ent->translatedButtonNum == UNTRANSLATED_CLICK)
  {
    die(0x9bc3, "Untranslated Button Code");
  };
  if ((ent->translatedButtonNum == 0x64)
    && (ent->x == 0x114)
    && (ent->y == 0x2f)) return; //No operation
  if (ent->translatedButtonNum < 0x2000)
  {
    totalMoveCount++;
  };
#else
  // 20230506
  if (   (ent->num == 0x64)
       &&(ent->x == 0x114)
       &&(ent->y == 0x2f) ) return; //No operation
  if (ent->num < 0x2000)
  {
    totalMoveCount++;
  };
#endif
#ifdef POST_TRANSLATE_CLICK
  switch(ent->translatedButtonNum)
#else
  switch (ent->num)
#endif
  {
  case 1:
  case 2:
  case 3:
  case 4:
  case 5:
  case 6:
  case 7:
  case 8:
  case 9:
  case 0x0a:
  case 0x0b:
  case 0x0c:
  case 0x0d:
  case 0x0e:
  case 0x0f:
  case 0x1c:
  case 0x1d:
  case 0x1e:
  case 0x1f:
  case 0x20:
  case 0x21:
  case 0x22:
  case 0x23:
  case 0x24:
  case 0x25:
  case 0x26:
  case 0x27:
  case 0x28:
  case 0x29:
  case 0x2a:
  case 0x2b:
  case 0x2c:
  case 0x2d:
  case 0x2e:
  case 0x2f:
  case 0x30:
  case 0x31:
  case 0x32:
  case 0x33:
  case 0x34:
  case 0x35:
  case 0x36:
  case 0x37:
  case 0x38:
  case 0x39:
  case 0x3a:
  case 0x3b:
  case 0x3c:
  case 0x3d:
  case 0x3e:
  case 0x3f:
  case 0x40:
  case 0x41:
  case 0x46:
  case 0x47:
  case 0x50:
  case 0x51:
  case 0x53:
  case 0x64:
  case 0x6f:
  case 0x7d:
  case 0x7e:
  case 0x7f:
  case 0x80:
  case 0x8c:
  case 0x91:
  case 0x92:
  case 0x93:
  case 0x94:
  case 0xc8:
  case 0xc9:
  case 0xd2:
  case 0xd3:
  case 0xd4:
  case 0xd5:
  case 0xd7:
  case 0x0add:
  case 0x2224:
  case 0x3333:
  case 0x4444:
  case 0x5555:
  case 0x6665:
  case 0x6666:
  case 0x6667:
  case 0x6668:
  case 0x6669:
  case 0x666a:
  case 0xffff:
    break;
  default:
    {
      char eline[100];
      sprintf(eline, " Add 0x%04x to comments at front of recording file",
#ifdef POST_TRANSLATE_CLICK
        ent->translatedButtonNum
#else
        ent->num
#endif
      );
      UI_MessageBox(eline, "Suggestion", MESSAGE_OK);
    };
    break;
  };
  if ((m_fileNum < 0)&& !m_isQueueingLines) return;
  if (m_fileNum >= 0)
  {
    if (!PlayFile.IsOpen() && d.ClockRunning)
    {
      if ((d.RandomNumber & 0x3651) == 0)
      {
        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;
      };
    };
#ifdef POST_TRANSLATE_CLICK
    // 20230506
    fprintf(GETFILE(m_fileNum), "%08x %04x %04x %04x %08x\n",
      d.Time, ent->x, ent->y, ent->translatedButtonNum, d.RandomNumber);
#else
    // 20230506
    fprintf(GETFILE(m_fileNum), "%08x %04x %04x %04x %08x\n",
                     d.Time, ent->x, ent->y, ent->num, d.RandomNumber);
#endif
    fflush(GETFILE(m_fileNum));
  }
  else
  {
    char buf[40];
#ifdef POST_TRANSLATE_CLICK
    // 20230506
    sprintf(buf,
      "%08x %04x %04x %04x %08x\n",
      d.Time, ent->x, ent->y, ent->translatedButtonNum, d.RandomNumber);
#else
    // 20230506
    sprintf(buf,
            "%08x %04x %04x %04x %08x\n",
            d.Time, ent->x, ent->y, ent->num, d.RandomNumber);
#endif
    m_lineQueue.push_back(buf);
  };
}

void RECORDFILE::Comment(const char *comment)
{
  if (m_fileNum < 0) return;
  fprintf(GETFILE(m_fileNum), "#%s\n", comment);
}


bool RecordfileIsOpen(void) {return RecordFile.IsOpen();};
void RecordfileOpen(bool open)
{
  if (open) RecordFile.Open();
  else RecordFile.Close();
}

void RecordfilePreOpen(void)
{
  RecordFile.PreOpen();
}


bool IsPlayFileOpen(void)                   {return PlayFile.IsOpen();}
bool PlayFile_Play(MouseQueueEnt *MQ)       {return PlayFile.Play(MQ);}
void PlayFile_Close(void)                   {PlayFile.Close();}
void PlayFile_Backspace(MouseQueueEnt *MQ)  {PlayFile.Backspace(MQ);}
void PlayFile_ReadEOF(void)                 {PlayFile.ReadEOF();}
bool PlayFile_IsEOF(void)                   {return PlayFile.IsEOF();}

void RecordFile_Open(void)                  {RecordFile.Open();};
void RecordFile_Close(void)                 {RecordFile.Close();}
bool IsRecordFileRecording(void)            {return RecordFile.IsRecording();}
void RecordFile_Record(MouseQueueEnt *MQ)   {RecordFile.Record(MQ);}
void RecordFile_Record(const char *l)    {RecordFile.Record(l);}
void RecordFile_Record(int x, int y, int z) {RecordFile.Record(x, y, z);}