// *********************************************************************
//    Copyright (c) 1989-2002  Warren Furlow
//    DecodeUCByte Copyright (c) 2000 Leo Duran
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *********************************************************************

// *********************************************************************
// HP41File.cpp : implementation file
// *********************************************************************

#include"StdAfx.h"
#include"V41.h"
#include"Udp.h"

/****************************/
// return if page is used, W&W RAMBOX RAM is not marked as used!
/****************************/
bool HP41::isPageUsed(uint page, uint bank) const
{
  const ModulePage *pPage = PageMatrix[page][bank-1];
  return pPage!=NULL && !(pPage->fWWRAMBOX && pPage->fRAM);
}

/****************************/
// Returns 0 for success, 1 for open fail, 2 for read fail, 3 for invalid file, 4 for load conflict or no space
/****************************/
int HP41::LoadMOD(
  ModuleHeader *&pModuleOut,     // output
  const char *pszFullPath)
  {
  pModuleOut=NULL;
  ModuleFileHeader *pMFH;
  uint page,hep_page=0;

  // open file and read its contents into a buffer
  HANDLE hFile;
  DWORD FileSize,SizeRead;
  hFile=CreateFile(pszFullPath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
  if (hFile==INVALID_HANDLE_VALUE)
    return(1);
  FileSize=GetFileSize(hFile,NULL);
  pMFH=(ModuleFileHeader *)malloc(FileSize);
  if (pMFH==NULL)
    {
    CloseHandle(hFile);
    return(1);
    }
  ReadFile(hFile,pMFH,FileSize,&SizeRead,NULL);
  CloseHandle(hFile);
  if (FileSize!=SizeRead)
    {
    free(pMFH);
    return(2);
    }
  if (FileSize < sizeof(ModuleFileHeader))
    {
    free(pMFH);
    return(3);
    }

  // get file format and module page size
  const int nFileFormat=get_file_format(pMFH->FileFormat);
  const DWORD dwModulePageSize = (1==nFileFormat) ? sizeof(ModuleFilePage) : sizeof(ModuleFilePageV2);

  // validate module header
  if (FileSize!=sizeof(ModuleFileHeader)+pMFH->NumPages*dwModulePageSize ||
    (1!=nFileFormat && 2!=nFileFormat) ||  pMFH->MemModules>4 || pMFH->XMemModules>3 ||
    pMFH->Original>1 || pMFH->AppAutoUpdate>1 || pMFH->Category>CATEGORY_MAX || pMFH->Hardware>HARDWARE_MAX)    /* out of range */
    {
    free(pMFH);
    return(3);
    }
  // validate number of main & extended memory modules
  if ((MemModules+pMFH->MemModules>4) || (XMemModules+pMFH->XMemModules>3))
    {
    free(pMFH);
    return(4);
    }
  ModuleHeader *pModuleNew=new ModuleHeader;
  pModuleOut=pModuleNew;
  // info strings
  strncpy(pModuleNew->szFullFileName,pszFullPath,sizeof(pModuleNew->szFullFileName));
  strcpy(pModuleNew->szFileFormat,pMFH->FileFormat);
  strcpy(pModuleNew->szTitle,pMFH->Title);
  strcpy(pModuleNew->szVersion,pMFH->Version);
  strcpy(pModuleNew->szPartNumber,pMFH->PartNumber);
  strcpy(pModuleNew->szAuthor,pMFH->Author);
  strcpy(pModuleNew->szCopyright,pMFH->Copyright);
  strcpy(pModuleNew->szLicense,pMFH->License);
  strcpy(pModuleNew->szComments,pMFH->Comments);
  // special module characteristics
  pModuleNew->MemModules=pMFH->MemModules;
  MemModules+=pMFH->MemModules;
  pModuleNew->XMemModules=pMFH->XMemModules;
  XMemModules+=pMFH->XMemModules;
  pModuleNew->Category=pMFH->Category;
  pModuleNew->Hardware=pMFH->Hardware;
  switch (pMFH->Hardware)
    {
    case HARDWARE_PRINTER:
      fPrinter=TRUE;
      break;
    case HARDWARE_CARDREADER:
      fCardReader=TRUE;
      break;
    case HARDWARE_TIMER:
      InitTimer();                          // sets fTimer
      break;
    case HARDWARE_WAND:
      fWand=TRUE;
      break;
    case HARDWARE_HPIL:
      fHPIL=TRUE;
      break;
    case HARDWARE_INFRARED:
      fInfrared=TRUE;
      break;
    case HARDWARE_HEPAX:                    // relevant in page information
    case HARDWARE_WWRAMBOX:                 // relevant in page information
    case HARDWARE_NONE:
    default:
      break;
    }
  pModuleNew->Original=pMFH->Original;
  pModuleNew->AppAutoUpdate=pMFH->AppAutoUpdate;
  pModuleNew->NumPages=pMFH->NumPages;
  memcpy(pModuleNew->HeaderCustom,pMFH->HeaderCustom,sizeof(pModuleNew->HeaderCustom));
  ModuleList.AddTail(pModuleNew);

  byte ImageNo = 0;                         // image no. in MOD file

  // these are arrays indexed on the page group number (1-8) (unique only within each mod file)
  // dual use: values are either a count stored as a negative number or a (positive) page number 1-f
  int LowerGroup[8]={0,0,0,0,0,0,0,0};      // <0, or =page # if lower page(s) go in group
  int UpperGroup[8]={0,0,0,0,0,0,0,0};      // <0, or =page # if upper page(s) go in group
  int OddGroup[8]={0,0,0,0,0,0,0,0};        // <0, or =page # if odd page(s) go in group
  int EvenGroup[8]={0,0,0,0,0,0,0,0};       // <0, or =page # if even page(s) go in group
  int OrderedGroup[8]={0,0,0,0,0,0,0,0};    // <0, or =page # if ordered page(s) go in group
  // load ROM pages with three pass process
  for (int pass=1;pass<=3;pass++)
    {
    for (int pageIndex=0;pageIndex<pModuleNew->NumPages;pageIndex++)
      {
      ModuleFilePage *pMFP;
      if (1==nFileFormat)                   // MOD1
         {
         pMFP=&((ModuleFilePage *)&pMFH[1])[pageIndex];
         }
      else                                  // MOD2
         {
         pMFP=(ModuleFilePage *)(&((ModuleFilePageV2 *)&pMFH[1])[pageIndex]);
         }
      flag fLoad=FALSE;
      switch(pass)
        {
        case 1:                             // pass 1: validate page variables, flag grouped pages
        if ((pMFP->Page>0x0f && pMFP->Page<POSITION_MIN) || pMFP->Page>POSITION_MAX || pMFP->PageGroup>8 ||
          pMFP->Bank==0 || pMFP->Bank>4 || pMFP->BankGroup>8 || pMFP->RAM>1 || pMFP->WriteProtect>1 || pMFP->FAT>1 ||  /* out of range values */
          (pMFP->PageGroup && pMFP->Page<=POSITION_ANY) ||    /* group pages cannot use non-grouped position codes */
          (!pMFP->PageGroup && pMFP->Page>POSITION_ANY))      /* non-grouped pages cannot use grouped position codes */
          {
          free(pMFH);
          UnloadMOD(pModuleNew);
          return(3);
          }
        if (pMFP->PageGroup==0)             // if not grouped do nothing in this pass
          break;
        if (pMFP->Page==POSITION_LOWER)
          LowerGroup[pMFP->PageGroup-1]-=1; // save the count of pages with each attribute as a negative number
        else if (pMFP->Page==POSITION_UPPER)
          UpperGroup[pMFP->PageGroup-1]-=1;
        else if (pMFP->Page==POSITION_ODD)
          OddGroup[pMFP->PageGroup-1]-=1;
        else if (pMFP->Page==POSITION_EVEN)
          EvenGroup[pMFP->PageGroup-1]-=1;
        else if (pMFP->Page==POSITION_ORDERED)
          OrderedGroup[pMFP->PageGroup-1]-=1;
        break;

        case 2:                             // pass 2: find free location for grouped pages
        if (pMFP->PageGroup==0)             // if not grouped do nothing in this pass
          break;
        // a matching page has already been loaded
        if (pMFP->Page==POSITION_LOWER && UpperGroup[pMFP->PageGroup-1]>0)       // this is the lower page and the upper page has already been loaded
          page=UpperGroup[pMFP->PageGroup-1]-1;
        else if (pMFP->Page==POSITION_LOWER && LowerGroup[pMFP->PageGroup-1]>0)  // this is another lower page
          page=LowerGroup[pMFP->PageGroup-1];
        else if (pMFP->Page==POSITION_UPPER && LowerGroup[pMFP->PageGroup-1]>0)  // this is the upper page and the lower page has already been loaded
          page=LowerGroup[pMFP->PageGroup-1]+1;
        else if (pMFP->Page==POSITION_UPPER && UpperGroup[pMFP->PageGroup-1]>0)  // this is another upper page
          page=UpperGroup[pMFP->PageGroup-1];
        else if (pMFP->Page==POSITION_ODD && EvenGroup[pMFP->PageGroup-1]>0)
          page=EvenGroup[pMFP->PageGroup-1]+1;
        else if (pMFP->Page==POSITION_ODD && OddGroup[pMFP->PageGroup-1]>0)
          page=OddGroup[pMFP->PageGroup-1];
        else if (pMFP->Page==POSITION_EVEN && OddGroup[pMFP->PageGroup-1]>0)
          page=OddGroup[pMFP->PageGroup-1]-1;
        else if (pMFP->Page==POSITION_EVEN && EvenGroup[pMFP->PageGroup-1]>0)
          page=EvenGroup[pMFP->PageGroup-1];
        else if (pMFP->Page==POSITION_ORDERED && OrderedGroup[pMFP->PageGroup-1]>0)
          page=++OrderedGroup[pMFP->PageGroup-1];
        // find first page in group
        else     // find free space depending on which combination of positions are specified
          {
          if (LowerGroup[pMFP->PageGroup-1]!=0 && UpperGroup[pMFP->PageGroup-1]!=0)         // lower and upper
            {
            page=8;
            while (page<=0xe && (isPageUsed(page,pMFP->Bank) || isPageUsed(page+1,pMFP->Bank)))
              page++;
            }
          else if (LowerGroup[pMFP->PageGroup-1]!=0)                                        // lower but no upper
            {
            page=8;
            while (page<=0xf && isPageUsed(page,pMFP->Bank))
              page++;
            }
          else if (UpperGroup[pMFP->PageGroup-1]!=0)                                        // upper but no lower
            {
            page=8;
            while (page<=0xf && isPageUsed(page,pMFP->Bank))
              page++;
            }
          else if (EvenGroup[pMFP->PageGroup-1]!=0 && OddGroup[pMFP->PageGroup-1]!=0)       // even and odd
            {
            page=8;
            while (page<=0xe && (isPageUsed(page,pMFP->Bank) || isPageUsed(page+1,pMFP->Bank)))
              page+=2;
            }
          else if (EvenGroup[pMFP->PageGroup-1]!=0)                                         // even only
            {
            page=8;
            while (page<=0xe && isPageUsed(page,pMFP->Bank))
              page+=2;
            }
          else if (OddGroup[pMFP->PageGroup-1]!=0)                                          // odd only
            {
            page=8; // return even page number, page no. is incremented when setting OddGroup
            while (page<=0xe && isPageUsed(page+1,pMFP->Bank))
              page+=2;
            }
          else if (OrderedGroup[pMFP->PageGroup-1]!=0)                                      // a block
            {
            uint count=-OrderedGroup[pMFP->PageGroup-1];
            for (page=8;page<=0x10-count;page++)
              {
              uint nFree=0;
              uint page2;
              for (page2=page;page2<=0x0f;page2++)  // count up free spaces
                {
                if (!isPageUsed(page2,pMFP->Bank))
                  nFree++;
                else
                  break;
                }
              if (count<=nFree)            // found a space
                break;
              }
            }
          else
            {
            page=8;
            while (page<=0xf && isPageUsed(page,pMFP->Bank))
              page++;
            }
          // save the position that was found in the appropriate array
          if (pMFP->Page==POSITION_LOWER)
            LowerGroup[pMFP->PageGroup-1]=page;
          else if (pMFP->Page==POSITION_UPPER)
            {
            ++page;                                 // found two positions - take the upper one
            UpperGroup[pMFP->PageGroup-1]=page;
            }
          else if (pMFP->Page==POSITION_EVEN)
            EvenGroup[pMFP->PageGroup-1]=page;
          else if (pMFP->Page==POSITION_ODD)
            {
            ++page;                                 // found two positions - take the odd one
            OddGroup[pMFP->PageGroup-1]=page;
            }
          else if (pMFP->Page==POSITION_ORDERED)
            OrderedGroup[pMFP->PageGroup-1]=page;
          }
        fLoad=TRUE;
        break;

        case 3:      // pass 3 - find location for non-grouped pages
        if (pMFP->PageGroup)
          break;
        if (pMFP->Page==POSITION_ANY)           // a single page that can be loaded anywhere 8-F
          {
          page=8;
          // look for an empty page or a page with W&W RAMBOX RAM
          while (page<=0xf && isPageUsed(page,pMFP->Bank))
            page++;
          }
        else                                    // page number is hardcoded
          page=pMFP->Page;
        fLoad=TRUE;
        break;
        }

      if (fLoad)    // load the image
        {
        size_t nPageCustomOffset;

        ModulePage *pNewPage=new ModulePage;
        pNewPage->pModule=pModuleNew;
        pNewPage->pAltPage=NULL;
        strcpy(pNewPage->szName,pMFP->Name);
        strcpy(pNewPage->szID,pMFP->ID);
        pNewPage->ImageNo=ImageNo++;            // save module position in MOD file
        pNewPage->Page=pMFP->Page;
        pNewPage->ActualPage=page;
        pNewPage->Bank=pMFP->Bank;
        pNewPage->PageGroup=pMFP->PageGroup;
        pNewPage->BankGroup=pMFP->BankGroup;
        if (pMFP->BankGroup)
          pNewPage->ActualBankGroup=pMFP->BankGroup+NextActualBankGroup*8;  // ensures each bank group has a number that is unique to the entire simulator
        else
          pNewPage->ActualBankGroup=0;
        pNewPage->fRAM=pMFP->RAM;
        pNewPage->fWriteProtect=pMFP->WriteProtect;
        pNewPage->fFAT=pMFP->FAT;
        pNewPage->fHEPAX=(pMFH->Hardware==HARDWARE_HEPAX);
        pNewPage->fWWRAMBOX=(pMFH->Hardware==HARDWARE_WWRAMBOX);
        if (1==nFileFormat)                   // MOD1
          {
          unpack_image(pNewPage->Image,pMFP->Image);
          nPageCustomOffset=offsetof(ModuleFilePage,PageCustom);
          }
        else                                  // MOD2
          {
          word *pwImage=(word *)pMFP->Image;
          for (size_t i = 0; i < sizeof(pNewPage->Image)/sizeof(pNewPage->Image[0]); ++i)
            {
            // swap bytes
            pNewPage->Image[i]=(pwImage[i] >> 8)|(pwImage[i] << 8);
            }
          nPageCustomOffset=offsetof(ModuleFilePageV2,PageCustom);
          }
        memcpy(pNewPage->PageCustom,(byte *)pMFP+nPageCustomOffset,sizeof(pNewPage->PageCustom));
        // patch the NULL timeout value to be longer - not known if this works for all revisions
        if (page==0 && (0==strcmpi(pNewPage->szName,"NUT0-D") || 0==strcmpi(pNewPage->szName,"NUT0-G") || 0==strcmpi(pNewPage->szName,"NUT0-N")))
          {
          pNewPage->Image[0x0ec7]=0x3ff;                               // original value =0x240
          pNewPage->Image[0x0fff]=compute_checksum(pNewPage->Image);   // fix the checksum so service rom wont report error
          }
        // HEPAX special case
        if (hep_page && pNewPage->fHEPAX && pNewPage->fRAM)            // hepax was just loaded previously and this is the first RAM page after it
          {
          pNewPage->ActualPage=hep_page;
          pNewPage->pAltPage=PageMatrix[hep_page][pMFP->Bank-1]->pAltPage;
          PageMatrix[hep_page][pMFP->Bank-1]->pAltPage=pNewPage;       // load this RAM into alternate page
          }
        // there is no free space or some load conflict exists (only W&W RAMBOX pages can be overloaded)
        else if (page>0xf || (PageMatrix[page][pMFP->Bank-1]!=NULL && !PageMatrix[page][pMFP->Bank-1]->fWWRAMBOX))
          {
          free(pMFH);
          delete pNewPage;
          UnloadMOD(pModuleNew);
          return(4);
          }
        else                                                           // otherwise load into primary page
        {
          pNewPage->pAltPage=PageMatrix[page][pMFP->Bank-1];           // save actual content to alternate page
          PageMatrix[page][pMFP->Bank-1]=pNewPage;
        }
        hep_page=0;
        if (pNewPage->fHEPAX && !pNewPage->fRAM)                       // detect HEPAX ROM
          hep_page=page;
        }
      }
    }

  free(pMFH);
  NextActualBankGroup++;
  return(0);
  }


/****************************/
// returns 0 for failure, 1 for success
/****************************/
int HP41::SaveMOD(
  ModuleHeader *pModule,
  const char *pszFullPath) const
{
  if (pModule==NULL)
    return(0);

  // validate the number of pages
  uint page,bank,count=0;
  for (page=0;page<=0xf;page++)
  {
    for (bank=1;bank<=4;bank++)
    {
      ModulePage *pPage = PageMatrix[page][bank-1];

      for (; pPage; pPage = pPage->pAltPage) // walk through alternate pages
      {
        if (pPage->pModule==pModule)
        {
          ++count;
        }
      }
    }
  }
  if (count!=pModule->NumPages)
    return(0);

  // get file format and module page size
  const int nFileFormat=get_file_format(pModule->szFileFormat);
  const DWORD dwModulePageSize = (1==nFileFormat) ? sizeof(ModuleFilePage) : sizeof(ModuleFilePageV2);

  // allocate a buffer
  byte *pBuff;
  DWORD FileSize;
  FileSize=sizeof(ModuleFileHeader)+dwModulePageSize*pModule->NumPages;
  pBuff=(byte*)malloc(FileSize);
  if (pBuff==NULL)
    return(0);
  memset(pBuff,0,sizeof(FileSize));

  // copy header into buffer
  ModuleFileHeader *pMFH=(ModuleFileHeader*)pBuff;
  memset(pMFH,0,sizeof(ModuleFileHeader));
  strncpy(pMFH->FileFormat,pModule->szFileFormat,sizeof(pMFH->FileFormat));
  strncpy(pMFH->Title,pModule->szTitle,sizeof(pMFH->Title));
  strncpy(pMFH->Version,pModule->szVersion,sizeof(pMFH->Version));
  strncpy(pMFH->PartNumber,pModule->szPartNumber,sizeof(pMFH->PartNumber));
  strncpy(pMFH->Author,pModule->szAuthor,sizeof(pMFH->Author));
  strncpy(pMFH->Copyright,pModule->szCopyright,sizeof(pMFH->Copyright));
  strncpy(pMFH->License,pModule->szLicense,sizeof(pMFH->License));
  strncpy(pMFH->Comments,pModule->szComments,sizeof(pMFH->Comments));
  pMFH->Category=pModule->Category;
  pMFH->Hardware=pModule->Hardware;
  pMFH->MemModules=pModule->MemModules;
  pMFH->XMemModules=pModule->XMemModules;
  pMFH->Original=0;
  pMFH->AppAutoUpdate=pModule->AppAutoUpdate;
  pMFH->NumPages=pModule->NumPages;
  memcpy(pMFH->HeaderCustom,pModule->HeaderCustom,sizeof(pMFH->HeaderCustom));

  // copy each page into buffer that points to this module header
  for (page=0;page<=0xf;page++)
  {
    for (bank=1;bank<=4;bank++)
    {
      ModulePage *pPage = PageMatrix[page][bank-1];

      for (; pPage; pPage = pPage->pAltPage) // walk through alternate pages
      {
        if (pPage->pModule==pModule)
        {
          size_t nPageCustomOffset;

          ModuleFilePage *pMFP=(ModuleFilePage*)(pBuff+sizeof(ModuleFileHeader)+pPage->ImageNo*dwModulePageSize);
          memset(pMFP,0,dwModulePageSize);
          strncpy(pMFP->Name,pPage->szName,sizeof(pMFP->Name));
          strncpy(pMFP->ID,pPage->szID,sizeof(pMFP->ID));
          pMFP->Page=pPage->Page;
          pMFP->PageGroup=pPage->PageGroup;
          pMFP->Bank=pPage->Bank;
          pMFP->BankGroup=pPage->BankGroup;
          pMFP->RAM=pPage->fRAM;
          pMFP->WriteProtect=pPage->fWriteProtect;
          pMFP->FAT=pPage->fFAT;
          if (1==nFileFormat)               // MOD1
          {
            pack_image(pPage->Image,pMFP->Image);
            nPageCustomOffset=offsetof(ModuleFilePage,PageCustom);
          }
          else                              // MOD2
          {
            word *pwImage=(word *)pMFP->Image;
            for (size_t i = 0; i < sizeof(pPage->Image) / sizeof(pPage->Image[0]); ++i)
            {
              // swap bytes for .rom format
              pwImage[i] = (pPage->Image[i] >> 8) | (pPage->Image[i] << 8);
            }
            nPageCustomOffset=offsetof(ModuleFilePageV2,PageCustom);
          }
          memcpy((byte *)pMFP+nPageCustomOffset,pPage->PageCustom,sizeof(pMFP->PageCustom));
        }
      }
    }
  }

  // write file
  int nResult=0;
  HANDLE hFile=CreateFile(pszFullPath,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
  if (hFile!=INVALID_HANDLE_VALUE)
  {
    DWORD SizeWritten;

    WriteFile(hFile,pBuff,FileSize,&SizeWritten,NULL);
    if (FileSize==SizeWritten)
      nResult=1;
    CloseHandle(hFile);
  }
  free(pBuff);
  return(nResult);
}


/****************************/
void HP41::UnloadMOD(ModuleHeader *pModule)
  {
  if (pModule==NULL)
    return;
  // free associated ROM pages
  uint page,bank;
  for (page=0;page<=0xf;page++)
    for (bank=1;bank<=4;bank++)
      if (PageMatrix[page][bank-1]!=NULL)
        {
        FreePage(page,bank,pModule);
        active_bank[page]=1;
        }
  // back out any hardware that this module contains
  if (MemModules>=pModule->MemModules)
    MemModules-=pModule->MemModules;
  else
    MemModules=0;
  if (XMemModules>=pModule->XMemModules)
    XMemModules-=pModule->XMemModules;
  else
    XMemModules=0;
  switch (pModule->Hardware)
    {
    case HARDWARE_PRINTER:
      fPrinter=FALSE;
      break;
    case HARDWARE_CARDREADER:
      fCardReader=FALSE;
      break;
    case HARDWARE_TIMER:
      DeInitTimer();      // clears fTimer
      break;
    case HARDWARE_WAND:
      fWand=FALSE;
      break;
    case HARDWARE_HPIL:
      fHPIL=FALSE;
      break;
    case HARDWARE_INFRARED:
      fInfrared=FALSE;
      break;
    case HARDWARE_HEPAX:
    case HARDWARE_NONE:
    default:
      break;
    }
  // delete pointer from list
  POSITION pos=ModuleList.GetHeadPosition();
  while (pos!=NULL)
    {
    if (pModule==(ModuleHeader *)ModuleList.GetAt(pos))
      {
      ModuleList.RemoveAt(pos);
      delete pModule;
      return;
      }
    ModuleList.GetNext(pos);
    }
  }


/****************************/
// Frees memory used by page and sets page pointer
/****************************/
ModulePage* HP41::FreePage(
  ModulePage *pPage,
  ModuleHeader *pModule)
  {
  if (pPage!=NULL)
    {
    ModulePage *pNxtPage = FreePage(pPage->pAltPage,pModule);

    if (pModule==NULL || pPage->pModule==pModule)
      {
      delete pPage;
      pPage=pNxtPage;
      }
    else
      {
      pPage->pAltPage = pNxtPage;
      }
    }
  return pPage;
  }

void HP41::FreePage(
  uint page,
  uint bank,
  ModuleHeader *pModule)
  {
  PageMatrix[page][bank-1]=FreePage(PageMatrix[page][bank-1],pModule);
  pCurPage=PageMatrix[CurPage][active_bank[CurPage]-1];
  }


/*****************************/
// reconfigure HEPAX ROM to free module page
/*****************************/
void HP41::ReconfigHepax()
{
  if (eDeepSleep!=IsSleeping())       // not in deep sleep, check for initializing HEPAX ROM
    {
    for (uint src=5;src<=0xf;++src)   // search for HEPAX ROM
      {
      if (PageMatrix[src][0]!=NULL && PageMatrix[src][0]->fHEPAX && !PageMatrix[src][0]->fRAM)
        {
        // HEPAX ROM found
        uint dest;

        // find free page as HEPAX destination
        for (dest=5;dest<=0xf && PageMatrix[dest][0]!=NULL;++dest) { }

        if (dest<=0xf)                // found free page
          {
          HepaxROMBLK(src,dest);      // move hepax ROM to free page
          ASSERT(isConfigHepax());
          }
        break;
        }
      }
    }
}


/*****************************/
// reconfigure W&W RAMBOX II bank switcher mapping
/*****************************/
void HP41::ReconfigRamboxBS()
{
  ModulePage *pRamboxPage = NULL;

  // check if W&W RAMBOX equipped
  uint page,bank;
  for (page=0;!pRamboxPage && page<=0xf;page++)
  {
    for (bank=1;!pRamboxPage && bank<=4;bank++)
    {
      ModulePage *pPage = PageMatrix[page][bank-1];

      // walk through alternate pages
      for (;!pRamboxPage && pPage; pPage = pPage->pAltPage)
      {
        if (pPage->fWWRAMBOX)
          pRamboxPage = pPage;
      }
    }
  }

  // RAMBOX equipped
  if (pRamboxPage)
  {
    // 0=all pages bank1, 1=odd pages bank2, 2=even pages bank2, 3=all pages bank2
    const byte byBank = pRamboxPage->pModule->HeaderCustom[0];

    // search for banks that match ActualBankGroup
    for (page=0;page<=0xf;page++)
    {
      if (PageMatrix[page][0]!=NULL &&
        PageMatrix[page][0]->ActualBankGroup==pRamboxPage->ActualBankGroup &&
        PageMatrix[page][1]!=NULL &&
        PageMatrix[page][1]->ActualBankGroup==pRamboxPage->ActualBankGroup)
      {
        // W&W RAMBOX II bank switching
        if (byBank == 0)
        {
          active_bank[page]=1;
        }
        else
        {
          // set odd page to bank 2
          if ((byBank & 0x1) != 0 && (page & 1) != 0)
          {
            active_bank[page]=2;
          }
          // set even page to bank 2
          if ((byBank & 0x2) != 0 && (page & 1) == 0)
          {
            active_bank[page]=2;
          }
        }
      }
    }
  }
}


/****************************/
// Loads the config file (.LOD) and executes its commands
// returns -1 = file not found, 0 = successful, >0 = number of errors
/****************************/
int HP41::LoadConfig(
  const char *pszLodFile)
  {
  CString sMsg;
  WFile hLodFile;
  char *psz,*psz2;
  char szMODFileName[_MAX_PATH]="";
  char szUCFileName[_MAX_PATH]="";
  CStringArray UCArray;
  int nErrors=0;
  char app_drive[_MAX_DRIVE],app_dir[_MAX_DIR];
  word NEW_PC_REG=0x0232;   // cold start initialization
  flag fPC_REGSet=FALSE;
  flag fTimerStatusLoaded=FALSE;
  flag fUdpCfgLoaded=FALSE; // $UDP configuration data not loaded

  _splitpath(theApp.szAppFullPath,app_drive,app_dir,NULL,NULL);
  if (!hLodFile.Open(pszLodFile))
    {
    sMsg.Format("Unable to open configuration file: %s",pszLodFile);
    AfxMessageBox(sMsg);
    return(-1);             // file not loaded
    }

  // free modules and rom pages
  while (!ModuleList.IsEmpty())
    {
    ModuleHeader *pModule=(ModuleHeader *)ModuleList.GetHead();
    UnloadMOD(pModule);
    }

  while (hLodFile.GetLine())
    {
    hLodFile.SkipWhiteSpace();
    if (hLodFile.LineLength()==0)
      ;  // ignore blank lines
    else if ((hLodFile.CurrentChar()==';')||(hLodFile.CurrentChar()=='*'))
      ;  // comments - do nothing
    else if (hLodFile.CurrentChar()==':')
      ;  // ignore register commands for the first pass
    else if (hLodFile.CurrentChar()=='$')
      {
      psz=hLodFile.GetToken();
      if (!strcmpi(psz,"$MODULE"))
        {
        hLodFile.SkipWhiteSpace();
        psz=hLodFile.GetToken();
        if (psz[0]==0)
          {
          sMsg.Format("MOD file name not specified in $MODULE command - Line:  %d",hLodFile.LineNumber());
          AfxMessageBox(sMsg);
          nErrors++;
          }
        CString strName(psz);
        strName.Replace("%20"," "); // decode %20 to spaces for file name
        _makepath(szMODFileName,app_drive,app_dir,(LPCTSTR)strName,"MOD");
        ModuleHeader *pModuleNew;
        int nRes=LoadMOD(pModuleNew,szMODFileName);
        if (nRes)       // some sort of error- try subdir
          {
          _makepath(szMODFileName,app_drive,(LPCTSTR)(CString(app_dir)+"MOD"),(LPCTSTR)strName,"MOD");
          nRes=LoadMOD(pModuleNew,szMODFileName);
          }
        if (nRes>=1 && nRes<=4)
          {
          LPCTSTR lpszMsg[] =
            {
            "File not found: %s",
            "Error reading file: %s",
            "Invalid file format: %s",
            "Load conflict or no space: %s"
            };

          sMsg.Format(lpszMsg[nRes-1],szMODFileName);
          AfxMessageBox(sMsg);
          nErrors++;
          }
        }
      // these are all legacy directives used in SDK41 so just ignore them
      else if (!strcmpi(psz,"$EDITOR"))
        {
        }
      else if (!strcmpi(psz,"$LABELS"))
        {
        }
      else if (!strcmpi(psz,"$RUN"))
        {
        }
      else if (!strcmpi(psz,"$GENLABELS"))
        {
        }
      else if (!strcmpi(psz,"$TYPEMATIC"))
        {
        }
      else if (!strcmpi(psz,"$HOLDTIME"))
        {
        }
      // these will be removed at saving
      else if (!strcmpi(psz,"$REG") || !strcmpi(psz,"$PAGE"))
        {
        sMsg.Format("%s command no longer supported - Line:  %d",psz,hLodFile.LineNumber());
        AfxMessageBox(sMsg);
        nErrors++;
        }
      else if (!strcmpi(psz,"$GET"))
        {
        UCArray.Add(hLodFile.GetToken());
        }
      else if (!strcmpi(psz,"$TCPIP"))
        {
          strAddrOut=hLodFile.GetToken();   // get the address value if any
          psz2=hLodFile.GetToken();         // get the out port value if any
          sscanf(psz2,"%hu",&wPortOut);
          psz2=hLodFile.GetToken();         // get the in port value if any
          sscanf(psz2,"%hu",&wPortIn);
        }
      else if (!strcmpi(psz,"$UDP"))
        {
          m_strAddrUdp=hLodFile.GetToken(); // get the address value if any
          psz2=hLodFile.GetToken();         // get the out port value if any
          sscanf(psz2,"%u",&m_dwPortUdp);
          fUdpCfgLoaded=TRUE;               // UDP configuration data loaded
        }
      else  // not recognised $ command
        {
        sMsg.Format("Unknown command - Line %d\n%s",hLodFile.LineNumber(),hLodFile.CurrentLine());
        AfxMessageBox(sMsg);
        nErrors++;
        }
      } // end if (hLodFile.CurrentChar()=='$')
    else
      {
      sMsg.Format("Illegal line format - Line %d\n%s",hLodFile.LineNumber(),hLodFile.CurrentLine());
      AfxMessageBox(sMsg);
      nErrors++;
      }
    }

  nBreakPts=0;      // cleanup breakpoint list
  HardwareReset();  // this is the default state if no registers are loaded

  // load registers
  hLodFile.Rewind();
  while (hLodFile.GetLine())
    {
    hLodFile.SkipWhiteSpace();
    if (hLodFile.LineLength()==0)
      ; // ignore blank lines
    else if ((hLodFile.CurrentChar()==';')||(hLodFile.CurrentChar()=='*'))
      ; // comments - do nothing
    else if (hLodFile.CurrentChar()=='$')
      ; // ignore commands
    else if (hLodFile.CurrentChar()==':')
      {
      uint ScanCount=0;           // set this for byte array variables
      byte *pbReg;                // set this to the variable's addr
      word MaxVal=0;              // set this for word variables
      word *pwReg;                // set this to the variable's addr
      psz=hLodFile.GetToken();    // get the variable name
      psz2=hLodFile.GetToken();   // get the value if any
      if (!strcmpi(psz,":A_REG"))
        {
        pbReg=A_REG;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":B_REG"))
        {
        pbReg=B_REG;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":C_REG"))
        {
        pbReg=C_REG;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":M_REG"))
        {
        pbReg=M_REG;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":N_REG"))
        {
        pbReg=N_REG;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":DIS_A_REG"))
        {
        pbReg=DIS_A_REG;
        ScanCount=12;
        }
      else if (!strcmpi(psz,":DIS_B_REG"))
        {
        pbReg=DIS_B_REG;
        ScanCount=12;
        }
      else if (!strcmpi(psz,":DIS_C_REG"))
        {
        pbReg=DIS_C_REG;
        ScanCount=12;
        }
      else if (!strcmpi(psz,":CLOCK_A"))
        {
        // don't load from registry, CLK_A setup with
        // actual time done in time module constructor
        }
      else if (!strcmpi(psz,":CLOCK_B"))
        {
        pbReg=CLK_B;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":ALARM_A"))
        {
        pbReg=ALM_A;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":ALARM_B"))
        {
        pbReg=ALM_B;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":TIMER_SCR_A"))
        {
        pbReg=SCR_A;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":TIMER_SCR_B"))
        {
        pbReg=SCR_B;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":INT_TIMER_A"))  // this is really the interval end value
        {
        pbReg=INTV_TV;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":INT_TIMER_B"))  // this is really the interval counter
        {
        pbReg=INTV_CNT;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":ACC_FACTOR"))
        {
        pbReg=ACC_F;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":TIMER_STATUS"))
        {
        fTimerStatusLoaded=TRUE;
        pbReg=TMR_S;
        ScanCount=14;
        }
      else if (!strcmpi(psz,":active_bank"))
        {
        pbReg=active_bank;
        ScanCount=16;
        }
      else if (!strcmpi(psz,":G_REG"))
        {
        pwReg=&G_REG;
        MaxVal=0xff;
        }
      else if (!strcmpi(psz,":F_REG"))
        {
        pwReg=&F_REG;
        MaxVal=0xff;
        }
      else if (!strcmpi(psz,":ST_REG"))
        {
        pwReg=&ST_REG;
        MaxVal=0xff;
        }
      else if (!strcmpi(psz,":Q_REG"))
        {
        pwReg=&Q_REG;
        MaxVal=13;
        }
      else if (!strcmpi(psz,":P_REG"))
        {
        pwReg=&P_REG;
        MaxVal=13;
        }
      else if (!strcmpi(psz,":KEY_REG"))
        {
        pwReg=&KEY_REG;
        MaxVal=0xff;
        }
      else if (!strcmpi(psz,":XST_REG"))
        {
        pwReg=&XST_REG;
        MaxVal=0x3f;
        }
      else if (!strcmpi(psz,":FI_REG"))
        {
        static word *pwRegs[]={ &FI_REG,&FI_REG_Timer,&m_wIrFI_REG };
        for (uint i=0; i < (sizeof(pwRegs)/sizeof(*pwRegs)) && (*psz2 != 0 && *psz2 != ';'); ++i)
          {
          uint Val;
          sscanf(psz2,"%X",&Val);
          if (Val>0x3fff)
            goto Error;
          *pwRegs[i]=Val;
          psz2 = hLodFile.GetToken();
          }
        }
      else if (!strcmpi(psz,":CARRY"))
        {
        pwReg=&CARRY;
        MaxVal=1;
        }
      else if (!strcmpi(psz,":KEYDOWN"))
        {
        pwReg=&KEYDOWN;
        MaxVal=1;
        }
      else if (!strcmpi(psz,":BATTERY"))
        {
        pwReg=&BATTERY;
        MaxVal=1;
        }
      else if (!strcmpi(psz,":PC_REG"))
        {
        fPC_REGSet=TRUE;
        pwReg=&NEW_PC_REG;
        MaxVal=0xffff;
        }
      else if (!strcmpi(psz,":RET_STK0"))
        {
        pwReg=&RET_STK0;
        MaxVal=0xffff;
        }
      else if (!strcmpi(psz,":RET_STK1"))
        {
        pwReg=&RET_STK1;
        MaxVal=0xffff;
        }
      else if (!strcmpi(psz,":RET_STK2"))
        {
        pwReg=&RET_STK2;
        MaxVal=0xffff;
        }
      else if (!strcmpi(psz,":RET_STK3"))
        {
        pwReg=&RET_STK3;
        MaxVal=0xffff;
        }
      else if (!strcmpi(psz,":DIS_ANNUN_REG"))
        {
        pwReg=&DIS_ANNUN_REG;
        MaxVal=0xfff;
        }
      else if (!strcmpi(psz,":sleep_mode"))
        {
        pwReg=&eSleepMode;
        MaxVal=2;
        }
      else if (!strcmpi(psz,":dis_mode"))
        {
        pwReg=&DisplayOn;
        MaxVal=1;
        }
      else if (!strcmpi(psz,":perph_in_control"))
        {
        pwReg=&perph_in_control;
        MaxVal=1;
        }
      else if (!strcmpi(psz,":perph_selected"))
        {
        pwReg=&perph_selected;
        MaxVal=0xff;
        }
      else if (!strcmpi(psz,":ram_selected"))
        {
        pwReg=&ram_selected;
        MaxVal=MAX_RAM-1;
        }
      else if (!strcmpi(psz,":TIMER_SEL"))
        {
        pwReg=&TimerSelA;
        TimerSelA=!TimerSelA;   // value in file is backwards
        MaxVal=1;
        }
      else if (!strcmpi(psz,":hex_mode"))
        {
        uint Val;
        sscanf(psz2,"%X",&Val);
        if (Val==1)
          BASE=16;
        else if (Val==0)
          BASE=10;
        else
          goto Error;
        }
      else if (!strcmpi(psz,":PT_REG"))
        {
        uint Val;
        sscanf(psz2,"%d",&Val);
        if (Val==1)
          PT_REG=&Q_REG;
        else if (Val==0)
          PT_REG=&P_REG;
        else
          goto Error;
        }
      else if (!strcmpi(psz,":RAM"))
        {
        uint RegIndex;
        sscanf(psz2,"%X",&RegIndex);
        if (RegIndex>=MAX_RAM)
          goto Error;
        for (int i=6;i>=0;i--)
          {
          uint Val;
          psz2=hLodFile.GetToken();
          sscanf(psz2,"%X",&Val);
          if (Val>0xff)
            goto Error;
          pRAM[RegIndex].Reg[i]=Val;
          }
        }
      else if (!strcmpi(psz,":HPIL_REG"))
        {
        for (uint i = 0; i < sizeof(m_pHpil->HPIL_REG); ++i)
          {
          uint Val;
          sscanf(psz2,"%X",&Val);
          if (Val>0xff)
            goto Error;
          m_pHpil->HPIL_REG[i]=(byte) Val;
          psz2=hLodFile.GetToken();
          }
        }
      else if (!strcmpi(psz,":BREAKPT"))
        {
        uint Enable,Val;
        sscanf(psz2,"%u",&Enable);
        psz2=hLodFile.GetToken();
        sscanf(psz2,"%X",&Val);
        if (nBreakPts<(sizeof(BreakPts)/sizeof(BreakPts[0])))
          {
          BreakPts[nBreakPts].En=Enable;
          BreakPts[nBreakPts].PageBank=(Val>>16)&0x3;
          BreakPts[nBreakPts].Addr=Val&0xFFFF;
          ++nBreakPts;
          }
        }
      else
        goto Error;
      if (ScanCount)
        {
        if (ScanCount!=strlen(psz2))    // there better be exactly the right number of chars
          goto Error;
        for (signed int i=ScanCount-1;i>=0;i--)
          {
          char ch=psz2[ScanCount-1-i];
          if ((ch>='0')&&(ch<='9'))
            pbReg[i]=ch-'0';
          else if  ((ch>='A')&&(ch<='F'))
            pbReg[i]=ch-'A'+10;
          else if  ((ch>='a')&&(ch<='f'))
            pbReg[i]=ch-'a'+10;
          else
            goto Error;
          }
        }
      if (MaxVal)
        {
        uint Val;
        sscanf(psz2,"%X",&Val);
        if (Val>MaxVal)
          goto Error;
        *pwReg=Val;
        }
      }
    else
      {
      // ignore any other type of line
      }
    continue;
    Error:
      {
      sMsg.Format("Illegal line format - Line %d\n%s",hLodFile.LineNumber(),hLodFile.CurrentLine());
      AfxMessageBox(sMsg);
      nErrors++;
      }
    }
  hLodFile.Close();

  // normalize values
  if (DisplayOn==1 && eSleepMode==eDeepSleep) // display state and sleep mode are inconsistent - probably since sleep mode is new parameter
    eSleepMode=eLightSleep;
  if (eKeystate==eKbdIdle && KEYDOWN)         // restore a pressed key
    {
    MinCLRKEY=3;
    eKeystate=eKbdPressed;
    }
  for (uint page=0;page<0xf;page++)
    if (PageMatrix[page][active_bank[page]-1]==NULL)
      active_bank[page]=1;
  if (fPC_REGSet)
    SetPC(NEW_PC_REG);

  if (fTimer && !fTimerStatusLoaded)  // time module equipped but timer status not loaded
    TMR_S[1]|=0x02;                   // bit 5 - set PUS (Power Up Status)

  m_fUdpCfgLoaded=fUdpCfgLoaded;      // UDP configuration data loaded

  if (fPrinter)                       // Thermal Printer
    {
    // at startup we don't know the printer mode, so we force the HP41 to send the MODE CHANGE
    // commmand at the beginning to synchronize the printer mode status of the HP41 and the printer

    const byte byUF12 = GetFlag(12); // get status of user flag 12

    // inverse printer status bit "#05 DWM Double wide Mode" to the current Flag 12 setting,
    // the printer ROM recognize this and sends a MODE CHANGE command with the current
    // calculator settings to the printer before sending a data byte
    m_byPrtStat&=~(1<<5);
    m_byPrtStat|=((byUF12^0x1)<<5);
    }

  if (fPrinter || fInfrared)          // have a Thermal or Infrared Printer
    {
    // initialize Thermal or Infrared Printer interface
    Udp::getInstance()->SetParam(m_strAddrUdp,m_dwPortUdp);
    }

  ReconfigHepax();      // reconfigure HEPAX ROM to free module page
  ReconfigRamboxBS();   // reconfigure W&W RAMBOX II bank switcher mapping

  // load any user code programs
  for (int i=0;i<UCArray.GetSize();i++)
    {
    char szError[64]="";
    _makepath(szUCFileName,app_drive,app_dir,UCArray[i],"RAW");
    if (!GetUserCode(szUCFileName,szError))
      {
      sMsg.Format("Error: %s\nFile: %s",szError,szUCFileName);
      AfxMessageBox(sMsg);
      nErrors++;
      }
    }

  theApp.m_pMainWnd->InvalidateRect(pRectLCD,FALSE);
  theApp.m_pMainWnd->InvalidateRect(pRectAnnun,FALSE);

  return(nErrors);
  }


/****************************/
// writes out .LOD file
// returns 0 if successful, or number of errors
/****************************/
int HP41::SaveConfig(
  const char *pszLodFile,
  flag bNoMessage)
  {
  WFile hLodFile;
  HANDLE hTempFile;
  DWORD dwBytesWritten;
  char szBuf[128];

  // get full path
  char szMsgFullPath[_MAX_PATH];
  char app_drive[_MAX_DRIVE],app_dir[_MAX_DIR];
  _splitpath(theApp.szAppFullPath,app_drive,app_dir,NULL,NULL);
  _makepath(szMsgFullPath,app_drive,app_dir,"V41TEMP","lod");

  // module list is empty, save nothing
  if (ModuleList.IsEmpty())
    return(0);

  // create a temp file to write out to
  hTempFile=CreateFile(szMsgFullPath,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
  if (hTempFile==INVALID_HANDLE_VALUE)
    {
    if (!bNoMessage)
      AfxMessageBox(CString("Unable to create temporary file: ")+szMsgFullPath);
    return(1);
    }

  // if lod file already exists, copy it to new lod file minus blank, $module, $page, $reg, $tcpip and $get commands and : lines
  if (hLodFile.Open(pszLodFile))
    {
    while (hLodFile.GetLine())
      {
      hLodFile.SkipWhiteSpace();
      if (hLodFile.EOLN())
        continue;
      else if (hLodFile.CurrentChar()=='$')
        {
        char *pszToken=hLodFile.GetToken();
        if (0==strcmpi(pszToken,"$PAGE"))
          continue;
        else if (0==strcmpi(pszToken,"$REG"))
          continue;
        else if (0==strcmpi(pszToken,"$MODULE"))
          continue;
        else if (0==strcmpi(pszToken,"$GET"))
          continue;
        else if (0==strcmpi(pszToken,"$TCPIP"))
          continue;
        else if (0==strcmpi(pszToken,"$UDP"))
          continue;
        }
      else if (hLodFile.CurrentChar()==':')
        continue;
      WriteFile(hTempFile,hLodFile.CurrentLine(),static_cast<DWORD>(hLodFile.LineLength()),&dwBytesWritten,NULL);
      WriteFile(hTempFile,"\r\n",2,&dwBytesWritten,NULL);
      }
    hLodFile.Close();
    }

  // write out MODULE config
  POSITION pos=ModuleList.GetHeadPosition();
  while (pos!=NULL)
    {
    ModuleHeader *pModule=(ModuleHeader *)ModuleList.GetAt(pos);
    char fname[_MAX_FNAME];
    _splitpath(pModule->szFullFileName,NULL,NULL,fname,NULL);
    CString strName(fname);
    strName.Replace(" ","%20");             // encode spaces to %20 for module name
    sprintf(szBuf,"$MODULE %s\t\t%s;%s\r\n",(LPCTSTR)strName,strName.GetLength() < 8 ? "\t" : "",pModule->szTitle);
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    if (pModule->AppAutoUpdate)             // update the module file if this attribute is set (MLDL/RAM use)
      SaveMOD(pModule,pModule->szFullFileName);
    ModuleList.GetNext(pos);
    }

  // HP-IL TCP/IP settings
  sprintf(szBuf,"$TCPIP %s %u %u\t;HPIL IP Settings\r\n",(LPCTSTR)strAddrOut,wPortOut,wPortIn);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);

  if (fPrinter || fInfrared || m_fUdpCfgLoaded) // have a Thermal or Infrared Printer or UDP data loaded
    {
    sprintf(szBuf,"$UDP %s %u\t\t;Thermal Printer Settings\r\n",(LPCTSTR)m_strAddrUdp,m_dwPortUdp);
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    }

  // write out registers
  char Reg14Format[]=":%s %0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%s\r\n";
  char Reg12Format[]=":%s %0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%s\r\n";
  char RAMRegFormat[]=":RAM %03X %02X %02X %02X %02X %02X %02X %02X\r\n";
  char szRegName[20];
  byte *pbReg;

  // CPU regs
  strcpy(szRegName,"A_REG");  pbReg=A_REG;
  sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;CPU A Register (56 bits)");
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  strcpy(szRegName,"B_REG");  pbReg=B_REG;
  sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;CPU B Register (56 bits)");
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  strcpy(szRegName,"C_REG");  pbReg=C_REG;
  sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;CPU C Register (56 bits)");
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  strcpy(szRegName,"M_REG");  pbReg=M_REG;
  sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;CPU M Register (56 bits)");
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  strcpy(szRegName,"N_REG");  pbReg=N_REG;
  sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;CPU N Register (56 bits)");
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);

  sprintf(szBuf,":G_REG %02X\t\t\t;CPU G Register (8 bits)\r\n",G_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":F_REG %02X\t\t\t;CPU Flag Out Register for Beeper (8 bits)\r\n",F_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":ST_REG %02X\t\t\t;CPU STATUS Register (14 bits)\r\n",ST_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":Q_REG %0X\t\t\t;CPU Q Pointer (0-D)\r\n",Q_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":P_REG %0X\t\t\t;CPU P Pointer (0-D)\r\n",P_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":KEY_REG %02X\t\t\t;CPU KEY Register (8 bits)\r\n",KEY_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":PT_REG %d\t\t\t;CPU Active Pointer P (0) or Q (1)\r\n",(PT_REG==&Q_REG));
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":XST_REG %02X\t\t\t;CPU XST Register (6 bits)\r\n",XST_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":FI_REG %04X %04X %04X\t\t;CPU FI Register (14 bits)\r\n",FI_REG,FI_REG_Timer,m_wIrFI_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":CARRY %0X\t\t\t;CPU CARRY Flag (0-1)\r\n",CARRY);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":KEYDOWN %0X\t\t\t;CPU KEYDOWN Flag (0-1)\r\n",KEYDOWN);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":BATTERY %0X\t\t\t;CPU BATTERY Flag (0-1)\r\n",BATTERY);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":PC_REG %04X\t\t\t;CPU Program Counter (16 bits)\r\n",PC_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":RET_STK0 %04X\t\t\t;CPU Return Stack 0 Register (16 bits)\r\n",RET_STK0);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":RET_STK1 %04X\t\t\t;CPU Return Stack 1 Register (16 bits)\r\n",RET_STK1);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":RET_STK2 %04X\t\t\t;CPU Return Stack 2 Register (16 bits)\r\n",RET_STK2);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":RET_STK3 %04X\t\t\t;CPU Return Stack 3 Register (16 bits)\r\n",RET_STK3);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);

  // display registers
  strcpy(szRegName,"DIS_A_REG");  pbReg=DIS_A_REG;
  sprintf(szBuf,Reg12Format,szRegName,pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;LCD A Register (12 bits - bit 8 for starburst 1-12)");
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  strcpy(szRegName,"DIS_B_REG");  pbReg=DIS_B_REG;
  sprintf(szBuf,Reg12Format,szRegName,pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;LCD B Register (48 bits - bits 7-4 for starburst 1-12)");
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  strcpy(szRegName,"DIS_C_REG");  pbReg=DIS_C_REG;
  sprintf(szBuf,Reg12Format,szRegName,pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;LCD C Register (48 bits - bits 3-0 for starburst 1-12)");
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":DIS_ANNUN_REG %03X\t\t;LCD Annunciator Register (12 bits)\r\n",DIS_ANNUN_REG);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);

  // state vars
  sprintf(szBuf,":sleep_mode %0X\t\t\t;Sleep State: 0=Awake, 1=Light, 2=Deep\r\n",eSleepMode);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":dis_mode %0X\t\t\t;LCD On/Off\r\n",DisplayOn);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":hex_mode %01X\t\t\t;CPU HEX Mode On/Off\r\n",(BASE==16));
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":perph_in_control %0X\t\t;Peripheral On/Off\r\n",perph_in_control);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":perph_selected %02X\t\t;Peripheral Value (00-FF)\r\n",perph_selected);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  sprintf(szBuf,":ram_selected %03X\t\t;RAM Register Selected (000-3FF)\r\n",ram_selected);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
  strcpy(szRegName,"active_bank");  pbReg=active_bank;
  sprintf(szBuf,":%s %0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X%0X\t;Page Bank State (16 pages)\r\n",szRegName,pbReg[15],pbReg[14],pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0]);
  WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);

  // ram registers
  for (uint RegIndex=0;RegIndex<MAX_RAM;RegIndex++)
    {
    if (RamExist(RegIndex))
      {
      sprintf(szBuf,RAMRegFormat,RegIndex,pRAM[RegIndex].Reg[6],pRAM[RegIndex].Reg[5],pRAM[RegIndex].Reg[4],pRAM[RegIndex].Reg[3],pRAM[RegIndex].Reg[2],pRAM[RegIndex].Reg[1],pRAM[RegIndex].Reg[0]);
      WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
      }
    }

  if (fHPIL)          // HP-IL hardware
    {
    // HP-IL 1LB6 registers
    int ipos = sprintf(szBuf,":HPIL_REG");
    for (uint i = 0; i < sizeof(m_pHpil->HPIL_REG); ++i)
      {
      ipos += sprintf(&szBuf[ipos]," %02X",m_pHpil->HPIL_REG[i]);
      }
    sprintf(&szBuf[ipos],"\t;HPIL Register (72 bits)\r\n");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    }

  // timer registers
  if (fTimer)
    {
    strcpy(szRegName,"CLOCK_A");  pbReg=CLK_A;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;Timer CLOCK A Register (56 bits)");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    strcpy(szRegName,"CLOCK_B");  pbReg=CLK_B;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;Timer CLOCK B Register (56 bits)");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    strcpy(szRegName,"ALARM_A");  pbReg=ALM_A;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;Timer ALARM A Register (56 bits)");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    strcpy(szRegName,"ALARM_B");  pbReg=ALM_B;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t\t;Timer ALARM B Register (56 bits)");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    strcpy(szRegName,"TIMER_SCR_A");  pbReg=SCR_A;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t;Timer SCRATCH A Register (56 bits)");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    strcpy(szRegName,"TIMER_SCR_B");  pbReg=SCR_B;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t;Timer SCRATCH B Register (56 bits)");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    strcpy(szRegName,"INT_TIMER_A");  pbReg=INTV_TV;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t;Timer INTERVAL A Register (56 bits but only 20 used)");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    strcpy(szRegName,"INT_TIMER_B");  pbReg=INTV_CNT;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t;Timer INTERVAL A Register (56 bits but only 20 used)");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    strcpy(szRegName,"ACC_FACTOR");  pbReg=ACC_F;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t;Timer ACCURACY FACTOR Register");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    strcpy(szRegName,"TIMER_STATUS");  pbReg=TMR_S;
    sprintf(szBuf,Reg14Format,szRegName,pbReg[13],pbReg[12],pbReg[11],pbReg[10],pbReg[9],pbReg[8],pbReg[7],pbReg[6],pbReg[5],pbReg[4],pbReg[3],pbReg[2],pbReg[1],pbReg[0],"\t;Timer STATUS Register");
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    sprintf(szBuf,":TIMER_SEL %0X\t\t\t;Timer Selected A or B (0-1)\r\n",!TimerSelA);
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    }

  // code breakpoints
  for (int i=0;i<nBreakPts;++i)
    {
    sprintf(szBuf,":BREAKPT %u %05X\t\t;Code Breakpoint\r\n",BreakPts[i].En,(static_cast<uint>(BreakPts[i].PageBank)<<16)|BreakPts[i].Addr);
    WriteFile(hTempFile,szBuf,static_cast<DWORD>(strlen(szBuf)),&dwBytesWritten,NULL);
    }

  CloseHandle(hTempFile);
  DeleteFile(pszLodFile);
  BOOL bRes=MoveFile(szMsgFullPath,pszLodFile);
  if (bRes==FALSE)
    {
    if (!bNoMessage)
      {
      CString sMsg;
      sMsg.Format("Unable to rename temporary file: %s to %s",szMsgFullPath,pszLodFile);
      AfxMessageBox(sMsg);
      }
    return(1);
    }
  AfxGetApp()->AddToRecentFileList(theApp.szSessionName);
  return(0);
  }


/****************************/
// This decoder returns the type of the current byte
// based on its value and the previous byte types
/****************************/
enum
  {
  FIRST,          // unknown previous
  SINGLE,         // single byte inst
  DOUBLE1,        // first byte of double byte inst
  DOUBLE2,        // 2 of 2
  TRIPLE1,        // 1 of 3
  TRIPLE2,        // 2 of 3
  TRIPLE3,        // 3 of 3
  TEXT,           // alpha string
  JUMP_TEXT,      // first byte of gto, xeq, w
  GLOBAL1,        // first byte of global
  GLOBAL2,        // second byte of global
  GLOBAL_LBL,     // third byte of label
  GLOBAL_END,     // third byte of end
  GLOBAL_KEYCODE, // fourth byte of label
  LOCAL1,         // special case of DOUBLE for local jumps
  LOCAL2          // 2 of 2
  };
/****************************/
int HP41::DecodeUCByte(
  int PrevType,
  byte CurrByte)
  {
  static int TextCount=0;
  int CurrType;

  switch (PrevType)
    {
    case FIRST:
    case SINGLE:
      {
      if ((CurrByte>=0x90 && CurrByte<=0xB0) || (CurrByte>=0xCE && CurrByte<=0xCF))  // 2 byte
        CurrType=DOUBLE1;
      else if(CurrByte>=0xB1 && CurrByte<=0xBF)       // GTO 00 .. GTO 14 2 byte
        CurrType=LOCAL1;
      else if(CurrByte>=0xD0 && CurrByte<=0xEF)       // GTO, XEQ 3 byte
        CurrType=TRIPLE1;
      else if (CurrByte>=0xF0 && CurrByte<=0xFF)      // text
        {
        TextCount=CurrByte&0x0F;
        CurrType=TEXT;
        }
      else if (CurrByte>=0x1D && CurrByte<=0x1F)      // GTO, XEQ, W text
        CurrType=JUMP_TEXT;
      else if (CurrByte>=0xC0 && CurrByte<=0xCD)      // global
        CurrType=GLOBAL1;
      else
        CurrType=SINGLE;
      break;
      }

    case LOCAL1:
      {
      CurrType=LOCAL2;
      break;
      }

    case DOUBLE1:
      {
      CurrType=DOUBLE2;
      break;
      }
    case LOCAL2:                                      // the short label offset (dbbb rrrr), Zenrom Pg 31 incorrectly says drrr rbbb
                                                      // d=direction 0 pos/1 neg, b=byte offset, r=register offset
    case DOUBLE2:
      {
      CurrType=DecodeUCByte(FIRST,CurrByte);
      break;
      }

    case TRIPLE1:
      {
      CurrType=TRIPLE2;
      break;
      }
    case TRIPLE2:
      {
      CurrType=TRIPLE3;
      break;
      }
    case TRIPLE3:
      {
      CurrType=DecodeUCByte(FIRST,CurrByte);
      break;
      }

    case TEXT:
      {
      if (TextCount)
        {
        TextCount--;
        CurrType=TEXT;
        }
      else
        CurrType=DecodeUCByte(FIRST,CurrByte);
      break;
      }
    case JUMP_TEXT:
      {
      if (CurrByte>0xF0)
        {
        TextCount=CurrByte&0x0F;
        CurrType=TEXT;
        }
      else      // no text after
        CurrType=DecodeUCByte(FIRST,CurrByte);
      break;
      }

    case GLOBAL1:
      {
      CurrType=GLOBAL2;
      break;
      }
    case GLOBAL2:
      {
      if (CurrByte>=0xF0)       // its a LBL
        {
        TextCount=CurrByte&0x0F;
        CurrType=GLOBAL_LBL;
        }
      else                      // its the END
        CurrType=GLOBAL_END;
      break;
      }
    case GLOBAL_LBL:
      {
      if (TextCount)
        {
        TextCount--;
        CurrType=GLOBAL_KEYCODE;
        }
      else
        CurrType=DecodeUCByte(FIRST,CurrByte);
      break;
      }
    case GLOBAL_END:
      {
      CurrType=DecodeUCByte(FIRST,CurrByte);
      break;
      }
    case GLOBAL_KEYCODE:
      {
      CurrType=DecodeUCByte(TEXT,CurrByte);
      break;
      }
    }
  return(CurrType);
  }


/****************************/
// returns the relative offset between the two
/****************************/
void HP41::CalcOffset(
  int LowReg,
  int LowByte,
  int HighReg,
  int HighByte,
  int &RegOff,
  int &ByteOff)
  {
  if (HighReg==0 && HighByte==0)   // this means there is nothing at the high addr
    {
    RegOff=0;
    ByteOff=0;
    }
  else
    {
    RegOff=HighReg-LowReg;
    ByteOff=HighByte-LowByte;
    if (ByteOff<0)
      {
      RegOff--;
      ByteOff+=7;
      }
    }
  }


/****************************/
// next byte in memory
/****************************/
void HP41::NextByte(int &RegIndex,int &ByteIndex,int Step)
  {
  // next byte
  ByteIndex-=Step;
  if (ByteIndex<0)
    {
    ByteIndex+=7;
    --RegIndex;
    }
  }


/****************************/
// position on current global and this finds the previous global
// and returns its starting byte and register offset and addr
/****************************/
void HP41::PrevGlobal(
  int CurrReg,
  int CurrByte,
  int &PrevReg,
  int &PrevByte,
  int &RegOff,
  int &ByteOff) const
  {
  int RegIndex=CurrReg;
  int ByteIndex=CurrByte;

  // extract the offsets from the current global
  ByteOff=(pRAM[RegIndex].Reg[ByteIndex]&0x0e)>>1;
  if (ByteOff>=7)         // just in case something is corrupted
    ByteOff=0;
  RegOff=(pRAM[RegIndex].Reg[ByteIndex]&0x01)<<8;
  NextByte(RegIndex,ByteIndex);
  RegOff=RegOff | pRAM[RegIndex].Reg[ByteIndex];

  // calc the absolute address of the prev global
  if (RegOff==0 && ByteOff==0)      // if there is no prev global
    {
    PrevReg=0;
    PrevByte=0;
    }
  else
    {
    PrevReg=CurrReg+RegOff;
    PrevByte=CurrByte+ByteOff;
    if (PrevByte>=7)
      {
      PrevReg++;
      PrevByte-=7;
      }
    }
  }


/****************************/
// check previous global object for GLOBAL_END,
// if object is GLOBAL_END return program address behind
// previous GLOBAL_END else address of previous object
/****************************/
bool HP41::IsPrevGlobalEnd(
  int CurrReg,
  int CurrByte,
  int &StartReg,
  int &StartByte)
  {
  bool bGlobalEnd;
  int  RegOff;      // register offset to previous global
  int  ByteOff;     // byte offset to previous global

  // check if previous object is a GLOBAL_END
  PrevGlobal(CurrReg,CurrByte,StartReg,StartByte,RegOff,ByteOff);

  bGlobalEnd = (StartReg|StartByte)==0;     // end of chain
  if (!bGlobalEnd)
    {
    byte off0 = pRAM[StartReg].Reg[StartByte];
    NextByte(StartReg,StartByte);
    byte off1 = pRAM[StartReg].Reg[StartByte];
    NextByte(StartReg,StartByte);
    byte off2 = pRAM[StartReg].Reg[StartByte];

    bGlobalEnd =    DecodeUCByte(FIRST,  off0) == GLOBAL1
                 && DecodeUCByte(GLOBAL1,off1) == GLOBAL2
                 && DecodeUCByte(GLOBAL2,off2) == GLOBAL_END;
    }

  if (bGlobalEnd)                           // current program begins directly after GLOBAL_END or at end of chain
    {
    if ((StartReg|StartByte)==0)            // end of chain
      {
      // REG 00
      StartReg=(pRAM[0x00d].Reg[2]<<4) | (pRAM[0x00d].Reg[1] >> 4);
      StartByte=0;
      }
    // next byte
    NextByte(StartReg,StartByte);
    }

  return bGlobalEnd;
  }


/****************************/
// Loads user code program
// returns 1 for success
/****************************/
int HP41::GetUserCode(
  const char *pszUCFile,
  char *pszError)
  {
  HANDLE hUCFile;   // file handle
  DWORD FileSize;   // size of file
  DWORD BytesRead;
  byte *pBuffer;    // program loaded into buffer
  int ProgSize;     // size of program in bytes up to and including any END inst
  int EndProg;      // file position of GLOBAL_END
  int EndAddr;      // the .END. address
  int FreeAddr;     // bottom of available free space
  int ByteType;     // type of current byte
  int RegIndex;     // register index for loop
  int ByteIndex;    // byte index for loop
  int RegOff;       // register offset to previous global
  int ByteOff;      // byte offset to previous global
  int PrevReg;      // register where last global starts (or start of program memory)
  int PrevByte;     // byte where last global starts
  int i;            // loop counter

  bool bOK = false; // no error detected

  // states to identify the raw file type
  typedef struct
  {
    bool bHP41Raw;              // this is a HP41 raw file
    bool bAllAddrNull;          // all address offsets are NULL
    bool bNullAfterNumber;      // NULL byte after every number, suspicion for HP42S file
    bool bPackedCode;           // packed code
  } RAWFILE;

  // actual raw file state
  RAWFILE sRawType={ true,true,true,true };

  // get .END. address
  EndAddr=((pRAM[0x00d].Reg[1]&0x0f)<<8) | pRAM[0x00d].Reg[0];
  if (EndAddr <= 0 || EndAddr >= MAX_RAM)
    {
    strcpy(pszError,"HP41 RAM memory corrupt, no valid .END. found");
    return(0);
    }

  // open file and copy it to a buffer
  hUCFile=CreateFile(pszUCFile,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
  if (hUCFile==INVALID_HANDLE_VALUE)
    {
    strcpy(pszError,"Unable to open file");
    return(0);
    }
  FileSize=GetFileSize(hUCFile,NULL);
  pBuffer=(byte*)malloc(FileSize);
  if (pBuffer)
    {
    ReadFile(hUCFile,pBuffer,FileSize,&BytesRead,NULL);
    }
  CloseHandle(hUCFile);
  if (pBuffer==NULL || FileSize!=BytesRead)
    {
    strcpy(pszError,"Error reading from file");
    free(pBuffer);
    return(0);
    }

  // get exact program size - there can be junk after the END instruction
  byte byLabel=0;
  word wOffset=0;
  bool bGlobEndFound=false;
  bool bNumberType=false;     // state for detecting numbers
  bool bPrevNumberType;
  RAWFILE sRawTypeEnd;        // raw file state at GLOBAL_END
  ByteType=FIRST;
  for (ProgSize=0;ProgSize<(int)FileSize;ProgSize++)
    {
    if (ByteType==GLOBAL_END)
      {
      bGlobEndFound=true;
      EndProg=ProgSize;       // save file position of GLOBAL_END
      sRawTypeEnd=sRawType;   // save raw file parser state at GLOBAL_END
      }

    ByteType=DecodeUCByte(ByteType,pBuffer[ProgSize]);
    switch (ByteType)
      {
       case GLOBAL1:          // global label (11bb bbbr)
         wOffset=pBuffer[ProgSize]&0x3F;
         break;
       case GLOBAL_END:
         if (wOffset!=0)      // is a calculated offset
         {
         short nAddr=ProgSize-wOffset-2;
         sRawType.bAllAddrNull=false;
         sRawType.bHP41Raw=sRawType.bHP41Raw && nAddr>=0;
         if (sRawType.bHP41Raw)
          {
          const int nType=DecodeUCByte(SINGLE,pBuffer[nAddr]);
          sRawType.bHP41Raw=sRawType.bHP41Raw && nType==GLOBAL1;
          }
         }
         break;

      case GLOBAL_LBL:        // global label found
        bOK=true;
        break;

      case LOCAL1:            // short GTO
        // get target label
        byLabel=pBuffer[ProgSize]&0x0F;
        break;
      case LOCAL2:            // this is the short label offset (dbbb rrrr), Zenrom Pg 31 incorrectly says drrr rbbb
                              // d=direction 0 pos/1 neg, b=byte offset, r=register offset
        {
        wOffset=pBuffer[ProgSize];
        short nAddr=(((wOffset&0x0F)*7)+((wOffset&0x70)>>4));
        if (nAddr!=0)         // is a calculated offset
          {
          sRawType.bAllAddrNull=false;
          if ((wOffset & 0x80))
            nAddr=-nAddr;
          nAddr=ProgSize+nAddr+1;
          // inside program range?
          sRawType.bHP41Raw=sRawType.bHP41Raw && nAddr>=0 && nAddr<(int)FileSize;
          if (sRawType.bHP41Raw)
            {
            const byte byData = pBuffer[nAddr];
            sRawType.bHP41Raw=sRawType.bHP41Raw && (byLabel==byData);
            }
          }
        break;
        }

      case TRIPLE1:           // 3 byte GTO/XEQ 11tt bbbr rrrr rrrr dlll llll
        wOffset=pBuffer[ProgSize]&0x0F;
        break;
      case TRIPLE2:
      case GLOBAL2:
        wOffset=(((wOffset&0x1)<<8)+pBuffer[ProgSize])*7+(wOffset>>1);
        break;
      case TRIPLE3:
        {
        byLabel=pBuffer[ProgSize];
        short nAddr=wOffset;
        if (nAddr!=0)         // is a calculated offset
          {
          sRawType.bAllAddrNull=false;
          if ((byLabel&0x80))
            nAddr=-nAddr;
          nAddr=ProgSize+nAddr-1;
          sRawType.bHP41Raw=sRawType.bHP41Raw && (nAddr>=0) && (nAddr<(int)FileSize);
          if (sRawType.bHP41Raw)
            {
            byte byData=pBuffer[nAddr];
            byLabel&=0x7F;    // remove offset direction bit
            if (byData==0xCF) // 2 byte LBL
              {
              if (nAddr+1<(int)FileSize)
                {
                byData=pBuffer[nAddr+1];
                }
              }
            else              // 1 byte LBL
              {
              ++byLabel;      // LBL 00 begin with code 01
              }
            sRawType.bHP41Raw=sRawType.bHP41Raw && (byLabel==byData);
            }
          }
        break;
        }
      }

      // is current byte a number (0-9 [10-19], '.' [1A], 'E' [1B], '+/-' [1C])?
      bPrevNumberType=bNumberType;
      bNumberType=(ByteType==SINGLE && pBuffer[ProgSize]>=0x10 && pBuffer[ProgSize]<=0x1C);
      if (bPrevNumberType && !bNumberType) // not a number any more
        {
        // number followed by NULL byte?
        sRawType.bNullAfterNumber=sRawType.bNullAfterNumber && (pBuffer[ProgSize]==0);
        }

      // single zero byte (NULL command)
      if ((ByteType==SINGLE && pBuffer[ProgSize]==0))
        {
        // is next byte a number?
        const byte byNextByte=(ProgSize+1 < (int)FileSize) ? pBuffer[ProgSize+1] : 0;
        const bool bNextNumberType=(byNextByte>=0x10 && byNextByte<=0x1C);

        // still packed code when before and after the NULL byte is a number
        sRawType.bPackedCode=sRawType.bPackedCode && bPrevNumberType && bNextNumberType;
        }
    }

  if (   ByteType!=GLOBAL_END // finished without GLOBAL_END
      && bGlobEndFound)       // but there was a GLOBAL_END earlier
    {
    ProgSize=EndProg;         // get saved file position of GLOBAL_END
    sRawType=sRawTypeEnd;     // restore raw file parser state at GLOBAL_END
    }

  // not a native HP41 User Code file
  const bool bHP41NonUserCode=(   !sRawType.bHP41Raw
                               || (sRawType.bAllAddrNull && (sRawType.bNullAfterNumber || !sRawType.bPackedCode)));
  if (bHP41NonUserCode)
    {
    const int nReply=AfxMessageBox(_T("Non-native HP41 User Code file detected!\nDo you want to continue?"),
      MB_YESNO|MB_ICONQUESTION|MB_SETFOREGROUND);
    if (nReply!=IDYES)
      {
      free(pBuffer);
      return(1);
      }
    }

  // find bottom of available memory - key assignments have 0xf0 in byte 6 - Zenrom pg 37-38
  // but exception Plotter module 82184A creates a buffer not terminated with 0xf0 in byte 6
  // or exception SandMath module with may have empty registers at buffer end
  FreeAddr=GetRamBufferEnd(); // go through buffers from bottom of RAM

  // make sure there is enough space - not counting any bytes available on register with .end.
  if (ProgSize>(EndAddr-FreeAddr)*7)
    {
    // no. of missing registers in RAM
    int nMissingReg=((ProgSize-(EndAddr-FreeAddr)*7)+6)/7;
    sprintf(pszError,"Not enough free registers, %d register(s) missing",nMissingReg);
    free(pBuffer);
    return(0);
    }

  // get previous global for offset calculation at GLOBAL1
  PrevGlobal(EndAddr,2,PrevReg,PrevByte,RegOff,ByteOff);

  // set starting location
  RegIndex=EndAddr;
  for (ByteIndex=2;ByteIndex<6;ByteIndex++) // bytes 2-0 were used by .END.
    {
    if (pRAM[RegIndex].Reg[ByteIndex+1])    // if non null
      break;                                // cant use it - stop here
    }

  // move program to RAM
  ByteType=FIRST;
  for (i=0;i<ProgSize;i++)
    {
    ByteType=DecodeUCByte(ByteType,pBuffer[i]);

    // fixup global chain as we go
    switch (ByteType)
      {
      case GLOBAL1:                         // if global, link it into previous global
        {
        CalcOffset(RegIndex,ByteIndex,PrevReg,PrevByte,RegOff,ByteOff);
        PrevReg=RegIndex;
        PrevByte=ByteIndex;
        pBuffer[i]=0xc0 | (ByteOff<<1) | ((RegOff&0x100)>>8);
        break;
        }
      case GLOBAL2:
        {
        pBuffer[i]=RegOff&0xff;
        break;
        }
      case GLOBAL_LBL:
        {
        break;
        }
      case GLOBAL_END:
        {
        pBuffer[i]=0x0d;
        break;
        }
      case GLOBAL_KEYCODE:                  // clear any key assignment from global label
      case LOCAL2:                          // or this is the short label offset (dbbb rrrr), Zenrom Pg 31 incorrectly says drrr rbbb
                                            // d=direction 0 pos/1 neg, b=byte offset, r=register offset
      case TRIPLE2:                         // or this the long label offset
        {
        pBuffer[i]=0;
        break;
        }
      case TRIPLE1:
        {
        pBuffer[i] &= 0xF0;                 // save opcode, delete part of offset
        break;
        }
      case TRIPLE3:
        {
        pBuffer[i] &= 0x7F;                 // save label, delete part of offset
        break;
        }
      }

    // copy byte
    if (RamExist(RegIndex))                 // just to be safe
      pRAM[RegIndex].Reg[ByteIndex]=pBuffer[i];

    // go to next location
    NextByte(RegIndex,ByteIndex);           // go to next ram reg
    }
  free(pBuffer);

  for (i=ByteIndex;i>=0;i--)                // null out the rest of this register
    pRAM[RegIndex].Reg[i]=0;
  if (ByteIndex<2)                          // grab another register for if necessary
    {
    RegIndex--;
    ByteIndex=6;
    for (i=ByteIndex;i>=0;i--)              // null out this register too
      pRAM[RegIndex].Reg[i]=0;
    }

  // create new .END.
  // see Zenrom pg 33-34
  CalcOffset(RegIndex,2,PrevReg,PrevByte,RegOff,ByteOff);
  pRAM[RegIndex].Reg[2]=0xc0 | (ByteOff<<1) | ((RegOff&0x100)>>8);
  pRAM[RegIndex].Reg[1]=RegOff&0xff;
  pRAM[RegIndex].Reg[0]=0x2d;

  // save new .END. address
  pRAM[0x00d].Reg[1]=(pRAM[0x00d].Reg[1]&0xf0) | ((RegIndex>>8)&0x0f);
  pRAM[0x00d].Reg[0]=RegIndex&0xff;
  return(1);
  }


/****************************/
// traverse catalog and return labels one at a time (reverse order)
// returns 0 when there are no more
/****************************/
enum              // for LoopStatus variable in Catalog1()
  {
  FIND,           // looking for a LBL
  DONE,           // finished decoding LBL
  NEXT            // go to next global
  };
/****************************/
int HP41::Catalog1(         // returns 0 when no more labels
  flag &fFirst,             // set to 1 for the first invocation
  Cat1Label *pLbl)
  {
  static int CurrReg;
  static int CurrByte;
  static int LastEndReg;    // save the last END addr
  static int LastEndByte;
  int ByteType;     // type of current byte
  int RegIndex;     // register index for loop
  int ByteIndex;    // byte index for loop
  int RegOff;       // register offset to previous global
  int ByteOff;      // byte offset to previous global
  int PrevReg;      // register where prev global starts (or start of program memory)
  int PrevByte;     // byte where prev global starts
  int LabelIndex;   // counts from 0..n for indexing text string
  int TextCount;    // counts from n..0 for counting text length
  int LoopStatus;   // state variable for loop

  // first time we call this
  if (fFirst)
    {
    int EndAddr=((pRAM[0x00d].Reg[1]&0x0f)<<8) | pRAM[0x00d].Reg[0];
    CurrReg=EndAddr;
    CurrByte=2;
    fFirst=FALSE;
    }

  LoopStatus=FIND;
  while (LoopStatus!=DONE)
    {
    if (CurrReg==0 && CurrByte==0)
      return(0);

    // decode current instruction - should be a global LBL or END
    RegIndex=CurrReg;
    ByteIndex=CurrByte;
    ByteType=FIRST;
    LabelIndex=0;
    TextCount=0;
    LoopStatus=FIND;
    while (LoopStatus==FIND)
      {
      ByteType=DecodeUCByte(ByteType,pRAM[RegIndex].Reg[ByteIndex]);
      switch (ByteType)
        {
        case GLOBAL1:
        case GLOBAL2:
          break;
        case GLOBAL_LBL:
          {
          TextCount=pRAM[RegIndex].Reg[ByteIndex]&0x0f;
          LabelIndex=0;
          break;
          }
        case GLOBAL_END:
          {
          LoopStatus=NEXT;
          LastEndReg=CurrReg;
          LastEndByte=CurrByte;

          // check if previous object is GLOBAL_END -> then we have no GLOBAL_LBL
          int StartReg;
          int StartByte;
          if (IsPrevGlobalEnd(CurrReg,CurrByte,StartReg,StartByte))
            {
            // check if between start and end address is code
            int NullReg = StartReg;
            int NullByte = StartByte;

            bool bNull = true;              // all 0 bytes
            while (bNull && (NullReg!=CurrReg || NullByte!=CurrByte))
              {
              bNull = (pRAM[NullReg].Reg[NullByte] == 0);
              NextByte(NullReg,NullByte);
              }

            if (!bNull)                     // has code
              {
              // this code has no GLOBAL_LBL, choose END or .END. as text
              strcpy(pLbl->szText,((pRAM[RegIndex].Reg[ByteIndex] & 32) != 0) ? ".END." : "END");
              pLbl->StartReg=StartReg;
              pLbl->StartByte=StartByte;
              pLbl->EndReg=LastEndReg;
              pLbl->EndByte=LastEndByte;
              LoopStatus=DONE;              // ready to return this value
              }
            }
          break;
          }
        case GLOBAL_KEYCODE:
          break;
        case TEXT:
          {
          if (TextCount)
            {
            pLbl->szText[LabelIndex++]=pRAM[RegIndex].Reg[ByteIndex];
            TextCount--;
            }
          if (TextCount==1)
            {
            int StartReg;
            int StartByte;

            LoopStatus=NEXT;                // not first GLOBAL_LBL

            // check if previous object is GLOBAL_END -> then this is the first GLOBAL_LBL
            if (IsPrevGlobalEnd(CurrReg,CurrByte,StartReg,StartByte))
              {
              pLbl->szText[LabelIndex]=0;   // null terminate
              pLbl->StartReg=StartReg;
              pLbl->StartByte=StartByte;
              pLbl->EndReg=LastEndReg;
              pLbl->EndByte=LastEndByte;
              LoopStatus=DONE;              // ready to return this value
              }
            }
          break;
          }
        default:    // we have landed on something other than global so get out
          {
          pLbl->szText[0]=0;
          pLbl->StartReg=0;
          pLbl->StartByte=0;
          pLbl->EndReg=0;
          pLbl->EndByte=0;
          LoopStatus=DONE;
          break;
          }
        }

      // next byte
      NextByte(RegIndex,ByteIndex);
      }

    // setup for next time around
    PrevGlobal(CurrReg,CurrByte,PrevReg,PrevByte,RegOff,ByteOff);
    CurrReg=PrevReg;
    CurrByte=PrevByte;
    }

  return(1);
  }


/****************************/
// write user code program out
/****************************/
int HP41::PutUserCode(
  const char *pszUCFile,
  char *pszError,
  const std::vector<Cat1Label *>& vpLbl) const
  {
  HANDLE hUCFile;
  DWORD dwBytesWritten;
  byte *pBuffer;
  int RegIndex;
  int ByteIndex;
  int StopReg;         // points to byte after last valid byte
  int StopByte;
  int ProgSize;

  // create file and write out buffer
  hUCFile=CreateFile(pszUCFile,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
  if (hUCFile==INVALID_HANDLE_VALUE)
    {
    strcpy(pszError,"Unable to create file");
    return 0;
    }

  for (std::vector<Cat1Label *>::const_iterator it=vpLbl.begin();it!=vpLbl.end();it++)
    {
    ProgSize=0;
    RegIndex=(*it)->StartReg;
    ByteIndex=(*it)->StartByte;
    StopReg=(*it)->EndReg;
    StopByte=(*it)->EndByte;
    NextByte(StopReg,StopByte,3);   // skip GLOBAL_END code for writing END

    // overestimate space needed
    pBuffer=(byte*)calloc((*it)->StartReg*7-(*it)->EndReg*7+20,1);

    // copy program to buffer
    while (RegIndex!=StopReg || ByteIndex!=StopByte)
      {
      // copy byte
      if (RamExist(RegIndex))                      // just to be safe
        pBuffer[ProgSize]=pRAM[RegIndex].Reg[ByteIndex];

      // next byte
      NextByte(RegIndex,ByteIndex);
      ProgSize++;
      }

    WriteFile(hUCFile,pBuffer,ProgSize,&dwBytesWritten,NULL);
    free(pBuffer);

    if (ProgSize!=(int)dwBytesWritten)
      {
      strcpy(pszError,"Error writing to file");
      CloseHandle(hUCFile);
      return 0;
      }
    }

  CloseHandle(hUCFile);
  return(1);
  }


/**************************************/
// Loads a bitmap from file - returns success=1
// malloc and returns pointers to bitmap, palette and palette index
// caller must free memory by calling FreeBMP()
/**************************************/
flag HP41::LoadBMP(
  const char *pszFile,
  BITMAPINFOHEADER *&pBM,
  LOGPALETTE *&pPalette,
  BITMAPINFO *&pPalIndexBuf)
  {
  HANDLE hBMFile;
  DWORD dwBytesRead;
  BITMAPFILEHEADER bmFileHeader;
  BITMAPINFOHEADER bmInfoHeader;
  int nColors;
  RGBQUAD *pRGBQUAD;

  // initialize return pointers
  pBM=NULL;
  pPalette=NULL;
  pPalIndexBuf=NULL;

  // get full path to file
  char app_drive[_MAX_DRIVE],app_dir[_MAX_DIR];
  char fname[_MAX_FNAME],ext[_MAX_EXT];
  char szFullPath[_MAX_PATH];
  _splitpath(theApp.szAppFullPath,app_drive,app_dir,NULL,NULL);
  _splitpath(pszFile,NULL,NULL,fname,ext);
  _makepath(szFullPath,app_drive,app_dir,fname,ext);

  // open file
  hBMFile=CreateFile(szFullPath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
  if (hBMFile==INVALID_HANDLE_VALUE)
    {
    CString sMsg;
    sMsg.Format(" Unable to open file: %s",szFullPath);
    AfxMessageBox(sMsg);
    return(FALSE);
    }

  // read headers
  ReadFile(hBMFile,&bmFileHeader,sizeof(BITMAPFILEHEADER),&dwBytesRead,NULL);
  if (sizeof(BITMAPFILEHEADER)!=dwBytesRead)
    return(FALSE);
  if (bmFileHeader.bfType!=0x4d42)
    return(FALSE);
  ReadFile(hBMFile,&bmInfoHeader,sizeof(BITMAPINFOHEADER),&dwBytesRead,NULL);
  if (sizeof(BITMAPINFOHEADER)!=dwBytesRead)
    return(FALSE);
  if (bmInfoHeader.biSize==sizeof(BITMAPCOREHEADER))
    return(FALSE);  // dont deal with this type

  // no color table for 24 bit, default size otherwise
  nColors=bmInfoHeader.biClrUsed;
  if ((nColors==0)&&(bmInfoHeader.biBitCount!=24))
    nColors=1<<bmInfoHeader.biBitCount;

  // fill in some default values if they are zero
  if (bmInfoHeader.biClrUsed==0)
    bmInfoHeader.biClrUsed=nColors;
  if (bmInfoHeader.biSizeImage==0)
    bmInfoHeader.biSizeImage=((((bmInfoHeader.biWidth*(DWORD)bmInfoHeader.biBitCount)+31)&~31)>>3)*bmInfoHeader.biHeight;

  // get a proper-sized buffer for header, color table and bits
  pBM=(BITMAPINFOHEADER*)malloc(bmInfoHeader.biSize+nColors*sizeof(RGBQUAD)+bmInfoHeader.biSizeImage);
  if (pBM==NULL)
    return(FALSE);
  memcpy(pBM,&bmInfoHeader,bmInfoHeader.biSize);

  // read color table
  ReadFile(hBMFile,(char*)pBM+bmInfoHeader.biSize,nColors*sizeof(RGBQUAD),&dwBytesRead,NULL);
  if (nColors*sizeof(RGBQUAD)!=dwBytesRead)
    {
    free(pBM);
    pBM=NULL;
    CloseHandle(hBMFile);
    return(FALSE);
    }
  pRGBQUAD=(RGBQUAD*)((char*)pBM+bmInfoHeader.biSize);

  // read bits
  if (bmFileHeader.bfOffBits!=0)                                   // use file header offset if specified
    SetFilePointer(hBMFile,bmFileHeader.bfOffBits,NULL,FILE_BEGIN);
  ReadFile(hBMFile,(char*)pBM+bmInfoHeader.biSize+nColors*sizeof(RGBQUAD),bmInfoHeader.biSizeImage,&dwBytesRead,NULL);  // read bits
  CloseHandle(hBMFile);
  if (bmInfoHeader.biSizeImage!=dwBytesRead)
    {
    free(pBM);
    pBM=NULL;
    return(FALSE);
    }

  // convert the color table to a LOGPALETTE structure
  if (nColors)
    {
    pPalette=(LOGPALETTE*)malloc(sizeof(LOGPALETTE)+sizeof(PALETTEENTRY)*nColors);
    if (pPalette)
      {
      pPalette->palVersion=0x300;
      pPalette->palNumEntries=nColors;
      for (int i=0;i<nColors;i++)
        {
        pPalette->palPalEntry[i].peRed=pRGBQUAD[i].rgbRed;
        pPalette->palPalEntry[i].peGreen=pRGBQUAD[i].rgbGreen;
        pPalette->palPalEntry[i].peBlue=pRGBQUAD[i].rgbBlue;
        pPalette->palPalEntry[i].peFlags=0;
        }
      }
    }
  else
    pPalette=NULL;

  // create pal index
  if (nColors)
    {
    pPalIndexBuf=(BITMAPINFO*)malloc(bmInfoHeader.biSize+sizeof(WORD)*nColors);
    memcpy(pPalIndexBuf,&bmInfoHeader,bmInfoHeader.biSize);
    WORD *pTable=(WORD*)((char*)pPalIndexBuf+bmInfoHeader.biSize);
    for (int i=0;i<nColors;i++)
      pTable[i]=i;
    }
  else
    pPalIndexBuf=NULL;

  return(1);
  }


/**************************************/
void HP41::FreeBMP(
  BITMAPINFOHEADER *&pBM,
  LOGPALETTE *&pPalette,
  BITMAPINFO *&pPalIndexBuf)
  {
  if (pBM!=NULL)
    {
    free(pBM);
    pBM=NULL;
    }
  if (pPalette!=NULL)
    {
    free(pPalette);
    pPalette=NULL;
    }
  if (pPalIndexBuf!=NULL)
    {
    free(pPalIndexBuf);
    pPalIndexBuf=NULL;
    }
  }
