[PATCH 1/3] mspatcha: Implement LZXD decompression and PA19 file parsing.

Conor McCarthy conor.mccarthy.444 at gmail.com
Sat Apr 20 10:20:46 CDT 2019


Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=12501
Signed-off-by: Conor McCarthy <conor.mccarthy.444 at gmail.com>
---
 dlls/mspatcha/Makefile.in |    4 +-
 dlls/mspatcha/lzxd_dec.c  |  765 +++++++++++++++++++++++++++++++++
 dlls/mspatcha/lzxd_dec.h  |   27 ++
 dlls/mspatcha/pa19.c      | 1031 +++++++++++++++++++++++++++++++++++++++++++++
 dlls/mspatcha/pa19.h      |   52 +++
 include/patchapi.h        |   73 ++++
 6 files changed, 1951 insertions(+), 1 deletion(-)
 create mode 100755 dlls/mspatcha/lzxd_dec.c
 create mode 100755 dlls/mspatcha/lzxd_dec.h
 create mode 100755 dlls/mspatcha/pa19.c
 create mode 100755 dlls/mspatcha/pa19.h

diff --git a/dlls/mspatcha/Makefile.in b/dlls/mspatcha/Makefile.in
index bd0da7d..34cf719 100644
--- a/dlls/mspatcha/Makefile.in
+++ b/dlls/mspatcha/Makefile.in
@@ -2,6 +2,8 @@ MODULE    = mspatcha.dll
 IMPORTLIB = mspatcha
 
 C_SRCS = \
-	mspatcha_main.c
+	mspatcha_main.c \
+	pa19.c \
+	lzxd_dec.c
 
 RC_SRCS = version.rc
diff --git a/dlls/mspatcha/lzxd_dec.c b/dlls/mspatcha/lzxd_dec.c
new file mode 100755
index 0000000..ba0499e
--- /dev/null
+++ b/dlls/mspatcha/lzxd_dec.c
@@ -0,0 +1,765 @@
+/*
+ * LZXD decoder
+ *
+ * Copyright 2019 Conor McCarthy
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * TODO
+ * - Implememnt interleaved decoding
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wine/debug.h"
+
+#include "patchapi.h"
+
+#include "lzxd_dec.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(mspatcha);
+
+
+#define ELEM_SIZE 2
+#define MAX_CODE_LEN 16
+#define MAX_ALIGN_CODE_LEN 7
+#define PRE_LEN_BITS 4
+#define MAX_PRE_CODE_LEN ((1 << PRE_LEN_BITS) - 1)
+#define MAIN_TABLE_SIZE (1 << MAX_CODE_LEN)
+#define ALIGN_TABLE_SIZE (1 << MAX_ALIGN_CODE_LEN)
+#define HUFF_ERROR 0xFFFF
+#define REP_COUNT 3
+#define MAX_POS_SLOTS 290
+#define ALIGN_CODE_COUNT 8
+#define PRE_LEN_CODE_COUNT 20
+#define MAIN_CODE_COUNT(slots) (256 + 8 * (slots))
+#define MAX_MAIN_CODES MAIN_CODE_COUNT(MAX_POS_SLOTS)
+#define LEN_CODE_COUNT 249
+#define MAX_CHUNK_UNCOMPRESSED_SIZE 0x8000
+#define E8_TRANSFORM_LIMIT_BITS 30
+#define E8_TRANSFORM_DEAD_ZONE 10
+
+#define my_min(a, b) ((a) < (b) ? (a) : (b))
+
+struct LZXD_dec {
+    /* use byte pointers instead of uint16 for simplicity on uncompressed
+     * chunks, and the stream is not 16-bit aligned anyway */
+    const BYTE *stream_buf;
+    /* the next word to load into the bit cache */
+    const BYTE *src;
+    /* location of the next chunk size field */
+    const BYTE *chunk_end;
+    /* position in the output where the maximum allowed decompressed chunk size is reached */
+    size_t uncomp_chunk_end;
+    /* end of the input */
+    const BYTE *stream_end;
+    /* bit cache */
+    UINT32 bits;
+    /* number of unused bits in the cache starting from bit 0 */
+    unsigned bit_pos;
+    /* number of padding bits added trying to read at the chunk end */
+    unsigned tail_bits;
+    /* repeat matches */
+    size_t reps[REP_COUNT];
+    /* distance slot count is required for loading code lengths */
+    unsigned dist_slot_count;
+    /* huffman code lengths */
+    BYTE align_lengths[ALIGN_CODE_COUNT];
+    BYTE main_lengths[MAX_MAIN_CODES];
+    BYTE len_lengths[LEN_CODE_COUNT];
+    UINT16 align_table[ALIGN_TABLE_SIZE];
+    UINT16 main_table[MAIN_TABLE_SIZE];
+    UINT16 len_table[MAIN_TABLE_SIZE];
+};
+
+/* PA19 container format is unaligned, so the LZXD stream is not aligned either.
+ * None of this is super optimized but it's fast enough for installer work.
+ */
+static inline UINT16 read_uint16(struct LZXD_dec *dec)
+{
+    /* bounds check was done before calling */
+    UINT16 u = dec->src[0] | (dec->src[1] << 8);
+    dec->src += ELEM_SIZE;
+    return u;
+}
+
+/* load the next chunk size, reset bit_pos and set up limits
+ */
+static int init_chunk(struct LZXD_dec *dec, size_t index, size_t buf_limit)
+{
+    UINT32 chunk_size;
+
+    if (dec->src + ELEM_SIZE > dec->stream_end)
+        return -1;
+
+    /* error if tail padding bits were decoded as input */
+    if (dec->bit_pos < dec->tail_bits)
+        return -1;
+
+    chunk_size = read_uint16(dec);
+
+    dec->chunk_end = dec->src + chunk_size;
+    if (dec->chunk_end > dec->stream_end)
+        return -1;
+
+    dec->bit_pos = 0;
+    dec->tail_bits = 0;
+
+    dec->uncomp_chunk_end = my_min(buf_limit, index + MAX_CHUNK_UNCOMPRESSED_SIZE);
+
+    return 0;
+}
+
+/* ensure at least 17 bits are loaded but do not advance
+ */
+static inline void prime_bits(struct LZXD_dec *dec)
+{
+    while (dec->bit_pos < 17)
+    {
+        if (dec->src + ELEM_SIZE <= dec->chunk_end)
+        {
+            dec->bits = (dec->bits << 16) | read_uint16(dec);
+        }
+        else
+        {
+            /* Need to pad at the end of the chunk to decode the last codes.
+               In an error state, 0xFFFF sends the decoder down the right
+               side of the huffman tree to error out sooner. */
+            dec->bits = (dec->bits << 16) | 0xFFFF;
+            dec->tail_bits += 16;
+        }
+        dec->bit_pos += 16;
+    }
+}
+
+/* read and advance n bits
+ */
+static inline UINT32 read_bits(struct LZXD_dec *dec, unsigned n)
+{
+    UINT32 bits;
+
+    dec->bit_pos -= n;
+    bits = dec->bits >> dec->bit_pos;
+    bits &= ((1 << n) - 1);
+
+    while (dec->bit_pos < 17)
+    {
+        if (dec->src + ELEM_SIZE <= dec->chunk_end)
+        {
+            dec->bits = (dec->bits << 16) | read_uint16(dec);
+        }
+        else
+        {
+            /* tail padding */
+            dec->bits = (dec->bits << 16) | 0xFFFF;
+            dec->tail_bits += 16;
+        }
+        dec->bit_pos += 16;
+    }
+
+    return bits;
+}
+
+/* read n bits but do not advance
+ */
+static inline UINT32 peek_bits(struct LZXD_dec *dec, unsigned n)
+{
+    UINT32 bits = dec->bits >> (dec->bit_pos - n);
+    return bits & ((1 << n) - 1);
+}
+
+static inline void advance_bits(struct LZXD_dec *dec, unsigned length)
+{
+    dec->bit_pos -= length;
+    prime_bits(dec);
+}
+
+/* read a 16-bit aligned UINT32
+ */
+static UINT32 read_uint32(struct LZXD_dec *dec)
+{
+    UINT32 u = 0;
+    unsigned n = 0;
+
+    assert((dec->bit_pos & 0xF) == 0);
+
+    while (dec->bit_pos)
+    {
+        dec->bit_pos -= 16;
+        u |= ((dec->bits >> dec->bit_pos) & 0xFFFF) << n;
+        n += 16;
+    }
+    while (n < 32 && dec->src + ELEM_SIZE <= dec->chunk_end)
+    {
+        u |= read_uint16(dec) << n;
+        n += 16;
+    }
+
+    return u;
+}
+
+static int make_huffman_codes(unsigned *codes, const BYTE *lengths, unsigned count)
+{
+    unsigned len_count[MAX_CODE_LEN + 1];
+    unsigned next_code[MAX_CODE_LEN + 1];
+    unsigned i;
+    unsigned code = 0;
+
+    memset(len_count, 0, sizeof(len_count));
+    for (i = 0; i < count; ++i)
+        ++len_count[lengths[i]];
+    len_count[0] = 0;
+
+    for (i = 1; i <= MAX_CODE_LEN; ++i)
+    {
+        code = (code + len_count[i - 1]) << 1;
+        next_code[i] = code;
+    }
+    for (i = 0; i < count; i++)
+    {
+        unsigned len = lengths[i];
+        if (len)
+        {
+            /* test for bad code tree */
+            if (next_code[len] >= (1U << len))
+                return -1;
+
+            codes[i] = next_code[len];
+            ++next_code[len];
+        }
+    }
+    return 0;
+}
+
+void make_decode_table(UINT16 *table, const unsigned *codes,
+    const BYTE *lengths, unsigned max_len, unsigned count)
+{
+    const size_t table_size = (size_t)1 << max_len;
+    size_t i;
+
+    for (i = 0; i < table_size; i++)
+        table[i] = HUFF_ERROR;
+
+    for (i = 0; i < count; i++) if (lengths[i])
+    {
+        BYTE diff = (BYTE)max_len - lengths[i];
+        size_t n = codes[i] << diff;
+        size_t end = n + ((size_t)1 << diff);
+        for (; n < end; ++n)
+            table[n] = (UINT16)i;
+    }
+}
+
+#define ret_if_failed(r_) do { int err_ = r_; if(err_) return err_; } while(0)
+
+static int decode_lengths(struct LZXD_dec *dec,
+    BYTE *lengths, unsigned index, unsigned count)
+{
+    unsigned codes[PRE_LEN_CODE_COUNT];
+    BYTE pre_lens[PRE_LEN_CODE_COUNT];
+    size_t i;
+    unsigned repeats = 1;
+
+    for (i = 0; i < PRE_LEN_CODE_COUNT; ++i)
+        pre_lens[i] = (BYTE)read_bits(dec, PRE_LEN_BITS);
+
+    ret_if_failed(make_huffman_codes(codes, pre_lens, PRE_LEN_CODE_COUNT));
+    make_decode_table(dec->main_table, codes, pre_lens, MAX_PRE_CODE_LEN, PRE_LEN_CODE_COUNT);
+
+    while (index < count)
+    {
+        UINT32 bits = peek_bits(dec, MAX_PRE_CODE_LEN);
+        UINT16 sym = dec->main_table[bits];
+        if (sym == HUFF_ERROR)
+            return -1;
+        advance_bits(dec, pre_lens[sym]);
+
+        if (sym < 17)
+        {
+            sym = (lengths[index] + 17 - sym) % 17;
+            do
+            {
+                lengths[index] = (BYTE)sym;
+                ++index;
+                --repeats;
+            } while (repeats && index < count);
+
+            repeats = 1;
+        }
+        else if (sym < 19)
+        {
+            unsigned zeros;
+
+            sym -= 13;
+            zeros = read_bits(dec, sym) + (1 << sym) - 12;
+            do
+            {
+                lengths[index] = 0;
+                ++index;
+                --zeros;
+            } while (zeros && index < count);
+        }
+        else
+        {
+            /* the repeat count applies to the next symbol */
+            repeats = 4 + read_bits(dec, 1);
+        }
+    }
+    return 0;
+}
+
+/* distance decoder for block_type == 1
+ */
+static size_t decode_dist_verbatim(struct LZXD_dec *dec, unsigned dist_slot)
+{
+    size_t dist;
+    unsigned footer_bits = 17;
+
+    if (dist_slot < 38)
+    {
+        footer_bits = (dist_slot >> 1) - 1;
+        dist = ((size_t)2 + (dist_slot & 1)) << footer_bits;
+    }
+    else
+    {
+        dist = ((size_t)1 << 19) + ((size_t)1 << 17) * (dist_slot - 38);
+    }
+    return dist + read_bits(dec, footer_bits);
+}
+
+/* distance decoder for block_type == 2
+ */
+static size_t decode_dist_aligned(struct LZXD_dec *dec, unsigned dist_slot)
+{
+    size_t dist;
+    unsigned footer_bits = 17;
+
+    if (dist_slot < 38)
+    {
+        footer_bits = (dist_slot >> 1) - 1;
+        dist = ((size_t)2 + (dist_slot & 1)) << footer_bits;
+    }
+    else
+    {
+        dist = ((size_t)1 << 19) + ((size_t)1 << 17) * (dist_slot - 38);
+    }
+    if (footer_bits >= 3)
+    {
+        UINT32 bits;
+        UINT16 sym;
+
+        footer_bits -= 3;
+        if (footer_bits)
+            dist += read_bits(dec, footer_bits) << 3;
+
+        bits = peek_bits(dec, MAX_ALIGN_CODE_LEN);
+        sym = dec->align_table[bits];
+        if (sym == HUFF_ERROR)
+            return ~(size_t)0;
+        advance_bits(dec, dec->align_lengths[sym]);
+
+        dist += sym;
+    }
+    else
+    {
+        dist += read_bits(dec, footer_bits);
+    }
+    return dist;
+}
+
+static inline void align_16_or_maybe_advance_anyway(struct LZXD_dec *dec)
+{
+    dec->bit_pos &= 0x30;
+    /* The specification requires 16 bits of zero padding in some cases where the stream is already aligned, but
+     * the logic behind the choice to pad or not is undefined (because it is illogical). Fortunately it seems always
+     * to coincide with a bit_pos of 0x20, but 0x20 doesn't always mean padding, so we test for zero too. A remote
+     * chance of failure may still exist but I've never seen it. */
+    if (dec->bit_pos == 0x20 && (dec->bits >> 16) == 0)
+        dec->bit_pos = 0x10;
+}
+
+static int copy_uncompressed(struct LZXD_dec *dec, BYTE *base, size_t *index_ptr, size_t buf_limit, UINT32 block_size)
+{
+    size_t index = *index_ptr;
+    size_t end = index + block_size;
+    size_t realign;
+
+    if (end > buf_limit)
+        return -1;
+    /* save the current alignment */
+    realign = (dec->src - dec->stream_buf) & 1;
+
+    while (dec->src < dec->stream_end)
+    {
+        /* now treat the input as an unaligned byte stream */
+        size_t to_copy = my_min(end - index, dec->uncomp_chunk_end - index);
+        to_copy = my_min(to_copy, (size_t)(dec->stream_end - dec->src));
+
+        memcpy(base + index, dec->src, to_copy);
+        index += to_copy;
+        dec->src += to_copy;
+
+        if (index == end)
+        {
+            /* realign at the end of the block */
+            dec->src += ((dec->src - dec->stream_buf) & 1) ^ realign;
+            /* fill the bit cache for block header decoding */
+            prime_bits(dec);
+            break;
+        }
+        /* chunk sizes are also unaligned */
+        ret_if_failed(init_chunk(dec, index, buf_limit));
+    }
+    *index_ptr = index;
+    return 0;
+}
+
+static int prime_next_chunk(struct LZXD_dec *dec, size_t index, size_t buf_limit)
+{
+    if (dec->src < dec->chunk_end)
+        return -1;
+    ret_if_failed(init_chunk(dec, index, buf_limit));
+    prime_bits(dec);
+    return 0;
+}
+
+#define MAX_LONG_MATCH_CODE_LEN 3
+#define LONG_MATCH_TABLE_SIZE (1 << MAX_LONG_MATCH_CODE_LEN)
+
+struct long_match {
+    BYTE code_len;
+    unsigned extra_bits;
+    unsigned base;
+};
+
+static const struct long_match long_match_table[LONG_MATCH_TABLE_SIZE] = {
+    {1, 8,  0x101},
+    {1, 8,  0x101},
+    {1, 8,  0x101},
+    {1, 8,  0x101},
+    {2, 10, 0x201},
+    {2, 10, 0x201},
+    {3, 12, 0x601},
+    {3, 15, 0x101}
+};
+
+static int decode_lzxd_block(struct LZXD_dec *dec, BYTE *base, size_t predef_size, size_t *index_ptr, size_t buf_limit)
+{
+    unsigned codes[MAX_MAIN_CODES];
+    unsigned main_code_count;
+    UINT32 block_type;
+    UINT32 block_size;
+    size_t i;
+    size_t block_limit;
+    size_t index = *index_ptr;
+    size_t (*dist_decoder)(struct LZXD_dec *dec, unsigned dist_slot);
+
+    if (index >= dec->uncomp_chunk_end && prime_next_chunk(dec, index, buf_limit))
+        return -1;
+
+    block_type = read_bits(dec, 3);
+
+    /* check for invalid block types */
+    if (block_type == 0 || block_type > 3)
+        return -1;
+
+    block_size = read_bits(dec, 8);
+    block_size = (block_size << 8) | read_bits(dec, 8);
+    block_size = (block_size << 8) | read_bits(dec, 8);
+
+    if (block_type == 3)
+    {
+        /* uncompressed block */
+        align_16_or_maybe_advance_anyway(dec);
+        /* must have run out of coffee at the office */
+        for (i = 0; i < REP_COUNT; ++i)
+            dec->reps[i] = read_uint32(dec) - 1;
+        /* copy the block to output */
+        return copy_uncompressed(dec, base, index_ptr, buf_limit, block_size);
+    }
+    else if (block_type == 2)
+    {
+        /* distance alignment decoder will be used */
+        for (i = 0; i < ALIGN_CODE_COUNT; ++i)
+            dec->align_lengths[i] = read_bits(dec, 3);
+    }
+
+    main_code_count = MAIN_CODE_COUNT(dec->dist_slot_count);
+    ret_if_failed(decode_lengths(dec, dec->main_lengths, 0, 256));
+    ret_if_failed(decode_lengths(dec, dec->main_lengths, 256, main_code_count));
+    ret_if_failed(decode_lengths(dec, dec->len_lengths, 0, LEN_CODE_COUNT));
+
+    dist_decoder = (block_type == 2) ? decode_dist_aligned : decode_dist_verbatim;
+
+    if (block_type == 2)
+    {
+        ret_if_failed(make_huffman_codes(codes, dec->align_lengths, ALIGN_CODE_COUNT));
+        make_decode_table(dec->align_table, codes, dec->align_lengths, MAX_ALIGN_CODE_LEN, ALIGN_CODE_COUNT);
+    }
+
+    ret_if_failed(make_huffman_codes(codes, dec->main_lengths, main_code_count));
+    make_decode_table(dec->main_table, codes, dec->main_lengths, MAX_CODE_LEN, main_code_count);
+
+    ret_if_failed(make_huffman_codes(codes, dec->len_lengths, LEN_CODE_COUNT));
+    make_decode_table(dec->len_table, codes, dec->len_lengths, MAX_CODE_LEN, LEN_CODE_COUNT);
+
+    block_limit = my_min(buf_limit, index + block_size);
+
+    while (index < block_limit)
+    {
+        UINT32 bits;
+        UINT16 sym;
+
+        if (index >= dec->uncomp_chunk_end && prime_next_chunk(dec, index, buf_limit))
+            return -1;
+
+        bits = peek_bits(dec, MAX_CODE_LEN);
+        sym = dec->main_table[bits];
+        if (sym == HUFF_ERROR)
+            return -1;
+        advance_bits(dec, dec->main_lengths[sym]);
+
+        if (sym < 256)
+        {
+            /* literal */
+            base[index] = (BYTE)sym;
+            ++index;
+        }
+        else {
+            size_t length;
+            size_t dist;
+            size_t end;
+            unsigned dist_slot;
+
+            sym -= 256;
+            length = (sym & 7) + 2;
+            dist_slot = sym >> 3;
+
+            if (length == 9)
+            {
+                /* extra length bits */
+                bits = peek_bits(dec, MAX_CODE_LEN);
+                sym = dec->len_table[bits];
+                if (sym == HUFF_ERROR)
+                    return -1;
+                advance_bits(dec, dec->len_lengths[sym]);
+
+                length += sym;
+            }
+            dist = dist_slot;
+            if (dist_slot > 3)
+            {
+                /* extra distance bits */
+                dist = dist_decoder(dec, dist_slot);
+                if (dist == ~(size_t)0)
+                    return -1;
+            }
+            if (length == 257)
+            {
+                /* extra-long match length */
+                bits = peek_bits(dec, MAX_LONG_MATCH_CODE_LEN);
+                advance_bits(dec, long_match_table[bits].code_len);
+
+                length = long_match_table[bits].base;
+                length += read_bits(dec, long_match_table[bits].extra_bits);
+            }
+            if (dist < 3)
+            {
+                /* repeat match */
+                size_t rep = dist;
+                dist = dec->reps[dist];
+                dec->reps[rep] = dec->reps[0];
+            }
+            else
+            {
+                dist -= REP_COUNT;
+                dec->reps[2] = dec->reps[1];
+                dec->reps[1] = dec->reps[0];
+            }
+            dec->reps[0] = dist;
+
+            while (dist >= index && length && index < block_limit)
+            {
+                /* undocumented: the encoder assumes an imaginary buffer
+                 * of zeros exists before the start of the real buffer */
+                base[index] = 0;
+                ++index;
+                --length;
+            }
+
+            end = my_min(index + length, block_limit);
+            while (index < end)
+            {
+                base[index] = base[index - dist - 1];
+                ++index;
+            }
+        }
+    }
+    /* error if tail padding bits were decoded as input */
+    if (dec->bit_pos < dec->tail_bits)
+        return -1;
+
+    *index_ptr = index;
+    return 0;
+}
+
+static void reverse_e8_transform(BYTE *decode_buf, ptrdiff_t len, ptrdiff_t e8_file_size)
+{
+    ptrdiff_t limit = my_min((ptrdiff_t)1 << E8_TRANSFORM_LIMIT_BITS, len);
+    ptrdiff_t i;
+
+    for (i = 0; i < limit; )
+    {
+        ptrdiff_t end = my_min(i + MAX_CHUNK_UNCOMPRESSED_SIZE - E8_TRANSFORM_DEAD_ZONE,
+            limit - E8_TRANSFORM_DEAD_ZONE);
+        ptrdiff_t next = i + MAX_CHUNK_UNCOMPRESSED_SIZE;
+
+        for (; i < end; ++i)
+        {
+            if (decode_buf[i] == 0xE8)
+            {
+                ptrdiff_t delta;
+                ptrdiff_t value = (ptrdiff_t)decode_buf[i + 1] |
+                    decode_buf[i + 2] << 8 |
+                    decode_buf[i + 3] << 16 |
+                    decode_buf[i + 4] << 24;
+
+                if (value >= -i && value < e8_file_size)
+                {
+                    if (value >= 0)
+                        delta = value - i;
+                    else
+                        delta = value + e8_file_size;
+
+                    decode_buf[i + 1] = (BYTE)delta;
+                    decode_buf[i + 2] = (BYTE)(delta >> 8);
+                    decode_buf[i + 3] = (BYTE)(delta >> 16);
+                    decode_buf[i + 4] = (BYTE)(delta >> 24);
+                }
+                i += 4;
+            }
+        }
+        i = next;
+    }
+}
+
+DWORD decode_lzxd_stream(const BYTE *src, const size_t input_size,
+    BYTE *dst, const size_t output_size,
+    const size_t predef_size,
+    DWORD large_window,
+    PPATCH_PROGRESS_CALLBACK progress_fn,
+    PVOID  progress_ctx)
+{
+    struct LZXD_dec *dec;
+    const BYTE *end = src + input_size;
+    size_t buf_size = predef_size + output_size;
+    UINT32 e8;
+    UINT32 e8_file_size = 0;
+    DWORD err = ERROR_SUCCESS;
+
+    if (input_size == 0)
+        return (output_size == 0) ?  ERROR_SUCCESS : ERROR_PATCH_CORRUPT;
+
+    if (progress_fn != NULL && !progress_fn(progress_ctx, 0, (ULONG)output_size))
+        return ERROR_CANCELLED;
+
+    dec = malloc(sizeof(*dec));
+    if (dec == NULL)
+        return ERROR_OUTOFMEMORY;
+
+    memset(dec->main_lengths, 0, sizeof(dec->main_lengths));
+    memset(dec->len_lengths, 0, sizeof(dec->len_lengths));
+    memset(dec->reps, 0, sizeof(dec->reps));
+
+    /* apparently the window size is not recorded and must be deduced from the file sizes */
+    {
+        unsigned max_window = large_window ? MAX_LARGE_WINDOW : MAX_NORMAL_WINDOW;
+        size_t window = (size_t)1 << 17;
+        /* round up the old file size per the lzxd spec - correctness verified by fuzz tests */
+        size_t total = (predef_size + 0x7FFF) & ~0x7FFF;
+        unsigned delta;
+
+        total += output_size;
+        dec->dist_slot_count = 34;
+        while (window < total && window < ((size_t)1 << 19))
+        {
+            dec->dist_slot_count += 2;
+            window <<= 1;
+        }
+        delta = 4;
+        while (window < total && window < max_window)
+        {
+            dec->dist_slot_count += delta;
+            delta <<= 1;
+            window <<= 1;
+        }
+    }
+
+    dec->bit_pos = 0;
+    dec->tail_bits = 0;
+    dec->stream_buf = src;
+    dec->src = src;
+    dec->stream_end = end;
+    dec->chunk_end = dec->src;
+
+    /* load the first chunk size and set the end pointer */
+    if(init_chunk(dec, predef_size, buf_size))
+    {
+        err = ERROR_PATCH_DECODE_FAILURE;
+        goto free_dec;
+    }
+
+    /* fill the bit cache */
+    prime_bits(dec);
+
+    e8 = read_bits(dec, 1);
+    if (e8)
+    {
+        /* E8 transform was used */
+        e8_file_size = read_bits(dec, 16) << 16;
+        e8_file_size |= read_bits(dec, 16);
+    }
+
+    {
+        size_t index = predef_size;
+        while (dec->src < dec->stream_end && index < buf_size)
+        {
+            if (decode_lzxd_block(dec, dst, predef_size, &index, buf_size))
+            {
+                err = ERROR_PATCH_DECODE_FAILURE;
+                goto free_dec;
+            }
+            if (progress_fn != NULL && !progress_fn(progress_ctx, (ULONG)(index - predef_size), (ULONG)output_size))
+            {
+                err = ERROR_CANCELLED;
+                goto free_dec;
+            }
+        }
+    }
+
+    if (e8)
+        reverse_e8_transform(dst + predef_size, output_size, e8_file_size);
+
+free_dec:
+    free(dec);
+
+    return err;
+}
diff --git a/dlls/mspatcha/lzxd_dec.h b/dlls/mspatcha/lzxd_dec.h
new file mode 100755
index 0000000..a7923cd
--- /dev/null
+++ b/dlls/mspatcha/lzxd_dec.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 Conor McCarthy
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#define MAX_NORMAL_WINDOW (8U << 20)
+#define MAX_LARGE_WINDOW (32U << 20)
+
+DWORD decode_lzxd_stream(const BYTE *src, const size_t input_size,
+    BYTE *dst, const size_t output_size,
+    const size_t predef_size,
+    DWORD large_window,
+    PPATCH_PROGRESS_CALLBACK progress_fn,
+    PVOID  progress_ctx);
diff --git a/dlls/mspatcha/pa19.c b/dlls/mspatcha/pa19.c
new file mode 100755
index 0000000..442dd01
--- /dev/null
+++ b/dlls/mspatcha/pa19.c
@@ -0,0 +1,1031 @@
+/*
+ * PatchAPI PA19 file handlers
+ *
+ * Copyright 2019 Conor McCarthy
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * TODO
+ * - Normalization of 32-bit PE executable files and reversal of special
+ *   processing of these executables is not implemented.
+ *   Without normalization, old files cannot be validated for patching. The
+ *   function NormalizeFileForPatchSignature() in Windows could be used to work
+ *   out exactly how normalization works.
+ *   Most/all of the special processing seems to be relocation of targets for
+ *   some jump/call instructions to match more of the old file and improve
+ *   compression. Patching of 64-bit exes works because mspatchc.dll does not
+ *   implement special processing of them. In 32-bit patches, the variable
+ *   'unknown_count' seems to indicate presence of data for reversing the
+ *   relocations. The meaning needs to be interpreted in relation to the PE
+ *   file format.
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wine/debug.h"
+
+#include "patchapi.h"
+
+#include "pa19.h"
+#include "lzxd_dec.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(mspatcha);
+
+#define PA19_FILE_MAGIC 0x39314150
+#define PATCH_OPTION_EXTRA_FLAGS 0x80000000
+
+#define my_max(a, b) ((a) > (b) ? (a) : (b))
+#define my_min(a, b) ((a) < (b) ? (a) : (b))
+
+
+/* The CRC32 code is copyright (C) 1986 Gary S. Brown and was placed in the
+ * public domain.
+ * CRC polynomial 0xedb88320
+ */
+static const UINT32 CRC_table[256] =
+{
+    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+static UINT32 compute_crc32(UINT32 crc, const BYTE *pData, INT_PTR iLen)
+{
+    crc ^= 0xFFFFFFFF;
+
+    while (iLen > 0) {
+        crc = CRC_table[(crc ^ *pData) & 0xFF] ^ (crc >> 8);
+        pData++;
+        iLen--;
+    }
+    return crc ^ 0xFFFFFFFF;
+}
+
+static UINT32 compute_zero_crc32(UINT32 crc, INT_PTR iLen)
+{
+    crc ^= 0xFFFFFFFF;
+
+    while (iLen > 0)
+    {
+        crc = CRC_table[crc & 0xFF] ^ (crc >> 8);
+        iLen--;
+    }
+    return crc ^ 0xFFFFFFFF;
+}
+
+
+/***********************************************************************************
+ *  PatchAPI PA19 file header
+ *
+ *  BYTE magic[4];
+ *  UINT32 options;
+ *  UINT32 options_2; (present if PATCH_OPTION_EXTRA_FLAGS set)
+ *  UINT32 timestamp; (if PATCH_OPTION_NO_TIMESTAMP is SET in options)
+ *  UVLI rebase;      (present if PATCH_OPTION_NO_REBASE is not set; used on 32-bit executables)
+ *  UVLI unpatched_size;
+ *  UINT32 crc32_patched;
+ *  BYTE input_file_count;
+ *
+ *  For each source file:
+ *      SVLI (patched_size - unpatched_size);
+ *      UINT32 crc32_unpatched;
+ *      BYTE ignore_range_count;
+ *      For each ignore range:
+ *          SVLI OffsetInOldFile;
+ *          UVLI LengthInBytes;
+ *      BYTE retain_range_count;
+ *      For each retain range:
+ *          SVLI (OffsetInOldFile - (prevOffsetInOldFile + prevLengthInBytes));
+ *          SVLI (OffsetInNewFile - OffsetInOldFile);
+ *          UVLI LengthInBytes;
+ *      UVLI unknown_count; (a count of pairs of values related to reversal of call target
+ *                           relocations done to improve compression of 32-bit executables)
+ *      UVLI interleave_count; (present only if PATCH_OPTION_INTERLEAVE_FILES is set in options)
+ *          UVLI interleave_values[interleave_count * 3 - 1];
+ *      UVLI lzxd_input_size;
+ *
+ *  For each source file:
+ *      UINT16 lzxd_block[lzxd_input_size / 2]; (NOT always 16-bit aligned)
+ *
+ *  UINT32 crc_hack; (rounds out the entire file crc32 to 0)
+*/
+
+
+#define MAX_RANGES 255
+
+struct input_file_info {
+    size_t input_size;
+    DWORD crc32;
+    BYTE ignore_range_count;
+    BYTE retain_range_count;
+    PATCH_IGNORE_RANGE ignore_table[MAX_RANGES];
+    PATCH_RETAIN_RANGE retain_table[MAX_RANGES];
+    size_t unknown_count;
+    size_t stream_size;
+    const BYTE *stream_start;
+    int next_i;
+    int next_r;
+};
+
+struct patch_file_header {
+    DWORD flags;
+    DWORD timestamp;
+    size_t patched_size;
+    DWORD patched_crc32;
+    unsigned input_file_count;
+    struct input_file_info *file_table;
+    const BYTE *src;
+    const BYTE *end;
+    DWORD err;
+};
+
+
+/* Currently supported options. Some such as PATCH_OPTION_FAIL_IF_BIGGER don't
+ * affect decoding but can get recorded in the patch file anyway */
+#define PATCH_OPTION_SUPPORTED_FLAGS ( \
+      PATCH_OPTION_USE_LZX_A \
+    | PATCH_OPTION_USE_LZX_B \
+    | PATCH_OPTION_USE_LZX_LARGE \
+    | PATCH_OPTION_NO_BINDFIX \
+    | PATCH_OPTION_NO_LOCKFIX \
+    | PATCH_OPTION_NO_REBASE \
+    | PATCH_OPTION_FAIL_IF_SAME_FILE \
+    | PATCH_OPTION_FAIL_IF_BIGGER \
+    | PATCH_OPTION_NO_CHECKSUM \
+    | PATCH_OPTION_NO_RESTIMEFIX \
+    | PATCH_OPTION_NO_TIMESTAMP \
+    | PATCH_OPTION_EXTRA_FLAGS)
+
+
+/* read a byte-aligned little-endian UINT32 from input and set error if eof
+ */
+static inline UINT32 read_raw_uint32(struct patch_file_header *ph)
+{
+    const BYTE *src = ph->src;
+
+    ph->src += 4;
+    if (ph->src > ph->end)
+    {
+        ph->err = ERROR_PATCH_CORRUPT;
+        return 0;
+    }
+    return src[0]
+        | (src[1] << 8)
+        | (src[2] << 16)
+        | (src[3] << 24);
+}
+
+/* Read a variable-length integer from a sequence of bytes terminated by
+ * a value with bit 7 set. Set error if invalid or eof */
+static UINT64 read_uvli(struct patch_file_header *ph)
+{
+    const BYTE *vli = ph->src;
+    UINT64 n;
+    ptrdiff_t i;
+    ptrdiff_t limit = my_min(ph->end - vli, 9);
+
+    if (ph->src >= ph->end)
+    {
+        ph->err = ERROR_PATCH_CORRUPT;
+        return 0;
+    }
+
+    n = vli[0] & 0x7F;
+    for (i = 1; i < limit && vli[i - 1] < 0x80; ++i)
+        n += (UINT64)(vli[i] & 0x7F) << (7 * i);
+
+    if (vli[i - 1] < 0x80)
+    {
+        ph->err = ERROR_PATCH_CORRUPT;
+        return 0;
+    }
+
+    ph->src += i;
+
+    return n;
+}
+
+/* Signed variant of the above. First byte sign flag is 0x40.
+ */
+static INT64 read_svli(struct patch_file_header *ph)
+{
+    const BYTE *vli = ph->src;
+    INT64 n;
+    ptrdiff_t i;
+    ptrdiff_t limit = my_min(ph->end - vli, 9);
+
+    if (ph->src >= ph->end)
+    {
+        ph->err = ERROR_PATCH_CORRUPT;
+        return 0;
+    }
+
+    n = vli[0] & 0x3F;
+    for (i = 1; i < limit && vli[i - 1] < 0x80; ++i)
+        n += (INT64)(vli[i] & 0x7F) << (7 * i - 1);
+
+    if (vli[i - 1] < 0x80)
+    {
+        ph->err = ERROR_PATCH_CORRUPT;
+        return 0;
+    }
+
+    if (vli[0] & 0x40)
+        n = -n;
+
+    ph->src += i;
+
+    return n;
+}
+
+static int compare_ignored_range(const void *a, const void *b)
+{
+    LONG delta = ((PATCH_IGNORE_RANGE*)a)->OffsetInOldFile - ((PATCH_IGNORE_RANGE*)b)->OffsetInOldFile;
+    if (delta > 0)
+        return 1;
+    if (delta < 0)
+        return -1;
+    return 0;
+}
+
+static int compare_retained_range_old(const void *a, const void *b)
+{
+    LONG delta = ((PATCH_RETAIN_RANGE*)a)->OffsetInOldFile - ((PATCH_RETAIN_RANGE*)b)->OffsetInOldFile;
+    if (delta > 0)
+        return 1;
+    if (delta < 0)
+        return -1;
+    return 0;
+}
+
+static int compare_retained_range_new(const void *a, const void *b)
+{
+    LONG delta = ((PATCH_RETAIN_RANGE*)a)->OffsetInNewFile - ((PATCH_RETAIN_RANGE*)b)->OffsetInNewFile;
+    if (delta > 0)
+        return 1;
+    if (delta < 0)
+        return -1;
+    return 0;
+}
+
+static int read_header(struct patch_file_header *ph, const BYTE *buf, size_t size)
+{
+    unsigned fileno;
+
+    ph->src = buf;
+    ph->end = buf + size;
+
+    ph->file_table = NULL;
+    ph->err = ERROR_SUCCESS;
+
+    if (read_raw_uint32(ph) != PA19_FILE_MAGIC)
+    {
+        ph->err = ERROR_PATCH_CORRUPT;
+        return -1;
+    }
+
+    ph->flags = read_raw_uint32(ph);
+    if ((ph->flags & PATCH_OPTION_SUPPORTED_FLAGS) != ph->flags)
+    {
+        FIXME("unsupported option flag(s): 0x%08x\n", ph->flags & ~PATCH_OPTION_SUPPORTED_FLAGS);
+        ph->err = ERROR_PATCH_PACKAGE_UNSUPPORTED;
+        return -1;
+    }
+
+    /* addional 32-bit flag field */
+    if (ph->flags & PATCH_OPTION_EXTRA_FLAGS)
+        (void)read_raw_uint32(ph);
+
+    /* the meaning of PATCH_OPTION_NO_TIMESTAMP is inverted for decoding */
+    if(ph->flags & PATCH_OPTION_NO_TIMESTAMP)
+        ph->timestamp = read_raw_uint32(ph);
+
+    /* not sure what this value is for but it seems to have no effect on output */
+    if(!(ph->flags & PATCH_OPTION_NO_REBASE))
+        (void)read_uvli(ph);
+
+    ph->patched_size = (size_t)read_uvli(ph);
+    ph->patched_crc32 = read_raw_uint32(ph);
+    ph->input_file_count = *ph->src;
+    ++ph->src;
+
+    if (ph->err != ERROR_SUCCESS)
+        return -1;
+
+    ph->file_table = calloc(ph->input_file_count, sizeof(struct input_file_info));
+    if (ph->file_table == NULL)
+    {
+        ph->err = ERROR_OUTOFMEMORY;
+        return -1;
+    }
+
+    for (fileno = 0; fileno < ph->input_file_count; ++fileno) {
+        struct input_file_info *fi = ph->file_table + fileno;
+        ptrdiff_t delta;
+        unsigned i;
+
+        delta = (ptrdiff_t)read_svli(ph);
+        fi->input_size = ph->patched_size + delta;
+
+        fi->crc32 = read_raw_uint32(ph);
+
+        fi->ignore_range_count = *ph->src;
+        ++ph->src;
+
+        for (i = 0; i < fi->ignore_range_count; ++i) {
+            PATCH_IGNORE_RANGE *ir = fi->ignore_table + i;
+
+            ir->OffsetInOldFile = (LONG)read_svli(ph);
+            ir->LengthInBytes = (ULONG)read_uvli(ph);
+
+            if (i != 0)
+            {
+                ir->OffsetInOldFile += fi->ignore_table[i - 1].OffsetInOldFile
+                    + fi->ignore_table[i - 1].LengthInBytes;
+            }
+            if (ir->OffsetInOldFile > fi->input_size
+                || ir->OffsetInOldFile + ir->LengthInBytes > fi->input_size
+                || ir->LengthInBytes > fi->input_size)
+            {
+                ph->err = ERROR_PATCH_CORRUPT;
+                return -1;
+            }
+        }
+
+        fi->retain_range_count = *ph->src;
+        ++ph->src;
+
+        for (i = 0; i < fi->retain_range_count; ++i) {
+            PATCH_RETAIN_RANGE *rr = fi->retain_table + i;
+
+            rr->OffsetInOldFile = (LONG)read_svli(ph);
+            if (i != 0)
+                rr->OffsetInOldFile +=
+                    fi->retain_table[i - 1].OffsetInOldFile + fi->retain_table[i - 1].LengthInBytes;
+
+            rr->OffsetInNewFile = rr->OffsetInOldFile + (LONG)read_svli(ph);
+            rr->LengthInBytes = (ULONG)read_uvli(ph);
+
+            if (rr->OffsetInOldFile > fi->input_size
+                || rr->OffsetInOldFile + rr->LengthInBytes > fi->input_size
+                || rr->OffsetInNewFile > ph->patched_size
+                || rr->OffsetInNewFile + rr->LengthInBytes > ph->patched_size
+                || rr->LengthInBytes > ph->patched_size)
+            {
+                ph->err = ERROR_PATCH_CORRUPT;
+                return -1;
+            }
+
+            /* ranges in new file must be equal and in the same order for all source files */
+            if (fileno != 0)
+            {
+                PATCH_RETAIN_RANGE *rr_0 = ph->file_table[0].retain_table + i;
+                if (rr->OffsetInNewFile != rr_0->OffsetInNewFile
+                    || rr->LengthInBytes != rr_0->LengthInBytes)
+                {
+                    ph->err = ERROR_PATCH_CORRUPT;
+                    return -1;
+                }
+            }
+        }
+
+        fi->unknown_count = (size_t)read_uvli(ph);
+        if (fi->unknown_count)
+        {
+            FIXME("special processing of 32-bit executables not implemented.\n");
+            ph->err = ERROR_PATCH_PACKAGE_UNSUPPORTED;
+            return -1;
+        }
+        fi->stream_size = (size_t)read_uvli(ph);
+    }
+
+    for (fileno = 0; fileno < ph->input_file_count; ++fileno)
+    {
+        struct input_file_info *fi = ph->file_table + fileno;
+
+        qsort(fi->ignore_table, fi->ignore_range_count, sizeof(fi->ignore_table[0]), compare_ignored_range);
+        qsort(fi->retain_table, fi->retain_range_count, sizeof(fi->retain_table[0]), compare_retained_range_old);
+
+        fi->stream_start = ph->src;
+        ph->src += fi->stream_size;
+    }
+
+    /* skip the crc adjustment field */
+    ph->src = my_min(ph->src + 4, ph->end);
+
+    {
+        UINT32 crc = compute_crc32(0, buf, ph->src - buf) ^ 0xFFFFFFFF;
+        if (crc != 0)
+            ph->err = ERROR_PATCH_CORRUPT;
+    }
+
+    return (ph->err == ERROR_SUCCESS) ? 0 : -1;
+}
+
+static void free_header(struct patch_file_header *ph)
+{
+    free(ph->file_table);
+}
+
+#define TICKS_PER_SEC 10000000
+#define SEC_TO_UNIX_EPOCH 11644473600LL
+
+static void posix_time_to_file_time(ULONG timestamp, FILETIME *ft)
+{
+    UINT64 ticks = ((UINT64)timestamp + SEC_TO_UNIX_EPOCH) * TICKS_PER_SEC;
+    ft->dwLowDateTime = (DWORD)ticks;
+    ft->dwHighDateTime = (DWORD)(ticks >> 32);
+}
+
+/* Get the next range to ignore in the old file.
+ * fi->next_i must be initialized before use */
+static ULONG next_ignored_range(const struct input_file_info *fi, size_t index, ULONG old_file_size, ULONG *end)
+{
+    ULONG start = old_file_size;
+    *end = old_file_size;
+    /* if patching is unnecessary, the ignored ranges are skipped during crc calc */
+    if (fi->next_i < fi->ignore_range_count && fi->stream_size != 0)
+    {
+        start = fi->ignore_table[fi->next_i].OffsetInOldFile;
+        *end = my_max(start + fi->ignore_table[fi->next_i].LengthInBytes, index);
+        start = my_max(start, index);
+    }
+    return start;
+}
+
+/* Get the next range to retain from the old file.
+ * fi->next_r must be initialized before use */
+static ULONG next_retained_range_old(const struct input_file_info *fi, size_t index, ULONG old_file_size, ULONG *end)
+{
+    ULONG start = old_file_size;
+    *end = old_file_size;
+    if (fi->next_r < fi->retain_range_count)
+    {
+        start = fi->retain_table[fi->next_r].OffsetInOldFile;
+        *end = my_max(start + fi->retain_table[fi->next_r].LengthInBytes, index);
+        start = my_max(start, index);
+    }
+    return start;
+}
+
+/* Get the next range to retain in the new file.
+ * fi->next_r must be initialized before use */
+static ULONG next_retained_range_new(const struct input_file_info *fi, size_t index, ULONG new_file_size, ULONG *end)
+{
+    ULONG start = new_file_size;
+    *end = new_file_size;
+    if (fi->next_r < fi->retain_range_count)
+    {
+        start = fi->retain_table[fi->next_r].OffsetInNewFile;
+        *end = my_max(start + fi->retain_table[fi->next_r].LengthInBytes, index);
+        start = my_max(start, index);
+    }
+    return start;
+}
+
+/* Find the next range in the old file which must be assumed zero-filled during crc32 calc
+ */
+static ULONG next_zeroed_range(struct input_file_info *fi, size_t index, ULONG old_file_size, ULONG *end)
+{
+    ULONG start = old_file_size;
+    ULONG end_i;
+    ULONG start_i;
+    ULONG end_r;
+    ULONG start_r;
+
+    *end = old_file_size;
+
+    start_i = next_ignored_range(fi, index, old_file_size, &end_i);
+    start_r = next_retained_range_old(fi, index, old_file_size, &end_r);
+
+    if (start_i < start_r)
+    {
+        start = start_i;
+        *end = end_i;
+        ++fi->next_i;
+    }
+    else
+    {
+        start = start_r;
+        *end = end_r;
+        ++fi->next_r;
+    }
+    return start;
+}
+
+/* Use the crc32 of the input file to match the file with an entry in the patch file table
+ */
+struct input_file_info *find_matching_old_file(const struct patch_file_header *ph, const BYTE *old_file_view, ULONG old_file_size)
+{
+    unsigned i;
+
+    for (i = 0; i < ph->input_file_count; ++i)
+    {
+        DWORD crc32 = 0;
+        ULONG index;
+
+        if (ph->file_table[i].input_size != old_file_size)
+            continue;
+
+        ph->file_table[i].next_i = 0;
+        for (index = 0; index < old_file_size; )
+        {
+            ULONG end;
+            ULONG start = next_zeroed_range(ph->file_table + i, index, old_file_size, &end);
+            crc32 = compute_crc32(crc32, old_file_view + index, start - index);
+            crc32 = compute_zero_crc32(crc32, end - start);
+            index = end;
+        }
+        if (ph->file_table[i].crc32 == crc32)
+            return ph->file_table + i;
+    }
+    return NULL;
+}
+
+/* Zero-fill ignored ranges in the old file data for decoder matching
+ */
+static void zero_fill_ignored_ranges(BYTE *old_file_buf, const struct input_file_info *fi)
+{
+    size_t i;
+    for (i = 0; i < fi->ignore_range_count; ++i)
+    {
+        memset(old_file_buf + fi->ignore_table[i].OffsetInOldFile,
+            0,
+            fi->ignore_table[i].LengthInBytes);
+    }
+}
+
+/* Zero-fill retained ranges in the old file data for decoder matching
+ */
+static void zero_fill_retained_ranges(BYTE *old_file_buf, BYTE *new_file_buf, const struct input_file_info *fi)
+{
+    size_t i;
+    for (i = 0; i < fi->retain_range_count; ++i)
+    {
+        memset(old_file_buf + fi->retain_table[i].OffsetInOldFile,
+            0,
+            fi->retain_table[i].LengthInBytes);
+    }
+}
+
+/* Copy the retained ranges to the new file buffer
+ */
+static void apply_retained_ranges(const BYTE *old_file_buf, BYTE *new_file_buf, const struct input_file_info *fi)
+{
+    size_t i;
+
+    if (old_file_buf == NULL)
+        return;
+
+    for (i = 0; i < fi->retain_range_count; ++i)
+    {
+        memcpy(new_file_buf + fi->retain_table[i].OffsetInNewFile,
+            old_file_buf + fi->retain_table[i].OffsetInOldFile,
+            fi->retain_table[i].LengthInBytes);
+    }
+}
+
+/* Compute the crc32 for the new file, assuming zero for the retained ranges
+ */
+static DWORD compute_target_crc32(struct input_file_info *fi, const BYTE *new_file_buf, ULONG new_file_size)
+{
+    DWORD crc32 = 0;
+    ULONG index;
+
+    qsort(fi->retain_table, fi->retain_range_count, sizeof(fi->retain_table[0]), compare_retained_range_new);
+    fi->next_r = 0;
+
+    for (index = 0; index < new_file_size; )
+    {
+        ULONG end;
+        ULONG start = next_retained_range_new(fi, index, new_file_size, &end);
+        ++fi->next_r;
+        crc32 = compute_crc32(crc32, new_file_buf + index, start - index);
+        crc32 = compute_zero_crc32(crc32, end - start);
+        index = end;
+    }
+    return crc32;
+}
+
+DWORD apply_patch_to_file_by_buffers(const BYTE *patch_file_view, const ULONG patch_file_size,
+    const BYTE *old_file_view, ULONG old_file_size,
+    BYTE **pnew_file_buf, const ULONG new_file_buf_size, ULONG *new_file_size,
+    FILETIME *new_file_time,
+    const ULONG apply_option_flags,
+    PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx,
+    const BOOL test_header_only)
+{
+    DWORD err = ERROR_SUCCESS;
+    struct input_file_info *file_info;
+    struct patch_file_header ph;
+    size_t buf_size;
+    BYTE *new_file_buf = NULL;
+    BYTE *decode_buf = NULL;
+
+    if (pnew_file_buf == NULL)
+    {
+        if (!test_header_only && !(apply_option_flags & APPLY_OPTION_TEST_ONLY))
+            return ERROR_INVALID_PARAMETER;
+    }
+    else
+    {
+        new_file_buf = *pnew_file_buf;
+    }
+
+    if (old_file_view == NULL)
+        old_file_size = 0;
+
+    if (read_header(&ph, patch_file_view, patch_file_size))
+    {
+        err = ph.err;
+        goto free_patch_header;
+    }
+
+    if (new_file_size != NULL)
+        *new_file_size = (ULONG)ph.patched_size;
+
+    if (new_file_buf != NULL && new_file_buf_size < ph.patched_size)
+    {
+        err = ERROR_INSUFFICIENT_BUFFER;
+        goto free_patch_header;
+    }
+
+    file_info = find_matching_old_file(&ph, old_file_view, old_file_size);
+    if (file_info == NULL)
+    {
+        err = ERROR_PATCH_WRONG_FILE;
+        goto free_patch_header;
+    }
+    if (file_info->input_size != old_file_size)
+    {
+        err = ERROR_PATCH_CORRUPT;
+        goto free_patch_header;
+    }
+    if (file_info->stream_size == 0 && (apply_option_flags & APPLY_OPTION_FAIL_IF_EXACT))
+    {
+        err = ERROR_PATCH_NOT_NECESSARY;
+        goto free_patch_header;
+    }
+    if (file_info->stream_size != 0
+        && file_info->input_size > ((ph.flags & PATCH_OPTION_USE_LZX_LARGE) ? MAX_LARGE_WINDOW : MAX_NORMAL_WINDOW))
+    {
+        /* interleaved by default but not the same as PATCH_OPTION_INTERLEAVE_FILES */
+        FIXME("interleaved LZXD decompression is not supported.\n");
+        err = ERROR_PATCH_PACKAGE_UNSUPPORTED;
+        goto free_patch_header;
+    }
+
+    if (test_header_only)
+        goto free_patch_header;
+
+    /* missing lzxd stream means it's a header test extract */
+    if (file_info->stream_start + file_info->stream_size > ph.end)
+    {
+        err = ERROR_PATCH_NOT_AVAILABLE;
+        goto free_patch_header;
+    }
+
+    buf_size = old_file_size + ph.patched_size;
+    decode_buf = new_file_buf;
+    if (new_file_buf == NULL || new_file_buf_size < buf_size)
+    {
+        /* decode_buf must have room for both files, so allocate a new buffer if
+         * necessary. This will be returned to the caller if new_file_buf == NULL */
+        decode_buf = VirtualAlloc(NULL, buf_size, MEM_COMMIT, PAGE_READWRITE);
+        if (decode_buf == NULL)
+        {
+            err = GetLastError();
+            goto free_patch_header;
+        }
+    }
+
+    if (old_file_view != NULL)
+        memcpy(decode_buf, old_file_view, file_info->input_size);
+
+    zero_fill_ignored_ranges(decode_buf, file_info);
+    zero_fill_retained_ranges(decode_buf, decode_buf + file_info->input_size, file_info);
+
+    if (file_info->stream_size != 0)
+    {
+        err = decode_lzxd_stream(file_info->stream_start, file_info->stream_size,
+            decode_buf, ph.patched_size, file_info->input_size,
+            ph.flags & PATCH_OPTION_USE_LZX_LARGE,
+            progress_fn, progress_ctx);
+    }
+    else if (file_info->input_size == ph.patched_size)
+    {
+        /* files are identical so copy old to new. copying is avoidable but rare */
+        memcpy(decode_buf + file_info->input_size, decode_buf, ph.patched_size);
+    }
+    else
+    {
+        err = ERROR_PATCH_CORRUPT;
+        goto free_decode_buf;
+    }
+
+    if(err != ERROR_SUCCESS)
+    {
+        if (err == ERROR_PATCH_DECODE_FAILURE)
+            FIXME("decode failure: data corruption or bug.\n");
+        goto free_decode_buf;
+    }
+
+    apply_retained_ranges(old_file_view, decode_buf + file_info->input_size, file_info);
+
+    if (ph.patched_crc32 != compute_target_crc32(file_info, decode_buf + file_info->input_size, ph.patched_size))
+    {
+        err = ERROR_PATCH_CORRUPT;
+        goto free_decode_buf;
+    }
+
+    /* retained ranges must be ignored for this test */
+    if ((apply_option_flags & APPLY_OPTION_FAIL_IF_EXACT)
+        && file_info->input_size == ph.patched_size
+        && memcmp(decode_buf, decode_buf + file_info->input_size, ph.patched_size) == 0)
+    {
+        err = ERROR_PATCH_NOT_NECESSARY;
+        goto free_decode_buf;
+    }
+
+    if (!(apply_option_flags & APPLY_OPTION_TEST_ONLY))
+    {
+        if (new_file_buf == NULL)
+        {
+            /* caller will VirtualFree the buffer */
+            new_file_buf = decode_buf;
+            *pnew_file_buf = new_file_buf;
+        }
+        memmove(new_file_buf, decode_buf + old_file_size, ph.patched_size);
+    }
+
+    if (new_file_time != NULL)
+    {
+        new_file_time->dwLowDateTime = 0;
+        new_file_time->dwHighDateTime = 0;
+
+        /* the meaning of PATCH_OPTION_NO_TIMESTAMP is inverted for decoding */
+        if (ph.flags & PATCH_OPTION_NO_TIMESTAMP)
+            posix_time_to_file_time(ph.timestamp, new_file_time);
+    }
+
+free_decode_buf:
+    if(decode_buf != NULL && decode_buf != new_file_buf)
+        VirtualFree(decode_buf, 0, MEM_RELEASE);
+
+free_patch_header:
+    free_header(&ph);
+
+    return err;
+}
+
+BOOL apply_patch_to_file_by_handles(HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl,
+    const ULONG apply_option_flags,
+    PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx,
+    const BOOL test_header_only)
+{
+    LARGE_INTEGER patch_size;
+    LARGE_INTEGER old_size;
+    HANDLE patch_map;
+    HANDLE old_map = NULL;
+    BYTE *patch_buf;
+    const BYTE *old_buf = NULL;
+    BYTE *new_buf = NULL;
+    ULONG new_size;
+    FILETIME new_time;
+    BOOL res = FALSE;
+    DWORD err = ERROR_SUCCESS;
+
+    /* truncate the output file if required, or set the handle to invalid */
+    if (test_header_only || (apply_option_flags & APPLY_OPTION_TEST_ONLY))
+    {
+        new_file_hndl = INVALID_HANDLE_VALUE;
+    }
+    else if (SetFilePointer(new_file_hndl, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER
+        || !SetEndOfFile(new_file_hndl))
+    {
+        err = GetLastError();
+        return FALSE;
+    }
+
+    if (patch_file_hndl == INVALID_HANDLE_VALUE)
+    {
+        SetLastError(ERROR_INVALID_HANDLE);
+        return FALSE;
+    }
+
+    old_size.QuadPart = 0;
+    if (!GetFileSizeEx(patch_file_hndl, &patch_size)
+        || (old_file_hndl != INVALID_HANDLE_VALUE && !GetFileSizeEx(old_file_hndl, &old_size)))
+    {
+        /* Last error set by API */
+        return FALSE;
+    }
+
+    patch_map = CreateFileMappingW(patch_file_hndl, NULL, PAGE_READONLY, 0, 0, NULL);
+    if (patch_map == NULL)
+    {
+        /* Last error set by API */
+        return FALSE;
+    }
+
+    if (old_file_hndl != INVALID_HANDLE_VALUE)
+    {
+        old_map = CreateFileMappingW(old_file_hndl, NULL, PAGE_READONLY, 0, 0, NULL);
+        if (old_map == NULL)
+        {
+            err = GetLastError();
+            goto close_patch_map;
+        }
+    }
+
+    patch_buf = MapViewOfFile(patch_map, FILE_MAP_READ, 0, 0, (SIZE_T)patch_size.QuadPart);
+    if (patch_buf == NULL)
+    {
+        err = GetLastError();
+        goto close_old_map;
+    }
+
+    if (old_size.QuadPart)
+    {
+        old_buf = MapViewOfFile(old_map, FILE_MAP_READ, 0, 0, (SIZE_T)old_size.QuadPart);
+        if (old_buf == NULL)
+        {
+            err = GetLastError();
+            goto unmap_patch_buf;
+        }
+    }
+
+    err = apply_patch_to_file_by_buffers(patch_buf, (ULONG)patch_size.QuadPart,
+        old_buf, (ULONG)old_size.QuadPart,
+        &new_buf, 0, &new_size,
+        &new_time,
+        apply_option_flags, progress_fn, progress_ctx,
+        test_header_only);
+
+    if(err)
+        goto free_new_buf;
+
+    res = TRUE;
+
+    if(new_file_hndl != INVALID_HANDLE_VALUE)
+    {
+        DWORD Written = 0;
+        res = WriteFile(new_file_hndl, new_buf, new_size, &Written, NULL);
+
+        if (!res)
+            err = GetLastError();
+        else if (new_time.dwLowDateTime || new_time.dwHighDateTime)
+            SetFileTime(new_file_hndl, &new_time, NULL, &new_time);
+    }
+
+free_new_buf:
+    if (new_buf != NULL)
+        VirtualFree(new_buf, 0, MEM_RELEASE);
+
+    if (old_buf != NULL)
+        UnmapViewOfFile(old_buf);
+
+unmap_patch_buf:
+    UnmapViewOfFile(patch_buf);
+
+close_old_map:
+    if (old_map != NULL)
+        CloseHandle(old_map);
+
+close_patch_map:
+    CloseHandle(patch_map);
+
+    SetLastError(err);
+
+    return res;
+}
+
+BOOL apply_patch_to_file(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name,
+    const ULONG apply_option_flags,
+    PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx,
+    const BOOL test_header_only)
+{
+    HANDLE patch_hndl;
+    HANDLE old_hndl = INVALID_HANDLE_VALUE;
+    HANDLE new_hndl = INVALID_HANDLE_VALUE;
+    BOOL res = FALSE;
+    DWORD err = ERROR_SUCCESS;
+
+    patch_hndl = CreateFileW(patch_file_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+    if (patch_hndl == INVALID_HANDLE_VALUE)
+    {
+        /* last error set by CreateFileW */
+        return FALSE;
+    }
+
+    if (old_file_name != NULL)
+    {
+        old_hndl = CreateFileW(old_file_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+        if (old_hndl == INVALID_HANDLE_VALUE)
+        {
+            err = GetLastError();
+            goto close_patch_file;
+        }
+    }
+
+    if (!test_header_only && !(apply_option_flags & APPLY_OPTION_TEST_ONLY))
+    {
+        new_hndl = CreateFileW(new_file_name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
+        if (new_hndl == INVALID_HANDLE_VALUE)
+        {
+            err = GetLastError();
+            goto close_old_file;
+        }
+    }
+
+    res = apply_patch_to_file_by_handles(patch_hndl, old_hndl, new_hndl, apply_option_flags, progress_fn, progress_ctx, test_header_only);
+    if(!res)
+        err = GetLastError();
+
+    if (new_hndl != INVALID_HANDLE_VALUE)
+        CloseHandle(new_hndl);
+
+close_old_file:
+    if (old_hndl != INVALID_HANDLE_VALUE)
+        CloseHandle(old_hndl);
+
+close_patch_file:
+    CloseHandle(patch_hndl);
+
+    /* set last error even on success as per windows */
+    SetLastError(err);
+
+    return res;
+}
diff --git a/dlls/mspatcha/pa19.h b/dlls/mspatcha/pa19.h
new file mode 100755
index 0000000..9a9d0f3
--- /dev/null
+++ b/dlls/mspatcha/pa19.h
@@ -0,0 +1,52 @@
+/*
+ * PatchAPI PA19 file format handlers
+ *
+ * Copyright 2019 Conor McCarthy
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+DWORD apply_patch_to_file_by_buffers(const BYTE *patch_file_view, const ULONG patch_file_size,
+    const BYTE *old_file_view, ULONG old_file_size,
+    BYTE **new_file_buf, const ULONG new_file_buf_size, ULONG *new_file_size,
+    FILETIME *new_file_time,
+    const ULONG apply_option_flags,
+    PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx,
+    const BOOL test_header_only);
+
+BOOL apply_patch_to_file_by_handles(HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl,
+    const ULONG apply_option_flags,
+    PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx,
+    const BOOL test_header_only);
+
+BOOL apply_patch_to_file(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name,
+    const ULONG apply_option_flags,
+    PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx,
+    const BOOL test_header_only);
diff --git a/include/patchapi.h b/include/patchapi.h
index 5adaf0a..1ccdf9d 100644
--- a/include/patchapi.h
+++ b/include/patchapi.h
@@ -1,5 +1,6 @@
 /*
  * Copyright 2011 Hans Leidekker for CodeWeavers
+ * Copyright 2019 Conor McCarthy
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -23,11 +24,33 @@
 extern "C" {
 #endif
 
+#define PATCH_OPTION_USE_LZX_A        0x00000001
+#define PATCH_OPTION_USE_LZX_B        0x00000002
+#define PATCH_OPTION_USE_LZX_LARGE    0x00000004 /* raise maximum window from 8 -> 32 Mb */
+
+#define PATCH_OPTION_NO_BINDFIX        0x00010000
+#define PATCH_OPTION_NO_LOCKFIX        0x00020000
+#define PATCH_OPTION_NO_REBASE         0x00040000
+#define PATCH_OPTION_FAIL_IF_SAME_FILE 0x00080000
+#define PATCH_OPTION_FAIL_IF_BIGGER    0x00100000
+#define PATCH_OPTION_NO_CHECKSUM       0x00200000
+#define PATCH_OPTION_NO_RESTIMEFIX     0x00400000
+#define PATCH_OPTION_NO_TIMESTAMP      0x00800000
+#define PATCH_OPTION_INTERLEAVE_FILES  0x40000000
+#define PATCH_OPTION_RESERVED1         0x80000000
+
 #define APPLY_OPTION_FAIL_IF_EXACT  0x00000001
 #define APPLY_OPTION_FAIL_IF_CLOSE  0x00000002
 #define APPLY_OPTION_TEST_ONLY      0x00000004
 #define APPLY_OPTION_VALID_FLAGS    0x00000007
 
+#define ERROR_PATCH_DECODE_FAILURE 0xC00E4101
+#define ERROR_PATCH_CORRUPT        0xC00E4102
+#define ERROR_PATCH_NEWER_FORMAT   0xC00E4103
+#define ERROR_PATCH_WRONG_FILE     0xC00E4104
+#define ERROR_PATCH_NOT_NECESSARY  0xC00E4105
+#define ERROR_PATCH_NOT_AVAILABLE  0xC00E4106
+
 typedef struct _PATCH_IGNORE_RANGE
 {
     ULONG OffsetInOldFile;
@@ -41,16 +64,66 @@ typedef struct _PATCH_RETAIN_RANGE
     ULONG OffsetInNewFile;
 } PATCH_RETAIN_RANGE, *PPATCH_RETAIN_RANGE;
 
+typedef struct _PATCH_INTERLEAVE_MAP {
+    ULONG CountRanges;
+    struct {
+        ULONG OldOffset;
+        ULONG OldLength;
+        ULONG NewLength;
+    } Range[1];
+} PATCH_INTERLEAVE_MAP, *PPATCH_INTERLEAVE_MAP;
+
+typedef BOOL(CALLBACK PATCH_SYMLOAD_CALLBACK)(ULONG, LPCSTR, ULONG, ULONG, ULONG, ULONG, ULONG, PVOID);
+
+typedef PATCH_SYMLOAD_CALLBACK *PPATCH_SYMLOAD_CALLBACK;
+
+typedef struct _PATCH_OPTION_DATA {
+    ULONG SizeOfThisStruct;
+    ULONG SymbolOptionFlags;
+    LPCSTR NewFileSymbolPath;
+    LPCSTR *OldFileSymbolPathArray;
+    ULONG ExtendedOptionFlags;
+    PPATCH_SYMLOAD_CALLBACK SymLoadCallback;
+    PVOID SymLoadContext;
+    PPATCH_INTERLEAVE_MAP* InterleaveMapArray;
+    ULONG MaxLzxWindowSize;
+} PATCH_OPTION_DATA, *PPATCH_OPTION_DATA;
+
+typedef BOOL (CALLBACK PATCH_PROGRESS_CALLBACK)(PVOID, ULONG, ULONG);
+
+typedef PATCH_PROGRESS_CALLBACK *PPATCH_PROGRESS_CALLBACK;
+
 BOOL WINAPI ApplyPatchToFileA(LPCSTR,LPCSTR,LPCSTR,ULONG);
 BOOL WINAPI ApplyPatchToFileW(LPCWSTR,LPCWSTR,LPCWSTR,ULONG);
 #define     ApplyPatchToFile WINELIB_NAME_AW(ApplyPatchToFile)
 
+BOOL WINAPI ApplyPatchToFileByHandles(HANDLE, HANDLE, HANDLE, ULONG);
+BOOL WINAPI ApplyPatchToFileExA(LPCSTR, LPCSTR, LPCSTR, ULONG, PPATCH_PROGRESS_CALLBACK, PVOID);
+BOOL WINAPI ApplyPatchToFileExW(LPCWSTR, LPCWSTR, LPCWSTR, ULONG, PPATCH_PROGRESS_CALLBACK, PVOID);
+#define     ApplyPatchToFileEx WINELIB_NAME_AW(ApplyPatchToFileEx)
+BOOL WINAPI ApplyPatchToFileByHandlesEx(HANDLE, HANDLE, HANDLE, ULONG, PPATCH_PROGRESS_CALLBACK, PVOID);
+BOOL WINAPI ApplyPatchToFileByBuffers(PBYTE, ULONG, PBYTE, ULONG, PBYTE*, ULONG, ULONG*, FILETIME*, ULONG,
+                                      PPATCH_PROGRESS_CALLBACK, PVOID);
+
+BOOL WINAPI TestApplyPatchToFileA(LPCSTR, LPCSTR, ULONG);
+BOOL WINAPI TestApplyPatchToFileW(LPCWSTR, LPCWSTR, ULONG);
+#define     TestApplyPatchToFile WINELIB_NAME_AW(TestApplyPatchToFile)
+BOOL WINAPI TestApplyPatchToFileByHandles(HANDLE, HANDLE, ULONG);
+BOOL WINAPI TestApplyPatchToFileByBuffers(PBYTE, ULONG, PBYTE, ULONG, ULONG*, ULONG);
+
 BOOL WINAPI GetFilePatchSignatureA(LPCSTR, ULONG, PVOID, ULONG, PPATCH_IGNORE_RANGE, ULONG,
                                    PPATCH_RETAIN_RANGE, ULONG, LPSTR);
 BOOL WINAPI GetFilePatchSignatureW(LPCWSTR, ULONG, PVOID, ULONG, PPATCH_IGNORE_RANGE, ULONG,
                                    PPATCH_RETAIN_RANGE, ULONG, LPWSTR);
 #define     GetFilePatchSignature WINELIB_NAME_AW(GetFilePatchSignature)
 
+BOOL WINAPI GetFilePatchSignatureByHandle(HANDLE, ULONG, PVOID, ULONG, PPATCH_IGNORE_RANGE,
+                                          ULONG, PPATCH_RETAIN_RANGE, ULONG, LPSTR);
+BOOL WINAPI GetFilePatchSignatureByBuffer(PBYTE, ULONG, ULONG, PVOID, ULONG, PPATCH_IGNORE_RANGE, ULONG,
+                                          PPATCH_RETAIN_RANGE, ULONG, LPSTR);
+INT WINAPI NormalizeFileForPatchSignature(PVOID, ULONG, ULONG, PATCH_OPTION_DATA*, ULONG,
+                                          ULONG, ULONG, PPATCH_IGNORE_RANGE, ULONG, PPATCH_RETAIN_RANGE);
+
 #ifdef __cplusplus
 }
 #endif
-- 
2.7.4




More information about the wine-devel mailing list