diff --git a/.gitignore b/.gitignore index 35a3473..36e8bad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ # Files from compilation and tests *.o +*.so +*.gch *.exe *.stackdump *.log -devil1test +__pycache__ demo-extractpld demo-extracttexture demo-extractmesh diff --git a/Makefile b/Makefile index 7cc9e14..9b199fb 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,30 @@ +CC=gcc +CFLAGS=-I"include" -Wall -fPIC #-g +LDFLAGS=-shared +TARGET=lib3ddevil1.so PEX=demo-extractpld TEX=demo-extracttexture MEX=demo-extractmesh +OBJ=devil1pld.o devil1tex.o devil1geo.o -CC=gcc -CFLAGS= -I"include" -Wall +all: $(TARGET) demos + +$(TARGET): $(OBJ) + $(LINK.cc) $(LDFLAGS) $^ -o $@ -all: pld texture mesh -pld: devil1pld.o devil1tex.o devil1geo.o +demos: pld texture mesh + +pld: $(OBJ) $(CC) $^ demo/extractpld.c $(CFLAGS) -o $(PEX) -texture: devil1pld.o devil1tex.o devil1geo.o +texture: $(OBJ) $(CC) $^ demo/extracttexture.c $(CFLAGS) -o $(TEX) -mesh: devil1pld.o devil1tex.o devil1geo.o +mesh: $(OBJ) $(CC) $^ demo/extractmesh.c $(CFLAGS) -o $(MEX) + devil1pld.o: src/devil1pld.c $(CC) -c $^ $(CFLAGS) @@ -26,4 +35,4 @@ devil1geo.o: src/devil1geo.c $(CC) -c $^ $(CFLAGS) clean: - rm *.o $(EX) $(PEX) $(TEX) $(MEX) + rm *.o $(TARGET) $(PEX) $(TEX) $(MEX) diff --git a/bindings/__init__.py b/bindings/__init__.py new file mode 100644 index 0000000..75322a0 --- /dev/null +++ b/bindings/__init__.py @@ -0,0 +1,23 @@ +import ctypes, os, sys + +def loadlibc(): + libc = None +# os.environ['PATH'] = os.path.abspath( +# os.path.join( +# os.path.dirname(__file__), "../")) \ +# + ';' \ +# + os.environ['PATH'] + # __file__ is this __init__.py + # This assumes that the repo's directory has not been modified + # and that + so = '/lib3ddevil1.so' + libdir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) + sharedlib = libdir + so + try: + libc = ctypes.cdll.LoadLibrary(sharedlib) + except OSError as e: + print("Error loading dynamically linked library.\nOSError: " + str(e)) + raise RuntimeError("Couldn't load %s" % sharedlib) + return libc + +libc = loadlibc() diff --git a/bindings/__test__.py b/bindings/__test__.py new file mode 100755 index 0000000..7550080 --- /dev/null +++ b/bindings/__test__.py @@ -0,0 +1,134 @@ +#!/usr/bin/python3 +from py3devil1pld import pyPldHeader +from py3devil1tex import pyTexturePack, pyTextureBatchDescriptor, pyTextureBatch +from py3devil1geo import pyGeoHeader, pyMeshHeader, pyMesh + +#print(libc) + +#--------------------------------------+ +# Regular Python +#--------------------------------------+ + +if __name__ == "__main__": + def pldtest(devil1pld, pldheader): + with open("pl01.pld", "rb") as f: + data = f.read() + devil1pld.getheader(ctypes.byref(pldheader), data) + devil1pld.printheader(ctypes.byref(pldheader)) + # for offset in pldheader.getoffsets(): + # print(hex(offset)) + + def textest(devil1tex, texheader): + print("texture test") + with open("pl01.pld_1.txp", "rb") as f: + data = f.read() +# texheader = ctypes.cast(data, ctypes.POINTER(TexturePack)) + th = ctypes.pointer(texheader) + devil1tex.getheader(ctypes.byref(th), data) + devil1tex.printheader(th) + batchdesc = TextureBatchDescriptor() + bd = ctypes.pointer(batchdesc) + print("\nbatch descriptor:") + devil1tex.getbatchdesc(ctypes.byref(bd), 1, data, len(data)) + devil1tex.printbatchdesc(bd) + print(bd.contents.textureSize) + + def geotest(devil1geo, geoheader): + print("geo test") + with open("pl00.pld_0", "rb") as f: + data = f.read() +# geoheader = ctypes.cast(data, ctypes.POINTER(Header)) + gh = ctypes.pointer(geoheader) + devil1geo.getheader(ctypes.byref(gh), data) + devil1geo.printheader(gh) + meshheader = MeshHeader() + mh = ctypes.pointer(meshheader) + devil1geo.getmeshheader(ctypes.byref(mh), 1, data) + devil1geo.printmeshheader(mh) + + def main(): + sharedlib='./lib3ddevil1.so' + libc = ctypes.cdll.LoadLibrary(sharedlib) + if (not libc): + print("Couldn't load %s" % sharedlib) + return 1 + print("OK") + + pldfn = Devil1PLD_FN.in_dll(libc, "DEVIL1PLD") + pldh = pyPldHeader() + pldtest(pldfn, pldh) + + texfn = Devil1TEX_FN.in_dll(libc, "DEVIL1TEX") + texh = TexturePack() + textest(texfn, texh) + + geofn = Devil1GEO_FN.in_dll(libc, "DEVIL1GEO") + geoh = Header() + geotest(geofn, geoh) + + def mainx(): + with open("pl01.pld", "rb") as f: + data = f.read() + pld = pyPldHeader(data) + pld.show() + pld2 = pyPldHeader() + pld2.show() + + with open("pl01.pld_1.txp", "rb") as f: + data = f.read() + txp = pyTexturePack(data) + txp.show() + print(txp.getbatchnumber()) + print(txp.getfirstbatchoffset()) + tbd = pyTextureBatchDescriptor(1, data) + tbd.show() + print(tbd.gettexturesize()) + #tx = pyTextures(0, tbd.gettexno(), data) + tx = pyTextureBatch(0, data) + ts = tx.gettextures() + for i in range(0, 10): + print(ts[0].data[i]) + + with open("pl00.pld_0", "rb") as f: + data = f.read() + gh = pyGeoHeader(data) + gh.show() + print("-------------") + print(gh.getnummesh()) + print(gh.getunknownb()) + print(gh.getunknownc()) + print(gh.getunknownd()) + print(gh.getpadding()) + print(gh.getunknownoffset()) + mh = pyMeshHeader(3, data) + print("-------------") + try: + mh = pyMeshHeader(87, data) + except RuntimeError as e: + print(str(e)) + try: + mh = pyMesh(87, data) + except RuntimeError as e: + print(str(e)) + print("-------------") + mh.show() + m = pyMesh(0, data) + m.show() + # p = m.getpositions() + # print("positions:") + # for point in p: + # print(point) + # n = m.getnormals() + # print("normals:") + # for point in n: + # print(point) + # u = m.getuvs() + # print("uvs:") + # for point in u: + # print(point) + dbatch = m.getbatchdata() + print(hex(dbatch.numVertex)) + print(hex(dbatch.padding)) +#---------------------------------------+ + # main() + mainx() diff --git a/bindings/py3devil1geo.py b/bindings/py3devil1geo.py new file mode 100644 index 0000000..43c4803 --- /dev/null +++ b/bindings/py3devil1geo.py @@ -0,0 +1,254 @@ +import ctypes +import os, sys +sys.path.append( + os.path.abspath( + os.path.join( + os.path.dirname(__file__), "../../"))) +from lib3ddevil1.bindings import libc +del os, sys + +#--------------------------------------+ +# Basic Struct +#--------------------------------------+ + +class Header(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("numMesh", ctypes.c_ubyte), + ("unknownNumberB", ctypes.c_ubyte), + ("unknownNumberC", ctypes.c_ubyte), + ("unknownNumberD", ctypes.c_ubyte), + ("padding", ctypes.c_int), + ("unknownOffset", ctypes.c_ulonglong) + ] + +class MeshHeader(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("numBatch", ctypes.c_short), + ("numVertex", ctypes.c_short), + ("u", ctypes.c_uint), + ("offsetBatches", ctypes.c_ulonglong), + ("flags", ctypes.c_ulonglong) + ] + +class Coordinate(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("x", ctypes.c_float), + ("y", ctypes.c_float), + ("z", ctypes.c_float) + ] + + def __str__(self): + return "(%s, %s, %s)" % (str(self.x), str(self.y), str(self.z)) + +class UVs(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("u", ctypes.c_short), + ("v", ctypes.c_short) + ] + + def __str__(self): + return "(%s, %s)" % (str(self.u), str(self.v)) + +class BoneIndexes(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("indexes", ctypes.c_ubyte * 4), + ] + +class BoneWeights(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("weights", ctypes.c_short) + ] + + +class BatchData(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("numVertex", ctypes.c_short), + ("uB", ctypes.c_short), + ("padding", ctypes.c_uint), + ("offsetPositions", ctypes.c_ulonglong), + ("offsetNormals", ctypes.c_ulonglong), + ("offsetUVs", ctypes.c_ulonglong), + ("offsetBoneIndexes", ctypes.c_ulonglong), + ("offsetBoneWeights", ctypes.c_ulonglong), + ("offsets", ctypes.c_ulonglong) + ] + +class VertexData(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("positions", ctypes.POINTER(Coordinate)), + ("normals", ctypes.POINTER(Coordinate)), + ("u", ctypes.POINTER(UVs)), + ("bi", ctypes.POINTER(BoneIndexes)), + ("bw", ctypes.POINTER(BoneWeights)) + ] + +class Batch(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("bd", ctypes.POINTER(BatchData)), + ("vd", VertexData) + ] + +class Mesh(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("b", ctypes.POINTER(Batch)) + ] + +class Devil1GEO_FN(ctypes.Structure): + _fields_ = [ + ("printheader", ctypes.CFUNCTYPE( + None, + ctypes.POINTER(Header))), + ("printmeshheader", ctypes.CFUNCTYPE( + None, + ctypes.POINTER(MeshHeader))), + ("printbatch", ctypes.CFUNCTYPE( + None, + ctypes.POINTER(Batch))), + ("printcoordinate", ctypes.CFUNCTYPE( + None, + ctypes.POINTER(Coordinate))), + ("getheader", ctypes.CFUNCTYPE( + ctypes.c_bool, + ctypes.POINTER(ctypes.POINTER(Header)), + ctypes.c_char_p)), + ("getmeshheader", ctypes.CFUNCTYPE( + ctypes.c_bool, + ctypes.POINTER(ctypes.POINTER(MeshHeader)), + ctypes.c_uint, + ctypes.c_char_p)), + ("getbatch", ctypes.CFUNCTYPE( + ctypes.c_bool, + ctypes.POINTER(Batch), + ctypes.c_uint, + ctypes.c_char_p)), + ("getmesh", ctypes.CFUNCTYPE( + ctypes.c_bool, + ctypes.POINTER(Mesh), + ctypes.c_uint, + ctypes.c_char_p, + ctypes.c_uint)) + ] + +devil1geo = Devil1GEO_FN.in_dll(libc, "DEVIL1GEO") +del libc + +#--------------------------------------+ +# Pythonic Object +#--------------------------------------+ + +class pyGeoHeader: + def __init__(self, filedata): + self.cstruct = ctypes.pointer(Header()) + ptrofptr = ctypes.byref(self.cstruct) + if filedata: + if not devil1geo.getheader(ptrofptr, filedata): + raise RuntimeError("failed to get geometry file header") + return + + def show(self): + devil1geo.printheader(self.cstruct) + + def getnummesh(self): + return self.cstruct.contents.numMesh + + def getunknownb(self): + return self.cstruct.contents.unknownNumberB + + def getunknownc(self): + return self.cstruct.contents.unknownNumberC + + def getunknownd(self): + return self.cstruct.contents.unknownNumberD + + def getpadding(self): + return hex(self.cstruct.contents.padding) + + def getunknownoffset(self): + return hex(self.cstruct.contents.unknownOffset) + + +class pyMeshHeader: + def __init__(self, i, filedata): + self.cstruct = ctypes.pointer(MeshHeader()) + ptrofptr = ctypes.byref(self.cstruct) + if filedata: + if not devil1geo.getmeshheader(ptrofptr, i, filedata): + raise RuntimeError("failed to get mesh header #" + str(i)) + return + pass + + def show(self): + devil1geo.printmeshheader(self.cstruct) + + def getnumbatch(self): + return self.cstruct.contents.numBatch + + def getnumvertex(self): + return self.cstruct.contents.numVertex + + def getunknown(self): + return hex(self.cstruct.contents.u) + + def getoffsetbatches(self): + return self.cstruct.contents.offsetBatches + + def getflags(self): + return self.cstruct.contents.flags + +class pyMesh: + def __init__(self, i, filedata): + self.cstruct = Mesh() + if filedata: + mh = pyMeshHeader(i, filedata) + # allocate memory for the size of batch * number of batches + memsize = ctypes.sizeof(Batch) * mh.getnumbatch() + self.cstruct.b = ctypes.cast( + ctypes.create_string_buffer(memsize), + ctypes.POINTER(Batch)) + if not devil1geo.getmesh( + ctypes.byref(self.cstruct), + i, + filedata, + len(filedata)): + raise RuntimeError("failed to get mesh") + del mh, memsize + return + + def show(self): + if self.cstruct.b: + devil1geo.printbatch(self.cstruct.b) + else: + print("nothing to show") + + def getbatchdata(self): + return self.cstruct.b.contents.bd.contents + + def getpositions(self): + length = self.cstruct.b.contents.bd.contents.numVertex + return self.cstruct.b.contents.vd.positions[:length] + + def getnormals(self): + length = self.cstruct.b.contents.bd.contents.numVertex + return self.cstruct.b.contents.vd.normals[:length] + + def getuvs(self): + length = self.cstruct.b.contents.bd.contents.numVertex + return self.cstruct.b.contents.vd.u[:length] + + def getboneindexes(self): + length = self.cstruct.b.contents.bd.contents.numVertex + return self.cstruct.b.contents.vd.bi[:length] + + def getboneweights(self): + length = self.cstruct.b.contents.bd.contents.numVertex + return self.cstruct.b.contents.vd.bw[:length] diff --git a/bindings/py3devil1pld.py b/bindings/py3devil1pld.py new file mode 100644 index 0000000..a16c8c5 --- /dev/null +++ b/bindings/py3devil1pld.py @@ -0,0 +1,66 @@ +import ctypes +import os, sys +# This is the folder containing the whole library. +sys.path.append( + os.path.abspath( + os.path.join( + os.path.dirname(__file__), "../../"))) +from lib3ddevil1.bindings import libc +del os, sys + +#--------------------------------------+ +# Basic Struct +#--------------------------------------+ + +class PldHeader(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("numOffset", ctypes.c_int), + ("offsets", ctypes.POINTER(ctypes.c_int)) + ] + +class Devil1PLD_FN(ctypes.Structure): + _fields_ = [ + ("getheader" , ctypes.CFUNCTYPE( + ctypes.c_bool, + ctypes.POINTER(PldHeader), + ctypes.c_char_p)), + ("sizeofsector", ctypes.CFUNCTYPE( + ctypes.c_int, + ctypes.POINTER(PldHeader), + ctypes.c_int)), + ("printheader" , ctypes.CFUNCTYPE(None, + ctypes.POINTER(PldHeader))) + ] + +devil1pld = Devil1PLD_FN.in_dll(libc, "DEVIL1PLD") +del libc + +#--------------------------------------+ +# Pythonic Object +#--------------------------------------+ + +class pyPldHeader: + def __init__(self, filedata = None): + # Store C Struct in order to call C functions + self.cstruct = PldHeader() + if filedata: + if not devil1pld.getheader(ctypes.byref(self.cstruct), filedata): + raise RuntimeError("failed to get .pld header") + self.eof = len(filedata) + + def show(self): + devil1pld.printheader(ctypes.byref(self.cstruct)) + return + + def getnumoffsets(self): + return self.cstruct.numOffsets + + # return pythonic list of offsets + def getoffsets(self): + return self.cstruct.offsets[:self.cstruct.numOffset] + + def sizeofsector(self, i): + ptr = ctypes.byref(self.cstruct) + return devil1pld.sizeofsector(ptr, i, self.eof) + diff --git a/bindings/py3devil1tex.py b/bindings/py3devil1tex.py new file mode 100644 index 0000000..dccf093 --- /dev/null +++ b/bindings/py3devil1tex.py @@ -0,0 +1,161 @@ +import ctypes +import os, sys +sys.path.append( + os.path.abspath( + os.path.join( + os.path.dirname(__file__), "../../"))) +from lib3ddevil1.bindings import libc +del os, sys + +#--------------------------------------+ +# Basic Struct +#--------------------------------------+ + +class TexturePack(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("id", ctypes.c_char * 4), # fixed length 4, reverse order + ("batchNumber", ctypes.c_int), + ("firstBatchOffset", ctypes.c_int), + ("unknownA", ctypes.c_int) + ] + +class TextureBatchDescriptor(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("batchIdx", ctypes.c_int), + ("hash", ctypes.c_int), + ("texNumber", ctypes.c_int), + ("unknownA", ctypes.c_int * 8), + ("textureSize", ctypes.c_int), + ("unknownB", ctypes.c_int * 30) + ] + +class Texture(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("data", ctypes.POINTER(ctypes.c_ubyte)) + ] + +class TextureBatch(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("batch", ctypes.POINTER(Texture)) + ] + +class Devil1TEX_FN(ctypes.Structure): + _fields_ = [ + ("printheader", ctypes.CFUNCTYPE( + None, + ctypes.POINTER(TexturePack))), + ("printbatchdesc", ctypes.CFUNCTYPE( + None, + ctypes.POINTER(TextureBatchDescriptor))), + ("getheader", ctypes.CFUNCTYPE( + ctypes.c_bool, + ctypes.POINTER( + ctypes.POINTER(TexturePack)), + ctypes.c_char_p)), + ("getbatchdesc", ctypes.CFUNCTYPE( + ctypes.c_bool, + ctypes.POINTER( + ctypes.POINTER(TextureBatchDescriptor)), + ctypes.c_uint, + ctypes.c_char_p, + ctypes.c_uint)), + ("getbatch", ctypes.CFUNCTYPE( + ctypes.c_bool, + ctypes.POINTER( + ctypes.POINTER(TextureBatch)), + ctypes.c_uint, + ctypes.c_char_p, + ctypes.c_uint)), + ("gettextures", ctypes.CFUNCTYPE( + ctypes.c_bool, + ctypes.POINTER(Texture), + ctypes.c_uint, + ctypes.c_char_p, + ctypes.c_uint)) + ] + +devil1tex = Devil1TEX_FN.in_dll(libc, "DEVIL1TEX") +del libc + +#--------------------------------------+ +# Pythonic Object +#--------------------------------------+ + +class pyTexturePack: + def __init__(self, filedata): + self.cstruct = ctypes.pointer(TexturePack()) + if not devil1tex.getheader(ctypes.byref(self.cstruct), filedata): + raise RuntimeError("failed to get texture pack header") + return + + def show(self): + devil1tex.printheader(self.cstruct) + + def getbatchnumber(self): + return self.cstruct.contents.batchNumber + + def getfirstbatchoffset(self): + return self.cstruct.contents.firstBatchOffset + +class pyTextureBatchDescriptor: + def __init__(self, i, filedata): + self.cstruct = ctypes.pointer(TextureBatchDescriptor()) + ptrofptr = ctypes.byref(self.cstruct) + if filedata: + if not devil1tex.getbatchdesc( + ptrofptr, + i, + filedata, + len(filedata)): + raise RuntimeError("failed to get texturebatchdescriptor #" + str(i)) + return + + def show(self): + devil1tex.printbatchdesc(self.cstruct) + + def getbatchidx(self): + return self.cstruct.contents.batchIdx + + def gethash(self): + return self.cstruct.contents.hash + + def gettexnumber(self): + return self.cstruct.contents.texNumber + + def gettexturesize(self): + return self.cstruct.contents.textureSize + +class pyTextureBatch: + def __init__(self, i, filedata): + self.cstruct = TextureBatch() + if filedata: + self.cstruct.batch = None + tbd = pyTextureBatchDescriptor(i, filedata) + self.amount = tbd.gettexnumber() + self.size = tbd.gettexturesize() + memsize = self.amount * self.size + self.cstruct.batch = ctypes.cast( + ctypes.create_string_buffer(memsize), + ctypes.POINTER(Texture)) + if not devil1tex.gettextures( + self.cstruct.batch, + i, + filedata, + len(filedata)): + raise RuntimeError("failed to get textures of batch #" + str(i)) + return + + def gettextures(self): + ptrs = self.cstruct.batch[:self.amount] + textures = [] + for ptr in ptrs: + texture = ctypes.cast(ptr.data, + ctypes.POINTER( + ctypes.c_ubyte * self.size))[0] + textures.append(texture) + return textures + diff --git a/demo/extractmesh.c b/demo/extractmesh.c index 71dbe23..d15dde7 100644 --- a/demo/extractmesh.c +++ b/demo/extractmesh.c @@ -59,10 +59,10 @@ void writemesh(const struct MeshHeader *mh, void extractmeshes(const char *filedata, const char *filename, unsigned int filesize) { - if (filedata == NULL || filesize <= 0) { + struct Header *h = NULL; + if (!(DEVIL1GEO.getheader(&h, filedata))|| filesize <= 0) { return; } - struct Header *h = (struct Header*)filedata; struct MeshHeader *mh = NULL; struct Mesh m; m.b = NULL; diff --git a/demo/extracttexture.c b/demo/extracttexture.c index f686ff7..8a187c3 100644 --- a/demo/extracttexture.c +++ b/demo/extracttexture.c @@ -8,10 +8,9 @@ void extracttextures(const char *filedata, struct Texture *t = NULL; struct TextureBatchDescriptor *d = NULL; char * fmt = NULL; - if (filedata == NULL || filesize == 0) { + if (!(DEVIL1TEX.getheader(&p, filedata)) || filesize == 0) { return; } - p = (struct TexturePack*)filedata; fmt = (char*)malloc(strlen(filename) + 3 + 4); unsigned int i; unsigned int j; diff --git a/docs/doc-python.txt b/docs/doc-python.txt new file mode 100644 index 0000000..6324206 --- /dev/null +++ b/docs/doc-python.txt @@ -0,0 +1,243 @@ + +lib3ddevil1/bindings +Python interface to C library functions of lib3ddevil1. + +This component uses ctypes thus it must access a compiled .so file (internally +a .dll for Windows). The .so may be located in the top level of the library +directory or any directory in the environment variable's path. + +Note that there are two categories of objects in each python interface. One is +derived from ctypes.Structure, and the other is a pythonic object. The former +is for internal use and requires knowledge of both python, C, and ctypes. The +latter is to be used by python users. + +Functions and Objects + class pyPldHeader + def __init__(self, filedata = None): + parameters: file data, is a single line list of data. + + This function may raise RuntimeError when it fails to initialize + based on file data. + + def show(self): + This prints the contents of the object. + + def getnumoffsets(self): + This returns the attribute numOffsets of a PldHeader. + See details of the C struct in doc-pld.txt. + + def getoffsets(self): + This returns a pythonic list of offsets. + The offsets can be accessed via list comprehensions. + + def sizeofsector(self, i): + parameter: i, an integer. + This returns the size of the i-th sector in the .pld. + + If the result is -1, then specified i is out of range. + + class PyTexturePack + def __init__(self, filedata): + parameters: file data, a single line list of data. + + This function may raise RuntimeError when it fails to initialize + based on file data. + + See details of the C struct in doc-tex.txt. + + def show(self): + This prints the contents of the object. + + def getbatchnumber(self): + This returns the attribute 'batchNumber'. + + def getfirstbatchoffset(self): + This returns the attribute 'firstBatchOffset'. + + class pyTextureBatchDescriptor: + def __init__(self, i, filedata): + parameters: i, for i-th instance of TextureBatchDescriptor. + filedata, a single line list of data. + + This function may raise RuntimeError when it fails to initialize + based on file data. + + See details of the C struct in doc-tex.txt. + + def show(self): + This prints the contents of the object. + + def getbatchidx(self): + This returns the attribute 'batchidx'. + + def gethash(self): + This returns the attribute 'hash'. + + def gettexnumber(self): + This returns the attribute 'texNumber'. + + def gettexturesize(self): + This returns the attribute 'textureSize'. + + class pyTextureBatch: + def __init__(self, i, filedata): + parameters: i, for i-th instance of TextureBatch. + file data, a single line list of data. + + This function may raise RuntimeError when it fails to initialize + based on file data. + + See details of the C struct in doc-tex.txt. + + def gettextures(self): + This returns a pythonic list of texture data. + The textures can be accessed via list comprehensions. + + class pyGeoHeader: + def __init__(self, filedata): + parameters: file data, a single line list of data. + + This function may raise RuntimeError when it fails to initialize + based on file data. + + See details of the C struct in doc-geo.txt. + + def show(self): + This prints the contents of the object. + + def getnummesh(self): + This returns the attribute 'numMesh'. + + def getunknownb(self): + This returns the attribute 'unknownNumberB'. + + def getunknownc(self): + This returns the attribute 'unknownNumberC'. + + def getunknownd(self): + This returns the attribute 'unknownNumberD'. + + def getpadding(self): + This returns the attribute 'padding'. + + def getunknownoffset(self): + This returns the attribute 'unknownOffset'. + + class pyMeshHeader: + def __init__(self, i, filedata): + + def show(self): + This prints the contents of the object. + + def getnumbatch(self): + This returns the attribute 'numBatch'. + + def getnumvertex(self): + This returns the attribute 'numVertex'. + + def getunknown(self): + This returns the attribute 'u'. + + def getoffsetbatches(self): + This returns the attribute 'offsetBatches'. + + def getflags(self): + This returns the attribute 'flags'. + + class pyMesh: + def __init__(self, i, filedata): + parameters: i, for i-th instance of Mesh. + filedata, a single line list of data. + + This function may raise RuntimeError when it fails to initialize + based on file data. + + See details of the C struct in doc-geo.txt. + + def show(self): + This prints the contents of the object. + + def getbatchdata(self): + This returns the attribute 'bd'. + It is a object without methods, and it's attributes are + conventionally accessed with the '.' operator. + + def getpositions(self): + This returns a pythonic list of 'positions'. + The positions can be accessed with list comprehensions. + + A position is a object 'Coordinate' without methods. + The attributes are x, y, and z to be accessed with the + '.' operator. + + def getnormals(self): + This returns a pythonic list of 'normals'. + The normals can be accessed with list comprehensions. + + A normal is a object 'Coordinate' without methods. + The attributes are x, y, and z to be accessed with the + '.' operator. + + def getuvs(self): + This returns a pythonic list of 'u'. + The UVs can be accessed with list comprehensions. + + A UV has two attributes, 'u' and 'v'. + + def getboneindexes(self): + This returns a pythonic list of 'bi'. + The bone indices can be accessed with list comprehensions. + + A bone index is a fixed length array of 4 elements, unsigned byte. + + def getboneweights(self): + This returns a pythonic list of 'bw'. + The bone weights can be accessed with list comprehensions. + + A bone weight is effectively an integer in python. + +Example Logic: Extract Pld's + with open("pl01.pld", "rb") as f: + data = f.read() + pld = pyPldHeader(data) + filename = "demonstrate" + i = 0 + for offset in pld.getoffsets(): + with open(filename + str(i), "wb") as fw: + end = offset + pld.sizeofsector(i) + chunk = data[offset:end] + fw.write(chunk) + i += 1 + +Example Logic: Extract Textures from a Single Batch + with open("pl01.pld_1.txp", "rb") as f: + data = f.read() + tp = pyTexturePack(data) + filename = "texture" # part 1 of output file name + id = 0 # part 2 of output file name + # Iterate through all of the batches in the package. + for i in range(0, tp.getbatchnumber()): + # Get a batch. + tx = pyTextureBatch(i, data) + # Iterate through all textures in batch. + for texture in tx.gettextures(): + with open(filename + str(id) + ".dds", "wb") as fw: + fw.write(texture) + id += 1 + +Example Logic: Iterate through all MeshHeaders and Meshes: + with open("pl00.pld_0", "rb") as f: + # Get data to extract from. + data = f.read() + # We want to know how many meshes are in the file. + gh = pyGeoHeader(data) + # Show each MeshHeader + for i in range(0, gh.getnummesh()): + mh = pyMeshHeader(i, data) + mh.show() + # Get each Mesh + for i in range(0, gh.getnummesh()): + m = pyMesh(i, data) + m.show() + # Whatever you do with each of them is up to you. + diff --git a/include/devil1geo.h b/include/devil1geo.h index 7b6b3d2..7293041 100644 --- a/include/devil1geo.h +++ b/include/devil1geo.h @@ -85,6 +85,9 @@ typedef struct { // input: pointer to struct void (* const printcoordinate)(struct Coordinate*, unsigned int); +// input: pointer to struct, file data + bool (* const getheader) (struct Header**, const char*); + // input: pointer of pointer to struct, order, file data // ** = 'pass by reference' of a pointer to struct bool (* const getmeshheader) (struct MeshHeader**, @@ -96,11 +99,11 @@ typedef struct { unsigned int offset, const char * const); -// input: pointer to struct, order, file data +// input: pointer to struct, order, file data, file size bool (* const getmesh) (struct Mesh*, unsigned int i, const char*, - unsigned int filesize); + unsigned int); } fn_devil1geo; extern fn_devil1geo const DEVIL1GEO; diff --git a/include/devil1tex.h b/include/devil1tex.h index 9013b2b..f45be35 100644 --- a/include/devil1tex.h +++ b/include/devil1tex.h @@ -45,6 +45,9 @@ typedef struct { // input: pointer to struct void (* const printbatchdesc)(struct TextureBatchDescriptor*); +// input: pointer to struct, file data + bool (* const getheader) (struct TexturePack**, const char*); + // input: pointer of pointer to struct, order, file data, file size // ** = 'pass by reference' of a pointer to struct bool (* const getbatchdesc) (struct TextureBatchDescriptor**, diff --git a/src/devil1geo.c b/src/devil1geo.c index b329072..72357af 100644 --- a/src/devil1geo.c +++ b/src/devil1geo.c @@ -9,6 +9,7 @@ static void printmeshbatch(struct Batch*); static void printcoordinate(struct Coordinate*, unsigned int); +static bool getgheader(struct Header**, const char*); static bool getmeshheader(struct MeshHeader**, unsigned int i, const char * const); @@ -20,6 +21,7 @@ fn_devil1geo const DEVIL1GEO = {printgheader, printmeshheader, printmeshbatch, printcoordinate, + getgheader, getmeshheader, getmeshbatch, getmesh}; @@ -74,14 +76,22 @@ static void printcoordinate(struct Coordinate *p, unsigned int count) { } } +static bool getgheader(struct Header** h, const char* filedata) { + if (filedata == NULL) { + return false; + } + (*h) = (struct Header*)filedata; + return true; +} + static bool getmeshheader(struct MeshHeader **hs, unsigned int i, const char * const filedata) { - bool done = false; - if (hs == NULL || filedata == NULL) { + bool done = false; + struct Header *h = NULL; + if (hs == NULL || !(getgheader(&h, filedata))) { return done; } - struct Header *h = (struct Header*)filedata; if (h -> numMesh < i) { return done; } diff --git a/src/devil1tex.c b/src/devil1tex.c index 5b4d8dd..f284cd8 100644 --- a/src/devil1tex.c +++ b/src/devil1tex.c @@ -8,8 +8,10 @@ static void printtph(struct TexturePack*); // Print Texture Batch Descriptor. static void printtbd(struct TextureBatchDescriptor*); -// Get Texture Batch Descriptor. +// Get Texture Pack Header +static inline bool getpackheader(struct TexturePack**, const char*); +// Get Texture Batch Descriptor. static bool gettexdescriptor(struct TextureBatchDescriptor**, unsigned int, const char *, @@ -30,6 +32,7 @@ static bool unpacktexbatch(struct Texture*, fn_devil1tex const DEVIL1TEX = {printtph, printtbd, + getpackheader, gettexdescriptor, gettexbatch, unpacktexbatch}; @@ -55,6 +58,14 @@ static void printtbd(struct TextureBatchDescriptor *bd) { } } +inline static bool getpackheader(struct TexturePack** p, const char *filedata) { + if (filedata == NULL) { + return false; + } + (*p) = (struct TexturePack*)filedata; + return true; +} + static bool gettexdescriptor(struct TextureBatchDescriptor **descriptor, unsigned int i, const char *filedata, @@ -75,10 +86,9 @@ static bool gettexbatch(struct TextureBatch **batch, const char *filedata, unsigned int filesize) { struct TexturePack *p = NULL; - if (filedata == NULL) { // no data to get batch from. + if (!(getpackheader(&p, filedata))) { return false; } - p = (struct TexturePack*)filedata; if (i > p -> batchNumber) { // no such batch in texture pack return false; }