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]
 |