Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=12501 Signed-off-by: Conor McCarthy conor.mccarthy.444@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