/*=======================================================================

Author: Warren Furlow (email: warren@furlow.org)

License: PUBLIC DOMAIN - May be freely copied and incorporated into any work

Description:  Contains routines for conversion between HP-41 ROM image file formats

Background:
Each HP-41 ROM page is 4096 words in size and each word is 10 bits.  There are
16 pages in the address space, but pages can be bank switched by up to 4 banks.
The pages 0-7 are for system use and pages that go there are hard coded to that
location.  Pages 8-F are for plug-in module usage through the four physical
ports.  Each port takes up two pages (Page8=Port1 Lower,Page9=Port1 Upper, etc.).
Note that some plug-in modules and peripherals are hard coded to map into certain
system pages (ex: time module).

Supported File Formats:
ROM - This format is used by V41 Release 7 and prior (Warren Furlow).
      It is always 8192 bytes with the High 2 bits followed by the Low 8 bits.

BIN - This format is used by Emu41 (J-F Garnier) and HP41EPC (HrastProgrammer).
      Note: HP41EPC uses BIN format but names them .ROM files.
      All bits are packed into 5120 bytes, but several consecutive pages may
      occupy the same file, so the file size could be a multiple of 5120.
      4 machine words are packed into 5 bytes:
      Byte0=Word0[7-0]
      Byte1=Word1[5-0]<<2 | Word0[9-8]
      Byte2=Word2[3-0]<<4 | Word1[9-6]
      Byte3=Word3[1-0]<<6 | Word2[9-4]
      Byte4=Word3[9-2]

HPX - HEPAX ROM File Format
      4 machine words are packed into 5 bytes:
      Byte0=Word0[9-2]
      Byte1=Word0[1-0]<<6 | Word1[9-4]
      Byte2=Word1[3-0]<<4 | Word2[9-6]
      Byte3=Word2[5-0]<<2 | Word3[9-8]
      Byte4=Word3[7-0]

ECO - ERAMCO ROM File Format
      4 machine words are packed into 5 bytes:
      Byte0=Word3[9-8]<<6 | Word2[9-8]<<4 | Word1[9-8]<<2 | Word0[9-8]
      Byte1=Word3[7-0]
      Byte2=Word2[7-0]
      Byte3=Word1[7-0]
      Byte4=Word0[7-0]

LST - This format is a text file dump of the disassembled machine code generated
      by the Zenrom MCED utility.  The first 4 digit hex number is the absolute
      address and the second 3 digit hex number is the machine word.  The mnemonic
      normally appears after that.  For the purposes of file conversion, only the
      machine word is actually used and only the first page is read from the file
      with the provided routines.
      Example:
      8000 158 M=C
      8001 398 C=ST
      8002 056 C=0    XS
      8003 284 CF     7

MOD - MOD File format is a more advanced multi-page format for containing an entire module or
      all operating system pages.  See MODFile.h  This format is used by V41 Release 8.

Sample usage: convert a lst file to a bin file:
  word *ROM;
  ROM=read_lst_file("test.lst");
  if (ROM==NULL)
    return;
  write_bin_file("test.bin",ROM);
  free(ROM);

Convert a bin file to a rom file:
  ROM=read_bin_file("test.bin",0);
  if (ROM==NULL)
    return;
  write_rom_file("test.rom",ROM);
  free(ROM);

MODULE FILE LOADER - for emulators etc
The exact loading procedure will be dependent on the emulator's implementation.  V41 uses a
three pass process to find empty pages and ensure that the ROM attributes are correctly followed.
Feel free to copy and adapt the algorithm in LoadMOD() to your software.

First Pass:  Validate variables, go through each page in the mod file.  If there any PageGroups,
count the number of pages in each group using the appropriate array (LowerGroup[8], UpperGroup[0], etc).
This count is stored as a negative number.  In the second pass this value will be replaced with
a positive number which will represent the actual page to be loaded

Second Pass: Go through each page again and find free locations for any that are grouped.
If a page is the first one encountered in a group, find a block of free space for the group.
For instance, Odd/Even will require two contiguous spaces.  If a page is the second or subsequent
one encountered in a group, then we already have the block of space found and simply need to
stick it in the right place.  For instance, the lower page has already been loaded, then the upper
page goes right after it.

Third Pass: Find a free location for any non-grouped pages.  This includes system pages which have
their page number hardcoded.

=========================================================================*/

#define _CRT_SECURE_NO_WARNINGS
#define _CRT_NONSTDC_NO_WARNINGS

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<memory.h>
#include"MODFile.h"

/******************************/
word *read_rom_file(const char *FullFileName)
  {
  FILE *File;
  long FileSize,SizeRead;
  word *ROM;
  int i;

  File=fopen(FullFileName,"rb");
  if (File==NULL)
    {
    fprintf(stderr,"ERROR: File Open Failed: %s\n",FullFileName);
    return(NULL);
    }
  fseek(File,0,SEEK_END);
  FileSize=ftell(File);
  fseek(File,0,SEEK_SET);
  if (FileSize!=8192)
    {
    fclose(File);
    fprintf(stderr,"ERROR: File Size Invalid: %s\n",FullFileName);
    fprintf(stderr,"  ROM file size is 8192 bytes\n");
    return(NULL);
    }
  ROM=(word*)malloc(sizeof(word)*0x1000);
  if (ROM==NULL)
    {
    fclose(File);
    fprintf(stderr,"ERROR: Memory Allocation\n");
    return(NULL);
    }
  SizeRead=(long)fread(ROM,1,8192,File);
  fclose(File);
  if (SizeRead!=8192)
    {
    fprintf(stderr,"ERROR: File Read Failed: %s\n",FullFileName);
    free(ROM);
    return(NULL);
    }
  for (i=0;i<0x1000;i++)
    ROM[i]=(ROM[i]<<8)|(ROM[i]>>8);
  return(ROM);
  }

/*******************************/
int write_rom_file(const char *FullFileName,const word *ROM)
  {
  FILE *File;
  long SizeWritten;
  word *ROM2;
  int i;

  if (ROM==NULL)
    return(0);
  File=fopen(FullFileName,"wb");
  if (File==NULL)
    {
    fprintf(stderr,"ERROR: File Open Failed: %s\n",FullFileName);
    return(0);
    }
  ROM2=(word*)malloc(sizeof(word)*0x1000);
  if (ROM2==NULL)
    {
    fclose(File);
    fprintf(stderr,"ERROR: Memory Allocation\n");
    return(0);
    }
  for (i=0;i<0x1000;i++)
    ROM2[i]=(ROM[i]<<8)|(ROM[i]>>8);
  SizeWritten=(long)fwrite(ROM2,1,8192,File);
  fclose(File);
  free(ROM2);
  if (SizeWritten!=8192)
    {
    fprintf(stderr,"ERROR: File Write Failed: %s\n",FullFileName);
    return(0);
    }
  return(1);
  }

/*******************************/
word *read_bin_file(const char *FullFileName,int Page)
  {
  FILE *File;
  long FileSize,SizeRead;
  byte *BIN;
  word *ROM;

  File=fopen(FullFileName,"rb");
  if (File==NULL)
    {
    fprintf(stderr,"ERROR: File Open Failed: %s\n",FullFileName);
    return(NULL);
    }
  fseek(File,0,SEEK_END);
  FileSize=ftell(File);
  fseek(File,0,SEEK_SET);
  if (FileSize%5120)
    {
    fclose(File);
    fprintf(stderr,"ERROR: File Size Invalid: %s\n",FullFileName);
    return(NULL);
    }
  BIN=(byte*)malloc(FileSize);
  if (BIN==NULL)
    {
    fclose(File);
    fprintf(stderr,"ERROR: Memory Allocation\n");
    return(NULL);
    }
  SizeRead=(long)fread(BIN,1,FileSize,File);
  fclose(File);
  if (SizeRead!=FileSize)
    {
    fprintf(stderr,"ERROR: File Read Failed: %s\n",FullFileName);
    free(BIN);
    return(NULL);
    }

  ROM=(word*)malloc(sizeof(word)*0x1000);
  if (ROM==NULL)
    {
    fprintf(stderr,"ERROR: Memory Allocation\n");
    return(NULL);
    }
  unpack_image(ROM,BIN+Page*5120);
  free(BIN);
  return(ROM);
  }

/*******************************/
int write_bin_file(const char *FullFileName,const word *ROM)
  {
  FILE *File;
  long SizeWritten;
  byte *BIN;

  if (ROM==NULL)
    return(0);
  File=fopen(FullFileName,"wb");
  if (File==NULL)
    {
    fprintf(stderr,"ERROR: File Open Failed: %s\n",FullFileName);
    return(0);
    }

  BIN=(byte*)malloc(5120);
  if (BIN==NULL)
    {
    fprintf(stderr,"ERROR: Memory Allocation\n");
    return(0);
    }
  pack_image(ROM,BIN);
  SizeWritten=(long)fwrite(BIN,1,5120,File);
  fclose(File);
  free(BIN);
  if (SizeWritten!=5120)
    {
    fprintf(stderr,"ERROR: File Write Failed: %s\n",FullFileName);
    return(0);
    }
  return(1);
  }

/*******************************/
word *read_lst_file(const char *FullFileName)
  {
  FILE *File;
  word *ROM;
  char LST[255];
  int i;
  word addr,mword;

  File=fopen(FullFileName,"rt");
  if (File==NULL)
    {
    fprintf(stderr,"ERROR: File Open Failed: %s\n",FullFileName);
    return(NULL);
    }
  ROM=(word*)malloc(sizeof(word)*0x1000);
  if (ROM==NULL)
    {
    fprintf(stderr,"ERROR: Memory Allocation\n");
    return(NULL);
    }
  i=0;
  while (fgets(LST,sizeof(LST),File)&&i<0x1000)
    {
    if (sscanf(LST,"%hx %hx",&addr,&mword)==2)
      ROM[i++]=mword;
    }
  fclose(File);
  if (i!=0x1000)
    {
    fprintf(stderr,"ERROR: File Size Invalid: %s\n",FullFileName);
    free(ROM);
    return(NULL);
    }
  return(ROM);
  }

/*******************************/
int write_lst_file(const char *FullFileName,const word *ROM,int Page)
  {
  FILE *File;
  int i;

  if (ROM==NULL)
    return(0);
  File=fopen(FullFileName,"wt");
  if (File==NULL)
    {
    fprintf(stderr,"ERROR: File Open Failed: %s\n",FullFileName);
    return(0);
    }

  for (i=0;i<0x1000;i++)
    fprintf(File,"%04X %03X\n",i+Page*0x1000,ROM[i]&0x03FF);
  fclose(File);
  return(1);
  }

/******************************/
int compare_rom_files(const char *FullFileName1,const char *FullFileName2)
  {
  word *ROM1,*ROM2;
  int res;
  ROM1=read_rom_file(FullFileName1);
  if (ROM1==NULL)
    return(0);
  ROM2=read_rom_file(FullFileName2);
  if (ROM2==NULL)
    {
    free(ROM1);
    return(0);
    }
  res=(0==memcmp(ROM1,ROM2,8192));
  free(ROM1);
  free(ROM2);
  return(res);
  }

/******************************/
void unpack_image(
  word *ROM,
  const byte *BIN)
  {
  int i;
  word *ptr=ROM;
  if ((ROM==NULL)||(BIN==NULL))
    return;
  for (i=0;i<5120;i+=5)
    {
    *ptr++=((BIN[i+1]&0x03)<<8) | BIN[i];
    *ptr++=((BIN[i+2]&0x0F)<<6) | ((BIN[i+1]&0xFC)>>2);
    *ptr++=((BIN[i+3]&0x3F)<<4) | ((BIN[i+2]&0xF0)>>4);
    *ptr++=(BIN[i+4]<<2) | ((BIN[i+3]&0xC0)>>6);
    }
  }

/******************************/
void pack_image(
  const word *ROM,
  byte *BIN)
  {
  int i,j;
  if ((ROM==NULL)||(BIN==NULL))
    return;
  for (i=0,j=0;i<0x1000;i+=4)
    {
    BIN[j++]=ROM[i]&0x00FF;
    BIN[j++]=((ROM[i+1]&0x003F)<<2) | ((ROM[i]&0x0300)>>8);
    BIN[j++]=((ROM[i+2]&0x000F)<<4) | ((ROM[i+1]&0x03C0)>>6);
    BIN[j++]=((ROM[i+3]&0x0003)<<6) | ((ROM[i+2]&0x03F0)>>4);
    BIN[j++]=(ROM[i+3]&0x03FC)>>2;
    }
  }

/******************************/
void unpack_image_hepax(
  word *ROM,
  const byte *HPX)
  {
  int i;
  word *ptr=ROM;
  if ((ROM==NULL)||(HPX==NULL))
    return;
  for (i=0;i<5120;i+=5)
    {
    *ptr++=((HPX[i+0]&0xFF)<<2) | (HPX[i+1]&0x30)>>6;
    *ptr++=((HPX[i+1]&0x3F)<<4) | (HPX[i+2]&0xF0)>>4;
    *ptr++=((HPX[i+2]&0x0F)<<6) | (HPX[i+3]&0xFC)>>2;
    *ptr++=((HPX[i+3]&0x03)<<8) | (HPX[i+4]&0xFF)>>0;
    }
  }

/******************************/
void pack_image_hepax(
  const word *ROM,
  byte *HPX)
  {
  int i,j;
  if ((ROM==NULL)||(HPX==NULL))
    return;
  for (i=0,j=0;i<0x1000;i+=4)
    {
    HPX[j++]=((ROM[i+0]&0x03FC)>>2);
    HPX[j++]=((ROM[i+0]&0x0003)<<6) | ((ROM[i+1]&0x03F0)>>4);
    HPX[j++]=((ROM[i+1]&0x000F)<<4) | ((ROM[i+2]&0x03C0)>>6);
    HPX[j++]=((ROM[i+2]&0x003F)<<2) | ((ROM[i+3]&0x0300)>>8);
    HPX[j++]=((ROM[i+3]&0x00FF)>>0);
    }
  }

/******************************/
void unpack_image_eramco(
  word *ROM,
  const byte *ECO)
  {
  int i;
  word *ptr=ROM;
  if ((ROM==NULL)||(ECO==NULL))
    return;
  for (i=0;i<5120;i+=5)
    {
    *ptr++=((ECO[i]&0x03)<<8) | ECO[i+4];
    *ptr++=((ECO[i]&0x0C)<<6) | ECO[i+3];
    *ptr++=((ECO[i]&0x30)<<4) | ECO[i+2];
    *ptr++=((ECO[i]&0xC0)<<2) | ECO[i+1];
    }
  }

/******************************/
void pack_image_eramco(
  const word *ROM,
  byte *ECO)
  {
  int i,j;
  if ((ROM==NULL)||(ECO==NULL))
    return;
  for (i=0,j=0;i<0x1000;i+=4)
    {
    ECO[j++]=((ROM[i+3]&0x0300)>>2) | ((ROM[i+2]&0x0300)>>4) | ((ROM[i+1]&0x0300)>>6) | ((ROM[i+0]&0x0300)>>8);
    ECO[j++]=((ROM[i+3]&0x00FF));
    ECO[j++]=((ROM[i+2]&0x00FF));
    ECO[j++]=((ROM[i+1]&0x00FF));
    ECO[j++]=((ROM[i+0]&0x00FF));
    }
  }

/****************************/
// Returns 0 for unknown format, 1 for MOD1, 2 for MOD2
/****************************/
int get_file_format(const char *lpszFormat)
  {
  dword dwFormat=0;
  if (0==strcmp(lpszFormat,MOD_FORMAT))
    {
    dwFormat=1;
    }
  else if (0==strcmp(lpszFormat,MOD_FORMAT2))
    {
    dwFormat=2;
    }
  return dwFormat;
  }

/******************************/
/* Returns 0 for ROM without FAT, 1 for ROM with FAT */
/******************************/
int has_fat(const word *ROM)
  {
  const word wROMID=ROM[0]&0x3FF;           /* ROM ID */
  const word wFAT=ROM[1]&0x3FF;             /* no. of functions in FAT */

  /* check range of ROM ID and no. of FAT entries */
  int nFAT=wROMID <= 0x1F && wFAT > 0x00 && wFAT <= 0x40;

  if (nFAT)                                 /* in range */
    {
    const word addr=(wFAT+1)*2;             /* address end of FAT */

    /* address of last function in table */
    const word jmp_addr=((ROM[addr]&0x0ff)<<8)|(ROM[addr+1]&0x0ff);

    nFAT=(jmp_addr==0);                     /* last element in FAT must be 0 */
    }
  return nFAT;
  }

/****************************/
static void unpack_rom(
  int nFileFormat,
  word *ROM,
  const byte *byImage)
  {
  /* write the ROM file */
  if (1==nFileFormat)  /* MOD1 */
    {
    unpack_image(ROM,byImage);
    }
  else                 /* MOD2 */
    {
    int j;
    word *pwImage=(word *)byImage;
    for (j = 0; j < 4096; ++j)
      {
      // swap bytes
      ROM[j]=(pwImage[j] >> 8)|(pwImage[j] << 8);
      }
    }
  }

/******************************/
void fputcUTF8(
  FILE* OutFile,                    /* output file or set to stdout */
  unsigned short utf16)
  {
  static unsigned short wnHigh=0;   /* high surrogate pair */

  if (utf16 <= 0x7F)                /* ASCII char(0xxxxxxx) */
    {
    putc(utf16, OutFile);
    }
  else if (utf16 <= 0x7FF)          /* 110xxxxx 10xxxxxx */
    {
    putc(0xC0 | (utf16 >> 6),OutFile);
    putc(0x80 | (utf16 & 0x3F),OutFile);
    }
  /* high surrogate 0xD800-0xDBFF */
  else if ((utf16 & 0xFC00) == 0xD800)
    {
    wnHigh=utf16 & 0x3FF;
    }
  /* low surrogate 0xDC00-0xDFFF */
  else if ((utf16 & 0xFC00) == 0xDC00)
    {
    const unsigned short wnLow=utf16 & 0x3FF;
    dword dwChar=((dword)wnHigh << 10) | (dword)wnLow;
    dwChar+=0x10000;

    /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
    putc(0xF0 | (dwChar >> 18),OutFile);
    putc(0x80 | ((dwChar >> 12) & 0x3F),OutFile);
    putc(0x80 | ((dwChar >> 6) & 0x3F),OutFile);
    putc(0x80 | ((dwChar) & 0x3F),OutFile);
    }
  else if (utf16 <= 0xFFFF)         /* 1110xxxx 10xxxxxx 10xxxxxx */
    {
    putc(0xE0 | (utf16 >> 12),OutFile);
    putc(0x80 | ((utf16 >> 6) & 0x3F),OutFile);
    putc(0x80 | ((utf16) & 0x3F),OutFile);
    }
  }

/******************************/
static void decode_mcode_entry(
  FILE *OutFile,         /* output file or set to stdout */
  const word *ROM,       /* pointer to the ROM */
  word jmp_addr,         /* address to decode */
  int codesize)          /* 4/8 K MCODE */
  {
  char ch,punct;
  int end;
  int prompt;
  int addr2=jmp_addr;
  int end_addr=jmp_addr-11;                             /* name max 11 characters long */
  if (end_addr < 0) end_addr=0;
  do
    {
    unsigned short wch;
    decode_fatchar(ROM[--addr2],&ch,&wch,&punct,&end);
    fputcUTF8(OutFile,wch);
    }
  while (!end && addr2>end_addr);
  while (addr2+11>jmp_addr)                             /* pad it out */
    {
    fprintf(OutFile," ");
    addr2--;
    }
  fprintf(OutFile," %dK MCODE",codesize);
  /* function type */
  if ((ROM[jmp_addr]&0x3FF)==0)
    {
    fprintf(OutFile," Nonprogrammable");
    if ((ROM[jmp_addr+1]&0x3FF)==0)
      fprintf(OutFile," Immediate");
    else
      fprintf(OutFile," NULLable");
    }
  else
    fprintf(OutFile," Programmable");
  /* prompt type -high two bits of first two chars */
  prompt=(ROM[jmp_addr-1]&0x300)>>8;
  if (prompt && !(ROM[jmp_addr-2]&0x0080))
    prompt|=(ROM[jmp_addr-2]&0x300)>>4;
  switch (prompt)
    {
    case 0:     /* no prompt */
      break;
    case 1:
      fprintf(OutFile," Prompt: Alpha (null input valid)");
      break;
    case 2:
      fprintf(OutFile," Prompt: 2 Digits, ST, INF, IND ST, +, -, * or /");
      break;
    case 3:
      fprintf(OutFile," Prompt: 2 Digits or non-null Alpha");
      break;
    case 11:
      fprintf(OutFile," Prompt: 3 Digits");
      break;
    case 12:
      fprintf(OutFile," Prompt: 2 Digits, ST, IND or IND ST");
      break;
    case 13:
      fprintf(OutFile," Prompt: 2 Digits, IND, IND ST or non-null Alpha");
      break;
    case 21:
      fprintf(OutFile," Prompt: non-null Alpha");
      break;
    case 22:
      fprintf(OutFile," Prompt: 2 Digits, IND or IND ST");
      break;
    case 23:
      fprintf(OutFile," Prompt: 2 digits or non-null Alpha");
      break;
    case 31:
      fprintf(OutFile," Prompt: 1 Digit, IND or IND ST");
      break;
    case 32:
      fprintf(OutFile," Prompt: 2 Digits, IND or IND ST");
      break;
    case 33:
      fprintf(OutFile," Prompt: 2 Digits, IND, IND ST, non-null Alpha . or ..");
      break;
    }
  }

/******************************/
/* HP82143A Unicode table */
static const unsigned short wc82143A[] =
  {
  0x25C6, 0x00D7, 0x0078, 0x2190, 0x03B1, 0x03B2, 0x0393, 0x2193,
  0x0394, 0x03C3, 0x25C6, 0x03BB, 0x00B5, 0x2221, 0x03C4, 0x0278,
  0x03B8, 0x03A9, 0x03B4, 0x00C5, 0x00E5, 0x00C4, 0x00E4, 0x00D6,
  0x00F6, 0x00DC, 0x00FC, 0x00C6, 0x00E6, 0x2260, 0x00A3, 0x2591,
  0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
  0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
  0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
  0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
  0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
  0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
  0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x2191, 0x005F,
  0x2E06, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
  0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
  0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
  0x0078, 0x0079, 0x007A, 0x03C0, 0x007C, 0x2192, 0x03A3, 0x0370
  };

/******************************/
static void decode_user_entry(
  FILE *OutFile,         /* output file or set to stdout */
  const word *ROM,       /* pointer to the ROM */
  const word jmp_addr)   /* address to decode */
  {
  const byte byGlobal=(byte)ROM[jmp_addr];
  const byte byText  =(byte)ROM[jmp_addr+2];

  byte byTextCount=byText&0xF;
  byte bySpaceCount=12;

  // global LBL
  if ((jmp_addr+byTextCount+3)<0x1000 && byGlobal>=0xC0 && byGlobal<=0xCD && (byText&0xF0)==0xF0)
    {
    if (byTextCount>1)
      {
      word txt_addr=jmp_addr+4;     /* begin LBL after keycode byte */

      --byTextCount;                /* skip assigned keycode byte */
      if (bySpaceCount>byTextCount)
        bySpaceCount-=byTextCount;
      else
        bySpaceCount= 0;

      while(byTextCount--)
        {
        const byte ch=(byte)ROM[txt_addr++];
        const unsigned short wch=wc82143A[ch];
        fputcUTF8(OutFile,wch);

        if (ch==0x02)               /* x-bar */
          {
          /* add the bar character to the 'x' */
          fputcUTF8(OutFile,0x0304);
          }
        }
      }
    }

  /* fill gap between name and type */
  while(bySpaceCount--)
    fprintf(OutFile," ");
  fprintf(OutFile,"USER CODE");
  }

/******************************/
static word decode_int_vector(const word *ROM,word addr)
  {
  return ((ROM[addr]&0x3FF)) ? addr : 0;
  }

/******************************/
/* Returns 0 for success, 1 for open fail, 2 for read fail, 3 for invalid file, 4 for allocation error */
/******************************/
int output_mod_info(
  FILE *OutFile,         /* output file or set to stdout */
  const char *FullFileName,
  int Verbose,           /* generate all info except FAT */
  int DecodeFat,         /* decode fat if it exists */
  const ModulePageAddr *pPageAddr)  /* pointer to page address struct */
  {
  FILE *MODFile;
  unsigned long FileSize,SizeRead;
  byte *pBuff;
  int nFileFormat;
  ModuleFileHeader *pMFH;
  dword dwModulePageSize;
  int i;

  if (DecodeFat)
    Verbose=1;

  /* open and read MOD file into a buffer */
  MODFile=fopen(FullFileName,"rb");
  if (MODFile==NULL)
    {
    if (Verbose)
      fprintf(OutFile,"ERROR: File open failed: %s\n",FullFileName);
    return(1);
    }
  fseek(MODFile,0,SEEK_END);
  FileSize=ftell(MODFile);
  fseek(MODFile,0,SEEK_SET);
  pBuff=(byte*)malloc(FileSize);
  if (pBuff==NULL)
    {
    fclose(MODFile);
    if (Verbose)
      fprintf(OutFile,"ERROR: Memory allocation\n");
    return(4);
    }
  SizeRead=(unsigned long)fread(pBuff,1,FileSize,MODFile);
  fclose(MODFile);
  if (SizeRead!=FileSize)
    {
    if (Verbose)
      fprintf(OutFile,"ERROR: File read failed: %s\n",FullFileName);
    free(pBuff);
    return(2);
    }

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

  if (0==nFileFormat)
    {
    if (Verbose)
      fprintf(OutFile,"ERROR: File type unknown: %s\n",FullFileName);
    free(pBuff);
    return(3);
    }
  /* validate file size */
  if (FileSize!=sizeof(ModuleFileHeader)+pMFH->NumPages*dwModulePageSize)
    {
    if (Verbose)
      fprintf(OutFile,"ERROR: File size invalid: %s\n",FullFileName);
    free(pBuff);
    return(3);
    }
  if (pMFH->MemModules>4 || pMFH->XMemModules>3 || pMFH->Original>1 || pMFH->AppAutoUpdate>1 ||
    pMFH->Category>CATEGORY_MAX || pMFH->Hardware>HARDWARE_MAX)    /* out of range */
    {
    if (Verbose)
      fprintf(OutFile,"ERROR: llegal value(s) in header: %s\n",FullFileName);
    free(pBuff);
    return(3);
    }

  if (!Verbose)
    {
    fprintf(OutFile,"%-20s %-30s %-20s\n",FullFileName,pMFH->Title,pMFH->Author);
    return(0);
    }

  /* output header info */
  fprintf(OutFile,"FILE NAME: %s\n",FullFileName);
  fprintf(OutFile,"FILE FORMAT: %s\n",pMFH->FileFormat);
  fprintf(OutFile,"TITLE: %s\n",pMFH->Title);
  fprintf(OutFile,"VERSION: %s\n",pMFH->Version);
  fprintf(OutFile,"PART NUMBER: %s\n",pMFH->PartNumber);
  fprintf(OutFile,"AUTHOR: %s\n",pMFH->Author);
  fprintf(OutFile,"COPYRIGHT (c) %s\n",pMFH->Copyright);
  fprintf(OutFile,"LICENSE: %s\n",pMFH->License);
  fprintf(OutFile,"COMMENTS: %s\n",pMFH->Comments);
  switch (pMFH->Category)
    {
    case CATEGORY_UNDEF:
      fprintf(OutFile,"CATEGORY: Not categorized\n");
      break;
    case CATEGORY_OS:
      fprintf(OutFile,"CATEGORY: Operating system\n");
      break;
    case CATEGORY_APP_PAC:
      fprintf(OutFile,"CATEGORY: HP Application Pac\n");
      break;
    case CATEGORY_HPIL_PERPH:
      fprintf(OutFile,"CATEGORY: HP-IL related modules and devices\n");
      break;
    case CATEGORY_STD_PERPH:
      fprintf(OutFile,"CATEGORY: HP Standard peripheral\n");
      break;
    case CATEGORY_CUSTOM_PERPH:
      fprintf(OutFile,"CATEGORY: Custom peripheral\n");
      break;
    case CATEGORY_BETA:
      fprintf(OutFile,"CATEGORY: BETA releases not fully debugged and finished\n");
      break;
    case CATEGORY_EXPERIMENTAL:
      fprintf(OutFile,"CATEGORY: Test programs not meant for normal usage\n");
      break;
    }
  switch (pMFH->Hardware)
    {
    case HARDWARE_NONE:
      fprintf(OutFile,"HARDWARE: None\n");
      break;
    case HARDWARE_PRINTER:
      fprintf(OutFile,"HARDWARE: 82143A Printer\n");
      break;
    case HARDWARE_CARDREADER:
      fprintf(OutFile,"HARDWARE: 82104A Card Reader\n");
      break;
    case HARDWARE_TIMER:
      fprintf(OutFile,"HARDWARE: 82182A Time Module or HP-41CX built in timer\n");
      break;
    case HARDWARE_WAND:
      fprintf(OutFile,"HARDWARE: 82153A Barcode Wand\n");
      break;
    case HARDWARE_HPIL:
      fprintf(OutFile,"HARDWARE: 82160A HP-IL Module\n");
      break;
    case HARDWARE_INFRARED:
      fprintf(OutFile,"HARDWARE: 82242A Infrared Printer Module\n");
      break;
    case HARDWARE_HEPAX:
      fprintf(OutFile,"HARDWARE: HEPAX Module\n");
      break;
    case HARDWARE_WWRAMBOX:
      fprintf(OutFile,"HARDWARE: W&W RAMBOX Device\n");
      break;
    case HARDWARE_MLDL2000:
      fprintf(OutFile,"HARDWARE: MLDL2000 Device\n");
      break;
    case HARDWARE_CLONIX:
      fprintf(OutFile,"HARDWARE: CLONIX-41 Module\n");
      break;
    }
  fprintf(OutFile,"MEMORY MODULES: %d\n",pMFH->MemModules);
  fprintf(OutFile,"EXENDED MEMORY MODULES: %d\n",pMFH->XMemModules);
  fprintf(OutFile,"ORIGINAL: %s\n",pMFH->Original?"Yes - unaltered":"No - this file has been updated by a user application");
  fprintf(OutFile,"APPLICATION AUTO UPDATE: %s\n",pMFH->AppAutoUpdate?"Yes - update this file when saving other data (for MLDL/RAM)":"No - do not update this file");
  fprintf(OutFile,"NUMBER OF PAGES: %d\n",pMFH->NumPages);

  /* go through each page */
  for (i=0;i<pMFH->NumPages;i++)
    {
    word ROM[0x1000];
    char ID[10];
    int nFAT;
    word checksum;
    dword ycrc;
    word page_addr=0x0000;
    const ModuleFilePage *pMFP=(ModuleFilePage*)(pBuff+sizeof(ModuleFileHeader)+dwModulePageSize*i);
    const ModulePageAddr *pData=pPageAddr;

    /* get ROM image from file and build YCRC */
    unpack_rom(nFileFormat,ROM,pMFP->Image);
    ycrc=YCRC(ROM);

    /* go through page address info */
    for (;pData;pData=pData->pNext)
      {
      /* found YCRC ID and bank no. */
      if (pData->dwYCrc==ycrc && pData->byBank==pMFP->Bank)
        {
        page_addr=pData->wAddress;
        break;
        }
      }

    /* output page info */
    fprintf(OutFile,"\n");
    fprintf(OutFile,"ROM NAME: %s\n",pMFP->Name);
    get_rom_id(ROM,ID);
    if (0==strcmp(pMFP->ID,ID))
      fprintf(OutFile,"ROM ID: %s\n",pMFP->ID);
    else
      fprintf(OutFile,"ROM ID: \"%s\" (ACTUAL ID: \"%s\")\n",pMFP->ID,ID);
    fprintf(OutFile,"YCRC: %08X\n",ycrc);
    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 */
      fprintf(OutFile,"WARNING: Page info invalid: %s\n",FullFileName);
    if (pMFP->Page<=0x0f)
      {
      fprintf(OutFile,"PAGE: %X - must be in this location\n",pMFP->Page);
      page_addr=pMFP->Page*0x1000;
      }
    else
      {
      fprintf(OutFile,"PAGE: May be in more than one location\n");
      switch (pMFP->Page)
        {
        case POSITION_ANY:
          fprintf(OutFile,"POSITION: Any page 8-F\n");
          break;
        case POSITION_LOWER:
          fprintf(OutFile,"POSITION: In lower relative to upper page\n");
          break;
        case POSITION_UPPER:
          fprintf(OutFile,"POSITION: In upper page relative to lower page\n");
          break;
        case POSITION_ODD:
          fprintf(OutFile,"POSITION: Any odd page (9,B,D,F)\n");
          break;
        case POSITION_EVEN:
          fprintf(OutFile,"POSITION: Any even page (8,A,C,E)\n");
          break;
        case POSITION_ORDERED:
          fprintf(OutFile,"POSITION: Sequentially in MOD file order\n");
          break;
        }
       }
    if (pMFP->PageGroup==0)
      fprintf(OutFile,"PAGE GROUP: 0 - not grouped\n");
    else
      fprintf(OutFile,"PAGE GROUP: %d\n",pMFP->PageGroup);
    fprintf(OutFile,"BANK: %d\n",pMFP->Bank);
    if (pMFP->BankGroup==0)
      fprintf(OutFile,"BANK GROUP: 0 - not grouped\n");
    else
      fprintf(OutFile,"BANK GROUP: %d\n",pMFP->BankGroup);
    fprintf(OutFile,"RAM: %s\n",pMFP->RAM?"Yes":"No");
    fprintf(OutFile,"WRITE PROTECTED: %s\n",pMFP->WriteProtect?"Yes":"No or Not Applicable");
    if (!pMFP->RAM && pMFP->WriteProtect)
      fprintf(OutFile,"WARNING: ROM pages should not have WriteProtect set\n");

    nFAT=has_fat(ROM);
    fprintf(OutFile,"FAT: %s\n",nFAT?"Yes":"No");

    /* output FAT */
    if (nFAT)
      {
      fprintf(OutFile,"XROM: %d\n",ROM[0]&0x3FF);
      fprintf(OutFile,"FCNS: %d\n",ROM[1]&0x3FF);
      if (!DecodeFat)
        fprintf(OutFile,"(FAT Not Decoded)\n");
      }
    if (nFAT && DecodeFat)
      {
      word entry_num;
      word addr,jmp_addr;
      byte byPage,page_idx,page_found;
      word ROM2[0x1000];

      word rom_size=4; /* 4K ROM size */

      /* 1st scan for 8K ROM */
      page_idx=0; /* no FAT in lower or upper page */
      /* while entry number is less then number of entries and fat terminator not found */
      for (entry_num=0,addr=2;entry_num<=(ROM[1]&0x3FF) && !((ROM[addr]&0x3FF)==0 && (ROM[addr+1]&0x3FF)==0);entry_num++,addr+=2)
        {
        jmp_addr=((ROM[addr]&0x0ff)<<8) | (ROM[addr+1]&0x0ff);

        if (jmp_addr >= 0x1000 && jmp_addr < 0x2000) /* address is in upper page */
          page_idx|=2;
        else if ((jmp_addr & 0x8000) != 0)           /* jump offset is negative, address is in lower page */
          page_idx|=1;
        }

      byPage=0xFF;  /* no valid 2nd page */
      page_found=0; /* 2nd 4k page not found */

      if (page_idx)                                 /* need 2nd page */
        {
        if (page_idx==2)                            /* address is from upper page */
          {
          /* search for upper page of same page group */
          if (pMFP->Page == POSITION_LOWER)
            byPage = POSITION_UPPER;
          else if (pMFP->Page == POSITION_EVEN)
            byPage = POSITION_ODD;
          else if (pMFP->Page >= 8 && pMFP->Page < 15)
            byPage = pMFP->Page+1;
          }
        else if (page_idx==1)                       /* address is from lower page */
          {
          /* search for lower page of same page group */
          if (pMFP->Page == POSITION_UPPER)
            byPage = POSITION_LOWER;
          else if (pMFP->Page == POSITION_ODD)
            byPage = POSITION_EVEN;
          else if (pMFP->Page > 8 && pMFP->Page <= 15)
            byPage = pMFP->Page-1;
          }

        if (byPage!=0xFF) /* found a corresponding page for 2nd half of 8K ROM */
          {
          int i;

          /* go through all pages */
          for (i=0;i<pMFH->NumPages;i++)
            {
            const ModuleFilePage *pMFP2nd=(ModuleFilePage*)(pBuff+sizeof(ModuleFileHeader)+dwModulePageSize*i);

            page_found=(pMFP->BankGroup==pMFP2nd->BankGroup && pMFP->Bank==pMFP2nd->Bank) &&
              (
                (pMFP2nd->PageGroup==0 && pMFP2nd->Page==byPage)                   /* fix page */
                || (pMFP->PageGroup==pMFP2nd->PageGroup && pMFP2nd->Page==byPage)  /* same page group, the other 4K page */
              );

            if (page_found) /* found 2nd half of 8k ROM, load ROM */
              {
              rom_size=8;          /* 8K ROM size */
              unpack_rom(nFileFormat,ROM2,pMFP2nd->Image);
              break;
              }
            }
          }
        }

      fprintf(OutFile,"XROM  Addr Function    Type\n");
      /* while entry number is less then number of entries and fat terminator not found */
      for (entry_num=0,addr=2;entry_num<=(ROM[1]&0x3FF) && !((ROM[addr]&0x3FF)==0 && (ROM[addr+1]&0x3FF)==0);entry_num++,addr+=2)
        {
        jmp_addr=((ROM[addr]&0x0ff)<<8) | (ROM[addr+1]&0x0ff);
        fprintf(OutFile,"%02d,%02d %04X ",ROM[0]&0x3FF,entry_num,(word)(jmp_addr+page_addr));
        if (jmp_addr<0x1000)                               
          {
          if (ROM[addr]&0x200)    /* User Code */
            {
            decode_user_entry(OutFile,ROM,jmp_addr);
            }
          else                    /* 4K MCODE def */
            {
            decode_mcode_entry(OutFile,ROM,jmp_addr,rom_size);
            }
          }
        else
          {
          byte byDecodedFAT=0;

          if (page_idx == 2)                          /* address is from upper page */
            {
            byDecodedFAT = 1;
            jmp_addr -= 0x1000;
            }
          else if (page_idx == 1)                     /* address is from lower page */
            {
            byDecodedFAT = 1;
            jmp_addr = (jmp_addr + 0x1000) & 0xFFF;
            }

          if (byDecodedFAT == 0)
            {
              fprintf(OutFile,"            %s (Not decoded)",
                (ROM[addr]&0x200)!=0 ? "USER CODE" : "8K MCODE");
            }
          else
            {
            if (ROM[addr]&0x200)    /* User Code */
              {
              decode_user_entry(OutFile,ROM2,jmp_addr);
              }
            else                    /* MCODE def */
              {
              decode_mcode_entry(OutFile,ROM2,jmp_addr,rom_size);
              }
            }
          }
        fprintf(OutFile,"\n");
        }

      /* interrupt vectors */
      fprintf(OutFile,"INTERRUPT VECTORS:\n");
      fprintf(OutFile,"Pause loop:                      %03X\n",decode_int_vector(ROM,0x0ff4));
      fprintf(OutFile,"Main running loop:               %03X\n",decode_int_vector(ROM,0x0ff5));
      fprintf(OutFile,"Deep sleep wake up, no key down: %03X\n",decode_int_vector(ROM,0x0ff6));
      fprintf(OutFile,"Off:                             %03X\n",decode_int_vector(ROM,0x0ff7));
      fprintf(OutFile,"I/O service:                     %03X\n",decode_int_vector(ROM,0x0ff8));
      fprintf(OutFile,"Deep sleep wake up:              %03X\n",decode_int_vector(ROM,0x0ff9));
      fprintf(OutFile,"Cold start:                      %03X\n",decode_int_vector(ROM,0x0ffa));
      }

    fprintf(OutFile,"CHECKSUM:                        %03X",ROM[0x0fff]&0x3FF);
    checksum = compute_checksum(ROM);
    if ((verify_checksum(checksum,ROM[0x0fff]&0x3FF))==1)
      fprintf(OutFile," (Correct - Same as Computed Value)\n");
    else
      fprintf(OutFile," (Incorrect - Computed Value: %03X)\n",checksum);
    }

  fprintf(OutFile,"\n");
  free(pBuff);
  return(0);
  }

/******************************/
/* Returns 0 for success, 1 for open fail, 2 for read fail, 3 for invalid file, 4 for allocation error */
/******************************/
int extract_roms(
  const char *FullFileName)
  {
  char drive[_MAX_DRIVE],dir[_MAX_DIR];
  FILE *MODFile;
  unsigned long FileSize,SizeRead;
  byte *pBuff;
  int nFileFormat;
  ModuleFileHeader *pMFH;
  dword dwModulePageSize;
  int i;

  /* open and read MOD file into a buffer */
  _splitpath(FullFileName,drive,dir,NULL,NULL);
  MODFile=fopen(FullFileName,"rb");
  if (MODFile==NULL)
    return(1);
  fseek(MODFile,0,SEEK_END);
  FileSize=ftell(MODFile);
  fseek(MODFile,0,SEEK_SET);
  pBuff=(byte*)malloc(FileSize);
  if (pBuff==NULL)
    {
    fclose(MODFile);
    return(4);
    }
  SizeRead=(unsigned long)fread(pBuff,1,FileSize,MODFile);
  fclose(MODFile);
  if (SizeRead!=FileSize)
    {
    free(pBuff);
    return(2);
    }

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

  /* validate file size & check header */
  if (FileSize!=sizeof(ModuleFileHeader)+pMFH->NumPages*dwModulePageSize ||
    0==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(pBuff);
    return(3);
    }

  /* go through each page */
  for (i=0;i<pMFH->NumPages;i++)
    {
    char ROMFileName[_MAX_PATH];
    ModuleFilePage *pMFP;
    word ROM[0x1000];
    pMFP=(ModuleFilePage*)(pBuff+sizeof(ModuleFileHeader)+dwModulePageSize*i);
    /* write the ROM file */
    unpack_rom(nFileFormat,ROM,pMFP->Image);
    _makepath(ROMFileName,drive,dir,pMFP->Name,"ROM");
    write_rom_file(ROMFileName,ROM);
    }
  free(pBuff);
  return(0);
  }

/*******************************/
/* Verify the checksum by adding content of 0-ffe  and content of fff. */
/* Because the correction value is the 2's complement, the result should be 001 */
/*******************************/
word verify_checksum(word checksum,word correction)
{
	/* checksum input is already the 2's complement, so */
	/* revert back to sum result and add correction value */
	correction+=(word)(-checksum)&0x3ff;
	return (correction&0x03ff)+(correction>>10);
}

/*******************************/
/* Sum all values and when bit sum overflows into 11th bit add 1. */
/* Then take 2's complement.  Source: Zenrom pg. 101 */
/*******************************/
word compute_checksum(const word *ROM)
  {
  word checksum=0;
  int i;
  if (ROM==NULL)
    return(0);
  for (i=0;i<0xfff;i++)
    {
    checksum+=ROM[i]&0x3ff;
    checksum=(checksum&0x03ff)+(checksum>>10);
    }
  return((word)((-(int)checksum)&0x3ff));
  }

/*******************************/
/* calculates 41CL YCRC (CRC-32/MPEG-2) over ROM page, */
/* new C/C++ implementation from Christoph Giesselink */
/* derived from Pacal program from Meindert Kuipers originally from */
/* Basic program from Monte Dalrymple as used for the HP41CL */
/*******************************/
dword YCRC(const word *ROM)
  {
  int i, bits;                        /* index */

  const dword dwPoly = 0x04C11DB7;    /* polynominal */
  dword dwCrc = 0xFFFFFFFF;           /* crc start value */

  for (i = 0; i <= 0xfff; ++i)        /* 4k page */
    {
    word wData = ROM[i];              /* copy of data word for shifting */

    for (bits = 0; bits < 16; ++bits)
      {
      /* polynominal on 32 bit Crc overflow XOR 16 bit Data overflow */
      const dword dwXorPoly = (-(int)(((dwCrc >> 16) ^ wData) >> 15)) & dwPoly;

      dwCrc <<= 1;                    /* shift the crc state left one bit */
      wData <<= 1;                    /* shift the data word left one bit */
      dwCrc ^= dwXorPoly;             /* xor polynominal or 0 */
      }
    }
  return dwCrc;
}

/*******************************/
/* gets the ROM ID at the end of the ROM.  This is valid for most ROMs except O/S which seems to only use 0x0ffe */
/* ROM ID is lcd coded and may have punct bits set but only uses chars 0-3f (no halfnut) */
/* the high two bits are apparently meaningless although some ROMs have them set */
/*******************************/
void get_rom_id(
  const word *ROM,
  char *ID)           /* output: provide char[9] */
  {
  unsigned short wch;
  char punct;
  int i;
  for (i=0x0ffe;i>=0x0ffb;i--)
    {
    decode_lcdchar((word)(ROM[i]&0x00ff),ID++,&wch,&punct);
    if (punct)
      *(ID++)=punct;
    }
  *ID=0;
  }

/*********************/
static const unsigned short LCDtoASCII[] =
  {
  L'@'  , L'A', L'B', L'C', L'D', L'E', L'F'  , L'G'  , L'H', L'I', L'J', L'K', L'L'  , L'M', L'N'  , L'O',
  L'P'  , L'Q', L'R', L'S', L'T', L'U', L'V'  , L'W'  , L'X', L'Y', L'Z', L'[', L'\\' , L']', 0x2191, L'_',
  L' '  , L'!', L'\"',L'#', L'$', L'%', L'&'  , L'\'' , L'(', L')', L'*', L'+', L'{'  , L'-', L'}'  , L'/',
  L'0'  , L'1', L'2', L'3', L'4', L'5', L'6'  , L'7'  , L'8', L'9', L'~', L';', L'<'  , L'=', L'>'  , L'?',
  0x0370, L'a', L'b', L'c', L'd', L'e', 0x00AF, 0x2E06, L'~', L'~', L'~', L'~', 0x00B5, L'~', 0x03A3, 0x2221,
  L'~'  , L'~', L'~', L'~', L'~', L'~', 0x00AF, L'~'  , L'~', L'~', L'~', L'~', 0x00B5, L'~', L'~'  , 0x2221,
  L'~'  , L'a', L'b', L'c', L'd', L'e', L'f'  , L'g'  , L'h', L'i', L'j', L'k', L'l'  , L'm', L'n'  , L'o',
  L'p'  , L'q', L'r', L's', L't', L'u', L'v'  , L'w'  , L'x', L'y', L'z', L'~', L'~'  , L'~', 0x03A3, 0x0370,
  };

/*********************/
/* Decodes a 9 bit LCD char into ASCII values where possible */
/* LCD char table: Mcode for Beginners, Emery, pg. 108 */
/*                 A programmers handbook v.2.05, pg. 24 */
/*********************/
void decode_lcdchar(
  word lcdchar,                     /* LCD char in (bits 8-0) */
  char *ch,                         /* ASCII char out */
  unsigned short *wch,              /* Unicode char out */
  char *punct)                      /* punctuation char out if any or ==0 */
  {
  char val=(char)(lcdchar&0x3f);    /* take off bits 6-8 */
  val+=(lcdchar&0x0100)>>2;         /* bit 8 set - second 4 rows */
  *wch=LCDtoASCII[val];             /* get Unicode character code */
  *ch=(*wch<0x80)?((char)*wch):'~'; /* get ASCII code or tilde for non printable character */
  switch (lcdchar&0x00c0)           /* punct bits 6,7 */
    {
    case 0:
      *punct=0;
      break;
    case 0x0040:
      *punct='.';
      break;
    case 0x0080:
      *punct=':';
      break;
    case 0x00c0:
      *punct=',';
      break;
    }
  }

/*********************/
/* Decodes a 10 bit FAT char into ASCII values where possible */
/*********************/
void decode_fatchar(
  word tyte,                        /* tyte in */
  char *ch,                         /* ASCII char out */
  unsigned short *wch,              /* Unicode char out */
  char *punct,                      /* punctuation char out if any or ==0 */
  int *end)
  {
  word lcdchar=tyte&0x003f;         /* ignore two high bits - used for prompting - Zenrom pg 100. */
  lcdchar+=(tyte&0x40)<<2;          /* bit 6 is special chars (bit 8 in the LCD table) */
  decode_lcdchar(lcdchar,ch,wch,punct);
  *end=(tyte&0x0080)>>7;            /* bit 7 indicates end of name */
  }
