/* p_wcle.cpp --

   This file is part of the UPX executable compressor.

   Copyright (C) Markus Franz Xaver Johannes Oberhumer
   Copyright (C) Laszlo Molnar
   All Rights Reserved.

   UPX and the UCL library are free software; you can redistribute them
   and/or modify them 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; see the file COPYING.
   If not, write to the Free Software Foundation, Inc.,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   Markus F.X.J. Oberhumer              Laszlo Molnar
   <markus@oberhumer.com>               <ezerotven+github@gmail.com>
 */

#include "conf.h"
#include "file.h"
#include "filter.h"
#include "packer.h"
#include "lefile.h"
#include "p_wcle.h"
#define WANT_EHDR_ENUM 1
#include "p_elf_enum.h"
#include "linker.h"

static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/i386-dos32.watcom.le.h"

#define LEOF_READ    (1 << 0)
#define LEOF_WRITE   (1 << 1)
#define LEOF_EXEC    (1 << 2)
#define LEOF_PRELOAD (1 << 6)
#define LEOF_HUGE32  (1 << 13)

#define IOT(x, y) iobject_table[x].y
#define OOT(x, y) oobject_table[x].y

#define LE_STUB_EDI (1)

#ifdef TESTING
#define dputc(x, y)                                                                                \
    do {                                                                                           \
        if (opt->debug.debug_level)                                                                \
            putc(x, y);                                                                            \
    } while (0)
#define Opt_debug opt->debug.debug_level
#else
#define dputc(x, y) ((void) 0)
#define Opt_debug   0
#endif

#define my_base_address reserved
#define objects         ih.object_table_entries
#define pages           ih.memory_pages
#define mps             ih.memory_page_size
#define opages          oh.memory_pages

/*************************************************************************
//
**************************************************************************/

const int *PackWcle::getCompressionMethods(int method, int level) const {
    return Packer::getDefaultCompressionMethods_le32(method, level);
}

const int *PackWcle::getFilters() const {
    static const int filters[] = {0x26, 0x24,           0x49, 0x46, 0x16, 0x13,  0x14,
                                  0x11, FT_ULTRA_BRUTE, 0x25, 0x15, 0x12, FT_END};
    return filters;
}

Linker *PackWcle::newLinker() const { return new ElfLinkerX86; }

void PackWcle::buildLoader(const Filter *ft) {
    // prepare loader
    initLoader(EM_386, stub_i386_dos32_watcom_le, sizeof(stub_i386_dos32_watcom_le));
    addLoader("IDENTSTR,WCLEMAIN", ph.first_offset_found == 1 ? "WCLEMAIN02" : "",
              "WCLEMAIN03,UPX1HEAD,WCLECUTP");

    // fake alignment for the start of the decompressor
    linker->defineSymbol("WCLECUTP", 0x1000);

    addLoader(getDecompressorSections(), "WCLEMAI2");
    if (ft->id) {
        assert(ft->calls > 0);
        addLoader(ft->addvalue ? "WCCTTPOS" : "WCCTTNUL");
        addFilter32(ft->id);
    }
#if 1
    // FIXME: if (has_relocation)
    { addLoader("WCRELOC1,RELOC320", big_relocs ? "REL32BIG" : "", "RELOC32J"); }
#endif
    addLoader(has_extra_code ? "WCRELSEL" : "", "WCLEMAI4");
}

/*************************************************************************
// util
**************************************************************************/

void PackWcle::handleStub(OutputFile *fo) {
    if (fo && !opt->watcom_le.le)
        Packer::handleStub(fi, fo, le_offset);
}

tribool PackWcle::canPack() {
    if (!LeFile::readFileHeader())
        return false;
    return true;
}

/*************************************************************************
//
**************************************************************************/

// IDEA: as all the entries go into object #1, I could create bundles with 255
// elements (of course I still have to handle empty bundles)

void PackWcle::encodeEntryTable() {
    unsigned count, n;
    SPAN_S_VAR(byte, p, ientries, soentries);
    n = 0;
    while (*p) {
        count = *p;
        n += count;
        if (p[1] == 0) // unused bundle
            p += 2;
        else if (p[1] == 3) { // 32-bit bundle
            unsigned object = get_le16(p + 2);
            if (object == 0 || object > objects)
                throwCantPack("bad object number in entry table");
            object -= 1;
            set_le16(p + 2, 1);
            p += 4;
            for (; count; count--, p += 5)
                set_le32(p + 1, IOT(object, my_base_address) + get_le32(p + 1));
        } else
            throwCantPack("unsupported bundle type in entry table");
    }

    // if (Opt_debug) printf("%d entries encoded.\n",n);
    UNUSED(n);

    soentries = ptr_udiff_bytes(p, ientries) + 1;
    oentries = ientries;
    ientries = nullptr;
}

void PackWcle::readObjectTable() {
    LeFile::readObjectTable();

    // temporary copy of the object descriptors
    if (!objects) // late detection, but protect against .alloc(0)
        throwCantPack("no objects");
    iobject_desc.alloc(objects * sizeof(*iobject_table));
    memcpy(iobject_desc, iobject_table, objects * sizeof(*iobject_table));

    unsigned ic, jc, virtual_size;

    for (ic = jc = virtual_size = 0; ic < objects; ic++) {
        jc += IOT(ic, npages);
        IOT(ic, my_base_address) = virtual_size;
        virtual_size += (IOT(ic, virtual_size) + mps - 1) & ~(mps - 1);
    }
    if (pages != jc)
        throwCantPack("bad page number");
}

void PackWcle::encodeObjectTable() {
    unsigned ic, jc;

    soobject_table = 2;
    oobject_table = New(le_object_table_entry_t, soobject_table);
    memset(oobject_table, 0, mem_size(sizeof(*oobject_table), soobject_table));

    // object #1:
    OOT(0, base_address) = IOT(0, base_address);

    ic = IOT(objects - 1, my_base_address) + IOT(objects - 1, virtual_size);
    jc = mem_size(mps, pages, sofixups, 1024);
    if (ic < jc)
        ic = jc;

    unsigned csection = (ic + ph.overlap_overhead + mps - 1) & ~(mps - 1);

    OOT(0, virtual_size) = csection + mps;
    OOT(0, flags) = LEOF_READ | LEOF_EXEC | LEOF_HUGE32 | LEOF_PRELOAD;
    OOT(0, pagemap_index) = 1;
    OOT(0, npages) = opages;

    // object #2: stack
    OOT(1, base_address) = (OOT(0, base_address) + OOT(0, virtual_size) + mps - 1) & ~(mps - 1);
    OOT(1, virtual_size) = mps + getDecompressorWrkmemSize();
    OOT(1, flags) = LEOF_READ | LEOF_HUGE32 | LEOF_WRITE;
    OOT(1, pagemap_index) = 1;

    oh.init_cs_object = 1;
    oh.init_eip_offset = neweip;
    oh.init_ss_object = 2;
    oh.init_esp_offset = OOT(1, virtual_size);
    oh.automatic_data_object = 2;
}

void PackWcle::encodePageMap() {
    opm_entries = New(le_pagemap_entry_t, sopm_entries = opages);
    for (unsigned ic = 0; ic < sopm_entries; ic++) {
        opm_entries[ic].l = (byte) (ic + 1);
        opm_entries[ic].m = (byte) ((ic + 1) >> 8);
        opm_entries[ic].h = 0;
        opm_entries[ic].type = 0;
    }
}

void PackWcle::encodeFixupPageTable() {
    unsigned ic;
    ofpage_table = New(unsigned, sofpage_table = 1 + opages);
    for (ofpage_table[0] = ic = 0; ic < opages; ic++)
        set_le32(ofpage_table + ic + 1, sofixups - FIXUP_EXTRA);
}

void PackWcle::encodeFixups() {
    sofixups = 1 * 7 + FIXUP_EXTRA;
    ofixups = New(byte, sofixups);
    memset(ofixups, 0, sofixups);
    ofixups[0] = 7;
    set_le16(ofixups + 2, (LE_STUB_EDI + neweip) & (mps - 1));
    ofixups[4] = 1;
}

void PackWcle::preprocessFixups() {
    big_relocs = 0;

    unsigned ic, jc;

    Array(unsigned, counts, objects + 2);
    countFixups(counts);

    for (ic = jc = 0; ic < objects; ic++)
        jc += counts[ic];

    if (jc == 0) {
        // FIXME: implement this
        throwCantPack("files without relocations are not supported");
    }

    MemBuffer mb_relocs(jc);
    ByteArray(srf, counts[objects + 0] + 1);
    ByteArray(slf, counts[objects + 1] + 1);

    SPAN_S_VAR(byte, relocs, mb_relocs);
    SPAN_S_VAR(byte, selector_fixups, srf_membuf);
    SPAN_S_VAR(byte, selfrel_fixups, slf_membuf);
    unsigned relocnum = 0;

    byte *fix = ifixups;
    for (ic = jc = 0; ic < pages; ic++) {
        while (ptr_udiff_bytes(fix, ifixups) < get_le32(ifpage_table + (ic + 1))) {
            const int fixp2 = get_le16_signed(fix + 2);
            unsigned value;

            switch (*fix) {
            case 2: // selector fixup
                if (fixp2 < 0) {
                    // cross page selector fixup
                    dputc('S', stdout);
                    fix += 5;
                    break;
                }
                dputc('s', stdout);
                memcpy(selector_fixups, "\x8C\xCB\x66\x89\x9D",
                       5); // mov bx, cs ; mov [xxx+ebp], bx
                if (IOT(fix[4] - 1, flags) & LEOF_WRITE)
                    selector_fixups[1] = 0xDB; // ds
                set_le32(selector_fixups + 5, jc + fixp2);
                selector_fixups += 9;
                fix += 5;
                break;
            case 5: // 16-bit offset
                if ((unsigned) fixp2 < 4096 && IOT(fix[4] - 1, my_base_address) == jc)
                    dputc('6', stdout);
                else
                    throwCantPack("unsupported 16-bit offset relocation");
                fix += (fix[1] & 0x10) ? 9 : 7;
                break;
            case 6: // 16:32 pointer
                if (fixp2 < 0) {
                    // cross page pointer fixup
                    dputc('P', stdout);
                    fix += (fix[1] & 0x10) ? 9 : 7;
                    break;
                }
                dputc('p', stdout);
                memcpy(iimage + jc + fixp2, fix + 5, (fix[1] & 0x10) ? 4 : 2);
                set_le32(relocs + 4 * relocnum++, jc + fixp2);
                set_le32(iimage + jc + fixp2,
                         get_le32(iimage + jc + fixp2) + IOT(fix[4] - 1, my_base_address));

                memcpy(selector_fixups, "\x8C\xCA\x66\x89\x95", 5);
                if (IOT(fix[4] - 1, flags) & LEOF_WRITE)
                    selector_fixups[1] = 0xDA; // ds
                set_le32(selector_fixups + 5, jc + fixp2 + 4);
                selector_fixups += 9;
                fix += (fix[1] & 0x10) ? 9 : 7;
                break;
            case 7: // 32-bit offset
                if (fixp2 < 0) {
                    fix += (fix[1] & 0x10) ? 9 : 7;
                    break;
                }
                // if (memcmp(iimage+jc+fixp2,fix+5,(fix[1] & 0x10) ? 4 : 2))
                //    throwCantPack("illegal fixup offset");

                // work around a pmwunlite bug: remove duplicated fixups
                // FIXME: fix the other cases too
                if (relocnum == 0 || get_le32(relocs + 4 * relocnum - 4) != jc + fixp2) {
                    set_le32(relocs + 4 * relocnum++, jc + fixp2);
                    set_le32(iimage + jc + fixp2,
                             get_le32(iimage + jc + fixp2) + IOT(fix[4] - 1, my_base_address));
                }
                fix += (fix[1] & 0x10) ? 9 : 7;
                break;
            case 8: // 32-bit self relative fixup
                if (fixp2 < 0) {
                    // cross page self relative fixup
                    dputc('R', stdout);
                    fix += (fix[1] & 0x10) ? 9 : 7;
                    break;
                }
                value = get_le32(fix + 5);
                if (fix[1] == 0)
                    value &= 0xffff;
                set_le32(iimage + jc + fixp2,
                         (value + IOT(fix[4] - 1, my_base_address)) - jc - fixp2 - 4);
                set_le32(selfrel_fixups, jc + fixp2);
                selfrel_fixups += 4;
                dputc('r', stdout);
                fix += (fix[1] & 0x10) ? 9 : 7;
                break;
            default:
                throwCantPack("unsupported fixup record");
            }
        }
        jc += mps;
    }

    // resize ifixups if it's too small
    if (sofixups < 4 * relocnum + 8192) {
        delete[] ifixups;
        sofixups = 4 * relocnum + 8192;
        ifixups = New(byte, sofixups);
    }
    SPAN_S_VAR(byte, orelocs, ifixups, sofixups);
    fix =
        ifixups + optimizeReloc(relocnum, relocs, orelocs, iimage, soimage, 32, true, &big_relocs);
    has_extra_code = ptr_udiff_bytes(selector_fixups, srf) != 0;
    // FIXME: this could be removed if has_extra_code = false
    // but then we'll need a flag
    *selector_fixups++ = 0xC3;                               // ret
    memcpy(fix, srf, ptr_udiff_bytes(selector_fixups, srf)); // copy selector fixup code
    fix += ptr_udiff_bytes(selector_fixups, srf);

    memcpy(fix, slf, ptr_udiff_bytes(selfrel_fixups, slf)); // copy self-relative fixup positions
    fix += ptr_udiff_bytes(selfrel_fixups, slf);
    set_le32(fix, 0xFFFFFFFFUL);
    fix += 4;

    sofixups = ptr_udiff_bytes(fix, ifixups);
}

#define RESERVED 0x1000
void PackWcle::encodeImage(Filter *ft) {
    // concatenate image & preprocessed fixups
    unsigned isize = soimage + sofixups;
    ibuf.alloc(isize);
    memcpy(ibuf, iimage, soimage);
    memcpy(ibuf + soimage, ifixups, sofixups);

    delete[] ifixups;
    ifixups = nullptr;

    mb_oimage.allocForCompression(isize, RESERVED + 512);
    oimage = SPAN_S_MAKE(byte, mb_oimage); // => now a SPAN_S
    // prepare packheader
    ph.u_len = isize;
    // prepare filter [already done]
    // compress
    upx_compress_config_t cconf;
    cconf.reset();
    cconf.conf_lzma.max_num_probs = 1846 + (768 << 4); // ushort: ~28 KiB stack
    compressWithFilters(ibuf, isize, raw_bytes(oimage + RESERVED, mb_oimage.getSize() - RESERVED),
                        ibuf + ft->addvalue, ft->buf_len, nullptr, 0, ft, 512, &cconf, 0);

    ibuf.dealloc();
    soimage = ph.c_len;
    while (soimage & 3)
        oimage[RESERVED + soimage++] = 0;
}

void PackWcle::pack(OutputFile *fo) {
    handleStub(fo);

    if (ih.byte_order || ih.word_order || ih.exe_format_level || ih.cpu_type < 2 ||
        ih.cpu_type > 5 || ih.target_os != 1 || ih.module_type != 0x200 ||
        ih.object_iterate_data_map_offset || ih.resource_entries || ih.module_directives_entries ||
        ih.imported_modules_count || ih.object_table_entries > 255)
        throwCantPack("watcom/le: unexpected value in header");

    readObjectTable();
    readPageMap();
    readResidentNames();
    readEntryTable();
    readFixupPageTable();
    readFixups();
    readImage();
    readNonResidentNames();

    //    if (find_le32(iimage,20,get_le32("UPX ")) >= 0)
    if (find_le32(raw_bytes(iimage, soimage), UPX_MIN(soimage, 256u), UPX_MAGIC_LE32) >= 0)
        throwAlreadyPacked();

    if (ih.init_ss_object != objects)
        throwCantPack("the stack is not in the last object");
    if (ih.init_cs_object == 0 || ih.init_cs_object > objects)
        throwCantPack("bad init_cs_object");

    preprocessFixups();

    const unsigned text_size = mem_size(mps, IOT(ih.init_cs_object - 1, npages));
    const unsigned text_vaddr = IOT(ih.init_cs_object - 1, my_base_address);

    // attach some useful data at the end of preprocessed fixups
    ifixups[sofixups++] = (byte) (ih.automatic_data_object & 0xff);
    unsigned ic = objects * sizeof(*iobject_table);
    memcpy(ifixups + sofixups, iobject_desc, ic);
    iobject_desc.dealloc();

    sofixups += ic;
    set_le32(ifixups + sofixups,
             ih.init_esp_offset + IOT(ih.init_ss_object - 1, my_base_address)); // old stack pointer
    set_le32(ifixups + sofixups + 4, ih.init_eip_offset + text_vaddr);          // real entry point
    set_le32(ifixups + sofixups + 8,
             mem_size(mps, pages)); // virtual address of unpacked relocations
    ifixups[sofixups + 12] = (byte) (unsigned) objects;
    sofixups += 13;

    // prepare filter
    Filter ft(ph.level);
    ft.buf_len = text_size;
    ft.addvalue = text_vaddr;
    // compress
    encodeImage(&ft);

    const unsigned lsize = getLoaderSize();
    neweip = getLoaderSection("WCLEMAIN");
    int e_len = getLoaderSectionStart("WCLECUTP");
    const unsigned d_len = lsize - e_len;
    assert(e_len > 0 && e_len < RESERVED);

    memmove(oimage + e_len, oimage + RESERVED, soimage);
    soimage += lsize;

    opages = (soimage + mps - 1) / mps;
    oh.bytes_on_last_page = soimage % mps;

    encodeObjectTable();
    encodeFixups();
    encodeFixupPageTable();
    encodePageMap();
    encodeEntryTable();

    encodeResidentNames();
    encodeNonResidentNames();

    // patch loader
    ic = (OOT(0, virtual_size) - d_len) & ~15;
    assert(ic > ((ph.u_len + ph.overlap_overhead + 31) & ~15));

    linker->defineSymbol("WCLECUTP", ic);

    linker->defineSymbol("original_entry", ih.init_eip_offset + text_vaddr);
    linker->defineSymbol("original_stack",
                         ih.init_esp_offset + IOT(ih.init_ss_object - 1, my_base_address));
    linker->defineSymbol("start_of_relocs", mem_size(mps, pages));
    defineDecompressorSymbols();
    defineFilterSymbols(&ft);
    linker->defineSymbol("filter_buffer_start", text_vaddr);

    unsigned jpos = (((ph.c_len + 3) & ~3) + d_len + 3) / 4;
    linker->defineSymbol("words_to_copy", jpos);
    linker->defineSymbol("copy_dest", ((ic + d_len + 3) & ~3) - 4);
    linker->defineSymbol("copy_source", e_len + jpos * 4 - 4);

    relocateLoader();

    MemBuffer loader(lsize);
    memcpy(loader, getLoader(), lsize);
    patchPackHeader(loader, lsize);

    memcpy(oimage, loader, e_len);
    memcpy(oimage + soimage - d_len, loader + e_len, d_len);

    writeFile(fo, opt->watcom_le.le);

    // verify
    verifyOverlappingDecompression(mb_oimage + e_len, mb_oimage.getSize() - e_len);

    // copy the overlay
    const unsigned overlaystart = ih.data_pages_offset + exe_offset + getImageSize();
    const unsigned overlay = file_size - overlaystart - ih.non_resident_name_table_length;
    checkOverlay(overlay);
    copyOverlay(fo, overlay, mb_oimage);

    // finally check the compression ratio
    if (!checkFinalCompressionRatio(fo))
        throwNotCompressible();
}

/*************************************************************************
//
**************************************************************************/

void PackWcle::decodeFixups() {
    mb_iimage.dealloc();
    iimage = nullptr;

    SPAN_S_VAR(const byte, p, oimage + soimage);
    MemBuffer mb_relocs;
    const unsigned fixupn = unoptimizeReloc(p, mb_relocs, oimage, soimage, 32, true);

    MemBuffer wrkmem(8 * fixupn + 8);
    unsigned ic, jc, o, r;
    for (ic = 0; ic < fixupn; ic++) {
        jc = get_le32(mb_relocs + 4 * ic);
        set_le32(wrkmem + ic * 8, jc);
        o = soobject_table;
        r = get_le32(oimage + jc);
        virt2rela(oobject_table, &o, &r);
        set_le32(wrkmem + ic * 8 + 4, OOT(o - 1, my_base_address));
        set_le32(oimage + jc, r);
    }
    set_le32(wrkmem + ic * 8, 0xFFFFFFFF); // end of 32-bit offset fixups
    mb_relocs.dealloc();                   // done

    // selector fixups then self-relative fixups
    SPAN_S_VAR(const byte, selector_fixups, p);

    // Find selfrel_fixups by skipping over selector_fixups.
    SPAN_S_VAR(const byte, q, selector_fixups);
    // The code is a subroutine that ends in RET (0xC3).
    while (*q != 0xC3) {
        // Defend against tampered selector_fixups; see PackWcle::preprocessFixups().
        // selector_fixups[] is x386 code with 9-byte blocks of 2 instructions each:
        // "\x8C\xCB\x66\x89\x9D"  // mov bx, cs ; mov [xxx+ebp], bx
        // "\x8C\xCA\x66\x89\x95"
        // and where byte [+1] also can be '\xDA' or '\xDB'.
        if (0x8C != q[0] || 0x66 != q[2] || 0x89 != q[3]) { // Unexpected; tampering?
            // Try to recover by looking for the RET.
            const byte *q2 = (const byte *) memchr(q, 0xC3, 9);
            if (q2) { // Assume recovery
                q = q2;
                break;
            }
        }
        // Guard against run-away.
        static const byte blank[9] = {};
        // catastrophic worst case or no-good early warning
        if (ptr_diff_bytes(oimage + ph.u_len - sizeof(blank), raw_bytes(q, 0)) < 0 ||
            !memcmp(blank, q, sizeof(blank))) {
            char msg[50];
            snprintf(msg, sizeof(msg), "bad selector_fixups %d",
                     ptr_diff_bytes(q, selector_fixups));
            throwCantPack(msg);
        }
        q += 9;
    }
    unsigned selectlen = ptr_udiff_bytes(q, selector_fixups) / 9;
    SPAN_S_VAR(const byte, selfrel_fixups, q + 1); // Skip the 0xC3

    const unsigned fbytes = fixupn * 9 + 1000 + selectlen * 5;
    ofixups = New(byte, fbytes);
    SPAN_S_VAR(byte, fp, ofixups, fbytes, ofixups);

    for (ic = 1, jc = 0; ic <= opages; ic++) {
        // self relative fixups
        while ((r = get_le32(selfrel_fixups)) / mps == ic - 1) {
            fp[0] = 8;
            set_le16(fp + 2, r & (mps - 1));
            o = 4 + get_le32(oimage + r);
            set_le32(oimage + r, 0);
            r += o;
            o = soobject_table;
            virt2rela(oobject_table, &o, &r);
            fp[4] = (byte) o;
            set_le32(fp + 5, r);
            fp[1] = (byte) (r > 0xFFFF ? 0x10 : 0);
            fp += fp[1] ? 9 : 7;
            selfrel_fixups += 4;
            dputc('r', stdout);
        }
        // selector fixups
        while (selectlen && (r = get_le32(selector_fixups + 5)) / mps == ic - 1) {
            fp[0] = 2;
            fp[1] = 0;
            set_le16(fp + 2, r & (mps - 1));
            unsigned x = selector_fixups[1] > 0xD0 ? oh.init_ss_object : oh.init_cs_object;
            fp[4] = (byte) x;
            fp += 5;
            selector_fixups += 9;
            selectlen--;
            dputc('s', stdout);
        }
        // 32 bit offset fixups
        while (get_le32(wrkmem + 4 * jc) < mem_size(mps, ic)) {
            if (jc > 1 &&
                ((get_le32(wrkmem + 4 * (jc - 2)) + 3) & (mps - 1)) < 3) // cross page fixup?
            {
                r = get_le32(oimage + get_le32(wrkmem + 4 * (jc - 2)));
                fp[0] = 7;
                fp[1] = (byte) (r > 0xFFFF ? 0x10 : 0);
                set_le16(fp + 2, get_le32(wrkmem + 4 * (jc - 2)) | ~3);
                set_le32(fp + 5, r);
                o = soobject_table;
                r = get_le32(wrkmem + 4 * (jc - 1));
                virt2rela(oobject_table, &o, &r);
                fp[4] = (byte) o;
                fp += fp[1] ? 9 : 7;
                dputc('0', stdout);
            }
            o = soobject_table;
            r = get_le32(wrkmem + 4 * (jc + 1));
            virt2rela(oobject_table, &o, &r);
            r = get_le32(oimage + get_le32(wrkmem + 4 * jc));
            fp[0] = 7;
            fp[1] = (byte) (r > 0xFFFF ? 0x10 : 0);
            set_le16(fp + 2, get_le32(wrkmem + 4 * jc) & (mps - 1));
            fp[4] = (byte) o;
            set_le32(fp + 5, r);
            fp += fp[1] ? 9 : 7;
            jc += 2;
        }
        set_le32(ofpage_table + ic, ptr_udiff_bytes(fp, ofixups));
    }
    for (ic = 0; ic < FIXUP_EXTRA; ic++)
        *fp++ = 0;
    sofixups = ptr_udiff_bytes(fp, ofixups);
}

void PackWcle::decodeFixupPageTable() {
    ofpage_table = New(unsigned, sofpage_table = 1 + opages);
    set_le32(ofpage_table, 0);
    // the rest of ofpage_table is filled by decodeFixups()
}

void PackWcle::decodeObjectTable() {
    soobject_table = oimage[ph.u_len - 1];
    oobject_table = New(le_object_table_entry_t, soobject_table);
    unsigned jc, ic = soobject_table * sizeof(*oobject_table);

    const unsigned extradata = ph.version == 10 ? 17 : 13;
    memcpy(oobject_table, oimage + ph.u_len - extradata - ic, ic);
    if (ph.version >= 12)
        oh.automatic_data_object = oimage[ph.u_len - ic - 14];

    for (ic = jc = 0; ic < soobject_table; ic++) {
        OOT(ic, my_base_address) = jc;
        jc += (OOT(ic, virtual_size) + mps - 1) & ~(mps - 1);
    }

    // restore original cs:eip & ss:esp
    ic = soobject_table;
    jc = get_le32(oimage + ph.u_len - (ph.version < 11 ? 13 : 9));
    virt2rela(oobject_table, &ic, &jc);
    oh.init_cs_object = ic;
    oh.init_eip_offset = jc;

    ic = soobject_table;
    if (ph.version < 10)
        jc = ih.init_esp_offset;
    else
        jc = get_le32(oimage + ph.u_len - (ph.version == 10 ? 17 : 13));
    virt2rela(oobject_table, &ic, &jc);
    oh.init_ss_object = ic;
    oh.init_esp_offset = jc;
}

void PackWcle::decodeImage() {
    mb_oimage.allocForDecompression(ph.u_len);
    oimage = SPAN_S_MAKE(byte, mb_oimage); // => now a SPAN_S

    decompress(iimage + ph.buf_offset + ph.getPackHeaderSize(), oimage);
    soimage = get_le32(oimage + ph.u_len - 5);
    opages = soimage / mps;
    oh.memory_page_size = mps;
}

void PackWcle::decodeEntryTable() {
    unsigned count, n, r;
    SPAN_S_VAR(byte, p, ientries, soentries);
    n = 0;
    while (*p) {
        count = *p;
        n += count;
        if (p[1] == 0) // unused bundle
            p += 2;
        else if (p[1] == 3) { // 32-bit offset bundle
            unsigned object = get_le16(p + 2);
            if (object != 1)
                throwCantUnpack("corrupted entry found");
            object = soobject_table;
            r = get_le32(p + 5);
            virt2rela(oobject_table, &object, &r);
            set_le16(p + 2, object--);
            p += 4;
            for (; count; count--, p += 5)
                set_le32(p + 1, get_le32(p + 1) - OOT(object, my_base_address));
        } else
            throwCantUnpack("unsupported bundle type in entry table");
    }

    // if (Opt_debug) printf("\n%d entries decoded.\n",n);
    UNUSED(n);

    soentries = ptr_udiff_bytes(p, ientries) + 1;
    oentries = ientries;
    ientries = nullptr;
}

tribool PackWcle::canUnpack() {
    if (!LeFile::readFileHeader())
        return false;
    fi->seek(exe_offset + ih.data_pages_offset, SEEK_SET);
    // FIXME: 1024 could be too large for some files
    // int len = 1024;
    int len = UPX_MIN(getImageSize(), 256u);
    if (len == 0)
        return false;
    return readPackHeader(len) ? 1 : -1;
}

/*static*/
void PackWcle::virt2rela(const le_object_table_entry_t *entr, unsigned *objn, unsigned *addr) {
    for (; *objn > 1; objn[0]--) {
        if (entr[*objn - 1].my_base_address > *addr)
            continue;
        *addr -= entr[*objn - 1].my_base_address;
        break;
    }
}

/*************************************************************************
//
**************************************************************************/

void PackWcle::unpack(OutputFile *fo) {
    handleStub(fo);

    readObjectTable();
    iobject_desc.dealloc();
    readPageMap();
    readResidentNames();
    readEntryTable();
    readFixupPageTable();
    readFixups();
    readImage();
    readNonResidentNames();

    decodeImage();
    decodeObjectTable();

    // unfilter
    if (ph.filter) {
        const unsigned text_size = mem_size(mps, OOT(oh.init_cs_object - 1, npages));
        const unsigned text_vaddr = OOT(oh.init_cs_object - 1, my_base_address);

        Filter ft(ph.level);
        ft.init(ph.filter, text_vaddr);
        ft.cto = (byte) ph.filter_cto;
        if (ph.version < 11)
            ft.cto = (byte) (get_le32(oimage + ph.u_len - 9) >> 24);
        ft.unfilter(oimage + text_vaddr, text_size);
    }

    decodeFixupPageTable();
    decodeFixups();
    decodeEntryTable();
    decodePageMap();
    decodeResidentNames();
    decodeNonResidentNames();

    for (unsigned ic = 0; ic < soobject_table; ic++)
        OOT(ic, my_base_address) = 0;

    while (oimage[soimage - 1] == 0)
        soimage--;
    oh.bytes_on_last_page = soimage % mps;

    // write decompressed file
    if (fo)
        writeFile(fo, opt->watcom_le.le);

    // copy the overlay
    const unsigned overlaystart = ih.data_pages_offset + exe_offset + getImageSize();
    const unsigned overlay = file_size - overlaystart - ih.non_resident_name_table_length;
    checkOverlay(overlay);
    copyOverlay(fo, overlay, mb_oimage);
}

/* vim:set ts=4 sw=4 et: */
