/****************************  library.cpp  **********************************
* Author:        Agner Fog
* Date created:  2006-08-27
* Last modified: 2010-06-27
* Project:       objconv
* Module:        library.cpp
* Description:
* This module contains code for reading, writing and manipulating function
* libraries (archives) of the UNIX type and OMF type.
*
* Copyright 2006-2010 GNU General Public License http://www.gnu.org/licenses
*****************************************************************************/

#include "stdafx.h"

// OMF Library flag names
SIntTxt OMFLibraryFlags[] = {
   {0,      "Flag = 0"},
   {1,      "Case sensitive"}
};


CLibrary::CLibrary() {
   // Constructor
   CurrentOffset = 0;
   CurrentNumber = 0;
   LongNames = 0;
   LongNamesSize = 0;
   AlignBy = 0;
   MemberFileType = 0;
   RepressWarnings = 0;
   PageSize = 16;
}


void CLibrary::Go() {
   // Do to library whatever the command line says
   char const * MemberName1 = 0;  // Name of library member
   char const * MemberName2 = 0;  // Modified name of library member
   int action = 0;                // Action to take on member
   int FileType1 = 0;             // File type of current member
   int WordSize1 = 0;             // Word size of current member

   // check LibraryOptions and whether an output file is specified
   if (cmd.FileOptions & CMDL_FILE_OUTPUT) {
      // Output is a library file
      if ((cmd.OutputFile == 0 || cmd.OutputFile[0] == 0) && !(cmd.LibraryOptions & CMDL_LIBRARY_ADDMEMBER)) {
         err.submit(2503); // Output file name missing
         return;
      }
      // Check extension
      if (strstr(cmd.OutputFile, ".lib") && strstr(cmd.OutputFile, ".LIB") && strstr(cmd.OutputFile, ".a")) {
         err.submit(1101); // Warning wrong extension
      }
      // Check if valid output type
      if (cmd.OutputType >= IMPORT_LIBRARY_MEMBER) {
         // Wrong output type
         if (cmd.OutputType == FILETYPE_ASM) {
            // Attempt to disassemble whole library
            err.submit(2620);
         }
         else {
            err.submit(2621);
         }
         return;
      }
   }

   // Desired alignment = 2 for COFF and ELF, 8 for Mach-O
   AlignBy = 2;
   if (cmd.OutputType == FILETYPE_MACHO_LE) AlignBy = 8;

   if (cmd.DumpOptions && !(cmd.LibraryOptions & CMDL_LIBRARY_EXTRACTMEM)) {
      // Dump library, but not its members
      Dump();
      return;
   }

   // Reserve space for data buffer
   DataBuffer.SetSize(GetBufferSize());

   // Fix member names before extracting or adding members
   if (GetDataSize()) Rebuild();

   if (err.Number()) return; // Stop if error

   // Check options
   if (!cmd.LibraryOptions) cmd.LibraryOptions = CMDL_LIBRARY_CONVERT;

   if (cmd.Verbose) {
      // Tell what we are doing
      if ((cmd.LibraryOptions & CMDL_LIBRARY_ADDMEMBER) && GetDataSize() == 0) {
         // Creating library
         printf("\nCreating library %s (%s)", FileName, GetFileFormatName(cmd.OutputType));
      }
      else if (OutputFileName) {
         // Print input file name
         printf("\nInput library: %s", FileName);
         if (cmd.InputType != cmd.OutputType) {
            // Converting library type. Print input file type
            int InType = cmd.InputType; 
            if (InType == FILETYPE_LIBRARY || InType == FILETYPE_OMFLIBRARY) InType = cmd.MemberType;
            printf(", Format: %s", GetFileFormatName(InType));
         }
         // Print output file name and type
         printf(", Output: %s, Format: %s", 
            OutputFileName, GetFileFormatName(cmd.OutputType));
      }
      else {
         printf("\nExtracting from library file: %s", FileName);
      }
   }

   // Convert library or extract or add or dump all members
   StartExtracting();                           // Initialize before ExtractMember()

   // Loop through input library
   while ((MemberName1 = ExtractMember(&MemberBuffer)) != 0) {

      // Check if any specific action required for this member
      action = cmd.SymbolChange(MemberName1, &MemberName2, SYMT_LIBRARYMEMBER);
      if ((action != SYMA_CHANGE_NAME && action != SYMA_EXTRACT_MEMBER) || MemberName2 == 0) {
         MemberName2 = MemberName1; // No name change
      }
      MemberBuffer.FileName = MemberName1;
      MemberBuffer.OutputFileName = MemberName2;

      if (action == SYMA_DELETE_MEMBER) {
         // Remove this member from library
         if (cmd.Verbose) {
            printf("\nRemoving member %s from library", MemberName1);
         }
         continue;
      }
      if (action == SYMA_ADD_MEMBER) {
         // Replace this member with new file
         // (Message comes later when adding new member)
         continue;
      }

      // Check type of this member
      FileType1 = MemberBuffer.GetFileType();
      if (FileType1 == 0) continue;
      WordSize1 = MemberBuffer.WordSize;

      // if (!(cmd.LibraryOptions & (CMDL_LIBRARY_EXTRACTMEM | CMDL_LIBRARY_ADDMEMBER))) {
      // Not adding or extracting members. Apply conversion options to all members
      if (cmd.SymbolChangesRequested() || FileType1 != cmd.OutputType) {

         // Conversion or name change requested
         MemberBuffer.Go();                   // Do required conversion
         if (err.Number()) break;             // Stop if error
      }
      // }
      // Check type again after conversion
      FileType1 = MemberBuffer.GetFileType();

      if (MemberFileType == 0) MemberFileType = FileType1;
      if (WordSize == 0) WordSize = WordSize1;
      if (FileType1 != MemberFileType || WordSize1 != WordSize) {
         // Library members have different type or word size
         err.submit(1102); 
      }

      if (cmd.LibraryOptions & CMDL_LIBRARY_EXTRACTMEM) {
         // Extract member(s)
         if (action == SYMA_EXTRACT_MEMBER || cmd.LibraryOptions == CMDL_LIBRARY_EXTRACTALL) {
            // Extract this member
            if (cmd.DumpOptions == 0 && cmd.OutputType != CMDL_OUTPUT_DUMP) {
               // Write this member to file
               if (err.Number()) return; // Check first if error

               if (cmd.SymbolChangesRequested()) {
                  // Conversion or name change requested
                  MemberBuffer.Go();
                  if (err.Number()) return; // Stop if error
               }

               if (cmd.Verbose) {
                  // Tell what we are doing
                  if (MemberName1 == MemberName2) {
                     printf("\nExtracting file %s from library", MemberName1);
                  }
                  else {
                     printf("\nExtracting library member %s to file %s", MemberName1, MemberName2);
                  }
               }
               // Write this member to file
               if (MemberName2) MemberBuffer.OutputFileName = MemberName2;
               MemberBuffer.Write();
            }
            else {
               // Dump this member
               MemberBuffer.Go();
            }
         }
      }
      else if (cmd.DumpOptions == 0) {
         // Add member to new library
         if (cmd.Verbose) {
            // Tell what we are doing
            if (strcmp(MemberName1, MemberName2) != 0) {
               printf("\nRenaming member %s to %s", MemberName1, MemberName2);
            }
         }
         // Put into new library
         InsertMember(&MemberBuffer);
      }
   } // End of loop through library
   // Stop if error
   if (err.Number()) return; 

   if (cmd.LibraryOptions & CMDL_LIBRARY_ADDMEMBER) {
      // Add object files to library
      SSymbolChange const * sym;
      // Loop through file names to add
      while ((sym = cmd.GetMemberToAdd()) != 0) {
         MemberBuffer.Reset();                     // Reset MemberBuffer
         // Name of object file
         MemberBuffer.FileName = sym->Name2;       // Name of object file
         MemberBuffer.OutputFileName = sym->Name1; // Name of new member
         // Read object file
         MemberBuffer.Read();
         // Stop if read failed
         if (err.Number()) continue; 
         // Detect file type
         int NewMemberType = MemberBuffer.GetFileType();
         if (cmd.Verbose) {
            // Tell what we are doing
            if (sym->Done) {
               printf("\nReplacing member %s with file %s", sym->Name1, sym->Name2);
            }
            else {
               printf("\nAdding member %s from file %s", sym->Name1, sym->Name2);
            }
            if (NewMemberType != cmd.OutputType) {
               // Converting type
               printf(". Converting from %s.", GetFileFormatName(NewMemberType));
            }
         }
         // Do any conversion required
         MemberBuffer.Go();

         // Stop if error
         if (err.Number()) continue; 

         // Check if file type is right after conversion
         MemberFileType = MemberBuffer.FileType;
         if (WordSize == 0) WordSize = MemberBuffer.WordSize;
         if (MemberFileType != cmd.OutputType) {
            // Library members have different type
            err.submit(2504, GetFileFormatName(MemberBuffer.FileType)); continue; 
         }
         if (MemberBuffer.WordSize != WordSize) {
            // Library members have different word size
            err.submit(2505, MemberBuffer.WordSize); continue; 
         }
         // Put into library
         MemberBuffer.FileName = MemberBuffer.OutputFileName;
         InsertMember(&MemberBuffer);
      } // End of loop through object file names
   } 
   // Stop if error
   if (err.Number()) return; 

   if (cmd.LibraryOptions & CMDL_LIBRARY_EXTRACTMEM) {
      cmd.CheckExtractSuccess();  // Check if members to extract were found
   }

   if (cmd.FileOptions & CMDL_FILE_OUTPUT) {
      // Make output library
      MakeBinaryFile();
      // Take over OutFile buffer
      *this << OutFile;
   }
   else {
      // No output library
      OutputFileName = 0;
   }
}


void CLibrary::Rebuild() {
   // Rebuild library: fix member names
   // Dispatch according to library type
   switch (cmd.InputType) {
   case FILETYPE_OMF: case FILETYPE_OMFLIBRARY:
      // Rebuild OMF style library
      RebuildOMF();  break;

   case FILETYPE_LIBRARY:
   default:
      // Rebuild UNIX style library
      RebuildUNIX();  break;
   }
}

void CLibrary::RebuildOMF() {
   // Rebuild OMF style library.
   // Removes paths from member names, truncates to 16 characters, and makes unique.
   // Symbol table is removed, not remade

   SOMFRecordPointer rec;                        // Current OMF record
   char * ModuleName;                            // Module name
   SStringEntry se;                              // String entry record to save
   COMFFileBuilder NewBuffer;                    // Buffer for rebuilt library
   uint32 Align;                                 // Alignment

   // Remember member file type
   cmd.MemberType = FILETYPE_OMF;

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

   // Read header
   if (rec.Type2 != OMF_LIBHEAD) {err.submit(2500); return;} // Does not start with library header

   // Read library header
   DictionaryOffset = rec.GetDword();
   DictionarySize = rec.GetWord();
   rec.GetByte(); // Ignore flag
   // Page size / alignment for members
   PageSize = rec.End + 1;
   Align = 1 << FloorLog2(PageSize);       // Make power of 2
   if (PageSize != Align) {
      err.submit(2601, PageSize);          // Error: not a power of 2
   }

   // Copy library header to new buffer
   NewBuffer.Push(Buf(), PageSize);

   // Reset record loop end when DictionaryOffset is known
   rec.FileEnd = DictionaryOffset;

   // Next record is start of first module
   rec.GetNext();
   if (rec.Type2 != OMF_THEADR) err.submit(2500);  // Member does not start with THEADR

   // Loop through the records of all OMF modules
   do {
      // Check record type
      switch (rec.Type2) {

      case OMF_THEADR:     // Start of module

         // Get name
         ModuleName = rec.GetString();

         // Remove path, truncate name and make unique
         ModuleName = FixMemberNameOMF(ModuleName);

         // Remember name to check for duplicates
         se.String = StringBuffer.PushString(ModuleName);
         se.Member = NewBuffer.GetDataSize();
         StringEntries.Push(se);

         // Make new THEADR record
         NewBuffer.StartRecord(OMF_THEADR);
         NewBuffer.PutString(ModuleName);
         NewBuffer.EndRecord();
         break;

      case OMF_MODEND: case OMF_LIBEND:   // End of module or library
         // Copy MODEND record
         NewBuffer.Push(Buf() + rec.FileOffset, rec.End + 1);
         
         // Align output file by PageSize
         NewBuffer.Align(PageSize);
         break;

      default:   // Any other record in module
         // Copy record unchanged
         NewBuffer.Push(Buf() + rec.FileOffset, rec.End + 1);
         break;
      }
   }  // Point to next record
   while (rec.GetNext(PageSize));                // End of loop through records

   // Put dictionary offset in LIBHEAD record
   DictionaryOffset = NewBuffer.GetDataSize();
   NewBuffer.Get<uint32>(3) = DictionaryOffset;

   // Take over modified library file
   *this << NewBuffer;

   // Empty used buffers
   StringEntries.SetNum(0);
   StringBuffer.SetSize(0);
}


void CLibrary::RebuildUNIX() {
   // Rebuild UNIX style library
   // Make member names unique and without path. Rebuild symbol table
   char const * MemberName1 = 0;  // Name of library member
   uint32 OutputFileType;

   // Save OutputType before changing it
   OutputFileType = cmd.OutputType;

   // Loop through input library
   CurrentOffset = 8;  CurrentNumber = 0;
   while ((MemberName1 = ExtractMember(&MemberBuffer)) != 0) {
      // Properties of member
      MemberBuffer.FileName = MemberBuffer.OutputFileName = MemberName1;
      // Check if import library
      if (MemberBuffer.Get<uint32>(0) == 0xFFFF0000) {
         // Import library. Cannot do anything sensible
         err.submit(2507, cmd.InputFile);  return;
      }
      // Remember member type
      cmd.MemberType = MemberBuffer.GetFileType();
      // Temporarily set OutputType to MemberType
      cmd.OutputType = cmd.MemberType;
      // Put into new library
      InsertMember(&MemberBuffer);
   }
   // Avoid getting warnings twice for duplicate symbols
   RepressWarnings = 1; 
   // Make library header etc. and add all members to OutFile
   MakeBinaryFile();
   RepressWarnings = 0;

   // Restore OutputType
   cmd.OutputType = OutputFileType;

   // Replace file buffer by OutFile
   *this << OutFile;

   // Clear buffers used for building OutFile
   StringBuffer.SetSize(0);
   StringEntries.SetNum(0);
   Indexes.SetNum(0);
   DataBuffer.SetSize(0);
} 


void CLibrary::Dump() {
   // Print contents of library
   printf("\nDump of library %s\nExported symbols by member:\n", cmd.InputFile);

   // Dispatch according to library type
   switch (cmd.InputType) {
   case FILETYPE_LIBRARY:
      DumpUNIX();  break;                        // Print contents of UNIX style library

   case FILETYPE_OMFLIBRARY:
      DumpOMF();   break;                        // Print contents of OMF style library

   default:
      err.submit(9000);                          // Should not occur
   }
}

void CLibrary::DumpOMF() {
   // Print contents of OMF style library
   uint8  Flags;                                 // Dictionary flags
   uint32 i;                                     // Loop counter
   uint32 Align;                                 // Member alignment
   uint32 RecordEnd;                             // End of OMF record
   SOMFRecordPointer rec;                        // Current OMF record
   char * MemberName;                            // Name of library member
   char * SymbolName;                            // Name of public symbol in member
   uint32 MemberStart;                           // Start of member
   uint32 MemberEnd;                             // End of member
   uint32 MemberNum = 0;                         // Member number
   uint32 FirstPublic;                           // Index to first public name of current member
   CMemoryBuffer Strings;                        // Local string buffer
   CSList<SStringEntry> MemberIndex;             // Local member index buffer
   COMF Member;                                  // Local buffer for member

   DictionaryOffset = GetDataSize();             // Loop end. This value is changed when library header is read
   rec.Start(Buf(), 0, DictionaryOffset);        // Initialize record pointer

   PageSize = 0;

   // Loop through the records of all OMF modules
   do {
      // Check record type
      switch (rec.Type2) {

      case OMF_LIBHEAD:  // Library header. This should be the first record
         if (PageSize || rec.FileOffset > 0) {
            err.submit(2600);  break;            // More than one header
         }
         // Read library header
         DictionaryOffset = rec.GetDword();
         DictionarySize = rec.GetWord();
         Flags = rec.GetByte();
         // Page size / alignment for members
         PageSize = rec.End + 1;
         Align = 1 << FloorLog2(PageSize);       // Make power of 2
         if (PageSize != Align) {
            err.submit(2601, PageSize);          // Error: not a power of 2
         }
         // Reset record loop end when DictionaryOffset is known
         rec.FileEnd = DictionaryOffset;

         // Print values from LIBHEAD
         printf("\nOMF Library. Page size %i. %s.",
            PageSize, Lookup(OMFLibraryFlags, Flags));
         break;

      case OMF_THEADR: // Module header. Member starts here
         MemberName = rec.GetString();           // Get name
         MemberStart = rec.FileOffset;           // Get start address
         printf("\nMember %s Offset 0x%X", MemberName, MemberStart);// Print member name
         break;

      case OMF_MODEND: // Member ends here.
         RecordEnd = rec.FileOffset + rec.End + 1;// End of record
         MemberEnd = RecordEnd;                   // = member end address

         // Store member in temporary buffer
         Member.SetSize(0);
         Member.Push(Buf() + MemberStart, MemberEnd - MemberStart);

         // Get public names from member
         FirstPublic = MemberIndex.GetNumEntries();
         Member.PublicNames(&Strings, &MemberIndex, ++MemberNum); 

         // Print public names
         for (i = FirstPublic; i < MemberIndex.GetNumEntries(); i++) {
            SymbolName = Strings.Buf() + MemberIndex[i].String;
            printf("\n  %s", SymbolName);
         }
         // Align next member by PageSize;
         MemberEnd = (MemberEnd + PageSize - 1) & - (int32)PageSize;
         rec.End = MemberEnd - rec.FileOffset - 1;
         break;

      case OMF_LIBEND: // Last member should end here
         RecordEnd = rec.FileOffset + rec.End + 1;// End of record
         if (RecordEnd != DictionaryOffset) err.submit(2602);
         break;
      }     
   }  // Go to next record
   while (rec.GetNext());                        // End of loop through records

   // Check hash table integrity
   CheckOMFHash(Strings, MemberIndex);

   // Check if there is an extended library dictionary
   uint32 ExtendedDictionaryOffset = DictionaryOffset + DictionarySize * 512;

   if (ExtendedDictionaryOffset > GetDataSize()) {
      err.submit(2500);                          // File is truncated
   }
   if (ExtendedDictionaryOffset < GetDataSize()) {
      // Library contains extended dictionary
      uint32 ExtendedDictionarySize = GetDataSize() - ExtendedDictionaryOffset;
      uint8 DictionaryType = Get<uint8>(ExtendedDictionaryOffset); // Read first byte of extended dictionary
      if (DictionaryType == OMF_LIBEXT) {
         // Extended dictionary in the official format
         printf("\nExtended dictionary IBM/MS format. size %i", ExtendedDictionarySize);
      }
      else if (ExtendedDictionarySize >= 10 && (DictionaryType == 0xAD || Get<uint16>(ExtendedDictionaryOffset + 2) == MemberNum)) {
         // Extended dictionary in the proprietary Borland format, documented only in US Patent 5408665, 1995
         printf("\nExtended dictionary Borland format. size %i", ExtendedDictionarySize);
      }
      else {
         // Unknown format
         printf("\nExtended dictionary size %i, unknown type 0x%02X", 
            ExtendedDictionarySize, DictionaryType);
      }
   }
}


void CLibrary::DumpUNIX() {
   // Print contents of UNIX style library

   const char * MemberName = 0;
   CurrentOffset = 8;  CurrentNumber = 0;

   // Loop through library
   while (CurrentOffset + sizeof(SUNIXLibraryHeader) < DataSize) {

      // Reset buffers
      StringBuffer.SetSize(0);
      MemberBuffer.Reset();
      StringEntries.SetNum(0);

      // Get member name
      MemberName = ExtractMember(&MemberBuffer);
      if (MemberName == 0) break;
      printf("\nMember %s", MemberName);

      // Detect file type of member
      MemberFileType = MemberBuffer.GetFileType();

      WordSize = MemberBuffer.WordSize;
      printf (" - %s", GetFileFormatName(MemberFileType));
      if (WordSize) printf("-%i", MemberBuffer.WordSize);

      // Get symbol table for specific file type
      switch (MemberFileType) {
      case FILETYPE_ELF: 
         if (WordSize == 32) {
            // Make instance of file parser, 32 bit template
            CELF<ELF32STRUCTURES> elf;
            MemberBuffer >> elf;        // Transfer MemberBuffer to elf object
            elf.PublicNames(&StringBuffer, &StringEntries, 0);
            break;
         }
         else {
            // Make instance of file parser, 64 bit template
            CELF<ELF64STRUCTURES> elf;
            MemberBuffer >> elf;        // Transfer MemberBuffer to elf object
            elf.PublicNames(&StringBuffer, &StringEntries, 0);
            break;
         }

      case FILETYPE_COFF: {
         CCOFF coff;
         MemberBuffer >> coff;       // Transfer MemberBuffer to coff object
         coff.PublicNames(&StringBuffer, &StringEntries, 0);
         break;}

      case FILETYPE_MACHO_LE:
         if (WordSize == 32) {
            // Make instance of file parser, 32 bit template
            CMACHO<MAC32STRUCTURES> mac;
            MemberBuffer >> mac;       // Transfer MemberBuffer to coff object
            mac.PublicNames(&StringBuffer, &StringEntries, 0);
            break;
         }
         else {
            // Make instance of file parser, 64 bit template
            CMACHO<MAC64STRUCTURES> mac;
            MemberBuffer >> mac;       // Transfer MemberBuffer to coff object
            mac.PublicNames(&StringBuffer, &StringEntries, 0);
            break;
         }

      case IMPORT_LIBRARY_MEMBER: {
         // This is an import library
         char * name1 = MemberBuffer.Buf() + 20;
         printf("\n  Import %s from %s", name1, name1 + strlen(name1) + 1);
         break;} 

      default:
         printf("\n   Cannot extract symbol names from this file type");
         break;
      }

      // Loop through table of public names
      for (uint32 i = 0; i < StringEntries.GetNumEntries(); i++) {
         uint32 j = StringEntries[i].String;
         printf("\n   %s", StringBuffer.Buf() + j);
      }
   }
}


uint32 CLibrary::NextHeader(uint32 Offset) {

   // Loop through library headers.
   // Input = current offset. Output = next offset
   SUNIXLibraryHeader * Header;   // Member header
   int32 MemberSize;          // Size of member
   //uint32 HeaderExtra = 0;    // Extra added to size of header
   uint32 NextOffset;         // Offset of next header

   if (Offset + sizeof(SUNIXLibraryHeader) >= DataSize) {
      // No more members
      return 0;
   }
   // Find header
   Header = &Get<SUNIXLibraryHeader>(Offset);

   // Size of member
   MemberSize = atoi(Header->FileSize);
   if (MemberSize < 0 || MemberSize + Offset + sizeof(SUNIXLibraryHeader) > DataSize) {
      err.submit(2500);  // Points outside file
      return 0;
   }
   if (strncmp(Header->Name, "#1/", 3) == 0) {
      // Name refers to long name after the header
      // This variant is used by Mac and some versions of BSD.
      // HeaderExtra is included in MemberSize:
      // HeaderExtra = atoi(Header->Name+3);
   }

   // Get next offset
   NextOffset = Offset + sizeof(SUNIXLibraryHeader) + MemberSize;
   // Round up to align by 2
   NextOffset = (NextOffset + 1) & ~ 1;
   // Check if last
   if (NextOffset >= DataSize) NextOffset = 0;
   return NextOffset;
}


void CLibrary::StartExtracting() {
   // Initialize before ExtractMember() 
   if (cmd.InputType == FILETYPE_OMFLIBRARY) {
      SOMFRecordPointer rec;                     // OMF records
      rec.Start(Buf(), 0, GetDataSize());        // Initialize record pointer
      if (rec.Type2 != OMF_LIBHEAD) {
         err.submit(2500);  return;              // This should not happen
      }
      // Read library header
      DictionaryOffset = rec.GetDword();         // Read dictionary offset
      DictionarySize   = rec.GetWord();          // Read dictionary size
      rec.GetByte();                             // Read flag
      // Page size / alignment for members
      PageSize = rec.End + 1;
      uint32 align = 1 << FloorLog2(PageSize);   // Make power of 2
      if (PageSize != align) {
         err.submit(2601, PageSize);             // Error: not a power of 2
      }
      CurrentOffset = PageSize;                  // Offset to first library member
   }
   else {
      // Unix style library. First header at offset 8
      CurrentOffset = 8;
   }
   // Current member number
   CurrentNumber = 0;
}


const char * CLibrary::ExtractMember(CFileBuffer * Destination) {
   // Extract library member
   // Dispatch according to library type
   if (cmd.InputType == FILETYPE_OMFLIBRARY || cmd.InputType == FILETYPE_OMF) {
      return ExtractMemberOMF(Destination);
   }
   else {
      return ExtractMemberUNIX(Destination);
   }
}


const char * CLibrary::ExtractMemberOMF(CFileBuffer * Destination) {
   // Extract member of OMF style library

   uint32 RecordEnd;                             // End of OMF record
   SOMFRecordPointer rec;                        // Current OMF record
   const char * MemberName;                      // Name of library member
   uint32 MemberStart;                           // Start of member
   uint32 MemberEnd;                             // End of member

   if (CurrentOffset >= DictionaryOffset) return 0;// No more members

   rec.Start(Buf(), CurrentOffset, DictionaryOffset);// Initialize record pointer

   // Loop through the records of all OMF modules
   do {
      // Check record type
      switch (rec.Type2) {

      case OMF_THEADR: // Module header. Member starts here
         MemberName  = rec.GetString();          // Get name
         MemberStart = rec.FileOffset;           // Get start address
         break;

      case OMF_MODEND: // Member ends here.
         RecordEnd = rec.FileOffset + rec.End +1;// End of record
         MemberEnd = RecordEnd;                  // = member end address

         // Save member as raw data
         if (Destination) {
            Destination->SetSize(0);             // Make sure destination buffer is empty
            Destination->FileType = Destination->WordSize = 0;
            Destination->Push(Buf() + MemberStart, MemberEnd - MemberStart);
         }

         // Align next member by PageSize;
         rec.GetNext(PageSize);
         CurrentOffset = rec.FileOffset;

         // Check name
         if (MemberName[0] == 0) MemberName = "NoName!";

         // Return member name
         return MemberName;

      case OMF_LIBEND: // Last member should end here
         RecordEnd = rec.FileOffset + rec.End + 1;// End of record
         if (RecordEnd != DictionaryOffset) err.submit(2602);

         // No more members:
         return 0;
      }     
   }  // Go to next record
   while (rec.GetNext());                        // End of loop through records

   err.submit(2610);                             // Library end record not found
   return 0;
}


const char * CLibrary::ExtractMemberUNIX(CFileBuffer * Destination) {
   // Extract member of UNIX style library
   // This function is called repeatedly to get each member of library/archive
   SUNIXLibraryHeader * Header = 0;     // Member header
   uint32 MemberSize = 0;              // Size of member
   uint32 HeaderExtra = 0;             // Extra added to size of header
   uint32 NameIndex;                   // Index into long names member
   char * Name = 0;                    // Name of member
   int Skip = 1;                       // Skip record and search for next
   int i;                              // Loop counter
   char * p;                           // Used for loop through string

   if (CurrentOffset == 0 || CurrentOffset + sizeof(SUNIXLibraryHeader) >= DataSize) {
      // No more members
      return 0;
   }

   // Search for member
   while (Skip && CurrentOffset) {
      HeaderExtra = 0;
      // Extract next library member from input library
      Header = &Get<SUNIXLibraryHeader>(CurrentOffset);
      // Size of member
      MemberSize = (uint32)atoi(Header->FileSize);
      if (MemberSize + CurrentOffset + sizeof(SUNIXLibraryHeader) > DataSize) {
         err.submit(2500);  // Points outside file
         return 0;
      }
      // Member name
      Name = Header->Name;
      if (strncmp(Name, "// ", 3) == 0) {
         // This is the long names member. Remember its position
         LongNames = CurrentOffset + sizeof(SUNIXLibraryHeader);
         LongNamesSize = MemberSize;
         // The long names are terminated by '/' or 0, depending on system,
         // but may contain non-terminating '/'. Find out which type we have:
         // Pointer to LongNames record
         p = Buf() + LongNames;
         // Find out whether we have terminating zeroes:
         if (LongNamesSize > 1 && p[LongNamesSize-1] == '/' || (p[LongNamesSize-1] <= ' ' && p[LongNamesSize-2] == '/')) {
            // Names are terminated by '/'. Replace all '/' by 0 in the longnames record
            for (uint32 j = 0; j < LongNamesSize; j++, p++) {
               if (*p == '/') *p = 0;
            }
         }
      }
      else if (strncmp(Name, "/ ", 2) == 0
      || strncmp(Name, "__.SYMDEF", 9) == 0) {
         // This is a symbol index member.
         // The symbol index is not used because we are always building a new symbol index.
      }
      else if (Name[0] == '/' && Name[1] >= '0' && Name[1] <= '9' && LongNames) {
         // Name contains index into LongNames record
         NameIndex = atoi(Name+1);
         if (NameIndex < LongNamesSize) {
            Name = Buf() + LongNames + NameIndex;
         }
         else {
            Name = (char*)"NoName!";
         }
         Skip = 0;
      }
      else if (strncmp(Name, "#1/", 3) == 0) {
         // Name refers to long name after the header
         // This variant is used by Mac and some versions of BSD
         HeaderExtra = atoi(Name+3);
         Name += sizeof(SUNIXLibraryHeader);
         if (MemberSize > HeaderExtra) {
            // The length of the name, HeaderExtra, is included in the 
            // Header->FileSize field. Subtract to get the real file size
            MemberSize -= HeaderExtra;
         }
         if (strncmp(Name, "__.SYMDEF", 9) == 0) {
            // Symbol table "__.SYMDEF SORTED" as long name
            Skip = 1;
         }
         else {
            Skip = 0;
         }
      }
      else {
         // Ordinary short name
         // Name may be terminated by '/' or space. Replace termination char by 0
         for (i = 15; i >= 0; i--) {
            if (Name[i] == ' ' || Name[i] == '/') Name[i] = 0;
            else break;
         }
         // Terminate name with max length by overwriting Date field, which we are not using
         Name[16] = 0;
         Skip = 0;
      }
      // Point to next member
      CurrentOffset = NextHeader(CurrentOffset);
      // Increment number
      CurrentNumber += !Skip;
   }  // End of while loop

   // Save member as raw data
   if (Destination) {
      Destination->SetSize(0);       // Make sure destination buffer is empty
      Destination->FileType = Destination->WordSize = 0;
      Destination->Push((int8*)Header + sizeof(SUNIXLibraryHeader) + HeaderExtra, MemberSize);
   }

   // Check name
   if (Name[0] == 0) Name = (char*)"NoName!";

   // Return member name
   return Name;
}

void CLibrary::InsertMember(CFileBuffer * member) {
   // Add member to output library
   if (cmd.OutputType == FILETYPE_OMF) {
      InsertMemberOMF(member);                   // OMF style output library
   }
   else {
      InsertMemberUNIX(member);                  // UNIX style output library
   }
}


void CLibrary::InsertMemberOMF(CFileBuffer * member) {
   // Add member to OMF library

   // Check file type
   if (member->GetFileType() != FILETYPE_OMF) err.submit(9000);

   // Get word size
   WordSize = member->WordSize;

   // Store offset
   uint32 offset = DataBuffer.GetDataSize();
   Indexes.Push(offset);

   // Store member
   DataBuffer.Push(member->Buf(), member->GetDataSize());
   DataBuffer.Align(PageSize);

   // Member index
   uint32 mindex = Indexes.GetNumEntries() - 1;

   // Get public string table
   COMF omf;
   *member >> omf;     // Translate member to class OMF
   omf.PublicNames(&StringBuffer, &StringEntries, mindex); // Make list of public names
   *member << omf;     // Return buffer to member
}


void CLibrary::InsertMemberUNIX(CFileBuffer * member) {
   // Add next library member to output library
   uint32 RawSize = 0;                 // Size of binary file
   uint32 AlignmentPadding = 0;        // Padding after file

   // Get word size
   WordSize = member->WordSize;

   // Make member header
   SUNIXLibraryHeader header;
   memset(&header, ' ', sizeof(SUNIXLibraryHeader));  // Fill with spaces

   // Name of member
   if (member->OutputFileName == 0 || *member->OutputFileName == 0) member->OutputFileName = member->FileName;
   char * name = FixMemberNameUNIX(member->OutputFileName);

   // Set length of member string after header
   int NameLength = 0;
   if (cmd.OutputType == FILETYPE_MACHO_LE) {
      // Mach-O library stores name after header record.
      // Name is zero padded to length NameLength = 20
      NameLength = 20;
      strcpy(header.Name, "#1/20");
   }
   else {
      // ELF and COFF library store names < 16 characters in the name field
      strcpy(header.Name, name);
   }
   // Date
   sprintf(header.Date, "%u", (uint32)time(0));
   // User and group id
   header.UserID[0] = '0';
   header.GroupID[0] = '0';
   // File mode
   strcpy(header.FileMode, "100666");
   // Size of binary file
   RawSize = member->GetDataSize();
   // Calculate alignment padding
   if (AlignBy) {
      AlignmentPadding = uint32(-int32(RawSize)) & (AlignBy-1);
   }

   // File size including name string
   sprintf(header.FileSize, "%u", NameLength + RawSize + AlignmentPadding);

   // Header end
   header.HeaderEnd[0] = '`';
   header.HeaderEnd[1] = '\n';
   // Remove terminating zeroes
   for (uint32 i = 0; i < sizeof(SUNIXLibraryHeader); i++) {
      if (((char*)&header)[i] == 0) ((char*)&header)[i] = ' ';
   }

   // Store offset
   uint32 offset = DataBuffer.GetDataSize();
   Indexes.Push(offset);

   // Store member header
   DataBuffer.Push(&header, sizeof(header));

   // Store member name after header if Mach-O
   if (NameLength) {
      // Mach-O library stores name after header record.
      char membernamebuffer[32];
      memset (membernamebuffer, 0, sizeof(membernamebuffer));
      strncpy (membernamebuffer, name, 16);
      DataBuffer.Push(membernamebuffer, NameLength);
   }

   // Store member
   DataBuffer.Push(member->Buf(), RawSize);

   // Align by padding with '\n'
   for (uint32 i = 0; i < AlignmentPadding; i++) {
      DataBuffer.Push("\n", 1);
   }

   // Member index
   uint32 mindex = Indexes.GetNumEntries() - 1;

   // Get public string table
   switch(member->GetFileType()) {
   case FILETYPE_COFF: {
      CCOFF coff;
      *member >> coff;     // Translate member to type COFF
      coff.PublicNames(&StringBuffer, &StringEntries, mindex); // Make list of public names
      *member << coff;     // Return buffer to member
      break;}

   case FILETYPE_OMF: {
      COMF omf;
      *member >> omf;      // Translate member to type COFF
      omf.PublicNames(&StringBuffer, &StringEntries, mindex); // Make list of public names
      *member << omf;      // Return buffer to member
      break;}

   case FILETYPE_ELF:
      if (WordSize == 32) {
         // Make instance of file parser, 32 bit template
         CELF<ELF32STRUCTURES> elf;
         *member >> elf;      // Translate member to type ELF
         elf.PublicNames(&StringBuffer, &StringEntries, mindex); // Make list of public names
         *member << elf;      // Return buffer to member
         break;
      }
      else {
         // Make instance of file parser, 64 bit template
         CELF<ELF64STRUCTURES> elf;
         *member >> elf;      // Translate member to type ELF
         elf.PublicNames(&StringBuffer, &StringEntries, mindex); // Make list of public names
         *member << elf;      // Return buffer to member
         break;
      }

   case FILETYPE_MACHO_LE:
      if (WordSize == 32) {
         // Make instance of file parser, 32 bit template
         CMACHO<MAC32STRUCTURES> mac;
         *member >> mac;      // Translate member to type ELF
         mac.PublicNames(&StringBuffer, &StringEntries, mindex); // Make list of public names
         *member << mac;      // Return buffer to member
         break;
      }
      else {
         // Make instance of file parser, 64 bit template
         CMACHO<MAC64STRUCTURES> mac;
         *member >> mac;      // Translate member to type ELF
         mac.PublicNames(&StringBuffer, &StringEntries, mindex); // Make list of public names
         *member << mac;      // Return buffer to member
         break;
      }

   default:                // Type not supported
      err.submit(2501, GetFileFormatName(member->GetFileType()));
      break;
   }
}


char * CLibrary::TruncateMemberName(char const * name) {
   // Remove path and truncate object file name to 15 characters
   // This function removes any path from the member name,
   // changes the extension to the default for the the output file type,
   // changes any spaces to underscores, and 
   // truncates the member name to 15 characters for the sake of compatibility.
   // The return value is an ASCII string in a static buffer
   static char TruncName[32];          // Truncated name
   int maxlen;                         // Max length, not including extension
   char const * p1;                    // Point to start of name without path
   char const * extension;             // Default extension for file type
   int i;                              // Loop counter
   int len;                            // String length
   static int DummyNumber = 0;         // Count invalid/null names
   int FileType;                       // File type

   // Remove path
   len = (int)strlen(name);  p1 = name;
   for (i = len-1; i >= 0; i--) {
      if (name[i] == '/' || name[i] == '\\' || name[i] == ':') {
         p1 = name + i + 1; break;
      }
   }
   // Remove extension
   len = (int)strlen(p1);
   for (i = len-1; i >= 0; i--) {
      if (p1[i] == '.') {
         len = i;  break;
      }
   }

   // Check if any name remains
   if (len == 0) {     // No name. Make one
      sprintf(TruncName, "NoName%i", ++DummyNumber);
      p1 = TruncName;  len = (int)strlen(p1); 
   }

   // Get file type
   FileType = cmd.OutputType;
   if (FileType == CMDL_OUTPUT_DUMP || FileType == 0) FileType = cmd.InputType;
   if (FileType >= FILETYPE_LIBRARY) FileType = cmd.MemberType;

   // Get default extension and max length of name without extension
   if (FileType == FILETYPE_COFF || FileType == FILETYPE_OMF) {
      maxlen = 11;  extension = ".obj";
   }
   else {
      maxlen = 13;  extension = ".o";
   }
   if (len > maxlen) len = maxlen;

   // Truncate name
   strncpy(TruncName, p1, len);
   TruncName[len] = 0;

   // Remove any spaces or other illegal characters
   len = (int)strlen(TruncName);
   for (i = 0; i < len; i++) {
      if ((uint8)TruncName[i] <= 0x20) TruncName[i] = '_';
   }

   // Add default extension
   strcpy(TruncName+strlen(TruncName), extension);

   // Terminate
   TruncName[15] = 0;  
   return TruncName;
}


char * CLibrary::FixMemberNameOMF(char const *name) {
   // Truncate library member name to 15 characters and make unique
   uint32 i;                                     // Loop counter
   uint32 IsDuplicate = 0;                       // Name is duplicate
   uint32 Len;                                   // String length
   uint32 n;                                     // Number appended to name
   uint32 np;                                    // Position of number in case number is appended to name
   static char FixedName[32];                    // Modified name

   // Truncate name and copy to FixedName buffer
   strcpy(FixedName, TruncateMemberName(name));

   // Find extension
   Len = (uint32)strlen(FixedName);
   for (np = 0; np < Len; np++) if (FixedName[np] == '.') break;
   // Where to place number in case number is appended to name
   if (np > 8) np = 8;

   // Loop to find unique name
   while (1) {
      IsDuplicate = 0;
      // Check if unique
      for (i = 0; i < StringEntries.GetNumEntries(); i++) {
         if (strcmp(FixedName, StringBuffer.Buf() + StringEntries[i].String) == 0) {
            // Duplicate name found
            IsDuplicate = 1;
            break;
         }
      }
      if (!IsDuplicate) break;                   // Name is unique

      // Name is duplicate. Modify name.
      n = atoi(FixedName+np);                    // Get any number in the end of name
      sprintf(FixedName+np, "%i.obj", n+1);      // .. and add 1. Append extension
   }
   return FixedName;
}

char * CLibrary::FixMemberNameUNIX(char const * name) {
   // This function does the same as TruncateMemberName(name).
   // In addition, it makes the name unique and terminates it with a '/'

   static char TruncName[32];          // Store truncated name
   uint32 len, maxlen;                 // Length and maximum length of name
   char const * extension;             // Default extension for file type
   uint32 i, n;                        // Temporary
   uint32 np;                          // Position of number in case number is appended to name

   strcpy(TruncName, TruncateMemberName(name));  // Truncate name
   TruncName[16] = 0;

   // Get default extension and max length of name without extension
   if (cmd.OutputType == FILETYPE_COFF || cmd.OutputType == FILETYPE_OMF) {
      maxlen = 11;  extension = ".obj";
   }
   else {
      maxlen = 13;  extension = ".o";
   }

   // Find extension
   len = (uint32)strlen(TruncName);
   for (np = 0; np < len; np++) if (TruncName[np] == '.') break;
   // Where to place number in case number is appended to name
   if (np > maxlen - 3) np = maxlen - 3;

   // Check if another member has the same truncated name
   while (MemberNameExistsUNIX(TruncName)) {
      // Another member has the same name. Make name unique
      n = atoi(TruncName+np);               // Get any number in the end of name
      // Increment number and re-insert extension
      sprintf(TruncName+np, "%i%s", n+1, extension);
   }

   // Terminate
   len = (int)strlen(TruncName);
   TruncName[len] = '/';                             // Terminate with '/'
   if (cmd.OutputType == FILETYPE_MACHO_LE) {
      // Mach-O library stores name differently
      TruncName[len] = 0;                            // Terminate with 0
   }

   for (i = len+1; i < 16; i++) TruncName[i] = ' ';  // Pad with spaces
   TruncName[16] = 0;                                // Terminate with 0
   
   return TruncName;
}


int CLibrary::MemberNameExistsUNIX(char * name) {
   // Check if DataBuffer contains a member with this name
   char Name1[20], Name2[20];
   uint32 i, j;
  
   // Terminate name without extension
   memcpy(Name1, name, 16);
   for (j = 0; j < 16; j++) {
      if (Name1[j] == '.' || Name1[j] == '/') Name1[j] = 0;
   }

   // Loop through previous members in DataBuffer
   for (i = 0; i < Indexes.GetNumEntries(); i++) {
      uint32 offset = Indexes[i];
      // Copy name of member i
      memcpy(Name2, DataBuffer.Buf() + offset, 16);
      // Terminate name2
      for (j = 0; j < 16; j++) {
         if (Name2[j] == '.' || Name2[j] == '/') Name2[j] = 0;
      }
      // Case-insensitive compare of names
      if (stricmp(Name1, Name2) == 0) {
         // Identical name found
         return i + 1;
      }
   }
   // No identical name found
   return 0;
}


void CLibrary::SortStringTable() {
   // Sort the string table in ASCII order

   // Length of table
   int32 n = StringEntries.GetNumEntries();
   if (n <= 0) return;

   // Point to table of SStringEntry records
   SStringEntry * Table = &StringEntries[0];
   // String pointers
   char * s1, * s2;
   // Temporary record for swapping
   SStringEntry temp;

   // Simple Bubble sort:
   int32 i, j;
   for (i = 0; i < n; i++) {
      for (j = 0; j < n - i - 1; j++) {
         s1 = StringBuffer.Buf() + Table[j].String;
         s2 = StringBuffer.Buf() + Table[j+1].String;
         if (strcmp(s1, s2) > 0) {
            // Swap records
            temp = Table[j];
            Table[j] = Table[j+1];
            Table[j+1] = temp;
         }
      }
   }
   // Now StringEntries has been sorted. Reorder StringBuffer to the sort order.
   CMemoryBuffer SortedStringBuffer;    // Temporary buffer for strings in sort order
   for (i = 0; i < n; i++) {
      // Pointer to old string
      s1 = StringBuffer.Buf() + Table[i].String;
      // Update table to point to new string
      Table[i].String = SortedStringBuffer.GetDataSize();
      // Put string into SortedStringBuffer
      SortedStringBuffer.PushString(s1);
   }
   if (SortedStringBuffer.GetDataSize() != StringBuffer.GetDataSize()) {
      // The two string buffers should be same size
      err.submit(9000); return;
   }
   // Copy SortedStringBuffer into StringBuffer
   memcpy(StringBuffer.Buf(), SortedStringBuffer.Buf(), StringBuffer.GetDataSize());

   // Check for duplicate symbols
   for (i = 0; i < n-1; i++) {
      s1 = StringBuffer.Buf() + Table[i].String;
      for (j = i + 1; j < n; j++) {
         s2 = StringBuffer.Buf() + Table[j].String;
         if (strcmp(s1,s2) == 0) {
            // Duplicate found
            // Compose error string "Modulename1 and Modulename2"
            char ErrorModuleNames[64];
            strcpy(ErrorModuleNames, GetModuleName(Table[i].Member));
            strcpy(ErrorModuleNames + strlen(ErrorModuleNames), " and ");
            strcpy(ErrorModuleNames + strlen(ErrorModuleNames), GetModuleName(Table[j].Member));
            // submit error message
            err.submit(1214, s1, ErrorModuleNames);
            // Prevent excessive messages
            i++;
         }
         else {
            break;
         }
      }
   }
}


uint32 EndianChange(uint32 n) {
   // Convert little-endian to big-endian number, or vice versa
   return (n << 24) | ((n & 0x0000FF00) << 8) | ((n & 0x00FF0000) >> 8) | (n >> 24);
}


uint32 RoundEven(uint32 n) {
   // Round up number to nearest even
   return (n + 1) & uint32(-2);
}


uint32 Round4(uint32 n) {
   // Round up number to nearest multiple of 4
   return (n + 3) & uint32(-4);
}


void CLibrary::MakeSymbolTableUnix() {
   // Make symbol table for COFF, ELF or MACHO library
   // Uses UNIX archive format for COFF, BSD and Mac
   uint32 i;                              // Loop counter
   uint32 MemberOffset;                   // Offset to member
   uint32 LongNameSize = 0;               // Length of symbol table name if stored after record

   int SymbolTableType = cmd.OutputType;  // FILETYPE_COFF       = 1: COFF
                                          // FILETYPE_ELF        = 3: ELF
                                          // FILETYPE_MACHO_LE   = 4: Mac, unsorted
                                          //              0x10000004: Mac, sorted
   // Newer Mac tools require the sorted type, unless there are multiple publics with same name
   if (SymbolTableType == FILETYPE_MACHO_LE) SymbolTableType |= 0x10000000; 

   // Make symbol table header
   SUNIXLibraryHeader SymTab;             // Symbol table header
   memset(&SymTab, 0, sizeof(SymTab));    // Fill with spaces
   SymTab.Name[0] = '/';                  // Name = '/'
   // The silly Mac linker requires that the symbol table has a date stamp not 
   // older than the .a file. Fix this by post-dating the symbol table:
   uint32 PostDate = 0;
   if (SymbolTableType & 0x10000000) PostDate = 100; // Post-date if mac sorted symbol table
   sprintf(SymTab.Date, "%u", (uint32)time(0) + PostDate); // Date stamp for symbol table
   SymTab.UserID[0] = '0';                // UserID = 0
   SymTab.GroupID[0] = '0';               // GroupID = 0
   strcpy(SymTab.FileMode, "100666");     // FileMode = 0100666
   SymTab.HeaderEnd[0] = '`';             // End with "`\n"
   SymTab.HeaderEnd[1] = '\n';
   
   // File header
   OutFile.Push("!<arch>\n", 8);

   uint32 NumMembers = Indexes.GetNumEntries();        // Number of members
   uint32 NumStrings = StringEntries.GetNumEntries();  // Number of symbol names
   uint32 StringsLen = StringBuffer.GetDataSize();     // Size of string table

   // Calculate sizes of string index records, not including header
   // Unsorted index, used in ELF and COFF libraries
   uint32 Index1Size = (NumStrings+1)*4 + StringsLen;
   // Sorted index, used in COFF libraries as second member
   uint32 Index2Size = (NumMembers+2)*4 + NumStrings*2 + StringsLen;
   // Sorted index, used in Mach-O libraries
   uint32 Index3Size = Round4(NumStrings*8 + 8 + StringsLen);

   // Offset to first member
   uint32 FirstMemberOffset = 0;
   switch (SymbolTableType) {
   case FILETYPE_COFF:
      FirstMemberOffset = 8 + 2*sizeof(SUNIXLibraryHeader) + RoundEven(Index1Size) + RoundEven(Index2Size);
      break;
   case FILETYPE_ELF:
      FirstMemberOffset = 8 + sizeof(SUNIXLibraryHeader) + RoundEven(Index1Size);
      break;
   case FILETYPE_MACHO_LE:
      FirstMemberOffset = 8 + sizeof(SUNIXLibraryHeader) + Index3Size;
      break;
   case FILETYPE_MACHO_LE | 0x10000000: // Mac, sorted
      LongNameSize = 20;
      FirstMemberOffset = 8 + sizeof(SUNIXLibraryHeader) + Index3Size + LongNameSize;
      break;      
   default:
      err.submit(2501, GetFileFormatName(cmd.OutputType));
   }

   // Make unsorted symbol table for COFF or ELF output
   if (SymbolTableType == FILETYPE_COFF || SymbolTableType == FILETYPE_ELF) {

      // Put file size into symbol table header
      sprintf(SymTab.FileSize, "%u", Index1Size);
      // Remove terminating zeroes
      for (i = 0; i < sizeof(SymTab); i++) {
         if (((char*)&SymTab)[i] == 0) ((char*)&SymTab)[i] = ' ';
      }

      // Store header
      OutFile.Push(&SymTab, sizeof(SymTab));

      // Store table of offsets
      uint32 BigEndian;             // Number converted to big-endian
      BigEndian = EndianChange(NumStrings);
      OutFile.Push(&BigEndian, sizeof(BigEndian));  // Number of symbols

      // Loop through strings
      for (i = 0; i < NumStrings; i++) {
         // Get record in temporary symbol table
         SStringEntry * psym = &StringEntries[i];
         // Get offset of member in DataBuffer
         MemberOffset = Indexes[psym->Member];
         // Add size of headers to compute member offset in final file
         BigEndian = EndianChange(MemberOffset + FirstMemberOffset);
         // Store offset as big endian number
         OutFile.Push(&BigEndian, sizeof(BigEndian));
      }

      // Store strings
      OutFile.Push(StringBuffer.Buf(), StringBuffer.GetDataSize());
      // Align by 2
      if (OutFile.GetDataSize() & 1) {
         OutFile.Push("\n", 1);
      }
   }

   // Sort string table
   if (!RepressWarnings && SymbolTableType != FILETYPE_MACHO_LE) SortStringTable();

   // Make sorted symbol table, COFF style
   if (SymbolTableType == FILETYPE_COFF) {
      if (NumMembers > 0xFFFF) err.submit(2502);  // Too many members

      // Reuse symbol table header, change size entry
      sprintf(SymTab.FileSize, "%u", Index2Size);

      // Remove terminating zeroes
      for (i = 0; i < sizeof(SymTab); i++) {
         if (((char*)&SymTab)[i] == 0) ((char*)&SymTab)[i] = ' ';
      }
      // Store header
      OutFile.Push(&SymTab, sizeof(SymTab));

      // Store number of members
      OutFile.Push(&NumMembers, sizeof(NumMembers));

      // Store member offsets
      for (i = 0; i < NumMembers; i++) {
         MemberOffset = Indexes[i] + FirstMemberOffset;
         OutFile.Push(&MemberOffset, sizeof(MemberOffset));
      }

      // Store number of symbols
      OutFile.Push(&NumStrings, sizeof(NumStrings));

      // Store member index for each string
      // Loop through strings
      for (i = 0; i < NumStrings; i++) {
         // Get record in temporary symbol table
         SStringEntry * psym = &StringEntries[i];
         // Get member index, 16 bits
         uint16 MemberI = (uint16)(psym->Member + 1);
         OutFile.Push(&MemberI, sizeof(MemberI));
      }

      // Store strings
      OutFile.Push(StringBuffer.Buf(), StringBuffer.GetDataSize());
      // Align by 2
      if (OutFile.GetDataSize() & 1) {
         OutFile.Push("\n", 1);
      }
   }

   // Make sorted or unsorted symbol table, Mach-O style
   if ((SymbolTableType & 0xFFFF) == FILETYPE_MACHO_LE) {

      if (SymbolTableType & 0x10000000) {
         // Sorted table. "__.SYMDEF SORTED" stored as long name
         memcpy(SymTab.Name, "#1/20           ", 16);
         // Put file size into symbol table header, including long name length
         sprintf(SymTab.FileSize, "%u", Index3Size + LongNameSize);
      }
      else {
         // Unsorted table. "__.SYMDEF" stored as short name
         memcpy(SymTab.Name, "__.SYMDEF       ", 16);
         // Put file size into symbol table header
         sprintf(SymTab.FileSize, "%u", Index3Size);
      }

      // Remove terminating zeroes
      for (i = 0; i < sizeof(SymTab); i++) {
         if (((char*)&SymTab)[i] == 0) ((char*)&SymTab)[i] = ' ';
      }

      // Store header
      OutFile.Push(&SymTab, sizeof(SymTab));

      if (SymbolTableType & 0x10000000) {
         // Store long name "__.SYMDEF SORTED"
         OutFile.Push("__.SYMDEF SORTED\0\0\0\0", LongNameSize);
      }

      // Store an array of records of string index and member offsets
      // Store length first
      uint32 ArrayLength = NumStrings * sizeof(SStringEntry);
      OutFile.Push(&ArrayLength, sizeof(ArrayLength));

      // Loop through strings
      for (i = 0; i < NumStrings; i++) {
         // Get record in temporary symbol table
         SStringEntry * psym = &StringEntries[i];
         SStringEntry Record;
         Record.String = psym->String;
         Record.Member = Indexes[psym->Member] + FirstMemberOffset;
         // Store symbol record
         OutFile.Push(&Record, sizeof(Record));
      }

      // Store length of string table
      StringsLen = Round4(StringsLen);             // Round up to align by 4
      OutFile.Push(&StringsLen, sizeof(StringsLen));
      // Store strings
      OutFile.Push(StringBuffer.Buf(), StringBuffer.GetDataSize());
      // Align by 4
      OutFile.Align(4);
      // Cross check precalculated size (8 is the size of "!<arch>\n" file identifier) 
      if (OutFile.GetDataSize() != Index3Size + sizeof(SymTab) + 8 + LongNameSize) err.submit(9000);
   }
}


void CLibrary::MakeBinaryFile() {
   if (cmd.OutputType == FILETYPE_OMF) {
      MakeBinaryFileOMF();                       // OMF style output library
   }
   else {
      MakeBinaryFileUNIX();                      // UNIX style output library
   }
}


void CLibrary::MakeBinaryFileOMF() {
   // Make OMF library
   uint32 PageSize;                              // Page size / alignment for output library
   uint32 temp;                                  // Temporary
   uint16 temp16;                                // Temporary
   uint8  temp8;                                 // Temporary
   uint32 MemberI;                               // Member number
   uint32 MemberOffset;                          // File offset of member in output file
   uint32 MemberStart;                           // Start of member in DataBuffer
   uint32 MemberEnd;                             // End of member in DataBuffer
   uint32 SymbolI;                               // Public symbol number
   uint32 DictionaryOffset2;                     // Offset to hash table
   CSList<uint32> MemberPageIndex;               // Remember page index of each member

   // Check number of entries
   if (DataBuffer.GetNumEntries() >= 0x8000) {
      err.submit(2606);  return;                 // Error: too big
   }

   // Find optimal page size
   PageSize = DataBuffer.GetDataSize() / (0x8000 - DataBuffer.GetNumEntries());
   // Make power of 2, minimum 16
   temp = FloorLog2(PageSize) + 1;
   if (temp < 4) temp = 4;
   PageSize = 1 << temp;

   // Make library header
   temp8 = OMF_LIBHEAD;
   OutFile.Push(&temp8, 1);                      // Library header type byte
   temp16 = PageSize - 3;
   OutFile.Push(&temp16, 2);                     // Record length
   OutFile.Push(0, 6);                           // Dictionary offset and size: insert later
   temp8 = 1;
   OutFile.Push(&temp8, 1);                      // Flag: case sensitive
   OutFile.Align(PageSize);                      // Align for first member

   // Allocate MemberPageIndex
   MemberPageIndex.SetNum(Indexes.GetNumEntries());

   // Insert members
   for (MemberI = 0; MemberI < Indexes.GetNumEntries(); MemberI++) {
   
      // Find member in DataBuffer 
      MemberStart = Indexes[MemberI];            // Start of member in DataBuffer
      if (MemberI+1 < Indexes.GetNumEntries()) {
         // Not last member
         MemberEnd = Indexes[MemberI+1];         // End of member in DataBuffer = start of next member
      }
      else {
         // Last member
         MemberEnd = DataBuffer.GetDataSize();   // End of member in DataBuffer = end of DataBuffer
      }

      // Put member into output file
      MemberOffset = OutFile.Push(DataBuffer.Buf() + MemberStart, MemberEnd - MemberStart);

      // Align next member
      OutFile.Align(PageSize);

      // Member page index
      MemberPageIndex[MemberI] = MemberOffset / PageSize;
   }

   // Change member index to member page index in StringEntries
   // Loop through StringEntries
   for (SymbolI = 0; SymbolI < StringEntries.GetNumEntries(); SymbolI++) {
      // Member index
      MemberI = StringEntries[SymbolI].Member;
      if (MemberI < MemberPageIndex.GetNumEntries()) {
         // Change to member page
         StringEntries[SymbolI].Member = MemberPageIndex[MemberI];
      }
   }

   // Make OMF_LIBEND record
   temp8 = OMF_LIBEND;
   OutFile.Push(&temp8, 1);                      // Library header type byte
   temp16 = PageSize - 3;                        // Length of rest of record
   OutFile.Push(&temp16, 2);                     // Record length
   OutFile.Align(PageSize);                      // Align

   // Offset to hash table
   DictionaryOffset2 = OutFile.GetDataSize();

   // Make hash table for public symbols
   COMFHashTable HashTable;
   HashTable.MakeHashTable(StringEntries, StringBuffer, OutFile, this); // Make hash table

   // Insert missing values in library header
   // Hash table offset
   OutFile.Get<uint32>(3) = DictionaryOffset2;

   // Hash table size
   OutFile.Get<uint16>(7) = (OutFile.GetDataSize() - DictionaryOffset2) / OMFBlockSize;
}


void CLibrary::MakeBinaryFileUNIX() {
   // Make UNIX library
   // Combine string index and members into binary file

   // Reserve file buffer for output file
   OutFile.SetSize(GetBufferSize());

   if (cmd.OutputType == FILETYPE_COFF || cmd.OutputType == FILETYPE_ELF || cmd.OutputType == FILETYPE_MACHO_LE) {
      // COFF, ELF and MAach-O libraries all use Unix-style archive with 
      // differences in symbol table format

      // Make symbol table
      MakeSymbolTableUnix();

      // Store all members
      OutFile.Push(DataBuffer.Buf(), DataBuffer.GetDataSize());
   }
   else {
      err.submit(2501, GetFileFormatName(cmd.OutputType));
   }
}


void CLibrary::CheckOMFHash(CMemoryBuffer &stringbuf, CSList<SStringEntry> &index) {
   // Check if OMF library hash table has correct entries for all symbol names
   uint32 i;                                     // Loop counter
   int8 * Name;                                  // Public symbol name
   COMFHashTable HashTab;                        // OMF hash table interpreter
   uint32 NString;                               // Number of occurrences of Name in hash table
   uint32 Module;                                // Module with first occurrence of Name
   uint32 Conf, ConfSum = 0;                     // Count number of conflicting entries in hash table

   // Initialize hash table interpreter
   HashTab.Init(&Get<SOMFHashBlock>(DictionaryOffset), DictionarySize);

   // Loop through public symbol names
   for (i = 0; i < index.GetNumEntries(); i++) {
      // Get public name
      Name = stringbuf.Buf() + index[i].String;
      // Make hash
      HashTab.MakeHash(Name);
      // Search for string
      NString = HashTab.FindString(Module, Conf);
      // Count conflicting strings
      ConfSum += Conf;

      // Make error message if not 1 occurrence of Name
      if (NString == 0) err.submit(2603, Name);  // Error if not found
      if (NString >  1) err.submit(1213, NString, Name);  // Warning more than one occurrence

      //printf("\n%i occurence of %s, module offset %i", NString, Name, Module);
   }
   printf("\n\nHash table %i blocks x 37 buckets at offet 0x%X.\n Efficiency: %i conflicts for %i entries",
      DictionarySize, DictionaryOffset, ConfSum, index.GetNumEntries());
}


const char * CLibrary::GetModuleName(uint32 Index) {
   // Get name of module from index (UNIX) or page index (OMF)
   static char name[32];
   if (cmd.OutputType == FILETYPE_OMF || cmd.OutputType == FILETYPE_OMFLIBRARY) {
      // Get name of module in OMF library
      if (Index * PageSize < OutFile.GetDataSize() && OutFile.Get<uint8>(Index * PageSize) == OMF_THEADR) {
         SOMFRecordPointer rec;                     // Record pointer
         rec.Start(OutFile.Buf(), Index * PageSize, OutFile.GetDataSize());
         if (rec.Type2 == OMF_THEADR) {
            // Get module name from THEADR record
            strncpy(name, rec.GetString(), 16);
            // Make sure name is not too long
            name[16] = 0;
            // Return name
            return name;
         }
      }
      // No module starts here
      return "?";
   }
   // UNIX style library.
   if (Index < Indexes.GetNumEntries()) {
      // Get offset from Index
      uint32 Offset = Indexes[Index];
      if (Offset < DataBuffer.GetDataSize()) {
         // Copy name from header
         memcpy(name, DataBuffer.Buf() + Offset, 16);
         // Check for long name
         if (strncmp(name, "#1/", 3) == 0) {
            // Long name format
            memcpy(name, DataBuffer.Buf()+Offset+sizeof(SUNIXLibraryHeader), 16);
         }
         // Find terminating '/'
         for (int i = 0; i < 16; i++) if (name[i] == '/') name[i] = 0;
         // Make sure name is not too long
         name[16] = 0;
         // return name
         return name;
      }
   }
   // Error
   return "?";
}
