Skip to content

Instantly share code, notes, and snippets.

@nm004
Last active August 21, 2024 10:14
Show Gist options
  • Save nm004/10426f5c0b8eb2cb887b3aeb5ae99f1a to your computer and use it in GitHub Desktop.
Save nm004/10426f5c0b8eb2cb887b3aeb5ae99f1a to your computer and use it in GitHub Desktop.
import sys
import argparse
def vlc_lzw_compress(data, nbit=8):
assert 1 < nbit < 9, 'nbit must satisfies, 1 < nbit < 9'
# LZW encoding
CLR = 1<<nbit
END = CLR+1
d = {}
n = nbit+1
x = END+1
y = 1 << n
O = [(CLR, n)]
k = (data[0],)
for i in data[1:]:
k = (k[0],i)
if k in d:
k = (d[k],)
else:
d[k] = x
O.append((k[0], n))
if x == y:
n += 1
y <<= 1
x += 1
if x == 0x1000:
O.append((CLR, n))
d = {}
n = nbit+1
x = END+1
y = 1 << n
k = (k[1],)
O.append((k[0], n))
O.append((END, n))
# 8-bit string encoding
b = bytearray()
x = 0
y = 0
for i, j in O:
x |= (i << y)
y += j
while y >= 8:
b.append(x & 0xff)
x >>= 8
y -= 8
if y:
b.append(x)
return bytes(b)
def main():
usage = '''wad3texture_to_gif < wad > gif'''
parser = argparse.ArgumentParser(usage=usage)
args = parser.parse_args()
# read wad file
wad = sys.stdin.buffer.read()
# parsing wad and get texture
num_dirs = int.from_bytes(wad[0x4:0xc], 'little')
dir_offset = int.from_bytes(wad[0x8:0xc], 'little')
o = dir_offset
entry_offset = int.from_bytes(wad[o+0:o+4], 'little')
disk_size = int.from_bytes(wad[o+4:o+8], 'little')
o0 = entry_offset
o1 = o0 + disk_size
entry = wad[o0:o1]
width = int.from_bytes(entry[0x10:0x14], 'little')
height = int.from_bytes(entry[0x14:0x18], 'little')
mip0_offset = int.from_bytes(entry[0x18:0x1c], 'little')
o0 = mip0_offset
o1 = o0 + width * height
mip0 = entry[o0:o1]
mip4_offset = int.from_bytes(entry[0x24:0x28], 'little')
o = mip4_offset + width * height // 64
colors_used = int.from_bytes(entry[o:o+2], 'little')
o0 = mip4_offset + width * height // 64 + 0x2
o1 = o0 + colors_used * 3
pallet = entry[o0:o1]
mip0_compressed = vlc_lzw_compress(mip0)
#width = height = 3
#mip0_compressed = vlc_lzw_compress(b'\x00\x00\x00\xff\x00\xff\xff\x00\xff')
b = (
# Header
# Magic
b'GIF'
# Version
+ b'89a'
# Logical Screen Width
+ width.to_bytes(2, 'little')
# Logical Screen Height
+ height.to_bytes(2, 'little')
# Global Color Table Flag = true
# Color Resolution = 7
# Sort Flag = false
# Size of Global Color Table = 0
+ b'\xf7'
# Background Color Index
+ b'\xff'
# Pixel Aspect Ratio
+ b'\x00'
# Global Color Table
+ pallet
# Graphic Control Extension
# Extension Introducer
+ b'\x21'
# Graphic Control Label
+ b'\xf9'
# Block Size
+ b'\x04'
# Reserved = 0
# Disposal Method = 0
# User Input Flag = false
# Transparent Color Flag = true
+ b'\x01'
# Delay Time
+ b'\x00\x00'
# Transparent Color Index
+ b'\xff'
# Block Terminator
+ b'\x00'
# Image Descriptor
# Image Separator
+ b'\x2c'
# Image Left Position
+ b'\x00\x00'
# Image Top Position
+ b'\x00\x00'
# Image Width
+ width.to_bytes(2, 'little')
# Image Height
+ height.to_bytes(2, 'little')
# Local Color Table Flag = true
# Interlace Flag = false
# Sort Flag = false
# Reserved = 0
# Size of Local Color Table = 0
+ b'\x00'
# Local Color Table
+ b''
# LZW Minimum Code Size
+ b'\x08'
+ b''.join(
# Block Size
len(mip0_compressed[i:i+0xff]).to_bytes(1)
# Image Data
+ mip0_compressed[i:i+0xff]
for i in range(0, len(mip0_compressed), 0xff)
)
# Block Terminator
+ b'\x00'
# Trailer
+ b'\x3b')
# write gif file
sys.stdout.buffer.write(b)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment