static VMeshRef ParseMeshPartNode(UTFNode meshPartNode)
 {
     if (meshPartNode.Nodes.Count > 0)
     {
         return(new VMeshRef(meshPartNode.Nodes[0].Data));
     }
     return(null);
 }
        static void ParseNode(BinaryReader reader, string stringBlock, int nodeBlockStart, int nodeStart, int dataBlockOffset, UTFNode parent)
        {
            int offset = nodeBlockStart + nodeStart;

            reader.BaseStream.Position = offset;

            int       peerOffset = reader.ReadInt32(); // next node on same level
            int       nameOffset = reader.ReadInt32(); // string for this node
            NodeFlags flags      = (NodeFlags)reader.ReadInt32();

            //int zero = reader.ReadInt32();
            reader.BaseStream.Position += ByteLen.INT;

            UTFNode node = new UTFNode
            {
                Name = stringBlock.Substring(nameOffset, stringBlock.IndexOf('\0', nameOffset) - nameOffset)
            };

            if ((flags & NodeFlags.Intermediate) == NodeFlags.Intermediate)
            {
                int childOffset = reader.ReadInt32();
                if (childOffset > 0)
                {
                    ParseNode(reader, stringBlock, nodeBlockStart, childOffset, dataBlockOffset, node);
                }

                //int allocatedSize = reader.ReadInt32();
                //int size = reader.ReadInt32();
                //int size2 = reader.ReadInt32();
                //int timestamp1 = reader.ReadInt32();
                //int timestamp2 = reader.ReadInt32();
                //int timestamp3 = reader.ReadInt32();
            }
            else if ((flags & NodeFlags.Leaf) == NodeFlags.Leaf)
            {
                int dataOffset = reader.ReadInt32();

                //int allocatedSize = reader.ReadInt32();
                reader.BaseStream.Position += ByteLen.INT;

                int size = reader.ReadInt32();
                //int size2 = reader.ReadInt32();
                //int timestamp1 = reader.ReadInt32();
                //int timestamp2 = reader.ReadInt32();
                //int timestamp3 = reader.ReadInt32();

                reader.BaseStream.Position = dataBlockOffset + dataOffset;
                node.Data = reader.ReadBytes(size);
            }

            parent.Nodes.Add(node);

            if (peerOffset > 0)
            {
                ParseNode(reader, stringBlock, nodeBlockStart, peerOffset, dataBlockOffset, parent);
            }
        }
 static VMeshRef ParseMultiLevelNode(UTFNode node)
 {
     foreach (UTFNode levelNode in node.Nodes)
     {
         if (levelNode.Name.Equals("level0", StringComparison.OrdinalIgnoreCase) && levelNode.Nodes.Count > 0)
         {
             return(ParseMeshPartNode(levelNode.Nodes[0]));
         }
     }
     return(null);
 }
        public UTFNode Read()
        {
            using (FileStream stream = new FileStream(File, FileMode.Open, FileAccess.Read, FileShare.Read))
                using (BinaryReader reader = new BinaryReader(stream))
                {
                    if (stream.Length < ByteLen.FILE_TAG + ByteLen.INT ||
                        Encoding.ASCII.GetString(reader.ReadBytes(ByteLen.FILE_TAG)) != FILE_TYPE ||
                        reader.ReadInt32() != FILE_VERSION)
                    {
                        return(null);
                    }

                    // get node info
                    int nodeBlockOffset = reader.ReadInt32();
                    //int nodeBlockSize = reader.ReadInt32();

                    //int unknown1 = reader.ReadInt32();
                    //int header_size = reader.ReadInt32();
                    stream.Position += ByteLen.INT * 3;

                    // get string info
                    int stringBlockOffset = reader.ReadInt32();
                    int stringBlockSize   = reader.ReadInt32();

                    //int unknown2 = reader.ReadInt32();
                    stream.Position += ByteLen.INT;

                    // get data info
                    int dataBlockOffset = reader.ReadInt32();

                    // goto string block
                    stream.Position = stringBlockOffset;

                    // read string block
                    byte[] buffer = new byte[stringBlockSize];
                    reader.Read(buffer, 0, stringBlockSize);
                    string stringBlock = Encoding.ASCII.GetString(buffer);

                    UTFNode info = new UTFNode();
                    ParseNode(reader, stringBlock, nodeBlockOffset, 0, dataBlockOffset, info);
                    return(info);
                }
        }
        public static Model3D LoadModel(string file)
        {
            UTFManager utfManager = new UTFManager(file);
            UTFNode    root       = utfManager.Read();

            if (root == null || root.Nodes.Count == 0)
            {
                return(null);
            }

            // select root (\) node
            root = root.Nodes[0];

            Dictionary <uint, VMeshData> meshes             = null;
            List <MeshReferenceMatch>    meshReferenceNodes = new List <MeshReferenceMatch>();
            List <CmpPart> constructs = new List <CmpPart>();
            Dictionary <string, string> mapFileToObj = new Dictionary <string, string>
            {
                { "\\", "Model" }
            };

            foreach (UTFNode node in root.Nodes)
            {
                switch (node.Name.ToLowerInvariant())
                {
                case "vmeshlibrary":
                    meshes = new Dictionary <uint, VMeshData>(node.Nodes.Count);
                    foreach (UTFNode vmsNode in node.Nodes)
                    {
                        if (vmsNode.Nodes.Count > 0)
                        {
                            meshes.Add(CrcTool.FlModelCrc(vmsNode.Name), new VMeshData(vmsNode.Nodes[0].Data));
                        }
                    }
                    break;

                case "cmpnd":
                    foreach (UTFNode cmpndNode in node.Nodes)
                    {
                        if (cmpndNode.Name.Equals("cons", StringComparison.OrdinalIgnoreCase))
                        {
                            foreach (UTFNode constructNode in cmpndNode.Nodes)
                            {
                                switch (constructNode.Name.ToLowerInvariant())
                                {
                                case "fix":
                                    FixConstruct.Parse(constructs, constructNode.Data);
                                    break;

                                case "rev":
                                    RevConstruct.Parse(constructs, constructNode.Data);
                                    break;

                                case "pris":
                                    PrisConstruct.Parse(constructs, constructNode.Data);
                                    break;

                                case "sphere":
                                    SphereConstruct.Parse(constructs, constructNode.Data);
                                    break;
                                }
                            }
                        }
                        else if (cmpndNode.Name.StartsWith("part_", StringComparison.OrdinalIgnoreCase) ||
                                 cmpndNode.Name.Equals("root", StringComparison.OrdinalIgnoreCase))
                        {
                            string objectName = null;
                            string fileName   = null;
                            //int index = -1;

                            foreach (UTFNode partNode in cmpndNode.Nodes)
                            {
                                switch (partNode.Name.ToLowerInvariant())
                                {
                                case "object name":
                                    objectName = Encoding.ASCII.GetString(partNode.Data).TrimEnd('\0');
                                    break;

                                case "file name":
                                    fileName = Encoding.ASCII.GetString(partNode.Data).TrimEnd('\0');
                                    break;
                                    //case "index":
                                    //    index = BitConverter.ToInt32(partNode.Data, 0);
                                    //    break;
                                }
                            }
                            if (objectName != null && fileName != null)
                            {
                                mapFileToObj[fileName] = objectName;
                            }
                        }
                    }
                    break;

                case "multilevel":
                    // multi LoD 3db model (\MultiLevel\Level0\VMeshPart\VMeshRef => \)
                    VMeshRef meshReference2 = ParseMultiLevelNode(node);
                    if (meshReference2 != null)
                    {
                        meshReferenceNodes.Add(new MeshReferenceMatch {
                            FileName = "\\", MeshReference = meshReference2
                        });
                    }
                    break;

                case "vmeshpart":
                    // single LoD 3db model (\VMeshPart\VMeshRef => \)
                    VMeshRef meshReference3 = ParseMeshPartNode(node);
                    if (meshReference3 != null)
                    {
                        meshReferenceNodes.Add(new MeshReferenceMatch {
                            FileName = "\\", MeshReference = meshReference3
                        });
                    }
                    break;

                default:
                    if (node.Name.EndsWith(".3db", StringComparison.OrdinalIgnoreCase))
                    {
                        foreach (UTFNode subNode in node.Nodes)
                        {
                            if (subNode.Name.Equals("multilevel", StringComparison.OrdinalIgnoreCase))
                            {
                                // multi LoD cmp model (\PARTNAME.3db\MultiLevel\Level0\VMeshPart\VMeshRef => PARTNAME.3db)
                                VMeshRef meshReference = ParseMultiLevelNode(subNode);
                                if (meshReference != null)
                                {
                                    meshReferenceNodes.Add(new MeshReferenceMatch {
                                        FileName = node.Name, MeshReference = meshReference
                                    });
                                }
                                break;
                            }
                            if (subNode.Name.Equals("vmeshpart", StringComparison.OrdinalIgnoreCase))
                            {
                                // single LoD cmp model (\PARTNAME.3db\VMeshPart\VMeshRef => PARTNAME.3db)
                                VMeshRef meshReference = ParseMeshPartNode(subNode);
                                if (meshReference != null)
                                {
                                    meshReferenceNodes.Add(new MeshReferenceMatch {
                                        FileName = node.Name, MeshReference = meshReference
                                    });
                                }
                                break;
                            }
                        }
                    }
                    break;
                }
            }

            if (meshes == null || meshReferenceNodes.Count == 0)
            {
                return(null);
            }

            List <MeshGroup> meshGroups = new List <MeshGroup>();

            foreach (MeshReferenceMatch meshReferenceNode in meshReferenceNodes)
            {
                string meshGroupName;
                if (mapFileToObj.TryGetValue(meshReferenceNode.FileName, out meshGroupName))
                {
                    VMeshData mesh;
                    if (meshes.TryGetValue(meshReferenceNode.MeshReference.VMeshLibId, out mesh))
                    {
                        meshGroups.Add(new MeshGroup
                        {
                            MeshReference = meshReferenceNode.MeshReference,
                            Mesh          = mesh,
                            Transform     = GetTransform(constructs, meshGroupName)
                        });
                    }
                }
            }

            return(GetCmpModelGroup(meshGroups));
        }