mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-07-09 02:16:46 +00:00
Attempts at changes
This commit is contained in:
@@ -321,49 +321,72 @@ namespace LibMSPackSharp.CAB
|
||||
}
|
||||
}
|
||||
|
||||
// Close existing file handles
|
||||
System.Close(State?.InputFileHandle);
|
||||
// Close existing output file handle in case of error
|
||||
System.Close(State?.OutputFileHandle);
|
||||
|
||||
// Allocate generic decompression state
|
||||
State = new DecompressState()
|
||||
if (State == null)
|
||||
{
|
||||
Folder = file.Folder,
|
||||
Data = file.Folder.Data,
|
||||
Offset = 0,
|
||||
Block = 0,
|
||||
Outlen = 0,
|
||||
State = new DecompressState()
|
||||
{
|
||||
Folder = null,
|
||||
Data = null,
|
||||
System = System,
|
||||
DecompressorState = null,
|
||||
InputFileHandle = null,
|
||||
InputCabinet = null,
|
||||
};
|
||||
|
||||
System = System,
|
||||
State.System.Read = SysRead;
|
||||
State.System.Write = SysWrite;
|
||||
}
|
||||
|
||||
InputCabinet = file.Folder.Data.Cab,
|
||||
InputFileHandle = System.Open(file.Folder.Data.Cab.Filename, OpenMode.MSPACK_SYS_OPEN_READ),
|
||||
OutputFileHandle = null, // Required for skipping existing data
|
||||
InputPointer = 0,
|
||||
InputEnd = 0,
|
||||
};
|
||||
// Do we need to change folder or reset the current folder?
|
||||
if (State.Folder != file.Folder || State.Offset > file.Header.FolderOffset || State.DecompressorState == null)
|
||||
{
|
||||
// Do we need to open a new cab file?
|
||||
if (State.InputFileHandle == null || file.Folder.Data.Cab != State.InputCabinet)
|
||||
{
|
||||
// Close previous file handle if from a different cab
|
||||
System.Close(State?.InputFileHandle);
|
||||
|
||||
State.System.Read = SysRead;
|
||||
State.System.Write = SysWrite;
|
||||
State.InputCabinet = file.Folder.Data.Cab;
|
||||
State.InputFileHandle = System.Open(file.Folder.Data.Cab.Filename, OpenMode.MSPACK_SYS_OPEN_READ);
|
||||
if (State.InputFileHandle == null)
|
||||
return Error = Error.MSPACK_ERR_OPEN;
|
||||
}
|
||||
|
||||
if (State.InputFileHandle == null)
|
||||
return Error = Error.MSPACK_ERR_OPEN;
|
||||
// Seek to start of data blocks
|
||||
// TODO: For LZX, they seem to start 8 bytes after the offset for some reason?
|
||||
if (!System.Seek(State.InputFileHandle, file.Folder.Data.Offset, SeekMode.MSPACK_SYS_SEEK_START))
|
||||
return Error = Error.MSPACK_ERR_SEEK;
|
||||
|
||||
// Seek to start of data blocks
|
||||
if (!System.Seek(State.InputFileHandle, file.Folder.Data.Offset, SeekMode.MSPACK_SYS_SEEK_START))
|
||||
return Error = Error.MSPACK_ERR_SEEK;
|
||||
// Set up decompressor
|
||||
if (InitDecompressionState(file.Folder.Header.CompType) != Error.MSPACK_ERR_OK)
|
||||
return Error;
|
||||
|
||||
// Set up decompressor
|
||||
if (InitDecompressionState(file.Folder.Header.CompType) != Error.MSPACK_ERR_OK)
|
||||
return Error;
|
||||
// Initialize new folder state
|
||||
State.Folder = file.Folder;
|
||||
State.Data = file.Folder.Data;
|
||||
State.Offset = 0;
|
||||
State.Block = 0;
|
||||
State.Outlen = 0;
|
||||
State.InputPointer = 0; // State.Input[0]
|
||||
State.InputEnd = 0; // State.Input[0]
|
||||
|
||||
// ReadError lasts for the lifetime of a decompressor
|
||||
ReadError = Error.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
// ReadError lasts for the lifetime of a decompressor
|
||||
ReadError = Error.MSPACK_ERR_OK;
|
||||
Error = Error.MSPACK_ERR_OK;
|
||||
|
||||
// If file has more than 0 bytes
|
||||
if (filelen != 0)
|
||||
{
|
||||
// Set the output file handle to null
|
||||
State.OutputFileHandle = null;
|
||||
UpdateDecompressionState();
|
||||
|
||||
// Get to correct offset.
|
||||
// - Use null fh to say 'no writing' to cabd_sys_write()
|
||||
// - If SysRead() has an error, it will set self.ReadError
|
||||
@@ -391,6 +414,7 @@ namespace LibMSPackSharp.CAB
|
||||
|
||||
// Close output file
|
||||
System.Close(State.OutputFileHandle);
|
||||
State.OutputFileHandle = null;
|
||||
|
||||
return Error;
|
||||
}
|
||||
|
||||
1
BurnOutSharp/External/libmspack/CAB/Enums.cs
vendored
1
BurnOutSharp/External/libmspack/CAB/Enums.cs
vendored
@@ -109,5 +109,4 @@ namespace LibMSPackSharp.CAB
|
||||
/// </summary>
|
||||
MSCABD_PARAM_SALVAGE = 3,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using static LibMSPackSharp.Compression.Constants;
|
||||
|
||||
namespace LibMSPackSharp.Compression
|
||||
@@ -168,7 +169,7 @@ namespace LibMSPackSharp.Compression
|
||||
/// </summary>
|
||||
public void INJECT_BITS_MSB(int bitdata, int nbits)
|
||||
{
|
||||
BitBuffer |= (uint)(bitdata << (BITBUF_WIDTH - nbits - BitsLeft));
|
||||
BitBuffer |= ((uint)bitdata << (BITBUF_WIDTH - nbits - BitsLeft));
|
||||
BitsLeft += nbits;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,11 +77,11 @@ namespace LibMSPackSharp.Compression
|
||||
public static bool MakeDecodeTableMSB(int nsyms, int nbits, byte[] length, ushort[] table)
|
||||
{
|
||||
ushort sym, next_symbol;
|
||||
uint leaf, fill;
|
||||
long leaf, fill;
|
||||
byte bit_num;
|
||||
uint pos = 0; // The current position in the decode table
|
||||
uint table_mask = (uint)1 << nbits;
|
||||
uint bit_mask = table_mask >> 1; // Don't do 0 length codes
|
||||
long pos = 0; // The current position in the decode table
|
||||
long table_mask = 1 << nbits;
|
||||
long bit_mask = table_mask >> 1; // Don't do 0 length codes
|
||||
|
||||
// Fill entries for codes short enough for a direct mapping
|
||||
for (bit_num = 1; bit_num <= nbits; bit_num++)
|
||||
@@ -116,7 +116,7 @@ namespace LibMSPackSharp.Compression
|
||||
}
|
||||
|
||||
// next_symbol = base of allocation for long codes
|
||||
next_symbol = ((table_mask >> 1) < nsyms) ? (ushort)nsyms : (ushort)(table_mask >> 1);
|
||||
next_symbol = (ushort)(((table_mask >> 1) < nsyms) ? nsyms : (table_mask >> 1));
|
||||
|
||||
// Give ourselves room for codes to grow by up to 16 more bits.
|
||||
// codes now start at bit nbits+16 and end at (nbits+16-codelength)
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using static LibMSPackSharp.Compression.Constants;
|
||||
|
||||
namespace LibMSPackSharp.Compression
|
||||
@@ -189,7 +190,6 @@ namespace LibMSPackSharp.Compression
|
||||
|
||||
OutputPointer = 0,
|
||||
OutputEnd = 0,
|
||||
OutputIsE8 = true,
|
||||
};
|
||||
|
||||
lzx.ResetState();
|
||||
@@ -282,13 +282,530 @@ namespace LibMSPackSharp.Compression
|
||||
/// should be considered unusable and lzxd_decompress() should not be
|
||||
/// called again on this stream.
|
||||
/// </summary>
|
||||
/// <param name="o">LZX decompression state, as allocated by lzxd_init().</param>
|
||||
/// <param name="out_bytes">the number of bytes of data to decompress.</param>
|
||||
/// <returns>an error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public Error Decompress(long out_bytes)
|
||||
{
|
||||
int warned = 0;
|
||||
int match_length, length_footer, extra, verbatim_bits, bytes_todo;
|
||||
int this_run, main_element, aligned_bits, j, warned = 0;
|
||||
byte[] buf = new byte[12];
|
||||
int runsrc, rundest;
|
||||
int frame_size = 0, end_frame, match_offset;
|
||||
|
||||
// Easy answers
|
||||
if (out_bytes < 0)
|
||||
return Error.MSPACK_ERR_ARGS;
|
||||
if (Error != Error.MSPACK_ERR_OK)
|
||||
return Error;
|
||||
|
||||
// Flush out any stored-up bytes before we begin
|
||||
int i = OutputEnd - OutputPointer;
|
||||
if (i > out_bytes)
|
||||
i = (int)out_bytes;
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
if (System.Write(OutputFileHandle, IntelStarted ? E8Buffer : Window, OutputPointer, i) != i)
|
||||
return Error = Error.MSPACK_ERR_WRITE;
|
||||
|
||||
OutputPointer += i;
|
||||
Offset += i;
|
||||
out_bytes -= i;
|
||||
}
|
||||
|
||||
if (out_bytes == 0)
|
||||
return Error.MSPACK_ERR_OK;
|
||||
|
||||
end_frame = (int)((uint)((Offset + out_bytes) / LZX_FRAME_SIZE) + 1);
|
||||
|
||||
while (Frame < end_frame)
|
||||
{
|
||||
// Have we reached the reset interval? (if there is one?)
|
||||
if (ResetInterval != 0 && ((Frame & ResetInterval) == 0))
|
||||
{
|
||||
if (BlockRemaining != 0)
|
||||
{
|
||||
// This is a file format error, we can make a best effort to extract what we can
|
||||
Console.WriteLine($"{BlockRemaining} bytes remaining at reset interval");
|
||||
if (warned == 0)
|
||||
{
|
||||
System.Message(null, "WARNING; invalid reset interval detected during LZX decompression");
|
||||
warned++;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-read the intel header and reset the huffman lengths
|
||||
ResetState();
|
||||
}
|
||||
|
||||
// LZX DELTA format has chunk_size, not present in LZX format
|
||||
if (IsDelta)
|
||||
{
|
||||
ENSURE_BITS(16);
|
||||
REMOVE_BITS_MSB(16);
|
||||
}
|
||||
|
||||
// Read header if necessary
|
||||
if (HeaderRead == 0)
|
||||
{
|
||||
// Read 1 bit. If bit=0, intel filesize = 0.
|
||||
// If bit=1, read intel filesize (32 bits)
|
||||
j = 0;
|
||||
i = (int)READ_BITS_MSB(1);
|
||||
if (i != 0)
|
||||
{
|
||||
i = (int)READ_BITS_MSB(16);
|
||||
j = (int)READ_BITS_MSB(16);
|
||||
}
|
||||
|
||||
IntelFileSize = (i << 16) | j;
|
||||
HeaderRead = 1;
|
||||
}
|
||||
|
||||
// Calculate size of frame: all frames are 32k except the final frame
|
||||
// which is 32kb or less. this can only be calculated when lzx->length
|
||||
// has been filled in.
|
||||
frame_size = LZX_FRAME_SIZE;
|
||||
if (Length != 0 && (Length - Offset) < frame_size)
|
||||
frame_size = (int)(Length - Offset);
|
||||
|
||||
// Decode until one more frame is available
|
||||
bytes_todo = (int)(FramePosition + frame_size - WindowPosition);
|
||||
while (bytes_todo > 0)
|
||||
{
|
||||
// Initialise new block, if one is needed
|
||||
if (BlockRemaining == 0)
|
||||
{
|
||||
// Realign if previous block was an odd-sized UNCOMPRESSED block
|
||||
if (BlockType == LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED && (BlockLength & 1) != 0)
|
||||
{
|
||||
READ_IF_NEEDED();
|
||||
InputPointer++;
|
||||
}
|
||||
|
||||
// Read block type (3 bits) and block length (24 bits)
|
||||
BlockType = (LZXBlockType)READ_BITS_MSB(3);
|
||||
i = (int)READ_BITS_MSB(16);
|
||||
j = (int)READ_BITS_MSB(8);
|
||||
BlockRemaining = BlockLength = (i << 8) | j;
|
||||
Console.WriteLine($"new block t{BlockType} len {BlockLength}");
|
||||
|
||||
// Read individual block headers
|
||||
switch (BlockType)
|
||||
{
|
||||
case LZXBlockType.LZX_BLOCKTYPE_ALIGNED:
|
||||
// Read lengths of and build aligned huffman decoding tree
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
j = (int)READ_BITS_MSB(3);
|
||||
ALIGNED_len[i] = (byte)j;
|
||||
}
|
||||
|
||||
// Rest of aligned header is same as verbatim
|
||||
|
||||
// Read lengths of and build main huffman decoding tree
|
||||
READ_LENGTHS(MAINTREE_len, 0, 256);
|
||||
READ_LENGTHS(MAINTREE_len, 256, LZX_NUM_CHARS + NumOffsets);
|
||||
BUILD_TABLE(MAINTREE_table, MAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS);
|
||||
|
||||
// If the literal 0xE8 is anywhere in the block...
|
||||
if (MAINTREE_len[0xE8] != 0)
|
||||
IntelStarted = true;
|
||||
|
||||
// Read lengths of and build lengths huffman decoding tree
|
||||
READ_LENGTHS(LENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS);
|
||||
BUILD_TABLE_MAYBE_EMPTY();
|
||||
break;
|
||||
|
||||
case LZXBlockType.LZX_BLOCKTYPE_VERBATIM:
|
||||
// Read lengths of and build main huffman decoding tree
|
||||
READ_LENGTHS(MAINTREE_len, 0, 256);
|
||||
READ_LENGTHS(MAINTREE_len, 256, LZX_NUM_CHARS + NumOffsets);
|
||||
BUILD_TABLE(MAINTREE_table, MAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS);
|
||||
|
||||
// If the literal 0xE8 is anywhere in the block...
|
||||
if (MAINTREE_len[0xE8] != 0)
|
||||
IntelStarted = true;
|
||||
|
||||
// Read lengths of and build lengths huffman decoding tree
|
||||
READ_LENGTHS(LENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS);
|
||||
BUILD_TABLE_MAYBE_EMPTY();
|
||||
break;
|
||||
|
||||
case LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED:
|
||||
// Because we can't assume otherwise
|
||||
IntelStarted = true;
|
||||
|
||||
// Read 1-16 (not 0-15) bits to align to bytes
|
||||
if (BitsLeft == 0)
|
||||
ENSURE_BITS(16);
|
||||
|
||||
BitsLeft = 0;
|
||||
BitBuffer = 0;
|
||||
|
||||
// Read 12 bytes of stored R0 / R1 / R2 values
|
||||
for (rundest = 0, i = 0; i < 12; i++)
|
||||
{
|
||||
READ_IF_NEEDED();
|
||||
buf[rundest++] = InputBuffer[InputPointer++];
|
||||
}
|
||||
|
||||
R[0] = (uint)(buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24));
|
||||
R[1] = (uint)(buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24));
|
||||
R[2] = (uint)(buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24));
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"Bad block type {BlockType}");
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode more of the block:
|
||||
// run = min(what's available, what's needed)
|
||||
this_run = BlockRemaining;
|
||||
if (this_run > bytes_todo)
|
||||
this_run = bytes_todo;
|
||||
|
||||
// Assume we decode exactly this_run bytes, for now
|
||||
bytes_todo -= this_run;
|
||||
BlockRemaining -= this_run;
|
||||
|
||||
// Decode at least this_run bytes
|
||||
switch (BlockType)
|
||||
{
|
||||
case LZXBlockType.LZX_BLOCKTYPE_ALIGNED:
|
||||
case LZXBlockType.LZX_BLOCKTYPE_VERBATIM:
|
||||
while (this_run > 0)
|
||||
{
|
||||
main_element = (int)READ_HUFFSYM_MSB(MAINTREE_table, MAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS);
|
||||
if (main_element < LZX_NUM_CHARS)
|
||||
{
|
||||
// Literal: 0 to LZX_NUM_CHARS-1
|
||||
Window[WindowPosition++] = (byte)main_element;
|
||||
this_run--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Match: LZX_NUM_CHARS ((slot<<3) | length_header (3 bits))
|
||||
main_element -= LZX_NUM_CHARS;
|
||||
|
||||
// Get match length
|
||||
match_length = main_element & LZX_NUM_PRIMARY_LENGTHS;
|
||||
if (match_length == LZX_NUM_PRIMARY_LENGTHS)
|
||||
{
|
||||
if (LENGTH_empty != 0)
|
||||
{
|
||||
Console.WriteLine("LENGTH symbol needed but tree is empty");
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH;
|
||||
}
|
||||
|
||||
length_footer = (int)READ_HUFFSYM_MSB(LENGTH_table, LENGTH_len, LZX_LENGTH_TABLEBITS, LZX_LENGTH_MAXSYMBOLS);
|
||||
match_length += length_footer;
|
||||
}
|
||||
|
||||
match_length += LZX_MIN_MATCH;
|
||||
|
||||
// Get match offset
|
||||
switch ((match_offset = (main_element >> 3)))
|
||||
{
|
||||
case 0:
|
||||
match_offset = (int)R[0];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
match_offset = (int)R[1];
|
||||
R[1] = R[0];
|
||||
R[0] = (uint)match_offset;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
match_offset = (int)R[2];
|
||||
R[2] = R[0];
|
||||
R[0] = (uint)match_offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (BlockType == LZXBlockType.LZX_BLOCKTYPE_VERBATIM)
|
||||
{
|
||||
if (match_offset == 3)
|
||||
{
|
||||
match_offset = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
extra = (match_offset >= 36) ? 17 : LZXExtraBits[match_offset];
|
||||
verbatim_bits = (int)READ_BITS_MSB(extra);
|
||||
match_offset = (int)(LZXPositionBase[match_offset] - 2 + verbatim_bits);
|
||||
}
|
||||
}
|
||||
else // LZX_BLOCKTYPE_ALIGNED
|
||||
{
|
||||
extra = (match_offset >= 36) ? 17 : LZXExtraBits[match_offset];
|
||||
match_offset = (int)(LZXPositionBase[match_offset] - 2);
|
||||
if (extra > 3) // >3: verbatim and aligned bits
|
||||
{
|
||||
extra -= 3;
|
||||
verbatim_bits = (int)READ_BITS_MSB(extra);
|
||||
match_offset += (verbatim_bits << 3);
|
||||
aligned_bits = (int)READ_HUFFSYM_MSB(ALIGNED_table, ALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS);
|
||||
match_offset += aligned_bits;
|
||||
}
|
||||
else if (extra == 3) // 3: aligned bits only
|
||||
{
|
||||
aligned_bits = (int)READ_HUFFSYM_MSB(ALIGNED_table, ALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS);
|
||||
match_offset += aligned_bits;
|
||||
}
|
||||
else if (extra > 0) // 1-2: verbatim bits only
|
||||
{
|
||||
verbatim_bits = (int)READ_BITS_MSB(extra);
|
||||
match_offset += verbatim_bits;
|
||||
}
|
||||
else // 0: not defined in LZX specification!
|
||||
{
|
||||
match_offset = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update repeated offset LRU queue
|
||||
R[2] = R[1];
|
||||
R[1] = R[0];
|
||||
R[0] = (uint)match_offset;
|
||||
break;
|
||||
}
|
||||
|
||||
// LZX DELTA uses max match length to signal even longer match
|
||||
if (match_length == LZX_MAX_MATCH && IsDelta)
|
||||
{
|
||||
int extra_len = 0;
|
||||
ENSURE_BITS(3); // 4 entry huffman tree
|
||||
if (PEEK_BITS_MSB(1) == 0)
|
||||
{
|
||||
REMOVE_BITS_MSB(1); // '0' -> 8 extra length bits
|
||||
extra_len = (int)READ_BITS_MSB(8);
|
||||
}
|
||||
else if (PEEK_BITS_MSB(2) == 2)
|
||||
{
|
||||
REMOVE_BITS_MSB(2); // '10' -> 10 extra length bits + 0x100
|
||||
extra_len = (int)READ_BITS_MSB(10);
|
||||
extra_len += 0x100;
|
||||
}
|
||||
else if (PEEK_BITS_MSB(3) == 6)
|
||||
{
|
||||
REMOVE_BITS_MSB(3); // '110' -> 12 extra length bits + 0x500
|
||||
extra_len = (int)READ_BITS_MSB(12);
|
||||
extra_len += 0x500;
|
||||
}
|
||||
else
|
||||
{
|
||||
REMOVE_BITS_MSB(3); // '111' -> 15 extra length bits
|
||||
extra_len = (int)READ_BITS_MSB(15);
|
||||
}
|
||||
|
||||
match_length += extra_len;
|
||||
}
|
||||
|
||||
if ((WindowPosition + match_length) > WindowSize)
|
||||
{
|
||||
Console.WriteLine("Match ran over window wrap");
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH;
|
||||
}
|
||||
|
||||
// Copy match
|
||||
rundest = WindowPosition;
|
||||
i = match_length;
|
||||
|
||||
// Does match offset wrap the window?
|
||||
if (match_offset > WindowPosition)
|
||||
{
|
||||
if (match_offset > Offset && (match_offset - WindowPosition) > ReferenceDataSize)
|
||||
{
|
||||
Console.WriteLine("Match offset beyond LZX stream");
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH;
|
||||
}
|
||||
|
||||
// j = length from match offset to end of window
|
||||
j = match_offset - WindowPosition;
|
||||
if (j > (int)WindowSize)
|
||||
{
|
||||
Console.WriteLine("Match offset beyond window boundaries");
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH;
|
||||
}
|
||||
|
||||
runsrc = (int)(WindowSize - j);
|
||||
if (j < i)
|
||||
{
|
||||
// If match goes over the window edge, do two copy runs
|
||||
i -= j;
|
||||
while (j-- > 0)
|
||||
{
|
||||
Window[rundest++] = Window[runsrc++];
|
||||
}
|
||||
|
||||
runsrc = 0;
|
||||
}
|
||||
|
||||
while (i-- > 0)
|
||||
{
|
||||
Window[rundest++] = Window[runsrc++];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
runsrc = rundest - match_offset;
|
||||
while (i-- > 0)
|
||||
{
|
||||
Window[rundest++] = Window[runsrc++];
|
||||
}
|
||||
}
|
||||
|
||||
this_run -= match_length;
|
||||
WindowPosition += match_length;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED:
|
||||
// As this_run is limited not to wrap a frame, this also means it
|
||||
// won't wrap the window (as the window is a multiple of 32k)
|
||||
rundest = WindowPosition;
|
||||
WindowPosition += this_run;
|
||||
while (this_run > 0)
|
||||
{
|
||||
if ((i = InputEnd - InputPointer) == 0)
|
||||
{
|
||||
READ_IF_NEEDED();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i > this_run)
|
||||
i = this_run;
|
||||
|
||||
Array.Copy(InputBuffer, InputPointer, Window, rundest, i);
|
||||
rundest += 1;
|
||||
InputPointer += i;
|
||||
this_run -= i;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH; // Might as well
|
||||
}
|
||||
|
||||
// Did the final match overrun our desired this_run length?
|
||||
if (this_run < 0)
|
||||
{
|
||||
if ((uint)(-this_run) > BlockRemaining)
|
||||
{
|
||||
Console.Write($"Overrun went past end of block by {-this_run} ({BlockRemaining} remaining)");
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH;
|
||||
}
|
||||
|
||||
BlockRemaining -= -this_run;
|
||||
}
|
||||
}
|
||||
|
||||
// Streams don't extend over frame boundaries
|
||||
if ((WindowPosition - FramePosition) != frame_size)
|
||||
{
|
||||
Console.WriteLine($"Decode beyond output frame limits {WindowPosition - FramePosition} != {frame_size}");
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH;
|
||||
}
|
||||
|
||||
// Re-align input bitstream
|
||||
if (BitsLeft > 0)
|
||||
ENSURE_BITS(16);
|
||||
if ((BitsLeft & 15) != 0)
|
||||
REMOVE_BITS_MSB(BitsLeft & 15);
|
||||
|
||||
// Check that we've used all of the previous frame first
|
||||
if (OutputPointer != OutputEnd)
|
||||
{
|
||||
Console.Write($"{OutputEnd - OutputPointer} avail bytes, new {frame_size} frame");
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH;
|
||||
}
|
||||
|
||||
// Does this intel block _really_ need decoding?
|
||||
if (IntelStarted && IntelFileSize != 0 && Frame < 32768 && frame_size > 10)
|
||||
{
|
||||
int data = 0;
|
||||
int dataend = frame_size - 10;
|
||||
int curpos = (int)Offset;
|
||||
int filesize = IntelFileSize;
|
||||
int abs_off, rel_off;
|
||||
|
||||
// Copy E8 block to the e8 buffer and tweak if needed
|
||||
OutputPointer = 0;
|
||||
Array.Copy(Window, FramePosition, E8Buffer, data, frame_size);
|
||||
|
||||
while (data < dataend)
|
||||
{
|
||||
if (E8Buffer[data++] != 0xE8)
|
||||
{
|
||||
curpos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
abs_off = E8Buffer[data + 0] | (E8Buffer[data + 1] << 8) | (E8Buffer[data + 2] << 16) | (E8Buffer[data + 3] << 24);
|
||||
if ((abs_off >= -curpos) && (abs_off < filesize))
|
||||
{
|
||||
rel_off = (abs_off >= 0) ? abs_off - curpos : abs_off + filesize;
|
||||
E8Buffer[data + 0] = (byte)rel_off;
|
||||
E8Buffer[data + 1] = (byte)(rel_off >> 8);
|
||||
E8Buffer[data + 2] = (byte)(rel_off >> 16);
|
||||
E8Buffer[data + 3] = (byte)(rel_off >> 24);
|
||||
}
|
||||
|
||||
data += 4;
|
||||
curpos += 5;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IntelStarted = false;
|
||||
OutputPointer = (int)FramePosition;
|
||||
}
|
||||
|
||||
OutputEnd = frame_size;
|
||||
|
||||
// Write a frame
|
||||
i = (int)(out_bytes < frame_size ? out_bytes : frame_size);
|
||||
if (System.Write(OutputFileHandle, IntelStarted ? E8Buffer : Window, OutputPointer, i) != i)
|
||||
return Error = Error.MSPACK_ERR_WRITE;
|
||||
|
||||
OutputPointer += i;
|
||||
Offset += i;
|
||||
out_bytes -= i;
|
||||
|
||||
// Advance frame start position
|
||||
FramePosition += (uint)frame_size;
|
||||
Frame++;
|
||||
|
||||
// Wrap window / frame position pointers
|
||||
if (WindowPosition == WindowSize)
|
||||
WindowPosition = 0;
|
||||
if (FramePosition == WindowSize)
|
||||
FramePosition = 0;
|
||||
}
|
||||
|
||||
if (out_bytes != 0)
|
||||
{
|
||||
Console.WriteLine($"{out_bytes} bytes left to output");
|
||||
return Error = Error.MSPACK_ERR_DECRUNCH;
|
||||
}
|
||||
|
||||
return Error.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use the cleaned up code for decompression based on wimlib
|
||||
/// </summary>
|
||||
public Error DecompressNew(long out_bytes)
|
||||
{
|
||||
int warned = 0;
|
||||
|
||||
// Easy answers
|
||||
if (out_bytes < 0)
|
||||
@@ -304,7 +821,7 @@ namespace LibMSPackSharp.Compression
|
||||
|
||||
if (leftover_bytes != 0)
|
||||
{
|
||||
try { System.Write(OutputFileHandle, OutputIsE8 ? E8Buffer : Window, OutputPointer, leftover_bytes); }
|
||||
try { System.Write(OutputFileHandle, Window, OutputPointer, leftover_bytes); }
|
||||
catch { return Error = Error.MSPACK_ERR_WRITE; }
|
||||
|
||||
OutputPointer += leftover_bytes;
|
||||
@@ -355,7 +872,7 @@ namespace LibMSPackSharp.Compression
|
||||
int bytes_todo = (int)(FramePosition + frame_size - WindowPosition);
|
||||
while (bytes_todo > 0)
|
||||
{
|
||||
ReadBlockHeader(buf);
|
||||
ReadBlockHeader();
|
||||
if (Error != Error.MSPACK_ERR_OK)
|
||||
return Error;
|
||||
|
||||
@@ -397,9 +914,7 @@ namespace LibMSPackSharp.Compression
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i > this_run)
|
||||
i = this_run;
|
||||
|
||||
i = Math.Min(i, this_run);
|
||||
Array.Copy(InputBuffer, InputPointer, Window, rundest, i);
|
||||
|
||||
rundest += i;
|
||||
@@ -409,7 +924,7 @@ namespace LibMSPackSharp.Compression
|
||||
}
|
||||
|
||||
// Realign if this was an odd-sized UNCOMPRESSED block
|
||||
if (InputFileHandle.Position != InputFileHandle.Length - 1 && (BlockLength & 1) != 0)
|
||||
if (InputPointer != InputEnd - 1 && (BlockLength & 1) != 0)
|
||||
{
|
||||
READ_IF_NEEDED();
|
||||
if (Error != Error.MSPACK_ERR_OK)
|
||||
@@ -462,20 +977,14 @@ namespace LibMSPackSharp.Compression
|
||||
|
||||
// Does this intel block _really_ need decoding?
|
||||
if (IntelStarted)
|
||||
{
|
||||
UndoE8Preprocessing(frame_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputIsE8 = false;
|
||||
OutputPointer = (int)FramePosition;
|
||||
}
|
||||
UndoE8Preprocessing((int)FramePosition, out_bytes);
|
||||
|
||||
OutputPointer = (int)FramePosition;
|
||||
OutputEnd = (int)(OutputPointer + frame_size);
|
||||
|
||||
// Write a frame
|
||||
int new_out_bytes = (int)((out_bytes < frame_size) ? out_bytes : frame_size);
|
||||
try { System.Write(OutputFileHandle, OutputIsE8 ? E8Buffer : Window, OutputPointer, new_out_bytes); }
|
||||
try { System.Write(OutputFileHandle, Window, OutputPointer, new_out_bytes); }
|
||||
catch { return Error = Error.MSPACK_ERR_WRITE; }
|
||||
|
||||
OutputPointer += new_out_bytes;
|
||||
@@ -537,9 +1046,19 @@ namespace LibMSPackSharp.Compression
|
||||
|
||||
private Error READ_LENGTHS(byte[] lengths, uint first, uint last)
|
||||
{
|
||||
int i_ptr = InputPointer;
|
||||
int i_end = InputEnd;
|
||||
uint bit_buffer = BitBuffer;
|
||||
int bits_left = BitsLeft;
|
||||
|
||||
if (ReadLens(lengths, first, last) != Error.MSPACK_ERR_OK)
|
||||
return Error;
|
||||
|
||||
InputPointer = i_ptr;
|
||||
InputEnd = i_end;
|
||||
BitBuffer = bit_buffer;
|
||||
BitsLeft = bits_left;
|
||||
|
||||
return Error = Error.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
@@ -593,7 +1112,7 @@ namespace LibMSPackSharp.Compression
|
||||
main_element -= LZX_NUM_CHARS;
|
||||
|
||||
// The length header consists of the lower 3 bits of the main element.
|
||||
// The position slot is the rest of it.
|
||||
// The position slot is the rest of it.
|
||||
int length_header = main_element & LZX_NUM_PRIMARY_LENGTHS;
|
||||
int position_slot = main_element >> 3;
|
||||
|
||||
@@ -747,7 +1266,7 @@ namespace LibMSPackSharp.Compression
|
||||
return Error = Error.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
private Error ReadBlockHeader(byte[] buffer)
|
||||
private Error ReadBlockHeader()
|
||||
{
|
||||
ENSURE_BITS(4);
|
||||
|
||||
@@ -763,20 +1282,20 @@ namespace LibMSPackSharp.Compression
|
||||
}
|
||||
else
|
||||
{
|
||||
int tmp;
|
||||
uint tmp;
|
||||
block_size = 0;
|
||||
|
||||
tmp = (int)READ_BITS_MSB(8);
|
||||
block_size |= tmp;
|
||||
tmp = (int)READ_BITS_MSB(8);
|
||||
tmp = (uint)READ_BITS_MSB(8);
|
||||
block_size |= (int)tmp;
|
||||
tmp = (uint)READ_BITS_MSB(8);
|
||||
block_size <<= 8;
|
||||
block_size |= tmp;
|
||||
block_size |= (int)tmp;
|
||||
|
||||
if (WindowSize >= 65536)
|
||||
{
|
||||
tmp = (int)READ_BITS_MSB(8);
|
||||
tmp = (uint)READ_BITS_MSB(8);
|
||||
block_size <<= 8;
|
||||
block_size |= tmp;
|
||||
block_size |= (int)tmp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,26 +1370,24 @@ namespace LibMSPackSharp.Compression
|
||||
break;
|
||||
|
||||
case LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED:
|
||||
// Read 1-16 (not 0-15) bits to align to bytes
|
||||
if (BitsLeft == 0)
|
||||
ENSURE_BITS(16);
|
||||
|
||||
BitsLeft = 0; BitBuffer = 0;
|
||||
|
||||
// Read 12 bytes of stored R[0] / R[1] / R[2] values
|
||||
for (int rundest = 0, k = 0; k < 12; k++)
|
||||
{
|
||||
READ_IF_NEEDED();
|
||||
if (Error != Error.MSPACK_ERR_OK)
|
||||
return Error;
|
||||
|
||||
buffer[rundest++] = InputBuffer[InputPointer++];
|
||||
ENSURE_BITS(16);
|
||||
BlockRemaining -= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
BitsLeft = 0;
|
||||
BitBuffer = 0;
|
||||
}
|
||||
|
||||
// TODO: uint[] R should be a part of a state object
|
||||
R[0] = (uint)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24));
|
||||
R[1] = (uint)(buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24));
|
||||
R[2] = (uint)(buffer[8] | (buffer[9] << 8) | (buffer[10] << 16) | (buffer[11] << 24));
|
||||
R[0] = BitConverter.ToUInt32(new Span<byte>(InputBuffer, InputPointer + 0, 4).ToArray().Reverse().ToArray(), 0);
|
||||
R[1] = BitConverter.ToUInt32(new Span<byte>(InputBuffer, InputPointer + 4, 4).ToArray().Reverse().ToArray(), 0);
|
||||
R[2] = BitConverter.ToUInt32(new Span<byte>(InputBuffer, InputPointer + 8, 4).ToArray().Reverse().ToArray(), 0);
|
||||
|
||||
InputPointer += 12;
|
||||
BlockRemaining -= 12;
|
||||
|
||||
break;
|
||||
|
||||
@@ -884,69 +1401,71 @@ namespace LibMSPackSharp.Compression
|
||||
|
||||
private Error ReadLens(byte[] lens, uint first, uint last)
|
||||
{
|
||||
uint x, y;
|
||||
int z;
|
||||
|
||||
// Read lengths for pretree (20 symbols, lengths stored in fixed 4 bits)
|
||||
for (int i = 0; i < LZX_PRETREE_MAXSYMBOLS; i++)
|
||||
for (x = 0; x < LZX_PRETREE_MAXSYMBOLS; x++)
|
||||
{
|
||||
uint y = (uint)READ_BITS_MSB(4);
|
||||
PRETREE_len[i] = (byte)y;
|
||||
y = (uint)READ_BITS_MSB(4);
|
||||
PRETREE_len[x] = (byte)y;
|
||||
}
|
||||
|
||||
BUILD_TABLE(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
|
||||
if (Error != Error.MSPACK_ERR_OK)
|
||||
return Error;
|
||||
|
||||
for (uint lensPtr = first; lensPtr < last;)
|
||||
for (x = first; x < last;)
|
||||
{
|
||||
uint num_zeroes, num_same;
|
||||
int tree_code = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
|
||||
switch (tree_code)
|
||||
z = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
|
||||
switch (z)
|
||||
{
|
||||
// Code = 17, run of ([read 4 bits]+4) zeros
|
||||
case 17:
|
||||
num_zeroes = (uint)READ_BITS_MSB(4);
|
||||
num_zeroes += 4;
|
||||
while (num_zeroes-- != 0)
|
||||
y = (uint)READ_BITS_MSB(4);
|
||||
y += 4;
|
||||
while (y-- != 0)
|
||||
{
|
||||
lens[lensPtr++] = 0;
|
||||
lens[x++] = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Code = 18, run of ([read 5 bits]+20) zeros
|
||||
case 18:
|
||||
num_zeroes = (uint)READ_BITS_MSB(5);
|
||||
num_zeroes += 20;
|
||||
while (num_zeroes-- != 0)
|
||||
y = (uint)READ_BITS_MSB(5);
|
||||
y += 20;
|
||||
while (y-- != 0)
|
||||
{
|
||||
lens[lensPtr++] = 0;
|
||||
lens[x++] = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Code = 19, run of ([read 1 bit]+4) [read huffman symbol]
|
||||
case 19:
|
||||
num_same = (uint)READ_BITS_MSB(1);
|
||||
num_same += 4;
|
||||
y = (uint)READ_BITS_MSB(1);
|
||||
y += 4;
|
||||
|
||||
tree_code = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
|
||||
tree_code = lens[lensPtr] - tree_code;
|
||||
if (tree_code < 0)
|
||||
tree_code += 17;
|
||||
z = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
|
||||
z = lens[x] - z;
|
||||
if (z < 0)
|
||||
z += 17;
|
||||
|
||||
while (num_same-- != 0)
|
||||
while (y-- != 0)
|
||||
{
|
||||
lens[lensPtr++] = (byte)tree_code;
|
||||
lens[x++] = (byte)z;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Code = 0 to 16, delta current length entry
|
||||
default:
|
||||
tree_code = lens[lensPtr] - tree_code;
|
||||
if (tree_code < 0)
|
||||
tree_code += 17;
|
||||
z = lens[x] - z;
|
||||
if (z < 0)
|
||||
z += 17;
|
||||
|
||||
lens[lensPtr++] = (byte)tree_code;
|
||||
lens[x++] = (byte)z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -975,81 +1494,50 @@ namespace LibMSPackSharp.Compression
|
||||
}
|
||||
}
|
||||
|
||||
private void UndoE8Preprocessing(uint frame_size)
|
||||
private void UndoE8Preprocessing(int data, long out_bytes)
|
||||
{
|
||||
if (frame_size > 10)
|
||||
int p8 = data;
|
||||
if (out_bytes > 10)
|
||||
{
|
||||
// Finish any bytes that weren't processed by the vectorized implementation.
|
||||
int start = WindowPosition;
|
||||
int p8_end = (int)(WindowPosition + frame_size - 10);
|
||||
int p8_end = (int)(out_bytes - 10);
|
||||
do
|
||||
{
|
||||
if (Window[WindowPosition] == 0xe8)
|
||||
if (Window[p8] == 0xe8)
|
||||
{
|
||||
int target = p8 + 1;
|
||||
int input_pos = p8 - data;
|
||||
|
||||
int abs_offset, rel_offset;
|
||||
|
||||
// XXX: This assumes unaligned memory accesses are okay.
|
||||
abs_offset = BitConverter.ToInt32(Window, WindowPosition + 1);
|
||||
abs_offset = BitConverter.ToInt32(new Span<byte>(Window, target, 4).ToArray().Reverse().ToArray(), 0);
|
||||
if (abs_offset >= 0)
|
||||
{
|
||||
if (abs_offset < 12_000_000)
|
||||
{
|
||||
// "good translation"
|
||||
rel_offset = abs_offset - start;
|
||||
Array.Copy(BitConverter.GetBytes(rel_offset), 0, Window, WindowPosition + 1, 4);
|
||||
rel_offset = abs_offset - input_pos;
|
||||
Array.Copy(BitConverter.GetBytes(rel_offset).Reverse().ToArray(), 0, Window, target, 4);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (abs_offset >= -start)
|
||||
if (abs_offset >= -input_pos)
|
||||
{
|
||||
// "compensating translation"
|
||||
rel_offset = abs_offset + 12_000_000;
|
||||
Array.Copy(BitConverter.GetBytes(rel_offset), 0, Window, WindowPosition + 1, 4);
|
||||
Array.Copy(BitConverter.GetBytes(rel_offset).Reverse().ToArray(), 0, Window, target, 4);
|
||||
}
|
||||
}
|
||||
|
||||
WindowPosition += 5;
|
||||
p8 += 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowPosition++;
|
||||
p8++;
|
||||
}
|
||||
} while (WindowPosition < p8_end);
|
||||
}
|
||||
|
||||
|
||||
int data = 0;
|
||||
int dataend = (int)(frame_size - 10);
|
||||
int curpos = (int)Offset;
|
||||
int filesize = IntelFileSize;
|
||||
int abs_off, rel_off;
|
||||
|
||||
// Copy e8 block to the e8 buffer and tweak if needed
|
||||
OutputIsE8 = true;
|
||||
OutputPointer = data;
|
||||
Array.Copy(Window, FramePosition, E8Buffer, data, frame_size);
|
||||
|
||||
while (data < dataend)
|
||||
{
|
||||
if (E8Buffer[data++] != 0xE8)
|
||||
{
|
||||
curpos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
abs_off = E8Buffer[data + 0] | (E8Buffer[data + 1] << 8) | (E8Buffer[data + 2] << 16) | (E8Buffer[data + 3] << 24);
|
||||
if ((abs_off >= -curpos) && (abs_off < filesize))
|
||||
{
|
||||
rel_off = (abs_off >= 0) ? abs_off - curpos : abs_off + filesize;
|
||||
E8Buffer[data + 0] = (byte)rel_off;
|
||||
E8Buffer[data + 1] = (byte)(rel_off >> 8);
|
||||
E8Buffer[data + 2] = (byte)(rel_off >> 16);
|
||||
E8Buffer[data + 3] = (byte)(rel_off >> 24);
|
||||
}
|
||||
|
||||
data += 4;
|
||||
curpos += 5;
|
||||
} while (p8 < p8_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,12 +126,6 @@ namespace LibMSPackSharp.Compression
|
||||
|
||||
public byte LENGTH_empty { get; set; }
|
||||
|
||||
// This is used purely for doing the intel E8 transform
|
||||
public byte[] E8Buffer { get; set; } = new byte[LZX_FRAME_SIZE];
|
||||
|
||||
/// <summary>
|
||||
/// Is the output pointer referring to E8?
|
||||
/// </summary>
|
||||
public bool OutputIsE8 { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using BurnOutSharp.Interfaces;
|
||||
using BurnOutSharp.Tools;
|
||||
using LibMSPackSharp;
|
||||
@@ -52,6 +53,18 @@ namespace BurnOutSharp.FileType
|
||||
return null;
|
||||
}
|
||||
|
||||
// If there are additional previous CABs, add those
|
||||
while (!string.IsNullOrWhiteSpace(cabFile.NextName))
|
||||
{
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
// If there are additional next CABs, add those
|
||||
while (!string.IsNullOrWhiteSpace(cabFile.NextName))
|
||||
{
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
// Loop through the found internal files
|
||||
var sub = cabFile.Files;
|
||||
while (sub != null)
|
||||
|
||||
Reference in New Issue
Block a user