You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Ventoy/GRUB2/MOD_SRC/grub-2.04/grub-core/ventoy/lzx.c

667 lines
15 KiB
C

/*
* Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* @file
*
* LZX decompression
*
* This algorithm is derived jointly from the document "[MS-PATCH]:
* LZX DELTA Compression and Decompression", available from
*
* http://msdn.microsoft.com/en-us/library/cc483133.aspx
*
* and from the file lzx-decompress.c in the wimlib source code.
*
*/
#include "wimboot.h"
#include "huffman.h"
#include "lzx.h"
/** Base positions, indexed by position slot */
static unsigned int lzx_position_base[LZX_POSITION_SLOTS];
/**
* Attempt to accumulate bits from LZX bitstream
*
* @v lzx Decompressor
* @v bits Number of bits to accumulate
* @v norm_value Accumulated value (normalised to 16 bits)
*
* Note that there may not be sufficient accumulated bits in the
* bitstream; callers must check that sufficient bits are available
* before using the value.
*/
static int lzx_accumulate ( struct lzx *lzx, unsigned int bits ) {
const uint16_t *src16;
/* Accumulate more bits if required */
if ( ( lzx->bits < bits ) &&
( lzx->input.offset < lzx->input.len ) ) {
src16 = (const uint16_t *)( ( char * ) lzx->input.data + lzx->input.offset );
lzx->input.offset += sizeof ( *src16 );
lzx->accumulator |= ( *src16 << ( 16 - lzx->bits ) );
lzx->bits += 16;
}
return ( lzx->accumulator >> 16 );
}
/**
* Consume accumulated bits from LZX bitstream
*
* @v lzx Decompressor
* @v bits Number of bits to consume
* @ret rc Return status code
*/
static int lzx_consume ( struct lzx *lzx, unsigned int bits ) {
/* Fail if insufficient bits are available */
if ( lzx->bits < bits ) {
DBG ( "LZX input overrun in %#zx/%#zx out %#zx)\n",
lzx->input.offset, lzx->input.len, lzx->output.offset );
return -1;
}
/* Consume bits */
lzx->accumulator <<= bits;
lzx->bits -= bits;
return 0;
}
/**
* Get bits from LZX bitstream
*
* @v lzx Decompressor
* @v bits Number of bits to fetch
* @ret value Value, or negative error
*/
static int lzx_getbits ( struct lzx *lzx, unsigned int bits ) {
int norm_value;
int rc;
/* Accumulate more bits if required */
norm_value = lzx_accumulate ( lzx, bits );
/* Consume bits */
if ( ( rc = lzx_consume ( lzx, bits ) ) != 0 )
return rc;
return ( norm_value >> ( 16 - bits ) );
}
/**
* Align LZX bitstream for byte access
*
* @v lzx Decompressor
* @v bits Minimum number of padding bits
* @ret rc Return status code
*/
static int lzx_align ( struct lzx *lzx, unsigned int bits ) {
int pad;
/* Get padding bits */
pad = lzx_getbits ( lzx, bits );
if ( pad < 0 )
return pad;
/* Consume all accumulated bits */
lzx_consume ( lzx, lzx->bits );
return 0;
}
/**
* Get bytes from LZX bitstream
*
* @v lzx Decompressor
* @v data Data buffer, or NULL
* @v len Length of data buffer
* @ret rc Return status code
*/
static int lzx_getbytes ( struct lzx *lzx, void *data, size_t len ) {
/* Sanity check */
if ( ( lzx->input.offset + len ) > lzx->input.len ) {
DBG ( "LZX input overrun in %#zx/%#zx out %#zx)\n",
lzx->input.offset, lzx->input.len, lzx->output.offset );
return -1;
}
/* Copy data */
if ( data )
memcpy ( data, ( lzx->input.data + lzx->input.offset ), len );
lzx->input.offset += len;
return 0;
}
/**
* Decode LZX Huffman-coded symbol
*
* @v lzx Decompressor
* @v alphabet Huffman alphabet
* @ret raw Raw symbol, or negative error
*/
static int lzx_decode ( struct lzx *lzx, struct huffman_alphabet *alphabet ) {
struct huffman_symbols *sym;
int huf;
int rc;
/* Accumulate sufficient bits */
huf = lzx_accumulate ( lzx, HUFFMAN_BITS );
if ( huf < 0 )
return huf;
/* Decode symbol */
sym = huffman_sym ( alphabet, huf );
/* Consume bits */
if ( ( rc = lzx_consume ( lzx, huffman_len ( sym ) ) ) != 0 )
return rc;
return huffman_raw ( sym, huf );
}
/**
* Generate Huffman alphabet from raw length table
*
* @v lzx Decompressor
* @v count Number of symbols
* @v bits Length of each length (in bits)
* @v lengths Lengths table to fill in
* @v alphabet Huffman alphabet to fill in
* @ret rc Return status code
*/
static int lzx_raw_alphabet ( struct lzx *lzx, unsigned int count,
unsigned int bits, uint8_t *lengths,
struct huffman_alphabet *alphabet ) {
unsigned int i;
int len;
int rc;
/* Read lengths */
for ( i = 0 ; i < count ; i++ ) {
len = lzx_getbits ( lzx, bits );
if ( len < 0 )
return len;
lengths[i] = len;
}
/* Generate Huffman alphabet */
if ( ( rc = huffman_alphabet ( alphabet, lengths, count ) ) != 0 )
return rc;
return 0;
}
/**
* Generate pretree
*
* @v lzx Decompressor
* @v count Number of symbols
* @v lengths Lengths table to fill in
* @ret rc Return status code
*/
static int lzx_pretree ( struct lzx *lzx, unsigned int count,
uint8_t *lengths ) {
unsigned int i;
unsigned int length;
int dup = 0;
int code;
int rc;
/* Generate pretree alphabet */
if ( ( rc = lzx_raw_alphabet ( lzx, LZX_PRETREE_CODES,
LZX_PRETREE_BITS, lzx->pretree_lengths,
&lzx->pretree ) ) != 0 )
return rc;
/* Read lengths */
for ( i = 0 ; i < count ; i++ ) {
if ( dup ) {
/* Duplicate previous length */
lengths[i] = lengths[ i - 1 ];
dup--;
} else {
/* Get next code */
code = lzx_decode ( lzx, &lzx->pretree );
if ( code < 0 )
return code;
/* Interpret code */
if ( code <= 16 ) {
length = ( ( lengths[i] - code + 17 ) % 17 );
} else if ( code == 17 ) {
length = 0;
dup = lzx_getbits ( lzx, 4 );
if ( dup < 0 )
return dup;
dup += 3;
} else if ( code == 18 ) {
length = 0;
dup = lzx_getbits ( lzx, 5 );
if ( dup < 0 )
return dup;
dup += 19;
} else if ( code == 19 ) {
length = 0;
dup = lzx_getbits ( lzx, 1 );
if ( dup < 0 )
return dup;
dup += 3;
code = lzx_decode ( lzx, &lzx->pretree );
if ( code < 0 )
return code;
length = ( ( lengths[i] - code + 17 ) % 17 );
} else {
DBG ( "Unrecognised pretree code %d\n", code );
return -1;
}
lengths[i] = length;
}
}
/* Sanity check */
if ( dup ) {
DBG ( "Pretree duplicate overrun\n" );
return -1;
}
return 0;
}
/**
* Generate aligned offset Huffman alphabet
*
* @v lzx Decompressor
* @ret rc Return status code
*/
static int lzx_alignoffset_alphabet ( struct lzx *lzx ) {
int rc;
/* Generate aligned offset alphabet */
if ( ( rc = lzx_raw_alphabet ( lzx, LZX_ALIGNOFFSET_CODES,
LZX_ALIGNOFFSET_BITS,
lzx->alignoffset_lengths,
&lzx->alignoffset ) ) != 0 )
return rc;
return 0;
}
/**
* Generate main Huffman alphabet
*
* @v lzx Decompressor
* @ret rc Return status code
*/
static int lzx_main_alphabet ( struct lzx *lzx ) {
int rc;
/* Generate literal symbols pretree */
if ( ( rc = lzx_pretree ( lzx, LZX_MAIN_LIT_CODES,
lzx->main_lengths.literals ) ) != 0 ) {
DBG ( "Could not construct main literal pretree\n" );
return rc;
}
/* Generate remaining symbols pretree */
if ( ( rc = lzx_pretree ( lzx, ( LZX_MAIN_CODES - LZX_MAIN_LIT_CODES ),
lzx->main_lengths.remainder ) ) != 0 ) {
DBG ( "Could not construct main remainder pretree\n" );
return rc;
}
/* Generate Huffman alphabet */
if ( ( rc = huffman_alphabet ( &lzx->main, lzx->main_lengths.literals,
LZX_MAIN_CODES ) ) != 0 ) {
DBG ( "Could not generate main alphabet\n" );
return rc;
}
return 0;
}
/**
* Generate length Huffman alphabet
*
* @v lzx Decompressor
* @ret rc Return status code
*/
static int lzx_length_alphabet ( struct lzx *lzx ) {
int rc;
/* Generate pretree */
if ( ( rc = lzx_pretree ( lzx, LZX_LENGTH_CODES,
lzx->length_lengths ) ) != 0 ) {
DBG ( "Could not generate length pretree\n" );
return rc;
}
/* Generate Huffman alphabet */
if ( ( rc = huffman_alphabet ( &lzx->length, lzx->length_lengths,
LZX_LENGTH_CODES ) ) != 0 ) {
DBG ( "Could not generate length alphabet\n" );
return rc;
}
return 0;
}
/**
* Process LZX block header
*
* @v lzx Decompressor
* @ret rc Return status code
*/
static int lzx_block_header ( struct lzx *lzx ) {
size_t block_len;
int block_type;
int default_len;
int len_high;
int len_low;
int rc;
/* Get block type */
block_type = lzx_getbits ( lzx, LZX_BLOCK_TYPE_BITS );
if ( block_type < 0 )
return block_type;
lzx->block_type = block_type;
/* Check block length */
default_len = lzx_getbits ( lzx, 1 );
if ( default_len < 0 )
return default_len;
if ( default_len ) {
block_len = LZX_DEFAULT_BLOCK_LEN;
} else {
len_high = lzx_getbits ( lzx, 8 );
if ( len_high < 0 )
return len_high;
len_low = lzx_getbits ( lzx, 8 );
if ( len_low < 0 )
return len_low;
block_len = ( ( len_high << 8 ) | len_low );
}
lzx->output.threshold = ( lzx->output.offset + block_len );
/* Handle block type */
switch ( block_type ) {
case LZX_BLOCK_ALIGNOFFSET :
/* Generated aligned offset alphabet */
if ( ( rc = lzx_alignoffset_alphabet ( lzx ) ) != 0 )
return rc;
/* Fall through */
case LZX_BLOCK_VERBATIM :
/* Generate main alphabet */
if ( ( rc = lzx_main_alphabet ( lzx ) ) != 0 )
return rc;
/* Generate lengths alphabet */
if ( ( rc = lzx_length_alphabet ( lzx ) ) != 0 )
return rc;
break;
case LZX_BLOCK_UNCOMPRESSED :
/* Align input stream */
if ( ( rc = lzx_align ( lzx, 1 ) ) != 0 )
return rc;
/* Read new repeated offsets */
if ( ( rc = lzx_getbytes ( lzx, &lzx->repeated_offset,
sizeof ( lzx->repeated_offset )))!=0)
return rc;
break;
default:
DBG ( "Unrecognised block type %d\n", block_type );
return -1;
}
return 0;
}
/**
* Process uncompressed data
*
* @v lzx Decompressor
* @ret rc Return status code
*/
static int lzx_uncompressed ( struct lzx *lzx ) {
void *data;
size_t len;
int rc;
/* Copy bytes */
data = ( lzx->output.data ?
( lzx->output.data + lzx->output.offset ) : NULL );
len = ( lzx->output.threshold - lzx->output.offset );
if ( ( rc = lzx_getbytes ( lzx, data, len ) ) != 0 )
return rc;
/* Align input stream */
if ( len % 2 )
lzx->input.offset++;
return 0;
}
/**
* Process an LZX token
*
* @v lzx Decompressor
* @ret rc Return status code
*
* Variable names are chosen to match the LZX specification
* pseudo-code.
*/
static int lzx_token ( struct lzx *lzx ) {
unsigned int length_header;
unsigned int position_slot;
unsigned int offset_bits;
unsigned int i;
size_t match_offset;
size_t match_length;
int verbatim_bits;
int aligned_bits;
int maindata;
int length;
uint8_t *copy;
/* Get maindata symelse*/
maindata = lzx_decode ( lzx, &lzx->main );
if ( maindata < 0 )
return maindata;
/* Check for literals */
if ( maindata < LZX_MAIN_LIT_CODES ) {
if ( lzx->output.data )
lzx->output.data[lzx->output.offset] = maindata;
lzx->output.offset++;
return 0;
}
maindata -= LZX_MAIN_LIT_CODES;
/* Calculate the match length */
length_header = ( maindata & 7 );
if ( length_header == 7 ) {
length = lzx_decode ( lzx, &lzx->length );
if ( length < 0 )
return length;
} else {
length = 0;
}
match_length = ( length_header + 2 + length );
/* Calculate the position slot */
position_slot = ( maindata >> 3 );
if ( position_slot < LZX_REPEATED_OFFSETS ) {
/* Repeated offset */
match_offset = lzx->repeated_offset[position_slot];
lzx->repeated_offset[position_slot] = lzx->repeated_offset[0];
lzx->repeated_offset[0] = match_offset;
} else {
/* Non-repeated offset */
offset_bits = lzx_footer_bits ( position_slot );
if ( ( lzx->block_type == LZX_BLOCK_ALIGNOFFSET ) &&
( offset_bits >= 3 ) ) {
verbatim_bits = lzx_getbits ( lzx, ( offset_bits - 3 ));
if ( verbatim_bits < 0 )
return verbatim_bits;
verbatim_bits <<= 3;
aligned_bits = lzx_decode ( lzx, &lzx->alignoffset );
if ( aligned_bits < 0 )
return aligned_bits;
} else {
verbatim_bits = lzx_getbits ( lzx, offset_bits );
if ( verbatim_bits < 0 )
return verbatim_bits;
aligned_bits = 0;
}
match_offset = ( lzx_position_base[position_slot] +
verbatim_bits + aligned_bits - 2 );
/* Update repeated offset list */
for ( i = ( LZX_REPEATED_OFFSETS - 1 ) ; i > 0 ; i-- )
lzx->repeated_offset[i] = lzx->repeated_offset[ i - 1 ];
lzx->repeated_offset[0] = match_offset;
}
/* Copy data */
if ( match_offset > lzx->output.offset ) {
DBG ( "LZX match underrun out 0x%x offset 0x%x len 0x%x\n",
lzx->output.offset, match_offset, match_length );
return -1;
}
if ( lzx->output.data ) {
copy = &lzx->output.data[lzx->output.offset];
for ( i = 0 ; i < match_length ; i++ )
copy[i] = copy[ i - match_offset ];
}
lzx->output.offset += match_length;
return 0;
}
/**
* Translate E8 jump addresses
*
* @v lzx Decompressor
*/
static void lzx_translate_jumps ( struct lzx *lzx ) {
size_t offset;
int32_t *target;
/* Sanity check */
if ( lzx->output.offset < 10 )
return;
/* Scan for jump instructions */
for ( offset = 0 ; offset < ( lzx->output.offset - 10 ) ; offset++ ) {
/* Check for jump instruction */
if ( lzx->output.data[offset] != 0xe8 )
continue;
/* Translate jump target */
target = ( ( int32_t * ) &lzx->output.data[ offset + 1 ] );
if ( *target >= 0 ) {
if ( *target < LZX_WIM_MAGIC_FILESIZE )
*target -= offset;
} else {
if ( *target >= -( ( int32_t ) offset ) )
*target += LZX_WIM_MAGIC_FILESIZE;
}
offset += sizeof ( *target );
}
}
/**
* Decompress LZX-compressed data
*
* @v data Compressed data
* @v len Length of compressed data
* @v buf Decompression buffer, or NULL
* @ret out_len Length of decompressed data, or negative error
*/
ssize_t lzx_decompress ( const void *data, size_t len, void *buf ) {
struct lzx lzx;
unsigned int i;
int rc;
/* Sanity check */
if ( len % 2 ) {
DBG ( "LZX cannot handle odd-length input data\n" );
return -1;
}
/* Initialise global state, if required */
if ( ! lzx_position_base[ LZX_POSITION_SLOTS - 1 ] ) {
for ( i = 1 ; i < LZX_POSITION_SLOTS ; i++ ) {
lzx_position_base[i] =
( lzx_position_base[i-1] +
( 1 << lzx_footer_bits ( i - 1 ) ) );
}
}
/* Initialise decompressor */
memset ( &lzx, 0, sizeof ( lzx ) );
lzx.input.data = data;
lzx.input.len = len;
lzx.output.data = buf;
for ( i = 0 ; i < LZX_REPEATED_OFFSETS ; i++ )
lzx.repeated_offset[i] = 1;
/* Process blocks */
while ( lzx.input.offset < lzx.input.len ) {
/* Process block header */
if ( ( rc = lzx_block_header ( &lzx ) ) != 0 )
return rc;
/* Process block contents */
if ( lzx.block_type == LZX_BLOCK_UNCOMPRESSED ) {
/* Copy uncompressed data */
if ( ( rc = lzx_uncompressed ( &lzx ) ) != 0 )
return rc;
} else {
/* Process token stream */
while ( lzx.output.offset < lzx.output.threshold ) {
if ( ( rc = lzx_token ( &lzx ) ) != 0 )
return rc;
}
}
}
/* Postprocess to undo E8 jump compression */
if ( lzx.output.data )
lzx_translate_jumps ( &lzx );
return lzx.output.offset;
}