Skip to content

Instantly share code, notes, and snippets.

@NWPlayer123
Last active March 22, 2018 02:50
Show Gist options
  • Save NWPlayer123/253e15479c3e517dedee585cef728656 to your computer and use it in GitHub Desktop.
Save NWPlayer123/253e15479c3e517dedee585cef728656 to your computer and use it in GitHub Desktop.
extracts TPL files (only tested on Monkey Ball 2, missing a few formats)
from struct import unpack
from PIL import Image
from os import mkdir
from os.path import exists
import sys
class I4_Texture:
def __init__(self, width, height, color):
self.image = Image.new("RGB", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #8x8 block size
for y in range(int(self.height / 8.0)):
for x in range(int(self.width / 8.0)):
self.DecodeBlock(f, x * 8, y * 8)
def DecodeBlock(self, f, x, y):
for ty in range(8): #8 pixel block height
for tx in range(4): #cuz we read 2 pixels worth
data = ord(f.read(1))
color1, color2 = self.color.I4ToColor(data)
self.img[x + (tx * 2), y + ty] = color1
self.img[x + (tx * 2) + 1, y + ty] = color2
class I8_Texture:
def __init__(self, width, height, color):
self.image = Image.new("RGB", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #8x4 block size
for y in range(int(self.height / 4.0)):
for x in range(int(self.width / 8.0)):
self.DecodeBlock(f, x * 8, y * 4)
def DecodeBlock(self, f, x, y):
for ty in range(4): #4 pixel block height
for tx in range(8): #8 pixel block width
i = ord(f.read(1))
self.img[x + tx, y + ty] = (i, i, i)
class IA4_Texture:
def __init__(self, width, height, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #8x4 block size
for y in range(int(self.height / 4.0)):
for x in range(int(self.width / 8.0)):
self.DecodeBlock(f, x * 8, y * 4)
def DecodeBlock(self, f, x, y):
for ty in range(4): #4 pixel block height
for tx in range(8): #8 pixel block width
color = self.color.IA4ToColor(ord(f.read(1)))
self.img[x + tx, y + ty] = color
class RGB565_Texture:
def __init__(self, width, height, color):
self.image = Image.new("RGB", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #4x4 block size
for y in range(int(self.height / 4.0)):
for x in range(int(self.width / 4.0)):
self.DecodeBlock(f, x * 4, y * 4)
def DecodeBlock(self, f, x, y):
for ty in range(4): #4 pixel block height
for tx in range(4): #4 pixel block width
pixel = unpack(">H", f.read(2))[0]
color = self.color.RGB565ToColor(pixel)
self.img[x + tx, y + ty] = color
class RGB5A3_Texture:
def __init__(self, width, height, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #4x4 block size
for y in range(int(self.height / 4.0)):
for x in range(int(self.width / 4.0)):
self.DecodeBlock(f, x * 4, y * 4)
def DecodeBlock(self, f, x, y):
for ty in range(4): #4 pixel block height
for tx in range(4): #4 pixel block width
pixel = unpack(">H", f.read(2))[0]
color = self.color.RGB5A3ToColor(pixel)
self.img[x + tx, y + ty] = color
class RGBA32_Texture:
def __init__(self, width, height, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color #we don't need it but add anyways
def DecodeImage(self, f):
for y in range(int(self.height / 4.0)):
for x in range(int(self.width / 4.0)):
self.DecodeBlock(f, x * 4, y * 4)
def DecodeBlock(self, f, x, y):
r = [];g = [];b = [];a = []
for i in range(16):
a.append(ord(f.read(1)))
r.append(ord(f.read(1)))
for i in range(16):
g.append(ord(f.read(1)))
b.append(ord(f.read(1)))
for ty in range(4):
for tx in range(4):
i = tx + (ty * 4)
self.img[x + tx, y + ty] = (r[i], g[i], b[i], a[i])
class CMPR_Texture:
def __init__(self, width, height, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f):
for y in range(int(self.height / 8.0)):
for x in range(int(self.width / 8.0)):
self.DecodeBlock(f, x * 8, y * 8)
def DecodeBlock(self, f, x, y):
self.DecodeTile(f.read(8), x + 0, y + 0)
self.DecodeTile(f.read(8), x + 4, y + 0)
self.DecodeTile(f.read(8), x + 0, y + 4)
self.DecodeTile(f.read(8), x + 4, y + 4)
def DecodeTile(self, data, x, y):
c1, c2 = unpack(">2H", data[0:4])
r1, g1, b1 = self.color.RGB565ToColor(c1)
r2, g2, b2 = self.color.RGB565ToColor(c2)
colors = [(), (), (), ()]
colors[0] = (r1, g1, b1, 0xFF)
colors[1] = (r2, g2, b2, 0xFF)
if c1 > c2:
r3 = ((r2 - r1) >> 1) - ((r2 - r1) >> 3)
g3 = ((g2 - g1) >> 1) - ((g2 - g1) >> 3)
b3 = ((b2 - b1) >> 1) - ((b2 - b1) >> 3)
colors[2] = (r1 + r3, g1 + g3, b1 + b3, 0xFF)
colors[3] = (r2 - r3, g2 - g3, b2 - b3, 0xFF)
else:
colors[2] = (int((r1 + r2 + 1) / 2.0),
int((g1 + g2 + 1) / 2.0),
int((b1 + b2 + 1) / 2.0),
0xFF)
colors[3] = (r2, g2, b2, 0x00)
index = unpack(">4B", data[4:])
for ty in range(4):
val = index[ty]
for tx in range(4):
color = colors[(val >> 6) & 3]
self.img[x + tx, y + ty] = color
val <<= 2
class ColorConversion:
def __init__(self):
self.Bits3To8 = self.MakeDepthConversionTable(3, 8)
self.Bits8To3 = self.MakeDepthConversionTable(8, 3)
self.Bits4To8 = self.MakeDepthConversionTable(4, 8)
self.Bits8To4 = self.MakeDepthConversionTable(8, 4)
self.Bits5To8 = self.MakeDepthConversionTable(5, 8)
self.Bits8To5 = self.MakeDepthConversionTable(8, 5)
self.Bits6To8 = self.MakeDepthConversionTable(6, 8)
self.Bits8To6 = self.MakeDepthConversionTable(8, 6)
def MakeDepthConversionTable(self, a, b):
a = 1 << a
b = 1 << b
result = []
for i in range(a):
result.append(int(float(i) / (a - 1) * (b - 1)) & 0xFF)
return result
def I4ToColor(self, v):
i1 = self.Bits4To8[v >> 4]
i2 = self.Bits4To8[v & 15]
return [(i1, i1, i1), (i2, i2, i2)]
def IA4ToColor(self, v):
i = self.Bits4To8[v & 15]
a = self.Bits4To8[v >> 4]
return (i, i, i, a)
def RGB565ToColor(self, v):
return (self.Bits5To8[(v >> 11) & 31],
self.Bits6To8[(v >> 5) & 63],
self.Bits5To8[(v >> 0) & 31])
def RGB5A3ToColor(self, v):
if v & 0x8000: #RGB555
return (self.Bits5To8[(v >> 10) & 31],
self.Bits5To8[(v >> 5) & 31],
self.Bits5To8[(v >> 0) & 31],
0xFF)
else: #RGB4A3
return (self.Bits4To8[(v >> 8) & 15],
self.Bits4To8[(v >> 4) & 15],
self.Bits4To8[(v >> 0) & 15],
self.Bits3To8[(v >> 12) & 7])
if __name__ == "__main__":
do_mipmaps = 0
print_info = 0
with open(sys.argv[1], "rb") as f:
name = sys.argv[1].split(".")[:-1]
name = ".".join(name)
if not exists(name):
mkdir(name)
count = unpack(">I", f.read(4))[0]
info = [unpack(">2I4H", f.read(16)) for i in range(count)]
if print_info:
types = ["I4 ",
"I8 ",
"IA4 ",
"IA8 ",
"RGB565",
"RGB5A3",
"RGBA32", "",
"CI4 ",
"CI8 ",
"CI14x2", "", "", "",
"CMPR "]
for i in range(len(info)):
print("%6s img%s off:%08X w:%4d h:%4d" % (types[info[i][0]],
str(i).ljust(2), info[i][1], info[i][2], info[i][3]))
color = ColorConversion()
for i in range(count):
f.seek(info[i][1]) #offset
if info[i][0] == 0:
if do_mipmaps:
if not exists("%s/img%d" % (name, i)):
mkdir("%s/img%d" % (name, i))
width = info[i][2]
height = info[i][3]
for j in range(info[i][4]): #num_mipmaps
tex = I4_Texture(width, height, color)
tex.DecodeImage(f)
tex.image.save("%s/img%d/img%d_%d.png" % (name, i, i, j))
width = int(width / 2.0)
height = int(height / 2.0)
else:
tex = I4_Texture(info[i][2], info[i][3], color)
tex.DecodeImage(f)
tex.image.save("%s/img%d.png" % (name, i))
elif info[i][0] == 1:
if do_mipmaps:
if not exists("%s/img%d" % (name, i)):
mkdir("%s/img%d" % (name, i))
width = info[i][2]
height = info[i][3]
for j in range(info[i][4]): #num_mipmaps
tex = I8_Texture(width, height, color)
tex.DecodeImage(f)
tex.image.save("%s/img%d/img%d_%d.png" % (name, i, i, j))
width = int(width / 2.0)
height = int(height / 2.0)
else:
tex = I8_Texture(info[i][2], info[i][3], color)
tex.DecodeImage(f)
tex.image.save("%s/img%d.png" % (name, i))
elif info[i][0] == 2:
if do_mipmaps:
if not exists("%s/img%d" % (name, i)):
mkdir("%s/img%d" % (name, i))
width = info[i][2]
height = info[i][3]
for j in range(info[i][4]): #num_mipmaps
tex = IA4_Texture(width, height, color)
tex.DecodeImage(f)
tex.image.save("%s/img%d/img%d_%d.png" % (name, i, i, j))
width = int(width / 2.0)
height = int(height / 2.0)
else:
tex = IA4_Texture(info[i][2], info[i][3], color)
tex.DecodeImage(f)
tex.image.save("%s/img%d.png" % (name, i))
elif info[i][0] == 4:
if do_mipmaps:
if not exists("%s/img%d" % (name, i)):
mkdir("%s/img%d" % (name, i))
width = info[i][2]
height = info[i][3]
for j in range(info[i][4]): #num_mipmaps
tex = RGB565_Texture(width, height, color)
tex.DecodeImage(f)
tex.image.save("%s/img%d/img%d_%d.png" % (name, i, i, j))
width = int(width / 2.0)
height = int(height / 2.0)
else:
tex = RGB565_Texture(info[i][2], info[i][3], color)
tex.DecodeImage(f)
tex.image.save("%s/img%d.png" % (name, i))
elif info[i][0] == 5:
if do_mipmaps:
if not exists("%s/img%d" % (name, i)):
mkdir("%s/img%d" % (name, i))
width = info[i][2]
height = info[i][3]
for j in range(info[i][4]): #num_mipmaps
tex = RGB5A3_Texture(width, height, color)
tex.DecodeImage(f)
tex.image.save("%s/img%d/img%d_%d.png" % (name, i, i, j))
width = int(width / 2.0)
height = int(height / 2.0)
else:
tex = RGB5A3_Texture(info[i][2], info[i][3], color)
tex.DecodeImage(f)
tex.image.save("%s/img%d.png" % (name, i))
elif info[i][0] == 6:
if do_mipmaps:
if not exists("%s/img%d" % (name, i)):
mkdir("%s/img%d" % (name, i))
width = info[i][2]
height = info[i][3]
for j in range(info[i][4]): #num_mipmaps
tex = RGBA32_Texture(width, height, color)
tex.DecodeImage(f)
tex.image.save("%s/img%d/img%d_%d.png" % (name, i, i, j))
width = int(width / 2.0)
height = int(height / 2.0)
else:
tex = RGBA32_Texture(info[i][2], info[i][3], color)
tex.DecodeImage(f)
tex.image.save("%s/img%d.png" % (name, i))
elif info[i][0] == 14:
if do_mipmaps:
if not exists("%s/img%d" % (name, i)):
mkdir("%s/img%d" % (name, i))
width = info[i][2]
height = info[i][3]
for j in range(info[i][4]): #num_mipmaps
tex = CMPR_Texture(width, height, color)
tex.DecodeImage(f)
tex.image.save("%s/img%d/img%d_%d.png" % (name, i, i, j))
width = int(width / 2.0)
height = int(height / 2.0)
else:
tex = CMPR_Texture(info[i][2], info[i][3], color)
tex.DecodeImage(f)
tex.image.save("%s/img%d.png" % (name, i))
elif info[i][0] in range(15):
raise BaseException(info[i][0])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment