267 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			267 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# **************************************************************
 | 
						|
# * 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]
 |