/* packer_c.cpp -- Packer compression handling

   This file is part of the UPX executable compressor.

   Copyright (C) 1996-2018 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 1996-2018 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"
//#include "filter.h"


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

bool Packer::isValidCompressionMethod(int method)
{
    if (M_IS_LZMA(method)) {
#if !(WITH_LZMA)
        assert(0 && "Internal error - LZMA not compiled in");
#else
        return true;
#endif
    }
    return (method >= M_NRV2B_LE32 && method <= M_LZMA);
}


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
**************************************************************************/

const char *Packer::getDecompressorSections() const
{
    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";

    if (ph.method == M_NRV2B_LE32)
        return opt->small ? nrv2b_le32_small : nrv2b_le32_fast;
    if (ph.method == M_NRV2D_LE32)
        return opt->small ? nrv2d_le32_small : nrv2d_le32_fast;
    if (ph.method == M_NRV2E_LE32)
        return opt->small ? nrv2e_le32_small : nrv2e_le32_fast;
//    if (ph.method == M_CL1B_LE32)
//        return opt->small ? cl1b_le32_small  : cl1b_le32_fast;
    if (M_IS_LZMA(ph.method)) {
        if (UPX_F_LINUX_ELF_i386   ==ph.format
        ||  UPX_F_LINUX_ELFI_i386  ==ph.format
        ||  UPX_F_LINUX_ELF64_AMD  ==ph.format
        ||  UPX_F_LINUX_ELF32_ARMEL==ph.format
        ||  UPX_F_LINUX_ELFPPC32   ==ph.format
        ||  UPX_F_LINUX_ELFPPC64LE ==ph.format
        ||  UPX_F_LINUX_ELF32_ARMEB==ph.format
        ||  UPX_F_BSD_ELF_i386     ==ph.format
        ||  UPX_F_VMLINUZ_ARMEL    ==ph.format
        ||  UPX_F_VMLINUX_ARMEL    ==ph.format
        ||  UPX_F_VMLINUX_ARMEB    ==ph.format
        ||  UPX_F_VMLINUX_PPC32    ==ph.format
        ||  UPX_F_VMLINUX_PPC64LE  ==ph.format
        ||  UPX_F_MACH_PPC32       ==ph.format
        ||  UPX_F_MACH_PPC64LE     ==ph.format
        ||  UPX_F_MACH_i386        ==ph.format
        ||  UPX_F_DYLIB_i386       ==ph.format
    ) {
            return opt->small ? lzma_elf_small  : lzma_elf_fast;
        }
        return opt->small ? lzma_small  : lzma_fast;
    }
    throwInternalError("bad decompressor");
    return NULL;
}


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 (UPX_F_LINUX_ELF_i386   ==ph.format
    ||  UPX_F_LINUX_ELFI_i386  ==ph.format
    ||  UPX_F_LINUX_ELF64_AMD  ==ph.format
    ||  UPX_F_LINUX_ELF32_ARMEL==ph.format
    ||  UPX_F_LINUX_ELFPPC32   ==ph.format
    ||  UPX_F_LINUX_ELFPPC64LE ==ph.format
    ||  UPX_F_LINUX_ELF32_ARMEB==ph.format
    ||  UPX_F_BSD_ELF_i386     ==ph.format
    ||  UPX_F_VMLINUZ_ARMEL    ==ph.format
    ||  UPX_F_VMLINUX_ARMEL    ==ph.format
    ||  UPX_F_VMLINUX_ARMEB    ==ph.format
    ||  UPX_F_VMLINUX_PPC32    ==ph.format
    ||  UPX_F_VMLINUX_PPC64LE  ==ph.format
    ||  UPX_F_MACH_PPC32       ==ph.format
    ||  UPX_F_MACH_PPC64LE     ==ph.format
    ||  UPX_F_MACH_i386        ==ph.format
    ||  UPX_F_DYLIB_i386       ==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 (linker->bele->isBE()) // big endian - bswap32
            acc_swab32s(&properties);

        linker->defineSymbol("lzma_properties", properties);
        // len - 2 because of properties
        linker->defineSymbol("lzma_c_len", ph.c_len - 2);
        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: */
