# Assassin's Creed: Origins model plugin for Noesis
# Version 1.0
# by theawesomecoder61 (pineapples721)
#
# Version 1.0 (3/27/2019):
# - initial version

from inc_noesis import *
import math
import os
import struct

MDL_MAGIC = 1096652136
MSH_DATA =  105229237

class submeshObj(object):
	def __init__(self):

		self.vertices = []
		self.faces = []
		self.normals = []
		self.uvs = []
		self.uvs2 = []
		self.uvs3 = []
		self.uvs4 = []
		self.boneIndices = []
		self.boneIndices2 = []
		self.boneWeights = []
		self.boneWeights2 = []
		self.VertexCount = 0
		self.numIndices = 0
		self.vertexOffset = 0
		self.faceOffset = 0
		self.FaceCount = 0
		self.IndexCount = 0
		self.MinFaceIndex = 99999999
		self.MaxFaceIndex = 0

		self.TextureIndex = 0
		self.id = 0
		self.num = 0
		self.NumOfVerticesBeforeMe = 0

def registerNoesisTypes():
	handle = noesis.register("Assassin's Creed: Odyssey model", ".acodmesh")
	noesis.setHandlerTypeCheck(handle, mdlValidate)
	noesis.setHandlerLoadModel(handle, mdlLoadModel)

	noesis.logPopup()
	return 1

def mdlValidate(data):
	bs = NoeBitStream(data)
	#print(bs)
	loc_mgic = bs.readInt()
	print("magic " + hex(loc_mgic))
	if loc_mgic != MDL_MAGIC:
		print("Not a model file")
		return 1
	return 1

def fnd4byte (bs, bytes_ar):
	old_offest = bs.tell()
	byte1 = bs.readUByte()
	byte2 = bs.readUByte()
	byte3 = bs.readUByte()
	byte4 = bs.readUByte()
	
	
	val2 = 0
	val = byte1 + byte2 * 0x100 + byte3 * 0x10000 + byte4 * 0x1000000

	while(val != bytes_ar):
		old_offest1 = bs.tell()
		byte1 = bs.readUByte()
		byte2 = bs.readUByte()
		byte3 = bs.readUByte()
		byte4 = bs.readUByte()
		
		val = byte1 + byte2 * 0x100 + byte3 * 0x10000 + byte4 * 0x1000000
		
		#str1 = hex(val)[2:]
		#while (len(str1) < 8): str1 = "0" + str1
		#print("0x" + str1)
		bs.seek(-3 , NOESEEK_REL)
		val2 = val2 +1 
		old_offest2 = bs.tell()
		if(old_offest1 == old_offest2): 
			bs.seek(old_offest, NOESEEK_ABS)
			return -1
	
	
	new_offest = bs.tell()
	bs.seek(old_offest, NOESEEK_ABS)	
	return new_offest - 1

	

def mdlLoadModel(data, mdlList):
	bs = NoeBitStream(data)
	submeshes = []
	meshes = []
	meshes2 = []
	vertex_pos = 0
	face_pos = 0
	WatchUV = False

	print("")
	print("")
	print("")
	print("New File")

	print("cur_offset1: " + hex(bs.tell()))

	ResourceIdentifier = bs.readUInt()
	FileSize = bs.readUInt()
	FileNameSize = bs.readUInt()
	
	print("New File")
	print("ResourceIdentifier: " + hex(ResourceIdentifier) + "  " + str(ResourceIdentifier) )
	print("FileSize: " +  hex(FileSize) + "  " + str(FileSize) )
	print("FileNameSize: " +  hex(FileNameSize) + "  " + str(FileNameSize) )

	FileName = NoeBitStream(bs.readBytes(FileNameSize)).readString()
	print("FileName: " +  FileName )
	print("")
	
	


									
	#					reader.BaseStream.Seek(10, SeekOrigin.Current);
    #                    reader.BaseStream.Seek(3, SeekOrigin.Current);
    # #                   meshBlock.ModelType = reader.ReadInt32();
    #                    meshBlock.ACount = reader.ReadInt32();
	
	bs.seek(10 + 3, NOESEEK_REL)
	print("cur_offset2: " + hex(bs.tell()))

	ModelType = bs.readUInt()
	ACount = bs.readUInt()
		
	print("ModelType: " +  hex(ModelType) + "  " + str(ModelType) )
	print("ACount: " +  hex(ACount) + "  " + str(ACount) )
	
	print("cur_offset2: " + hex(bs.tell()))
	
	
	
	
	
	
	
	
	val2 = fnd4byte(bs, 4238218645)
	print("magic model start offset : " + hex(val2))
	dtest = 0

	while(val2 > 0):# and (dtest < 200):
		#try:
			meshes2 = []
			dtest = dtest + 1
			print("")
			bs.seek(val2, NOESEEK_ABS)
			print("cur_offset3: " + hex(bs.tell()))

			loc_mgic2 = bs.readUInt()
			print("	magic model vlaue: " + hex(loc_mgic2))
			
			
			
			
			print("	Model " +  FileName + ":   "+ str(dtest))			
			
			
			

			
			
			bs.seek(22, NOESEEK_REL)
			print("	cur_offset4: " + hex(bs.tell()))

			compiledMesh_VertexTableSize = bs.readUInt()
			compiledMesh_Unknown1 = bs.readUInt()
			compiledMesh_Unknown2 = bs.readUInt()

			
			print("	compiledMesh_VertexTableSize: " + hex(compiledMesh_VertexTableSize) + "  " + str(compiledMesh_VertexTableSize) )
			print("	compiledMesh_Unknown1: " +  hex(compiledMesh_Unknown1) + "  " + str(compiledMesh_Unknown1) )
			print("	compiledMesh_Unknown2: " +  hex(compiledMesh_Unknown2) + "  " + str(compiledMesh_Unknown2) )
			
			
			
			
			bs.seek(33, NOESEEK_REL)
			print("	cur_offset5: " + hex(bs.tell()))

			submesh_MeshCount = bs.readUInt()

			

			print("	submesh_MeshCount: " + hex(submesh_MeshCount) + "  " + str(submesh_MeshCount) )

			Entries_FaceOffset = []
			Entries_VertexOffset = []
			for i in range (0, submesh_MeshCount):
				#bs.seek(16, NOESEEK_REL)
				val11 = bs.readUInt()
				val12 = bs.readUInt()
				val13 = bs.readUInt()
				val14 = bs.readUInt()		
				Entries_FaceOffset.append(  bs.readUInt())
				Entries_VertexOffset.append(  bs.readUInt())
				print("		val1: " + hex(val11) + " 	 val2: " + hex(val12) + " 	 val3: " + hex(val13) + " 	 val4: " + hex(val14))

				print("		Entries_FaceOffset: " + hex(Entries_FaceOffset[len(Entries_FaceOffset) - 1]) + " 	 Entries_VertexOffset: " + hex(Entries_VertexOffset[len(Entries_VertexOffset) - 1]))


			
			print("	cur_offset6: " + hex(bs.tell())) # uv_external
			extUV_offset = 0
			
			if(compiledMesh_Unknown2 != 0):        # WATCH HERE!!!! May be  compiledMesh_Unknown2 - size of one vertex????
				extUV_offset = bs.tell()
				unknown0DataSize = bs.readUInt()
				bs.seek(unknown0DataSize, NOESEEK_REL)

			print("	cur_offset7: " + hex(bs.tell()))


			#string725
			
			vertexDataSize = bs.readUInt()
			#actualVertexTableSize = compiledMesh.VertexTableSize != 8 && compiledMesh.VertexTableSize != 16 ? compiledMesh.VertexTableSize : compiledMesh.Unknown1; // this variable now holds the true vertex table size
			print("	vertexDataSize: " +  hex(vertexDataSize) + "  " + str(vertexDataSize) )

			actualVertexTableSize = compiledMesh_Unknown1
			if((compiledMesh_VertexTableSize != 8) and 
				(compiledMesh_VertexTableSize != 16) ): actualVertexTableSize = compiledMesh_VertexTableSize #?????
			print("	actualVertexTableSize: " +  hex(actualVertexTableSize) + "  " + str(actualVertexTableSize) )

			
			vertexOffset = bs.tell()
			print("	vertexOffset: " +  hex(vertexOffset) + "  " + str(vertexOffset) )

			
			bs.seek(vertexDataSize, NOESEEK_REL)
			print("	cur_offset8: " + hex(bs.tell()))  #good

			if(compiledMesh_Unknown2 == 0):
				bs.seek(12, NOESEEK_REL)
			else:
				unknown0DataSize = bs.readUInt()
				bs.seek(unknown0DataSize, NOESEEK_REL)
				
			print("	cur_offset9: " + hex(bs.tell())) #good
			
			if(compiledMesh_Unknown2 != 0):
				unknown1DataSize = bs.readUInt()
				bs.seek(unknown1DataSize, NOESEEK_REL)	
				
				
			print("	cur_offset10: " + hex(bs.tell()))

			print("	Face OFFSET: " + hex(bs.tell()))



			faceDataSize = bs.readUInt()
			faceOffset = bs.tell()
			print("	faceDataSize: " +  hex(faceDataSize) + "  " + str(faceDataSize) )
			
			bs.seek(faceDataSize, NOESEEK_REL)	

			print("	cur_offset11: " + hex(bs.tell()))
			




			
			unknown2DataSize = bs.readUInt()
			print("	unknown2DataSize: " +  hex(unknown2DataSize) + "  " + str(unknown2DataSize) )
			bs.seek(unknown2DataSize, NOESEEK_REL)	
			print("	cur_offset12: " + hex(bs.tell()))
			
			unknown3DataSize = bs.readUInt()
			print("	unknown3DataSize: " +  hex(unknown3DataSize) + "  " + str(unknown3DataSize) )
			bs.seek(unknown3DataSize, NOESEEK_REL)	
			print("	cur_offset13: " + hex(bs.tell()))

			unknown4DataSize = bs.readUInt()
			print("	unknown4DataSize: " +  hex(unknown4DataSize) + "  " + str(unknown4DataSize) )
			bs.seek(unknown4DataSize, NOESEEK_REL)	
			print("	cur_offset14: " + hex(bs.tell()))


			bs.seek(23, NOESEEK_REL)	
			print("	cur_offset15: " + hex(bs.tell()))


			meshCount = bs.readUInt()
			print("	meshCount: " +  hex(meshCount) + "  " + str(meshCount) )




			print("")
			totalVertices = 0;
			
			# str 776
			submeshes = []
			for i in range (0, meshCount):
				print("")
				print("	cur_offset16: " + hex(bs.tell()))
				msh = submeshObj()
				msh.id 	= bs.readUShort()
				msh.num = i
				print("	mesh: " + str(msh.num) + "	 id: "  + hex(msh.id) + "  " + str(msh.id) )
				bts = []
				for j in range(0,18):
					bts.append(hex(bs.readUByte()))
				#bs.seek(18, NOESEEK_REL)	
				msh.VertexCount = bs.readUInt()
				print("	VertexCount: " + hex(msh.VertexCount) + "  " + str(msh.VertexCount) )
				#bs.seek(4, NOESEEK_REL)
				for j in range(0,4):
					bts.append(hex(bs.readUByte()))
				print("	"+	str(bts))
				msh.FaceCount = bs.readUInt()
				print("	FaceCount: " + hex(msh.FaceCount) + "  " + str(msh.FaceCount) )
				msh.IndexCount = msh.FaceCount * 3;
				print("	IndexCount: " + hex(msh.IndexCount) + "  " + str(msh.IndexCount) )
				msh.MinFaceIndex = 65535
				msh.TextureIndex = bs.readUInt()
				print("	TextureIndex: " + hex(msh.TextureIndex) + "  " + str(msh.TextureIndex) )
				
				
				submeshes.append(msh)
			
			print("	cur_offset17: " + hex(bs.tell()))
			end_of_mesh_data = bs.tell()
			for mesh in submeshes:
				print("")
				print("		MESH num:" + str (mesh.num)) 
				#reader.BaseStream.Seek(vertexOffset + (submeshBlock.Entries[i].VertexOffset * actualVertexTableSize), SeekOrigin.Begin);

				real_vertex_offset = vertexOffset + Entries_VertexOffset[mesh.num] * actualVertexTableSize
				bs.seek(real_vertex_offset, NOESEEK_ABS)
				print("		go to vertex buffer, offset: " + hex(bs.tell()) + ", VertexCount: " + hex(mesh.VertexCount) + "  " + str(mesh.VertexCount) + ", actualVertexTableSize: " + hex(actualVertexTableSize) + "  " + str(actualVertexTableSize))
			
				for i in range (0, mesh.VertexCount):
					st_offset = bs.tell()
					x = float(bs.readShort())/65535 * 100
					y = float(bs.readShort())/65535 * 100
					z = float(bs.readShort())/65535 * 100
					u = 0
					v = 0
					scl = bs.readByte()
					scl2 = bs.readByte()
					
					if(	(actualVertexTableSize == 16) or
						(actualVertexTableSize == 20) or
						(actualVertexTableSize == 28) ):
						if (scl<0):
							x = -x
							y = -y
							z = -z

					scaleFactor = 1
				
					bs.seek(st_offset + actualVertexTableSize -4, NOESEEK_ABS)
					u = bs.readShort()
					v = bs.readShort()
					
					u = float(u) /65535 * 32
					v = float(v) /65535 * 32

					mesh.vertices.append(NoeVec3((y, z, x)))	
					mesh.uvs.append(NoeVec3((u, v, 0)))
					bs.seek(st_offset + actualVertexTableSize, NOESEEK_ABS)

				if (mesh.num > 0):
					mesh.NumOfVerticesBeforeMe = totalVertices;
					totalVertices = totalVertices + mesh.VertexCount;

				print("		NumOfVerticesBeforeMe " + hex(mesh.NumOfVerticesBeforeMe) + "  " + str(mesh.NumOfVerticesBeforeMe))
			
				#reader.BaseStream.Seek(faceOffset + (submeshBlock.Entries[i].FaceOffset * 2), SeekOrigin.Begin);


				real_face_offset = faceOffset + Entries_FaceOffset[mesh.num] * 2
				bs.seek(real_face_offset, NOESEEK_ABS)
				print("		go to face buffer, offset: " + hex(bs.tell()) + ", FaceCount: " + hex(mesh.FaceCount) + "  " + str(mesh.FaceCount) )
			
				for i in range (0, mesh.FaceCount):
					v1 = bs.readUShort() #+ mesh.NumOfVerticesBeforeMe
					v2 = bs.readUShort() #+ mesh.NumOfVerticesBeforeMe
					v3 = bs.readUShort() #+ mesh.NumOfVerticesBeforeMe
					if((v1 != v2) and (v2 != v3) and (v1 != v3)):
						if (v1 < mesh.MinFaceIndex):  mesh.MinFaceIndex = v1 
						if (v1 < mesh.MinFaceIndex):  mesh.MinFaceIndex = v2 
						if (v1 < mesh.MinFaceIndex):  mesh.MinFaceIndex = v3
						
						if (v1 > mesh.MaxFaceIndex):  mesh.MaxFaceIndex = v1
						if (v2 > mesh.MaxFaceIndex):  mesh.MaxFaceIndex = v2
						if (v3 > mesh.MaxFaceIndex):  mesh.MaxFaceIndex = v3
						
						#mesh.faces.append([v1, v2, v3])#extend
						mesh.faces.extend([v1, v2, v3])
				
				print("		MinFaceIndex: " + hex(mesh.MinFaceIndex) + "  " + str(mesh.MinFaceIndex) )
				print("		MaxFaceIndex: " + hex(mesh.MaxFaceIndex) + "  " + str(mesh.MaxFaceIndex) )		
				print("		len faces: " + hex(len(mesh.faces)) + "  " + hex(len(mesh.faces)) )		
				print("		len vertices: " + hex(len(mesh.vertices)) + "  " + str(len(mesh.vertices)) )		

				
				meshes2.append(mesh)
				#for i in range(0, mesh.VertexCount):

				#	Nmesh.uvs.append(mesh.uvs[i])
					

					
					
			ext_uv = []		
			ext_uv2 = []		
			if (extUV_offset > 0):
				bs.seek(extUV_offset, NOESEEK_ABS)
				print("		go to external UV buffer, offset: " + hex(bs.tell()))
				sumVertex = 0
				for msh in meshes2:
					sumVertex = sumVertex + (msh.VertexCount)
				ext_uv_buffer_size = bs.readUInt()
				ext_uv_size = ext_uv_buffer_size / sumVertex
				print("		ext_uv_buffer_size:" + hex(ext_uv_buffer_size) +"  "+ str(ext_uv_buffer_size)+ "	sumVertex: " + str(sumVertex) + " 	ext_uv_size" + str(ext_uv_size))
				
















				dd = 0
				for msh in meshes2:
					#if(dd == 1): ext_uv_size = 0
					#if(dd == 2): ext_uv_size = 16
					u = 0
					v = 0
					u2 = 0
					v2 = 0
					print("		submesh " + str(dd) +" UV buffer, offset: " + hex(bs.tell()))
					for i in range(0, msh.VertexCount):
						if(ext_uv_size > 15): #16
							st_offset = bs.tell()
							bs.seek(4, NOESEEK_REL)
							u = bs.readShort()
							v = bs.readShort()
							u = float(u) /65535 * 32
							v = 1 - float(v) /65535 * 32	
							u2 = bs.readShort()
							v2 = bs.readShort()
							u2 = float(u2) /65535 * 32
							v2 = 1 - float(v2) /65535 * 32					
							bs.seek(st_offset + 16, NOESEEK_ABS)

						elif(ext_uv_size > 0): #8
							st_offset = bs.tell()
							#bs.seek(0, NOESEEK_REL)
							u = bs.readShort()
							v = bs.readShort()
							u = float(u) /65535 * 32
							v = 1 - float(v) /65535 * 32	
							u2 = bs.readShort()
							v2 = bs.readShort()
							u2 = float(u2) /65535 * 32
							v2 = 1 - float(v2) /65535 * 32					
							bs.seek(st_offset + 8, NOESEEK_ABS)
						msh.uvs4.append(NoeVec3((u, v, 0)))
						msh.uvs3.append(NoeVec3((u2, v2, 0)))
					print("		submesh " + str(dd) +" UV buffer, end offset: " + hex(bs.tell()))
					dd = dd +1
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
			if (extUV_offset > 0):		
				ii = 0
				iname = 0
				for msh in meshes2:
					iname = iname +1
					#for i in range(0, msh.VertexCount):
					#	msh.uvs3.append(ext_uv[ii])
					#	msh.uvs4.append(ext_uv2[ii])
					#	ii = ii + 1
					
					
					Nmesh = 0
					#Nmesh2 = 0
					if(WatchUV):
						Nmesh = NoeMesh(msh.faces, msh.uvs3)
					else:
						Nmesh = NoeMesh(msh.faces, msh.vertices)
					#Nmesh2 = NoeMesh(msh.faces, msh.vertices)

					
					#for ii in range(0, msh.VertexCount):
					#	Nmesh.uvs.append(msh.uvs3[ii])
					#	Nmesh.lmUVs.append(msh.uvs4[ii])
					#	Nmesh2.uvs.append(msh.uvs[ii])
						
					Nmesh.name = FileName + "_" + str(dtest) +"_" + str(iname) + "_ExtUV"
					#Nmesh2.name = FileName + "_" + str(dtest)+ "_" + str(iname) + "_IntUV"
					Nmesh.setUVs(msh.uvs3, 0)
					Nmesh.setUVs(msh.uvs4, 1)
					Nmesh.setUVs(msh.uvs,  2)
					print("	external UV")
					#Nmesh.setUVs(msh.uvs2, 2)
					
					#mesh.setUVs(uvList)
					#mesh.setUVs(uvList2, 1)
					#mesh.setUVs(uvList3, 2)
					#mesh.setUVs(uvList4, 3)

					meshes.append( Nmesh)
					#meshes.append( Nmesh2)
			
			else:
				iname = 0

				for msh in meshes2:	
					iname = iname +1		
					Nmesh = 0
					if(WatchUV):
						Nmesh = NoeMesh(msh.faces, msh.uvs)
					else:
						Nmesh = NoeMesh(msh.faces, msh.vertices)
						
					#for ii in range(0, msh.VertexCount):
					#	Nmesh.uvs.append(msh.uvs[ii])
					#	Nmesh.lmUVs.append(msh.uvs2[ii])


					
					Nmesh.name = FileName + "_" + str(dtest) + "_" + str(iname) + "_IntUV"
					Nmesh.setUVs(msh.uvs)
					#Nmesh.setUVs(msh.uvs2, 1)
					print("	internal UV")
					meshes.append( Nmesh)
					
					
					
					
			bs.seek(end_of_mesh_data, NOESEEK_ABS)		
			print("	End of Model Data: " + hex(end_of_mesh_data))
			val2 = fnd4byte(bs, 4238218645)
			print("	next magic model offset: " + hex(val2))
		#except RuntimeError  :
		#	print("ERROR")
		#	val2 = -1
	
					
					
					
				
				

	
	if(len(meshes) > 0):		
		mdl = NoeModel(meshes)
		
		mdlList.append(mdl)
			
	
	print("")
	print("FINAL!!!")
	print("")
	print("")
	return 1
	
