看起来不同游戏之间还不一样,这个用了双缓冲区
import texture2ddecoder
import lz4.block
import struct
from PIL import Image
class LZ4StreamDecoder:
def __init__(self):
self.dictionary = b""
self.buffer_size = 0x4000
def decompress_continue(self, compressed_data, compressed_size):
if compressed_size == 0:
return b"", True
decompressed = lz4.block.decompress(
compressed_data,
uncompressed_size=self.buffer_size,
dict=self.dictionary
)
self.dictionary = decompressed
return decompressed, True
def block_decompress(stream_decoder, data_ptr, compressed_size_ptr):
if len(data_ptr) < 4:
return b"", 4, False
compressed_size = struct.unpack('<I', data_ptr[:4])[0]
bytes_consumed = 4
if compressed_size == 0:
return b"", bytes_consumed, True
if len(data_ptr) < 4 + compressed_size:
return b"", bytes_consumed, False
compressed_block = data_ptr[4:4 + compressed_size]
bytes_consumed += compressed_size
decompressed_data, success = stream_decoder.decompress_continue(
compressed_block, compressed_size
)
return decompressed_data, bytes_consumed, success
def init_with_lz4_etc2_data(compressed_data):
stream_decoder = LZ4StreamDecoder()
total_uncompressed_size = struct.unpack('<I', compressed_data[4:8])[0]
output_buffer = bytearray()
current_pos = 8
buffer_index = 0
while current_pos < len(compressed_data):
remaining_data = compressed_data[current_pos:]
decompressed_chunk, bytes_consumed, success = block_decompress(
stream_decoder, remaining_data, None
)
current_pos += bytes_consumed
if len(decompressed_chunk) == 0:
break
output_buffer.extend(decompressed_chunk)
buffer_index = (buffer_index + 1) % 2
if len(output_buffer) > total_uncompressed_size:
output_buffer = output_buffer[:total_uncompressed_size]
return bytes(output_buffer)
def get_pkm_width(pkm_data):
if len(pkm_data) < 16:
return 0
return struct.unpack('>H', pkm_data[8:10])[0]
def get_pkm_height(pkm_data):
if len(pkm_data) < 16:
return 0
return struct.unpack('>H', pkm_data[10:12])[0]
def get_pkm_format(pkm_data):
if len(pkm_data) < 16:
return 0
return struct.unpack('>H', pkm_data[6:8])[0]
def init_with_etc2_data(pkm_data, data_size):
width = get_pkm_width(pkm_data)
height = get_pkm_height(pkm_data)
etc_format = get_pkm_format(pkm_data)
if etc_format in [2, 3, 4, 10, 11]:
mode = "RGBA"
decoder_func = texture2ddecoder.decode_etc2a8
elif etc_format in [1, 9]:
mode = "RGB"
decoder_func = texture2ddecoder.decode_etc2
else:
print(f"不支持的ETC格式: {etc_format}")
return None
decoded_pixels = decoder_func(pkm_data[16:], width, height)
image = Image.frombytes(mode, (width, height), decoded_pixels)
return image
def decrypt(input_file, output_file):
with open(input_file, "rb") as f:
compressed_data = f.read()
pkm_data = init_with_lz4_etc2_data(compressed_data)
image = init_with_etc2_data(pkm_data, len(pkm_data))
image.save(output_file, 'PNG')
return True
if __name__ == "__main__":
decrypt("gn_tb_216.png", "dec.png")