# hi this is super hacky and only converts a tiny segment of display lists. # it's really just to get a feel for how the formats work. # after extracting/decompressing OoT, run this script on object_tsubo. # the top of the pot is missing, i know. see the comment above. import array import io import struct import sys name = "pot" tw, th = 32, 64 # hardcoded texture width and height viscale = 256 # inverted scale, this is arbitrary v_base = 0x1838 # offset to first G_VTX command with open(sys.argv[1], "rb") as f: data = f.read() f = io.BytesIO(data) # hardcoded for a rga5a1 texture at the start of the file: pix = array.array('H', f.read(tw * th * 2)) pix.byteswap() rgbs = [] for p in pix: r = (p & 0xF800) >> 11 g = (p & 0x07C0) >> 6 b = (p & 0x003E) >> 1 a = p & 1 # TODO: round color calculations. or not? i don't imagine the N64 bothers. rgb = (r * 255 // 31, g * 255 // 31, b * 255 // 31) rgbs.append(rgb) verts = [] texes = [] norms = [] # unimplemented polys = [] vi = 0 # vertex index to offset by (incremented after each chunk) f.seek(v_base) opcode = ord(f.read(1)) while opcode == 0x01: # G_VTX counts = f.read(3) numv = ((counts[0] & 0xF) << 4) | ((counts[1] & 0xF0) >> 4) vbidx = counts[2] // 2 - numv vaddr = struct.unpack(">i", f.read(4))[0] back = f.tell() f.seek(vaddr & 0xFFFFFF) for i in range(numv): if 0: # colored vertices vertex = struct.unpack(">hhhHhhBBBB", f.read(16)) x, y, z, w, tx, ty, r, g, b, a = vertex else: # lit vertices vertex = struct.unpack(">hhhHhhbbbB", f.read(16)) x, y, z, w, tx, ty, n, p, q, a = vertex pos = (x / viscale, y / viscale, z / viscale) # FIXME: texture coordinates are slightly off tpos = ((tx / 32 / tw), 1 - (ty / 32 / th)) verts.append(pos) texes.append(tpos) f.seek(back) while 1: opcode = ord(f.read(1)) if opcode not in (6, 5): break if opcode == 5: indices = struct.unpack('>bbbbbbb', f.read(7)) a0, a1, a2, _, _, _, _ = indices atri = a0 // 2 + 1 + vi, a1 // 2 + 1 + vi, a2 // 2 + 1 + vi polys.append(atri) elif opcode == 6: indices = struct.unpack('>bbbbbbb', f.read(7)) a0, a1, a2, _, b0, b1, b2 = indices # TODO: assert all indices are in range(32) atri = a0 // 2 + 1 + vi, a1 // 2 + 1 + vi, a2 // 2 + 1 + vi btri = b0 // 2 + 1 + vi, b1 // 2 + 1 + vi, b2 // 2 + 1 + vi polys.append(atri) polys.append(btri) vi = len(verts) # write the model file with open("{}.obj".format(name), "w") as f: fprint = lambda *args, **kwargs: print(*args, file=f, **kwargs) fprint("mtllib {}.mtl".format(name)) fprint("o {}".format(name)) for vert in verts: fprint("v", *("{:.8f}".format(v) for v in vert)) for tex in texes: fprint("vt", *("{:.8f}".format(v) for v in tex)) #fprint("g {}".format(name)) fprint("usemtl {}".format(name)) fprint("s off") for poly in polys: fprint("f", *("{}/{}".format(i, i) for i in poly)) # write the material file with open("{}.mtl".format(name), "w") as f: fprint = lambda *args, **kwargs: print(*args, file=f, **kwargs) fprint("newmtl {}".format(name)) fprint("Ns 0.0") # i don't know what any of these do but they all look terrible fprint("Ka 1.0 1.0 1.0") fprint("Kd 0.8 0.8 0.8") fprint("Ks 0.0 0.0 0.0") fprint("Ke 0.0 0.0 0.0") fprint("d 1.0") fprint("illum 0") fprint("map_Kd {}.bmp".format(name)) #fprint("map_Ka {}.bmp".format(name)) # write the texture file with open("{}.bmp".format(name), "wb") as f: f.write(b'BM') # format: 32-bit BGRA # everything else: sane default f.write(struct.pack("