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