Last active
          March 22, 2018 02:50 
        
      - 
      
- 
        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)
  
        
  
    
      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 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