/*
kzip.exe compression wrapper (exe->library) 0.1
by Luigi Auriemma
e-mail: aluigi@autistici.org
web:    aluigi.org

  int kzip(                     // return the size of the compressed data or -1 if error
    unsigned char *in,          // the decompressed buffer (input)
    int insz,                   // decompressed size
    unsigned char *const_out,   // the buffer which will contain the compressed data (output)
    int const_outsz,            // size of the output buffer
    int zlib                    // 0 for deflate, 1 for zlib
  );

this function/wrapper allows to use the greap compression offered by
the kzip tool written by Ken Silverman (http://advsys.net/ken/utils.htm)
as a library internally in any Windows program.
the last "zlib" argument must be 0 for the deflate compression or 1
for the zlib one (header + deflate + crc).

the output buffer must be allocated using the following macro:
#define MAXZIPLEN(n) ((n)+(((n)/1000)+1)+12)

so:
  outsz = MAXZIPLEN(insz);
  out   = malloc(outsz);

my function uses some internal functions available in QuickBMS
(http://quickbms.aluigi.org) src/utils.h, they are very simple:
    mymemmem: just the memmem() available on Unix
    quickbms_tmpname: needed to create a temporary file
    mydump: create a file containing desired data (a dump)
*/ 

// kzip.exe is recreated all the times because I don't want to leave
// temporary files on the disk while the input and output files
// are just pipes which means:
// - no disk usage
// - faster... a lot faster judging from my tests
// the memory usage is the only problem at the moment because obviously
// it's double for both input and output



#ifdef WIN32    // Windows only, Linux maybe via wine
#include <process.h>



void *mymemmem(const void *b1, const void *b2, size_t len1, size_t len2);
u8 *quickbms_tmpname(u8 **fname, u8 *prefix, u8 *ext);
u32 mydump(u8 *fname, u8 *data, u32 size);


/*
HANDLE WINAPI kzip_FindFirstFile(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData) {
    int             i;
    unsigned char   *p;

    // memset
    p = (void *)lpFindFileData;
    for(i = 0; i < sizeof(WIN32_FIND_DATA); i++) {
        p[i] = 0;
    }
    lpFindFileData->dwFileAttributes    = FILE_ATTRIBUTE_NORMAL;
    lpFindFileData->nFileSizeLow        = 0xdeadbeef;   // sign

    // strrchr
    for(p = (void *)lpFileName; *p; p++);
    for(--p; (void *)p >= (void *)lpFileName; p--) {
        if((*p == '\\') || (*p == '/')) {
            p++;
            break;
        }
    }
    // strncpy
    for(i = 0; p[i] && (i < (MAX_PATH - 1)); i++) {
        lpFindFileData->cFileName[i] = p[i];
    }
    lpFindFileData->cFileName[i] = 0;
    return((HANDLE)0xdeadc0de); // different than INVALID_HANDLE_VALUE
    // yeah or the compiler will kill the kzip_exe_patch - kzip_FindFirstFile solution
}
*/

// source code above... I'm obligated in using it this way otherwise
// the C compiler will add stack protections and other stuff
static u8 kzip_FindFirstFile[] =
    "\x55\x89\xe5\x53\x8b\x55\x08\x8b\x4d\x0c\x31\xc0\xc6\x04\x01\x00"
    "\x40\x3d\x40\x01\x00\x00\x75\xf4\xc7\x01\x80\x00\x00\x00\xc7\x41"
    "\x20\xef\xbe\xad\xde\x89\xd0\xeb\x01\x40\x80\x38\x00\x75\xfa\xeb"
    "\x0f\x8a\x18\x80\xfb\x2f\x74\x05\x80\xfb\x5c\x75\x03\x40\xeb\x05"
    "\x48\x39\xd0\x73\xec\x31\xd2\xeb\x05\x88\x5c\x11\x2c\x42\x8a\x1c"
    "\x10\x81\xfa\x02\x01\x00\x00\x7f\x04\x84\xdb\x75\xec\xc6\x44\x11"
    "\x2c\x00\xb8\xde\xc0\xad\xde\x5b\xc9\xc2\x08\x00"
    "\xcc";



// yeah I know that after this modification kzip_exe remains
// modified but it shouldn't affect the program
int kzip_exe_patch(int insz) {
    u32     findme,
            len,
            func_len,
            kzip_exe_path_offset = 0x4000;  // 0x24d
    void    *filesize;

    /* can't be used due to the compiler's optimizations
    findme   = 0xdeadc0de;
    filesize = mymemmem(kzip_FindFirstFile, (void *)&findme, 1024, 4);
    if(!filesize) return(-1);
    filesize += 32; // because the compiler may have added more operations
    func_len = filesize - (void *)kzip_FindFirstFile;
    */

    func_len = sizeof(kzip_FindFirstFile);
    if(kzip_exe_path_offset == 0x24d) { // where is located the help
        if(func_len > 0x9e) kzip_exe_path_offset = 0x4000;
    }

    findme   = 0xdeadbeef;
    filesize = mymemmem(kzip_FindFirstFile, (void *)&findme, func_len, 4);
    if(!filesize) return(-1);
    len      = filesize - (void *)kzip_FindFirstFile;

    memcpy(kzip_exe + kzip_exe_path_offset, kzip_FindFirstFile, func_len);
    *(u32 *)(kzip_exe + kzip_exe_path_offset + len) = insz;

    kzip_exe[0x3265] = 0x90;
    kzip_exe[0x3266] = 0xe8;
    *(u32 *)(kzip_exe + 0x3267) = kzip_exe_path_offset - (0x3267 + 4);
    memset(kzip_exe + 0x340c, 0x90, 1 + 1 + 6 + 2 + 6 + 1 + 6);
    kzip_exe[0x3232] = '\\';
    return(0);
}



typedef struct {
    HANDLE  pipe;
    u8      *data;
    int     size;
} kzip_pipe_t;

DWORD WINAPI kzip_in_pipe(void *arg) {
    kzip_pipe_t *kzip_pipe;
    DWORD   len;

    kzip_pipe = (kzip_pipe_t *)arg;
    while(ConnectNamedPipe(kzip_pipe->pipe, NULL)) {
        while(WriteFile(kzip_pipe->pipe, kzip_pipe->data, kzip_pipe->size, &len, NULL)) {
            FlushFileBuffers(kzip_pipe->pipe);
        }
        DisconnectNamedPipe(kzip_pipe->pipe);
    }
    return(0);
}



int kzip(u8 *in, int insz, u8 *const_out, int const_outsz, int zlib) {
    int     size    = -1;
    intptr_t    pid;
    kzip_pipe_t kzip_pipe;
    HANDLE  in_pipe  = INVALID_HANDLE_VALUE,
            out_pipe = INVALID_HANDLE_VALUE,
            tid      = NULL;    // CreateThread wants NULL
    DWORD   len;
    u32     tmp,
            crc,
            rest_size;
    int     outsz,
            call_kzip_old = 1;
    u8      head[0x1e],
            rest[4 + 4 + 4],
            *exe_fname  = NULL, // NO static because the exe must be recreated all the times with correct size!
            *in_fname   = NULL,
            *out_fname  = NULL,
            *out        = NULL;

    out   = const_out;
    outsz = const_outsz;
    if(zlib) {
        outsz -= (1 + 1 + 4);
        *out++ = 0x78;  // CM, CINFO
        *out++ = 0x5e;  // FCHECK, FDICT, FLEVEL
    }

    if(kzip_exe_patch(insz) < 0) goto quit;
    quickbms_tmpname(&exe_fname, NULL, "exe");
    mydump(exe_fname, (void *)kzip_exe, sizeof(kzip_exe));

    quickbms_tmpname(&in_fname, "\\\\.\\pipe", "tmp");
    in_pipe = CreateNamedPipe(
        in_fname,
        PIPE_ACCESS_OUTBOUND,
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
        PIPE_UNLIMITED_INSTANCES,
        insz,
        insz,
        NMPWAIT_USE_DEFAULT_WAIT,
        NULL);
    if(in_pipe == INVALID_HANDLE_VALUE) goto quit;

    quickbms_tmpname(&out_fname, "\\\\.\\pipe", "zip");
    out_pipe = CreateNamedPipe(
        out_fname,
        PIPE_ACCESS_INBOUND,
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
        PIPE_UNLIMITED_INSTANCES,
        outsz,
        outsz,
        NMPWAIT_USE_DEFAULT_WAIT,
        NULL);
    if(out_pipe == INVALID_HANDLE_VALUE) goto quit;

    kzip_pipe.pipe = in_pipe;
    kzip_pipe.data = in;
    kzip_pipe.size = insz;
    tid = CreateThread(NULL, 0, kzip_in_pipe, (void *)&kzip_pipe, 0, &len);
    if(!tid) goto quit;

    pid = spawnl(
        P_NOWAIT,
        exe_fname,
        exe_fname,
        "/y",   // force overwrite
        "/q",   // quiet mode
        out_fname,
        in_fname,
        NULL);
    if(pid < 0) goto quit;

    if(!ConnectNamedPipe(out_pipe, NULL)) goto quit;

    call_kzip_old = 0;
    size      = -1;
    rest_size = 0;
    for(;;) {
        // PK header
        if(!ReadFile(out_pipe, head, sizeof(head), &len, NULL) && (GetLastError() != ERROR_MORE_DATA)) break;

        if(!memcmp(head, "PK\x01\x02", 4)) {
            if(size == rest_size) {
                if(zlib) {
                    crc = (u32)adler32(0, Z_NULL, 0);
                    crc = (u32)adler32(crc, in, insz);
                    out[size++] = crc >> 24;
                    out[size++] = crc >> 16;
                    out[size++] = crc >> 8;
                    out[size++] = crc;
                    size++; // CM, CINFO
                    size++; // FCHECK, FDICT, FLEVEL
                }
            } else {
                size = -1;
            }
            break;
        }

        // name
        tmp = head[0x1a] | (head[0x1b]<<8) | (head[0x1c]<<16) | (head[0x1d]<<24);
        if((int)tmp > outsz) break;
        if(!ReadFile(out_pipe, out, tmp, &len, NULL) && (GetLastError() != ERROR_MORE_DATA)) break;

        // compressed data, skip the one too big for the buffer
        for(tmp = 0;;) {
            if(ReadFile(out_pipe, out, outsz, &len, NULL)) break;
            if(GetLastError() != ERROR_MORE_DATA) goto quit;
            tmp++;
        }
        if(!tmp) size = len;
        else     size = -1;

        // crc, compressed and uncompressed size
        if(!ReadFile(out_pipe, rest, sizeof(rest), &len, NULL)) break;
        rest_size = rest[4] | (rest[5]<<8) | (rest[6]<<16) | (rest[7]<<24);
    }

quit:
    if(tid) TerminateThread(tid, 0);
    if(out_pipe != INVALID_HANDLE_VALUE) {
        DisconnectNamedPipe(out_pipe);
        CloseHandle(out_pipe);
    }
    if(in_pipe != INVALID_HANDLE_VALUE) {
        DisconnectNamedPipe(in_pipe);
        CloseHandle(in_pipe);
    }
    /* doesn't work as I expect, the exe is still there
    if(pid > 0) {
        TerminateProcess((HANDLE)pid, -9);
        while(GetExitCodeProcess((HANDLE)pid, &len) && (len == STILL_ACTIVE)) {
            Sleep(10);
        }
    } */
    while(unlink(exe_fname)) Sleep(10);
    FREEZ(out_fname)
    FREEZ(in_fname)
    FREEZ(exe_fname)
    if(call_kzip_old) {
        printf("- some problems with the optimized kzip, switching to kzip_old\n");
        return(kzip_old(in, insz, out, outsz, zlib));
    }
    return(size);
}


#else
int kzip(u8 *in, int insz, u8 *const_out, int const_outsz, int zlib) {
    return(-1);
}
#endif

