/* packer_c.cpp -- Packer compression handling

   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 "packer.h"
#include "linker.h"

/*************************************************************************
// compression method util
**************************************************************************/

/*static*/ bool Packer::isValidCompressionMethod(int m) {
    return M_IS_LZMA(m) || M_IS_NRV2B(m) || M_IS_NRV2D(m) || M_IS_NRV2E(m);
}

const int *Packer::getDefaultCompressionMethods_8(int method, int level, int small) const {
#define M_LZMA_003 (M_LZMA | 0x00300)
#define M_LZMA_407 (M_LZMA | 0x40700)
    static const int m_all[] = {M_NRV2B_8,     M_NRV2D_8,  M_NRV2E_8,  M_LZMA,
                                M_ULTRA_BRUTE, M_LZMA_003, M_LZMA_407, M_END};
    // static const int m_cl1b[]  = { M_CL1B_8, M_END };
    static const int m_lzma[] = {M_LZMA, M_END};
    static const int m_nrv2b[] = {M_NRV2B_8, M_END};
    static const int m_nrv2d[] = {M_NRV2D_8, M_END};
    static const int m_nrv2e[] = {M_NRV2E_8, M_END};

    if (method == M_ALL)
        return m_all;
    // if (M_IS_CL1B(method))  return m_cl1b;
    if (M_IS_LZMA(method))
        return m_lzma;
    if (M_IS_NRV2B(method))
        return m_nrv2b;
    if (M_IS_NRV2D(method))
        return m_nrv2d;
    if (M_IS_NRV2E(method))
        return m_nrv2e;
    if (small < 0)
        small = file_size <= 512 * 1024;
    if (level == 1 || small)
        return m_nrv2b;
    return m_nrv2e;
}

const int *Packer::getDefaultCompressionMethods_le32(int method, int level, int small) const {
    static const int m_all[] = {M_NRV2B_LE32,  M_NRV2D_LE32, M_NRV2E_LE32, M_LZMA,
                                M_ULTRA_BRUTE, M_LZMA_003,   M_LZMA_407,   M_END};
    // static const int m_cl1b[]  = { M_CL1B_LE32, M_END };
    static const int m_lzma[] = {M_LZMA, M_END};
    static const int m_nrv2b[] = {M_NRV2B_LE32, M_END};
    static const int m_nrv2d[] = {M_NRV2D_LE32, M_END};
    static const int m_nrv2e[] = {M_NRV2E_LE32, M_END};

    if (method == M_ALL)
        return m_all;
    // if (M_IS_CL1B(method))  return m_cl1b;
    if (M_IS_LZMA(method))
        return m_lzma;
    if (M_IS_NRV2B(method))
        return m_nrv2b;
    if (M_IS_NRV2D(method))
        return m_nrv2d;
    if (M_IS_NRV2E(method))
        return m_nrv2e;
    if (small < 0)
        small = file_size <= 512 * 1024;
    if (level == 1 || small)
        return m_nrv2b;
    return m_nrv2e;
}

/*************************************************************************
// loader util
**************************************************************************/

static bool callsManyTimes(int format) {
    // clang-format off
    return
        UPX_F_LINUX_ELF_i386   ==format
    ||  UPX_F_LINUX_ELFI_i386  ==format
    ||  UPX_F_LINUX_ELF64_AMD64==format
    ||  UPX_F_LINUX_ELF32_ARM  ==format
    ||  UPX_F_LINUX_ELF32_PPC32   ==format
    ||  UPX_F_LINUX_ELF64_PPC64LE ==format
    ||  UPX_F_LINUX_ELF32_ARMEB==format
    ||  UPX_F_BSD_ELF_i386     ==format
    ||  UPX_F_VMLINUX_ARM      ==format
    ||  UPX_F_VMLINUX_ARMEB    ==format
    ||  UPX_F_VMLINUX_PPC32    ==format
    ||  UPX_F_VMLINUX_PPC64LE  ==format
    ||  UPX_F_VMLINUZ_ARM      ==format
    ||  UPX_F_MACH_PPC32       ==format
    ||  UPX_F_MACH_PPC64       ==format
    ||  UPX_F_MACH_i386        ==format
    ||  UPX_F_DYLIB_i386       ==format
    ;
    // clang-format on
}

const char *Packer::getDecompressorSections() const {
    // clang-format off
    static const char nrv2b_le32_small[] =
        "N2BSMA10,N2BDEC10,N2BSMA20,N2BDEC20,N2BSMA30,"
        "N2BDEC30,N2BSMA40,N2BSMA50,N2BDEC50,N2BSMA60,"
        "N2BDEC60";
    static const char nrv2b_le32_fast[] =
        "N2BFAS10,+80CXXXX,N2BFAS11,N2BDEC10,N2BFAS20,"
        "N2BDEC20,N2BFAS30,N2BDEC30,N2BFAS40,N2BFAS50,"
        "N2BDEC50,N2BFAS60,+40CXXXX,N2BFAS61,N2BDEC60";
    static const char nrv2d_le32_small[] =
        "N2DSMA10,N2DDEC10,N2DSMA20,N2DDEC20,N2DSMA30,"
        "N2DDEC30,N2DSMA40,N2DSMA50,N2DDEC50,N2DSMA60,"
        "N2DDEC60";
    static const char nrv2d_le32_fast[] =
        "N2DFAS10,+80CXXXX,N2DFAS11,N2DDEC10,N2DFAS20,"
        "N2DDEC20,N2DFAS30,N2DDEC30,N2DFAS40,N2DFAS50,"
        "N2DDEC50,N2DFAS60,+40CXXXX,N2DFAS61,N2DDEC60";
    static const char nrv2e_le32_small[] =
        "N2ESMA10,N2EDEC10,N2ESMA20,N2EDEC20,N2ESMA30,"
        "N2EDEC30,N2ESMA40,N2ESMA50,N2EDEC50,N2ESMA60,"
        "N2EDEC60";
    static const char nrv2e_le32_fast[] =
        "N2EFAS10,+80CXXXX,N2EFAS11,N2EDEC10,N2EFAS20,"
        "N2EDEC20,N2EFAS30,N2EDEC30,N2EFAS40,N2EFAS50,"
        "N2EDEC50,N2EFAS60,+40CXXXX,N2EFAS61,N2EDEC60";
#if 0
    static const char cl1b_le32_small[] =
        "CL1ENTER,CL1SMA10,CL1RLOAD,"
        "CL1WID01,CL1SMA1B,"
        "CL1WID02,CL1SMA1B,"
        "CL1WID03,CL1SMA1B,"
        "CL1WID04,CL1SMA1B,"
        "CL1WID05,CL1SMA1B,"
        "CL1WID06,CL1SMA1B,"
        "CL1WID07,CL1SMA1B,"
        "CL1WID08,CL1SMA1B,"
        "CL1WID09,CL1SMA1B,"
        "CL1WID10,"
        "CL1START,"
        "CL1TOP00,CL1SMA1B,"
        "CL1TOP01,CL1SMA1B,"
        "CL1TOP02,CL1SMA1B,"
        "CL1TOP03,CL1SMA1B,"
        "CL1TOP04,CL1SMA1B,"
        "CL1TOP05,CL1SMA1B,"
        "CL1TOP06,CL1SMA1B,"
        "CL1TOP07,CL1SMA1B,"
        "CL1OFF01,CL1SMA1B,"
        "CL1OFF02,CL1SMA1B,"
        "CL1OFF03,CL1SMA1B,"
        "CL1OFF04,"
        "CL1LEN00,CL1SMA1B,"
        "CL1LEN01,CL1SMA1B,"
        "CL1LEN02,"
        "CL1COPY0";
    static const char cl1b_le32_fast[] =
        "CL1ENTER,"          "CL1RLOAD,"
        "CL1WID01,CL1FAS1B,"
        "CL1WID02,CL1FAS1B,"
        "CL1WID03,CL1FAS1B,"
        "CL1WID04,CL1FAS1B,"
        "CL1WID05,CL1FAS1B,"
        "CL1WID06,CL1FAS1B,"
        "CL1WID07,CL1FAS1B,"
        "CL1WID08,CL1FAS1B,"
        "CL1WID09,CL1FAS1B,"
        "CL1WID10,"
        "CL1START,"
        "CL1TOP00,CL1FAS1B,"
        "CL1TOP01,CL1FAS1B,"
        "CL1TOP02,CL1FAS1B,"
        "CL1TOP03,CL1FAS1B,"
        "CL1TOP04,CL1FAS1B,"
        "CL1TOP05,CL1FAS1B,"
        "CL1TOP06,CL1FAS1B,"
        "CL1TOP07,CL1FAS1B,"
        "CL1OFF01,CL1FAS1B,"
        "CL1OFF02,CL1FAS1B,"
        "CL1OFF03,CL1FAS1B,"
        "CL1OFF04,"
        "CL1LEN00,CL1FAS1B,"
        "CL1LEN01,CL1FAS1B,"
        "CL1LEN02,"
        "CL1COPY0";
#endif
    static const char lzma_small[] =
        "LZMA_DEC00,LZMA_DEC10,LZMA_DEC30";
    static const char lzma_fast[] =
        "LZMA_DEC00,LZMA_DEC20,LZMA_DEC30";
    static const char lzma_elf_small[] =
        "LZMA_ELF00,LZMA_DEC10,LZMA_DEC30";
    static const char lzma_elf_fast[] =
        "LZMA_ELF00,LZMA_DEC20,LZMA_DEC30";
    // clang-format on

    const unsigned method = ph_forced_method(ph.method);
    if (method == M_NRV2B_LE32)
        return opt->small ? nrv2b_le32_small : nrv2b_le32_fast;
    if (method == M_NRV2D_LE32)
        return opt->small ? nrv2d_le32_small : nrv2d_le32_fast;
    if (method == M_NRV2E_LE32)
        return opt->small ? nrv2e_le32_small : nrv2e_le32_fast;
    //    if (method == M_CL1B_LE32)
    //        return opt->small ? cl1b_le32_small  : cl1b_le32_fast;
    if (M_IS_LZMA(method)) {
        if (callsManyTimes(ph.format)) {
            return opt->small ? lzma_elf_small : lzma_elf_fast;
        }
        return opt->small ? lzma_small : lzma_fast;
    }
    throwInternalError("bad decompressor");
    return nullptr;
}

unsigned Packer::getDecompressorWrkmemSize() const {
    unsigned size = 0;
    if (M_IS_LZMA(ph.method)) {
        const lzma_compress_result_t *res = &ph.compress_result.result_lzma;
        // FIXME - this is for i386 only
        size = 8 + 4 + ALIGN_UP(2 * res->num_probs, 4u);
        size = ALIGN_UP(size, 16u);
    }
    assert((int) size >= 0);
    return size;
}

void Packer::defineDecompressorSymbols() {
    if (callsManyTimes(ph.format)) {
        // ELF calls the decompressor many times; the parameters change!
        return;
    }
    if (M_IS_LZMA(ph.method)) {
        const lzma_compress_result_t *res = &ph.compress_result.result_lzma;
        upx_uint32_t properties = // lc, lp, pb, dummy
            (res->lit_context_bits << 0) | (res->lit_pos_bits << 8) | (res->pos_bits << 16);
        if (bele->isBE()) // big endian - bswap32
            properties = bswap32(properties);

        linker->defineSymbol("lzma_properties", properties);
        // len - 2 because of properties
        if (linker->findSymbol("lzma_c_len", false)) {
            linker->defineSymbol("lzma_c_len", ph.c_len - 2);
        }
        if (linker->findSymbol("lzma_u_len", false)) {
            linker->defineSymbol("lzma_u_len", ph.u_len);
        }
        unsigned stack = getDecompressorWrkmemSize();
        linker->defineSymbol("lzma_stack_adjust", 0u - stack);

        if (ph.format == UPX_F_DOS_EXE) {
            linker->defineSymbol("lzma_properties_hi", properties >> 16); // pb
            linker->defineSymbol("lzma_c_len_hi", (ph.c_len - 2) >> 16);
            linker->defineSymbol("lzma_u_len_hi", ph.u_len >> 16);
            linker->defineSymbol("lzma_u_len_segment", (ph.u_len & 0xf0000) >> 4);
        }
    }
}

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