/****************************  containers.cpp  **********************************
* Author:        Agner Fog
* Date created:  2006-07-15
* Last modified: 2016-07-07
* Project:       objconv
* Module:        containers.cpp
* Description:
* Objconv is a portable C++ program for converting object file formats.
* Compile for console mode on any platform.
*
* This module contains container classes CMemoryBuffer and CFileBuffer for
* dynamic memory allocation and file read/write. See containers.h for
* further description.
*
* Copyright 2006-2016 GNU General Public License http://www.gnu.org/licenses
*****************************************************************************/

#include "stdafx.h"

// Names of file formats
SIntTxt FileFormatNames[] = {
    {FILETYPE_COFF,         "COFF"},
    {FILETYPE_OMF,          "OMF"},
    {FILETYPE_ELF,          "ELF"},
    {FILETYPE_MACHO_LE,     "Mach-O Little Endian"},
    {FILETYPE_MACHO_BE,     "Mach-O Big Endian"},
    {FILETYPE_DOS,          "DOS executable"},
    {FILETYPE_WIN3X,        "Windows 3.x executable"},
    {FILETYPE_LIBRARY,      "Function library"},
    {FILETYPE_OMFLIBRARY,   "Function library (OMF)"},
    {IMPORT_LIBRARY_MEMBER, "Windows import library member"},
    {FILETYPE_MAC_UNIVBIN,  "MacIntosh universal binary"},   
    {FILETYPE_MS_WPO,       "Whole program optimization intermediate file, Microsoft specific"},
    {FILETYPE_INTEL_WPO,    "Whole program optimization intermediate file, Intel specific"},
    {FILETYPE_WIN_UNKNOWN,  "Unknown subtype, Windows"},   
    {FILETYPE_ASM,          "Disassembly"}
};


// Members of class CMemoryBuffer
CMemoryBuffer::CMemoryBuffer() {  
    // Constructor
    buffer = 0;
    NumEntries = DataSize = BufferSize = 0;
}

CMemoryBuffer::~CMemoryBuffer() {
    // Destructor
    SetSize(0);                         // De-allocate buffer
}

void CMemoryBuffer::SetSize(uint32_t size) {
    // Allocate, reallocate or deallocate buffer of specified size.
    // DataSize is initially zero. It is increased by Push or PushString.
    // Setting size > DataSize will allocate more buffer and fill it with zeroes but not increase DataSize.
    // Setting size < DataSize will decrease DataSize so that some of the data are discarded.
    // Setting size = 0 will discard all data and de-allocate the buffer.
    if (size == 0) {
        // Deallocate
        if (buffer) delete[] buffer;     // De-allocate buffer
        buffer = 0;
        NumEntries = DataSize = BufferSize = 0;
        return;
    }
    if (size < DataSize) {
        // Request to delete some data
        DataSize = size;
        return;
    }
    if (size <= BufferSize) {
        // Request to reduce size but not delete it
        return;                          // Ignore
    }
//  size = (size + 15) & uint32_t(-16);   // Round up size to value divisible by 16
    size = (size + BufferSize + 15) & uint32_t(-16);   // Double size and round up to value divisible by 16
    int8_t * buffer2 = 0;                 // New buffer
    buffer2 = new int8_t[size];           // Allocate new buffer
    if (buffer2 == 0) {err.submit(9006); return;} // Error can't allocate
    memset (buffer2, 0, size);          // Initialize to all zeroes
    if (buffer) {
        // A smaller buffer is previously allocated
        memcpy (buffer2, buffer, BufferSize); // Copy contents of old buffer into new
        delete[] buffer;                 // De-allocate old buffer
    }
    buffer = buffer2;                   // Save pointer to buffer
    BufferSize = size;                  // Save size
}

uint32_t CMemoryBuffer::Push(void const * obj, uint32_t size) {
    // Add object to buffer, return offset
    // Parameters: 
    // obj = pointer to object, 0 if fill with zeroes
    // size = size of object to push

    // Old offset will be offset to new object
    uint32_t OldOffset = DataSize;

    // New data size will be old data size plus size of new object
    uint32_t NewOffset = DataSize + size;

    if (NewOffset > BufferSize) {
        // Buffer too small, allocate more space.
        // We can use SetSize for this only if it is certain that obj is not 
        // pointing to an object previously allocated in the old buffer
        // because it would be deallocated before copied into the new buffer:
        // SetSize (NewOffset + NewOffset / 2 + 1024);

        // Allocate more space without using SetSize:
        // Double the size + 1 kB, and round up size to value divisible by 16
        uint32_t NewSize = (NewOffset * 2 + 1024 + 15) & uint32_t(-16);
        int8_t * buffer2 = 0;                        // New buffer
        // Allocate new buffer
        buffer2 = new int8_t[NewSize];
        if (buffer2 == 0) {
            // Error can't allocate
            err.submit(9006);  return 0;
        }
        // Initialize to all zeroes
        memset (buffer2, 0, NewSize);
        if (buffer) {
            // A smaller buffer is previously allocated
            // Copy contents of old buffer into new
            memcpy (buffer2, buffer, BufferSize);
        }
        BufferSize = NewSize;                      // Save size
        if (obj && size) {                         
            // Copy object to new buffer
            memcpy (buffer2 + OldOffset, obj, size);
            obj = 0;                                // Prevent copying once more
        }
        // Delete old buffer after copying object
        if (buffer) delete[] buffer;

        // Save pointer to new buffer
        buffer = buffer2;
    }
    // Copy object to buffer if nonzero
    if (obj && size) {
        memcpy (buffer + OldOffset, obj, size);
    }
    if (size) {
        // Adjust new offset
        DataSize = NewOffset;
        NumEntries++;
    }
    // Return offset to allocated object
    return OldOffset;
}

uint32_t CMemoryBuffer::PushString(char const * s) {
    // Add ASCIIZ string to buffer, return offset
    return Push (s, uint32_t(strlen(s))+1);
}

uint32_t CMemoryBuffer::GetLastIndex() {
    // Index of last object pushed (zero-based)
    return NumEntries - 1;
}

void CMemoryBuffer::Align(uint32_t a) {
    // Align next entry to address divisible by a
    uint32_t NewOffset = (DataSize + a - 1) / a * a;
    if (NewOffset > BufferSize) {
        // Allocate more space
        SetSize (NewOffset + 2048);
    }
    // Set DataSize to after alignment space
    DataSize = NewOffset;
}

// Members of class CFileBuffer
CFileBuffer::CFileBuffer() : CMemoryBuffer() {  
    // Default constructor
    FileName = 0;
    OutputFileName = 0;
    FileType = WordSize = Executable = 0;
}

CFileBuffer::CFileBuffer(char const * filename) : CMemoryBuffer() {  
    // Constructor
    FileName = filename;
    FileType = WordSize = 0;
}

void CFileBuffer::Read(int IgnoreError) {                   
    // Read file into buffer
    uint32_t status;                             // Error status

#ifdef _MSC_VER  // Microsoft compiler prefers this:

    int fh;                                    // File handle
    fh = _open(FileName, O_RDONLY | O_BINARY); // Open file in binary mode
    if (fh == -1) {
        // Cannot read file
        if (!IgnoreError) err.submit(2103, FileName); // Error. Input file must be read
        SetSize(0); return;                     // Make empty file buffer
    }
    DataSize = filelength(fh);                 // Get file size
    if (DataSize <= 0) {
        if (!IgnoreError) err.submit(2105, FileName); // Wrong size
        return;}
    SetSize(DataSize + 2048);                  // Allocate buffer, 2k extra
    status = _read(fh, Buf(), DataSize);       // Read from file
    if (status != DataSize) err.submit(2103, FileName);
    status = _close(fh);                       // Close file
    if (status != 0) err.submit(2103, FileName);

#else            // Works with most compilers:

    FILE * fh = fopen(FileName, "rb");
    if (!fh) {
        // Cannot read file
        if (!IgnoreError) err.submit(2103, FileName); // Error. Input file must be read
        SetSize(0); return;                     // Make empty file buffer
    }
    // Find file size
    fseek(fh, 0, SEEK_END);
    long int fsize = ftell(fh);
    if (fsize <= 0 || (unsigned long)fsize >= 0xFFFFFFFF) {
        // File too big or zero size
        err.submit(2105, FileName); fclose(fh); return;
    }
    DataSize = (uint32_t)fsize;
    rewind(fh);
    // Allocate buffer
    SetSize(DataSize + 2048);                 // Allocate buffer, 2k extra
    // Read entire file
    status = (uint32_t)fread(Buf(), 1, DataSize, fh);
    if (status != DataSize) err.submit(2103, FileName);
    status = fclose(fh);
    if (status != 0) err.submit(2103, FileName);

#endif
}

void CFileBuffer::Write() {                  
    // Write buffer to file:
    if (OutputFileName) FileName = OutputFileName;
    // Two alternative ways to write a file:

#ifdef _MSC_VER       // Microsoft compiler prefers this:

    int fh;                                       // File handle
    uint32_t status;                                // Error status
    // Open file in binary mode
    fh = _open(FileName, O_RDWR | O_BINARY | O_CREAT | O_TRUNC, _S_IREAD | _S_IWRITE); 
    // Check if error
    if (fh == -1) {err.submit(2104, FileName);  return;}
    // Write file
    status = _write(fh, Buf(), DataSize);
    // Check if error
    if (status != DataSize) err.submit(2104, FileName);
    // Close file
    status = _close(fh);
    // Check if error
    if (status != 0) err.submit(2104, FileName);

#else                // Works with most compilers:

    // Open file in binary mode
    FILE * ff = fopen(FileName, "wb");
    // Check if error
    if (!ff) {err.submit(2104, FileName);  return;}
    // Write file
    uint32_t n = (uint32_t)fwrite(Buf(), 1, DataSize, ff);
    // Check if error
    if (n != DataSize) err.submit(2104, FileName);
    // Close file
    n = fclose(ff);
    // Check if error
    if (n) {err.submit(2104, FileName);  return;}

#endif
}

int CFileBuffer::GetFileType() {
    // Detect file type
    if (FileType) return FileType;            // File type already known
    if (!DataSize) return 0;                  // No file
    if (!Buf()) return 0;                     // No contents

    uint32_t namelen = FileName ? (uint32_t)strlen(FileName) : 0;

    if (strncmp((char*)Buf(),"!<arch>",7) == 0) {
        // UNIX style library. Contains members of file type COFF, ELF or MACHO
        FileType = FILETYPE_LIBRARY;
    }
    else if (strncmp((char*)Buf(),ELFMAG,4) == 0) {
        // ELF file
        FileType = FILETYPE_ELF;
        Executable = Get<Elf32_Ehdr>(0).e_type != ET_REL;
        switch (Buf()[EI_CLASS]) {
        case ELFCLASS32:
            WordSize = 32; break;
        case ELFCLASS64:
            WordSize = 64; break;
        }
    }
    else if (Get<uint32_t>(0) == MAC_MAGIC_32) {
        // Mach-O 32 little endian
        FileType = FILETYPE_MACHO_LE;
        WordSize = 32;
        Executable = Get<MAC_header_32>(0).filetype != MAC_OBJECT;
    }
    else if (Get<uint32_t>(0) == MAC_MAGIC_64) {
        // Mach-O 64 little endian
        FileType = FILETYPE_MACHO_LE;
        WordSize = 64;
        Executable = Get<MAC_header_64>(0).filetype != MAC_OBJECT;
    }
    else if (Get<uint32_t>(0) == MAC_CIGAM_32) {
        // Mach-O 32 big endian
        FileType = FILETYPE_MACHO_BE;
        WordSize = 32;
    }
    else if (Get<uint32_t>(0) == MAC_CIGAM_64) {
        // Mach-O 64 big endian
        FileType = FILETYPE_MACHO_BE;
        WordSize = 64;
    }
    else if (Get<uint32_t>(0) == MAC_CIGAM_UNIV) {
        // MacIntosh universal binary
        FileType = FILETYPE_MAC_UNIVBIN;
        WordSize = 0;
    }   
    else if (Get<uint32_t>(0) == 0xFFFF0000 || Get<uint32_t>(0) == 0x10000) {
        // Windows subtypes:
        if (Get<uint16_t>(4) == 0) {
            // This type only occurs when attempting to extract a member from an import library
            FileType = IMPORT_LIBRARY_MEMBER;
        }
        else if (Get<uint16_t>(4) == 1) {
            // Whole program optimization intermediate file for MS compiler. Undocumented
            FileType = FILETYPE_MS_WPO;
        }
        else {
            // Other subtypes not known
            FileType = FILETYPE_WIN_UNKNOWN;
        }
        // Get word size
        if (Get<uint16_t>(6) == PE_MACHINE_I386) {
            WordSize = 32;
        }
        else if (Get<uint16_t>(6) == PE_MACHINE_X8664) {
            WordSize = 64;
        }
        else {
            WordSize = 0;
        }
    }
    else if (Get<uint16_t>(0) == PE_MACHINE_I386) {
        // COFF/PE 32
        FileType = FILETYPE_COFF;
        WordSize = 32;
        Executable = (Get<SCOFF_FileHeader>(0).Flags & PE_F_EXEC) != 0;
    }
    else if (Get<uint16_t>(0) == PE_MACHINE_X8664) {
        // COFF64/PE32+
        FileType = FILETYPE_COFF;
        WordSize = 64;
        Executable = (Get<SCOFF_FileHeader>(0).Flags & PE_F_EXEC) != 0;
    }
    else if (Get<uint8_t>(0) == OMF_THEADR) {
        // OMF 16 or 32
        FileType = FILETYPE_OMF;
        // Word size can only be determined by searching through records in file:
        GetOMFWordSize(); // Determine word size
    }
    else if (Get<uint8_t>(0) == OMF_LIBHEAD) {
        // OMF Library 16 or 32
        FileType = FILETYPE_OMFLIBRARY;
    }
    else if ((Get<uint16_t>(0) & 0xFFF9) == 0x5A49) {
        // DOS file or file with DOS stub
        FileType = FILETYPE_DOS;
        WordSize = 16;
        Executable = 1;
        uint32_t Signature = Get<uint32_t>(0x3C);
        if (Signature + 8 < DataSize) {
            if (Get<uint16_t>(Signature) == 0x454E) {
                // Windows 3.x file
                FileType = FILETYPE_WIN3X;
            }
            else if (Get<uint16_t>(Signature) == 0x4550) {
                // COFF file
                uint16_t MachineType = Get<uint16_t>(Signature + 4);
                if (MachineType == PE_MACHINE_I386) {
                    FileType = FILETYPE_COFF;
                    WordSize = 32;
                }
                else if (MachineType == PE_MACHINE_X8664) {
                    FileType = FILETYPE_COFF;
                    WordSize = 64;
                }
            }
        }
    }
    else if (namelen > 4 && stricmp(FileName + namelen - 4, ".com") == 0) {
        // DOS .com file recognized only from its extension
        FileType = FILETYPE_DOS;
        WordSize = 16;  Executable = 1;
    }
    else if (Get<uint16_t>(0) == 0 && namelen > 4 && stricmp(FileName + namelen - 4, ".obj") == 0) {
        // Possibly alias record in COFF library
        FileType = FILETYPE_COFF;
        WordSize = 0;
        Executable = 0;
    }
    else {
        // Unknown file type
        int utype = Get<uint32_t>(0);        
        err.submit(2018, utype, FileName); 
        FileType = 0;
    }
    return FileType;
}


char const * CFileBuffer::GetFileFormatName(int FileType) {
    // Get name of file format type
    return Lookup (FileFormatNames, FileType);
}


void CFileBuffer::SetFileType(int type) {
    // Set file format type
    FileType = type;
}

void CFileBuffer::Reset() {
    // Set all members to zero
    SetSize(0);                          // Deallocate memory buffer
    memset(this, 0, sizeof(*this));
}

char * CFileBuffer::SetFileNameExtension(const char * f) {
    // Set file name extension according to FileType
    static char name[MAXFILENAMELENGTH+8];
    int i;

    if (strlen(f) > MAXFILENAMELENGTH) err.submit(2203, f);
    strncpy(name, f, MAXFILENAMELENGTH);

    // Search for last '.' in file name
    for (i = (int)strlen(name)-1; i > 0; i--) if (name[i] == '.') break;
    if (i < 1) {
        // '.' not found. Append '.' to name
        i = (int)strlen(name); if (i > MAXFILENAMELENGTH-4) i = MAXFILENAMELENGTH-4;
    }
    // Get default extension
    if (cmd.OutputType == FILETYPE_ASM) {
        strcpy(name+i, ".asm"); // Assembly file
    }
    else if (cmd.OutputType == FILETYPE_COFF || cmd.OutputType == FILETYPE_OMF) {
        if ((FileType & (FILETYPE_LIBRARY | FILETYPE_OMFLIBRARY)) || (cmd.LibraryOptions & CMDL_LIBRARY_ADDMEMBER)) {
            strcpy(name+i, ".lib"); // Windows function library
        }
        else {
            strcpy(name+i, ".obj"); // Windows object file
        }
    }
    else { // output type is ELF or MACHO
        if ((FileType & (FILETYPE_LIBRARY | FILETYPE_OMFLIBRARY)) || (cmd.LibraryOptions & CMDL_LIBRARY_ADDMEMBER)) {
            strcpy(name+i, ".a");   // Linux/BSD/Mac function library
        }
        else {
            strcpy(name+i, ".o");   // Linux/BSD/Mac object file
        }
    }
    return name;
}

void CFileBuffer::CheckOutputFileName() {
    // Make output file name or check that requested name is valid
    if (!(cmd.FileOptions & CMDL_FILE_OUTPUT)) return;

    OutputFileName = cmd.OutputFile;
    if (OutputFileName == 0) {
        // Output file name not specified. Make filename
        OutputFileName = cmd.OutputFile = SetFileNameExtension(FileName);
    }
    if (strcmp(FileName,OutputFileName) == 0 && !(cmd.FileOptions & CMDL_FILE_IN_OUT_SAME)) {
        // Input and output files have same name
        err.submit(2005, FileName);
    }
}

void operator >> (CFileBuffer & a, CFileBuffer & b) {
    // Transfer ownership of buffer and other properties from a to b
    b.SetSize(0);                            // De-allocate old buffer from target if it has one
    b.buffer = a.buffer;                     // Transfer buffer
    a.buffer = 0;                            // Remove buffer from source, so that buffer has only one owner

    // Copy properties
    b.DataSize   = a.GetDataSize();          // Size of data, offset to vacant space
    b.BufferSize = a.GetBufferSize();        // Size of allocated buffer
    b.NumEntries = a.GetNumEntries();        // Number of objects pushed
    b.Executable = a.Executable;             // File is executable
    if (a.WordSize) b.WordSize = a.WordSize; // Segment word size (16, 32, 64)
    if (a.FileName) b.FileName = a.FileName; // Name of input file
    if (a.OutputFileName) b.OutputFileName = a.OutputFileName;// Name of output file
    if (a.GetFileType())  b.FileType = a.GetFileType();       // Object file type
    a.SetSize(0);                            // Reset a's properties
}

void CFileBuffer::GetOMFWordSize() {
    // Determine word size for OMF file.
    // There is no simple way to get the word size. Looking for odd-numbered
    // record types is not sufficient. A 32-bit OMF file may use 16-bit SEGDEF 
    // records. We have to look for segments with the 'P' attribute set. And
    // even this is not completely safe, because MASM may generate empty 32-bit
    // segments so we have to look only at segments with nonzero size. 
    // We can still have any mixture of 16- and 32-bit segments, though, so no
    // method is absolutely safe.

    // We have to parse through all records in file buffer
    uint8_t  RecordType;                            // Type of current record
    uint32_t RecordStart;                           // Index to start of current record
    uint32_t RecordEnd;                             // Index to end of current record
    uint32_t RecordLength;                          // Length of current record
    uint32_t Index = 0;                             // Current offset from buffer while reading
    OMF_SAttrib SegAttr;                          // Segment attributed
    uint32_t SegLength;                             // Segment length

    WordSize = 16;                                // WordSize = 16 if no 32 bit records found

    while (Index < GetDataSize()) {
        RecordStart = Index;                       // Record starts here
        RecordType = Get<uint8_t>(Index++);          // Get first byte of record = type
        RecordLength = Get<uint16_t>(Index);         // Next two bytes = length
        Index += 2;
        RecordEnd = RecordStart + RecordLength + 3;// End of record
        if (RecordEnd > GetDataSize()) {
            // Record goes beyond end of file
            err.submit(2301);  break;
        }
        if ((RecordType & 1) && RecordType < OMF_LIBHEAD) { // Odd-numbered type means 32 bit
            WordSize = 32;                                   // ..but this method is not safe
        }
        if ((RecordType & 0xFE) == OMF_SEGDEF) {   // Segment definition record
            SegAttr.b = Get<uint8_t>(Index++);        // Get segment attributes
            if (SegAttr.u.A == 0) {
                // Frame and Offset only included if A = 0
                Index += 2+1;
            }
            SegLength = (RecordType & 1) ? Get<uint32_t>(Index) : Get<uint16_t>(Index); // Segment length

            if (SegAttr.u.P && SegLength) {         // if segment has P attribute and nonzero length
                WordSize = 32;                       // .. then it is a 32-bit segment
            }
        }
        Index = RecordEnd;                         // Point to next record
    }
}


// Class CTextFileBuffer is used for building text files
// Constructor
CTextFileBuffer::CTextFileBuffer() {
    column = 0;
    // Use UNIX linefeeds only if GASM output
    LineType = (cmd.SubType == SUBTYPE_GASM) ? 1 : 0;
}

void CTextFileBuffer::Put(const char * text) {
    // Write text string to buffer
    uint32_t len = (uint32_t)strlen(text);            // Length of text
    Push(text, len);                              // Add to buffer without terminating zero
    column += len;                                // Update column
}

void CTextFileBuffer::Put(const char character) {
    // Write single character to buffer
    Push(&character, 1);                          // Add to buffer
    column ++;                                    // Update column
}

void CTextFileBuffer::NewLine() {
    // Add linefeed
    if (LineType == 0) {
        Push("\r\n", 2);                           // DOS/Windows style linefeed
    }
    else {
        Push("\n", 1);                             // UNIX style linefeed
    }
    column = 0;                                   // Reset column
}

void CTextFileBuffer::Tabulate(uint32_t i) {
    // Insert spaces until column i
    uint32_t j;
    if (i > column) {                             // Only insert spaces if we are not already past i
        for (j = column; j < i; j++) Push(" ", 1); // Insert i - column spaces
        column = i;                                // Update column
    }
}

void CTextFileBuffer::PutDecimal(int32_t x, int IsSigned) {
    // Write decimal number to buffer, unsigned or signed
    char text[16];
    sprintf(text, IsSigned ? "%i" : "%u", x);
    Put(text);
}

void CTextFileBuffer::PutHex(uint8_t x, int MasmForm) {
    // Write hexadecimal 8 bit number to buffer
    // If MasmForm >= 1 then the function will write the number in a
    // way that can be read by the assembler, e.g. 0FFH or 0xFF
    char text[16];
    if (MasmForm && cmd.SubType == SUBTYPE_GASM) {
        // Needs 0x prefix
        sprintf(text, "0x%02X", x);
        Put(text);
        return;
    }
    if (MasmForm && x >= 0xA0) {
        Put("0");                                  // Make sure it doesn't begin with a letter
    }
    sprintf(text, "%02X", x);
    Put(text);
    if (MasmForm) Put("H");
}

void CTextFileBuffer::PutHex(uint16_t x, int MasmForm) {
    // Write hexadecimal 16 bit number to buffer
    // If MasmForm >= 1 then the function will write the number in a
    // way that can be read by the assembler, e.g. 0FFH or 0xFF
    // If MasmForm == 2 then leading zeroes are stripped
    char text[16];
    if (MasmForm && cmd.SubType == SUBTYPE_GASM) {
        // Needs 0x prefix
        sprintf(text, MasmForm==1 ? "0x%04X" : "0x%X", x);
        Put(text);
        return;
    }
    sprintf(text, (MasmForm < 2) ? "%04X" : "%X", x);
    // Check if leading zero needed
    if (MasmForm && text[0] > '9') {
        Put("0");                                  // Leading zero needed
    }
    Put(text);
    if (MasmForm) Put("H");
}

void CTextFileBuffer::PutHex(uint32_t x, int MasmForm) {
    // Write hexadecimal 32 bit number to buffer
    // If MasmForm >= 1 then the function will write the number in a
    // way that can be read by the assembler, e.g. 0FFH or 0xFF
    // If MasmForm == 2 then leading zeroes are stripped
    char text[16];
    if (MasmForm && cmd.SubType == SUBTYPE_GASM) {
        // Needs 0x prefix
        sprintf(text, MasmForm==1 ? "0x%08X" : "0x%X", x);
        Put(text);
        return;
    }

    sprintf(text, (MasmForm < 2) ? "%08X" : "%X", x);
    // Check if leading zero needed
    if (MasmForm && text[0] > '9') {
        Put("0");                                  // Leading zero needed
    }
    Put(text);
    if (MasmForm) Put("H");
}

void CTextFileBuffer::PutHex(uint64_t x, int MasmForm) {
    // Write unsigned hexadecimal 64 bit number to buffer
    // If MasmForm >= 1 then the function will write the number in a
    // way that can be read by the assembler, e.g. 0FFH or 0xFF
    // If MasmForm == 2 then leading zeroes are stripped
    char text[32];
    if (MasmForm < 2) {  // Print all digits
        sprintf(text, "%08X%08X", HighDWord(x), uint32_t(x));
    }
    else { // Skip leading zeroes
        if (HighDWord(x)) {
            sprintf(text, "%X%08X", HighDWord(x), uint32_t(x));
        }
        else {
            sprintf(text, "%X", uint32_t(x));
        }
    }
    if (MasmForm) {
        if (cmd.SubType == SUBTYPE_GASM) {
            // Needs 0x prefix
            Put("0x");
            Put(text);
        }
        else {
            // use 0FFH form
            if (text[0] > '9')  Put("0");           // Leading zero needed
            Put(text);
            Put("H");
        }
    }
    else {
        // write hexadecimal number only
        Put(text);
    }
}

void CTextFileBuffer::PutFloat(float x) {
    // Write floating point number to buffer
    char text[64];
    sprintf(text, "%.7G", x);
    Put(text);
}

void CTextFileBuffer::PutFloat(double x) {
    // Write floating point number to buffer
    char text[64];
    sprintf(text, "%.16G", x);
    Put(text);
}
