Created
March 6, 2014 16:51
-
-
Save smealum/9394125 to your computer and use it in GitHub Desktop.
pokemon X/Y simple model/texture parser
This file contains 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
import struct | |
import math | |
import os | |
import sys | |
if sys.version > '3': | |
buffer = memoryview | |
def getWord(b, k, n=4): | |
return sum(list(map(lambda c: b[k+c]<<(c*8),range(n)))) | |
totalVcount=1 | |
currentVcount=0 | |
totalNcount=1 | |
currentNcount=0 | |
hasNormals=False | |
totalTCcount=1 | |
currentTCcount=0 | |
texTable=[] | |
def adaptU(u): | |
u*=2.0 | |
if u>1.0: | |
u=1.0-u | |
# if u<0.0: | |
# u=-u | |
return u | |
def parseVertices(b, o, s, f): | |
global hasNormals | |
global currentVcount, currentNcount, currentTCcount | |
out="" | |
# print("# "+hex(int(s/f))+" vertices") | |
# for i in range(o,o+s,f): | |
# arr=[struct.unpack('f',buffer(srcdata[(i+k*4):(i+(k+1)*4)]))[0] for k in range(3)] | |
# print("v "+str(arr[0])+" "+str(arr[1])+" "+str(arr[2])) | |
# currentVcount=currentVcount+1 | |
for i in range(int(s/f)): | |
arr=[struct.unpack('f',buffer(srcdata[(o+k*4):(o+(k+1)*4)]))[0] for k in range(8)] | |
out+=("v "+str(arr[0])+" "+str(arr[1])+" "+str(arr[2]))+"\n" | |
out+=("vn "+str(arr[3])+" "+str(arr[4])+" "+str(arr[5]))+"\n" | |
out+=("vt "+str(adaptU(arr[6]))+" "+str(arr[7]))+"\n" | |
hasNormals=True | |
currentVcount=currentVcount+1 | |
currentNcount=currentNcount+1 | |
currentTCcount=currentTCcount+1 | |
o=o+f | |
return out | |
def parseFaces(b, o, s, f): | |
global hasNormals | |
global totalVcount, totalNcount, totalTCcount | |
out="" | |
if f==1: | |
#for u8 face descriptors | |
for i in range(int(s/(3*f))): | |
arr=[struct.unpack('B',buffer(srcdata[(o+k):(o+(k+1))]))[0] for k in range(8)] | |
# if hasNormals: | |
out+=("f "+str(arr[0]+totalVcount)+"/"+str(arr[0]+totalTCcount)+"/"+str(arr[0]+totalNcount)+" "+ | |
str(arr[1]+totalVcount)+"/"+str(arr[1]+totalTCcount)+"/"+str(arr[1]+totalNcount)+" "+ | |
str(arr[2]+totalVcount)+"/"+str(arr[2]+totalTCcount)+"/"+str(arr[2]+totalNcount)) | |
# else: | |
# out+=("f "+str(arr[0]+totalVcount)+" "+str(arr[1]+totalVcount)+" "+str(arr[2]+totalVcount)) | |
out+="\n" | |
o=o+3*f | |
elif f==2: | |
#for u16 face descriptors | |
for i in range(int(s/(3*f))): | |
arr=[struct.unpack('H',buffer(srcdata[(o+k*2):(o+(k+1)*2)]))[0] for k in range(8)] | |
# if hasNormals: | |
out+=("f "+str(arr[0]+totalVcount)+"/"+str(arr[0]+totalTCcount)+"/"+str(arr[0]+totalNcount)+" "+ | |
str(arr[1]+totalVcount)+"/"+str(arr[1]+totalTCcount)+"/"+str(arr[1]+totalNcount)+" "+ | |
str(arr[2]+totalVcount)+"/"+str(arr[2]+totalTCcount)+"/"+str(arr[2]+totalNcount)) | |
# else: | |
# out+=("f "+str(arr[0]+totalVcount)+" "+str(arr[1]+totalVcount)+" "+str(arr[2]+totalVcount)) | |
out+="\n" | |
o=o+3*f | |
return out | |
def parseFC(b, o, d, vn, g=False): | |
headerData=getWord(b,o+0xA8,1) | |
if vn<=256: | |
faceDataFormat=1 | |
else: | |
faceDataFormat=2 | |
out="" | |
out+=("# face format "+hex(faceDataFormat))+"\n" | |
faceDataOffset=d+getWord(b,o+0x10) | |
if g: | |
return faceDataOffset | |
faceDataSize=getWord(b,o+0x18)*faceDataFormat | |
out+=("# vn "+hex(vn))+"\n" | |
out+=("# "+hex(faceDataSize))+"\n" | |
out+=("# "+hex(headerData))+"\n" | |
out+=parseFaces(b, faceDataOffset, faceDataSize, faceDataFormat) | |
return (faceDataOffset, out) | |
def parseVTX(b, o, d, fc, g=False): | |
global totalVcount, totalNcount, totalTCcount | |
global currentVcount, currentNcount, currentTCcount | |
vertexDataFormat=getWord(b,o+0x3A,1) | |
vertexDataOffset=d+getWord(b,o+0x30) | |
if g: | |
return (vertexDataOffset, vertexDataFormat) | |
vertexDataSize=fc-vertexDataOffset | |
out="" | |
out+=("# vertex format "+hex(vertexDataFormat))+"\n" | |
out+=parseVertices(b, vertexDataOffset, vertexDataSize, vertexDataFormat) | |
totalVcount=totalVcount+currentVcount | |
totalNcount=totalNcount+currentNcount | |
totalTCcount=totalTCcount+currentTCcount | |
currentVcount=0 | |
currentNcount=0 | |
currentTCcount=0 | |
return out | |
def parseSymbol(b,o): | |
len=0 | |
while getWord(b,o+len,1)!=0x00: | |
len+=1 | |
return(b[o:o+len].decode("ascii")) | |
def parseTexTableEntry(b, o, desc, symb): | |
global texTable | |
out="" | |
texNameOffset=symb+getWord(b,o+0x8) | |
s=parseSymbol(b,texNameOffset) | |
texTable.append(s) | |
out+="newmtl "+s+"\nillum 2\nKd 0.800000 0.800000 0.800000\nKa 0.200000 0.200000 0.200000\nKs 0.000000 0.000000 0.000000\nKe 0.000000 0.000000 0.000000\nNs 0.000000\n" | |
out+="map_Kd "+s+".png\n\n" | |
return out | |
def parseTableEntry(b, o, desc, data): | |
out="" | |
id=getWord(b,o) | |
texID=getWord(b,o,2) | |
vtxOffset=desc+getWord(b,o+0x08) | |
fcOffset=getWord(b,o+0x10) | |
flag=getWord(b,fcOffset+0x3A,2) | |
out+=("# flag "+hex(flag))+"\n" | |
out+=("# desc0 "+hex(fcOffset))+"\n" | |
out+=("# desc "+hex(getWord(b,fcOffset+0x64)))+"\n" | |
if getWord(b,fcOffset+0x78)!=0xFFFFFFFF: | |
fcOffset3=desc+getWord(b,fcOffset+0x98) | |
else: | |
fcOffset3=0 | |
fcOffset=desc+getWord(b,fcOffset+0x64) | |
fcOffset2=getWord(b,o+0x34) | |
out+=("# desc0_2 "+hex(fcOffset2))+"\n" | |
out+=("# desc2 "+hex(getWord(b,fcOffset2+0x30)))+"\n" | |
out+=("# desc3 "+hex(fcOffset3-desc))+"\n" | |
fcOffset2=desc+getWord(b,fcOffset2+0x30) | |
vf=getWord(b,o+0x0C) | |
vf2=getWord(b,o+0x04) | |
out+=("# "+hex(o)+" "+hex(getWord(b,o))+" "+hex(vf2)+" "+hex(vf)+" "+hex(getWord(b,o+0x14))+" "+hex(getWord(b,o+0x1C)))+"\n" | |
vo=parseVTX(b, vtxOffset, data, 0, True) | |
fo=parseFC(b, fcOffset, data, 0, True) | |
vn=int((fo-vo[0])/vo[1]) | |
fc=parseFC(b, fcOffset, data, vn) | |
if fcOffset2!=fcOffset: | |
fc2=parseFC(b, fcOffset2, data, vn) | |
if fcOffset3!=0: | |
fc3=parseFC(b, fcOffset3, data, vn) | |
out+=parseVTX(b, vtxOffset, data, fc[0]) | |
out+=("g OBJ_"+hex(o))+"\n" | |
out+=("usemtl "+texTable[texID])+"\n" | |
out+=(fc[1]) | |
if fcOffset2!=fcOffset: | |
out+=(fc2[1]) | |
if fcOffset3!=0: | |
out+=(fc3[1]) | |
return out | |
srcfn=sys.argv[1] | |
dstfn=sys.argv[2] | |
if len(sys.argv)>3: | |
dstdir=sys.argv[3] | |
else: | |
dstdir="." | |
srcdata=bytearray(open(srcfn, 'rb').read()) | |
if srcdata[0]!=0x42: | |
srcdata[:]=srcdata[0x80:len(srcdata)] | |
symbOffset=getWord(srcdata, 0x0C) | |
descOffset=getWord(srcdata, 0x10) | |
dataOffset=getWord(srcdata, 0x14) | |
tableOffset=0x6C+getWord(srcdata, 0x104) | |
texTableOffset=0x78+getWord(srcdata, tableOffset) | |
texTableEntries=getWord(srcdata, tableOffset+0x4) | |
tableEntries=getWord(srcdata, tableOffset+0x10) | |
tableOffset=0x38+getWord(srcdata, tableOffset+0x14) | |
out="" | |
for i in range(texTableEntries): | |
out+=parseTexTableEntry(srcdata, texTableOffset+i*0x58, descOffset, symbOffset) | |
open(dstdir+"/"+dstfn+".mtl", 'w').write(out) | |
out="mtllib "+dstfn+".mtl\n" | |
for i in range(tableEntries): | |
out+=parseTableEntry(srcdata, tableOffset+i*0x38, descOffset, dataOffset) | |
open(dstdir+"/"+dstfn+".obj", 'w').write(out) |
This file contains 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
import struct | |
import math | |
import os | |
import sys | |
from PIL import Image | |
from subprocess import call | |
if sys.version > '3': | |
buffer = memoryview | |
def getWord(b, k, n=4): | |
return sum(list(map(lambda c: b[k+c]<<(c*8),range(n)))) | |
#returns RGBA tuple (with added pixel size) | |
#format ID only confirmed for 0, 4, 5 and 7 ! | |
def parsePixel(b, o, f): | |
if f==0: | |
#RGBA8 | |
pixel=getWord(b, o, 1) | |
return ((getWord(b, o+3, 1), getWord(b, o+2, 1), getWord(b, o+1, 1), getWord(b, o, 1)), 4) | |
elif f==1: | |
#RGB8 | |
pixel=getWord(b, o, 1) | |
return ((getWord(b, o+2, 1), getWord(b, o+1, 1), getWord(b, o+0, 1), 255), 3) | |
elif f==2: | |
#RGBA5551 | |
pixel=getWord(b, o, 2) | |
return ((((pixel>>11)&0x1F)*8, ((pixel>>6)&0x1F)*8, ((pixel>>1)&0x1F)*8, (255 if pixel&0x1==1 else 0)), 2) | |
elif f==3: | |
#RGB565 | |
pixel=getWord(b, o, 2) | |
return ((((pixel>>11)&0x1F)*8, ((pixel>>5)&0x3F)*4, ((pixel)&0x1F)*8, 255), 2) | |
elif f==4: | |
#RGBA4 | |
pixel=getWord(b, o, 2) | |
return ((((pixel>>12)&0xF)*16, ((pixel>>8)&0xF)*16, ((pixel>>4)&0xF)*16, (pixel&0xF)*16), 2) | |
elif f==5: | |
#LA8 | |
pixel=getWord(b, o+1, 1) | |
return ((pixel, pixel, pixel, getWord(b, o, 1)),2) | |
elif f==7: | |
#L8 | |
pixel=getWord(b, o, 1) | |
return ((pixel, pixel, pixel, 255),1) | |
# elif f==0xA: | |
# #? | |
# return ((0,0,0,0), 1) | |
# elif f==0xC: | |
# #? | |
# return ((0,0,0,0), 1) | |
elif f==0xD: | |
#A4 ? | |
return ((0,0,0,0), 1) | |
else: | |
print("# unknown format : "+hex(f)) | |
# return ((0,0,0,0), 1) | |
tileOrder=[0,1,8,9,2,3,10,11,16,17,24,25,18,19,26,27,4,5,12,13,6,7,14,15,20,21,28,29,22,23,30,31,32,33,40,41,34,35,42,43,48,49,56,57,50,51,58,59,36,37,44,45,38,39,46,47,52,53,60,61,54,55,62,63] | |
def parseTile(im, b, o, f, x, y): | |
global tileOrder | |
for k in range(8*8): | |
i=tileOrder[k]%8 | |
j=int((tileOrder[k]-i)/8) | |
pixel=parsePixel(b,o,f) | |
im.putpixel((x+i,y+j), pixel[0]) | |
o=o+pixel[1] | |
return o | |
def parseTexture(b, o, w, h, f, n): | |
im = Image.new("RGBA", (w,h)) | |
dstname=n+".png" | |
# if f==0xB or f==0xC: | |
# #ETC1; used external etc program | |
# open("tmp_etc","wb").write(b[o:(o+s)]) | |
# #not super clean | |
# call(["etc.exe", "tmp_etc", str(w), str(h)]) | |
# imgData=bytearray(open("tmp_etc.data","rb").read()) | |
# for j in range(0,h): | |
# for i in range(0,w): | |
# k=(i+j*w)*4 | |
# im.putpixel((i,j), (imgData[k],imgData[k+1],imgData[k+2],imgData[k+3])) | |
# else: | |
for j in range(0,h,8): | |
for i in range(0,w,8): | |
o=parseTile(im, b, o, f, i, j) | |
print("writing "+dstname) | |
im.save(dstname) | |
def parseEntry(b, o, d, n): | |
textureDataWidth=getWord(b,o+0x2,2) | |
textureDataHeight=getWord(b,o+0x0,2) | |
if textureDataWidth!=0 and textureDataHeight!=0: | |
textureDataFormat=getWord(b,o+0x10,1) | |
textureDataOffset=d+getWord(b,o+0x8) | |
print(hex(textureDataFormat)) | |
parseTexture(b, textureDataOffset, textureDataWidth, textureDataHeight, textureDataFormat, n) | |
def parseSymbols(b,o,n): | |
sym=[] | |
for i in range(n): | |
len=0 | |
while getWord(b,o+len,1)!=0x00: | |
len+=1 | |
sym.append(b[o:o+len].decode("ascii")) | |
o+=len+1 | |
return sym | |
srcfn=sys.argv[1] | |
srcdata=bytearray(open(srcfn, 'rb').read()) | |
if srcdata[1]!=0x54: | |
exit() | |
srcdata[:]=srcdata[0x80:len(srcdata)] | |
symbOffset=getWord(srcdata, 0x0C) | |
descOffset=getWord(srcdata, 0x10) | |
dataOffset=getWord(srcdata, 0x14) | |
numTex=getWord(srcdata, 0x60) | |
symbols=parseSymbols(srcdata,symbOffset,numTex) | |
print(symbols) | |
k=0 | |
while getWord(srcdata,descOffset)!=0x0: | |
if getWord(srcdata,descOffset+0x8-0x20)!=getWord(srcdata,descOffset+0x8): | |
parseEntry(srcdata, descOffset, dataOffset, symbols[k]) | |
k+=1 | |
descOffset+=0x20 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
maybe try this fork https://gist.github.com/magical/5a78c91a4c1ac6056943 i think someone told me it's better