Skip to content

Instantly share code, notes, and snippets.

@smealum
Created March 6, 2014 16:51
Show Gist options
  • Save smealum/9394125 to your computer and use it in GitHub Desktop.
Save smealum/9394125 to your computer and use it in GitHub Desktop.
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
Copy link

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
Copy link

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
Copy link
Author

smealum 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