Archipelago/worlds/cv64/lzkn64.py

267 lines
12 KiB
Python
Raw Normal View History

# **************************************************************
# * LZKN64 Compression and Decompression Utility *
# * Original repo at https://github.com/Fluvian/lzkn64, *
# * converted from C to Python with permission from Fluvian. *
# **************************************************************
TYPE_COMPRESS = 1
TYPE_DECOMPRESS = 2
MODE_NONE = 0x7F
MODE_WINDOW_COPY = 0x00
MODE_RAW_COPY = 0x80
MODE_RLE_WRITE_A = 0xC0
MODE_RLE_WRITE_B = 0xE0
MODE_RLE_WRITE_C = 0xFF
WINDOW_SIZE = 0x3FF
COPY_SIZE = 0x21
RLE_SIZE = 0x101
# Compresses the data in the buffer specified in the arguments.
def compress_buffer(file_buffer: bytearray) -> bytearray:
# Size of the buffer to compress
buffer_size = len(file_buffer) - 1
# Position of the current read location in the buffer.
buffer_position = 0
# Position of the current write location in the written buffer.
write_position = 4
# Allocate write_buffer with size of 0xFFFFFF (24-bit).
write_buffer = bytearray(0xFFFFFF)
# Position in the input buffer of the last time one of the copy modes was used.
buffer_last_copy_position = 0
while buffer_position < buffer_size:
# Calculate maximum length we are able to copy without going out of bounds.
if COPY_SIZE < (buffer_size - 1) - buffer_position:
sliding_window_maximum_length = COPY_SIZE
else:
sliding_window_maximum_length = (buffer_size - 1) - buffer_position
# Calculate how far we are able to look back without going behind the start of the uncompressed buffer.
if buffer_position - WINDOW_SIZE > 0:
sliding_window_maximum_offset = buffer_position - WINDOW_SIZE
else:
sliding_window_maximum_offset = 0
# Calculate maximum length the forwarding looking window is able to search.
if RLE_SIZE < (buffer_size - 1) - buffer_position:
forward_window_maximum_length = RLE_SIZE
else:
forward_window_maximum_length = (buffer_size - 1) - buffer_position
sliding_window_match_position = -1
sliding_window_match_size = 0
forward_window_match_value = 0
forward_window_match_size = 0
# The current mode the compression algorithm prefers. (0x7F == None)
current_mode = MODE_NONE
# The current submode the compression algorithm prefers.
current_submode = MODE_NONE
# How many bytes will have to be copied in the raw copy command.
raw_copy_size = buffer_position - buffer_last_copy_position
# How many bytes we still have to copy in RLE matches with more than 0x21 bytes.
rle_bytes_left = 0
"""Go backwards in the buffer, is there a matching value?
If yes, search forward and check for more matching values in a loop.
If no, go further back and repeat."""
for search_position in range(buffer_position - 1, sliding_window_maximum_offset - 1, -1):
matching_sequence_size = 0
while file_buffer[search_position + matching_sequence_size] == file_buffer[buffer_position +
matching_sequence_size]:
matching_sequence_size += 1
if matching_sequence_size >= sliding_window_maximum_length:
break
# Once we find a match or a match that is bigger than the match before it, we save its position and length.
if matching_sequence_size > sliding_window_match_size:
sliding_window_match_position = search_position
sliding_window_match_size = matching_sequence_size
"""Look one step forward in the buffer, is there a matching value?
If yes, search further and check for a repeating value in a loop.
If no, continue to the rest of the function."""
matching_sequence_value = file_buffer[buffer_position]
matching_sequence_size = 0
while file_buffer[buffer_position + matching_sequence_size] == matching_sequence_value:
matching_sequence_size += 1
if matching_sequence_size >= forward_window_maximum_length:
break
# If we find a sequence of matching values, save them.
if matching_sequence_size >= 1:
forward_window_match_value = matching_sequence_value
forward_window_match_size = matching_sequence_size
# Try to pick which mode works best with the current values.
if sliding_window_match_size >= 3:
current_mode = MODE_WINDOW_COPY
elif forward_window_match_size >= 3:
current_mode = MODE_RLE_WRITE_A
if forward_window_match_value != 0x00 and forward_window_match_size <= COPY_SIZE:
current_submode = MODE_RLE_WRITE_A
elif forward_window_match_value != 0x00 and forward_window_match_size > COPY_SIZE:
current_submode = MODE_RLE_WRITE_A
rle_bytes_left = forward_window_match_size
elif forward_window_match_value == 0x00 and forward_window_match_size <= COPY_SIZE:
current_submode = MODE_RLE_WRITE_B
elif forward_window_match_value == 0x00 and forward_window_match_size > COPY_SIZE:
current_submode = MODE_RLE_WRITE_C
elif forward_window_match_size >= 2 and forward_window_match_value == 0x00:
current_mode = MODE_RLE_WRITE_A
current_submode = MODE_RLE_WRITE_B
"""Write a raw copy command when these following conditions are met:
The current mode is set and there are raw bytes available to be copied.
The raw byte length exceeds the maximum length that can be stored.
Raw bytes need to be written due to the proximity to the end of the buffer."""
if (current_mode != MODE_NONE and raw_copy_size >= 1) or raw_copy_size >= 0x1F or \
(buffer_position + 1) == buffer_size:
if buffer_position + 1 == buffer_size:
raw_copy_size = buffer_size - buffer_last_copy_position
write_buffer[write_position] = MODE_RAW_COPY | raw_copy_size & 0x1F
write_position += 1
for written_bytes in range(raw_copy_size):
write_buffer[write_position] = file_buffer[buffer_last_copy_position]
write_position += 1
buffer_last_copy_position += 1
if current_mode == MODE_WINDOW_COPY:
write_buffer[write_position] = MODE_WINDOW_COPY | ((sliding_window_match_size - 2) & 0x1F) << 2 | \
(((buffer_position - sliding_window_match_position) & 0x300) >> 8)
write_position += 1
write_buffer[write_position] = (buffer_position - sliding_window_match_position) & 0xFF
write_position += 1
buffer_position += sliding_window_match_size
buffer_last_copy_position = buffer_position
elif current_mode == MODE_RLE_WRITE_A:
if current_submode == MODE_RLE_WRITE_A:
if rle_bytes_left > 0:
while rle_bytes_left > 0:
# Dump raw bytes if we have less than two bytes left, not doing so would cause an underflow
# error.
if rle_bytes_left < 2:
write_buffer[write_position] = MODE_RAW_COPY | rle_bytes_left & 0x1F
write_position += 1
for writtenBytes in range(rle_bytes_left):
write_buffer[write_position] = forward_window_match_value & 0xFF
write_position += 1
rle_bytes_left = 0
break
if rle_bytes_left < COPY_SIZE:
write_buffer[write_position] = MODE_RLE_WRITE_A | (rle_bytes_left - 2) & 0x1F
write_position += 1
else:
write_buffer[write_position] = MODE_RLE_WRITE_A | (COPY_SIZE - 2) & 0x1F
write_position += 1
write_buffer[write_position] = forward_window_match_value & 0xFF
write_position += 1
rle_bytes_left -= COPY_SIZE
else:
write_buffer[write_position] = MODE_RLE_WRITE_A | (forward_window_match_size - 2) & 0x1F
write_position += 1
write_buffer[write_position] = forward_window_match_value & 0xFF
write_position += 1
elif current_submode == MODE_RLE_WRITE_B:
write_buffer[write_position] = MODE_RLE_WRITE_B | (forward_window_match_size - 2) & 0x1F
write_position += 1
elif current_submode == MODE_RLE_WRITE_C:
write_buffer[write_position] = MODE_RLE_WRITE_C
write_position += 1
write_buffer[write_position] = (forward_window_match_size - 2) & 0xFF
write_position += 1
buffer_position += forward_window_match_size
buffer_last_copy_position = buffer_position
else:
buffer_position += 1
# Write the compressed size.
write_buffer[1] = 0x00
write_buffer[1] = write_position >> 16 & 0xFF
write_buffer[2] = write_position >> 8 & 0xFF
write_buffer[3] = write_position & 0xFF
# Return the compressed write buffer.
return write_buffer[0:write_position]
# Decompresses the data in the buffer specified in the arguments.
def decompress_buffer(file_buffer: bytearray) -> bytearray:
# Position of the current read location in the buffer.
buffer_position = 4
# Position of the current write location in the written buffer.
write_position = 0
# Get compressed size.
compressed_size = (file_buffer[1] << 16) + (file_buffer[2] << 8) + file_buffer[3] - 1
# Allocate writeBuffer with size of 0xFFFFFF (24-bit).
write_buffer = bytearray(0xFFFFFF)
while buffer_position < compressed_size:
mode_command = file_buffer[buffer_position]
buffer_position += 1
if MODE_WINDOW_COPY <= mode_command < MODE_RAW_COPY:
copy_length = (mode_command >> 2) + 2
copy_offset = file_buffer[buffer_position] + (mode_command << 8) & 0x3FF
buffer_position += 1
for current_length in range(copy_length, 0, -1):
write_buffer[write_position] = write_buffer[write_position - copy_offset]
write_position += 1
elif MODE_RAW_COPY <= mode_command < MODE_RLE_WRITE_A:
copy_length = mode_command & 0x1F
for current_length in range(copy_length, 0, -1):
write_buffer[write_position] = file_buffer[buffer_position]
write_position += 1
buffer_position += 1
elif MODE_RLE_WRITE_A <= mode_command <= MODE_RLE_WRITE_C:
write_length = 0
write_value = 0x00
if MODE_RLE_WRITE_A <= mode_command < MODE_RLE_WRITE_B:
write_length = (mode_command & 0x1F) + 2
write_value = file_buffer[buffer_position]
buffer_position += 1
elif MODE_RLE_WRITE_B <= mode_command < MODE_RLE_WRITE_C:
write_length = (mode_command & 0x1F) + 2
elif mode_command == MODE_RLE_WRITE_C:
write_length = file_buffer[buffer_position] + 2
buffer_position += 1
for current_length in range(write_length, 0, -1):
write_buffer[write_position] = write_value
write_position += 1
# Return the current position of the write buffer, essentially giving us the size of the write buffer.
while write_position % 16 != 0:
write_position += 1
return write_buffer[0:write_position]