#!/usr/bin/python3 import ctypes, sys #--------------------------------------+ # Devil 1: PLD Base #--------------------------------------+ 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))) ] #--------------------------------------+ # Devil 1: TEX Base #--------------------------------------+ 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.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)) ] #--------------------------------------+ # Devil 1: GEO Base #--------------------------------------+ 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) ] 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( None, 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)) ] #--------------------------------------+ # Python Objs #--------------------------------------+ sharedlib = './lib3ddevil1.so' libc = ctypes.cdll.LoadLibrary(sharedlib) if not libc: print("Couldn't load %s" % sharedlib) sys.exit() del sys print("\nlib3ddevil1 loaded.") devil1pld = Devil1PLD_FN.in_dll(libc, "DEVIL1PLD") devil1tex = Devil1TEX_FN.in_dll(libc, "DEVIL1TEX") devil1geo = Devil1GEO_FN.in_dll(libc, "DEVIL1GEO") class PLDHeader: def __init__(self, filedata = None): # Store C Struct in order to call C functions self.cstruct = PldHeader() if filedata: devil1pld.getheader(ctypes.byref(self.cstruct), filedata) 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) class TEXturePack: def __init__(self, filedata): self.cstruct = ctypes.pointer(TexturePack()) devil1tex.getheader(ctypes.byref(self.cstruct), filedata) return def show(self): devil1tex.printheader(self.cstruct) def getbatchno(self): return self.cstruct.contents.batchNumber def getfirstbatchoffset(self): return self.cstruct.contents.firstBatchOffset class TEXtureBatchDescriptor: def __init__(self, i, filedata): self.cstruct = ctypes.pointer(TextureBatchDescriptor()) ptrofptr = ctypes.byref(self.cstruct) if filedata: devil1tex.getbatchdesc(ptrofptr, i, filedata, len(filedata)) 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 gettexno(self): return self.cstruct.contents.texNumber def gettexturesize(self): return self.cstruct.contents.textureSize # Needs testing / correction - gettextures will 'return by parameter' # a dynamic array of textures. Need to be able to access multiple Texture() class TEXtures: def __init__(self, i, count, filedata): self.cstruct = ctypes.byref(Texture()) if filedata: devil1tex.gettextures(self.cstruct, i, filedata, len(filedata)) return class GEOHeader: def __init__(self, filedata): self.cstruct = ctypes.pointer(Header()) ptrofptr = ctypes.byref(self.cstruct) if filedata: devil1geo.getheader(ptrofptr, filedata) return def show(self): devil1geo.printheader(self.cstruct) pass class MEShHeader: def __init__(self, i, filedata): self.cstruct = ctypes.pointer(MeshHeader()) ptrofptr = ctypes.byref(self.cstruct) if filedata: devil1geo.getmeshheader(ptrofptr, i, filedata) return pass def show(self): devil1geo.printmeshheader(self.cstruct) def getbatchno(self): return self.cstruct.contents.numBatch class MEsh: def __init__(self, i, filedata): self.cstruct = Mesh() if filedata: mh = MEShHeader(i, filedata) # allocate memory for the size of batch * number of batches memsize = ctypes.sizeof(Batch) * mh.getbatchno() 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)): print("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 getpositions(self): length = self.cstruct.b.contents.bd.contents.numVertex return self.cstruct.b.contents.vd.positions[:length] #--------------------------------------+ # 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 = PldHeader() 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 = PLDHeader(data) pld.show() pld2 = PLDHeader() pld2.show() with open("pl01.pld_1.txp", "rb") as f: data = f.read() txp = TEXturePack(data) txp.show() print(txp.getbatchno()) print(txp.getfirstbatchoffset()) tbd = TEXtureBatchDescriptor(1, data) tbd.show() print(tbd.gettexturesize()) tx = TEXtures(0, tbd.gettexno(), data) with open("pl00.pld_0", "rb") as f: data = f.read() gh = GEOHeader(data) gh.show() mh = MEShHeader(3, data) mh.show() m = MEsh(0, data) m.show() p = m.getpositions() for point in p: print(point) #---------------------------------------+ # main() mainx()