Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
pokemon X/Y simple model/texture parser
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)
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
@nielspieleke

This comment has been minimized.

Copy link

commented Sep 2, 2014

I've got some problems with your script
I like to use these two scripts to get the pokemon models and textures.
I already have python installed and PIL too, but I don't know how to use the script.
If you could explain a bit how I could use the scripts, that would be much appreciated
And you will not only help me but a friend of mine too
Thanks in advance

@nielspieleke

This comment has been minimized.

Copy link

commented Sep 3, 2014

Hello,
I got your script working after some help from friends
Though I see that the tectures of the models aren't extracted properly
It would be very helpful if you could look into the code again and make some changes
Thanks in advance

@smealum

This comment has been minimized.

Copy link
Owner Author

commented Sep 5, 2014

maybe try this fork https://gist.github.com/magical/5a78c91a4c1ac6056943 i think someone told me it's better

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.