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