Created
November 19, 2018 03:23
-
-
Save NWPlayer123/eaa124d45b8ad31b83ebbc26be55c629 to your computer and use it in GitHub Desktop.
NUT converter for Donkey Konga 2 (and others?)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from PIL import Image | |
from struct import unpack | |
from math import ceil | |
import sys | |
class I4_Texture: | |
def __init__(self, height, width, 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(ceil(self.height / 8.0))): | |
for x in range(int(ceil(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) | |
try: | |
self.img[x + (tx * 2), y + ty] = color1 | |
self.img[x + (tx * 2) + 1, y + ty] = color2 | |
except: pass | |
class I8_Texture: | |
def __init__(self, height, width, 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(ceil(self.height / 4.0))): | |
for x in range(int(ceil(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)) | |
try: self.img[x + tx, y + ty] = (i, i, i) | |
except: pass | |
class IA4_Texture: | |
def __init__(self, height, width, color): | |
self.image = Image.new("RGBA", (width, height)) | |
self.img = self.image.load() | |
self.height = height | |
self.width = width | |
self.color = color | |
def DecodeImage(self, f): #8x4 block size | |
for y in range(int(ceil(self.height / 4.0))): | |
for x in range(int(ceil(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))) | |
try: self.img[x + tx, y + ty] = color | |
except: pass | |
class IA8_Texture: | |
def __init__(self, height, width, 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(ceil(self.height / 4.0))): | |
for x in range(int(ceil(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 | |
a = ord(f.read(1)) | |
i = ord(f.read(1)) | |
try: self.img[x + tx, y + ty] = (i, i, i, a) | |
except: pass | |
class RGB565_Texture: | |
def __init__(self, height, width, 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(ceil(self.height / 4.0))): | |
for x in range(int(ceil(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) | |
try: self.img[x + tx, y + ty] = color | |
except: pass | |
class RGB5A3_Texture: | |
def __init__(self, height, width, 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(ceil(self.height / 4.0))): | |
for x in range(int(ceil(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) | |
try: self.img[x + tx, y + ty] = color | |
except: pass | |
class CI4_Texture: | |
def __init__(self, height, width, color): | |
self.image = Image.new("RGBA", (width, height)) | |
self.img = self.image.load() | |
self.height = height | |
self.width = width | |
self.color = color | |
def DecodePalette(self, f, type, count): | |
palette = [] | |
if type == 1: #RGB565 | |
for i in range(count): #num_entries | |
pixel = unpack(">H", f.read(2))[0] | |
color = self.color.RGB565ToColor(pixel) | |
palette.append(color) | |
elif type == 2: #RGB5A3 | |
for i in range(count): #num_entries | |
pixel = unpack(">H", f.read(2))[0] | |
color = self.color.RGB5A3ToColor(pixel) | |
palette.append(color) | |
#3, 11 = IA8(?) | |
return palette | |
def DecodeImage(self, f, palette): | |
for y in range(int(ceil(self.height / 8.0))): | |
for x in range(int(ceil(self.width / 8.0))): | |
self.DecodeBlock(f, palette, x * 8, y * 8) | |
def DecodeBlock(self, f, palette, x, y): | |
for ty in range(8): | |
for tx in range(4): | |
index = ord(f.read(1)) | |
index = [index >> 4, index & 15] | |
for i in range(2): | |
try: self.img[x + (tx * 2) + i, y + ty] = palette[index[i]] | |
except: pass | |
class CI8_Texture: | |
def __init__(self, height, width, color): | |
self.image = Image.new("RGBA", (width, height)) | |
self.img = self.image.load() | |
self.height = height | |
self.width = width | |
self.color = color | |
def DecodePalette(self, f, type, count): | |
palette = [] | |
if type == 1: #RGB565 | |
for i in range(count): #num_entries | |
pixel = unpack(">H", f.read(2))[0] | |
color = self.color.RGB565ToColor(pixel) | |
palette.append(color) | |
elif type == 2: #RGB5A3 | |
for i in range(count): #num_entries | |
pixel = unpack(">H", f.read(2))[0] | |
color = self.color.RGB5A3ToColor(pixel) | |
palette.append(color) | |
#3, 11 = IA8(?) | |
return palette | |
def DecodeImage(self, f, palette): | |
for y in range(int(ceil(self.height / 4.0))): | |
for x in range(int(ceil(self.width / 8.0))): | |
self.DecodeBlock(f, palette, x * 8, y * 4) | |
def DecodeBlock(self, f, palette, x, y): | |
for ty in range(4): | |
for tx in range(8): | |
index = ord(f.read(1)) | |
try: self.img[x + tx, y + ty] = palette[index] | |
except: pass | |
class RGBA32_Texture: | |
def __init__(self, height, width, 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(ceil(self.height / 4.0))): | |
for x in range(int(ceil(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) | |
try: self.img[x + tx, y + ty] = (r[i], g[i], b[i], a[i]) | |
except: pass | |
class CMPR_Texture: | |
def __init__(self, height, width, 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(ceil(self.height / 8.0))): | |
for x in range(int(ceil(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] | |
try: | |
self.img[x + tx, y + ty] = color | |
val <<= 2 | |
except: pass | |
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]) | |
def half(f): | |
return unpack(">H", f.read(2))[0] | |
def full(f): | |
return unpack(">I", f.read(4))[0] | |
palette_types = ["N/A", "RGB565", "RGB5A3", "IA8", "", "", "", "", "", "", "", "IA8(?)"] | |
texture_types = ["", "", "", "ARGB8", "CMPR", "CI4", "CI8", "", "", "", "I8", "IA8"] | |
printinfo = 0 | |
if __name__ == "__main__": | |
with open(sys.argv[1], "rb") as f: | |
name = sys.argv[1].split(".")[:-1] | |
name = ".".join(name) | |
assert f.read(4) == b"NUTC" | |
assert half(f) == 0x8002 #format version | |
ntextures = half(f) | |
f.seek(0x18, 1) | |
color = ColorConversion() | |
for i in range(ntextures): | |
base = f.tell() | |
texinfo = unpack(">3I2H4B2H", f.read(0x18)) | |
if printinfo: | |
#print(i) for filename debug | |
print("File position: %08X" % base) | |
print("Texture size: 0x%X" % texinfo[0]) | |
print("Palette size: 0x%02X" % texinfo[1]) | |
print("Image size: 0x%X" % texinfo[2]) | |
print("Header size: 0x%X" % texinfo[3]) | |
print("Palette count: 0x%02X" % texinfo[4]) | |
print("Mipmap count: 0x%02X" % texinfo[6]) | |
print("Palette format: %s" % (palette_types[texinfo[7]])) | |
print("Image format: %s" % (texture_types[texinfo[8]])) | |
print("Dimensions: %dx%d" % (texinfo[9], texinfo[10])) | |
if texinfo[8] == 3: | |
tex = RGBA32_Texture(texinfo[10], texinfo[9], color) | |
f.seek(base + texinfo[3]) | |
tex.DecodeImage(f) | |
if ntextures > 1: | |
tex.image.save("%s%02d.png" % (name, i)) | |
else: | |
tex.image.save("%s.png" % name) | |
elif texinfo[8] == 4: | |
tex = CMPR_Texture(texinfo[10], texinfo[9], color) | |
f.seek(base + texinfo[3]) | |
tex.DecodeImage(f) | |
if ntextures > 1: | |
tex.image.save("%s%02d.png" % (name, i)) | |
else: | |
tex.image.save("%s.png" % name) | |
elif texinfo[8] == 5: | |
tex = CI4_Texture(texinfo[10], texinfo[9], color) | |
f.seek(base + texinfo[3] + texinfo[2]) #skip to palette | |
palette = tex.DecodePalette(f, texinfo[7], texinfo[4]) | |
f.seek(base + texinfo[3]) #skip to image data | |
tex.DecodeImage(f, palette) | |
if ntextures > 1: | |
tex.image.save("%s%02d.png" % (name, i)) | |
else: | |
tex.image.save("%s.png" % name) | |
elif texinfo[8] == 6: | |
tex = CI8_Texture(texinfo[10], texinfo[9], color) | |
f.seek(base + texinfo[3] + texinfo[2]) #skip to palette | |
palette = tex.DecodePalette(f, texinfo[7], texinfo[4]) | |
f.seek(base + texinfo[3]) #skip to image data | |
tex.DecodeImage(f, palette) | |
if ntextures > 1: | |
tex.image.save("%s%02d.png" % (name, i)) | |
else: | |
tex.image.save("%s.png" % name) | |
elif texinfo[8] == 10: | |
tex = I8_Texture(texinfo[10], texinfo[9], color) | |
f.seek(base + texinfo[3]) | |
tex.DecodeImage(f) | |
if ntextures > 1: | |
tex.image.save("%s%02d.png" % (name, i)) | |
else: | |
tex.image.save("%s.png" % name) | |
elif texinfo[8] == 11: | |
tex = IA8_Texture(texinfo[10], texinfo[9], color) | |
f.seek(base + texinfo[3]) | |
tex.DecodeImage(f) | |
if ntextures > 1: | |
tex.image.save("%s%02d.png" % (name, i)) | |
else: | |
tex.image.save("%s.png" % name) | |
else: raise BaseException("Unknown image type: %d" % texinfo[8]) | |
f.seek(base + texinfo[0]) #skip to next |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment