/****************************    omf.cpp    *********************************
* Author:        Agner Fog
* Date created:  2007-01-29
* Last modified: 2018-05-26
* Project:       objconv
* Module:        omf.cpp
* Description:
* Module for reading OMF files
*
* Class COMF is used for reading, interpreting and dumping OMF files.
*
* Copyright 2007-2018 GNU General Public License http://www.gnu.org/licenses
*****************************************************************************/
#include "stdafx.h"

// OMF Record type names
SIntTxt OMFRecordTypeNames[] = {
   {OMF_THEADR,      "Translator Header"},
   {OMF_LHEADR,      "Library Module Header"},
   {OMF_COMENT,      "Comment"},
   {OMF_MODEND,      "Module End"},
   {OMF_EXTDEF,      "External Names Definition"},
   {OMF_PUBDEF,      "Public Names Definition"},
   {OMF_LINNUM,      "Line Numbers"},
   {OMF_LNAMES,      "List of Names"},
   {OMF_SEGDEF,      "Segment Definition"},
   {OMF_GRPDEF,      "Group Definition"},
   {OMF_FIXUPP,      "Fixup"},
   {OMF_LEDATA,      "Enumerated Data"},
   {OMF_LIDATA,      "Iterated Data"},
   {OMF_COMDEF,      "Communal Names Definition"},
   {OMF_BAKPAT,      "Backpatch"},
   {OMF_LEXTDEF,     "Local External Names"},
   {OMF_LPUBDEF,     "Local Public Names"},
   {OMF_LCOMDEF,     "Local Communal Names"},
   {OMF_CEXTDEF,     "COMDAT External Names"},
   {OMF_COMDAT,      "Initialized Communal Data"},
   {OMF_LINSYM,      "Symbol Line Numbers"},
   {OMF_ALIAS,       "Alias Definition"},
   {OMF_NBKPAT,      "Named Backpatch"},
   {OMF_LLNAMES,     "Local Logical Names"},
   {OMF_VERNUM,      "OMF Version Number"},
   {OMF_VENDEXT,     "Vendor-specific OMF Extension"},
   {OMF_LIBHEAD,     "Library Header"},
   {OMF_LIBEND,      "Library End"}
};

// Segment combination names
SIntTxt OMFSegmentCombinationNames[] = {
   {0,     "Private"},
   {1,     "Invalid"},
   {2,     "Public"},
   {3,     "Invalid"},
   {4,     "Public 4"},
   {5,     "Stack"},
   {6,     "Common"},
   {7,     "Public 7"}
};

// Relocation mode names
static SIntTxt OMFRelocationModeNames[] = {
   {0,     "Relatv"},
   {1,     "Direct"}
};


// Fixup location names
static SIntTxt OMFFixupLocationNames[] = {
   {OMF_Fixup_8bit,        "8 bit"},                       // 0
   {OMF_Fixup_16bit,       "16 bit"},                      // 1
   {OMF_Fixup_Segment,     "segment selector, 16 bit"},    // 2
   {OMF_Fixup_Far,         "far pointer 16+16 bit"},       // 3
   {OMF_Fixup_Hi8bit,      "high 8 bit of 16 bits"},       // 4
   {OMF_Fixup_16bitLoader, "16 bit loader resolved"},      // 5
   {OMF_Fixup_Pharlab48,   "farword 48 bit, Pharlab only"},// 6
   {OMF_Fixup_32bit,       "32 bit"},                      // 9
   {OMF_Fixup_Farword,     "farword 32+16 bit"},           // 11
   {OMF_Fixup_32bitLoader, "32 bit loader resolved"}       // 13
};

// Alignment value translation table
static const uint32_t OMFAlignTranslate[8] = {0,1,2,16,256,4,0,0};


// Class COMF members:
// Constructor

COMF::COMF() {
   // Default constructor
   memset(this, 0, sizeof(*this));     // reset everything
}


void COMF::ParseFile() {
   // Parse file buffer
   //uint8_t  RecordType;                            // Type of current record
   uint32_t Checksum;                              // Record checksum
   uint32_t ChecksumZero = 0;                      // Count number of records with zero checksum
   SOMFRecordPointer rec;                        // Current record pointer

   // Make first entry zero in name lists
   LocalNameOffset.PushZero();
   SegmentNameOffset.PushZero();
   GroupNameOffset.PushZero();
   SymbolNameOffset.PushZero();

   // Initialize record pointer
   rec.Start(Buf(), 0, GetDataSize());

   // Loop through records to set record pointers and store names
   do {
      // Read record
      //RecordType = rec.Type2;                    // First byte of record = type

      // Compute checksum
      Checksum = 0;  rec.Index = 0;
      while (rec.Index < rec.End) Checksum += rec.GetByte();
      uint32_t CheckByte = rec.GetByte();
      if ((Checksum + CheckByte) & 0xFF) {
         // Checksum failed
         if (CheckByte == 0) {
            ChecksumZero++;
         }
         else err.submit(1202);                  // Checksum error
      }

      // Store record pointer
      rec.Index = 3;                             // Offset to current byte while parsing
      Records.Push(rec);                         // Store record pointer in list

      if (rec.Type2 == OMF_LNAMES) {
         // LNAMES record. Store local names by name index
         // Loop through strings in record
         while (rec.Index < rec.End) {
            char * LocalName = rec.GetString();
            uint32_t LocalNameIndex = NameBuffer.PushString(LocalName); // Store local name
            LocalNameOffset.Push(LocalNameIndex);// Store local name index
         }
         if (rec.Index != rec.End) err.submit(1203);  // Check for consistency
      }

      if (rec.Type2 == OMF_SEGDEF) {
         // SEGDEF record. Store segment names by segment index
         OMF_SAttrib Attributes;
         if (rec.Type2 == OMF_SEGDEF) {
            Attributes.b = rec.GetByte();     // Read attributes
            if (Attributes.u.A == 0) {
               // Frame and Offset only included if A = 0
               rec.GetWord();  rec.GetByte();
            }
            rec.GetNumeric(); // Length
         }
         uint32_t NameIndex  = rec.GetIndex();
         if (NameIndex < LocalNameOffset.GetNumEntries()) {
            SegmentNameOffset.Push(LocalNameOffset[NameIndex]); // List by segment index
         }
      }

      if (rec.Type2 == OMF_GRPDEF) {
         // GRPDEF record. Store group name
         uint32_t NameIndex  = rec.GetIndex();
         if (NameIndex < LocalNameOffset.GetNumEntries()) {
            GroupNameOffset.Push(LocalNameOffset[NameIndex]); // List by group index
         }
      }

      if (rec.Type2 == OMF_EXTDEF) {
         // EXTDEF record. Store external symbol names
         // Loop through strings in record
         while (rec.Index < rec.End) {
            char * symbolname = rec.GetString();
            rec.GetIndex();
            uint32_t SymbolNameIndex = NameBuffer.PushString(symbolname); // Store external name
            SymbolNameOffset.Push(SymbolNameIndex);   // Save in name index table
         }
         if (rec.Index != rec.End) err.submit(1203);  // Check for consistency
      }

      if (rec.Type2 == OMF_CEXTDEF) {
         // CEXTDEF record. Store communal symbol names
         // Loop through entries in record
         uint32_t SymbolNameIndex;                 // Index into NameBuffer
         while (rec.Index < rec.End) {
            uint32_t LIndex = rec.GetIndex();      // Index into preceding LNAMES
            rec.GetIndex();                      // Type index. Ignore
            // Get name from LocalNameOffset and put into SymbolNameOffset.
            if (LIndex < LocalNameOffset.GetNumEntries()) {
               SymbolNameIndex = LocalNameOffset[LIndex];
            }
            else SymbolNameIndex = 0;
            SymbolNameOffset.Push(SymbolNameIndex);   // Save in name index table
         }
         if (rec.Index != rec.End) err.submit(1203);  // Check for consistency
      }
   }  // Point to next record
   while (rec.GetNext());                        // End of loop through records

   NumRecords = Records.GetNumEntries();         // Number of records

   if (ChecksumZero) printf("\nChecksums are zero"); // This is taken out of the loop to report it only once
}


void COMF::Dump(int options) {
   // Dump file
   if (options & DUMP_FILEHDR) DumpRecordTypes(); // Dump summary of record types

   if (options & DUMP_STRINGTB) DumpNames(); // Dump names records

   if (options & DUMP_SYMTAB) DumpSymbols(); // Dump public/external name records   

   if (options & DUMP_SECTHDR) DumpSegments(); // Dump segment records

   if (options & DUMP_RELTAB) DumpRelocations(); // Dump fixup records

   if (options & DUMP_COMMENT) DumpComments(); // Dump coment records
}

void COMF::DumpRecordTypes() {
   // Dump summary of records
   printf("\nSummary of records:");
   for (uint32_t i = 0; i < NumRecords; i++) {
      // Print record type
      printf("\n  Record %02X, %s%s, total length %i", Records[i].Type,
         Lookup(OMFRecordTypeNames, Records[i].Type2), 
         (Records[i].Type & 1) ? ".32" : "",
         Records[i].End+1);
   }
}


void COMF::DumpNames() {
   // Dump local names records
   uint32_t i;           // Record index
   uint32_t ln = 0;     // Local name index
   printf("\n\nLocal names:");
   for (i = 0; i < NumRecords; i++) {
      if (Records[i].Type2 == OMF_LNAMES) {
         // LNAMES record. There should be only one
         // Loop through strings in record
         while (Records[i].Index < Records[i].End) {
            printf("\n  %2i  %s", ++ln, Records[i].GetString());
         }
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }

      if (Records[i].Type2 == OMF_THEADR || Records[i].Type2 == OMF_LHEADR) {
         // Module header record
         // Loop through strings in record
         while (Records[i].Index < Records[i].End) {
            printf("\n  Module: %s\n", Records[i].GetString());
         }
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }
      if (Records[i].Type2 == OMF_COMDEF) {
         // COMDEF record. Communal names
         uint32_t DType, DSize, DNum;
         printf("\n\n Communal names:");

         // Loop through strings in record
         while (Records[i].Index < Records[i].End) {
            printf("\n  \"%s\":", Records[i].GetString());
            printf(" %i", Records[i].GetByte()); // Type index, should be 0
            DType = Records[i].GetByte(); // Data type            
            switch (DType) {
            case 0x61:
               DNum  = Records[i].GetLength();
               DSize = Records[i].GetLength();
               printf(" FAR: %i*%i bytes", DNum, DSize); 
               break;
            case 0x62:
               DSize = Records[i].GetLength();
               printf(" NEAR: 0x%X bytes", DSize); 
               break;
            default:
               DSize = Records[i].GetLength();
               if (DType < 0x60) { // Borland segment index
                  printf(" segment %i, size 0x%X", DType, DSize); 
                  break;
               }
               printf(" unknown type %i, size 0x%X", DType, DSize); 
               break;
            }
         }
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }
   }
}

void COMF::DumpSymbols() {
   // Dump public, external and communal names records
   uint32_t i;          // Record index
   uint32_t xn = 0;     // External name index
   char * string;
   uint32_t TypeIndex;
   uint32_t Group;
   uint32_t Segment;
   uint32_t BaseFrame;
   uint32_t Offset;

   for (i = 0; i < NumRecords; i++) {
      if (Records[i].Type2 == OMF_EXTDEF) {
         // EXTDEF record.
         Records[i].Index = 3;
         printf("\n\nExternal names:");

         // Loop through strings in record
         while (Records[i].Index < Records[i].End) {
            string = Records[i].GetString();
            TypeIndex = Records[i].GetIndex();
            printf("\n  %2i  %s, Type %i", ++xn, string, TypeIndex);
         }
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }

      if (Records[i].Type2 == OMF_PUBDEF) {
         // PUBDEF record.
         printf("\n\nPublic names:");
         Records[i].Index = 3;
         Group = Records[i].GetIndex();
         Segment = Records[i].GetIndex();
         BaseFrame = 0;
         if (Segment == 0) BaseFrame = Records[i].GetWord();
         // Loop through strings in record
         while (Records[i].Index < Records[i].End) {
            string = Records[i].GetString();
            Offset = Records[i].GetNumeric();
            TypeIndex = Records[i].GetIndex();
            printf("\n  %s, Segment %s, Group %s, Offset 0x%X, Type %i", 
               string, GetSegmentName(Segment), GetGroupName(Group), Offset, TypeIndex);
            if (BaseFrame) printf(", Frame %i", BaseFrame);
         }
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }

      if (Records[i].Type2 == OMF_CEXTDEF) {
         // CEXTDEF record.
         printf("\n\nCommunal names:");
         Records[i].Index = 3;
         while (Records[i].Index < Records[i].End) {
            uint32_t LIndex = Records[i].GetIndex();    // Index into preceding LNAMES
            uint32_t Type = Records[i].GetIndex();      // Type index. Ignored
            printf("\n  %2i  %s, Type %i", ++xn, GetLocalName(LIndex), Type);
         }
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }
   }
}


void COMF::DumpSegments() {
   // Dump all segment records

   // Define structure of attributes
   OMF_SAttrib Attributes;

   // Record values
   uint32_t Frame, Offset, SegLength, NameIndex, ClassIndex, OverlayIndex;

   uint32_t i;                                     // Record number
   uint32_t SegNum = 0;                            // Segment number
   
   printf("\n\nSegment records:");
   for (i = 0; i < NumRecords; i++) {
      if (Records[i].Type2 == OMF_SEGDEF) {
         // SEGDEF record
         Records[i].Index = 3;
         // Loop through entries in record. There should be only 1
         while (Records[i].Index < Records[i].End) {
            Attributes.b = Records[i].GetByte();     // Read attributes
            if (Attributes.u.A == 0) {
               // Frame and Offset only included if A = 0
               Frame = Records[i].GetWord();  Offset = Records[i].GetByte();
            }
            else Frame = Offset = 0;
            SegLength    = Records[i].GetNumeric();
            NameIndex    = Records[i].GetIndex();
            ClassIndex   = Records[i].GetIndex();
            OverlayIndex = Records[i].GetIndex();

            printf("\n  Segment %2i, Name %s, Class %s, Align %i, %s, %i bit",
               ++SegNum, GetLocalName(NameIndex), GetLocalName(ClassIndex), 
               OMFAlignTranslate[Attributes.u.A], 
               Lookup(OMFSegmentCombinationNames, Attributes.u.C),
               Attributes.u.P ? 32 : 16);
            if (Attributes.u.B) printf(", big");
            if (Attributes.u.A == 0) printf(", Frame %i, Offset 0x%X", Frame, Offset);
            printf(", Length %i", SegLength);
            if (OverlayIndex) printf("\n   Overlay %i", OverlayIndex);
         }
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }
   }
   printf("\n\nGroup records:");
   for (i = 0; i < NumRecords; i++) {
      if (Records[i].Type2 == OMF_GRPDEF) {
         // GRPDEF record
         Records[i].Index = 3;
         ClassIndex = Records[i].GetIndex();
         printf("\n  Group: %s\n   Segments:", GetLocalName(ClassIndex));

         // Loop through remaining entries in record
         while (Records[i].Index < Records[i].End) {
            uint8_t Type = Records[i].GetByte();
            if (Type != 0xFF) printf(" Type=%X:", Type);
            NameIndex =  Records[i].GetIndex();
            printf(" %s", GetSegmentName(NameIndex));
         }
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }
   }
}


void COMF::DumpRelocations() {
   // Dump all LEDATA, LIDATA, COMDAT and FIXUPP records
   //uint32_t LastDataRecord = 0;          // Index to the data record that relocations refer to
   uint32_t LastDataRecordSize = 0;      // Size of the data record that relocations refer to
   int8_t * LastDataRecordPointer = 0;   // Pointer to data in the data record that relocations refer to
   uint32_t i;                           // Loop counter
   uint32_t Segment, Offset, Size;       // Contents of LEDATA or LIDATA record
   uint32_t LastOffset = 0;              // Offset of last LEDATA or LIDATA record
   uint32_t Frame, Target, TargetDisplacement; // Contents of FIXUPP record
   uint8_t byte1, byte2;                 // First two bytes of subrecord

   // Bitfields in subrecords
   OMF_SLocat Locat;         // Structure of first two bytes of FIXUP subrecord swapped = Locat field
   OMF_SFixData FixData;     // Structure of FixData field in FIXUP subrecord of FIXUPP record
   OMF_STrdDat TrdDat;       // Structure of Thread Data field in THREAD subrecord of FIXUPP record

   printf("\n\nLEDATA, LIDATA, COMDAT and FIXUPP records:");
   for (i = 0; i < NumRecords; i++) {
      if (Records[i].Type2 == OMF_LEDATA) {
         // LEDATA record
         Segment = Records[i].GetIndex();             // Read segment and offset
         Offset  = Records[i].GetNumeric();
         Size    = Records[i].End - Records[i].Index; // Calculate size of data
         //LastDataRecord = i;                          // Save for later FIXUPP that refers to this record
         LastDataRecordSize = Size;
         LastDataRecordPointer = Records[i].buffer + Records[i].FileOffset + Records[i].Index;
         if (Segment < 0x4000) {
            printf("\n  LEDATA: segment %s, Offset 0x%X, Size 0x%X", // Dump segment, offset, size
               GetSegmentName(Segment), Offset, Size);
            LastOffset = Offset;
         }
         else { // Undocumented Borland communal section
            printf("\n  LEDATA communal section %i, Offset 0x%X, Size 0x%X", // Dump segment, offset, size
               (Segment & ~0x4000), Offset, Size);
            LastOffset = Offset;
         }
      }

      if (Records[i].Type2 == OMF_LIDATA) {
         // LIDATA record
         Segment = Records[i].GetIndex();
         Offset  = Records[i].GetNumeric();
         //LastDataRecord = i;
         LastDataRecordSize = Records[i].End - Records[i].Index; // Size before expansion of repeat blocks
         LastDataRecordPointer = Records[i].buffer + Records[i].FileOffset + Records[i].Index;
         printf("\n  LIDATA: segment %s, Offset 0x%X, Size ",
            GetSegmentName(Segment), Offset);
         // Call recursive function to interpret repeat data block
         Size = Records[i].InterpretLIDATABlock();
         printf(" = 0x%X", Size);
         LastOffset = Offset;
      }

      if (Records[i].Type2 == OMF_COMDAT) {
         // COMDAT record
         //uint32_t Flags = Records[i].GetByte(); // 1 = continuation, 2 = iterated, 4 = local, 8 = data in code segment
         uint32_t Attributes = Records[i].GetByte(); 
         uint32_t Base = 0;
         // 0 = explicit, 1 = far code, 2 = far data, 3 = code32, 4 = data32
         // 0x00 = no match, 0x10 = pick any, 0x20 = same size, 0x30 = exact match
         uint32_t Align = Records[i].GetByte(); // Alignment
         Offset  = Records[i].GetNumeric();    // Offset
         uint32_t TypeIndex = Records[i].GetIndex(); // Type index
         if ((Attributes & 0x0F) == 0) {
            Base = Records[i].GetIndex(); // Public base
         }
         uint32_t NameIndex = Records[i].GetIndex(); // LNAMES index
         Size    = Records[i].End - Records[i].Index; // Calculate size of data

         printf("\n  COMDAT: name %s, Offset 0x%X, Size 0x%X, Attrib 0x%02X, Align %i, Type %i, Base %i",
            GetLocalName(NameIndex), Offset, Size, Attributes, Align, TypeIndex, Base);
         LastOffset = Offset;
      }

      if (Records[i].Type2 == OMF_FIXUPP) {
         // FIXUPP record
         printf("\n  FIXUPP:");
         Records[i].Index = 3;

         // Loop through entries in record
         while (Records[i].Index < Records[i].End) {

            // Read first byte
            byte1 = Records[i].GetByte();
            if (byte1 & 0x80) {
               // This is a FIXUP subrecord
               Frame = 0; Target = 0; TargetDisplacement = 0;

               // read second byte
               byte2 = Records[i].GetByte();
               // swap bytes and put into byte12 bitfield
               Locat.bytes[1] = byte1;
               Locat.bytes[0] = byte2;
               // Read FixData
               FixData.b = Records[i].GetByte();

               // print mode and location
               printf("\n   %s %s, Offset 0x%X", 
                  Lookup(OMFRelocationModeNames, Locat.s.M),
                  Lookup(OMFFixupLocationNames, Locat.s.Location),
                  Locat.s.Offset + LastOffset);

               // Read conditional fields
               if (FixData.s.F == 0) {
                  if (FixData.s.Frame < 4) {
                     Frame = Records[i].GetIndex();
                  }
                  else Frame = 0;

                  switch (FixData.s.Frame) { // Frame method
                  case 0:  // F0: segment
                     printf(", segment %s", GetSegmentName(Frame));  break;

                  case 1:  // F1: group
                     printf(", group %s", GetGroupName(Frame));  break;

                  case 2:  // F2: external symbol
                     printf(", external frame %s", GetSymbolName(Frame));  break;

                  case 4:  // F4: frame = source, 
                           // or Borland floating point emulation record (undocumented?)
                     printf(", frame = source; or Borland f.p. emulation record");
                     break;

                  case 5:  // F5: frame = target
                     printf(", frame = target");  break;

                  default:
                     printf(", target frame %i method F%i", Frame, FixData.s.Frame);
                  }
               }
               else {
                  printf(", frame uses thread %i", FixData.s.Frame);
               }
               
               if (FixData.s.T == 0) {
                  // Target specified
                  Target = Records[i].GetIndex();
                  uint32_t TargetMethod = FixData.s.Target + FixData.s.P * 4;

                  switch (FixData.s.Target) { // = Target method modulo 4
                  case 0: // T0 and T4: Target = segment
                  case 1: // T1 and T5: Target = segment group
                     printf(". Segment %s (T%i)",
                        GetSegmentName(Target), TargetMethod);
                     break;
                  case 2: // T2 and T6: Target = external symbol
                     printf(". Symbol %s (T%i)",
                        GetSymbolName(Target), TargetMethod);
                     break;
                  default: // Unknown method
                     printf(", target %i unknown method T%i", Target, TargetMethod);
                  }
               }
               else {
                  // Target specified in previous thread
                  printf(", target uses thread %i", FixData.s.Target);
               }

               if (FixData.s.P == 0) {
                  TargetDisplacement = Records[i].GetNumeric();
                  printf("\n    target displacement %i", TargetDisplacement);
               }
               // Get inline addend
               if (LastDataRecordPointer && Locat.s.Offset < LastDataRecordSize) {
                  int8_t * inlinep = LastDataRecordPointer + Locat.s.Offset;
                  switch (Locat.s.Location) {
                  case 0: case 4:  // 8 bit
                     printf(", inline 0x%X", *inlinep);  break;

                  case 1: case 2: case 5: // 16 bit
                     printf(", inline 0x%X", *(int16_t*)inlinep);  break;

                  case 3: // 16+16 bit
                     printf(", inline 0x%X:0x%X", *(int16_t*)(inlinep+2), *(int16_t*)inlinep);  break;

                  case 9: case 13: // 32 bit
                     printf(", inline 0x%X", *(int32_t*)inlinep);  break;

                  case 6: case 11: // 16+32 bit
                     printf(", inline 0x%X:0x%X", *(int16_t*)(inlinep+4), *(int32_t*)inlinep);  break;
                  }
               }
            }
            else {
               // This is a THREAD subrecord
               TrdDat.b = byte1;                 // Put byte into bitfield

               uint32_t Index  = 0;
               if (TrdDat.s.Method < 4) {
                  Index  = Records[i].GetIndex(); // has index field if method < 4 ?
               }
               printf("\n   %s Thread %i. Method %s%i, index %i",
                  (TrdDat.s.D ? "Frame" : "Target"), TrdDat.s.Thread,
                  (TrdDat.s.D ? "F" : "T"), TrdDat.s.Method, Index);
            }
         } // Finished loop through subrecords
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }
   } // Finished loop through records
}


void COMF::DumpComments() {
   // Dump COMENT records
   uint32_t i;           // Record index
   int startindex;
   printf("\n");
   for (i = 0; i < NumRecords; i++) {
      if (Records[i].Type2 == OMF_COMENT) {
         // COMENT record
         printf("\nCOMENT record:\n");
         startindex = Records[i].Index;
         // Print as hex
         while (Records[i].Index < Records[i].End) {
            printf("%02X ", Records[i].GetByte());
         }
         // Print again as string
         Records[i].Index = startindex;
         printf("\n");
         while (Records[i].Index < Records[i].End) {
            printf("%c ", Records[i].GetByte());
         }
         printf("\n");
         if (Records[i].Index != Records[i].End) err.submit(1203);   // Check for consistency
      }
   }
}


void COMF::PublicNames(CMemoryBuffer * Strings, CSList<SStringEntry> * Index, int m) {
   // Make list of public names
   // Strings will receive ASCIIZ strings
   // Index will receive records of type SStringEntry with Member = m
   SOMFRecordPointer rec;                        // Current OMF record
   char * string;                                // Symbol name
   SStringEntry se;                              // String entry record to save

   // Initialize record pointer
   rec.Start(Buf(), 0, GetDataSize());

   // Loop through records and search for PUBDEF records
   do {
      // Read record
      if (rec.Type2 == OMF_PUBDEF) {

         // Public symbols definition found
         rec.GetIndex(); // Read group
         uint32_t Segment = rec.GetIndex(); // Read segment
         if (Segment == 0) rec.GetWord(); // Read base frame
         // Loop through strings in record
         while (rec.Index < rec.End) {
            string = rec.GetString();  // Read name
            rec.GetNumeric(); // Read offset
            rec.GetIndex(); // Read type
            // Make SStringEntry record
            se.Member = m;
            // Store name
            se.String = Strings->PushString(string);
            // Store name index
            Index->Push(se);
         }
         if (rec.Index != rec.End) err.submit(1203);   // Check for consistency
      }
      if (rec.Type2 == OMF_CEXTDEF) {
         // CEXTDEF record. Store communal symbol names
         // Loop through entries in record
         while (rec.Index < rec.End) {
            uint32_t LIndex = rec.GetIndex() - 1;  // Index into preceding LNAMES
            rec.GetIndex();                      // Type index. Ignore
            // Check if index valid
            if (LIndex < LocalNameOffset.GetNumEntries()) {
               // Make SStringEntry record
               se.Member = m;
               // Get name
               char * name = GetLocalName(LIndex);
               if (strlen(name) > 0) {
                  // Store name
                  se.String = Strings->PushString(name);
                  // Store name index
                  Index->Push(se);
               }
            }
         }
         if (rec.Index != rec.End) err.submit(1203);  // Check for consistency
      }
      if (rec.Type2 == OMF_LNAMES) {
         // LNAMES record. Check if file has been parsed
         if (Records.GetNumEntries() == 0) {
            // ParseFile has not been called. We need to store LNAMES table because
            // these names may be needed by subsequent EXTDEF records.
            // Loop through strings in record
            while (rec.Index < rec.End) {
               char * LocalName = rec.GetString();
               uint32_t LocalNameIndex = NameBuffer.PushString(LocalName); // Store local name
               LocalNameOffset.Push(LocalNameIndex);// Store local name index
            }
            if (rec.Index != rec.End) err.submit(1203);  // Check for consistency
         }
      }
   } // Get next record
   while (rec.GetNext());                        // End of loop through records
}

char * COMF::GetLocalName(uint32_t i) {
   // Get section name or class name by name index
    if (i == 0 || i >= LocalNameOffset.GetNumEntries())  {
        i = NameBuffer.PushString("null");
        return (char*)NameBuffer.Buf() + i;
    }
    return (char*)NameBuffer.Buf() + LocalNameOffset[i];
}

uint32_t COMF::GetLocalNameO(uint32_t i) {
   // Get section name or class by converting name index offset into NameBuffer
   if (i > 0 && i < LocalNameOffset.GetNumEntries()) {
      return LocalNameOffset[i];
   }
   return 0;
}

const char * COMF::GetSegmentName(uint32_t i) {
   // Get section name by segment index
   if (i == 0) return "none";
   if ((i & 0xC000) == 0x4000) {
      // Borland communal section
      static char text[32];
      sprintf(text, "communal section %i", i - 0x4000);
      return text;
   }
   if (i <= NumRecords) {
      return(char*) NameBuffer.Buf() + SegmentNameOffset[i];
   }
   return "?";
}


const char * COMF::GetSymbolName(uint32_t i) {
   // Get external symbol name by index
   if (i == 0) return "null";
   if (i < SymbolNameOffset.GetNumEntries()) {
      return (char*)NameBuffer.Buf() + SymbolNameOffset[i];
   }
   // return "?";
   // index out of range
   static char temp[100];
   sprintf(temp, "Unknown index %i", i);
   return temp;
}

const char * COMF::GetGroupName(uint32_t i) {
   // Get group name by index
   if (i == 0) return "none";
   if (i <= NumRecords) {
      return (char*)NameBuffer.Buf() + GroupNameOffset[i];
   }
   return "?";
}

const char * COMF::GetRecordTypeName(uint32_t i) {
   // Get record type name
   return Lookup(OMFRecordTypeNames, i);
}

// Member functions for parsing SOMFRecordPointer
uint8_t  SOMFRecordPointer::GetByte() {
   // Read next byte from buffer
   return *(buffer + FileOffset + Index++);
}

uint16_t SOMFRecordPointer::GetWord() {
   // Read next 16 bit word from buffer
   uint16_t x = *(uint16_t*)(buffer + FileOffset + Index);
   Index += 2;
   return x;
}

uint32_t SOMFRecordPointer::GetDword() {
   // Read next 32 bit dword from buffer
   uint32_t x = *(uint32_t*)(buffer + FileOffset + Index);
   Index += 4;
   return x;
}

uint32_t SOMFRecordPointer::GetIndex() {
   // Read byte or word, depending on sign of first byte
   uint32_t byte1, byte2;
   byte1 = GetByte();
   if (byte1 & 0x80) {
      // Two byte index
      byte2 = GetByte();
      return ((byte1 & 0x7F) << 8) | byte2;
   }
   else {
      // One byte index
      return byte1;
   }
}

uint32_t SOMFRecordPointer::GetNumeric(){
   // Read word or dword, depending on record type even or odd
   if (Type & 1) {
      // Odd record type. Number is 32 bits
      return GetDword();
   }
   else {
      // Even record type. Number is 16 bit s
      return GetWord();
   }
}

uint32_t SOMFRecordPointer::GetLength() {
   // Read 1, 2, 3 or 4 bytes, depending on value of first byte
   uint32_t x = GetByte();
   switch (x) {
   case 0x81: // 16-bit value
      return GetWord();
   case 0x82: // 24-bit value
      x = GetWord();
      return (GetByte() << 16) + x;
   case 0x84: // 32-bit value
      return GetDword();
   default: // 8-bit value
      if (x > 0x80) err.submit(1203);
      return x;
   }
}

char * SOMFRecordPointer::GetString() {
   // Read string and return as ASCIIZ string in static buffer
   static char String[256];
   uint8_t Length = GetByte();
   if (Length == 0 /*|| Length >= sizeof(String)*/) {
      String[0] = 0;
   }
   else {
      // Copy string
      memcpy(String, buffer + FileOffset + Index, Length);
      // Terminate by 0
      String[Length] = 0;
   }
   // Point to next
   Index += Length;

   return String;
}

void SOMFRecordPointer::Start(int8_t * Buffer, uint32_t FileOffset, uint32_t FileEnd) {
   // Start scanning through records
   this->buffer = Buffer;
   this->FileOffset = FileOffset;
   this->FileEnd = FileEnd;
   Index = 0;
   Type = GetByte();
   Type2 = Type;  if (Type2 < OMF_LIBHEAD) Type2 &= ~1; // Make even
   uint16_t RecordSize = GetWord();
   End = Index + RecordSize - 1;
   if (FileOffset + RecordSize + 3 > FileEnd) err.submit(2301); // Extends beyond end of file
}

uint8_t SOMFRecordPointer::GetNext(uint32_t align) {
   // Get next record. Returns record type, made even. Returns 0 if finished
   // align = alignment after MODEND records = page size. Applies to lib files only
   FileOffset += End + 1;

   // Check if alignment needed
   if (align > 1 && Type2 == OMF_MODEND) {
      // Align after MODEND record in library
      FileOffset = (FileOffset + align - 1) & - (int32_t)align;
   }
   if (FileOffset >= FileEnd) return 0;          // End of file
   Index = 0;                                    // Start reading record
   Type = GetByte();                             // Get record type
   Type2 = Type;  if (Type2 < OMF_LIBHEAD) Type2 &= ~1; // Make even
   uint16_t RecordSize = GetWord();                // Get record size
   End = Index + RecordSize - 1;                 // Point to checksum byte
   if ((uint64_t)FileOffset + RecordSize + 3 > FileEnd) err.submit(2301); // Extends beyond end of file
   return Type2;
}

uint32_t SOMFRecordPointer::InterpretLIDATABlock() {
   // Interpret Data block in LIDATA record recursively
   // Prints repeat count and returns total size
   uint32_t RepeatCount = GetNumeric();
   uint32_t BlockCount  = GetWord();
   uint32_t Size = 0;
   printf("%i * ", RepeatCount);
   if (BlockCount == 0) {
      Size = GetByte();
      Index += Size;
      printf("%i", Size);
      return RepeatCount * Size;
   }
   // Nested repeat blocks
   printf("(");
   for (uint32_t i = 0; i < BlockCount; i++) {
      // Recursion
      Size += InterpretLIDATABlock();
      if (i+1 < BlockCount) printf(" + ");
   }
   printf(")");
   return RepeatCount * Size;
}


uint32_t SOMFRecordPointer::UnpackLIDATABlock(int8_t * destination, uint32_t MaxSize) {
   // Unpack Data block in LIDATA record recursively and store data at destination
   uint32_t RepeatCount = GetNumeric();            // Outer repeat count
   uint32_t BlockCount  = GetWord();               // Inner repeat count
   uint32_t Size = 0;                              // Size of data expanded so far
   uint32_t RSize;                                 // Size of recursively expanded data
   uint32_t SaveIndex;                             // Save Index for repetition
   uint32_t i, j;                                  // Loop counters
   if (BlockCount == 0) {
      // Contains one repeated block
      Size = GetByte();                          // Size of repeated block
      if (RepeatCount * Size > MaxSize) {
         // Data outside allowed area
         err.submit(2310);                       // Error message
         Index += Size;                          // Point to after block
         return 0;                               // No data stored
      }

      // Loop RepeatCount times
      for (i = 0; i < RepeatCount; i++) {
         // copy data block into destination
         memcpy(destination, buffer + FileOffset + Index, Size);
         destination += Size;
      }
      Index += Size;                             // Point to after block
      return RepeatCount * Size;                 // Size of expanded data
   }
   // Nested repeat blocks
   SaveIndex = Index;
   // Loop RepeatCount times
   for (i = 0; i < RepeatCount; i++) {
      // Go back and repeat unpacking
      Index = SaveIndex;
      // Loop BlockCount times
      for (j = 0; j < BlockCount; j++) {
         // Recursion
         RSize = UnpackLIDATABlock(destination, MaxSize);
         destination += RSize;
         MaxSize -= RSize;
         Size += RSize;
      }
   }
   return Size;
}


// Members of COMFFileBuilder, class for building OMF files
COMFFileBuilder::COMFFileBuilder() {
   // Constructor
   Index = 0;
}

void COMFFileBuilder::StartRecord(uint8_t type) {
   // Start building new record
   this->Type = type;                            // Save type
   RecordStart = Index = GetDataSize();          // Remember start position
   PutByte(Type);                                // Put type into record
   PutWord(0);                                   // Reserve space for size, put in later
}

void COMFFileBuilder::EndRecord() {
   // Finish building current record
   // Update length
   Get<uint16_t>(RecordStart + 1) = GetSize() + 1;

   // Make checksum
   int8_t checksum = 0;
   for (uint32_t i = RecordStart; i < Index; i++) checksum += Buf()[i];
   PutByte(-checksum);

   // Check size limit
   if (GetSize() > 0x407) {
      err.submit(9005);
   }
}

void COMFFileBuilder::PutByte(uint8_t x) {
   // Put byte into buffer
   Push(&x, 1);
   Index++;
}

void COMFFileBuilder::PutWord(uint16_t x) {
   // Put 16 bit word into buffer
   Push(&x, 2);
   Index += 2;
}

void COMFFileBuilder::PutDword(uint32_t x) {
   // Put 32 bit dword into buffer
   Push(&x, 4);
   Index += 4;
}

void COMFFileBuilder::PutIndex(uint32_t x) {
   // Put byte or word into buffer (word if > 0x7F)
   if (x < 0x80) {
      // One byte
      PutByte(x);
   }
   else {
      // Two bytes
      if (x > 0x7fff) {
         err.submit(2303);             // Index out of range
      }
      PutByte((uint8_t)(x >> 8) | 0x80); // First byte = high byte | 0x80
      PutByte(uint8_t(x));               // Second byte = low byte
   }
}

void COMFFileBuilder::PutNumeric(uint32_t x) {
   // Put word or dword into buffer, depending on type being even or odd
   if (Type & 1) {
      PutDword(x);                     // Type is odd, put 32 bits
   }
   else {
      if (x > 0xffff) err.submit(2304);// Index out of range
      PutWord(uint16_t(x));              // Type is even, put 16 bits
   }
}

void COMFFileBuilder::PutString(const char * s) {
   // Put ASCII string into buffer, preceded by size
   uint32_t len = (uint32_t)strlen(s);     // Check length
   if (len > 255) {
      // String too long
      err.submit(1204, s);             // Issue warning
      len = 255;                       // Truncate string to 255 characters
   }
   PutByte(uint8_t(len));                // Store length
   Push(s, len);                       // Store len bytes
   Index += len;                       // Update index
}

void COMFFileBuilder::PutBinary(void * p, uint32_t Size) {
   // Put binary data of any length
   if (Size > 1024) {err.submit(9000); Size = 1024;}  // 1024 bytes size limit
   Push(p, Size);
   Index += Size;
}

uint32_t COMFFileBuilder::GetSize() {
   // Get size of data added so far
   if (Index <= RecordStart + 3) return 0;
   return Index - RecordStart - 3;     // Type and size fields not included in size
}
