#Script Version 1.01
#Dead or Alive 5 LR PC

from inc_noesis import *
import time
import noesis
import array
import struct

#rapi methods should only be used during handler callbacks
import rapi

texonly = 0         #load only the textures
warn    = 0         #print logs

#registerNoesisTypes is called by Noesis to allow the script to register formats.
#Do not implement this function in script files unless you want them to be dedicated format modules!
def registerNoesisTypes():
    handle = noesis.register("Dead Or Alive 5 LR PC", ".tmc")
    noesis.setHandlerTypeCheck(handle, tmcCheckType)
    noesis.setHandlerLoadModel(handle, tmcLoadModel)
    if warn:
        noesis.logPopup()
    return 1

#check if it's this type based on the data
def tmcCheckType(data):
    bs = NoeBitStream(data)
    if bs.readBytes(3) != b"TMC":
        return 0
    return 1

class tmchead():
    BlockName = ''
    Count1 = Count2 = Offset1 = Offset2 = Offset3 = base = 0
    offsets = []
    def __init__(self, base, bytestream):
        bytestream.seek(base)
        self.BlockName = bytestream.readBytes(8).decode("ASCII").rstrip("\0")
        self.Flg1, self.HSize, self.Size, self.Count1, self.Count2, self.Count3, self.Offset1, self.Offset2, self.Offset3, self.Flg2 = bytestream.read("<10L")
        self.base = base
        bytestream.seek(base+self.Offset1)
        self.offsets = [bytestream.readUInt() for i in range(self.Count1)]

#load the model
def tmcLoadModel(data, mdlList):
    starttime = time.time()
    ctx = rapi.rpgCreateContext()
    bs = NoeBitStream(data)
    bs.setEndian(NOE_LITTLEENDIAN)

    #prepare the l file
    lname = rapi.getInputName()
    lname = lname[:-3] + {'TMC':'TMCL', 'GMD':'TTGL'}[lname[-3:].upper()]
    if not rapi.checkFileExists(lname):
        print("file not found:", lname)
        return 0
    tmclData = NoeBitStream(rapi.loadIntoByteArray(lname))
    tmclData.setEndian(NOE_LITTLEENDIAN)

    tmch     = tmchead(0, bs)#0MdlGeo,1TTX,2VtxLay,3IdxLay,4MtrCol,5MdlInfo, ! 6HieLay,7LHeader,8NodeLay,9GlblMtx,10BnOfsMtx, !! 11cpf,12MCAPACK,13RENPACK
    mdlgeoh, ttdm, vtxlayh, idxlayh, mtrcolh, mdlinfoh, hielayh, lheaderh, nodelayh, glbmtxh  = [tmchead(tmch.offsets[i], bs) for i in range(10)]           

    if warn:print('l ok')
    
    #load textures
    texListM = []
    texNameListM = []
    if ttdm.Count1: #parse TTDM
        bs.seek(ttdm.base + ttdm.Offset2)
        txszs = [bs.readUInt() for i in range(ttdm.Count2)]
        for i in range(ttdm.Count1):
            texName = 'M_' + str(i) + '.dds'
            bs.seek(ttdm.base + ttdm.offsets[i])
            data = bs.readBytes(txszs[i])
            Height = int.from_bytes(data[12:16], byteorder='little')
            Width = int.from_bytes(data[16:20], byteorder='little')
            if   data[87] == 49: texFmt = noesis.NOESISTEX_DXT1
            elif data[87] == 51: texFmt = noesis.NOESISTEX_DXT3
            elif data[87] == 53: texFmt = noesis.NOESISTEX_DXT5
            data = data[128:]
            texListM.append(NoeTexture(texName, Width, Height, data, texFmt))
            texNameListM.append(texName)
    texListL = []
    texNameListL = []
    ttdlh = tmchead(ttdm.base + ttdm.Offset3, bs) #parse TTDL
    if ttdlh.Count1:
        bs.seek(ttdlh.base + ttdlh.Offset2)
        txszs = [bs.readUInt() for i in range(ttdlh.Count1)]
        for i in range(ttdlh.Count1):
            texName = 'L_' + str(i) + '.dds'
            tmclData.seek(0x80 + ttdlh.offsets[i])
            data = tmclData.readBytes(txszs[i])
            Height = int.from_bytes(data[12:16], byteorder='little')
            Width = int.from_bytes(data[16:20], byteorder='little')
            if   data[87] == 49: texFmt = noesis.NOESISTEX_DXT1
            elif data[87] == 51: texFmt = noesis.NOESISTEX_DXT3
            elif data[87] == 53: texFmt = noesis.NOESISTEX_DXT5
            data = data[128:]
            texListL.append(NoeTexture(texName, Width, Height, data, texFmt))
            texNameListL.append(texName)            
    texList = []
    texNameList = []
    ttdhh = tmchead(ttdm.base + ttdm.HSize, bs) #parse TTDH
    for offs in ttdhh.offsets:
        bs.seek(ttdhh.base + offs)
        texFlg, texId = (bs.readUInt(), bs.readUInt())
        if texFlg: 
            texList.append(texListL[texId])
            texNameList.append(texNameListL[texId])
        else: 
            texList.append(texListM[texId])
            texNameList.append(texNameListM[texId])
            
    if warn:print('tx ok')

    if texonly:
        mdl = NoeModel([])
        mdl.setModelMaterials(NoeModelMaterials(texList, []))
        mdlList.append(mdl)
        rapi.rpgClearBufferBinds()
        return 1

    #parse matrices
    #parse GlblMtx
    GlblMtxs = []
    for offs in glbmtxh.offsets:
        bs.seek(glbmtxh.base + offs)
        gmatrix = NoeMat44.fromBytes(bs.readBytes(0x40))
        GlblMtxs.append(gmatrix)
    #parse NodeLay
    mtx44 = NoeMat44()
    NodeLays_meshnodes = {}
    for offidx ,offs in enumerate(nodelayh.offsets):
        nodeobjh = tmchead(nodelayh.base + offs, bs)
        bs.seek(nodeobjh.base + 0x40)
        nodename = bs.readString()
        for mtxoffs in nodeobjh.offsets:
            bs.seek(nodeobjh.base + mtxoffs)
            objectindex = bs.readUInt()
            objmatrix = (None if GlblMtxs[offidx] == mtx44 else GlblMtxs[offidx])
            NodeLays_meshnodes[objectindex] = (nodename, GlblMtxs[offidx].toMat43())

    #parse mesh data
    matList = []
    #mdlinfo
    mdlinfos = {}
    for objidx, objoffs in enumerate(mdlinfoh.offsets):#get the transparency from those
        if objoffs == 0: continue
        objh  = tmchead(mdlinfoh.base + objoffs, bs)
        objinfos = {}
        for matoffset in objh.offsets:#parse mats
            if matoffset == 0: continue
            bs.seek(objh.base + matoffset)
            matindex, unkflags0, Unk0, fUnk0, v0_1, v00_1, v000_1 = bs.read("<3Lf3L")
            objinfos[matindex] = [unkflags0, Unk0, fUnk0, v0_1, v00_1, v000_1]
        mdlinfos[objidx] = objinfos
    #mdlgeo
    for objidx, objoffs in enumerate(mdlgeoh.offsets):
        if objoffs == 0: continue #fix to support mods
        objh  = tmchead(mdlgeoh.base + objoffs, bs)
        declh = tmchead(mdlgeoh.base + objoffs + objh.Offset3, bs)
        fixmatrix = None
        if objidx in NodeLays_meshnodes:
            objname, fixmatrix = NodeLays_meshnodes[objidx]
        else:
            bs.seek(objh.base + 0x50)
            objname = bs.readString()
        if warn:print(objname)
        if 'sweat' in objname or "zdmodel" in objname or "OPTblur" in objname or "_after_" in objname:
            continue
        decls = []
        for decloffset in declh.offsets:#parse decls
            bs.seek(declh.base + decloffset + 0xC)
            IDXBUFindex, IDXcount, VERTcount, unk0, unk1, VTXBUFindex, vsize, vdatalayerscount =  bs.read("<5L16xL2L4x")#this reach the 0x40 offset           
            vdatalayers = [(bs.readUInt(), bs.readUInt()) for i in range(vdatalayerscount)]#vdatalayers[2, 30002, 1000D, 20005, 60003, 5000B, 105000B]            
            decls.append([IDXBUFindex,vsize,vdatalayers, IDXcount, VERTcount, unk0, unk1, VTXBUFindex]) #vdatalayeroffset, vd3dtype, vusage+vusagelayer

        declmats = [[] for i in range(len(decls))]
        for matoffset in objh.offsets:#parse mats
            if matoffset == 0: continue #fix to support mods
            bs.seek(objh.base + matoffset)
            matindex, mtrcolindex, matTexCount, declfvfindex, transparentmat,v0_ff,transparentmasc,Pad2,fUnk0,fUnk1,Pad3,Pad4, v0_1,v3_4_5,v1_2, twosided, mIDXstart, mIDXcount, mVERTstart, mVERTcount = bs.read("<2L4xL40xL4xL3L2f2L4L4L")
            texbase = objh.base + matoffset + 0xD0 #textures info in mat

            txrs = []
            for texinfo in range(matTexCount):#parse material textures
                bs.seek(texbase + texinfo * 0x70)
                texSlot, texmaptype, texId = (bs.readUInt(), bs.readUInt(), bs.readUInt())
                txrs.append((texSlot, texmaptype, texId))

            declmats[declfvfindex].append((matindex, mtrcolindex, transparentmat, v0_ff,transparentmasc,Pad2,fUnk0,fUnk1,Pad3,Pad4, v0_1,v3_4_5,v1_2, twosided, mIDXstart,mIDXcount, mVERTstart, mVERTcount,  txrs))

        #parse raw data
        rapi.rpgSetTransform(fixmatrix)
        for enumdecl, (IDXBUFindex,vsize,vdatalayers, IDXcount, VERTcount, unk0, unk1, VTXBUFindex) in enumerate(decls): #??unk1 is tritype??

            fvfdata = {fvf[1]:(fvf[0]//0x10000) for fvf in vdatalayers}
            bs.seek(vtxlayh.base + vtxlayh.offsets[VTXBUFindex])
            BUFdata = bs.readBytes(vsize * VERTcount)
            if 2 in fvfdata:         rapi.rpgBindPositionBufferOfs(BUFdata, noesis.RPGEODATA_FLOAT, vsize, fvfdata[2])
            elif 3 in fvfdata:       rapi.rpgBindPositionBufferOfs(BUFdata, noesis.RPGEODATA_FLOAT, vsize, fvfdata[3])
            else:                    rapi.rpgBindPositionBuffer(None, noesis.RPGEODATA_FLOAT, vsize)
            if 0x5000B in fvfdata:   rapi.rpgBindUV1BufferOfs(BUFdata, noesis.RPGEODATA_HALFFLOAT, vsize, fvfdata[0x5000B])
            elif 0x5000A in fvfdata: rapi.rpgBindUV1BufferOfs(BUFdata, noesis.RPGEODATA_HALFFLOAT, vsize, fvfdata[0x5000A]) 
            else:                    rapi.rpgBindUV1Buffer(None, noesis.RPGEODATA_HALFFLOAT, vsize)
            if 0x30002 in fvfdata:   rapi.rpgBindNormalBufferOfs(BUFdata, noesis.RPGEODATA_FLOAT, vsize, fvfdata[0x30002])
            else:                    rapi.rpgBindNormalBuffer(None, noesis.RPGEODATA_HALFFLOAT, vsize)

            for matindex, mtrcolindex, transparentmat, v0_ff,transparentmasc,Pad2,fUnk0,fUnk1,Pad3,Pad4, v0_1,v3_4_5,v1_2, twosided, mIDXstart,mIDXcount, mVERTstart, mVERTcount, txrs in declmats[enumdecl]:

                #unkflags0, Unk0, fUnk0, v0_1i, v00_1, v000_1 = mdlinfos[objidx][matindex]

                meshname = objname + ("_%x"%matindex if matindex else "")
                matname = objname + "_mat%x"%matindex
                rapi.rpgSetName(meshname)
                material = NoeMaterial(matname, "")
                if twosided:
                    material.setFlags(noesis.NMATFLAG_TWOSIDED, 0)
                for texindex, (texSlot, texmaptype, texId) in enumerate(txrs):
                    if texSlot == 0:
                        material.setTexture(texNameList[texId])
                        if not (transparentmat or transparentmasc):
                            material.setDiffuseColor(NoeVec4([1.0, 1.0, 1.0, 0.0]))
                            material.setDefaultBlend(0)
                    elif texSlot == 1:
                        if texmaptype == 0:
                            pass
                    elif texSlot == 2:
                        if texmaptype == 1:
                            material.setNormalTexture(texNameList[texId])
                    elif texSlot == 3:
                        material.setNormalTexture("")

                matList.append(material)
                #read mat indices
                bs.seek(idxlayh.base + idxlayh.offsets[IDXBUFindex] + mIDXstart*2)
                idata = bs.readBytes(mIDXcount*2)
                rapi.rpgSetMaterial(matname)
                rapi.rpgCommitTriangles(idata, noesis.RPGEODATA_USHORT, mIDXcount, noesis.RPGEO_TRIANGLE_STRIP, 1)

    mdl = rapi.rpgConstructModel()
    mdl.setModelMaterials(NoeModelMaterials(texList, matList))
    mdlList.append(mdl)
    rapi.rpgClearBufferBinds()

    return 1