'''Noesis import plugin. Written by Tsukihime''' from inc_noesis import * import noesis import rapi import struct def registerNoesisTypes(): '''Register the plugin. Just change the Game name and extension.''' handle = noesis.register("UC Gundam Online", ".det") noesis.setHandlerTypeCheck(handle, noepyCheckType) noesis.setHandlerLoadModel(handle, noepyLoadModel) return 1 def noepyCheckType(data): '''Verify that the format is supported by this plugin. Default yes''' if len(data) < 4: return 0 bs = NoeBitStream(data) version = bs.readUShort() if version not in [2, 3, 4]: return 0 return 1 def noepyLoadModel(data, mdlList): '''Build the model, set materials, bones, and animations. You do not need all of them as long as they are empty lists (they are by default)''' ctx = rapi.rpgCreateContext() parser = get_parser(data) parser.parse_file() mdl = rapi.rpgConstructModel() mdl.setModelMaterials(NoeModelMaterials(parser.texList, parser.matList)) mdl.setBones(parser.boneList) mdl.setAnims(parser.animList) mdlList.append(mdl) return 1 def get_parser(data): bs = NoeBitStream(data) version = bs.readShort() if version == 2: return Parser2(data) elif version == 3: return Parser3(data) elif version == 4: return Parser4(data) class Mesh(object): def __init__(self): self.name = "" self.idxStart = 0 self.numIdx = 0 self.vertStart = 0 self.numVerts = 0 self.vertSize = 0 self.idxBuff = bytes() self.vertBuff = bytes() self.submeshes = [] class Header(object): def __init__(self): self.version = 0 self.vertChunks = 0 self.faceChunks = 0 self.scriptChunks = 0 self.shaderChunks = 0 self.matChunks = 0 self.texChunks = 0 class UCGOParser(object): '''This class contains methods for parsing each struct that is common amongst version 1, 3, and 4 formats''' def __init__(self, data): self.inFile = NoeBitStream(data) self.animList = [] self.texList = [] self.matList = [] self.boneList = [] self.meshList = [] self.vertInfo = [] self.idxInfo = [] def make_transform(self, mesh): pass def build_meshes(self): rapi.rpgSetOption(noesis.RPGOPT_TRIWINDBACKWARD, 1) for i in range(len(self.meshList)): mesh = self.meshList[i] rapi.rpgSetName(mesh.name) if mesh.vertSize == 32: rapi.rpgBindPositionBufferOfs(mesh.vertBuff, noesis.RPGEODATA_FLOAT, 32, 0) rapi.rpgBindNormalBufferOfs(mesh.vertBuff, noesis.RPGEODATA_FLOAT, 32, 12) rapi.rpgBindUV1BufferOfs(mesh.vertBuff, noesis.RPGEODATA_FLOAT, 32, 24) elif mesh.vertSize == 40: rapi.rpgBindPositionBufferOfs(mesh.vertBuff, noesis.RPGEODATA_FLOAT, 40, 0) rapi.rpgBindNormalBufferOfs(mesh.vertBuff, noesis.RPGEODATA_FLOAT, 40, 20) rapi.rpgBindUV1BufferOfs(vertBuff, noesis.RPGEODATA_FLOAT, 40, 32) elif mesh.vertSize == 48: rapi.rpgBindPositionBufferOfs(vertBuff, noesis.RPGEODATA_FLOAT, 48, 0) rapi.rpgBindNormalBufferOfs(vertBuff, noesis.RPGEODATA_FLOAT, 48, 28) rapi.rpgBindUV1BufferOfs(mesh.vertBuff, noesis.RPGEODATA_FLOAT, 48, 40) for sub in mesh.submeshes: idxStart = sub.idxOfs idxEnd = idxStart + sub.numIdx * 2 idxBuff = mesh.idxBuff[idxStart:idxEnd] rapi.rpgSetMaterial(self.tex) rapi.rpgCommitTriangles(idxBuff, noesis.RPGEODATA_USHORT, sub.numIdx, noesis.RPGEO_TRIANGLE, 1) def read_name(self): string = self.inFile.readBytes(self.inFile.readUInt()) return noeStrFromBytes(string) def read_uname(self): string = self.inFile.readBytes(self.inFile.readUInt() * 2) return noeStrFromBytes(string, 'utf-16') def parse_header(self): header = Header() header.version = self.inFile.readShort() self.inFile.readShort() self.inFile.read('2H') unk = self.inFile.readUInt() header.vertChunks = self.inFile.readUInt() header.faceChunks = self.inFile.readUInt() header.scriptChunks = self.inFile.readUInt() header.shaderChunks = self.inFile.readUInt() - 1 self.inFile.read("2L") header.matChunks = self.inFile.readUInt() header.texChunks = self.inFile.readUInt() if header.version == 4: self.inFile.readUInt() return header def parse_vertices(self, vertChunks): #32, 40, 48 for i in range(vertChunks): chunkID = self.inFile.readUShort() self.inFile.read('3H') chunkType = self.inFile.readUInt() size = self.inFile.readUInt() vertSize = self.inFile.readUInt() numVerts = self.inFile.readUInt() self.inFile.read("3L") vertBuff = self.inFile.readBytes(numVerts*vertSize) self.vertInfo.append([numVerts, vertSize, vertBuff]) def parse_faces(self, faceChunks): for i in range(faceChunks): chunkID = self.inFile.readUShort() self.inFile.read('3H') chunkType = self.inFile.readUInt() size = self.inFile.readUInt() idxSize = self.inFile.readUInt() numIdx = self.inFile.readUInt() self.inFile.read("3L") idxBuff = self.inFile.readBytes(numIdx * 2) self.idxInfo.append([numIdx, idxBuff]) def parse_scripts(self, scriptChunks): '''brute force skip''' for i in range(scriptChunks): self.inFile.read('4H') self.read_uname() t = self.inFile.readUShort() self.inFile.read("2H") while t != 0x5258: t = self.inFile.readUShort() self.inFile.seek(-8, 1) def parse_shader_fields(self, numFields): fieldName = self.read_name() size = self.inFile.readUInt() self.inFile.readUInt() self.inFile.readBytes(size) def parse_shader_chunks(self, shaderChunks): for i in range(shaderChunks): self.inFile.read('4H') self.inFile.readUInt() #shader? self.inFile.read('2H') shaderName = self.read_uname() #shader filename numFields = self.inFile.readUInt() for i in range(numFields): size = self.inFile.readUInt() self.inFile.readUInt() self.inFile.readBytes(size) fieldName = self.read_name() self.inFile.seek(740, 1) def parse_material(self): '''Data for the material object''' pass def parse_material_chunks(self, matChunks): ''' ''' for i in range(matChunks): self.inFile.read('2H') self.inFile.read('2H') matName = self.read_uname() self.parse_material() def parse_texture(self): pass def parse_texture_chunks(self, texChunks): for i in range(texChunks): self.inFile.read('2H') self.inFile.read('2H') texName = self.read_uname() self.parse_texture() self.tex = texName def parse_bone_chunk(self): '''Contained inside the mesh''' self.inFile.read('2H') self.inFile.read('2H') self.inFile.read('4l') boneName = self.read_uname() print(boneName) # skip chunk after bone name self.inFile.seek(248, 1) def parse_empty_chunk(self): '''This seems empty, but it has a variable data length so we need to treat it as such''' self.inFile.read('2L') size = self.inFile.readUInt() self.inFile.readUInt() self.inFile.readBytes(size) #self.inFile.readUInt() def parse_frame_chunks(self, frameChunks): '''Chunk including the SCENE ROOT. Seems to hold information regarding mesh counts and frame counts and other things''' for i in range(frameChunks): meshType, xr = self.inFile.read('2H') #2, 3, 6 or 7 if meshType == 2: self.inFile.seek(164, 1) self.parse_empty_chunk() self.read_uname() elif meshType == 3: self.inFile.seek(108, 1) unk1 = self.inFile.readUInt() # 0 or 4 self.inFile.seek(56, 1) self.parse_empty_chunk() self.read_uname() elif meshType == 6: self.inFile.seek(156, 1) self.parse_empty_chunk() self.inFile.readUInt() root = self.read_uname() #scene root elif meshType == 7: self.inFile.seek(112, 1) unk1 = self.inFile.readUInt() # 0 or 4 if unk1 == 0: self.inFile.read('8f') elif unk1 == 4: self.inFile.read('16f') self.inFile.read('2L') self.parse_empty_chunk() self.inFile.readUInt() root = self.read_uname() #scene root def parse_submesh_chunk(self, count, mesh): ''' This is inside the mesh. Occurs variable times. Submesh info?''' submeshes = [] for i in range(count): submesh = Mesh() ctype, xr = self.inFile.read('2H') # 01, 02 if ctype == 1: self.inFile.seek(28, 1) elif ctype == 2: self.inFile.seek(16, 1) submesh.matNum = self.inFile.readUInt() self.inFile.seek(12, 1) self.inFile.read('2H') self.inFile.readUInt() submesh.idxStart = self.inFile.readUInt() * 3 submesh.numIdx = self.inFile.readUInt() * 3 submesh.idxOfs = self.inFile.readUInt() * 2 submesh.vertStart = self.inFile.readUInt() submesh.numVerts = self.inFile.readUInt() self.inFile.readUInt() submeshes.append(submesh) self.inFile.read("%dL" %count) self.inFile.read('2L') vertInfo = self.vertInfo[self.inFile.readUInt()] idxInfo = self.idxInfo[self.inFile.readUInt()] mesh.numVerts = vertInfo[0] mesh.vertSize = vertInfo[1] mesh.vertBuff = vertInfo[2] mesh.numIdx = idxInfo[0] mesh.idxBuff = idxInfo[1] self.inFile.read('3L') mesh.submeshes = submeshes def parse_mesh_chunks(self, meshChunks): for i in range(meshChunks): mesh = Mesh() chunkType, xr = self.inFile.read('2H') if chunkType == 2: self.inFile.seek(56, 1) count, count2 = self.inFile.read('2L') self.inFile.seek(4, 1) self.parse_submesh_chunk(count, mesh) mesh.name = self.read_uname() self.inFile.seek(116, 1) self.parse_empty_chunk() boneName = self.read_uname() elif chunkType == 3: self.inFile.seek(56, 1) ctype1, xr = self.inFile.read('2H') count, count2 = self.inFile.read('2L') self.inFile.seek(8, 1) self.parse_submesh_chunk(count, mesh) mesh.name = self.read_uname() self.inFile.seek(96, 1) mesh.pos = self.inFile.readBytes(12) self.inFile.seek(8, 1) self.parse_empty_chunk() boneName = self.read_uname() elif chunkType == 6: self.inFile.seek(48, 1) ctype1, xr = self.inFile.read('2H') count = self.inFile.readUInt() self.inFile.read("%dL" %ctype1) self.parse_submesh_chunk(count, mesh) mesh.name = self.read_uname() self.inFile.seek(112, 1) self.parse_empty_chunk() self.inFile.readInt() mesh.boneName = self.read_uname() elif chunkType == 7: self.inFile.seek(48, 1) ctype1, xr = self.inFile.read('2H') count = self.inFile.readUInt() self.inFile.read("%dL" %ctype1) self.parse_submesh_chunk(count, mesh) mesh.name = self.read_uname() self.inFile.seek(68, 1) self.inFile.readUInt() mesh.transform = self.inFile.readBytes(64) self.inFile.read('2L') self.parse_empty_chunk() self.inFile.readInt() mesh.boneName = self.read_uname() else: print(chunkType, self.inFile.tell()) break self.meshList.append(mesh) def parse_bone_chunks(self, boneCount): for i in range(boneCount): self.inFile.readUInt() # id? self.inFile.read('2H') unk = self.inFile.readUInt() for j in range(unk): self.inFile.readUInt() self.inFile.read('2H') self.inFile.seek(24, 1) self.inFile.seek(16, 1) boneName = self.read_uname() def parse_file(self): '''Main parser method''' header = self.parse_header() self.inFile.seek(48, 1) self.parse_vertices(header.vertChunks) self.parse_faces(header.faceChunks) self.parse_scripts(header.scriptChunks) self.parse_shader_chunks(header.shaderChunks) self.parse_material_chunks(header.matChunks) self.parse_texture_chunks(header.texChunks) print("Scene Root unk", self.inFile.tell()) self.inFile.readUInt() # root frame has a special integer ? self.parse_frame_chunks(1) meshCount, frameCount, unk, unk, boneCount = self.inFile.read('5L') self.parse_mesh_chunks(meshCount) self.parse_frame_chunks(frameCount) # don't know maybe self.parse_bone_chunks(boneCount) # DONE modelName = self.read_uname() self.inFile.read('4L') print('done', self.inFile.tell()) print("EOF:", self.inFile.tell() == self.inFile.dataSize) self.build_meshes() class Parser2(UCGOParser): '''Version 2 parser''' def parse_material(self): self.inFile.read('18f') self.inFile.read('2H') self.inFile.read('2H') size = self.inFile.readUInt() self.inFile.readUInt() self.inFile.readBytes(size) self.inFile.readUInt() def parse_texture(self): self.inFile.seek(24, 1) class Parser3(UCGOParser): '''Version 3 parser''' def parse_material(self): self.inFile.read('18f') self.inFile.read('2H') self.inFile.read('2H') size = self.inFile.readUInt() self.inFile.readUInt() self.inFile.readBytes(size) self.inFile.readUInt() def parse_texture(self): self.inFile.seek(24, 1) class Parser4(UCGOParser): '''Version 4 parser''' def parse_material(self): self.inFile.read('50f') self.inFile.read('2H') self.inFile.read('2H') size = self.inFile.readUInt() self.inFile.readUInt() self.inFile.readBytes(size) self.inFile.readUInt() def parse_texture(self): self.inFile.seek(44, 1)