private static void Encode(Stream input, Stream output, bool with_size) { int input_size = (int)(input.Length - input.Position); byte[] input_buffer = new byte[input_size]; input.Read(input_buffer, 0, input_size); long outputInitialPosition = output.Position; if (with_size) { output.Seek(2, SeekOrigin.Current); } /* * Here we create and populate the "LZSS graph": * * Each value in the uncompressed file forms a node in this graph. * The various edges between these nodes represent LZSS matches. * * Using a shortest-path algorithm, these edges can be used to * find the optimal combination of matches needed to produce the * smallest possible file. * * The outputted array only contains one edge per node: the optimal * one. This means, in order to produce the smallest file, you just * have to traverse the graph from one edge to the next, encoding * each match as you go along. */ LZSSGraphEdge[] node_meta_array = new LZSSGraphEdge[input_size + 1]; // Initialise the array node_meta_array[0].cost = 0; for (int i = 1; i < input_size + 1; ++i) { node_meta_array[i].cost = int.MaxValue; } // Find matches for (int i = 0; i < input_size; ++i) { int max_read_ahead = Math.Min(0xF + 3, input_size - i); int max_read_behind = Math.Max(0, i - 0x1000); // Search for zero-fill matches if (i < 0x1000) { for (int k = 0; k < 0xF + 3; ++k) { if (input_buffer[i + k] == 0) { int length = k + 1; // Update this node's optimal edge if this one is better if (length >= 3 && node_meta_array[i + k + 1].cost > node_meta_array[i].cost + 1 + 16) { node_meta_array[i + k + 1].cost = node_meta_array[i].cost + 1 + 16; node_meta_array[i + k + 1].previous_node_index = i; node_meta_array[i + k + 1].match_length = k + 1; node_meta_array[i + k + 1].match_offset = 0xFFF; } } else { break; } } } // Search for dictionary matches for (int j = i; j-- > max_read_behind;) { for (int k = 0; k < max_read_ahead; ++k) { if (input_buffer[i + k] == input_buffer[j + k]) { int distance = i - j; int length = k + 1; // Update this node's optimal edge if this one is better if (length >= 3 && node_meta_array[i + k + 1].cost > node_meta_array[i].cost + 1 + 16) { node_meta_array[i + k + 1].cost = node_meta_array[i].cost + 1 + 16; node_meta_array[i + k + 1].previous_node_index = i; node_meta_array[i + k + 1].match_length = k + 1; node_meta_array[i + k + 1].match_offset = j; } } else { break; } } } // Do literal match // Update this node's optimal edge if this one is better (or the same, since literal matches usually decode faster) if (node_meta_array[i + 1].cost >= node_meta_array[i].cost + 1 + 8) { node_meta_array[i + 1].cost = node_meta_array[i].cost + 1 + 8; node_meta_array[i + 1].previous_node_index = i; node_meta_array[i + 1].match_length = 0; } } // Reverse the edge link order, so the array can be traversed from start to end, rather than vice versa node_meta_array[0].previous_node_index = int.MaxValue; node_meta_array[input_size].next_node_index = int.MaxValue; for (int node_index = input_size; node_meta_array[node_index].previous_node_index != int.MaxValue; node_index = node_meta_array[node_index].previous_node_index) { node_meta_array[node_meta_array[node_index].previous_node_index].next_node_index = node_index; } /* * LZSS graph complete */ UInt8_NE_L_OutputBitStream bitStream = new UInt8_NE_L_OutputBitStream(output); MemoryStream data = new MemoryStream(); for (int node_index = 0; node_meta_array[node_index].next_node_index != int.MaxValue; node_index = node_meta_array[node_index].next_node_index) { int next_index = node_meta_array[node_index].next_node_index; if (node_meta_array[next_index].match_length != 0) { // Compressed Push(bitStream, false, output, data); int match_offset_adjusted = node_meta_array[next_index].match_offset - 0x12; // I don't think there's any reason for this, the format's just stupid NeutralEndian.Write1(data, (byte)(match_offset_adjusted & 0xFF)); NeutralEndian.Write1(data, (byte)(((match_offset_adjusted & 0xF00) >> 4) | ((node_meta_array[next_index].match_length - 3) & 0x0F))); } else { // Uncompressed Push(bitStream, true, output, data); NeutralEndian.Write1(data, input_buffer[node_index]); } } // Write remaining data (normally we don't flush until we have a full descriptor byte) bitStream.Flush(true); byte[] dataArray = data.ToArray(); output.Write(dataArray, 0, dataArray.Length); if (with_size) { ushort size = (ushort)(outputInitialPosition - output.Position - 2); output.Seek(outputInitialPosition, SeekOrigin.Begin); LittleEndian.Write2(output, size); } }
private static void EncodeInternal(Stream destination, byte[] buffer, int pos, int size) { /* * Here we create and populate the "LZSS graph": * * Each value in the uncompressed file forms a node in this graph. * The various edges between these nodes represent LZSS matches. * * Using a shortest-path algorithm, these edges can be used to * find the optimal combination of matches needed to produce the * smallest possible file. * * The outputted array only contains one edge per node: the optimal * one. This means, in order to produce the smallest file, you just * have to traverse the graph from one edge to the next, encoding * each match as you go along. */ LZSSGraphEdge[] node_meta_array = new LZSSGraphEdge[size + 1]; // Initialise the array node_meta_array[0].cost = 0; for (int i = 1; i < size + 1; ++i) { node_meta_array[i].cost = int.MaxValue; } // Find matches for (int i = 0; i < size; ++i) { int max_read_ahead = Math.Min(0x100 + 8, size - i); int max_read_behind = Math.Max(0, i - 0x2000); // Search for dictionary matches for (int j = i; j-- > max_read_behind;) { for (int k = 0; k < max_read_ahead; ++k) { if (buffer[pos + i + k] == buffer[pos + j + k]) { int distance = i - j; int length = k + 1; // Get the cost of the match (or bail if it can't be compressed) int cost; if (length >= 2 && length <= 5 && distance <= 256) { cost = 2 + 2 + 8; // Descriptor bits, length bits, offset byte } else if (length >= 3 && length <= 9) { cost = 2 + 16; // Descriptor bits, offset/length bytes } else if (length >= 10) { cost = 2 + 16 + 8; // Descriptor bits, offset bytes, length byte } else { continue; // In the event a match cannot be compressed } // Update this node's optimal edge if this one is better if (node_meta_array[i + k + 1].cost > node_meta_array[i].cost + cost) { node_meta_array[i + k + 1].cost = node_meta_array[i].cost + cost; node_meta_array[i + k + 1].previous_node_index = i; node_meta_array[i + k + 1].match_length = k + 1; node_meta_array[i + k + 1].match_offset = j; } } else { break; } } } // Do literal match // Update this node's optimal edge if this one is better (or the same, since literal matches usually decode faster) if (node_meta_array[i + 1].cost >= node_meta_array[i].cost + 1 + 8) { node_meta_array[i + 1].cost = node_meta_array[i].cost + 1 + 8; node_meta_array[i + 1].previous_node_index = i; node_meta_array[i + 1].match_length = 0; } } // Reverse the edge link order, so the array can be traversed from start to end, rather than vice versa node_meta_array[0].previous_node_index = int.MaxValue; node_meta_array[size].next_node_index = int.MaxValue; for (int node_index = size; node_meta_array[node_index].previous_node_index != int.MaxValue; node_index = node_meta_array[node_index].previous_node_index) { node_meta_array[node_meta_array[node_index].previous_node_index].next_node_index = node_index; } /* * LZSS graph complete */ UInt8_NE_H_OutputBitStream bitStream = new UInt8_NE_H_OutputBitStream(destination); MemoryStream data = new MemoryStream(); for (int node_index = 0; node_meta_array[node_index].next_node_index != int.MaxValue; node_index = node_meta_array[node_index].next_node_index) { int next_index = node_meta_array[node_index].next_node_index; int length = node_meta_array[next_index].match_length; int distance = next_index - node_meta_array[next_index].match_length - node_meta_array[next_index].match_offset; if (length != 0) { if (length >= 2 && length <= 5 && distance <= 256) { Push(bitStream, false, destination, data); Push(bitStream, false, destination, data); NeutralEndian.Write1(data, (byte)-distance); Push(bitStream, ((length - 2) & 2) != 0, destination, data); Push(bitStream, ((length - 2) & 1) != 0, destination, data); } else if (length >= 3 && length <= 9) { Push(bitStream, false, destination, data); Push(bitStream, true, destination, data); NeutralEndian.Write1(data, (byte)(((-distance >> (8 - 3)) & 0xF8) | ((10 - length) & 7))); NeutralEndian.Write1(data, (byte)(-distance & 0xFF)); } else //if (length >= 3) { Push(bitStream, false, destination, data); Push(bitStream, true, destination, data); NeutralEndian.Write1(data, (byte)((-distance >> (8 - 3)) & 0xF8)); NeutralEndian.Write1(data, (byte)(-distance & 0xFF)); NeutralEndian.Write1(data, (byte)(length - 9)); } } else { Push(bitStream, true, destination, data); NeutralEndian.Write1(data, buffer[pos + node_index]); } } Push(bitStream, false, destination, data); Push(bitStream, true, destination, data); NeutralEndian.Write1(data, 0xF0); NeutralEndian.Write1(data, 0); NeutralEndian.Write1(data, 0); bitStream.Flush(true); byte[] bytes = data.ToArray(); destination.Write(bytes, 0, bytes.Length); }
internal static void Encode(Stream source, Stream destination) { int size_bytes = (int)(source.Length - source.Position); byte[] buffer_bytes = new byte[size_bytes + (size_bytes & 1)]; source.Read(buffer_bytes, 0, size_bytes); int size = (size_bytes + 1) / 2; ushort[] buffer = new ushort[size]; for (int i = 0; i < size; ++i) { buffer[i] = (ushort)((buffer_bytes[i * 2] << 8) | buffer_bytes[(i * 2) + 1]); } /* * Here we create and populate the "LZSS graph": * * Each value in the uncompressed file forms a node in this graph. * The various edges between these nodes represent LZSS matches. * * Using a shortest-path algorithm, these edges can be used to * find the optimal combination of matches needed to produce the * smallest possible file. * * The outputted array only contains one edge per node: the optimal * one. This means, in order to produce the smallest file, you just * have to traverse the graph from one edge to the next, encoding * each match as you go along. */ LZSSGraphEdge[] node_meta_array = new LZSSGraphEdge[size + 1]; // Initialise the array node_meta_array[0].cost = 0; for (int i = 1; i < size + 1; ++i) { node_meta_array[i].cost = int.MaxValue; } // Find matches for (int i = 0; i < size; ++i) { int max_read_ahead = Math.Min(0x100, size - i); int max_read_behind = Math.Max(0, i - 0x100); // Search for dictionary matches for (int j = i; j-- > max_read_behind;) { for (int k = 0; k < max_read_ahead; ++k) { if (buffer[i + k] == buffer[j + k]) { int distance = i - j; int length = k + 1; // Update this node's optimal edge if this one is better if (node_meta_array[i + k + 1].cost > node_meta_array[i].cost + 1 + 16) { node_meta_array[i + k + 1].cost = node_meta_array[i].cost + 1 + 16; node_meta_array[i + k + 1].previous_node_index = i; node_meta_array[i + k + 1].match_length = k + 1; node_meta_array[i + k + 1].match_offset = j; } } else { break; } } } // Do literal match // Update this node's optimal edge if this one is better (or the same, since literal matches usually decode faster) if (node_meta_array[i + 1].cost >= node_meta_array[i].cost + 1 + 16) { node_meta_array[i + 1].cost = node_meta_array[i].cost + 1 + 16; node_meta_array[i + 1].previous_node_index = i; node_meta_array[i + 1].match_length = 0; } } // Reverse the edge link order, so the array can be traversed from start to end, rather than vice versa node_meta_array[0].previous_node_index = int.MaxValue; node_meta_array[size].next_node_index = int.MaxValue; for (int node_index = size; node_meta_array[node_index].previous_node_index != int.MaxValue; node_index = node_meta_array[node_index].previous_node_index) { node_meta_array[node_meta_array[node_index].previous_node_index].next_node_index = node_index; } /* * LZSS graph complete */ UInt16BE_NE_H_OutputBitStream bitStream = new UInt16BE_NE_H_OutputBitStream(destination); MemoryStream data = new MemoryStream(); for (int node_index = 0; node_meta_array[node_index].next_node_index != int.MaxValue; node_index = node_meta_array[node_index].next_node_index) { int next_index = node_meta_array[node_index].next_node_index; int length = node_meta_array[next_index].match_length; int distance = next_index - node_meta_array[next_index].match_length - node_meta_array[next_index].match_offset; if (length != 0) { // Compressed Push(bitStream, true, destination, data); NeutralEndian.Write1(data, (byte)-distance); NeutralEndian.Write1(data, (byte)(length - 1)); } else { // Uncompressed Push(bitStream, false, destination, data); BigEndian.Write2(data, buffer[node_index]); } } Push(bitStream, true, destination, data); NeutralEndian.Write1(data, 0); NeutralEndian.Write1(data, 0); bitStream.Flush(true); byte[] bytes = data.ToArray(); destination.Write(bytes, 0, bytes.Length); }