/* p_mach.cpp -- pack Mach Object executable

   This file is part of the UPX executable compressor.

   Copyright (C) 2004-2006 John Reiser
   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.

   John Reiser
   jreiser@users.sourceforge.net
 */


#include "conf.h"

#include "file.h"
#include "filter.h"
#include "linker.h"
#include "packer.h"
#include "p_mach.h"
#include "ui.h"

static const
#include "stub/powerpc-darwin.macho-entry.h"
static const
#include "stub/powerpc-darwin.macho-fold.h"

PackMachPPC32::PackMachPPC32(InputFile *f) :
    super(f), n_segment(0), rawmseg(0), msegcmd(0)
{
}

PackMachPPC32::~PackMachPPC32()
{
    delete [] msegcmd;
    delete [] rawmseg;
}

const int *PackMachPPC32::getCompressionMethods(int /*method*/, int /*level*/) const
{
    // There really is no LE bias in M_NRV2E_LE32.
    static const int m_nrv2e[] = { M_NRV2E_LE32, M_END };
    return m_nrv2e;
}


const int *PackMachPPC32::getFilters() const
{
    static const int filters[] = { 0xd0, FT_END };
    return filters;
}

Linker *PackMachPPC32::newLinker() const
{
    return new ElfLinkerPpc32;
}

void
PackMachPPC32::addStubEntrySections(Filter const *)
{
    addLoader("MACOS000", NULL);
   //addLoader(getDecompressorSections(), NULL);
    addLoader(
        ( M_IS_NRV2E(ph.method) ? "NRV_HEAD,NRV2E,NRV_TAIL"
        : M_IS_NRV2D(ph.method) ? "NRV_HEAD,NRV2D,NRV_TAIL"
        : M_IS_NRV2B(ph.method) ? "NRV_HEAD,NRV2B,NRV_TAIL"
        : M_IS_LZMA(ph.method)  ? "LZMA_ELF00,LZMA_DEC20,LZMA_DEC30"
        : NULL), NULL);
    addLoader("ELFMAINY,IDENTSTR,+40,ELFMAINZ,FOLDEXEC", NULL);
}


void PackMachPPC32::defineSymbols(Filter const *)
{
    // empty
}


void
PackMachPPC32::buildMachLoader(
    upx_byte const *const proto,
    unsigned        const szproto,
    upx_byte const *const fold,
    unsigned        const szfold,
    Filter const *ft
)
{
    initLoader(proto, szproto);

    struct b_info h; memset(&h, 0, sizeof(h));
    unsigned fold_hdrlen = 0;
  if (0 < szfold) {
    h.sz_unc = (szfold < fold_hdrlen) ? 0 : (szfold - fold_hdrlen);
    h.b_method = (unsigned char) ph.method;
    h.b_ftid = (unsigned char) ph.filter;
    h.b_cto8 = (unsigned char) ph.filter_cto;
  }
    unsigned char const *const uncLoader = fold_hdrlen + fold;

    unsigned char *const cprLoader = new unsigned char[sizeof(h) + h.sz_unc];
  if (0 < szfold) {
    unsigned sz_cpr = 0;
    int r = upx_compress(uncLoader, h.sz_unc, sizeof(h) + cprLoader, &sz_cpr,
        NULL, ph.method, 10, NULL, NULL );
    h.sz_cpr = sz_cpr;
    if (r != UPX_E_OK || h.sz_cpr >= h.sz_unc)
        throwInternalError("loader compression failed");
  }
    memcpy(cprLoader, &h, sizeof(h));

    // This adds the definition to the "library", to be used later.
    linker->addSection("FOLDEXEC", cprLoader, sizeof(h) + h.sz_cpr, 0);
    delete [] cprLoader;

    int const GAP = 128;  // must match stub/l_mac_ppc.S
    segcmdo.vmsize += h.sz_unc - h.sz_cpr + GAP + 64;

    addStubEntrySections(ft);

    defineSymbols(ft);
    relocateLoader();
}

void
PackMachPPC32::buildLoader(const Filter *ft)
{
    buildMachLoader(
        stub_powerpc_darwin_macho_entry, sizeof(stub_powerpc_darwin_macho_entry),
        stub_powerpc_darwin_macho_fold,  sizeof(stub_powerpc_darwin_macho_fold),  ft );
}
void PackMachPPC32::patchLoader() { }
void PackMachPPC32::updateLoader(OutputFile *) {}

void
PackMachPPC32::patchLoaderChecksum()
{
    unsigned char *const ptr = getLoader();
    l_info *const lp = &linfo;
    // checksum for loader; also some PackHeader info
    lp->l_checksum = 0;
    lp->l_magic = UPX_MAGIC_LE32;  // LE32 always
    lp->l_lsize = (unsigned short) lsize;
    lp->l_version = (unsigned char) ph.version;
    lp->l_format  = (unsigned char) ph.format;
    // INFO: lp->l_checksum is currently unused
    lp->l_checksum = upx_adler32(ptr, lsize);
}

static int __acc_cdecl_qsort
compare_segment_command(void const *const aa, void const *const bb)
{
    Mach_segment_command const *const a = (Mach_segment_command const *)aa;
    Mach_segment_command const *const b = (Mach_segment_command const *)bb;
    unsigned const xa = a->cmd - Mach_segment_command::LC_SEGMENT;
    unsigned const xb = b->cmd - Mach_segment_command::LC_SEGMENT;
           if (xa < xb)        return -1;  // LC_SEGMENT first
           if (xa > xb)        return  1;
    if (a->vmaddr < b->vmaddr) return -1;  // ascending by .vmaddr
    if (a->vmaddr > b->vmaddr) return  1;
                               return  0;
}

void
PackMachPPC32::pack4(OutputFile *fo, Filter &ft)  // append PackHeader
{
    // offset of p_info in compressed file
    overlay_offset = sizeof(mhdro) + sizeof(segcmdo) + sizeof(threado) + sizeof(linfo);

    super::pack4(fo, ft);
    segcmdo.filesize = fo->getBytesWritten();
    segcmdo.vmsize += segcmdo.filesize;
    fo->seek(sizeof(mhdro), SEEK_SET);
    fo->write(&segcmdo, sizeof(segcmdo));
    fo->write(&threado, sizeof(threado));
    fo->write(&linfo, sizeof(linfo));
}

void
PackMachPPC32::pack3(OutputFile *fo, Filter &ft)  // append loader
{
    BE32 disp;
    unsigned const zero = 0;
    unsigned len = fo->getBytesWritten();
    fo->write(&zero, 3& -len);
    len += (3& -len) + sizeof(disp);
    set_be32(&disp, 4+ len - sz_mach_headers);  // 4: sizeof(instruction)
    fo->write(&disp, sizeof(disp));

    threado.state.srr0 = len + segcmdo.vmaddr;  /* entry address */
    super::pack3(fo, ft);
}

// Determine length of gap between PT_LOAD phdri[k] and closest PT_LOAD
// which follows in the file (or end-of-file).  Optimize for common case
// where the PT_LOAD are adjacent ascending by .p_offset.  Assume no overlap.

unsigned PackMachPPC32::find_SEGMENT_gap(
    unsigned const k
)
{
    if (Mach_segment_command::LC_SEGMENT!=msegcmd[k].cmd
    ||  0==msegcmd[k].filesize ) {
        return 0;
    }
    unsigned const hi = get_native32(&msegcmd[k].fileoff) +
                        get_native32(&msegcmd[k].filesize);
    unsigned lo = ph.u_file_size;
    unsigned j = k;
    for (;;) { // circular search, optimize for adjacent ascending
        ++j;
        if (n_segment==j) {
            j = 0;
        }
        if (k==j) {
            break;
        }
        if (Mach_segment_command::LC_SEGMENT==msegcmd[j].cmd
        &&  0!=msegcmd[j].filesize ) {
            unsigned const t = get_native32(&msegcmd[j].fileoff);
            if ((t - hi) < (lo - hi)) {
                lo = t;
                if (hi==lo) {
                    break;
                }
            }
        }
    }
    return lo - hi;
}

void
PackMachPPC32::pack2(OutputFile *fo, Filter &ft)  // append compressed body
{
    Extent x;
    unsigned k;

    // count passes, set ptload vars
    uip->ui_total_passes = 0;
    for (k = 0; k < n_segment; ++k) {
        if (Mach_segment_command::LC_SEGMENT==msegcmd[k].cmd
        &&  0!=msegcmd[k].filesize ) {
            uip->ui_total_passes++;
            if (find_SEGMENT_gap(k)) {
                uip->ui_total_passes++;
            }
        }
    }

    // compress extents
    unsigned total_in = 0;
    unsigned total_out = 0;

    unsigned hdr_u_len = mhdri.sizeofcmds;

    uip->ui_pass = 0;
    ft.addvalue = 0;

    int nx = 0;
    for (k = 0; k < n_segment; ++k)
    if (Mach_segment_command::LC_SEGMENT==msegcmd[k].cmd
    &&  0!=msegcmd[k].filesize ) {
        x.offset = msegcmd[k].fileoff;
        x.size   = msegcmd[k].filesize;
        if (0 == nx) { // 1st LC_SEGMENT must cover Mach_header at 0==fileoffset
            unsigned const delta = mhdri.sizeofcmds;
            x.offset    += delta;
            x.size      -= delta;
        }
        packExtent(x, total_in, total_out,
            ((Mach_segment_command::VM_PROT_EXECUTE & msegcmd[k].initprot)
                ? &ft : 0 ), fo, hdr_u_len );
        hdr_u_len = 0;
        ++nx;
    }
    for (k = 0; k < n_segment; ++k) {
        x.size = find_SEGMENT_gap(k);
        if (x.size) {
            x.offset = get_native32(&msegcmd[k].fileoff) +
                       get_native32(&msegcmd[k].filesize);
            packExtent(x, total_in, total_out, 0, fo);
        }
    }

    if ((off_t)total_in != file_size)
        throwEOFException();
    segcmdo.filesize = fo->getBytesWritten();
}

#undef PAGE_MASK
#undef PAGE_SIZE
#define PAGE_MASK (~0u<<12)
#define PAGE_SIZE -PAGE_MASK
void
PackMachPPC32::pack1(OutputFile *fo, Filter &/*ft*/)  // generate executable header
{
    mhdro = mhdri;
    mhdro.ncmds = 2;
    mhdro.sizeofcmds = sizeof(segcmdo) + sizeof(threado);
    mhdro.flags = Mach_header::MH_NOUNDEFS;
    fo->write(&mhdro, sizeof(mhdro));

    segcmdo.cmd = Mach_segment_command::LC_SEGMENT;
    segcmdo.cmdsize = sizeof(segcmdo);
    strncpy((char *)&segcmdo.segname, "__TEXT", sizeof(segcmdo.segname));
    segcmdo.vmaddr = PAGE_MASK & (~PAGE_MASK +
        msegcmd[n_segment -1].vmsize + msegcmd[n_segment -1].vmaddr );
    segcmdo.vmsize = 0;    // adjust later
    segcmdo.fileoff = 0;
    segcmdo.filesize = 0;  // adjust later
    segcmdo.initprot = segcmdo.maxprot =
        Mach_segment_command::VM_PROT_READ |
        Mach_segment_command::VM_PROT_WRITE |
        Mach_segment_command::VM_PROT_EXECUTE;
    segcmdo.nsects = 0;
    segcmdo.flags = 0;
    fo->write(&segcmdo, sizeof(segcmdo));

    threado.cmd = Mach_segment_command::LC_UNIXTHREAD;
    threado.cmdsize = sizeof(threado);
    threado.flavor = Mach_thread_command::PPC_THREAD_STATE;
    threado.count =  Mach_thread_command::PPC_THREAD_STATE_COUNT;
    memset(&threado.state, 0, sizeof(threado.state));
    fo->write(&threado, sizeof(threado));
    sz_mach_headers = fo->getBytesWritten();

    memset((char *)&linfo, 0, sizeof(linfo));
    fo->write(&linfo, sizeof(linfo));

    return;
}

void
PackMachPPC32::unpack(OutputFile *fo)
{
    fi->seek(overlay_offset, SEEK_SET);
    p_info hbuf;
    fi->readx(&hbuf, sizeof(hbuf));
    unsigned orig_file_size = get_native32(&hbuf.p_filesize);
    blocksize = get_native32(&hbuf.p_blocksize);
    if (file_size > (off_t)orig_file_size || blocksize > orig_file_size)
        throwCantUnpack("file header corrupted");

    ibuf.alloc(blocksize + OVERHEAD);
    b_info bhdr; memset(&bhdr, 0, sizeof(bhdr));
    fi->readx(&bhdr, sizeof(bhdr));
    ph.u_len = get_native32(&bhdr.sz_unc);
    ph.c_len = get_native32(&bhdr.sz_cpr);
    ph.filter_cto = bhdr.b_cto8;

    // Uncompress Macho headers
    fi->readx(ibuf, ph.c_len);
    Mach_header *const mhdr = (Mach_header *)new upx_byte[ph.u_len];
    decompress(ibuf, (upx_byte *)mhdr, false);
    unsigned const ncmds = mhdr->ncmds;

    msegcmd = new Mach_segment_command[ncmds];
    unsigned char *ptr = (unsigned char *)(1+mhdr);
    for (unsigned j= 0; j < ncmds; ++j) {
        msegcmd[j] = *(Mach_segment_command *)ptr;
        ptr += (unsigned) ((Mach_segment_command *)ptr)->cmdsize;
    }

    // Put LC_SEGMENT together at the beginning, ascending by .vmaddr.
    qsort(msegcmd, ncmds, sizeof(*msegcmd), compare_segment_command);
    n_segment = 0;
    for (unsigned j= 0; j < ncmds; ++j) {
        n_segment += (Mach_segment_command::LC_SEGMENT==msegcmd[j].cmd);
    }

    unsigned total_in = 0;
    unsigned total_out = 0;
    unsigned c_adler = upx_adler32(NULL, 0);
    unsigned u_adler = upx_adler32(NULL, 0);
    Mach_segment_command const *sc = (Mach_segment_command const *)(1+ mhdr);
    unsigned k;

    fi->seek(- (off_t)(sizeof(bhdr) + ph.c_len), SEEK_CUR);
    for (
        k = 0;
        k < ncmds;
        (++k), (sc = (Mach_segment_command const *)(sc->cmdsize + (char const *)sc))
    ) {
        if (Mach_segment_command::LC_SEGMENT==sc->cmd
        &&  0!=sc->filesize ) {
            unsigned filesize = get_be32(&sc->filesize);
            unpackExtent(filesize, fo, total_in, total_out, c_adler, u_adler, false, sizeof(bhdr));
        }
    }
    for (unsigned j = 0; j < ncmds; ++j) {
        unsigned const size = find_SEGMENT_gap(j);
        if (size) {
            unsigned const where = get_native32(&msegcmd[k].fileoff) +
                                   get_native32(&msegcmd[k].filesize);
            if (fo)
                fo->seek(where, SEEK_SET);
            unpackExtent(size, fo, total_in, total_out,
                c_adler, u_adler, false, sizeof(bhdr));
        }
    }
}


bool
PackMachPPC32::canPack()
{
    fi->seek(0, SEEK_SET);
    fi->readx(&mhdri, sizeof(mhdri));

    if (Mach_header::MH_MAGIC         !=mhdri.magic
    ||  Mach_header::CPU_TYPE_POWERPC !=mhdri.cputype
    ||  Mach_header::MH_EXECUTE       !=mhdri.filetype
    )
        return false;

    rawmseg = (Mach_segment_command *)new char[mhdri.sizeofcmds];
    fi->readx(rawmseg, mhdri.sizeofcmds);

    msegcmd = new Mach_segment_command[mhdri.ncmds];
    unsigned char *ptr = (unsigned char *)rawmseg;
    for (unsigned j= 0; j < mhdri.ncmds; ++j) {
        msegcmd[j] = *(Mach_segment_command *)ptr;
        ptr += (unsigned) ((Mach_segment_command *)ptr)->cmdsize;
    }


    // Put LC_SEGMENT together at the beginning, ascending by .vmaddr.
    qsort(msegcmd, mhdri.ncmds, sizeof(*msegcmd), compare_segment_command);

    // Check that LC_SEGMENTs form one contiguous chunk of the file.
    for (unsigned j= 0; j < mhdri.ncmds; ++j) {
        if (Mach_segment_command::LC_SEGMENT==msegcmd[j].cmd) {
            if (0xfff & (msegcmd[j].fileoff | msegcmd[j].vmaddr)) {
                return false;
            }
            if (0 < j) {
                unsigned const sz = ~0xfff
                                  & (0xfff + msegcmd[j-1].filesize);
                if ((sz + msegcmd[j-1].fileoff)!=msegcmd[j].fileoff) {
                    return false;
                }
            }
            ++n_segment;
            sz_segment = msegcmd[j].filesize + msegcmd[j].fileoff - msegcmd[0].fileoff;
        }
    }
    // set options
    opt->o_unix.blocksize = file_size;
    return 0 < n_segment;
}

/*
vi:ts=4:et
*/
