/* vim :set ts=4 sw=4 sts=4 et : */
/*
    pev - the PE file analyzer toolkit

    readpe.c - show PE file headers

    Copyright (C) 2013 - 2020 pev authors

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    In addition, as a special exception, the copyright holders give
    permission to link the code of portions of this program with the
    OpenSSL library under certain conditions as described in each
    individual source file, and distribute linked combinations
    including the two.

    You must obey the GNU General Public License in all respects
    for all of the code used other than OpenSSL.  If you modify
    file(s) with this exception, you may extend this exception to your
    version of the file(s), but you are not obligated to do so.  If you
    do not wish to do so, delete this exception statement from your
    version.  If you delete this exception statement from all source
    files in the program, then also delete it here.
*/

#include "common.h"
#include "output.h"
#include <stdbool.h>
#include <time.h>

#define PROGRAM "readpe"

typedef struct {
    bool all;
    bool dos;
    bool coff;
    bool opt;
    bool dirs;
    bool imports;
    bool exports;
    bool all_headers;
    bool all_sections;
} options_t;

static void usage(void)
{
    static char formats[255];
    output_available_formats(formats, sizeof(formats), '|');
    printf("Usage: %s OPTIONS FILE\n"
        "Show PE file headers\n"
        "\nExample: %s --header optional winzip.exe\n"
        "\nOptions:\n"
        " -A, --all                             Full output (default).\n"
        " -H, --all-headers                     Show all PE headers.\n"
        " -S, --all-sections                    Show PE section headers.\n"
        " -f, --format <%s> Change output format (default: text).\n"
        " -d, --dirs                            Show data directories.\n"
        " -h, --header <dos|coff|optional>      Show specific header. It can be used multiple times.\n"
        " -i, --imports                         Show imported functions.\n"
        " -e, --exports                         Show exported functions.\n"
        " -V, --version                         Show version.\n"
        " --help                                Show this help.\n",
        PROGRAM, PROGRAM, formats);
}

static void parse_headers(options_t *options, const char *optarg)
{
    if (!strcmp(optarg, "dos"))
        options->dos = true;
    else if (!strcmp(optarg, "coff"))
        options->coff = true;
    else if (!strcmp(optarg, "optional"))
        options->opt = true;
    else
        EXIT_ERROR("invalid header option");
}

static void free_options(options_t *options)
{
    // FIX: Don't need to test for NULL pointer.
    //if (options == NULL)
    //  return;

    free(options);
}

static options_t *parse_options(int argc, char *argv[])
{
    options_t *options = calloc_s(1, sizeof(options_t));

    /* Parameters for getopt_long() function */
    static const char short_options[] = "AHSh:dief:V";

    static const struct option long_options[] = {
        { "help",             no_argument,       NULL,  1  },
        { "all",              no_argument,       NULL, 'A' },
        { "all-headers",      no_argument,       NULL, 'H' },
        { "all-sections",     no_argument,       NULL, 'S' },
        { "header",           required_argument, NULL, 'h' },
        { "imports",          no_argument,       NULL, 'i' },
        { "exports",          no_argument,       NULL, 'e' },
        { "dirs",             no_argument,       NULL, 'd' },
        { "format",           required_argument, NULL, 'f' },
        { "version",          no_argument,       NULL, 'V' },
        {  NULL,              0,                 NULL,  0  }
    };

    options->all = true;

    int c, ind;

    while ((c = getopt_long(argc, argv, short_options, long_options, &ind)))
    {
        if (c < 0)
            break;

        switch (c)
        {
            case 1: // --help option
                usage();
                exit(EXIT_SUCCESS);
            case 'A':
                options->all = true;
                break;
            case 'H':
                options->all = false;
                options->all_headers = true;
                break;
            case 'd':
                options->all = false;
                options->dirs = true;
                break;
            case 'S':
                options->all = false;
                options->all_sections = true;
                break;
            case 'V':
                printf("%s %s\n%s\n", PROGRAM, TOOLKIT, COPY);
                exit(EXIT_SUCCESS);
            case 'h':
                options->all = false;
                parse_headers(options, optarg);
                break;
            case 'i':
                options->all = false;
                options->imports = true;
                break;
            case 'e':
                options->all = false;
                options->exports = true;
                break;
            case 'f':
                if (output_set_format_by_name(optarg) < 0)
                    EXIT_ERROR("invalid format option");
                break;
            default:
                fprintf(stderr, "%s: try '--help' for more information\n", PROGRAM);
                exit(EXIT_FAILURE);
        }
    }

    return options;
}

static void print_sections(pe_ctx_t *ctx)
{
#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
    static const char * const flags_name[] = {
        "contains executable code",
        "contains initialized data",
        "contains uninitialized data",
        "contains data referenced through the GP",
        "contains extended relocations",
        "can be discarded as needed",
        "cannot be cached",
        "is not pageable",
        "can be shared in memory",
        "is executable",
        "is readable",
        "is writable"
    };
    // valid flags only for executables referenced in pecoffv8
    static const unsigned int valid_flags[] = {
        IMAGE_SCN_CNT_CODE,
        IMAGE_SCN_CNT_INITIALIZED_DATA,
        IMAGE_SCN_CNT_UNINITIALIZED_DATA,
        IMAGE_SCN_GPREL,
        IMAGE_SCN_LNK_NRELOC_OVFL,
        IMAGE_SCN_MEM_DISCARDABLE,
        IMAGE_SCN_MEM_NOT_CACHED,
        IMAGE_SCN_MEM_NOT_PAGED,
        IMAGE_SCN_MEM_SHARED,
        IMAGE_SCN_MEM_EXECUTE,
        IMAGE_SCN_MEM_READ,
        IMAGE_SCN_MEM_WRITE
    };
    static const size_t max_flags = LIBPE_SIZEOF_ARRAY(valid_flags);
#endif

    output_open_scope("Sections", OUTPUT_SCOPE_TYPE_ARRAY);

    const uint32_t num_sections = pe_sections_count(ctx);
    if (num_sections == 0 || num_sections > MAX_SECTIONS)
        return;

    IMAGE_SECTION_HEADER **sections = pe_sections(ctx);
    if (sections == NULL)
        return;

    static char s[MAX_MSG];
    static char section_name_buffer[SECTION_NAME_SIZE+1];

    for (uint32_t i=0; i < num_sections; i++)
    {
        output_open_scope("Section", OUTPUT_SCOPE_TYPE_OBJECT);

        const char *section_name = pe_section_name(ctx, sections[i], section_name_buffer, sizeof(section_name_buffer));
        output("Name", section_name);

        snprintf(s, MAX_MSG, "%#x (%" PRIu32 " bytes)", sections[i]->Misc.VirtualSize,
            sections[i]->Misc.VirtualSize);
        output("Virtual Size", s);

        snprintf(s, MAX_MSG, "%#x", sections[i]->VirtualAddress);
        output("Virtual Address", s);

        snprintf(s, MAX_MSG, "%#x (%" PRIu32 " bytes)", sections[i]->SizeOfRawData,
            sections[i]->SizeOfRawData);
        output("Size Of Raw Data", s);

        snprintf(s, MAX_MSG, "%#x", sections[i]->PointerToRawData);
        output("Pointer To Raw Data", s);

        snprintf(s, MAX_MSG, "%" PRIu16, sections[i]->NumberOfRelocations);
        output("Number Of Relocations", s);

        snprintf(s, MAX_MSG, "%#x", sections[i]->Characteristics);
        output("Characteristics", s);

        output_open_scope("Characteristic Names", OUTPUT_SCOPE_TYPE_ARRAY);

#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
        for (size_t j=0; j < max_flags; j++) {
            if (sections[i]->Characteristics & valid_flags[j]) {
                snprintf(s, MAX_MSG, "%s", flags_name[j]);
                output(NULL, s);
            }
        }
#else
        if (pe_use_rom_section_characteristic(ctx)) {
            for (unsigned int flag = 1; flag != 0; flag <<= 1) {
                if (sections[i]->Characteristics & flag) {
                    const char *characteristic_name = pe_rom_section_characteristic_name(flag);
                    char formatted_characteristic_name[32];
                    if (characteristic_name == NULL) {
                        snprintf(formatted_characteristic_name, sizeof(formatted_characteristic_name)-1, "UNKNOWN[%#x]", flag);
                        characteristic_name = formatted_characteristic_name;
                    }
                    output(NULL, characteristic_name);
                }
            }
        } else {
            for (unsigned int flag = 1; flag != 0; flag <<= 1) {
                if (flag & 0x00F00000)
                    continue;
                if (sections[i]->Characteristics & flag) {
                    const char *characteristic_name = NULL;
                    char formatted_characteristic_name[32];
                    if (pe_coff(ctx)->Machine == IMAGE_FILE_MACHINE_M68K)
                        characteristic_name = pe_m68k_section_characteristic_name(flag);
                    if (characteristic_name == NULL)
                        characteristic_name = pe_section_characteristic_name(flag);
                    if (characteristic_name == NULL) {
                        snprintf(formatted_characteristic_name, sizeof(formatted_characteristic_name)-1, "UNKNOWN[%#x]", flag);
                        characteristic_name = formatted_characteristic_name;
                    }
                    output(NULL, characteristic_name);
                }
            }
            if (sections[i]->Characteristics & 0x00F00000) {
                unsigned int flag = sections[i]->Characteristics & 0x00F00000;
                const char *characteristic_name = pe_section_characteristic_name(flag);
                char formatted_characteristic_name[32];
                if (characteristic_name == NULL) {
                    snprintf(formatted_characteristic_name, sizeof(formatted_characteristic_name)-1, "UNKNOWN[%#x]", flag);
                    characteristic_name = formatted_characteristic_name;
                }
                output(NULL, characteristic_name);
            }
        }
#endif

        output_close_scope(); // Characteristic Names

        output_close_scope(); // Section
    }

    output_close_scope(); // Sections
}

static void print_directories(pe_ctx_t *ctx)
{
#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
    typedef struct {
        ImageDirectoryEntry entry;
        const char * const name;
    } ImageDirectoryEntryName;
    static const ImageDirectoryEntryName directoryEntryNames[] = {
        { IMAGE_DIRECTORY_ENTRY_EXPORT,         "Export Table"              }, // "Export directory",
        { IMAGE_DIRECTORY_ENTRY_IMPORT,         "Import Table"              }, // "Import directory",
        { IMAGE_DIRECTORY_ENTRY_RESOURCE,       "Resource Table"            }, // "Resource directory",
        { IMAGE_DIRECTORY_ENTRY_EXCEPTION,      "Exception Table"           }, // "Exception directory",
        { IMAGE_DIRECTORY_ENTRY_SECURITY,       "Certificate Table"         }, // "Security directory",
        { IMAGE_DIRECTORY_ENTRY_BASERELOC,      "Base Relocation Table"     }, // "Base relocation table",
        { IMAGE_DIRECTORY_ENTRY_DEBUG,          "Debug"                     }, // "Debug directory",
        { IMAGE_DIRECTORY_ENTRY_ARCHITECTURE,   "Architecture"              }, // "Architecture-specific data",
        { IMAGE_DIRECTORY_ENTRY_GLOBALPTR,      "Global Ptr"                }, // "Global pointer",
        { IMAGE_DIRECTORY_ENTRY_TLS,            "Thread Local Storage (TLS)"}, // "Thread local storage (TLS) directory",
        { IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG,    "Load Config Table"         }, // "Load configuration directory",
        { IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT,   "Bound Import"              }, // "Bound import directory",
        { IMAGE_DIRECTORY_ENTRY_IAT,            "Import Address Table (IAT)"}, // "Import address table (IAT)",
        { IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT,   "Delay Import Descriptor"   }, // "Delay import table",
        { IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, "CLR Runtime Header"        }, // "COM descriptor table"
        { IMAGE_DIRECTORY_RESERVED,             ""                          }  // "Reserved"
    };
    //static const size_t max_directory_entry = LIBPE_SIZEOF_ARRAY(names);
#endif
    output_open_scope("Data directories", OUTPUT_SCOPE_TYPE_ARRAY);

    const uint32_t num_directories = pe_directories_count(ctx);
    if (num_directories == 0 || num_directories > MAX_DIRECTORIES)
        return;

    IMAGE_DATA_DIRECTORY **directories = pe_directories(ctx);
    if (directories == NULL)
        return;

    static char s[MAX_MSG];

    for (uint32_t i=0; i < num_directories; i++) {
        if (directories[i]->Size) {
            output_open_scope("Directory", OUTPUT_SCOPE_TYPE_OBJECT);
            snprintf(s, MAX_MSG, "%#x (%" PRIu32 " bytes)",
                    directories[i]->VirtualAddress,
                    directories[i]->Size);
#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            output(directoryEntryNames[i].name, s);
#else
            output(pe_directory_name(i), s);
#endif
            output_close_scope(); // Directory
        }
    }

    output_close_scope(); // Data directories
}

static void print_optional_header(pe_ctx_t *ctx, IMAGE_OPTIONAL_HEADER *header)
{
#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
    typedef struct {
        WindowsSubsystem subsystem;
        const char * const name;
    } WindowsSubsystemName;
    static const WindowsSubsystemName subsystemNames[] = {
        { IMAGE_SUBSYSTEM_UNKNOWN,                  "Unknown subsystem"     },
        { IMAGE_SUBSYSTEM_NATIVE,                   "System native"         },
        { IMAGE_SUBSYSTEM_WINDOWS_GUI,              "Windows GUI"           },
        { IMAGE_SUBSYSTEM_WINDOWS_CUI,              "Windows CLI"           },
        { IMAGE_SUBSYSTEM_UNKNOWN,                  "Unknown subsystem"     },
        { IMAGE_SUBSYSTEM_UNKNOWN,                  "Unknown subsystem"     },
        { IMAGE_SUBSYSTEM_UNKNOWN,                  "Unknown subsystem"     },
        { IMAGE_SUBSYSTEM_POSIX_CUI,                "Posix CLI"             },
        { IMAGE_SUBSYSTEM_WINDOWS_CE_GUI,           "Windows CE GUI"        },
        { IMAGE_SUBSYSTEM_EFI_APPLICATION,          "EFI application"       },
        { IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER,  "EFI driver with boot"  },
        { IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER,       "EFI run-time driver"   },
        { IMAGE_SUBSYSTEM_EFI_ROM,                  "EFI ROM"               },
        { IMAGE_SUBSYSTEM_XBOX,                     "XBOX"                  },
        { IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION, "Boot application"      }
    };
    static const size_t max_subsystem = LIBPE_SIZEOF_ARRAY(subsystemNames);
#endif

    if (!header)
        return;

    static char s[MAX_MSG];

    output_open_scope("Optional/Image header", OUTPUT_SCOPE_TYPE_OBJECT);

    switch (header->type)
    {
        case MAGIC_ROM:
        {
            snprintf(s, MAX_MSG, "%#x (%s)", header->_rom->Magic, "ROM");
            output("Magic number", s);

            snprintf(s, MAX_MSG, "%" PRIu8, header->_rom->MajorLinkerVersion);
            output("Linker major version", s);

            snprintf(s, MAX_MSG, "%" PRIu8, header->_rom->MinorLinkerVersion);
            output("Linker minor version", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->SizeOfCode);
            output("Size of .text section", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->SizeOfInitializedData);
            output("Size of .data section", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->SizeOfUninitializedData);
            output("Size of .bss section", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->AddressOfEntryPoint);
            output("Entrypoint", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->BaseOfCode);
            output("Address of .text section", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->BaseOfData);
            output("Address of .data section", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->BaseOfBss);
            output("Address of .bss section", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->GprMask);
            output("GprMask", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->CprMask[0]);
            output("CprMask[0]", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->CprMask[1]);
            output("CprMask[1]", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->CprMask[2]);
            output("CprMask[2]", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->CprMask[3]);
            output("CprMask[3]", s);

            snprintf(s, MAX_MSG, "%#x", header->_rom->GpValue);
            output("GpValue", s);
            break;
        }
        case MAGIC_PE32_0:
        case MAGIC_PE32:
        {
            snprintf(s, MAX_MSG, "%#x (%s)", header->_32->Magic, header->type == MAGIC_PE32_0 ? "PE32 ZERO" : "PE32");
            output("Magic number", s);

            snprintf(s, MAX_MSG, "%" PRIu8, header->_32->MajorLinkerVersion);
            output("Linker major version", s);

            snprintf(s, MAX_MSG, "%" PRIu8, header->_32->MinorLinkerVersion);
            output("Linker minor version", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->SizeOfCode);
            output("Size of .text section", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->SizeOfInitializedData);
            output("Size of .data section", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->SizeOfUninitializedData);
            output("Size of .bss section", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->AddressOfEntryPoint);
            output("Entrypoint", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->BaseOfCode);
            output("Address of .text section", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->BaseOfData);
            output("Address of .data section", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->ImageBase);
            output("ImageBase", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->SectionAlignment);
            output("Alignment of sections", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->FileAlignment);
            output("Alignment factor", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_32->MajorOperatingSystemVersion);
            output("Major version of required OS", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_32->MinorOperatingSystemVersion);
            output("Minor version of required OS", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_32->MajorImageVersion);
            output("Major version of image", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_32->MinorImageVersion);
            output("Minor version of image", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_32->MajorSubsystemVersion);
            output("Major version of subsystem", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_32->MinorSubsystemVersion);
            output("Minor version of subsystem", s);

#ifndef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            snprintf(s, MAX_MSG, "Win32 version value:             %#x", header->_32->Win32VersionValue);
            output_open_scope(s, OUTPUT_SCOPE_TYPE_OBJECT);

            if (header->_32->Win32VersionValue == 0)
                strcpy(s, "(default)");
            else
                snprintf(s, MAX_MSG, "%u", header->_32->Win32VersionValue & 0xff);
            output("Overwrite OS major version", s);

            if (header->_32->Win32VersionValue == 0)
                strcpy(s, "(default)");
            else
                snprintf(s, MAX_MSG, "%u", (header->_32->Win32VersionValue >> 8) & 0xff);
            output("Overwrite OS minor version", s);

            if (header->_32->Win32VersionValue == 0)
                strcpy(s, "(default)");
            else
                snprintf(s, MAX_MSG, "%u", (header->_32->Win32VersionValue >> 16) & 0x3fff);
            output("Overwrite OS build number", s);

            if (header->_32->Win32VersionValue == 0)
                strcpy(s, "(default)");
            else {
                uint8_t platform_id = header->_32->Win32VersionValue >> 30;
                static const char *const win32_version_value_platform_id[4] = { "NT", "CE", "Win32s", "Win9x" };
                snprintf(s, MAX_MSG, "%u (%s)", platform_id, win32_version_value_platform_id[platform_id]);
            }
            output("Overwrite OS platform id", s);

            output_close_scope();
#endif

            snprintf(s, MAX_MSG, "%#x", header->_32->SizeOfImage);
            output("Size of image", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->SizeOfHeaders);
            output("Size of headers", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->CheckSum);
            output("Checksum", s);

            const uint16_t subsystem = header->_32->Subsystem;
#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            const char *subsystem_name = "Unknown";
            for (size_t i=0; i < max_subsystem; i++) {
                if (subsystem == subsystemNames[i].subsystem)
                    subsystem_name = subsystemNames[i].name;
            }
#else
            const char *subsystem_name = pe_windows_subsystem_name(subsystem);
            if (subsystem_name == NULL)
                subsystem_name = "Unknown";
#endif
            snprintf(s, MAX_MSG, "%#x (%s)", subsystem, subsystem_name);
            output("Subsystem required", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->DllCharacteristics);
            output("DLL characteristics", s);

#ifndef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            output_open_scope("DLL characteristics names", OUTPUT_SCOPE_TYPE_ARRAY);

            for (uint16_t i=0, flag=0x0001; i < 16; i++, flag <<= 1) {
                if (header->_32->DllCharacteristics & flag) {
                    const char *characteristic_name = NULL;
                    char formatted_characteristic_name[32];
                    if (pe_coff(ctx)->Characteristics & IMAGE_FILE_DLL)
                        characteristic_name = pe_dll_image_dllcharacteristic_name(flag);
                    if (characteristic_name == NULL)
                        characteristic_name = pe_image_dllcharacteristic_name(flag);
                    if (characteristic_name == NULL) {
                        snprintf(formatted_characteristic_name, sizeof(formatted_characteristic_name)-1, "UNKNOWN[%#x]", flag);
                        characteristic_name = formatted_characteristic_name;
                    }
                    output(NULL, characteristic_name);
                }
            }

            output_close_scope(); // DLL characteristics names
#endif

            snprintf(s, MAX_MSG, "%#x", header->_32->SizeOfStackReserve);
            output("Size of stack to reserve", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->SizeOfStackCommit);
            output("Size of stack to commit", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->SizeOfHeapReserve);
            output("Size of heap space to reserve", s);

            snprintf(s, MAX_MSG, "%#x", header->_32->SizeOfHeapCommit);
            output("Size of heap space to commit", s);

#ifndef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            snprintf(s, MAX_MSG, "%#x", header->_32->LoaderFlags);
            output("Loader Flags", s);

            output_open_scope("Loader Flags names", OUTPUT_SCOPE_TYPE_ARRAY);

            for (uint32_t i=0, flag=0x00000001; i < 32; i++, flag <<= 1) {
                if (header->_32->LoaderFlags & flag) {
                    const char *flag_name = NULL;
                    char formatted_flag_name[32];
                    if (pe_coff(ctx)->Characteristics & IMAGE_FILE_DLL)
                        flag_name = pe_dll_image_loader_flags_name(flag);
                    if (flag_name == NULL)
                        flag_name = pe_image_loader_flags_name(flag);
                    if (flag_name == NULL) {
                        snprintf(formatted_flag_name, sizeof(formatted_flag_name)-1, "UNKNOWN[%#x]", flag);
                        flag_name = formatted_flag_name;
                    }
                    output(NULL, flag_name);
                }
            }

            output_close_scope(); // Loader Flags names
#endif

            break;
        }
        case MAGIC_PE64:
        {
            snprintf(s, MAX_MSG, "%#x (%s)", header->_64->Magic, "PE32+");
            output("Magic number", s);

            snprintf(s, MAX_MSG, "%" PRIu8, header->_64->MajorLinkerVersion);
            output("Linker major version", s);

            snprintf(s, MAX_MSG, "%" PRIu8, header->_64->MinorLinkerVersion);
            output("Linker minor version", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->SizeOfCode);
            output("Size of .text section", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->SizeOfInitializedData);
            output("Size of .data section", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->SizeOfUninitializedData);
            output("Size of .bss section", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->AddressOfEntryPoint);
            output("Entrypoint", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->BaseOfCode);
            output("Address of .text section", s);

            snprintf(s, MAX_MSG, "%#"PRIx64, header->_64->ImageBase);
            output("ImageBase", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->SectionAlignment);
            output("Alignment of sections", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->FileAlignment);
            output("Alignment factor", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_64->MajorOperatingSystemVersion);
            output("Major version of required OS", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_64->MinorOperatingSystemVersion);
            output("Minor version of required OS", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_64->MajorImageVersion);
            output("Major version of image", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_64->MinorImageVersion);
            output("Minor version of image", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_64->MajorSubsystemVersion);
            output("Major version of subsystem", s);

            snprintf(s, MAX_MSG, "%" PRIu16, header->_64->MinorSubsystemVersion);
            output("Minor version of subsystem", s);

#ifndef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            snprintf(s, MAX_MSG, "Win32 version value:             %#x", header->_64->Win32VersionValue);
            output_open_scope(s, OUTPUT_SCOPE_TYPE_OBJECT);

            if (header->_64->Win32VersionValue == 0)
                strcpy(s, "(default)");
            else
                snprintf(s, MAX_MSG, "%u", header->_64->Win32VersionValue & 0xff);
            output("Overwrite OS major version", s);

            if (header->_64->Win32VersionValue == 0)
                strcpy(s, "(default)");
            else
                snprintf(s, MAX_MSG, "%u", (header->_64->Win32VersionValue >> 8) & 0xff);
            output("Overwrite OS minor version", s);

            if (header->_64->Win32VersionValue == 0)
                strcpy(s, "(default)");
            else
                snprintf(s, MAX_MSG, "%u", (header->_64->Win32VersionValue >> 16) & 0x3fff);
            output("Overwrite OS build number", s);

            if (header->_64->Win32VersionValue == 0)
                strcpy(s, "(default)");
            else {
                uint8_t platform_id = header->_64->Win32VersionValue >> 30;
                static const char *const win32_version_value_platform_id[4] = { "NT", "CE", "Win32s", "Win9x" };
                snprintf(s, MAX_MSG, "%u (%s)", platform_id, win32_version_value_platform_id[platform_id]);
            }
            output("Overwrite OS platform id", s);

            output_close_scope();
#endif

            snprintf(s, MAX_MSG, "%#x", header->_64->SizeOfImage);
            output("Size of image", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->SizeOfHeaders);
            output("Size of headers", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->CheckSum);
            output("Checksum", s);

            const uint16_t subsystem = header->_64->Subsystem;
#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            const char *subsystem_name = "Unknown";
            for (size_t i=0; i < max_subsystem; i++) {
                if (subsystem == subsystemNames[i].subsystem)
                    subsystem_name = subsystemNames[i].name;
            }
#else
            const char *subsystem_name = pe_windows_subsystem_name(subsystem);
            if (subsystem_name == NULL)
                subsystem_name = "Unknown";
#endif
            snprintf(s, MAX_MSG, "%#x (%s)", subsystem, subsystem_name);
            output("Subsystem required", s);

            snprintf(s, MAX_MSG, "%#x", header->_64->DllCharacteristics);
            output("DLL characteristics", s);

#ifndef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            output_open_scope("DLL characteristics names", OUTPUT_SCOPE_TYPE_ARRAY);

            for (uint16_t i=0, flag=0x0001; i < 16; i++, flag <<= 1) {
                if (header->_64->DllCharacteristics & flag) {
                    const char *characteristic_name = NULL;
                    char formatted_characteristic_name[32];
                    if (pe_coff(ctx)->Characteristics & IMAGE_FILE_DLL)
                        characteristic_name = pe_dll_image_dllcharacteristic_name(flag);
                    if (characteristic_name == NULL)
                        characteristic_name = pe_image_dllcharacteristic_name(flag);
                    if (characteristic_name == NULL) {
                        snprintf(formatted_characteristic_name, sizeof(formatted_characteristic_name)-1, "UNKNOWN[%#x]", flag);
                        characteristic_name = formatted_characteristic_name;
                    }
                    output(NULL, characteristic_name);
                }
            }

            output_close_scope(); // DLL characteristics names
#endif

            snprintf(s, MAX_MSG, "%#"PRIx64, header->_64->SizeOfStackReserve);
            output("Size of stack to reserve", s);

            snprintf(s, MAX_MSG, "%#"PRIx64, header->_64->SizeOfStackCommit);
            output("Size of stack to commit", s);

            snprintf(s, MAX_MSG, "%#"PRIx64, header->_64->SizeOfHeapReserve);
            output("Size of heap space to reserve", s);

            snprintf(s, MAX_MSG, "%#"PRIx64, header->_64->SizeOfHeapCommit);
            output("Size of heap space to commit", s);

#ifndef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            snprintf(s, MAX_MSG, "%#x", header->_64->LoaderFlags);
            output("Loader Flags", s);

            output_open_scope("Loader Flags names", OUTPUT_SCOPE_TYPE_ARRAY);

            for (uint32_t i=0, flag=0x00000001; i < 32; i++, flag <<= 1) {
                if (header->_64->LoaderFlags & flag) {
                    const char *flag_name = NULL;
                    char formatted_flag_name[32];
                    if (pe_coff(ctx)->Characteristics & IMAGE_FILE_DLL)
                        flag_name = pe_dll_image_loader_flags_name(flag);
                    if (flag_name == NULL)
                        flag_name = pe_image_loader_flags_name(flag);
                    if (flag_name == NULL) {
                        snprintf(formatted_flag_name, sizeof(formatted_flag_name)-1, "UNKNOWN[%#x]", flag);
                        flag_name = formatted_flag_name;
                    }
                    output(NULL, flag_name);
                }
            }

            output_close_scope(); // Loader Flags names
#endif

            break;
        }
    }

    output_close_scope(); // Optional/Image heade
}

static void print_coff_header(pe_ctx_t *ctx, IMAGE_COFF_HEADER *header)
{
#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
    typedef struct {
        ImageCharacteristics characteristic;
        const char * const name;
    } ImageCharacteristicsName;
    static const ImageCharacteristicsName characteristicsTable[] = {
        { IMAGE_FILE_RELOCS_STRIPPED,           "base relocations stripped"                 },
        { IMAGE_FILE_EXECUTABLE_IMAGE,          "executable image"                          },
        { IMAGE_FILE_LINE_NUMS_STRIPPED,        "line numbers removed (deprecated)"         },
        { IMAGE_FILE_LOCAL_SYMS_STRIPPED,       "local symbols removed (deprecated)"        },
        { IMAGE_FILE_AGGRESSIVE_WS_TRIM,        "aggressively trim (deprecated for Windows 2000 and later)" },
        { IMAGE_FILE_LARGE_ADDRESS_AWARE,       "can handle more than 2 GB addresses"       },
        { IMAGE_FILE_16BIT_MACHINE,         ""                                          },
        { IMAGE_FILE_BYTES_REVERSED_LO,         "little-endian (deprecated)"                },
        { IMAGE_FILE_32BIT_MACHINE,             "32-bit machine"                            },
        { IMAGE_FILE_DEBUG_STRIPPED,            "debugging information removed"             },
        { IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP,   "copy to swap if it's on removable media"   },
        { IMAGE_FILE_NET_RUN_FROM_SWAP,         "copy to swap if it's on network media"     },
        { IMAGE_FILE_SYSTEM,                    "system file"                               },
        { IMAGE_FILE_DLL,                       "DLL image"                                 },
        { IMAGE_FILE_UP_SYSTEM_ONLY,            "uniprocessor machine"                      },
        { IMAGE_FILE_BYTES_REVERSED_HI,         "big-endian (deprecated)"                   }
    };

    typedef struct {
        MachineType type;
        const char * const name;
    } MachineTypeName;
    static const MachineTypeName machineTypeTable[] = {
        { IMAGE_FILE_MACHINE_UNKNOWN,   "Any machine type"                  },
        { IMAGE_FILE_MACHINE_AM33,      "Matsushita AM33"                   },
        { IMAGE_FILE_MACHINE_AMD64,     "x86-64 (64-bits)"                  },
        { IMAGE_FILE_MACHINE_ARM,       "ARM little endian"                 },
        { IMAGE_FILE_MACHINE_ARMV7,     "ARMv7 (or higher) Thumb mode only" },
        { IMAGE_FILE_MACHINE_CEE,       "clr pure MSIL (object only)"       },
        { IMAGE_FILE_MACHINE_EBC,       "EFI byte code"                     },
        { IMAGE_FILE_MACHINE_I386,      "Intel 386 and compatible (32-bits)"},
        { IMAGE_FILE_MACHINE_IA64,      "Intel Itanium"                     },
        { IMAGE_FILE_MACHINE_M32R,      "Mitsubishi M32R little endian"     },
        { IMAGE_FILE_MACHINE_MIPS16,    "MIPS16"                            },
        { IMAGE_FILE_MACHINE_MIPSFPU,   "MIPS with FPU"                     },
        { IMAGE_FILE_MACHINE_MIPSFPU16, "MIPS16 with FPU"                   },
        { IMAGE_FILE_MACHINE_POWERPC,   "Power PC little endian"            },
        { IMAGE_FILE_MACHINE_POWERPCFP, "Power PC with floating point support" },
        { IMAGE_FILE_MACHINE_R4000,     "MIPS little endian"                },
        { IMAGE_FILE_MACHINE_SH3,       "Hitachi SH3"                       },
        { IMAGE_FILE_MACHINE_SH3DSP,    "Hitachi SH3 DSP"                   },
        { IMAGE_FILE_MACHINE_SH4,       "Hitachi SH4"                       },
        { IMAGE_FILE_MACHINE_SH5,       "Hitachi SH5"                       },
        { IMAGE_FILE_MACHINE_THUMB,     "ARM or Thumb (\"interworking\")"   },
        { IMAGE_FILE_MACHINE_WCEMIPSV2, "MIPS little-endian WCE v2"         }
    };
    static const size_t max_machine_type = LIBPE_SIZEOF_ARRAY(machineTypeTable);
#endif

    output_open_scope("COFF/File header", OUTPUT_SCOPE_TYPE_OBJECT);

#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
    const char *machine = "Unknown machine type";
    for (size_t i=0; i < max_machine_type; i++) {
        if (header->Machine == machineTypeTable[i].type)
            machine = machineTypeTable[i].name;
    }
#else
    const char *machine = pe_machine_type_name(header->Machine);
    if (machine == NULL)
        machine = "Unknown machine type";
#endif

    static char s[MAX_MSG];

    snprintf(s, MAX_MSG, "%#x %s", header->Machine, machine);
    output("Machine", s);

    snprintf(s, MAX_MSG, "%" PRIu16, header->NumberOfSections);
    output("Number of sections", s);

    if (pe_is_repro(ctx)) {
        snprintf(s, MAX_MSG, "0x%" PRIx32 " (reproducible hash)", header->TimeDateStamp);
    } else {
        char timestr[40] = "invalid";
        const time_t timestamp = header->TimeDateStamp;
        struct tm *t = gmtime(&timestamp);
        if (t)
            strftime(timestr, sizeof(timestr), "%a, %d %b %Y %H:%M:%S UTC", t);
        snprintf(s, MAX_MSG, "%" PRIu32 " (%s)", header->TimeDateStamp, timestr);
    }
    output("Date/time stamp", s);

    snprintf(s, MAX_MSG, "%#x", header->PointerToSymbolTable);
    output("Symbol Table offset", s);

    snprintf(s, MAX_MSG, "%" PRIu32, header->NumberOfSymbols);
    output("Number of symbols", s);

    snprintf(s, MAX_MSG, "%#x", header->SizeOfOptionalHeader);
    output("Size of optional header", s);

    snprintf(s, MAX_MSG, "%#x", header->Characteristics);
    output("Characteristics", s);

    output_open_scope("Characteristics names", OUTPUT_SCOPE_TYPE_ARRAY);

    for (uint16_t i=0, flag=0x0001; i < 16; i++, flag <<= 1) {
        if (header->Characteristics & flag) {
#ifdef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
            output(NULL, characteristicsTable[i].name);
#else
            const char *characteristic_name = pe_image_characteristic_name(flag);
            char formatted_characteristic_name[32];
            if (characteristic_name == NULL) {
                snprintf(formatted_characteristic_name, sizeof(formatted_characteristic_name)-1, "UNKNOWN[%#x]", flag);
                characteristic_name = formatted_characteristic_name;
            }
            output(NULL, characteristic_name);
#endif
        }
    }

    output_close_scope(); // Characteristics names

    output_close_scope(); // COFF/File header
}

static void print_dos_header(IMAGE_DOS_HEADER *header)
{
    char s[MAX_MSG];

    output_open_scope("DOS Header", OUTPUT_SCOPE_TYPE_OBJECT);

    snprintf(s, MAX_MSG, "%#x (MZ)", header->e_magic);
    output("Magic number", s);

    snprintf(s, MAX_MSG, "%" PRIu16, header->e_cblp);
    output("Bytes in last page", s);

    snprintf(s, MAX_MSG, "%" PRIu16, header->e_cp);
    output("Pages in file", s);

    snprintf(s, MAX_MSG, "%" PRIu16, header->e_crlc);
    output("Relocations", s);

    snprintf(s, MAX_MSG, "%" PRIu16, header->e_cparhdr);
    output("Size of header in paragraphs", s);

    snprintf(s, MAX_MSG, "%" PRIu16, header->e_minalloc);
    output("Minimum extra paragraphs", s);

    snprintf(s, MAX_MSG, "%" PRIu16, header->e_maxalloc);
    output("Maximum extra paragraphs", s);

    snprintf(s, MAX_MSG, "%#x", header->e_ss);
    output("Initial (relative) SS value", s);

    snprintf(s, MAX_MSG, "%#x", header->e_sp);
    output("Initial SP value", s);

    snprintf(s, MAX_MSG, "%#x", header->e_ip);
    output("Initial IP value", s);

    snprintf(s, MAX_MSG, "%#x", header->e_cs);
    output("Initial (relative) CS value", s);

    snprintf(s, MAX_MSG, "%#x", header->e_lfarlc);
    output("Address of relocation table", s);

    snprintf(s, MAX_MSG, "%#x", header->e_ovno);
    output("Overlay number", s);

    snprintf(s, MAX_MSG, "%#x", header->e_oemid);
    output("OEM identifier", s);

    snprintf(s, MAX_MSG, "%#x", header->e_oeminfo);
    output("OEM information", s);

    snprintf(s, MAX_MSG, "%#x", header->e_lfanew);
    output("PE header offset", s);

    output_close_scope(); // DOS Header
}

static void print_exports(pe_ctx_t *ctx)
{
    output_open_scope("Exported functions", OUTPUT_SCOPE_TYPE_ARRAY);

    const pe_exports_t *exports = pe_exports(ctx);

    if (exports->name || exports->functions_count > 0) {
        output_open_scope("Library", OUTPUT_SCOPE_TYPE_OBJECT);
        output("Name", exports->name);
    }
    if (exports->functions_count > 0) {
        output_open_scope("Functions", OUTPUT_SCOPE_TYPE_ARRAY);
    }

    for (size_t i=0; i < exports->functions_count; i++) {
        const pe_exported_function_t *func = &exports->functions[i];
        if (func->address != 0) {
            output_open_scope("Function", OUTPUT_SCOPE_TYPE_OBJECT);

            char ordinal_str[32] = { 0 };
            char address_str[16] = { 0 };
            char hint_str[16] = { 0 };
            snprintf(ordinal_str, sizeof(ordinal_str)-1, "%"PRIu32, func->ordinal);
            snprintf(address_str, sizeof(address_str)-1, "%#"PRIx32, func->address);
            snprintf(hint_str, sizeof(hint_str)-1, "0x%"PRIx32, func->hint);

            if (func->fwd_name != NULL) {
                char full_name[300 * 2 + 4];
                snprintf(full_name, sizeof(full_name)-1, "%s -> %s", func->name, func->fwd_name);
                output("Ordinal", ordinal_str);
                output("Address", address_str);
                output("Hint", hint_str);
                output("Name", full_name);
            } else {
                output("Ordinal", ordinal_str);
                output("Address", address_str);
                output("Hint", hint_str);
                output("Name", func->name);
            }

            output_close_scope(); // Function
        }
    }

    if (exports->functions_count > 0) {
        output_close_scope(); // Functions
    }
    if (exports->name || exports->functions_count > 0) {
        output_close_scope(); // Library
    }

    output_close_scope(); // Exported functions
}

static void print_import_library(const pe_imported_dll_t *dll)
{
        output("Name", dll->name);
        output_open_scope("Functions", OUTPUT_SCOPE_TYPE_ARRAY);

        for (size_t j=0; j < dll->functions_count; j++) {
            const pe_imported_function_t *func = &dll->functions[j];
            output_open_scope("Function", OUTPUT_SCOPE_TYPE_OBJECT);
            {
                if (func->ordinal) {
                    char ordinal_str[16];
                    snprintf(ordinal_str, sizeof(ordinal_str)-1, "%"PRIu16, func->ordinal);
                    output("Ordinal", ordinal_str);
                } else {
                    char hint_str[16];
                    snprintf(hint_str, sizeof(hint_str)-1, "0x%"PRIx16, func->hint);
                    output("Hint", hint_str);
                    output("Name", func->name);
                }
            }
            output_close_scope(); // Function
        }

        output_close_scope(); // Functions
}

static void print_imports(pe_ctx_t *ctx)
{
    output_open_scope("Imported functions", OUTPUT_SCOPE_TYPE_ARRAY);

    const pe_imports_t *imports = pe_imports(ctx);
    for (size_t i=0; i < imports->dll_count; i++) {
        const pe_imported_dll_t *dll = &imports->dlls[i];
        output_open_scope("Library", OUTPUT_SCOPE_TYPE_OBJECT);
        print_import_library(dll);
        output_close_scope(); // Library
    }
    for (size_t i=0; i < imports->delay_dll_count; i++) {
        const pe_imported_dll_t *dll = &imports->delay_dlls[i];
        output_open_scope("Delay Loaded Library", OUTPUT_SCOPE_TYPE_OBJECT);
        print_import_library(dll);
        output_close_scope(); // Delay Loaded Library
    }

    output_close_scope(); // Imported functions
}

int main(int argc, char *argv[])
{
    pev_config_t config;
    PEV_INITIALIZE(&config);

    if (argc < 2) {
        usage();
        return EXIT_FAILURE;
    }

    output_set_cmdline(argc, argv);

    options_t *options = parse_options(argc, argv); // opcoes

    pe_ctx_t ctx;

    pe_err_e err = pe_load_file(&ctx, argv[argc-1]);
    if (err != LIBPE_E_OK) {
        pe_error_print(stderr, err);
        return EXIT_FAILURE;
    }

    err = pe_parse(&ctx);
    if (err != LIBPE_E_OK) {
        pe_error_print(stderr, err);
        return EXIT_FAILURE;
    }

    if (!pe_is_pe(&ctx))
        EXIT_ERROR("not a valid PE file");

    output_open_document();

    // dos header
    if (options->dos || options->all_headers || options->all) {
        IMAGE_DOS_HEADER *header_ptr = pe_dos(&ctx);
        if (header_ptr)
            print_dos_header(header_ptr);
        else if (pe_is_exec(&ctx)) { LIBPE_WARNING("unable to read DOS header"); }
    }

    // coff/file header
    if (options->coff || options->all_headers || options->all) {
#ifndef LIBPE_ENABLE_OUTPUT_COMPAT_WITH_V06
        if (ctx.pe.signature) {
            static char s[MAX_MSG];
            output_open_scope("PE header", OUTPUT_SCOPE_TYPE_OBJECT);
            snprintf(s, MAX_MSG, "0x%08x (%.4s)", ctx.pe.signature, (const char *)&ctx.pe.signature);
            output("Signature", s);
            output_close_scope();
        }
#endif
        IMAGE_COFF_HEADER *header_ptr = pe_coff(&ctx);
        if (header_ptr)
            print_coff_header(&ctx, header_ptr);
        else { LIBPE_WARNING("unable to read COFF file header"); }
    }

    // optional header
    if (options->opt || options->all_headers || options->all) {
        IMAGE_OPTIONAL_HEADER *header_ptr = pe_optional(&ctx);
        if (header_ptr)
            print_optional_header(&ctx, header_ptr);
        else if (pe_is_exec(&ctx)) { LIBPE_WARNING("unable to read Optional (Image) file header"); }
    }

    IMAGE_DATA_DIRECTORY **directories = pe_directories(&ctx);
    bool directories_warned = false;

    // directories
    if (options->dirs || options->all) {
        if (directories != NULL)
            print_directories(&ctx);
        else if (pe_is_exec(&ctx) && !directories_warned) {
            LIBPE_WARNING("directories not found");
            directories_warned = true;
        }
    }

    // imports
    if (options->imports || options->all) {
        if (directories != NULL)
            print_imports(&ctx);
        else if (pe_is_exec(&ctx) && !directories_warned) {
            LIBPE_WARNING("directories not found");
            directories_warned = true;
        }
    }

    // exports
    if (options->exports || options->all) {
        if (directories != NULL)
            print_exports(&ctx);
        else if (pe_is_exec(&ctx) && !directories_warned) {
            LIBPE_WARNING("directories not found");
            directories_warned = true;
        }
    }

    // sections
    if (options->all_sections || options->all) {
        if (pe_sections(&ctx) != NULL)
            print_sections(&ctx);
        else { LIBPE_WARNING("unable to read sections"); }
    }

    output_close_document();

    // libera a memoria
    free_options(options);

    // free
    err = pe_unload(&ctx);
    if (err != LIBPE_E_OK) {
        pe_error_print(stderr, err);
        return EXIT_FAILURE;
    }

    PEV_FINALIZE(&config);

    return EXIT_SUCCESS;
}
