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