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

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

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

Conor McCarthy
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=12501
Signed-off-by: Conor McCarthy <[hidden email]>
---
 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



Reply | Threaded
Open this post in threaded view
|

[PATCH 2/3] mspatcha: Implement ApplyPatchToFile and related functions.

Conor McCarthy
This implementation can patch non-executables and 64-bit executable files, but patching of 32-bit
executables is not supported. Those are subject to special processing in which relocatable
function calls are altered to match with those in the old file to improve compression. To reverse
this, the meaning of the decoding data must be interpreted. Details, including where to find that
data in the patch file, are included in pa19.c. Interleaved decompression of large files is also
not supported.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=12501
Signed-off-by: Conor McCarthy <[hidden email]>
---
 dlls/mspatcha/mspatcha.spec   |  28 +++--
 dlls/mspatcha/mspatcha_main.c | 254 ++++++++++++++++++++++++++++++++++++------
 2 files changed, 235 insertions(+), 47 deletions(-)

diff --git a/dlls/mspatcha/mspatcha.spec b/dlls/mspatcha/mspatcha.spec
index cc8ca94..8c014b9 100644
--- a/dlls/mspatcha/mspatcha.spec
+++ b/dlls/mspatcha/mspatcha.spec
@@ -1,12 +1,16 @@
-1 stdcall ApplyPatchToFileA(str str str long)
-2 stub ApplyPatchToFileByHandles
-3 stub ApplyPatchToFileByHandlesEx
-4 stub ApplyPatchToFileExA
-5 stub ApplyPatchToFileExW
-6 stdcall ApplyPatchToFileW(wstr wstr wstr long)
-7 stdcall GetFilePatchSignatureA(str long ptr long ptr long ptr long ptr)
-8 stub GetFilePatchSignatureByHandle
-9 stdcall GetFilePatchSignatureW(wstr long ptr long ptr long ptr long ptr)
-10 stub TestApplyPatchToFileA
-11 stub TestApplyPatchToFileByHandles
-12 stub TestApplyPatchToFileW
+@ stdcall ApplyPatchToFileA(str str str long)
+@ stdcall ApplyPatchToFileByBuffers(ptr long ptr long ptr long ptr ptr long ptr ptr)
+@ stdcall ApplyPatchToFileByHandles(ptr ptr ptr long)
+@ stdcall ApplyPatchToFileByHandlesEx(ptr ptr ptr long ptr ptr)
+@ stdcall ApplyPatchToFileExA(str str str long ptr ptr)
+@ stdcall ApplyPatchToFileExW(wstr wstr wstr long ptr ptr)
+@ stdcall ApplyPatchToFileW(wstr wstr wstr long)
+@ stdcall GetFilePatchSignatureA(str long ptr long ptr long ptr long str)
+@ stdcall GetFilePatchSignatureByBuffer(ptr long long ptr long ptr long ptr long str)
+@ stdcall GetFilePatchSignatureByHandle(ptr long ptr long ptr long ptr long str)
+@ stdcall GetFilePatchSignatureW(wstr long ptr long ptr long ptr long wstr)
+@ stdcall NormalizeFileForPatchSignature(ptr long long ptr long long long ptr long ptr)
+@ stdcall TestApplyPatchToFileA(str str long)
+@ stdcall TestApplyPatchToFileByBuffers(ptr long ptr long ptr long)
+@ stdcall TestApplyPatchToFileByHandles(ptr ptr long)
+@ stdcall TestApplyPatchToFileW(wstr wstr long)
diff --git a/dlls/mspatcha/mspatcha_main.c b/dlls/mspatcha/mspatcha_main.c
index f78b8ab..deeb5e9 100644
--- a/dlls/mspatcha/mspatcha_main.c
+++ b/dlls/mspatcha/mspatcha_main.c
@@ -2,6 +2,7 @@
  * PatchAPI
  *
  * Copyright 2011 David Hedberg for CodeWeavers
+ * Copyright 2019 Conor McCarthy (implementations)
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -16,6 +17,31 @@
  * 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
+ *  - Special processing of 32-bit executables is not supported, so this
+ *    version cannot patch 32-bit .exe and .dll files. See pa19.c for details.
+ *  - Implement interleaved decoding when PATCH_OPTION_INTERLEAVE_FILES was
+ *    used or the old file exceeds the lzxd window size.
+ *  - APPLY_OPTION_FAIL_IF_CLOSE is ignored. Normalization of 32-bit PE files
+ *    is required for checking this.
+ *  - GetFilePatchSignature* and NormalizeFileForPatchSignature require a
+ *    solution to the above 32-bit exe problem.
  */
 
 #include "config.h"
@@ -28,6 +54,8 @@
 #include "patchapi.h"
 #include "wine/debug.h"
 
+#include "pa19.h"
+
 WINE_DEFAULT_DEBUG_CHANNEL(mspatcha);
 
 /*****************************************************
@@ -39,89 +67,245 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
 
     switch (fdwReason)
     {
-        case DLL_WINE_PREATTACH:
-            return FALSE;    /* prefer native version */
-        case DLL_PROCESS_ATTACH:
-            DisableThreadLibraryCalls(hinstDLL);
-            break;
+    case DLL_WINE_PREATTACH:
+        return FALSE;    /* prefer native version */
+    case DLL_PROCESS_ATTACH:
+        DisableThreadLibraryCalls(hinstDLL);
+        break;
     }
 
     return TRUE;
 }
 
-static inline WCHAR *strdupAW( const char *src )
+static WCHAR *strdupAW(const char *src)
 {
     WCHAR *dst = NULL;
     if (src)
     {
-        int len = MultiByteToWideChar( CP_ACP, 0, src, -1, NULL, 0 );
-        if ((dst = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) )))
-            MultiByteToWideChar( CP_ACP, 0, src, -1, dst, len );
+        int len = MultiByteToWideChar(CP_ACP, 0, src, -1, NULL, 0);
+        if ((dst = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR))))
+            MultiByteToWideChar(CP_ACP, 0, src, -1, dst, len);
     }
     return dst;
 }
 
 /*****************************************************
- *    ApplyPatchToFileA (MSPATCHA.1)
+ *    TestApplyPatchToFileA (MSPATCHA.@)
  */
-BOOL WINAPI ApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, LPCSTR new_file, ULONG apply_flags)
+BOOL WINAPI TestApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, ULONG apply_flags)
+{
+    BOOL ret;
+    WCHAR *patch_fileW, *old_fileW = NULL;
+
+    if (!(patch_fileW = strdupAW(patch_file))) return FALSE;
+    if (old_file && !(old_fileW = strdupAW(old_file)))
+    {
+        HeapFree(GetProcessHeap(), 0, patch_fileW);
+        return FALSE;
+    }
+    ret = apply_patch_to_file(patch_fileW, old_fileW, NULL, apply_flags, NULL, NULL, TRUE);
+    HeapFree(GetProcessHeap(), 0, patch_fileW);
+    HeapFree(GetProcessHeap(), 0, old_fileW);
+    return ret;
+}
+
+BOOL WINAPI TestApplyPatchToFileW(LPCWSTR patch_file_name, LPCWSTR old_file_name, ULONG apply_option_flags)
+{
+    return apply_patch_to_file(patch_file_name, old_file_name, NULL, apply_option_flags, NULL, NULL, TRUE);
+}
+
+BOOL WINAPI TestApplyPatchToFileByHandles(HANDLE patch_file_hndl, HANDLE old_file_hndl, ULONG apply_option_flags)
+{
+    return apply_patch_to_file_by_handles(patch_file_hndl, old_file_hndl, NULL,
+        apply_option_flags, NULL, NULL, TRUE);
+}
+
+BOOL WINAPI TestApplyPatchToFileByBuffers(BYTE *patch_file_buf, ULONG patch_file_size,
+    BYTE *old_file_buf, ULONG old_file_size,
+    ULONG* new_file_size,
+    ULONG  apply_option_flags)
+{
+    /* preserve last error on success as per windows */
+    DWORD last = GetLastError();
+
+    DWORD err = apply_patch_to_file_by_buffers(patch_file_buf, patch_file_size,
+        old_file_buf, old_file_size,
+        NULL, 0, new_file_size, NULL,
+        apply_option_flags,
+        NULL, NULL,
+        TRUE);
+
+    SetLastError(err == ERROR_SUCCESS ? last : err);
+
+    return err == ERROR_SUCCESS;
+}
+
+/*****************************************************
+ *    ApplyPatchToFileExA (MSPATCHA.@)
+ */
+BOOL WINAPI ApplyPatchToFileExA(LPCSTR patch_file, LPCSTR old_file, LPCSTR new_file, ULONG apply_flags,
+    PPATCH_PROGRESS_CALLBACK progress_fn, PVOID progress_ctx)
 {
     BOOL ret;
     WCHAR *patch_fileW, *new_fileW, *old_fileW = NULL;
 
-    if (!(patch_fileW = strdupAW( patch_file ))) return FALSE;
-    if (old_file && !(old_fileW = strdupAW( old_file )))
+    if (!(patch_fileW = strdupAW(patch_file))) return FALSE;
+    if (old_file && !(old_fileW = strdupAW(old_file)))
     {
-        HeapFree( GetProcessHeap(), 0, patch_fileW );
+        HeapFree(GetProcessHeap(), 0, patch_fileW);
         return FALSE;
     }
-    if (!(new_fileW = strdupAW( new_file )))
+    if (!(new_fileW = strdupAW(new_file)))
     {
-        HeapFree( GetProcessHeap(), 0, patch_fileW );
-        HeapFree( GetProcessHeap(), 0, old_fileW );
+        HeapFree(GetProcessHeap(), 0, patch_fileW);
+        HeapFree(GetProcessHeap(), 0, old_fileW);
         return FALSE;
     }
-    ret = ApplyPatchToFileW( patch_fileW, old_fileW, new_fileW, apply_flags );
-    HeapFree( GetProcessHeap(), 0, patch_fileW );
-    HeapFree( GetProcessHeap(), 0, old_fileW );
-    HeapFree( GetProcessHeap(), 0, new_fileW );
+    ret = apply_patch_to_file(patch_fileW, old_fileW, new_fileW, apply_flags, progress_fn, progress_ctx, FALSE);
+    HeapFree(GetProcessHeap(), 0, patch_fileW);
+    HeapFree(GetProcessHeap(), 0, old_fileW);
+    HeapFree(GetProcessHeap(), 0, new_fileW);
     return ret;
 }
 
 /*****************************************************
- *    ApplyPatchToFileW (MSPATCHA.6)
+ *    ApplyPatchToFileA (MSPATCHA.@)
  */
-BOOL WINAPI ApplyPatchToFileW(LPCWSTR patch_file, LPCWSTR old_file, LPCWSTR new_file, ULONG apply_flags)
+BOOL WINAPI ApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, LPCSTR new_file, ULONG apply_flags)
 {
-    FIXME("stub - %s, %s, %s, %08x\n", debugstr_w(patch_file), debugstr_w(old_file),
-          debugstr_w(new_file), apply_flags);
+    return ApplyPatchToFileExA(patch_file, old_file, new_file, apply_flags, NULL, NULL);
+}
 
-    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
-    return FALSE;
+/*****************************************************
+ *    ApplyPatchToFileW (MSPATCHA.@)
+ */
+BOOL WINAPI ApplyPatchToFileW(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name,
+    ULONG apply_option_flags)
+{
+    return apply_patch_to_file(patch_file_name, old_file_name, new_file_name, apply_option_flags,
+        NULL, NULL, FALSE);
+}
+
+/*****************************************************
+ *    ApplyPatchToFileByHandles (MSPATCHA.@)
+ */
+BOOL WINAPI ApplyPatchToFileByHandles(HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl,
+    ULONG apply_option_flags)
+{
+    return apply_patch_to_file_by_handles(patch_file_hndl, old_file_hndl, new_file_hndl,
+        apply_option_flags, NULL, NULL, FALSE);
+}
+
+/*****************************************************
+ *    ApplyPatchToFileExW (MSPATCHA.@)
+ */
+BOOL WINAPI ApplyPatchToFileExW(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name,
+    ULONG apply_option_flags,
+    PPATCH_PROGRESS_CALLBACK progress_fn, PVOID progress_ctx)
+{
+    return apply_patch_to_file(patch_file_name, old_file_name, new_file_name, apply_option_flags,
+        progress_fn, progress_ctx, FALSE);
 }
 
 /*****************************************************
- *    GetFilePatchSignatureA (MSPATCHA.7)
+ *    ApplyPatchToFileByHandlesEx (MSPATCHA.@)
+ */
+BOOL WINAPI ApplyPatchToFileByHandlesEx(HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl,
+    ULONG apply_option_flags,
+    PPATCH_PROGRESS_CALLBACK progress_fn,
+    PVOID progress_ctx)
+{
+    return apply_patch_to_file_by_handles(patch_file_hndl, old_file_hndl, new_file_hndl,
+        apply_option_flags, progress_fn, progress_ctx, FALSE);
+}
+
+/*****************************************************
+ *    ApplyPatchToFileByBuffers (MSPATCHA.@)
+ */
+BOOL WINAPI ApplyPatchToFileByBuffers(PBYTE patch_file_view, ULONG  patch_file_size,
+    PBYTE  old_file_view, ULONG  old_file_size,
+    PBYTE* new_file_buf, ULONG  new_file_buf_size, ULONG* new_file_size,
+    FILETIME* new_file_time,
+    ULONG  apply_option_flags,
+    PPATCH_PROGRESS_CALLBACK progress_fn, PVOID  progress_ctx)
+{
+    /* preserve last error on success as per windows */
+    DWORD last = GetLastError();
+
+    DWORD err = apply_patch_to_file_by_buffers(patch_file_view, patch_file_size,
+        old_file_view, old_file_size,
+        new_file_buf, new_file_buf_size, new_file_size, new_file_time,
+        apply_option_flags,
+        progress_fn, progress_ctx,
+        FALSE);
+
+    SetLastError(err == ERROR_SUCCESS ? last : err);
+
+    return err == ERROR_SUCCESS;
+}
+
+/*****************************************************
+ *    GetFilePatchSignatureA (MSPATCHA.@)
  */
 BOOL WINAPI GetFilePatchSignatureA(LPCSTR filename, ULONG flags, PVOID data, ULONG ignore_range_count,
-                                   PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count,
-                                   PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, LPSTR buffer)
+    PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count,
+    PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, LPSTR buffer)
 {
     FIXME("stub - %s, %x, %p, %u, %p, %u, %p, %u, %p\n", debugstr_a(filename), flags, data,
-          ignore_range_count, ignore_range, retain_range_count, retain_range, bufsize, buffer);
+        ignore_range_count, ignore_range, retain_range_count, retain_range, bufsize, buffer);
     SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
     return FALSE;
 }
 
 /*****************************************************
- *    GetFilePatchSignatureW (MSPATCHA.9)
+ *    GetFilePatchSignatureW (MSPATCHA.@)
  */
 BOOL WINAPI GetFilePatchSignatureW(LPCWSTR filename, ULONG flags, PVOID data, ULONG ignore_range_count,
-                                   PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count,
-                                   PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, LPWSTR buffer)
+    PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count,
+    PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, LPWSTR buffer)
 {
     FIXME("stub - %s, %x, %p, %u, %p, %u, %p, %u, %p\n", debugstr_w(filename), flags, data,
-          ignore_range_count, ignore_range, retain_range_count, retain_range, bufsize, buffer);
+        ignore_range_count, ignore_range, retain_range_count, retain_range, bufsize, buffer);
+    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+    return FALSE;
+}
+
+/*****************************************************
+ *    GetFilePatchSignatureByHandle (MSPATCHA.@)
+ */
+BOOL WINAPI GetFilePatchSignatureByHandle(HANDLE handle, ULONG flags, PVOID options, ULONG ignore_range_count,
+    PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count,
+    PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, LPSTR buffer)
+{
+    FIXME("stub - %p, %x, %p, %u, %p, %u, %p, %u, %p\n", handle, flags, options,
+        ignore_range_count, ignore_range, retain_range_count, retain_range, bufsize, buffer);
+    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+    return FALSE;
+}
+
+/*****************************************************
+ *    GetFilePatchSignatureByBuffer (MSPATCHA.@)
+ */
+BOOL WINAPI GetFilePatchSignatureByBuffer(PBYTE file_buf, ULONG file_size, ULONG flags, PVOID options,
+    ULONG ignore_range_count, PPATCH_IGNORE_RANGE ignore_range,
+    ULONG retain_range_count, PPATCH_RETAIN_RANGE retain_range,
+    ULONG bufsize, LPSTR buffer)
+{
+    FIXME("stub - %p, %u, %x, %p, %u, %p, %u, %p, %u, %p\n", file_buf, file_size, flags, options,
+        ignore_range_count, ignore_range, retain_range_count, retain_range, bufsize, buffer);
     SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
     return FALSE;
 }
+
+/*****************************************************
+ *    NormalizeFileForPatchSignature (MSPATCHA.@)
+ */
+INT WINAPI NormalizeFileForPatchSignature(PVOID file_buffer, ULONG file_size, ULONG flags, PATCH_OPTION_DATA *options,
+    ULONG new_coff_base, ULONG new_coff_time, ULONG ignore_range_count, PPATCH_IGNORE_RANGE ignore_range,
+    ULONG retain_range_count, PPATCH_RETAIN_RANGE retain_range)
+{
+    FIXME("stub - %p, %u, %x, %p, %u, %u, %u, %p, %u, %p\n", file_buffer, file_size, flags, options, new_coff_base,
+        new_coff_time, ignore_range_count, ignore_range, retain_range_count, retain_range);
+    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+    return 0;
+}
--
2.7.4



Reply | Threaded
Open this post in threaded view
|

[PATCH 3/3] mspatcha: Test implementations of ApplyPatchToFile and related functions.

Conor McCarthy
In reply to this post by Conor McCarthy
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=12501
Signed-off-by: Conor McCarthy <[hidden email]>
---
 configure                         |   1 +
 configure.ac                      |   1 +
 dlls/mspatcha/tests/Makefile.in   |   5 +
 dlls/mspatcha/tests/patch_apply.c | 400 ++++++++++++++++++++++++++++++++++++++
 dlls/mspatcha/tests/test_files.h  | 211 ++++++++++++++++++++
 5 files changed, 618 insertions(+)
 create mode 100755 dlls/mspatcha/tests/Makefile.in
 create mode 100755 dlls/mspatcha/tests/patch_apply.c
 create mode 100755 dlls/mspatcha/tests/test_files.h

diff --git a/configure b/configure
index c21c70d..c31f095 100755
--- a/configure
+++ b/configure
@@ -20307,6 +20307,7 @@ wine_fn_config_makefile dlls/msisys.ocx enable_msisys_ocx
 wine_fn_config_makefile dlls/msls31 enable_msls31
 wine_fn_config_makefile dlls/msnet32 enable_msnet32
 wine_fn_config_makefile dlls/mspatcha enable_mspatcha
+wine_fn_config_makefile dlls/mspatcha/tests enable_tests
 wine_fn_config_makefile dlls/msports enable_msports
 wine_fn_config_makefile dlls/msrle32 enable_msrle32
 wine_fn_config_makefile dlls/msrle32/tests enable_tests
diff --git a/configure.ac b/configure.ac
index 02e7e9e..478c8a4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3465,6 +3465,7 @@ WINE_CONFIG_MAKEFILE(dlls/msisys.ocx)
 WINE_CONFIG_MAKEFILE(dlls/msls31)
 WINE_CONFIG_MAKEFILE(dlls/msnet32)
 WINE_CONFIG_MAKEFILE(dlls/mspatcha)
+WINE_CONFIG_MAKEFILE(dlls/mspatcha/tests)
 WINE_CONFIG_MAKEFILE(dlls/msports)
 WINE_CONFIG_MAKEFILE(dlls/msrle32)
 WINE_CONFIG_MAKEFILE(dlls/msrle32/tests)
diff --git a/dlls/mspatcha/tests/Makefile.in b/dlls/mspatcha/tests/Makefile.in
new file mode 100755
index 0000000..f55b67f
--- /dev/null
+++ b/dlls/mspatcha/tests/Makefile.in
@@ -0,0 +1,5 @@
+TESTDLL   = mspatcha.dll
+IMPORTS   = mspatcha kernel32
+
+C_SRCS = \
+ patch_apply.c
diff --git a/dlls/mspatcha/tests/patch_apply.c b/dlls/mspatcha/tests/patch_apply.c
new file mode 100755
index 0000000..818c307
--- /dev/null
+++ b/dlls/mspatcha/tests/patch_apply.c
@@ -0,0 +1,400 @@
+/*
+ * Unit tests for Patch API functions
+ *
+ * Copyright (c) 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
+ *
+ * NOTES
+ *
+ * Until mspatchc.dll is implemented, the inability to create test patch files
+ * under Wine limits testing to the supplied small files.
+ */
+
+#include "wine/test.h"
+#include "windef.h"
+#include "winbase.h"
+#include "winerror.h"
+
+#include "patchapi.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "test_files.h"
+
+static BOOL (WINAPI *pApplyPatchToFileA)(LPCSTR, LPCSTR, LPCSTR, ULONG);
+static BOOL (WINAPI *pApplyPatchToFileByHandles)(HANDLE, HANDLE, HANDLE, ULONG);
+static BOOL (WINAPI *pApplyPatchToFileExA)(LPCSTR, LPCSTR, LPCSTR, ULONG, PPATCH_PROGRESS_CALLBACK, PVOID);
+static BOOL (WINAPI *pApplyPatchToFileByHandlesEx)(HANDLE, HANDLE, HANDLE, ULONG, PPATCH_PROGRESS_CALLBACK, PVOID);
+static BOOL (WINAPI *pApplyPatchToFileByBuffers)(PBYTE, ULONG, PBYTE, ULONG, PBYTE*, ULONG, ULONG*, FILETIME*, ULONG, PPATCH_PROGRESS_CALLBACK, PVOID);
+static BOOL (WINAPI *pTestApplyPatchToFileA)(LPCSTR, LPCSTR, ULONG);
+static BOOL (WINAPI *pTestApplyPatchToFileByHandles)(HANDLE, HANDLE, ULONG);
+static BOOL (WINAPI *pTestApplyPatchToFileByBuffers)(PBYTE, ULONG, PBYTE, ULONG, ULONG*, ULONG);
+
+
+static BOOL init_function_pointers(void)
+{
+    HMODULE mspatcha = LoadLibraryA("mspatcha.dll");
+    if (!mspatcha)
+    {
+        win_skip("mspatcha.dll not found\n");
+        return FALSE;
+    }
+    pApplyPatchToFileA = (void *)GetProcAddress(mspatcha, "ApplyPatchToFileA");
+    pApplyPatchToFileExA = (void *)GetProcAddress(mspatcha, "ApplyPatchToFileExA");
+    pApplyPatchToFileByHandles = (void *)GetProcAddress(mspatcha, "ApplyPatchToFileByHandles");
+    pApplyPatchToFileByBuffers = (void *)GetProcAddress(mspatcha, "ApplyPatchToFileByBuffers");
+    pApplyPatchToFileByHandlesEx = (void *)GetProcAddress(mspatcha, "ApplyPatchToFileByHandlesEx");
+    pTestApplyPatchToFileA = (void *)GetProcAddress(mspatcha, "TestApplyPatchToFileA");
+    pTestApplyPatchToFileByHandles = (void *)GetProcAddress(mspatcha, "TestApplyPatchToFileByHandles");
+    pTestApplyPatchToFileByBuffers = (void *)GetProcAddress(mspatcha, "TestApplyPatchToFileByBuffers");
+    return TRUE;
+}
+
+static BOOL CALLBACK progress(void *context, ULONG current, ULONG max)
+{
+    *(ULONG*)context = current * 100 / (max + !max);
+    return TRUE;
+}
+
+static BOOL CALLBACK progress_cancel(void *context, ULONG current, ULONG max)
+{
+    *(ULONG*)context = current * 100 / (max + !max);
+    return FALSE;
+}
+
+int create_file(const BYTE *buf, size_t size, const char *name)
+{
+    size_t w;
+    FILE *f = fopen(name, "wb");
+    if (f == NULL)
+        return -1;
+    w = fwrite(buf, 1, size, f);
+    fclose(f);
+    return w != size;
+}
+
+#define CREATE_FILE(buf, name) create_file(buf, sizeof(buf), name)
+
+static void compare_file(const char *name, const BYTE *buf, size_t size)
+{
+    size_t i;
+    FILE *f = fopen(name, "rb");
+    ok(f != NULL, "Expected test result file opened, got NULL\n");
+    if (f)
+    {
+        for (i = 0; i < size; ++i)
+        {
+            int c = fgetc(f);
+            ok(c == buf[i], "Character mismatch at offset %u: %02X vs %02X\n", (int)i, c, buf[i]);
+            if (c != buf[i])
+                break;
+        }
+        fseek(f, 0, SEEK_END);
+        ok(ftell(f) == size, "Output size larger than expected\n");
+        fclose(f);
+    }
+}
+
+static const char old_file_temp[] = "old_file.temp";
+static const char patch_file_temp[] = "patch_file.temp";
+static const char output_file_temp[] = "output.temp";
+
+static void test_ApplyPatchToFile(void)
+{
+    ULONG current;
+    DWORD err;
+
+    ok(CREATE_FILE(old_two_files_ranges_0, old_file_temp) == 0, "Failed to create temporary input file %s\n", old_file_temp);
+    ok(CREATE_FILE(patch_two_files_ranges, patch_file_temp) == 0, "Failed to create temporary input file %s\n", patch_file_temp);
+
+    /* two input files */
+    SetLastError(0xdeadbeef);
+    ok(pApplyPatchToFileA(patch_file_temp, old_file_temp, output_file_temp, APPLY_OPTION_FAIL_IF_EXACT), "ApplyPatchToFileA: expected TRUE\n");
+    err = GetLastError();
+    ok(err == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got 0x%X\n", err);
+    /* ensure that ranges were applied properly by comparing with the result from Windows */
+    compare_file(output_file_temp, windows_output_0, sizeof(windows_output_0));
+
+    /* missing patch file */
+    SetLastError(0xdeadbeef);
+    ok(!pApplyPatchToFileA("snarfalargle", old_file_temp, output_file_temp, 0), "ApplyPatchToFileA: expected FALSE\n");
+    err = GetLastError();
+    ok(err == ERROR_FILE_NOT_FOUND, "Expected ERROR_FILE_NOT_FOUND, got 0x%X\n", err);
+
+    /* missing old file */
+    SetLastError(0xdeadbeef);
+    ok(!pApplyPatchToFileA(patch_file_temp, "elbernoffle", output_file_temp, 0), "ApplyPatchToFileA: expected FALSE\n");
+    err = GetLastError();
+    ok(err == ERROR_FILE_NOT_FOUND, "Expected ERROR_FILE_NOT_FOUND, got 0x%X\n", err);
+
+    /* bad output file */
+    SetLastError(0xdeadbeef);
+    ok(!pApplyPatchToFileA(patch_file_temp, old_file_temp, "up\\down\\left\\right", 0), "ApplyPatchToFileA: expected FALSE\n");
+    err = GetLastError();
+    ok(err == ERROR_PATH_NOT_FOUND, "Expected ERROR_PATH_NOT_FOUND, got 0x%X\n", err);
+
+    /* progress function in ApplyPatchToFileExA */
+    SetLastError(0xdeadbeef);
+    current = 0xdeadbeef;
+    ok(pApplyPatchToFileExA(patch_file_temp, old_file_temp, output_file_temp, APPLY_OPTION_FAIL_IF_EXACT, progress, &current), "ApplyPatchToFileExA: expected TRUE\n");
+    err = GetLastError();
+    ok(err == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got 0x%X\n", err);
+    ok(current <= 100, "Expected 0-100, got 0xdeadbeef\n");
+
+    /* cancellation in ApplyPatchToFileExA */
+    SetLastError(0xdeadbeef);
+    current = 0xdeadbeef;
+    ok(!pApplyPatchToFileExA(patch_file_temp, old_file_temp, output_file_temp, APPLY_OPTION_FAIL_IF_EXACT, progress_cancel, &current), "ApplyPatchToFileExA: expected FALSE\n");
+    err = GetLastError();
+    ok(err == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got 0x%X\n", err);
+    ok(current <= 100, "Expected 0-100, got 0xdeadbeef\n");
+
+    ok(CREATE_FILE(patch_null_input_uncompressed, patch_file_temp) == 0, "Failed to create temporary input file %s\n", patch_file_temp);
+
+    /* null old file */
+    SetLastError(0xdeadbeef);
+    ok(pApplyPatchToFileA(patch_file_temp, NULL, output_file_temp, APPLY_OPTION_FAIL_IF_EXACT), "ApplyPatchToFileA: expected TRUE\n");
+    err = GetLastError();
+    ok(err == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got 0x%X\n", err);
+
+    DeleteFileA(old_file_temp);
+    DeleteFileA(patch_file_temp);
+    DeleteFileA(output_file_temp);
+}
+
+static void test_ApplyPatchToFileByHandles(void)
+{
+    HANDLE patch_hndl;
+    HANDLE old_hndl;
+    HANDLE new_hndl;
+    ULONG current;
+    DWORD err;
+    DWORD size;
+    DWORD w;
+
+    ok(CREATE_FILE(old_blocktype2_no_timestamp, old_file_temp) == 0, "Failed to create temporary input file %s\n", old_file_temp);
+    ok(CREATE_FILE(patch_blocktype2_no_timestamp, patch_file_temp) == 0, "Failed to create temporary input file %s\n", patch_file_temp);
+
+    patch_hndl = CreateFileA(patch_file_temp, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+    ok(patch_hndl != INVALID_HANDLE_VALUE, "Failed to open patch file %s: %u\n", patch_file_temp, GetLastError());
+
+    old_hndl = CreateFileA(old_file_temp, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+    ok(old_hndl != INVALID_HANDLE_VALUE, "Failed to open old file %s: %u\n", old_file_temp, GetLastError());
+
+    new_hndl = CreateFileA(output_file_temp, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
+    ok(new_hndl != INVALID_HANDLE_VALUE, "Failed to open new file %s: %u\n", output_file_temp, GetLastError());
+
+    SetLastError(0xdeadbeef);
+    ok(pApplyPatchToFileByHandles(patch_hndl, old_hndl, new_hndl, APPLY_OPTION_FAIL_IF_EXACT), "ApplyPatchToFileByHandles: expected TRUE\n");
+    err = GetLastError();
+    ok(err == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got 0x%X\n", err);
+    w = SetFilePointer(patch_hndl, 0, NULL, FILE_CURRENT);
+    ok(w == 0, "Expected 0, got %u\n", w);
+    w = SetFilePointer(old_hndl, 0, NULL, FILE_CURRENT);
+    ok(w == 0, "Expected 0, got %u\n", w);
+
+    size = SetFilePointer(new_hndl, 0, NULL, FILE_END);
+    WriteFile(new_hndl, "  ", 2, &w, 0);
+
+    SetLastError(0xdeadbeef);
+    current = 0xdeadbeef;
+    ok(pApplyPatchToFileByHandlesEx(patch_hndl, old_hndl, new_hndl, APPLY_OPTION_FAIL_IF_EXACT, progress, &current), "ApplyPatchToFileByHandlesEx: expected TRUE\n");
+    err = GetLastError();
+    ok(err == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got 0x%X\n", err);
+    ok(current <= 100, "Expected 0-100, got 0xdeadbeef\n");
+    w = SetFilePointer(new_hndl, 0, NULL, FILE_CURRENT);
+    ok(w == size, "Expected %u, got %u\n", size, w);
+
+    SetLastError(0xdeadbeef);
+    current = 0xdeadbeef;
+    ok(!pApplyPatchToFileByHandlesEx(patch_hndl, old_hndl, new_hndl, APPLY_OPTION_FAIL_IF_EXACT, progress_cancel, &current), "ApplyPatchToFileByHandlesEx: expected FALSE\n");
+    err = GetLastError();
+    ok(err == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got 0x%X\n", err);
+    ok(current <= 100, "Expected 0-100, got 0xdeadbeef\n");
+
+    SetLastError(0xdeadbeef);
+    ok(pTestApplyPatchToFileByHandles(patch_hndl, old_hndl, APPLY_OPTION_FAIL_IF_EXACT), "TestApplyPatchToFileByHandles: expected TRUE\n");
+    err = GetLastError();
+    ok(err == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got 0x%X\n", err);
+
+    CloseHandle(new_hndl);
+    CloseHandle(old_hndl);
+    CloseHandle(patch_hndl);
+    DeleteFileA(old_file_temp);
+    DeleteFileA(patch_file_temp);
+    DeleteFileA(output_file_temp);
+}
+
+static void test_ApplyPatchToFileByBuffers(void)
+{
+    BYTE *new_buf = NULL;
+    ULONG new_size = 0;
+    FILETIME new_time = { 0, 0 };
+    ULONG current = 0xdeadbeef;
+    BYTE target_buf[sizeof(old_two_files_ranges_1)];
+    DWORD err;
+
+    /* output buffer allocated by api */
+    SetLastError(0xdeadbeef);
+    ok(pApplyPatchToFileByBuffers(patch_two_files_ranges, sizeof(patch_two_files_ranges),
+        old_two_files_ranges_0, sizeof(old_two_files_ranges_0), &new_buf, 0, &new_size, &new_time, 0, progress, &current), "ApplyPatchToFileByBuffers: expected TRUE\n");
+    err = GetLastError();
+    ok(err == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%X\n", err);
+    ok(new_buf != NULL, "Expected buffer returned, got NULL\n");
+    ok(new_size == sizeof(windows_output_0), "Expected %u, got %u\n", (unsigned)sizeof(windows_output_0), new_size);
+    if(new_buf != NULL)
+        ok(memcmp(new_buf, windows_output_0, new_size) == 0, "Expected data equal, got not equal\n");
+    ok(current <= 100, "Expected 0-100, got 0xdeadbeef\n");
+
+    if (new_buf != NULL)
+        ok(VirtualFree(new_buf, 0, MEM_RELEASE), "ApplyPatchToFileByBuffers output buffer should be allocaed with VirtualAlloc\n");
+
+    /* buffer supplied by caller */
+    new_buf = target_buf;
+    new_size = sizeof(target_buf);
+    new_time.dwHighDateTime = 0;
+    SetLastError(0xdeadbeef);
+    ok(pApplyPatchToFileByBuffers(patch_blocktype2_no_timestamp, sizeof(patch_blocktype2_no_timestamp),
+        old_blocktype2_no_timestamp, sizeof(old_blocktype2_no_timestamp), &new_buf, sizeof(target_buf), &new_size, &new_time, 0, NULL, NULL), "ApplyPatchToFileByBuffers: expected TRUE\n");
+    err = GetLastError();
+    ok(err == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%X\n", err);
+    ok(new_buf == target_buf, "Buffer pre-allocated; pointer should not be modified\n");
+    ok(new_size == BLOCKTYPE2_NO_TIMESTAMP_SIZE, "Expected %u, got %u\n", BLOCKTYPE2_NO_TIMESTAMP_SIZE, new_size);
+    ok(new_time.dwHighDateTime == 0, "Expected 0, got nonzero\n");
+
+    /* null old file */
+    SetLastError(0xdeadbeef);
+    new_size = sizeof(target_buf);
+    new_time.dwHighDateTime = 0;
+    ok(pApplyPatchToFileByBuffers(patch_null_input_uncompressed, sizeof(patch_null_input_uncompressed),
+        NULL, 0, &new_buf, sizeof(target_buf), &new_size, &new_time, 0, NULL, NULL), "ApplyPatchToFileByBuffers: expected TRUE\n");
+    err = GetLastError();
+    ok(err == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%X\n", err);
+    ok(new_size == NULL_INPUT_UNCOMPRESSED_SIZE, "Expected %u, got %u\n", NULL_INPUT_UNCOMPRESSED_SIZE, new_size);
+    ok(new_time.dwHighDateTime != 0, "Expected nonzero, got 0\n");
+
+    /* null output */
+    SetLastError(0xdeadbeef);
+    ok(!pApplyPatchToFileByBuffers(patch_null_input_uncompressed, sizeof(patch_null_input_uncompressed),
+        NULL, 0, NULL, 0, &new_size, &new_time, 0, NULL, NULL), "ApplyPatchToFileByBuffers: expected FALSE\n");
+    err = GetLastError();
+    ok(err == ERROR_INVALID_PARAMETER, "Expected ERROR_INVALID_PARAMETER, got 0x%X\n", err);
+
+    /* null output test only */
+    SetLastError(0xdeadbeef);
+    new_time.dwHighDateTime = 0;
+    ok(pApplyPatchToFileByBuffers(patch_null_input_uncompressed, sizeof(patch_null_input_uncompressed),
+        NULL, 0, NULL, 0, NULL, &new_time, APPLY_OPTION_TEST_ONLY, NULL, NULL), "ApplyPatchToFileByBuffers: expected TRUE\n");
+    err = GetLastError();
+    ok(err == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%X\n", err);
+    ok(new_time.dwHighDateTime != 0, "Expected nonzero, got 0\n");
+
+    /* null old file test only */
+    SetLastError(0xdeadbeef);
+    new_size = 0;
+    ok(pApplyPatchToFileByBuffers(patch_null_input_uncompressed, sizeof(patch_null_input_uncompressed),
+        NULL, 0, NULL, 0, &new_size, NULL, APPLY_OPTION_TEST_ONLY, NULL, NULL), "ApplyPatchToFileByBuffers: expected TRUE\n");
+    err = GetLastError();
+    ok(err == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%X\n", err);
+    ok(new_size == NULL_INPUT_UNCOMPRESSED_SIZE, "Expected %u, got %u\n", NULL_INPUT_UNCOMPRESSED_SIZE, new_size);
+
+    /* alternate old file */
+    new_buf = target_buf;
+    new_size = sizeof(target_buf);
+    SetLastError(0xdeadbeef);
+    ok(pApplyPatchToFileByBuffers(patch_two_files_ranges, sizeof(patch_two_files_ranges),
+        old_two_files_ranges_1, sizeof(old_two_files_ranges_1), &new_buf, sizeof(target_buf), &new_size, NULL, 0, NULL, NULL), "ApplyPatchToFileByBuffers: expected TRUE\n");
+    err = GetLastError();
+    ok(new_size == TWO_FILES_RANGES_SIZE, "Expected %u, got %u\n", TWO_FILES_RANGES_SIZE, new_size);
+    ok(err == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%X\n", err);
+
+    /* wrong old file */
+    new_buf = target_buf;
+    new_size = sizeof(target_buf);
+    SetLastError(0xdeadbeef);
+    ok(!pApplyPatchToFileByBuffers(patch_two_files_ranges, sizeof(patch_two_files_ranges),
+        old_blocktype2_no_timestamp, sizeof(old_blocktype2_no_timestamp), &new_buf, sizeof(target_buf), &new_size, &new_time, 0, NULL, NULL), "ApplyPatchToFileByBuffers: expected FALSE\n");
+    err = GetLastError();
+    ok(err == ERROR_PATCH_WRONG_FILE, "Expected ERROR_PATCH_WRONG_FILE, got 0x%X\n", err);
+
+    /* patch created from identical files */
+    SetLastError(0xdeadbeef);
+    ok(pApplyPatchToFileByBuffers(patch_unnecessary, sizeof(patch_unnecessary),
+        old_unnecessary, sizeof(old_unnecessary), &new_buf, sizeof(target_buf), &new_size, &new_time, 0, NULL, NULL), "ApplyPatchToFileByBuffers: expected TRUE\n");
+    err = GetLastError();
+    ok(err == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%X\n", err);
+
+    /* patch created from identical files and APPLY_OPTION_FAIL_IF_EXACT */
+    SetLastError(0xdeadbeef);
+    ok(!pApplyPatchToFileByBuffers(patch_unnecessary, sizeof(patch_unnecessary),
+        old_unnecessary, sizeof(old_unnecessary), &new_buf, sizeof(target_buf), &new_size, &new_time, APPLY_OPTION_FAIL_IF_EXACT, NULL, NULL), "ApplyPatchToFileByBuffers: expected FALSE\n");
+    err = GetLastError();
+    ok(err == ERROR_PATCH_NOT_NECESSARY, "Expected ERROR_PATCH_NOT_NECESSARY, got 0x%X\n", err);
+
+    /* header test */
+    SetLastError(0xdeadbeef);
+    new_size = 0;
+    ok(pTestApplyPatchToFileByBuffers(patch_blocktype2_no_timestamp, sizeof(patch_blocktype2_no_timestamp),
+        old_blocktype2_no_timestamp, sizeof(old_blocktype2_no_timestamp), &new_size, 0), "TestApplyPatchToFileByBuffers: expected TRUE\n");
+    err = GetLastError();
+    ok(err == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%X\n", err);
+    ok(new_size == BLOCKTYPE2_NO_TIMESTAMP_SIZE, "Expected %u, got %u\n", BLOCKTYPE2_NO_TIMESTAMP_SIZE, new_size);
+
+    /* header test, no size returned */
+    SetLastError(0xdeadbeef);
+    ok(pTestApplyPatchToFileByBuffers(patch_blocktype2_no_timestamp, sizeof(patch_blocktype2_no_timestamp),
+        old_blocktype2_no_timestamp, sizeof(old_blocktype2_no_timestamp), NULL, 0), "TestApplyPatchToFileByBuffers: expected TRUE\n");
+    err = GetLastError();
+    ok(err == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%X\n", err);
+}
+
+static void test_TestApplyPatchToFile(void)
+{
+    DWORD err;
+
+    ok(CREATE_FILE(old_two_files_ranges_0, old_file_temp) == 0, "Failed to create temporary input file %s\n", old_file_temp);
+    ok(CREATE_FILE(patch_header_only, patch_file_temp) == 0, "Failed to create temporary input file %s\n", patch_file_temp);
+
+    /* correct old file */
+    SetLastError(0xdeadbeef);
+    ok(pTestApplyPatchToFileA(patch_file_temp, old_file_temp, 0), "TestApplyPatchToFileA: expected TRUE\n");
+    err = GetLastError();
+    ok(err == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got 0x%X\n", err);
+
+    ok(CREATE_FILE(old_blocktype2_no_timestamp, old_file_temp) == 0, "Failed to create temporary input file %s\n", old_file_temp);
+
+    /* wrong old file */
+    SetLastError(0xdeadbeef);
+    ok(!pTestApplyPatchToFileA(patch_file_temp, old_file_temp, 0), "TestApplyPatchToFileA: expected FALSE\n");
+    err = GetLastError();
+    ok(err == ERROR_PATCH_WRONG_FILE, "Expected ERROR_PATCH_WRONG_FILE, got 0x%X\n", err);
+
+    DeleteFileA(old_file_temp);
+    DeleteFileA(patch_file_temp);
+}
+
+START_TEST(patch_apply)
+{
+    if (!init_function_pointers())
+        return;
+
+    test_ApplyPatchToFile();
+    test_ApplyPatchToFileByBuffers();
+    test_ApplyPatchToFileByHandles();
+    test_TestApplyPatchToFile();
+}
diff --git a/dlls/mspatcha/tests/test_files.h b/dlls/mspatcha/tests/test_files.h
new file mode 100755
index 0000000..1724f07
--- /dev/null
+++ b/dlls/mspatcha/tests/test_files.h
@@ -0,0 +1,211 @@
+#define TWO_FILES_RANGES_SIZE 1023
+
+/* BYTE* arguments to ApplyPatchToFileByBuffers() are not const even for input */
+static BYTE old_two_files_ranges_0[] = {
+0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x38,0x49,0x3F,0x47,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x4E,0x3F,0x3D,0x35,
+0x4D,0x32,0x31,0x51,0x30,0x35,0x37,0x52,0x33,0x38,0x3A,0x39,0x3A,0x30,0x35,0x37,0x32,0x36,0x34,0x31,0x3A,0x32,0x31,0x38,0x38,0x3C,0x3C,0x3B,0x4F,0x4E,0x30,0x41,
+0x35,0x33,0x33,0x31,0x30,0x40,0x38,0x46,0x3F,0x32,0x31,0x3E,0x45,0x33,0x33,0x3B,0x31,0x31,0x35,0x38,0x57,0x38,0x37,0x39,0x33,0x30,0x35,0x33,0x48,0x36,0x37,0x32,
+0x48,0x3B,0x47,0x3E,0x33,0x3E,0x30,0x37,0x31,0x35,0x3F,0x4B,0x47,0x39,0x35,0x53,0x32,0x34,0x30,0x37,0x30,0x48,0x30,0x34,0x53,0x36,0x33,0x31,0x31,0x34,0x40,0x35,
+0x37,0x30,0x69,0x4A,0x3F,0x32,0x32,0x30,0x3A,0x39,0x40,0x54,0x31,0x32,0x30,0x3C,0x39,0xE8,0x30,0x34,0x32,0x39,0x4D,0x35,0x45,0x32,0x34,0x30,0x35,0x32,0x33,0x41,
+0x33,0x30,0x64,0x30,0x46,0x3E,0x32,0x32,0x39,0x33,0x33,0x62,0x34,0x3D,0x38,0x3A,0x39,0x32,0x32,0x34,0x38,0x50,0x39,0x39,0x44,0x36,0x45,0x38,0x40,0x47,0x3F,0x4B,
+0x3D,0x38,0x33,0x30,0x35,0x33,0x35,0x46,0x31,0x5B,0x4B,0x40,0x49,0x3F,0x3F,0x4B,0x3D,0x38,0x33,0x3D,0x70,0x34,0x42,0x32,0x61,0x36,0x4D,0x36,0x3F,0x30,0x35,0x36,
+0x37,0x30,0x31,0x31,0x34,0x36,0x3C,0x32,0x3E,0x33,0x34,0x38,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x33,0x30,0x34,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,
+0x33,0x4E,0x40,0x5B,0x33,0x38,0x49,0x3F,0x47,0x3C,0x32,0x40,0x38,0x4A,0x39,0x33,0x42,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0xE8,0xAA,0x01,0x00,0x00,0x49,0x3F,
+0x47,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0xE8,0x8C,0x01,0x00,0x00,0x32,0x31,0x51,0x30,0x35,0x37,0x52,0x33,0x38,0x3A,0x39,0x3A,0x30,0x35,
+0x37,0x32,0x36,0x34,0x31,0x3A,0x32,0x31,0x38,0x38,0x3C,0x3C,0x3B,0x4F,0x4E,0x30,0x41,0x35,0x33,0x33,0x31,0x30,0x40,0x38,0x46,0x3F,0x32,0x31,0x3E,0x45,0x33,0x33,
+0x3B,0x31,0x31,0x35,0x38,0x57,0x38,0x37,0x33,0x30,0x35,0x33,0x48,0x36,0x37,0x32,0x48,0x3B,0x47,0x3E,0x33,0x3E,0x30,0x37,0x31,0x35,0x3F,0x4B,0x47,0x39,0x35,0x53,
+0x32,0x34,0x30,0x37,0x30,0x48,0x30,0x34,0x53,0x36,0x4F,0x31,0x38,0x32,0x52,0x44,0x37,0x3D,0x4C,0x33,0x38,0x36,0x57,0x30,0x35,0x3C,0x32,0x33,0xE8,0x98,0xFE,0xFF,
+0xFF,0x4D,0x31,0x31,0x33,0x32,0x36,0x32,0x32,0x31,0x39,0x35,0x5B,0x31,0x38,0x30,0x35,0x3C,0x3C,0x34,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,
+0x38,0xE8,0x28,0x01,0x00,0x00,0x31,0x32,0x34,0x38,0x49,0x31,0x40,0x31,0x41,0x31,0x3F,0x36,0x30,0x3E,0x3B,0x36,0x3A,0x45,0x32,0x50,0x3C,0x45,0x3D,0x31,0x31,0x3C,
+0x3A,0x3A,0x46,0x4A,0x3F,0x36,0x33,0x43,0x3C,0x3D,0x32,0x37,0x36,0x3B,0x3E,0x36,0x40,0x32,0x39,0x51,0x31,0x33,0x32,0x36,0x44,0x31,0x35,0x34,0x3A,0x32,0x37,0x3D,
+0x33,0x48,0x45,0x31,0x31,0x30,0x3A,0x31,0x39,0x31,0x37,0x32,0x35,0x35,0x3B,0x35,0x35,0x48,0x38,0x33,0x61,0x40,0x36,0x33,0x36,0x39,0x30,0x30,0x3C,0x36,0x56,0x3D,
+0xE8,0x12,0xFF,0xFF,0xFF,0x3C,0x3D,0x33,0x45,0xE8,0x32,0x3D,0x3F,0x37,0x35,0x37,0xE8,0x7E,0x00,0x00,0x00,0x34,0x31,0x30,0x34,0x31,0x30,0x32,0x30,0x41,0x35,0x31,
+0x44,0x3C,0x31,0x3A,0x4F,0x42,0x34,0x34,0x47,0x41,0x36,0x41,0x32,0x32,0x35,0x3A,0x41,0x37,0x32,0x43,0x31,0x38,0x40,0x3D,0x34,0x33,0x35,0x30,0x3F,0x35,0x32,0x36,
+0x31,0x35,0x30,0x33,0x49,0x41,0x33,0x37,0x44,0x35,0x3B,0x3C,0x32,0x33,0x4D,0x33,0x32,0xE8,0xE9,0xFF,0xFF,0xFF,0x33,0x38,0x49,0x3F,0x3C,0x32,0x33,0x4D,0x33,0x32,
+0x38,0x3D,0x31,0x55,0x33,0x37,0x3C,0x30,0x3B,0x35,0x3D,0x4F,0x42,0x4F,0x33,0x33,0x3A,0x4D,0x35,0x34,0xE8,0x62,0xFF,0xFF,0xFF,0x31,0x33,0x5E,0x33,0x52,0x51,0x3E,
+0x31,0x41,0x34,0x40,0x44,0x3F,0x30,0x52,0x33,0x35,0x36,0x3D,0x3F,0x37,0x41,0x44,0x33,0x39,0x34,0x39,0x30,0x33,0x31,0x37,0x41,0x46,0x3C,0x3A,0xE8,0x30,0x3C,0x3B,
+0x48,0x36,0x32,0x33,0x33,0x36,0x3A,0x42,0x43,0x43,0x30,0x37,0x35,0x30,0x4B,0x46,0x4B,0x3C,0x54,0x36,0x3F,0x32,0x31,0x3D,0x3B,0x34,0x30,0x3E,0x32,0x34,0x4C,0x3F,
+0x30,0x41,0x32,0x43,0x30,0x36,0x33,0x41,0x36,0x35,0x31,0x30,0x47,0x38,0x31,0x35,0xE8,0x89,0xFF,0xFF,0xFF,0xFF,0xFF,0x32,0x52,0x32,0x32,0x50,0x39,0x3D,0x37,0x4E,
+0x33,0x34,0x30,0x4E,0x3A,0x49,0x35,0x37,0x37,0x33,0x44,0x36,0x32,0x3A,0x50,0x3A,0x32,0x34,0x36,0x3D,0x36,0x34,0x3C,0x4C,0x32,0x45,0x38,0x39,0x33,0x33,0x35,0x33,
+0x32,0x33,0x3D,0x38,0x38,0x32,0x37,0x32,0x42,0x32,0x36,0x40,0x57,0x34,0x33,0x33,0x4F,0x4D,0x42,0x39,0x34,0x30,0x3A,0x31,0x33,0x45,0x48,0x46,0x35,0x42,0x3D,0x38,
+0x3B,0x33,0x34,0x33,0x3F,0x38,0x37,0x4B,0x36,0x33,0x30,0x43,0x42,0x38,0x31,0x4B,0x40,0x4D,0x3F,0x56,0x41,0x33,0x41,0x36,0x3A,0x31,0x4F,0x36,0x44,0x33,0x45,0x34,
+0x4B,0x37,0x46,0x34,0x36,0x3F,0x30,0x3B,0x36,0x39,0x36,0x3B,0x34,0xE8,0x4D,0xFF,0xFF,0xFF,0x39,0x34,0x3D,0x38,0x3D,0x34,0x31,0x31,0x37,0x33,0x30,0x32,0x38,0x34,
+0x38,0x39,0x35,0x33,0x30,0x34,0x4B,0xE8,0x37,0xFF,0xFF,0xFF,0x33,0x32,0x3A,0x31,0x3B,0x36,0x3A,0x44,0x31,0x57,0x33,0x3E,0x3E,0x33,0x3C,0x32,0x33,0x4D,0x33,0x32,
+0x44,0x33,0x4E,0x40,0x5B,0x33,0x38,0x49,0x3F,0x47,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x4E,0x3F,0x3D,0x35,0x4D,0x32,0x31,0x51,0x30,0x35,
+0x37,0x52,0x33,0x38,0x3A,0x39,0x3A,0x30,0x35,0x37,0x32,0x36,0x34,0x31,0x3A,0x32,0x31,0x38,0x38,0x3C,0x3C,0x3B,0x4F,0x4E,0x30,0x41,0x35,0x33,0x33,0x31,0x30,0x40,
+0x38,0x46,0x3F,0x32,0x31,0x3E,0x45,0x33,0x33,0x3B,0x31,0x31,0x35,0x38,0x57,0x38,0x37,0x39,0x33,0x30,0x35,0x33,0x48,0x36,0x37,0x32,0x48,0x3B,0x47,0x3E};
+
+static BYTE old_two_files_ranges_1[] = {
+0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x38,0x49,0x3F,0x47,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x4E,0x3F,0x3D,0x35,
+0x4D,0x32,0x31,0x51,0x30,0x35,0x37,0x52,0x33,0x38,0x3A,0x39,0x3A,0x30,0x35,0x37,0x32,0x36,0x34,0x31,0x3A,0x32,0x31,0x38,0x38,0x3C,0x3C,0x3B,0x4F,0x4E,0x30,0x41,
+0x35,0x33,0x33,0x31,0x30,0x40,0x38,0x46,0x3F,0x32,0x31,0x3E,0x45,0x33,0x33,0x3B,0x31,0x31,0x35,0x38,0x57,0x38,0x37,0x39,0x33,0x30,0x35,0x33,0x48,0x36,0x37,0x32,
+0x48,0x3B,0x47,0x3E,0x33,0x3E,0x30,0x37,0x31,0x35,0x3F,0x4B,0x47,0x39,0x35,0x53,0x32,0x34,0x30,0x37,0x30,0x48,0x30,0x34,0x53,0x36,0x33,0x31,0x31,0x34,0x40,0x35,
+0x37,0x30,0x69,0x4A,0x3F,0x32,0x32,0x30,0x3A,0x39,0x40,0x54,0x31,0x32,0x30,0x3C,0x39,0xE8,0x30,0x34,0x32,0x39,0x4D,0x35,0x45,0x32,0x34,0x30,0x35,0x32,0x33,0x41,
+0x33,0x30,0x64,0x30,0x46,0x3E,0x32,0x32,0x39,0x33,0x33,0x62,0x34,0x3D,0x38,0x3A,0x39,0x32,0x32,0x34,0x38,0x50,0x39,0x39,0x44,0x36,0x45,0x38,0x40,0x47,0x3F,0x4B,
+0x3D,0x38,0x33,0x30,0x35,0x33,0x35,0x46,0x31,0x5B,0x4B,0x40,0x49,0x3F,0x3F,0x4B,0x3D,0x38,0x33,0x3D,0x70,0x34,0x42,0x32,0x61,0x36,0x4D,0x36,0x3F,0x30,0x35,0x36,
+0x37,0x30,0x31,0x31,0x34,0x36,0x3C,0x32,0x3E,0x33,0x34,0x38,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x33,0x30,0x34,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,
+0x33,0x4E,0x40,0x5B,0x33,0x38,0x49,0x3F,0x47,0x3C,0x32,0x40,0x38,0x4A,0x39,0x33,0x42,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0xE8,0xAA,0x01,0x00,0x00,0x49,0x3F,
+0x47,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0xE8,0x8C,0x01,0x00,0x00,0x32,0x31,0x51,0x30,0x35,0x37,0x52,0x33,0x38,0x3A,0x39,0x3A,0x30,0x35,
+0x37,0x32,0x36,0x34,0x31,0x3A,0x32,0x31,0x38,0x38,0x3C,0x3C,0x3B,0x4F,0x4E,0x30,0x41,0x35,0x33,0x33,0x31,0x30,0x40,0x38,0x46,0x3F,0x32,0x31,0x3E,0x45,0x33,0x33,
+0x3B,0x31,0x31,0x35,0x38,0x57,0x38,0x37,0x39,0x33,0x30,0x35,0x33,0x48,0x36,0x37,0x32,0x48,0x3B,0x47,0x3E,0x33,0x3E,0x30,0x37,0x31,0x35,0x3F,0x4B,0x47,0x39,0x35,
+0x53,0x32,0x34,0x30,0x37,0x30,0x48,0x30,0x34,0x53,0x36,0x4F,0x31,0x38,0x32,0x52,0x44,0x37,0x3D,0x4C,0x33,0x38,0x36,0x57,0x30,0x35,0x3C,0x32,0x33,0xE8,0x98,0xFE,
+0xFF,0xFF,0x4D,0x31,0x31,0x33,0x32,0x36,0x32,0x4A,0x32,0x31,0x39,0x35,0x5B,0x31,0x38,0x30,0x35,0x3C,0x3C,0x34,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,
+0x5B,0x33,0x38,0xE8,0x28,0x01,0x00,0x00,0x31,0x32,0x34,0x38,0x49,0x31,0x40,0x31,0x41,0x31,0x3F,0x36,0x30,0x3E,0x3B,0x36,0x3A,0x45,0x32,0x50,0x3C,0x45,0x3D,0x31,
+0x31,0x3C,0x3A,0x3A,0x46,0x4A,0x3F,0x36,0x33,0x43,0x3C,0x3D,0x32,0x37,0x36,0x3B,0x3E,0x36,0x40,0x32,0x39,0x51,0x31,0x33,0x32,0x36,0x44,0x31,0x35,0x34,0x3A,0x32,
+0x37,0x3D,0x33,0x48,0x45,0x31,0x31,0x30,0x3A,0x31,0x39,0x31,0x37,0x32,0x35,0x35,0x3B,0x35,0x35,0x48,0x38,0x33,0x61,0x40,0x36,0x33,0x36,0x39,0x30,0x30,0x3C,0x36,
+0x56,0x3D,0xE8,0x12,0xFF,0xFF,0xFF,0x3C,0x3D,0x33,0x45,0xE8,0x32,0x3D,0x3F,0x37,0x35,0x37,0xE8,0x7E,0x00,0x00,0x00,0x34,0x31,0x30,0x34,0x31,0x30,0x32,0x30,0x41,
+0x35,0x31,0x44,0x3C,0x31,0x3A,0x4F,0x42,0x34,0x34,0x47,0x41,0x36,0x41,0x32,0x32,0x35,0x3A,0x41,0x37,0x32,0x43,0x31,0x38,0x40,0x3D,0x34,0x33,0x35,0x30,0x3F,0x35,
+0x32,0x36,0x31,0x35,0x30,0x33,0x49,0x41,0x33,0x37,0x44,0x35,0x3B,0x3C,0x32,0x33,0x4D,0x33,0x32,0xE8,0xE9,0xFF,0xFF,0xFF,0x33,0x38,0x49,0x3F,0x3C,0x32,0x33,0x4D,
+0x33,0x32,0x38,0x3D,0x31,0x55,0x33,0x37,0x3C,0x30,0x3B,0x35,0x3D,0x4F,0x42,0x4F,0x33,0x33,0x3A,0x4D,0x35,0x34,0xE8,0x62,0xFF,0xFF,0xFF,0x31,0x33,0x5E,0x33,0x52,
+0x51,0x3E,0x31,0x41,0x34,0x40,0x44,0x3F,0x30,0x52,0x33,0x35,0x36,0x3D,0x3F,0x37,0x41,0x44,0x33,0x39,0x34,0x39,0x30,0x33,0x31,0x37,0x41,0x46,0x3C,0x3A,0xE8,0x30,
+0x3C,0x3B,0x48,0x36,0x32,0x33,0x33,0x36,0x3A,0x42,0x43,0x43,0x30,0x37,0x35,0x30,0x4B,0x46,0x4B,0x3C,0x54,0x36,0x3F,0x32,0x31,0x3D,0x3B,0x34,0x30,0x3E,0x32,0x34,
+0x4C,0x3F,0x30,0x41,0x32,0x43,0x30,0x36,0x33,0x41,0x36,0x35,0x31,0x30,0x47,0x38,0x31,0x35,0xE8,0x89,0xFF,0xFF,0xFF,0xFF,0xFF,0x32,0x52,0x32,0x32,0x50,0x39,0x3D,
+0x37,0x4E,0x33,0x34,0x30,0x4E,0x3A,0x49,0x35,0x37,0x37,0x33,0x44,0x36,0x32,0x3A,0x50,0x3A,0x32,0x34,0x36,0x3D,0x36,0x34,0x3C,0x4C,0x32,0x45,0x38,0x39,0x33,0x33,
+0x35,0x33,0x32,0x33,0x3D,0x38,0x38,0x32,0x37,0x32,0x42,0x32,0x36,0x40,0x57,0x34,0x33,0x33,0x4F,0x4D,0x42,0x39,0x34,0x30,0x3A,0x31,0x33,0x45,0x48,0x46,0x35,0x42,
+0x3D,0x38,0x3B,0x33,0x34,0x33,0x3F,0x38,0x37,0x4B,0x36,0x33,0x30,0x43,0x42,0x38,0x31,0x4B,0x40,0x4D,0x3F,0x56,0x41,0x33,0x41,0x36,0x3A,0x31,0x4F,0x36,0x44,0x33,
+0x45,0x34,0x4B,0x37,0x46,0x34,0x36,0x3F,0x30,0x3B,0x36,0x39,0x36,0x3B,0x34,0xE8,0x4D,0xFF,0xFF,0xFF,0x39,0x34,0x3D,0x38,0x3D,0x34,0x31,0x31,0x37,0x33,0x30,0x32,
+0x38,0x34,0x38,0x39,0x35,0x33,0x30,0x34,0x4B,0xE8,0x37,0xFF,0xFF,0xFF,0x33,0x32,0x3A,0x31,0x3B,0x36,0x3A,0x44,0x31,0x57,0x33,0x3E,0x3E,0x33,0x3C,0x32,0x33,0x4D,
+0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x38,0x49,0x3F,0x47,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x4E,0x3F,0x3D,0x35,0x4D,0x32,0x31,0x51,
+0x30,0x35,0x37,0x52,0x33,0x38,0x3A,0x39,0x3A,0x30,0x35,0x37,0x32,0x36,0x34,0x31,0x3A,0x32,0x31,0x38,0x38,0x3C,0x3C,0x3B,0x4F,0x4E,0x30,0x41,0x35,0x33,0x33,0x31,
+0x30,0x40,0x38,0x46,0x3F,0x32,0x31,0x3E,0x45,0x33,0x33,0x3B,0x31,0x31,0x35,0x38,0x57,0x38,0x37,0x39,0x33,0x30,0x35,0x33,0x48,0x36,0x37,0x32,0x48,0x3B,0x47,0x3E};
+
+static BYTE patch_two_files_ranges[] = {
+0x50,0x41,0x31,0x39,0x02,0x00,0xC4,0x00,0xC5,0xA5,0xBA,0x5C,0x7F,0x87,0xEC,0x96,0x45,0xA3,0x02,0xC1,0x0F,0x64,0x44,0xAC,0x14,0x1E,0x86,0x81,0x52,0x83,0x81,0x0E,
+0x85,0x81,0x40,0x81,0x81,0x03,0x83,0x81,0x5F,0x88,0x81,0xF4,0x81,0x4C,0x81,0x81,0x16,0x84,0x81,0x7B,0x83,0x81,0x36,0x84,0x81,0x18,0x82,0x81,0x61,0x83,0x81,0x3D,
+0x87,0x81,0x7C,0x8B,0x81,0x0C,0x81,0x81,0x02,0x89,0x81,0x63,0x83,0x81,0x5F,0x84,0x81,0x3D,0x86,0x81,0x14,0x14,0x83,0x80,0x81,0x34,0x84,0x80,0x81,0x13,0x81,0x80,
+0x81,0x5A,0x88,0x80,0x81,0x39,0x81,0x80,0x81,0x22,0x81,0x80,0x81,0x09,0x81,0x80,0x81,0x5B,0x82,0x80,0x81,0x07,0x89,0x80,0x81,0x50,0x88,0x80,0x81,0x34,0x82,0x80,
+0x81,0x60,0x84,0x80,0x81,0xE5,0x80,0x81,0x2A,0x86,0x80,0x81,0x00,0x81,0x80,0x81,0x12,0x83,0x80,0x81,0x4B,0x8C,0x80,0x81,0x05,0x82,0x80,0x81,0x6C,0x82,0x80,0x81,
+0x01,0x82,0x80,0x81,0x80,0x06,0x81,0x81,0x64,0xB1,0x64,0x3C,0x14,0xA6,0x81,0x8C,0x81,0xA4,0x81,0xAB,0x81,0xAD,0x81,0xA3,0x81,0x83,0x81,0xA4,0x81,0xAE,0x81,0x96,
+0x81,0xB0,0x81,0xA6,0x81,0xBE,0x81,0xB0,0x81,0x0F,0x81,0x81,0x85,0x81,0x83,0x81,0xA9,0x81,0xBB,0x81,0xB4,0x81,0x14,0x14,0x83,0x80,0x81,0x34,0x84,0x80,0x81,0x12,
+0x81,0x81,0x81,0x58,0x88,0xC1,0x81,0x38,0x81,0x80,0x81,0x23,0x81,0xC1,0x81,0x08,0x81,0x80,0x81,0x5A,0x82,0xC1,0x81,0x05,0x89,0x81,0x81,0x4F,0x88,0x80,0x81,0x33,
+0x82,0x81,0x81,0x60,0x84,0x81,0x81,0xE4,0x80,0x81,0x29,0x86,0x81,0x81,0x01,0x81,0x80,0x81,0x13,0x83,0xC1,0x81,0x4C,0x8C,0x80,0x81,0x06,0x82,0xC1,0x81,0x6D,0x82,
+0x80,0x81,0x00,0x82,0x81,0x81,0x80,0x14,0x81,0x84,0x00,0x00,0x10,0xF1,0x3F,0x00,0x00,0x00,0x00,0x23,0x00,0x00,0x40,0x0E,0x04,0x4E,0xBF,0x18,0xD2,0x42,0x96,0xBF,
+0xB1,0xFF,0xFF,0x95,0xFF,0x00,0x04,0x00,0x00,0x00,0x00,0x41,0x91,0x10,0x41,0x95,0x18,0x17,0xF4,0xE2,0xBB,0x33,0xE2,0x70,0x69,0x2D,0x54,0xA0,0xF5,0xFF,0xCE,0x10,
+0x7C,0x00,0x00,0x00,0x00,0x32,0x00,0x04,0x50,0x18,0x50,0xD0,0x34,0x17,0xB8,0x05,0xAE,0x82,0xC0,0xE5,0x42,0xF0,0x5B,0xFF,0x6F,0x6F,0xEF,0x9D,0xFF,0x6A,0xF0,0x01,
+0xE8,0xE5,0xD2,0x53,0x48,0x04,0x71,0xC0,0x86,0x1D,0x78,0x9E,0x71,0x3E,0x19,0xAC,0xDC,0x74,0x80,0x96,0x16,0xC1,0xE2,0xE3,0x14,0xA1,0xC3,0xAF,0x58,0xF8,0xDF,0x52,
+0xAB,0xAD,0x5D,0xF5,0xB3,0x5E,0x9B,0x81,0x33,0x6C,0xE7,0x01,0x99,0x3E,0x33,0x92,0x00,0x00,0x10,0xF2,0x3F,0x00,0x00,0x00,0x00,0x22,0x05,0x00,0x05,0x0F,0x43,0x9E,
+0xED,0x20,0xA2,0x51,0x44,0x88,0x4B,0xFE,0x5D,0xDB,0x63,0x63,0xFA,0x22,0x82,0x00,0x00,0x00,0x00,0x4A,0x00,0xC0,0xC0,0x0F,0x86,0xFE,0x42,0xFB,0x42,0x64,0x75,0x04,
+0x2C,0x97,0xB8,0xE9,0x12,0x4B,0x5D,0x7C,0x7F,0x00,0x10,0x00,0x00,0x00,0x00,0x50,0x23,0x40,0x05,0x9A,0x29,0x98,0x36,0x08,0xBE,0x61,0xCF,0x15,0xA1,0x5D,0xF1,0xFF,
+0xFE,0xF6,0x7F,0xEF,0xB8,0x0A,0x80,0x7F,0x25,0xBD,0x06,0x16,0x4F,0xF3,0x9A,0xEC,0xE3,0x22,0x76,0xC9,0xCE,0x64,0x48,0xDB,0xF9,0xC7,0x3B,0x95,0x91,0xDB,0xBA,0x7A,
+0xF9,0xD4,0x0D,0x0A,0x0C,0x40,0x7C,0x56,0x1E,0x8A,0x4D,0x78,0x4D,0x47,0x3F,0x8F,0x0D,0xDB,0x67,0xEB,0x90,0x61,0x4D,0x1E,0x3B,0x79,0x88,0x9D,0xE8,0xB4,0x78,0x8F,
+0xDC,0x00,0x80,0xCD,0xCA,0xAE,0x3C};
+
+static BYTE patch_header_only[] = {
+0x50,0x41,0x31,0x39,0x02,0x00,0xC4,0x00,0xC5,0xA5,0xBA,0x5C,0x7F,0x87,0xEC,0x96,0x45,0xA3,0x02,0xC1,0x0F,0x64,0x44,0xAC,0x14,0x1E,0x86,0x81,0x52,0x83,0x81,0x0E,
+0x85,0x81,0x40,0x81,0x81,0x03,0x83,0x81,0x5F,0x88,0x81,0xF4,0x81,0x4C,0x81,0x81,0x16,0x84,0x81,0x7B,0x83,0x81,0x36,0x84,0x81,0x18,0x82,0x81,0x61,0x83,0x81,0x3D,
+0x87,0x81,0x7C,0x8B,0x81,0x0C,0x81,0x81,0x02,0x89,0x81,0x63,0x83,0x81,0x5F,0x84,0x81,0x3D,0x86,0x81,0x14,0x14,0x83,0x80,0x81,0x34,0x84,0x80,0x81,0x13,0x81,0x80,
+0x81,0x5A,0x88,0x80,0x81,0x39,0x81,0x80,0x81,0x22,0x81,0x80,0x81,0x09,0x81,0x80,0x81,0x5B,0x82,0x80,0x81,0x07,0x89,0x80,0x81,0x50,0x88,0x80,0x81,0x34,0x82,0x80,
+0x81,0x60,0x84,0x80,0x81,0xE5,0x80,0x81,0x2A,0x86,0x80,0x81,0x00,0x81,0x80,0x81,0x12,0x83,0x80,0x81,0x4B,0x8C,0x80,0x81,0x05,0x82,0x80,0x81,0x6C,0x82,0x80,0x81,
+0x01,0x82,0x80,0x81,0x80,0x06,0x81,0x81,0x64,0xB1,0x64,0x3C,0x14,0xA6,0x81,0x8C,0x81,0xA4,0x81,0xAB,0x81,0xAD,0x81,0xA3,0x81,0x83,0x81,0xA4,0x81,0xAE,0x81,0x96,
+0x81,0xB0,0x81,0xA6,0x81,0xBE,0x81,0xB0,0x81,0x0F,0x81,0x81,0x85,0x81,0x83,0x81,0xA9,0x81,0xBB,0x81,0xB4,0x81,0x14,0x14,0x83,0x80,0x81,0x34,0x84,0x80,0x81,0x12,
+0x81,0x81,0x81,0x58,0x88,0xC1,0x81,0x38,0x81,0x80,0x81,0x23,0x81,0xC1,0x81,0x08,0x81,0x80,0x81,0x5A,0x82,0xC1,0x81,0x05,0x89,0x81,0x81,0x4F,0x88,0x80,0x81,0x33,
+0x82,0x81,0x81,0x60,0x84,0x81,0x81,0xE4,0x80,0x81,0x29,0x86,0x81,0x81,0x01,0x81,0x80,0x81,0x13,0x83,0xC1,0x81,0x4C,0x8C,0x80,0x81,0x06,0x82,0xC1,0x81,0x6D,0x82,
+0x80,0x81,0x00,0x82,0x81,0x81,0x80,0x14,0x81,0x83,0x6F,0xB5,0x1B};
+
+static BYTE windows_output_0[] = {
+0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x38,0x49,0x3F,0x47,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x4E,0x3F,0x3D,0x35,
+0x4D,0x32,0x31,0x51,0x30,0x35,0x37,0x52,0x33,0x38,0x3A,0x39,0x3A,0x30,0x35,0x37,0x32,0x36,0x34,0x31,0x3A,0x32,0x31,0x38,0x38,0x3C,0x3C,0x3B,0x4F,0x4E,0x30,0x41,
+0x35,0x33,0x33,0x31,0x30,0x40,0x38,0x46,0x3F,0x32,0x31,0x3E,0x45,0x33,0x33,0x3B,0x31,0x31,0x35,0x38,0x57,0x38,0x37,0x39,0x33,0x30,0x35,0x33,0x48,0x36,0x37,0x32,
+0x48,0x3B,0x47,0x3E,0x33,0x3E,0x30,0x37,0x31,0x35,0x3F,0x4B,0x47,0x39,0x35,0x53,0x32,0x34,0x30,0x37,0x30,0x48,0x30,0x34,0x53,0x36,0x33,0x31,0x31,0x34,0x40,0x35,
+0x37,0x30,0x69,0x4A,0x3F,0x32,0x32,0x30,0x3A,0x39,0x40,0x54,0x31,0x32,0x30,0x3C,0x39,0xE8,0x30,0x34,0x32,0x39,0x4D,0x35,0x45,0x32,0x34,0x30,0x35,0x32,0x33,0x41,
+0x33,0x30,0x64,0x30,0x46,0x3E,0x32,0x32,0x39,0x33,0x33,0x62,0x34,0x3D,0x38,0x3A,0x39,0x32,0x32,0x34,0x38,0x50,0x39,0x39,0x44,0x36,0x45,0x38,0x40,0x47,0x3F,0x4B,
+0x3D,0x38,0x33,0x30,0x35,0x33,0x35,0x46,0x31,0x5B,0x4B,0x40,0x49,0x3F,0x3F,0x4B,0x3D,0x38,0x33,0x3D,0x70,0x34,0x42,0x32,0x61,0x36,0x4D,0x36,0x3F,0x30,0x35,0x36,
+0x37,0x30,0x31,0x31,0x34,0x36,0x3C,0x32,0x3E,0x33,0x34,0x38,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x33,0x30,0x34,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,
+0x33,0x4E,0x40,0x5B,0x33,0x38,0x49,0x3F,0x47,0x3C,0x32,0x40,0x38,0x4A,0x39,0x33,0x42,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0xE8,0xAA,0x01,0x00,0x00,0x49,0x3F,
+0x47,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0xE8,0x8C,0x01,0x00,0x00,0x32,0x31,0x51,0x30,0x35,0x37,0x52,0x33,0x38,0x3A,0x39,0x3A,0x30,0x35,
+0x37,0x32,0x36,0x34,0x31,0x3A,0x32,0x31,0x38,0x38,0x3C,0x3C,0x3B,0x4F,0x4E,0x30,0x41,0x35,0x33,0x33,0x31,0x30,0x40,0x38,0x46,0x3F,0x32,0x31,0x3E,0x45,0x33,0x33,
+0x3B,0x31,0x31,0x35,0x38,0x57,0x38,0x37,0x39,0x33,0x30,0x33,0x33,0x48,0x36,0x37,0x32,0x48,0x3B,0x47,0x3E,0x33,0x3E,0x30,0x37,0x31,0x35,0x3F,0x4B,0x47,0x39,0x35,
+0x53,0x32,0x34,0x30,0x37,0x30,0x48,0x30,0x34,0x53,0x36,0x4F,0x31,0x38,0x32,0x52,0x44,0x37,0x3D,0x4C,0x33,0x38,0x36,0x57,0x30,0x35,0x3C,0x32,0x33,0xE8,0x98,0xFE,
+0xFF,0xFF,0x4D,0x31,0x31,0x33,0x32,0x36,0x32,0x32,0x31,0x39,0x35,0x5B,0x31,0x38,0x30,0x35,0x3C,0x3C,0x34,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x33,
+0x33,0x38,0xE8,0x28,0x01,0x00,0x00,0x31,0x32,0x34,0x38,0x49,0x31,0x40,0x31,0x41,0x31,0x3F,0x36,0x30,0x3E,0x3B,0x36,0x3A,0x45,0x32,0x50,0x3C,0x45,0x3D,0x31,0x31,
+0x3C,0x3A,0x3A,0x46,0x4A,0x3F,0x36,0x33,0x43,0x3C,0x3D,0x32,0x37,0x36,0x3B,0x3E,0x36,0x40,0x32,0x39,0x51,0x31,0x33,0x32,0x36,0x44,0x31,0x35,0x34,0x3A,0x32,0x37,
+0x3D,0x33,0x48,0x45,0x31,0x31,0x30,0x3A,0x31,0x31,0x31,0x37,0x32,0x35,0x35,0x3B,0x35,0x35,0x48,0x38,0x33,0x61,0x40,0x36,0x33,0x36,0x39,0x30,0x30,0x3C,0x36,0x56,
+0x3D,0xE8,0x12,0xFF,0xFF,0xFF,0x3C,0x33,0x33,0x45,0xE8,0x32,0x3D,0x3F,0x37,0x35,0x37,0xE8,0x7E,0x00,0x00,0x00,0x34,0x31,0x30,0x34,0x31,0x30,0x32,0x30,0x41,0x35,
+0x31,0x44,0x3C,0x31,0x3A,0x4F,0x42,0x34,0x34,0x47,0x41,0x36,0x41,0x32,0x32,0x35,0x3A,0x41,0x37,0x32,0x43,0x31,0x38,0x40,0x3D,0x34,0x33,0x35,0x30,0x35,0x35,0x32,
+0x36,0x31,0x35,0x30,0x33,0x49,0x41,0x33,0x44,0x44,0x35,0x3B,0x3C,0x32,0x33,0x4D,0x33,0x32,0xE8,0xE9,0xFF,0xFF,0xFF,0x33,0x38,0x49,0x3F,0x3C,0x32,0x33,0x4D,0x33,
+0x32,0x38,0x3D,0x31,0x55,0x33,0x37,0x3C,0x30,0x3B,0x35,0x3D,0x4F,0x42,0x4F,0x33,0x33,0x3A,0x4D,0x35,0x34,0xE8,0x62,0xFF,0xFF,0xFF,0x31,0x33,0x5E,0x33,0x52,0x51,
+0x3E,0x31,0x41,0x34,0x40,0x44,0x3F,0x30,0x52,0x33,0x35,0x36,0x3D,0x3F,0x37,0x41,0x44,0x33,0x39,0x34,0x39,0x30,0x33,0x31,0x37,0x41,0x46,0x3C,0x3A,0xE8,0x30,0x3C,
+0x3B,0x48,0x36,0x32,0x33,0x33,0x36,0x3A,0x42,0x43,0x43,0x30,0x37,0x35,0x30,0x4B,0x46,0x4B,0x3C,0x54,0x36,0x3F,0x32,0x31,0x3D,0x3B,0x34,0x30,0x3E,0x32,0x34,0x4C,
+0x3F,0x30,0x41,0x32,0x43,0x30,0x36,0x33,0x41,0x36,0x35,0x31,0x30,0x47,0x38,0x31,0x35,0xE8,0x89,0xFF,0xFF,0xFF,0xFF,0xFF,0x32,0x52,0x32,0x32,0x50,0x39,0x3D,0x37,
+0x4E,0x33,0x34,0x30,0x4E,0x3A,0x49,0x35,0x37,0x37,0x33,0x44,0x36,0x32,0x3A,0x50,0x3A,0x32,0x34,0x36,0x3D,0x36,0x34,0x3C,0x4C,0x45,0x45,0x38,0x39,0x33,0x33,0x35,
+0x33,0x32,0x33,0x3D,0x38,0x38,0x32,0x37,0x32,0x42,0x32,0x36,0x40,0x57,0x34,0x33,0x33,0x4F,0x4D,0x42,0x39,0x34,0x30,0x3A,0x31,0x33,0x45,0x46,0x46,0x35,0x42,0x3D,
+0x38,0x3B,0x33,0x34,0x33,0x3F,0x38,0x37,0x4B,0x36,0x33,0x30,0x43,0x42,0x38,0x31,0x4B,0x40,0x4D,0x3F,0x56,0x41,0x33,0x41,0x36,0x3A,0x31,0x4F,0x36,0x44,0x33,0x45,
+0x34,0x4B,0x37,0x46,0x34,0x36,0x3F,0x30,0x3B,0x36,0x39,0x36,0x3B,0x34,0xE8,0x4D,0xFF,0xFF,0xFF,0x39,0x34,0x3D,0x38,0x3D,0x34,0x31,0x31,0x37,0x33,0x30,0x32,0x38,
+0x34,0x38,0x39,0x35,0x33,0x30,0x34,0x4B,0xE8,0x37,0xFF,0xFF,0xFF,0x33,0x32,0x3A,0x31,0x3B,0x36,0x3A,0x44,0x31,0x57,0x33,0x3E,0x3E,0x33,0x3C,0x32,0x33,0x4D,0x33,
+0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x38,0x49,0x3F,0x47,0x3C,0x32,0x33,0x4D,0x33,0x32,0x44,0x33,0x4E,0x40,0x5B,0x33,0x4E,0x3F,0x3D,0x35,0x4D,0x32,0x31,0x51,0x30,
+0x35,0x37,0x52,0x33,0x38,0x3A,0x39,0x3A,0x30,0x35,0x37,0x32,0x36,0x34,0x31,0x3A,0x32,0x31,0x38,0x38,0x3C,0x3C,0x3B,0x4F,0x4E,0x30,0x41,0x35,0x33,0x33,0x31,0x30,
+0x40,0x38,0x46,0x3F,0x32,0x31,0x3E,0x45,0x33,0x33,0x3B,0x31,0x31,0x35,0x38,0x57,0x38,0x37,0x39,0x33,0x30,0x35,0x33,0x48,0x36,0x37,0x32,0x48,0x3B,0x47,0x3E};
+
+#define BLOCKTYPE2_NO_TIMESTAMP_SIZE 1024
+
+static BYTE old_blocktype2_no_timestamp[] = {
+0x0E,0x52,0xCE,0x03,0xD9,0xFC,0xA7,0x06,0xF4,0x1D,0xC5,0x05,0x14,0x54,0xFD,0x06,0xA1,0xC2,0xDE,0x07,0x14,0x54,0xFD,0x06,0xB0,0xAC,0x7A,0x00,0x0E,0x52,0xCE,0x03,
+0x0E,0x52,0xCE,0x03,0x82,0x7E,0xBA,0x04,0xE5,0x00,0x7B,0x00,0x0E,0x52,0xCE,0x03,0x0E,0x52,0xCE,0x03,0x0E,0x52,0xCE,0x03,0x0E,0x52,0xCE,0x03,0xAF,0x95,0x59,0x00,
+0x0A,0x28,0x93,0x06,0x0E,0x52,0xCE,0x03,0x46,0x94,0x38,0x03,0xAD,0xBD,0xCC,0x03,0x97,0xB3,0xAD,0x02,0x0E,0x52,0xCE,0x03,0x56,0xD1,0x32,0x00,0xD6,0xDC,0xA3,0x04,
+0x05,0x9C,0xBE,0x05,0x88,0x25,0x30,0x01,0x63,0x74,0x09,0x01,0x75,0x27,0xD0,0x07,0x80,0x03,0x3F,0x01,0xF9,0xD4,0x98,0x06,0x14,0x1A,0x6E,0x06,0x0E,0x52,0xCE,0x03,
+0x0E,0x52,0xCE,0x03,0xE3,0x9C,0x04,0x02,0xC6,0x36,0x2C,0x01,0x0E,0x52,0xCE,0x03,0xA5,0x44,0xC0,0x03,0x0F,0xD2,0x8F,0x01,0x21,0x39,0x89,0x02,0x4A,0xFB,0x56,0x01,
+0xF7,0x34,0x60,0x03,0xB2,0x08,0x7A,0x03,0x16,0x0A,0x07,0x07,0xB5,0xD5,0xC5,0x02,0x01,0x5A,0x3F,0x06,0x0E,0x52,0xCE,0x03,0xF7,0x0A,0xEC,0x06,0x07,0x5A,0xC5,0x01,
+0x14,0x54,0xFD,0x06,0x79,0x63,0x08,0x03,0x74,0x8A,0xE0,0x04,0x1C,0x0E,0x41,0x02,0x84,0xCE,0xA9,0x07,0x8B,0x5A,0x9E,0x02,0x87,0xB6,0xDB,0x07,0xD0,0xBF,0x77,0x04,
+0xD3,0xB6,0x15,0x01,0xA5,0x44,0xC0,0x03,0xEE,0xBC,0x96,0x05,0x1A,0x99,0xA1,0x07,0x05,0x9C,0xBE,0x05,0x18,0x36,0xEF,0x03,0xD5,0x29,0x71,0x01,0x89,0xDC,0xD3,0x01,
+0x26,0xFB,0x79,0x06,0x75,0xCB,0x98,0x03,0x84,0xC7,0x4F,0x03,0x06,0x02,0x65,0x03,0x94,0xCA,0x63,0x03,0x35,0x52,0x88,0x07,0xC6,0xCC,0xFC,0x02,0xA2,0x8E,0xB1,0x01,
+0x9B,0xDB,0x34,0x06,0xB4,0x2D,0x3C,0x07,0x20,0xBF,0x0E,0x05,0x84,0xC7,0x4F,0x03,0xFA,0x91,0x5B,0x03,0xAD,0xBD,0xCC,0x03,0xC8,0xA3,0xAE,0x01,0x8F,0x99,0x0B,0x01,
+0x48,0xA8,0xF2,0x03,0xDC,0x54,0xFC,0x07,0xDA,0xAB,0x04,0x02,0x46,0x80,0x24,0x06,0x8F,0x4E,0x16,0x05,0xB9,0xA9,0x30,0x01,0x74,0x72,0xA1,0x05,0x34,0x67,0x4C,0x01,
+0xB1,0x56,0xA4,0x00,0x0A,0x28,0x93,0x06,0x31,0xCA,0x54,0x04,0x14,0x54,0xFD,0x06,0x5A,0xCA,0x57,0x03,0x85,0x6C,0x93,0x04,0x97,0xC3,0x42,0x06,0x0A,0x28,0x93,0x06,
+0x21,0x39,0x89,0x02,0x89,0x5E,0xFB,0x06,0xFD,0x6D,0x46,0x00,0x54,0x23,0x25,0x05,0x0E,0x52,0xCE,0x03,0x5A,0xDB,0xFA,0x01,0x0E,0x52,0xCE,0x03,0x81,0xE4,0x1E,0x01,
+0x9B,0x21,0x30,0x04,0x09,0x53,0xAC,0x05,0xF1,0xA9,0x2D,0x07,0xDD,0x4D,0x30,0x06,0xFF,0x38,0xBD,0x02,0xAD,0xAA,0xDB,0x03,0xEC,0xAE,0x4E,0x01,0x40,0x19,0x44,0x02,
+0x35,0x52,0x88,0x07,0x4A,0x85,0xEF,0x02,0x80,0x03,0x3F,0x01,0xDB,0x5A,0x88,0x07,0x36,0x77,0x4F,0x07,0x9B,0x21,0x30,0x04,0x1D,0x52,0x4F,0x02,0xA5,0x97,0xFB,0x00,
+0x2F,0xC8,0x67,0x01,0x32,0x96,0xB0,0x06,0x35,0x52,0x88,0x07,0x4E,0x46,0x30,0x06,0xE9,0x98,0x28,0x05,0xF8,0x0D,0xD9,0x01,0x0E,0x52,0xCE,0x03,0x6F,0x7C,0x24,0x07,
+0x1A,0x99,0xA1,0x07,0x11,0x2D,0x22,0x04,0x15,0xA4,0xB7,0x05,0x77,0xD9,0xA0,0x06,0xB8,0x56,0x63,0x02,0xD8,0xB3,0xE2,0x05,0x9F,0xE5,0x87,0x02,0x2D,0x97,0x69,0x03,
+0xE9,0x98,0x28,0x05,0xDC,0x08,0xA4,0x02,0x0A,0x28,0x93,0x06,0x5F,0xF4,0x6C,0x03,0xC4,0xC3,0xBC,0x01,0xA5,0x44,0xC0,0x03,0xA1,0x0C,0xFE,0x05,0x3C,0x05,0x75,0x07,
+0x76,0x91,0xA1,0x06,0x42,0x63,0x1F,0x04,0x36,0xCE,0x65,0x04,0xB0,0x0A,0x67,0x02,0xB2,0x5D,0xE4,0x02,0x87,0x78,0x84,0x06,0xFD,0xF4,0x8D,0x05,0x6E,0x25,0xA3,0x06,
+0xD8,0xE0,0x0C,0x01,0x12,0x7F,0xAD,0x04,0xD8,0x18,0x1C,0x02,0xFD,0xC6,0x99,0x01,0x41,0xCC,0x03,0x07,0xAF,0xBC,0xCB,0x03,0x8C,0x2E,0xF9,0x07,0x0A,0x28,0x93,0x06,
+0xCB,0xA6,0xAC,0x01,0x87,0x78,0x84,0x06,0x88,0x6C,0x61,0x02,0xA8,0x31,0x66,0x03,0x05,0xFD,0xD0,0x07,0x14,0x54,0xFD,0x06,0x70,0x1D,0x18,0x06,0x87,0x78,0x84,0x06,
+0x0E,0x52,0xCE,0x03,0x2A,0x24,0xF0,0x02,0x18,0xDB,0x54,0x04,0x8F,0xCF,0x4B,0x03,0xD1,0x44,0xCA,0x02,0x2E,0xEB,0x35,0x07,0xCF,0xBA,0xA5,0x01,0x70,0x6D,0x49,0x01,
+0x4D,0x1B,0xA7,0x01,0x96,0x88,0x0C,0x06,0x20,0x75,0x29,0x00,0x98,0xB9,0x56,0x04,0x5C,0x45,0x03,0x00,0x07,0xE5,0xAC,0x00,0x09,0x12,0x6A,0x02,0x9E,0xFB,0xC2,0x02,
+0x43,0x70,0x56,0x02,0xDA,0x0C,0x6E,0x04,0x1F,0xD4,0xBB,0x05,0x98,0xB6,0xC2,0x03,0x71,0x8B,0x0C,0x04,0x23,0x99,0x84,0x06,0x07,0xAE,0xC5,0x04,0x04,0xE5,0x61,0x00,
+0xDE,0xE5,0xC1,0x02,0x6F,0x79,0x37,0x01,0xA8,0xA6,0x74,0x07,0xF9,0xCD,0xD7,0x03,0x6E,0x39,0xFE,0x02,0x87,0x78,0x80,0x03,0x14,0xA8,0x7C,0x05,0xEC,0x42,0xB0,0x03,
+0x9F,0xE5,0x87,0x02,0xA5,0x44,0xC0,0x03,0xD8,0x41,0x5C,0x06,0xAB,0x8E,0xD2,0x05,0xA7,0x33,0x9A,0x01,0x62,0x03,0x5C,0x07,0xD5,0x27,0x82,0x07,0xEF,0x85,0xD2,0x03,
+0xA5,0x84,0x92,0x05,0x8D,0x30,0xE3,0x03,0x8A,0xC2,0x51,0x04,0x57,0x8C,0x27,0x01,0xF9,0xD4,0x98,0x06,0x62,0xB1,0xAB,0x06,0x27,0x5F,0x4B,0x00,0xBB,0xD0,0x92,0x04,
+0x5F,0x4B,0x14,0x06,0x8D,0x03,0x68,0x06,0x4C,0x5F,0x17,0x00,0x2D,0x57,0xF5,0x00,0x03,0xF9,0xE9,0x05,0x0E,0x52,0xCE,0x03,0xC2,0xC3,0x24,0x06,0x86,0x4A,0x94,0x01,
+0x00,0xE2,0xE0,0x01,0x27,0x30,0xBE,0x00,0x3A,0xA8,0xC6,0x00,0xEF,0x85,0xD2,0x03,0x31,0x58,0x7E,0x01,0xB1,0x03,0x02,0x04,0x0E,0x52,0xCE,0x03,0xE5,0xA7,0x59,0x01,
+0x56,0xB7,0x19,0x01,0x87,0x78,0x84,0x06,0x06,0x7A,0x52,0x02,0x85,0x6C,0x93,0x04,0x87,0xDB,0x40,0x04,0x1A,0xCF,0xBF,0x06,0xAB,0x8E,0xD2,0x05,0xD0,0x9F,0xD3,0x03,
+0xC8,0xC7,0xF8,0x01,0x2F,0x13,0x04,0x06,0xE9,0x98,0x28,0x05,0x1E,0x06,0x61,0x07,0x9F,0xE5,0x87,0x02,0x17,0x0D,0xDB,0x03,0xFA,0x91,0x5B,0x03,0x36,0xA6,0xA5,0x03,
+0x9B,0x1A,0xC2,0x03,0x8B,0xD9,0x3F,0x04,0x7F,0x5B,0x6C,0x01,0xAA,0x08,0x58,0x04,0xF3,0x03,0x33,0x02,0xD5,0x29,0x71,0x01,0x37,0x32,0x48,0x3B,0x47,0x3E,0x3E,0xCC};
+
+static BYTE patch_blocktype2_no_timestamp[] = {
+0x50,0x41,0x31,0x39,0x01,0x00,0x44,0x00,0x00,0x88,0xEA,0x93,0x4F,0x32,0x01,0x80,0x40,0xBF,0x4B,0x5E,0x00,0x00,0x80,0x00,0x84,0xFE,0x01,0x00,0x20,0x00,0x40,0x00,
+0x08,0x00,0x81,0x00,0x00,0x24,0x03,0x00,0x66,0x50,0x00,0xEF,0x0B,0xFB,0xFE,0xFB,0xEE,0x11,0x71,0x65,0x4E,0xE6,0xD8,0x87,0x0A,0x24,0x25,0xCC,0xE6,0xED,0x36,0x37,
+0x38,0x5A,0x78,0xAF,0x4B,0xF0,0x12,0x25,0xA1,0x4A,0xE1,0x45,0x9C,0x1A,0x5D,0xC0,0xAB,0x16,0xDE,0xB4,0x35,0x8E,0xCB,0xC8,0xDB,0x78,0xDB,0xCA,0x5B,0x5D,0x85,0xAA,
+0xD4,0x28,0x45,0x90,0x8A,0xAA,0x50,0x21,0x57,0x00,0x00,0x03,0x00,0x46,0x43,0x00,0x60,0x02,0x45,0x4C,0x99,0x65,0xDC,0xD0,0xA7,0xCD,0xB7,0xC2,0x4B,0x4B,0xBC,0x8D,
+0xC0,0x2B,0x9C,0xB3,0xC4,0xBC,0x85,0xC0,0x4D,0xC4,0x8B,0x20,0xDD,0x14,0xDE,0x85,0xB0,0x19,0xFF,0x34,0xF5,0x27,0x5A,0xEE,0xBF,0x00,0x04,0x00,0x00,0x00,0x00,0x0C,
+0x01,0x08,0x10,0xFC,0x38,0xC7,0x60,0xBF,0x5F,0xFC,0x7E,0xF0,0x84,0xF4,0x09,0x4A,0xF2,0x26,0x9D,0x1F,0x1A,0x1D,0xC7,0x7A,0x53,0x32,0x6C,0x40,0xAF,0xB1,0x1A,0xED,
+0x3F,0xD0,0x13,0xB7,0xF3,0xDB,0xB9,0x5C,0xA4,0xF3,0x44,0xE7,0x66,0x44,0x98,0x9E,0xF4,0xBC,0x13,0x7C,0xBD,0xE0,0x42,0x53,0x87,0x74,0x2D,0x00,0xDB,0xDF,0xEA,0xE9,
+0x46,0xE8,0xC1,0x1D,0x78,0x92,0xCA,0xFB,0x2F,0x82,0xD8,0x66,0x48,0x39,0xF3,0x66,0xC7,0xBC,0xEA,0xD8,0x67,0xDB,0x77,0x4A,0xAE,0x0C,0x47,0x21,0x1B,0xE8,0x44,0x28,
+0xEB,0x5E,0xA4,0x0E,0xC7,0x83,0x28,0xFB,0x8A,0x03,0x0D,0x27,0x53,0x15,0xEE,0xC6,0x94,0xB5,0x46,0xE3,0x58,0xFE,0x26,0x09,0xCF,0x75,0xD8,0x3A,0x1E,0x79,0x47,0xA3,
+0x91,0xB2,0xD7,0x36,0xB6,0x97,0x07,0xC0,0xE0,0x03,0xB8,0x49,0x0E,0x4D,0xC3,0x33,0xBA,0xBE,0x5C,0xEB,0xC7,0xEC,0xCD,0x69,0x9F,0xB8,0x41,0x8A,0x78,0x26,0x85,0x8A,
+0x90,0xBF,0xB8,0x46,0x2D,0x3C,0x72,0x34,0xDC,0x6D,0x33,0x68,0x55,0x13,0x67,0x32,0x1C,0xD2,0x90,0x7C,0xA4,0x54,0xD2,0x14,0x8A,0x58,0xB8,0xCF,0x1F,0xF8,0x5A,0x38,
+0xC8,0xD6,0xD5,0x20,0x34,0xEE,0x43,0xEC,0x5F,0x24,0xD1,0x1F,0x71,0xB3,0x72,0x0F,0x2E,0x22,0xD3,0xEE,0x9C,0x95,0x7E,0x2C,0x81,0x60,0x05,0x9F,0x13,0xA0,0x84,0xF0,
+0xE4,0x00,0x2B,0x84,0x96,0x3D,0xF8,0x60,0xF0,0xAE,0xE8,0x3A,0x81,0xEB,0xF9,0x7E,0xB7,0x8F,0x2A,0x88,0x7F,0xBA,0x43,0xE0,0xD2,0x6F,0x17,0xFD,0xE7,0x9E,0x6C,0x94,
+0xF2,0x63,0x3F,0xA2,0xAD,0xE7,0x9E,0x12,0xB5,0xF3,0x2D,0x53,0x4A,0x09,0x65,0x2C,0x84,0x4F,0x0E,0xA6,0x0E,0xE8,0xC3,0x2B,0x01,0x4E,0x28,0xF9,0xDB,0x89,0x8E,0xE7,
+0x17,0x58,0x81,0xDF,0x04,0x05,0xD4,0x8A,0xEB,0xC7,0xB2,0xE3,0xF1,0x8A,0xF0,0x16,0x06,0xA8,0x48,0x03,0x96,0x3F,0xD4,0xE4,0xCF,0x61,0xC8,0x81,0x43,0xE9,0x7F,0xA9,
+0x7C,0xCA,0xCA,0xEE,0x67,0xAB,0x50,0x3C,0xAF,0xFF,0xE2,0x85,0x29,0xE4,0xA9,0x21,0x31,0xC7,0xCD,0xFE,0xCE,0xA9,0xB8,0xB7,0xBB,0xCD,0x6B,0xFF,0x16,0x64,0xDD,0xD4,
+0xBA,0x56,0xEC,0x93,0xBE,0xDF,0x92,0xBF,0xE6,0xD4,0x83,0xF5,0xC7,0x7A,0x98,0x33,0x7E,0x77,0x1E,0x18,0x1C,0x47,0x03,0x40,0x4B,0x36,0x5E,0xC3,0x06};
+
+#define NULL_INPUT_UNCOMPRESSED_SIZE 65
+
+static BYTE patch_null_input_uncompressed[] = {
+0x50,0x41,0x31,0x39,0x01,0x00,0xC4,0x00,0xC5,0xA5,0xBA,0x5C,0xC1,0x48,0x7F,0x15,0x2D,0x01,0x41,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xD4,0x52,0x00,0x00,0x30,
+0x10,0x04,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xF0,0x9B,0x50,0x8E,0xD9,0x89,0x50,0x0D,0x9C,0xD6,0x8A,0xF9,0x6E,0xB9,0xEF,0xEE,0x0F,0x9C,
+0xA6,0xE1,0x16,0x54,0x80,0x55,0x8B,0x97,0x65,0x8D,0x02,0xA1,0x85,0xEA,0x51,0x08,0xD6,0xE6,0x0B,0x19,0x01,0xF5,0x7F,0x9D,0x63,0x71,0x58,0x28,0x33,0x70,0xA8,0x93,
+0xF8,0x62,0x5E,0x56,0x11,0x47,0x60,0x22,0x9E,0x76,0x26,0x8C,0xFC,0x41,0x97,0x00,0x0E,0x73,0xBF,0xE4};
+
+#define UNNECESSARY_SIZE 65
+
+static BYTE old_unnecessary[] = {
+0xF0,0x9B,0x50,0x8E,0xD9,0x89,0x50,0x0D,0x9C,0xD6,0x8A,0xF9,0x6E,0xB9,0xEF,0xEE,0x0F,0x9C,0xA6,0xE1,0x16,0x54,0x80,0x55,0x8B,0x97,0x65,0x8D,0x02,0xA1,0x85,0xEA,
+0x51,0x08,0xD6,0xE6,0x0B,0x19,0x01,0xF5,0x7F,0x9D,0x63,0x71,0x58,0x28,0x33,0x70,0xA8,0x93,0xF8,0x62,0x5E,0x56,0x11,0x47,0x60,0x22,0x9E,0x76,0x26,0x8C,0xFC,0x41,
+0x97};
+
+static BYTE patch_unnecessary[] = {
+0x50,0x41,0x31,0x39,0x01,0x00,0xC4,0x00,0xC5,0xA5,0xBA,0x5C,0xC1,0x48,0x7F,0x15,0x2D,0x01,0x80,0x48,0x7F,0x15,0x2D,0x00,0x00,0x80,0x80,0x90,0x68,0xE9,0x14};
+
--
2.7.4



Reply | Threaded
Open this post in threaded view
|

Re: [PATCH 3/3] mspatcha: Test implementations of ApplyPatchToFile and related functions.

Marvin-2
Hi,

While running your changed tests, I think I found new failures.
Being a bot and all I'm not very good at pattern recognition, so I might be
wrong, but could you please double-check?

Full results can be found at:
https://testbot.winehq.org/JobDetails.pl?Key=51221

Your paranoid android.


=== wxppro (32 bit report) ===

mspatcha:
patch_apply.c:171: Test failed: ApplyPatchToFileA: expected TRUE
patch_apply.c:173: Test failed: Expected ERROR_SUCCESS, got 0x3
0b40:patch_apply: unhandled exception c0000005 at 00000000

=== w2003std (32 bit report) ===

mspatcha:
patch_apply.c:171: Test failed: ApplyPatchToFileA: expected TRUE
patch_apply.c:173: Test failed: Expected ERROR_SUCCESS, got 0x3
06ac:patch_apply: unhandled exception c0000005 at 00000000

=== wvistau64 (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0
073c:patch_apply: unhandled exception c0000005 at 771BD5D5

=== wvistau64_zh_CN (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0
0bb4:patch_apply: unhandled exception c0000005 at 774DD5D5

=== wvistau64_fr (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0
06a8:patch_apply: unhandled exception c0000005 at 7755D5D5

=== wvistau64_he (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0
0acc:patch_apply: unhandled exception c0000005 at 773BD5D5

=== w2008s64 (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0
052c:patch_apply: unhandled exception c0000005 at 7722D5D5

=== w7u (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0

=== w7pro64 (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0

=== w8 (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0

=== w8adm (task log) ===

Task errors:
The task timed out

=== w864 (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0

=== w1064 (32 bit report) ===

mspatcha:
patch_apply.c:164: Test failed: Expected ERROR_CANCELLED, got 0x0

=== wvistau64 (64 bit report) ===

mspatcha:
073c:patch_apply: unhandled exception c0000005 at 000000007701C5D8

=== w2008s64 (64 bit report) ===

mspatcha:
0460:patch_apply: unhandled exception c0000005 at 000000007708C5D8