/* p_exe.cpp -- dos/exe executable format

   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 "p_exe.h"
#define WANT_EHDR_ENUM 1
#include "p_elf_enum.h"
#include "linker.h"

static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/i086-dos16.exe.h"

#define MAXMATCH     0x2000
#define MAXRELOCSIZE (0x8000 - MAXMATCH)

#define DI_LIMIT 0xff00 // see the assembly why

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

PackExe::PackExe(InputFile *f) : super(f) {
    bele = &N_BELE_RTP::le_policy;
    COMPILE_TIME_ASSERT(sizeof(exe_header_t) == 32)
    COMPILE_TIME_ASSERT_ALIGNED1(exe_header_t)
}

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

const int *PackExe::getCompressionMethods(int method, int level) const {
    bool small = ih_imagesize <= 256 * 1024;
    // disable lzma for "--brute" unless explicitly given "--lzma"
    // (note that class PackMaster creates per-file local options)
    if (opt->all_methods_use_lzma == 1 && !opt->method_lzma_seen)
        opt->all_methods_use_lzma = 0;
    return Packer::getDefaultCompressionMethods_8(method, level, small);
}

const int *PackExe::getFilters() const { return nullptr; }

int PackExe::fillExeHeader(struct exe_header_t *eh) const {
#define oh (*eh)
    // fill new exe header
    int flag = 0;
    if (!opt->dos_exe.no_reloc && !M_IS_LZMA(ph.method))
        flag |= USEJUMP;
    if (ih.relocs == 0)
        flag |= NORELOC;

    mem_clear(&oh);
    oh.ident = 'M' + 'Z' * 256;
    oh.headsize16 = 2;

    unsigned minsp = 0x200;
    if (M_IS_LZMA(ph.method))
        minsp = stack_for_lzma;
    minsp = ALIGN_UP(minsp, 16u);
    assert(minsp < 0xff00);
    if (oh.sp > minsp)
        minsp = oh.sp;
    if (minsp < 0xff00 - 2)
        minsp = ALIGN_UP(minsp, 2u);
    oh.sp = minsp;

    unsigned destpara = (ph.u_len + ph.overlap_overhead - ph.c_len + 31) / 16;
    oh.ss = ph.c_len / 16 + destpara;
    if (ih.ss * 16 + ih.sp < 0x100000 && ih.ss > oh.ss && ih.sp > 0x200)
        oh.ss = ih.ss;
    if (oh.ss * 16 + 0x50 < ih.ss * 16 + ih.sp && oh.ss * 16 + 0x200 > ih.ss * 16 + ih.sp)
        oh.ss += 0x20;

    if (oh.ss != ih.ss)
        flag |= SS;
    if (oh.sp != ih.sp || M_IS_LZMA(ph.method))
        flag |= SP;
    return flag;
#undef oh
}

void PackExe::addLoaderEpilogue(int flag) {
    addLoader("EXEMAIN5");
    if (relocsize)
        addLoader(ph.u_len <= DI_LIMIT || (ph.u_len & 0x7fff) >= relocsize ? "EXENOADJ"
                                                                           : "EXEADJUS",
                  "EXERELO1", has_9a ? "EXEREL9A" : "", "EXERELO2",
                  ih_exesize > 0xFE00 ? "EXEREBIG" : "", "EXERELO3");
    addLoader("EXEMAIN8", device_driver ? "DEVICEEND" : "", (flag & SS) ? "EXESTACK" : "",
              (flag & SP) ? "EXESTASP" : "", (flag & USEJUMP) ? "EXEJUMPF" : "");
    if (!(flag & USEJUMP))
        addLoader(ih.cs ? "EXERCSPO" : "", "EXERETIP");

    linker->defineSymbol("original_cs", ih.cs);
    linker->defineSymbol("original_ip", ih.ip);
    linker->defineSymbol("original_sp", ih.sp);
    linker->defineSymbol("original_ss", ih.ss);
    linker->defineSymbol(
        "reloc_size",
        (ph.u_len <= DI_LIMIT || (ph.u_len & 0x7fff) >= relocsize ? 0 : MAXRELOCSIZE) - relocsize);
}

void PackExe::buildLoader(const Filter *) {
    // get flag
    exe_header_t dummy_oh;
    int flag = fillExeHeader(&dummy_oh);

    initLoader(EM_386, stub_i086_dos16_exe, sizeof(stub_i086_dos16_exe));

    if (M_IS_LZMA(ph.method)) {
        addLoader("LZMA_DEC00", opt->small ? "LZMA_DEC10" : "LZMA_DEC20", "LZMA_DEC30",
                  use_clear_dirty_stack ? "LZMA_DEC31" : "", "LZMA_DEC32",
                  ph.u_len > 0xffff ? "LZMA_DEC33" : "");

        addLoaderEpilogue(flag);
        defineDecompressorSymbols();
        const unsigned lsize0 = getLoaderSize();

        // Lzma decompression code starts at ss:0x10, and its size is
        // lsize bytes. It also needs getDecompressorWrkmemSize() bytes
        // during uncompression. It also uses some stack, so 0x100
        // more bytes are allocated
        stack_for_lzma = 0x10 + lsize0 + getDecompressorWrkmemSize() + 0x100;
        stack_for_lzma = ALIGN_UP(stack_for_lzma, 16u);

        unsigned clear_dirty_stack_low = 0x10 + lsize0;
        clear_dirty_stack_low = ALIGN_UP(clear_dirty_stack_low, 2u);
        if (use_clear_dirty_stack)
            linker->defineSymbol("clear_dirty_stack_low", clear_dirty_stack_low);

        relocateLoader();
        const unsigned lsize = getLoaderSize();
        assert(lsize0 == lsize);
        MemBuffer loader(lsize);
        memcpy(loader, getLoader(), lsize);

        MemBuffer compressed_lzma;
        compressed_lzma.allocForCompression(lsize);
        unsigned c_len_lzma = MemBuffer::getSizeForCompression(lsize);
        int r = upx_compress(loader, lsize, compressed_lzma, &c_len_lzma, nullptr, M_NRV2B_LE16, 9,
                             nullptr, nullptr);
        assert(r == UPX_E_OK);
        assert(c_len_lzma < lsize);

        info("lzma+relocator code compressed: %u -> %u", lsize, c_len_lzma);
        // reinit the loader
        initLoader(EM_386, stub_i086_dos16_exe, sizeof(stub_i086_dos16_exe));
        // prepare loader
        if (device_driver)
            addLoader("DEVICEENTRY,LZMADEVICE,DEVICEENTRY2");

        linker->addSection("COMPRESSED_LZMA", compressed_lzma, c_len_lzma, 0);
        addLoader("LZMAENTRY,NRV2B160,NRVDDONE,NRVDECO1,NRVGTD00,NRVDECO2");

    } else if (device_driver)
        addLoader("DEVICEENTRY,DEVICEENTRY2");

    addLoader("EXEENTRY", M_IS_LZMA(ph.method) && device_driver ? "LONGSUB" : "SHORTSUB",
              "JNCDOCOPY", relocsize ? "EXERELPU" : "", "EXEMAIN4",
              M_IS_LZMA(ph.method) ? "" : "EXEMAIN4B", "EXEMAIN4C",
              M_IS_LZMA(ph.method) ? "COMPRESSED_LZMA_START,COMPRESSED_LZMA" : "",
              "+G5DXXXX,UPX1HEAD,EXECUTPO");
    if (ph.method == M_NRV2B_8)
        addLoader("NRV2B16S", // decompressor
                  ph.u_len > DI_LIMIT ? "N2B64K01" : "", "NRV2BEX1",
                  opt->cpu_x86 == opt->CPU_8086 ? "N2BX8601" : "N2B28601", "NRV2BEX2",
                  opt->cpu_x86 == opt->CPU_8086 ? "N2BX8602" : "N2B28602", "NRV2BEX3",
                  ph.c_len > 0xffff ? "N2B64K02" : "", "NRV2BEX9");
    else if (ph.method == M_NRV2D_8)
        addLoader("NRV2D16S", ph.u_len > DI_LIMIT ? "N2D64K01" : "", "NRV2DEX1",
                  opt->cpu_x86 == opt->CPU_8086 ? "N2DX8601" : "N2D28601", "NRV2DEX2",
                  opt->cpu_x86 == opt->CPU_8086 ? "N2DX8602" : "N2D28602", "NRV2DEX3",
                  ph.c_len > 0xffff ? "N2D64K02" : "", "NRV2DEX9");
    else if (ph.method == M_NRV2E_8)
        addLoader("NRV2E16S", ph.u_len > DI_LIMIT ? "N2E64K01" : "", "NRV2EEX1",
                  opt->cpu_x86 == opt->CPU_8086 ? "N2EX8601" : "N2E28601", "NRV2EEX2",
                  opt->cpu_x86 == opt->CPU_8086 ? "N2EX8602" : "N2E28602", "NRV2EEX3",
                  ph.c_len > 0xffff ? "N2E64K02" : "", "NRV2EEX9");
    else if M_IS_LZMA (ph.method)
        return;
    else
        throwInternalError("unknown compression method");

    addLoaderEpilogue(flag);
}

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

int PackExe::readFileHeader() {
    ih_exesize = ih_imagesize = ih_overlay = 0;
    fi->readx(&ih, sizeof(ih));
    if (ih.ident != 'M' + 'Z' * 256 && ih.ident != 'Z' + 'M' * 256)
        return 0;
    ih_exesize = ih.m512 + ih.p512 * 512 - (ih.m512 ? 512 : 0);
    if (ih_exesize == 0)
        ih_exesize = file_size;
    ih_imagesize = ih_exesize - ih.headsize16 * 16;
    ih_overlay = file_size - ih_exesize;
    if (file_size_u < sizeof(ih) || ((ih.m512 | ih.p512) && ih.m512 + ih.p512 * 512u < sizeof(ih)))
        throwCantPack("illegal exe header");
    if (ih_exesize > file_size_u || ih_imagesize < 4 || ih_imagesize > ih_exesize)
        throwCantPack("exe header corrupted");
    NO_printf("dos/exe header: %d %d %d\n", ih_exesize, ih_imagesize, ih_overlay);
    return UPX_F_DOS_EXE;
}

tribool PackExe::canPack() {
    if (fn_has_ext(fi->getName(), "sys")) // dos/sys
        return false;
    if (!readFileHeader())
        return false;
    if (file_size < 1024 || ih_imagesize < 512)
        throwCantPack("file is too small for dos/exe");
    fi->seek(0x3c, SEEK_SET);
    LE32 offs;
    fi->readx(&offs, sizeof(offs));
    if (ih.relocoffs >= 0x40 && offs) {
        if (opt->dos_exe.force_stub)
            opt->overlay = opt->COPY_OVERLAY;
        else
            throwCantPack("dos/exe: can't pack new-exe");
    }
    return true;
}

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

static unsigned optimize_relocs(SPAN_S(byte) image, const unsigned image_size,
                                SPAN_S(const byte) relocs, const unsigned relocnum,
                                SPAN_S(byte) crel, bool *has_9a) {
#if WITH_XSPAN >= 2
    ptr_check_no_overlap(image.data(image_size), image_size, relocs.data(), relocs.size_bytes(),
                         crel.data(), crel.size_bytes());
#endif
    if (opt->exact)
        throwCantPackExact();

    SPAN_S_VAR(byte, const crel_start, crel);
    unsigned seg_high = 0;
#if 0
    unsigned seg_low = 0xffffffff;
    unsigned off_low = 0xffffffff;
    unsigned off_high = 0;
    unsigned linear_low = 0xffffffff;
    unsigned linear_high = 0;
#endif

    // pass 1 - find 0x9a bounds in image
    for (unsigned i = 0; i < relocnum; i++) {
        unsigned addr = get_le32(relocs + 4 * i);
        if (addr >= image_size - 1)
            throwCantPack("unexpected relocation 1");
        if (addr >= 3 && image[addr - 3] == 0x9a) {
            unsigned seg = get_le16(image + addr);
            if (seg > seg_high)
                seg_high = seg;
#if 0
            if (seg < seg_low)
                seg_low = seg;
            unsigned off = get_le16(image + addr - 2);
            if (off < off_low)
                off_low = off;
            if (off > off_high)
                off_high = off;
            unsigned l = (seg << 4) + off;
            if (l < linear_low)
                linear_low = l;
            if (l > linear_high)
                linear_high = l;
#endif
        }
    }
    // printf("%d %d\n", seg_low, seg_high);
    // printf("%d %d\n", off_low, off_high);
    // printf("%d %d\n", linear_low, linear_high);

    // pass 2 - reloc

    crel += 4; // to be filled in later

    unsigned ones = 0;
    unsigned es = 0;
    for (unsigned i = 0; i < relocnum;) {
        unsigned addr = get_le32(relocs + 4 * i);
        unsigned di = addr & 0x0f;
        set_le16(crel + 0, di);
        set_le16(crel + 2, (addr >> 4) - es);
        crel += 4;
        es = addr >> 4;

        for (++i; i < relocnum; i++) {
            unsigned t;
            addr = get_le32(relocs + 4 * i);
            NO_printf("%x\n", es * 16 + di);
            if ((addr - es * 16 > 0xfffe) || (i == relocnum - 1 && addr - es * 16 > 0xff00)) {
                // segment change
                t = 1 + (0xffff - di) / 254;
                memset(crel, 1, t);
                crel += t;
                ones += t - 1; // -1 is used to help the assembly stuff
                break;
            }
            unsigned offs = addr - es * 16;
            if (offs >= 3 && image[es * 16 + offs - 3] == 0x9a && offs > di + 3) {
                for (t = di; t < offs - 3; t++)
                    if (image[es * 16 + t] == 0x9a && get_le16(image + es * 16 + t + 3) <= seg_high)
                        break;
                if (t == offs - 3) {
                    // code 0: search for 0x9a
                    *crel++ = 0;
                    di = offs;
                    *has_9a = true;
                    continue;
                }
            }
            t = offs - di;
            if ((int) t < 2)
                throwCantPack("unexpected relocation 2");
            while (t >= 256) {
                // code 1: add 254, don't reloc
                *crel++ = 1;
                t -= 254;
                ones++;
            }
            *crel++ = (byte) t;
            di = offs;
        }
    }
    *crel++ = 1;
    ones++;
    set_le16(crel_start, ones);
    set_le16(crel_start + 2, seg_high);

    // OutputFile::dump("x.rel", crel_start, ptr_udiff_bytes(crel, crel_start));
    return ptr_udiff_bytes(crel, crel_start);
}

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

void PackExe::pack(OutputFile *fo) {
    unsigned ic;

    const unsigned relocnum = ih.relocs;
    if (relocnum > MAXRELOCSIZE) // early check
        throwCantPack("too many relocations");
    checkOverlay(ih_overlay);

    // read image
    // image + space for optimized relocs + safety/alignments
    ibuf.alloc(ih_imagesize + 4 * relocnum + 1024);
    fi->seek(ih.headsize16 * 16, SEEK_SET);
    fi->readx(ibuf, ih_imagesize);

    checkAlreadyPacked(ibuf, UPX_MIN(ih_imagesize, 127u));

    device_driver = get_le32(ibuf) == 0xffffffffu;

    // relocations
    relocsize = 0;
    has_9a = false;
    if (relocnum) {
        MemBuffer mb_relocs(4 * relocnum);
        SPAN_S_VAR(byte, relocs, mb_relocs);
        fi->seek(ih.relocoffs, SEEK_SET);
        fi->readx(relocs, 4 * relocnum);

        // dos/exe runs in real-mode, so convert to linear addresses
        for (ic = 0; ic < relocnum; ic++) {
            unsigned jc = get_le32(relocs + 4 * ic);
            set_le32(relocs + 4 * ic, ((jc >> 16) * 16 + (jc & 0xffff)) & 0xfffff);
        }
        upx_qsort(raw_bytes(relocs, 4 * relocnum), relocnum, 4, le32_compare);

        SPAN_S_VAR(byte, image, ibuf + 0, ih_imagesize);
        SPAN_S_VAR(byte, crel, ibuf + ih_imagesize, ibuf);
        relocsize = optimize_relocs(image, ih_imagesize, relocs, relocnum, crel, &has_9a);
        set_le16(crel + relocsize, relocsize + 2);
        relocsize += 2;
        assert(relocsize >= 11);
        if (relocsize > MAXRELOCSIZE) // optimize_relocs did not help
            throwCantPack("too many relocations");
#if TESTING && 0
        unsigned rout_len = MemBuffer::getSizeForCompression(relocsize);
        MemBuffer rout(rout_len);
        ucl_nrv2b_99_compress(raw_bytes(crel, relocsize), relocsize, rout, &rout_len, nullptr, 9,
                              nullptr, nullptr);
        printf("dos/exe reloc compress: %d -> %d\n", relocsize, rout_len);
#endif
    }

    // prepare packheader
    ph.u_len = ih_imagesize + relocsize;
    obuf.allocForCompression(ph.u_len);
    // prepare filter
    Filter ft(ph.level);
    // compress (max_match = 8192)
    upx_compress_config_t cconf;
    cconf.reset();
    cconf.conf_ucl.max_match = MAXMATCH;
    cconf.conf_lzma.max_num_probs = 1846 + (768 << 4); // ushort: ~28 KiB stack
    compressWithFilters(&ft, 32, &cconf);

    if (M_IS_NRV2B(ph.method) || M_IS_NRV2D(ph.method) || M_IS_NRV2E(ph.method))
        if (ph.max_run_found + ph.max_match_found > 0x8000)
            throwCantPack("decompressor limit exceeded, send a bugreport");

#if TESTING
    if (opt->debug.debug_level) {
        printf("image+relocs %d -> %d\n", ih_imagesize + relocsize, ph.c_len);
        printf("offsets: %d - %d\nmatches: %d - %d\nruns: %d - %d\n", 0 /*ph.min_offset_found*/,
               ph.max_offset_found, 0 /*ph.min_match_found*/, ph.max_match_found,
               0 /*ph.min_run_found*/, ph.max_run_found);
    }
#endif

    int flag = fillExeHeader(&oh);

    const unsigned lsize = getLoaderSize();
    MemBuffer loader(lsize);
    memcpy(loader, getLoader(), lsize);
    // OutputFile::dump("xxloader.dat", loader, lsize);

    // patch loader
    const unsigned packedsize = ph.c_len;
    const unsigned e_len = getLoaderSectionStart("EXECUTPO");
    const unsigned d_len = lsize - e_len;
    assert((e_len & 15) == 0);

    const unsigned copysize = (1 + packedsize + d_len) & ~1;
    const unsigned firstcopy = copysize % 0x10000 ? copysize % 0x10000 : 0x10000;

    // set oh.min & oh.max
    ic = ih.min * 16 + ih_imagesize;
    if (ic < oh.ss * 16u + oh.sp)
        ic = oh.ss * 16u + oh.sp;
    oh.min = (ic - (packedsize + lsize)) / 16;
    ic = oh.min + (ih.max - ih.min);
    oh.max = ic < 0xffff && ih.max != 0xffff ? ic : 0xffff;

    // set extra info
    byte extra_info[9];
    unsigned eisize = 0;
    if (oh.ss != ih.ss) {
        set_le16(extra_info + eisize, ih.ss);
        eisize += 2;
        assert((flag & SS) != 0); // set in fillExeHeader()
    }
    if (oh.sp != ih.sp) {
        set_le16(extra_info + eisize, ih.sp);
        eisize += 2;
        assert((flag & SP) != 0); // set in fillExeHeader()
    }
    if (ih.min != oh.min) {
        set_le16(extra_info + eisize, ih.min);
        eisize += 2;
        flag |= MINMEM;
    }
    if (ih.max != oh.max) {
        set_le16(extra_info + eisize, ih.max);
        eisize += 2;
        flag |= MAXMEM;
    }
    extra_info[eisize++] = (byte) flag;

    if (M_IS_NRV2B(ph.method) || M_IS_NRV2D(ph.method) || M_IS_NRV2E(ph.method))
        linker->defineSymbol("bx_magic", 0x7FFF + 0x10 * ((packedsize & 15) + 1));

    unsigned decompressor_entry = 1 + (packedsize & 15);
    if (M_IS_LZMA(ph.method))
        decompressor_entry = 0x10;
    linker->defineSymbol("decompressor_entry", decompressor_entry);

    // patch loader
    if (flag & USEJUMP) {
        // I use a relocation entry to set the original cs
        unsigned n = getLoaderSectionStart("EXEJUMPF") + 1;
        n += packedsize + 2;
        oh.relocs = 1;
        oh.firstreloc = (n & 0xf) + ((n >> 4) << 16);
    } else {
        oh.relocs = 0;
        oh.firstreloc = ih.cs * 0x10000 + ih.ip;
    }

    oh.relocoffs = offsetof(exe_header_t, firstreloc);

    linker->defineSymbol("destination_segment", oh.ss - ph.c_len / 16 - e_len / 16);
    linker->defineSymbol("source_segment", e_len / 16 + (copysize - firstcopy) / 16);
    linker->defineSymbol("copy_offset", firstcopy - 2);
    linker->defineSymbol("words_to_copy", firstcopy / 2);

    linker->defineSymbol("exe_stack_sp", oh.sp);
    linker->defineSymbol("exe_stack_ss", oh.ss);
    linker->defineSymbol("interrupt", get_le16(ibuf + 8));
    linker->defineSymbol("attribute", get_le16(ibuf + 4));
    linker->defineSymbol("orig_strategy", get_le16(ibuf + 6));

    const unsigned outputlen = sizeof(oh) + e_len + packedsize + d_len + eisize;
    oh.m512 = outputlen & 511;
    oh.p512 = (outputlen + 511) >> 9;

    const char *exeentry = M_IS_LZMA(ph.method) ? "LZMAENTRY" : "EXEENTRY";
    oh.ip = device_driver ? getLoaderSection(exeentry) - 2 : 0;

    defineDecompressorSymbols();
    relocateLoader();
    memcpy(loader, getLoader(), lsize);
    patchPackHeader(loader, e_len);

    NO_fprintf(stderr, "\ne_len=%x d_len=%x c_len=%x oo=%x ulen=%x copysize=%x imagesize=%x", e_len,
               d_len, packedsize, ph.overlap_overhead, ph.u_len, copysize, ih_imagesize);

    // write header + write loader + compressed file
#if TESTING
    if (opt->debug.debug_level)
        printf("\n%d %d %d %d\n", (int) sizeof(oh), e_len, packedsize, d_len);
#endif
    fo->write(&oh, sizeof(oh));       // program header
    fo->write(loader, e_len);         // entry code
    fo->write(obuf, packedsize);      // compressed data
    fo->write(loader + e_len, d_len); // decompressor code
    fo->write(extra_info, eisize);    // extra info for unpacking
    assert(eisize <= 9);
    NO_printf("%-13s: program hdr  : %8u bytes\n", getName(), usizeof(oh));
    NO_printf("%-13s: entry        : %8u bytes\n", getName(), e_len);
    NO_printf("%-13s: compressed   : %8u bytes\n", getName(), packedsize);
    NO_printf("%-13s: decompressor : %8u bytes\n", getName(), d_len);
    NO_printf("%-13s: extra info   : %8u bytes\n", getName(), eisize);

    // verify
    verifyOverlappingDecompression();

    // copy the overlay
    copyOverlay(fo, ih_overlay, obuf);
    NO_fprintf(stderr, "dos/exe %x %x\n", relocsize, ph.u_len);

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

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

tribool PackExe::canUnpack() {
    if (!readFileHeader())
        return false;
    const unsigned off = ih.headsize16 * 16;
    fi->seek(off, SEEK_SET);
    bool b = readPackHeader(4096);
    return b && (off + ph.c_len <= file_size_u);
}

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

void PackExe::unpack(OutputFile *fo) {
    ibuf.alloc(file_size);
    obuf.allocForDecompression(ph.u_len);

    // read the file
    fi->seek(ih.headsize16 * 16, SEEK_SET);
    fi->readx(ibuf, ih_imagesize);

    // get compressed data offset
    unsigned e_len = ph.buf_offset + ph.getPackHeaderSize();
    if (ih_imagesize <= e_len + ph.c_len)
        throwCantUnpack("file damaged");

    checkOverlay(ih_overlay);

    // decompress
    decompress(ibuf + e_len, obuf);

    unsigned imagesize = ih_imagesize;
    imagesize -= 1;
    const byte flag = ibuf[imagesize];

    // relocations
    unsigned relocnum = 0;
    SPAN_S_VAR(const byte, relocstart, obuf + ph.u_len, obuf);
    MemBuffer mb_relocs;
    SPAN_0_VAR(byte, relocs, nullptr);
    if (!(flag & NORELOC)) {
        mb_relocs.alloc(4 * MAXRELOCSIZE);
        relocs = SPAN_S_MAKE(byte, mb_relocs); // => now a SPAN_S

        relocsize = get_le16(obuf + ph.u_len - 2);
        ph.u_len -= 2;
        if (relocsize < 11 || relocsize > MAXRELOCSIZE || relocsize >= imagesize)
            throwCantUnpack("bad relocations");
        relocstart -= relocsize;

        // unoptimize_relocs
        unsigned ones = get_le16(relocstart);
        const unsigned seg_high = get_le16(relocstart + 2);
        SPAN_S_VAR(const byte, p, relocstart + 4);
        unsigned es = 0;
        while (ones) {
            unsigned di = get_le16(p);
            es += get_le16(p + 2);
            bool dorel = true;
            for (p += 4; ones && di < 0x10000; p++) {
                if (dorel) {
                    set_le16(relocs + (4 * relocnum + 0), di);
                    set_le16(relocs + (4 * relocnum + 2), es);
                    NO_printf("dos/exe unreloc %4d %6x\n", relocnum, es * 16 + di);
                    relocnum++;
                }
                dorel = true;
                if (*p == 0) {
                    SPAN_S_VAR(const byte, q, obuf + (es * 16 + di), obuf);
                    while (!(*q == 0x9a && get_le16(q + 3) <= seg_high))
                        q++;
                    di = ptr_udiff_bytes(q, obuf + (es * 16)) + 3;
                } else if (*p == 1) {
                    di += 254;
                    if (di < 0x10000)
                        ones--;
                    dorel = false;
                } else
                    di += *p;
            }
        }
    }

    // fill new exe header
    mem_clear(&oh);
    oh.ident = 'M' + 'Z' * 256;

    if (relocnum) {
        oh.relocs = relocnum;
        while (relocnum & 3) // paragraph align
            set_le32(relocs + (4 * relocnum++), 0);
    }

    unsigned outputlen = sizeof(oh) + 4 * relocnum + ptr_udiff_bytes(relocstart, obuf);
    oh.m512 = outputlen & 511;
    oh.p512 = (outputlen + 511) >> 9;
    oh.headsize16 = 2 + relocnum / 4;

    oh.max = ih.max;
    oh.min = ih.min;
    oh.sp = ih.sp;
    oh.ss = ih.ss;

    if (flag & MAXMEM) {
        imagesize -= 2;
        oh.max = get_le16(ibuf + imagesize);
    }
    if (flag & MINMEM) {
        imagesize -= 2;
        oh.min = get_le16(ibuf + imagesize);
    }
    if (flag & SP) {
        imagesize -= 2;
        oh.sp = get_le16(ibuf + imagesize);
    }
    if (flag & SS) {
        imagesize -= 2;
        oh.ss = get_le16(ibuf + imagesize);
    }

    unsigned ip = (flag & USEJUMP) ? get_le32(ibuf + imagesize - 4) : (unsigned) ih.firstreloc;
    oh.ip = ip & 0xffff;
    oh.cs = ip >> 16;

    oh.relocoffs = sizeof(oh);
    oh.firstreloc = 0;
    if (!fo)
        return;

    // write header + relocations + uncompressed file
    fo->write(&oh, sizeof(oh));
    if (relocnum)
        fo->write(relocs, 4 * relocnum);
    fo->write(obuf, ptr_udiff_bytes(relocstart, obuf));

    // copy the overlay
    copyOverlay(fo, ih_overlay, obuf);
}

/*

memory layout at decompression time
===================================

normal exe
----------

a, at load time

(e - copying code, C - compressed data, d - decompressor+relocator,
 x - not specified, U - uncompressed code+data, R uncompressed relocation)

eeCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCdddd
^ CS:0                                       ^ SS:0

b, after copying

xxxxxxxxxxxxxxxCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCdddd
^ES:DI=0       ^ DS:SI=0                     ^ CS=SS, IP in range 0..0xf

c, after uncompression

UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUURRdddd
                                           ^ ES:DI

device driver
-------------

the file has 2 entry points, CS:0 in device driver mode, and
CS:exe_as_device_entry in normal mode. the code in section DEVICEENTRY
sets up the same environment for section EXEENTRY, as it would see in normal
execution mode.

lzma uncompression for normal exes
----------------------------------

(n - nrv2b uncompressor, l - nrv2b compressed lzma + relocator code)

a, at load time

nneelllCCCCCCCCCCCCCCCCCCCCCCCCC

^ CS:0                                       ^ SS:0

b, after nrv2b

nneelllCCCCCCCCCCCCCCCCCCCCCCCCC             dddd
^ CS:0                                       ^ SS:0x10

after this, normal ee code runs

lzma + device driver
--------------------

(D - device driver adapter)

a, at load time

DDnneelllCCCCCCCCCCCCCCCCCCCCCCCCC

*/

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