/****************************  library.cpp  **********************************
* Author:        Agner Fog
* Date created:  2006-08-27
* Last modified: 2022-05-07
* 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-2022 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

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

    // Remove path form member names and check member type before extracting or adding members
    AlignBy = 2;
    if (GetDataSize()) FixNames();    

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

    // 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
        }
        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;

    // Determine library type and subtype
    if (cmd.LibrarySubtype == 0) {    
        switch (cmd.OutputType) {
        case FILETYPE_OMF: case FILETYPE_OMFLIBRARY:
            cmd.LibrarySubtype = LIBTYPE_OMF;  break;
        case FILETYPE_COFF: case FILETYPE_DOS: 
            cmd.LibrarySubtype = LIBTYPE_WINDOWS;  break;
        case FILETYPE_ELF:
            cmd.LibrarySubtype = LIBTYPE_LINUX;  break;
        case FILETYPE_MACHO_LE: case FILETYPE_MACHO_BE:
            cmd.LibrarySubtype = LIBTYPE_BSD_MAC;  break;
        case FILETYPE_LIBRARY:
            switch (cmd.InputType) {
            case FILETYPE_COFF: case FILETYPE_DOS: 
                cmd.LibrarySubtype = LIBTYPE_WINDOWS;  break;
            case FILETYPE_ELF:
                cmd.LibrarySubtype = LIBTYPE_LINUX;  break;
            case FILETYPE_MACHO_LE: case FILETYPE_MACHO_BE:
                cmd.LibrarySubtype = LIBTYPE_BSD_MAC;  break;
            }
            break;
        default:
            cmd.LibrarySubtype = LIBTYPE_SHORTNAMES;
        }
    }

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

    // 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));
                if (cmd.DesiredWordSize) printf("%i", cmd.DesiredWordSize);
            }
            // Print output file name and type
            printf(", Output: %s, Format: %s", OutputFileName, GetFileFormatName(cmd.OutputType));
            if (cmd.DesiredWordSize) printf("%i", cmd.DesiredWordSize);
        }
        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 ? MemberName2 : MemberName1;

        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) {

                // Check file type before conversion
                int FileType0 = MemberBuffer.GetFileType();
                // 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 (MemberBuffer.OutputFileName == 0 || FileType1 != FileType0) {
                    MemberBuffer.OutputFileName = MemberBuffer.SetFileNameExtension(MemberBuffer.FileName);
                }
                MemberBuffer.FileName = MemberBuffer.OutputFileName;
            }
        }
        if (MemberFileType == 0) MemberFileType = FileType1;
        if (WordSize == 0) WordSize = WordSize1;
        if (FileType1 != MemberFileType || WordSize1 != WordSize) {
            if (WordSize1 == 0) {
                // Library member has no wordsize
                err.submit(1109, MemberBuffer.FileName);
            }
            else {
                // 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() || FileType1 != cmd.OutputType) {
                        // Conversion or name change requested

                        // Check type before conversion
                        int FileType0 = MemberBuffer.GetFileType();
                        MemberBuffer.Go();
                        if (err.Number()) return; // Stop if error
                        // Check type after conversion
                        FileType1 = MemberBuffer.GetFileType();
                        if (MemberBuffer.OutputFileName == 0 /*|| FileType1 != FileType0*/) {
                            MemberBuffer.OutputFileName = MemberBuffer.SetFileNameExtension(MemberBuffer.FileName);
                        }
                    }
                    if (MemberBuffer.OutputFileName == 0) {
                        MemberBuffer.OutputFileName = MemberBuffer.FileName;
                    }
                    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, MemberBuffer.OutputFileName);
                        }
                    }
                    if (WordSize1 == 0) {
                        err.submit(1109, MemberName1);
                    }
                    // Write this member to file
                    MemberBuffer.Write();
                }
                else {
                    // Dump this member
                    MemberBuffer.Go();
                }
            }
        }
        else if (cmd.DumpOptions == 0) {
            // Add member to new library
            if (MemberName2 == 0) MemberName2 = MemberName1;
            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::FixNames() {
    // Rebuild library or fix member names
    // Dispatch according to library type
    switch (cmd.InputType) {
    case FILETYPE_OMF: case FILETYPE_OMFLIBRARY:
        // Rebuild OMF style library and fix long member names
        RebuildOMF();  break;

    case FILETYPE_LIBRARY:
    default:
        // Fix names in UNIX style library
        StripMemberNamesUNIX();  break;
    }
}

/*  Unused:
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::RebuildUNIX() {
    // Rebuild UNIX style library
    // Make member names unique and without path. Rebuild symbol table
    char const * MemberName1 = 0;  // Name of library member
    uint32_t 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_t>(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::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_t 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();
    if ((uint64_t)DictionaryOffset + DictionarySize >= GetDataSize()) {err.submit(2035); return;}

    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 = ShortenMemberName(ModuleName);

            // Remember name to check for duplicates
            // Not needed any more:
            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_t>(3) = DictionaryOffset;

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

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


void CLibrary::StripMemberNamesUNIX() {
    // Remove path from member names, set extension to default
    char * MemberName1 = 0;  // Name of library member

    // Loop through input library
    CurrentOffset = 8;  CurrentNumber = 0;
    while ((MemberName1 = ExtractMember(&MemberBuffer)) != 0) {
        // Properties of member
        // Check if import library
        if (MemberBuffer.Get<uint32_t>(0) == 0xFFFF0000) {
            // Import library. Cannot do anything sensible
            err.submit(2507, cmd.InputFile);  return;
        }
        if (MemberName1[0] == '/') continue;  // names record
        // remember member type
        if (cmd.MemberType == 0) {
            cmd.MemberType = MemberBuffer.GetFileType();
        }
        // Fix name
        StripMemberName(MemberName1);
        // Check word size
        if (cmd.DesiredWordSize == 0) {
            cmd.DesiredWordSize = MemberBuffer.WordSize;
        }
        else if (cmd.DesiredWordSize != MemberBuffer.WordSize && MemberBuffer.WordSize != 0) {
            err.submit(2012, MemberBuffer.WordSize, cmd.DesiredWordSize);  // wrong word size
            return;
        }
        if (cmd.OutputType == FILETYPE_LIBRARY || cmd.OutputType == FILETYPE_OMFLIBRARY) {
            cmd.OutputType = cmd.MemberType;
        }
    }
}


void CLibrary::Dump() {
    // Print contents of library

    // 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_t  Flags;                                 // Dictionary flags
    uint32_t i;                                     // Loop counter
    uint32_t Align;                                 // Member alignment
    uint32_t 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_t MemberStart = 0;                       // Start of member
    uint32_t MemberEnd;                             // End of member
    uint32_t MemberNum = 0;                         // Member number
    uint32_t 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;

    printf("\nDump of library %s\nExported symbols by member:\n", cmd.InputFile);

    // 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();
            if ((uint64_t)DictionaryOffset + DictionarySize >= GetDataSize()) {err.submit(2035); return;}
            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 = (char*)Strings.Buf() + MemberIndex[i].String;
                printf("\n  %s", SymbolName);
            }
            // Align next member by PageSize;
            MemberEnd = (MemberEnd + PageSize - 1) & - (int32_t)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_t ExtendedDictionaryOffset = DictionaryOffset + DictionarySize * 512;

    if (ExtendedDictionaryOffset > GetDataSize()) {
        err.submit(2500);                          // File is truncated
    }
    if (ExtendedDictionaryOffset < GetDataSize()) {
        // Library contains extended dictionary
        uint32_t ExtendedDictionarySize = GetDataSize() - ExtendedDictionaryOffset;
        uint8_t DictionaryType = Get<uint8_t>(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_t>(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;

    printf("\nDump of library %s", cmd.InputFile);

    if (cmd.DumpOptions & DUMP_SECTHDR) {
        // dump headers
        SUNIXLibraryHeader * Header = 0;    // Member header
        uint32_t MemberSize = 0;              // Size of member
        //uint32_t HeaderExtra = 0;             // Extra added to size of header
        //uint32_t NameIndex;                 // Index into long names member
        char * Name = 0;                    // Name of member
        int symindex = 0;                   // count symbol index records
        int i;                              // loop counter

        // Search for member
        while (CurrentOffset) {
            //HeaderExtra = 0;
            // Extract next library member from input library
            Header = &Get<SUNIXLibraryHeader>(CurrentOffset);
            // Size of member
            MemberSize = (uint32_t)atoi(Header->FileSize);
            // Member name
            Name = Header->Name;
            // Terminate name
            for (i = 0; i < 15; i++) {
                if (Name[i] == ' ') {
                    Name[i+1] = 0;  break;
                }
            }
            if (i == 16) Name[i] = 0;

            if (strncmp(Name, "//", 2) == 0) {
                // This is the long names member. 
                printf("\nLongnames header \"%s\". Offset 0x%X, size 0x%X", Name,
                    CurrentOffset + (uint32_t)sizeof(SUNIXLibraryHeader), MemberSize);
            }
            else if (Name[0] == '/' && Name[1] <= ' ') {
                // Symbol index
                printf("\nSymbol index %i, \"%s\"", ++symindex, Name);
            }
            else if (strncmp(Name, "__.SYMDEF", 9) == 0) {
                // Mac/BSD Symbol index
                printf("\nSymbol index %i, \"%s\"", ++symindex, Name);
            }
            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 (strncmp(Name, "__.SYMDEF", 9) == 0) {
                    // Symbol table "__.SYMDEF SORTED" as long name
                    printf("\nSymbol index %i, \"%s\"", ++symindex, Name);
                }
            }
            else break;
            // Point to next member
            CurrentOffset = NextHeader(CurrentOffset);
        }
        CurrentOffset = 8;  CurrentNumber = 0; 
    }

    printf("\n\nExported symbols by member:\n");

    // 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);
        MemberBuffer.FileName = MemberName;

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

        WordSize = MemberBuffer.WordSize;
        printf (" - %s", GetFileFormatName(MemberFileType));
        if (WordSize) {
            printf("-%i", MemberBuffer.WordSize);
        }
        else {
            printf(". Type not specified. Possibly alias record");
        }

        // 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 = (char*)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_t i = 0; i < StringEntries.GetNumEntries(); i++) {
            uint32_t j = StringEntries[i].String;
            printf("\n   %s", StringBuffer.Buf() + j);
        }
    }
}


uint32_t CLibrary::NextHeader(uint32_t Offset) {

    // Loop through library headers.
    // Input = current offset. Output = next offset
    SUNIXLibraryHeader * Header;   // Member header
    int32_t MemberSize;          // Size of member
    //uint32_t HeaderExtra = 0;    // Extra added to size of header
    uint32_t 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_t 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;
}


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);
    }
}


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

    uint32_t RecordEnd;                             // End of OMF record
    SOMFRecordPointer rec;                        // Current OMF record
    char * MemberName = 0;                        // Name of library member
    uint32_t MemberStart = 0;                       // Start of member
    uint32_t MemberEnd = 0;                         // 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 = (char*)"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;
}


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_t MemberSize = 0;              // Size of member
    uint32_t HeaderExtra = 0;             // Extra added to size of header
    uint32_t 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_t)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 = (char*)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_t 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 = (char*)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_t*)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_t offset = DataBuffer.GetDataSize();
    Indexes.Push(offset);

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

    // Member index
    uint32_t 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_t RawSize = 0;                 // Size of binary file
    uint32_t AlignmentPadding = 0;        // Padding after file
    char * name = 0;                    // Name of member
    int NameLength = 0;                 // length of name
    int NameAfter = 0;                  // length of name after MachO header 
    int i;                              // loop counter, index

    // 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;

    if (cmd.LibrarySubtype == LIBTYPE_SHORTNAMES) {
        // Make short name
        name = ShortenMemberName(member->OutputFileName);
    }
    else {
        // Remove path from library member name. Original long name is overwritten
        name = StripMemberName((char*)(member->OutputFileName));
    }
    NameLength = (int)strlen(name);

    if (cmd.OutputType == FILETYPE_MACHO_LE && cmd.LibrarySubtype != LIBTYPE_SHORTNAMES) {
        // Mach-O library stores name after header record.
        // Name is zero padded to length 4 modulo 8 to align by 8
        int pad = 8 - ((NameLength + 4) & 7);
        NameAfter = NameLength + pad;
        sprintf(header.Name, "#1/%i ", NameAfter);
    }
    else {
        // ELF and COFF library store names < 16 characters in the name field
        if (NameLength < 16) {
            // (Don't use sprintf to write header.Name here: It seems that Gnu sprintf checks the size of the 
            // buffer it is writing to. Gives buffer overrun error when termniating zero goes beyond the name field)
            memset(header.Name, ' ', 16);
            memcpy(header.Name, name, NameLength);
            header.Name[NameLength] = '/';
        }
        else {
            // store in LongNamesBuffer
            if (cmd.OutputType == FILETYPE_COFF) {
                // COFF: Name is zero-terminated
                i = LongNamesBuffer.PushString(name);
            }
            else {
                // ELF: Name terminated by "/\n"
                i = LongNamesBuffer.Push(name, NameLength);
                LongNamesBuffer.Push("/\n", 2);
            }
            // store index into long names member
            sprintf(header.Name, "/%i ", i);
        }
    }

    // Date
    sprintf(header.Date, "%u ", (uint32_t)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_t(-int32_t(RawSize)) & (AlignBy-1);
    }

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

    // Header end
    header.HeaderEnd[0] = '`';
    header.HeaderEnd[1] = '\n';

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

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

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

    if (cmd.OutputType == FILETYPE_MACHO_LE) {    
        // Store member name after header if Mach-O
        if (NameAfter) {
            // Mach-O library stores name after header record.
            DataBuffer.PushString(name); 
        }
        DataBuffer.Align(AlignBy);
    }

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

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

    // Member index
    uint32_t 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;
    }
}

/*   unused:
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_t)TruncName[i] <= 0x20) TruncName[i] = '_';
    }

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

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

char * CLibrary::StripMemberName(char * name) {
    // Remove path from zero-terminated library member name and set extension to default.
    // Note: Original long name is overwritten
    char * p1;                          // Point to start of name without path
    const char * extension = 0;         // Default extension for file type
    int i;                              // Loop counter
    int len0;                           // Original string length
    int len;                            // String length
    int nlen;                           // length of name without extension
    int elen = 0;                       // length of extension
    static int DummyNumber = 0;         // Count invalid/null names
    int FileType;                       // File type

    // Length
    len0 = len = (int)strlen(name);

    // Remove path
    p1 = name;
    for (i = len-1; i >= 0; i--) {
        if (name[i] == '/' || name[i] == '\\' || name[i] == ':') {
            p1 = name + i + 1; break;
        }
    }
    // move to begin of buffer
    if (p1 > name) {
        strcpy(name, p1);
    }
        
    // Get file type
    if (cmd.MemberType) {
        FileType = cmd.MemberType;
    }
    else if (cmd.LibraryOptions & CMDL_LIBRARY_EXTRACTMEM) {
        FileType = cmd.InputType;
    }
    else {    
        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) {
        extension = ".obj";  elen = 4;
    }
    else if (FileType == FILETYPE_ELF || FileType == FILETYPE_MACHO_LE || FileType == FILETYPE_MACHO_BE) {
        extension = ".o";  elen = 2;
    }

    // find extension
    len = (int)strlen(name);
    for (nlen = len-1; nlen >= 0; nlen--) {
        if (name[nlen] == '.') {
            break;
        }
    }

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

    // Check if any name remains
    if ((len == 0 && len0 > 12) || nlen == 0) {     // No name. Make one
        sprintf(name, "NoName%i", ++DummyNumber);
        len = (int)strlen(name); 
    }

    // Replace extension
    if (len + elen <= len0 && extension != 0) {
        strcpy(name + nlen, extension);
    }
    // Terminate
    return name;
}


char * CLibrary::ShortenMemberName(char const *name) {
    // Truncate library member name to 15 characters and make unique
    // The path is removed and the extension set to default.
    // The original long name is not overwritten
    static char fixedName[32];          // Modified name
    char const * p1;                    // Point to start of name without path
    char const * extension;             // Default extension for file type
    int i;                              // Loop counter
    int len;                            // Filename length
    int len0;                           // Filename length without extension
    int elen;                           // length of extension
    static int RunningNumber = 0;       // Enumerate truncated names
    int FileType;                       // File type

    // Length
    len = (int)strlen(name);

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

    // move to static buffer
    if (len > 15) len = 15;
    memcpy(fixedName, p1, len);
    fixedName[len] = 0;

    // find extension
    for (i = len-1; i >= 0; i--) {
        if (fixedName[i] == '.') {
            fixedName[i] = 0;  break;
        }
    }
    // length without extension
    len0 = (int)strlen(fixedName);

    // Remove any spaces or other illegal characters
    for (i = 0; i < len0; i++) {
        if ((uint8_t)fixedName[i] <= 0x20 || fixedName[i] == '.') fixedName[i] = '_';
    }

    // Check if any name remains
    if (len0 == 0) {     // No name. Make one
        sprintf(fixedName, "NoName_%X", RunningNumber++);
        len0 = (int)strlen(fixedName); 
    }

    // 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) {
        extension = ".obj";  elen = 4;
    }
    else {
        extension = ".o";  elen = 2;
    }

    // Make unique and add extension
    if (len0 + elen >= 15) {
        // Name is truncated or possibly identical to some other truncated name.
        // Insert 2-, 3- or 4-digit running hexadecimal number.
        if (RunningNumber < 0x100) {
            sprintf(fixedName + 12 - elen, "_%02X%s", RunningNumber++, extension);
        }
        else if (RunningNumber < 0x1000) {
            sprintf(fixedName + 12 - elen, "%03X%s", RunningNumber++, extension);
        }
        else {
            sprintf(fixedName + 11 - elen, "%04X%s", (RunningNumber++ & 0xFFFF), extension);
        }
    }
    else {
        // Short name. Just add extension
        strcpy(fixedName + len0, extension);
    }

    // Return static name buffer
    return fixedName;
}

/*  Unused:
int CLibrary::MemberNameExistsUNIX(char * name) {
    // Check if DataBuffer contains a member with this name
    char Name1[20], Name2[20];
    uint32_t 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_t 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_t 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_t i, j;
    for (i = 0; i < n; i++) {
        for (j = 0; j < n - i - 1; j++) {
            s1 = (char*)StringBuffer.Buf() + Table[j].String;
            s2 = (char*)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 = (char*)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 = (char*)StringBuffer.Buf() + Table[i].String;
        for (j = i + 1; j < n; j++) {
            s2 = (char*)StringBuffer.Buf() + Table[j].String;
            if (strcmp(s1,s2) == 0) {
                // Duplicate found
                // Compose error string "Modulename1 and Modulename2"
                uint32_t errstring = LongNamesBuffer.GetDataSize();
                LongNamesBuffer.PushString(GetModuleName(Table[i].Member));
                LongNamesBuffer.SetSize(LongNamesBuffer.GetDataSize()-1); // remove terminating zero
                LongNamesBuffer.Push(" and ", 5);
                LongNamesBuffer.PushString(GetModuleName(Table[j].Member));
                err.submit(1214, s1, (char*)LongNamesBuffer.Buf() + errstring);
                LongNamesBuffer.SetSize(errstring);  // remove string again
                i++;  // Prevent excessive messages
            }
            else {
                break;
            }
        }
    }
}


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


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


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


void CLibrary::MakeSymbolTableUnix() {
    // Make symbol table for COFF, ELF or MACHO library
    // Uses UNIX archive format for COFF, BSD and Mac
    uint32_t i;                              // Loop counter
    uint32_t MemberOffset;                   // Offset to member
    uint32_t 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, ' ', 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_t PostDate = 0;
    if (SymbolTableType & 0x10000000) PostDate = 100; // Post-date if mac sorted symbol table
    sprintf(SymTab.Date, "%u ", (uint32_t)time(0) + PostDate); // Date stamp for symbol table

    SymTab.UserID[0] = '0';                // UserID = 0  (may be omitted in COFF)
    SymTab.GroupID[0] = '0';               // GroupID = 0 (may be omitted in COFF)
    strcpy(SymTab.FileMode, "100666");     // FileMode = 0100666 (may be 0 in COFF)

    SymTab.HeaderEnd[0] = '`';             // End with "`\n"
    SymTab.HeaderEnd[1] = '\n';

    // File header
    OutFile.Push("!<arch>\n", 8);

    uint32_t NumMembers = Indexes.GetNumEntries();        // Number of members
    uint32_t NumStrings = StringEntries.GetNumEntries();  // Number of symbol names
    uint32_t 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_t Index1Size = (NumStrings+1)*4 + StringsLen;
    // Sorted index, used in COFF libraries as second member
    uint32_t Index2Size = (NumMembers+2)*4 + NumStrings*2 + StringsLen;
    // Sorted index, used in Mach-O libraries
    uint32_t Index3Size = Round4(NumStrings*8 + 8 + StringsLen);
    // Longnames member
    uint32_t LongnamesMemberSize = 0;
    // Official MS COFF reference says that the "//" longnames member must be present,
    // even if it is unused, but MS LIB does not make it unless it is needed.
    // Here, we will include the longnames member only if it is needed
    if ((SymbolTableType == FILETYPE_COFF || SymbolTableType == FILETYPE_ELF) && LongNamesBuffer.GetDataSize()) {
        LongnamesMemberSize = sizeof(SUNIXLibraryHeader) + LongNamesBuffer.GetDataSize();
    }

    // Offset to first member
    uint32_t FirstMemberOffset = 0;
    switch (SymbolTableType) {
    case FILETYPE_COFF:
        FirstMemberOffset = 8 + 2*sizeof(SUNIXLibraryHeader) + RoundEven(Index1Size) 
            + RoundEven(Index2Size) + RoundEven(LongnamesMemberSize);
        break;
    case FILETYPE_ELF:
        FirstMemberOffset = 8 + sizeof(SUNIXLibraryHeader) + RoundEven(Index1Size) 
            + RoundEven(LongnamesMemberSize);
        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_t 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_t MemberI = (uint16_t)(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 longnames table member for COFF or ELF output
    // (The decision whether to include a "//" longnames member is taken above)
    if (LongnamesMemberSize) {
        // reuse SymTab
        strcpy(SymTab.Name, "//       ");    // Name = "//"
        memset(SymTab.FileSize, ' ', 10);
        sprintf(SymTab.FileSize, "%u", LongNamesBuffer.GetDataSize());

        // 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 data
        OutFile.Push(LongNamesBuffer.Buf(), LongNamesBuffer.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 & 0x3FFFFFF); // prevent string overflow
        }

        // 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_t 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_t PageSize;                              // Page size / alignment for output library
    uint32_t temp;                                  // Temporary
    uint16_t temp16;                                // Temporary
    uint8_t  temp8;                                 // Temporary
    uint32_t MemberI;                               // Member number
    uint32_t MemberOffset;                          // File offset of member in output file
    uint32_t MemberStart;                           // Start of member in DataBuffer
    uint32_t MemberEnd;                             // End of member in DataBuffer
    uint32_t SymbolI;                               // Public symbol number
    uint32_t DictionaryOffset2;                     // Offset to hash table
    CSList<uint32_t> 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_t>(3) = DictionaryOffset2;

    // Hash table size
    OutFile.Get<uint16_t>(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_t i;                                     // Loop counter
    char * Name;                                    // Public symbol name
    COMFHashTable HashTab;                          // OMF hash table interpreter
    uint32_t NString;                               // Number of occurrences of Name in hash table
    uint32_t Module;                                // Module with first occurrence of Name
    uint32_t 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 = (char*)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_t 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_t>(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_t 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 after record
                memcpy(name, DataBuffer.Buf()+Offset+sizeof(SUNIXLibraryHeader), 16);
            }
            else if (name[0] == '/') {
                // Long name in longnames record
                uint32_t NameIndex = atoi(name+1);
                if (NameIndex < LongNamesSize) {
                    return (char*)Buf() + LongNames + NameIndex;
                }
                else {
                    return "?";
                }
            }

            // 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 "?";
}
