private static List <int> BeginReadMGX(BufferedStreamReader streamReader)
        {
            string type   = Encoding.UTF8.GetString(BitConverter.GetBytes(streamReader.Peek <int>()));
            int    offset = 0x20; //Base offset due to NIFL header

            //Deal with deicer's extra header nonsense
            if (type.Equals("mgx\0"))
            {
                streamReader.Seek(0xC, SeekOrigin.Begin);
                //Basically always 0x60, but some deicer files from the Alpha have 0x50...
                int headJunkSize = streamReader.Read <int>();

                streamReader.Seek(headJunkSize - 0x10, SeekOrigin.Current);
                type    = Encoding.UTF8.GetString(BitConverter.GetBytes(streamReader.Peek <int>()));
                offset += headJunkSize;
            }

            //Proceed based on file variant
            if (type.Equals("NIFL"))
            {
                //There shouldn't be a nifl variant of this for now.
                MessageBox.Show("Error, NIFL .mgx found");
                return(null);
            }
            else if (type.Equals("VTBF"))
            {
                return(ReadVTBFMGX(streamReader));
            }
            else
            {
                MessageBox.Show("Improper File Format!");
                return(null);
            }
        }
        private static LobbyActionCommon BeginReadRebootLAC(BufferedStreamReader streamReader)
        {
            string type   = Encoding.UTF8.GetString(BitConverter.GetBytes(streamReader.Peek <int>()));
            int    offset = 0x20; //Base offset due to NIFL header

            //Deal with deicer's extra header nonsense
            if (type.Equals("lac\0"))
            {
                streamReader.Seek(0xC, SeekOrigin.Begin);
                //Basically always 0x60, but some deicer files from the Alpha have 0x50...
                int headJunkSize = streamReader.Read <int>();

                streamReader.Seek(headJunkSize - 0x10, SeekOrigin.Current);
                type    = Encoding.UTF8.GetString(BitConverter.GetBytes(streamReader.Peek <int>()));
                offset += headJunkSize;
            }

            //Proceed based on file variant
            if (type.Equals("NIFL"))
            {
                //NIFL
                return(ReadNIFLRebootLAC(streamReader, offset));
            }
            else if (type.Equals("VTBF"))
            {
                //Lacs should really never be VTBF...
            }
            else
            {
                MessageBox.Show("Improper File Format!");
            }

            return(null);
        }
Beispiel #3
0
        public static List <stamData> ReadLM(this BufferedStreamReader streamReader)
        {
            var stamList = new List <stamData>();
            var lmStart  = streamReader.Position();

            streamReader.Read <int>();
            var lmEnd = streamReader.Read <int>() + lmStart;

            streamReader.Seek(0x8, SeekOrigin.Current);
            while (streamReader.Position() < lmEnd)
            {
                var tag = streamReader.Peek <int>();
                switch (tag)
                {
                case stam:
                    stamList.Add(streamReader.ReadStam());
                    break;

                default:
                    streamReader.SkipBasicAXSStruct();
                    break;
                }

                //Make sure to stop the loop if needed
                if (stamList[stamList.Count - 1].lastStam == true)
                {
                    break;
                }
            }

            streamReader.Seek(lmEnd, SeekOrigin.Begin);

            return(stamList);
        }
Beispiel #4
0
        private void FromStream(Stream stream)
        {
            // Read in the DLL or EXE and get the timestamp.
            Stream      = new BufferedStreamReader(stream, 4096);
            StreamStart = stream.Position;

            Stream.Read <IMAGE_DOS_HEADER>(out _dosHeader);
            Stream.Seek(_dosHeader.e_lfanew, SeekOrigin.Begin);
            Stream.Seek(sizeof(uint), SeekOrigin.Current); // NT Header Signature

            Stream.Read <IMAGE_FILE_HEADER>(out _fileHeader);
            bool isPe32 = Stream.Peek <ushort>() == 0x10B;

            if (isPe32)
            {
                Stream.Read <IMAGE_OPTIONAL_HEADER32>(out _optionalHeader32);
                PopulateDataDirectories(Stream, _optionalHeader32.NumberOfRvaAndSizes);
            }
            else
            {
                Stream.Read <IMAGE_OPTIONAL_HEADER64>(out _optionalHeader64);
                PopulateDataDirectories(Stream, _optionalHeader64.NumberOfRvaAndSizes);
            }

            _imageSectionHeaders = new IMAGE_SECTION_HEADER[FileHeader.NumberOfSections];
            for (int x = 0; x < ImageSectionHeaders.Length; ++x)
            {
                Stream.Read <IMAGE_SECTION_HEADER>(out ImageSectionHeaders[x], true);
            }

            PopulateImportDescriptors(Stream);
        }
Beispiel #5
0
        public static bool TryGuess(Stream stream, int streamLength)
        {
            var data = new BufferedStreamReader(stream, 4096);
            var pos  = stream.Position;

            try
            {
                var initialPos = data.Position();

                // Total Groups
                data.Read(out int binCount);

                // Safeguard against unlikely big files.
                if (binCount is > short.MaxValue or < 1)
                {
                    return(false);
                }

                // Total Items
                Span <byte> groups = stackalloc byte[binCount];
                for (int x = 0; x < binCount; x++)
                {
                    groups[x] = data.Read <byte>();
                }

                // Alignment
                data.Seek(Utilities.RoundUp((int)data.Position(), 4) - data.Position(), SeekOrigin.Current);

                // Now compare against total running file count.
                int currentCount  = 0;
                int expectedCount = 0;

                for (int x = 0; x < binCount; x++)
                {
                    expectedCount = data.Read <short>();
                    if (currentCount != expectedCount)
                    {
                        return(false);
                    }

                    currentCount += groups[x];
                }

                // Skip group ids.
                data.Seek(sizeof(short) * binCount, SeekOrigin.Current);

                // Check offsets.
                var firstFileOffset = data.Peek <int>();

                if (streamLength != -1 && firstFileOffset > streamLength)
                {
                    return(false);
                }

                // Seek to expected first file position.
                data.Seek(sizeof(int) * currentCount, SeekOrigin.Current);
                data.Seek(Utilities.RoundUp((int)data.Position(), 16), SeekOrigin.Begin); // Alignment

                // Try checking if first file is past expected header size, or empty 0.
                var currentOffset = data.Position() - initialPos;
                return(firstFileOffset == 0 || firstFileOffset >= (currentOffset));
            }
            finally
            {
                stream.Position = pos;
            }
        }
Beispiel #6
0
        private void ReadPartsCMX()
        {
            parts = new PartsCMX();
            string filePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\cmx\" + cmxFilename + @"_ext\parts.cmx";

            using (Stream stream = (Stream) new FileStream(filePath, FileMode.Open))
                using (var streamReader = new BufferedStreamReader(stream, 8192))
                {
                    int type = streamReader.Peek <int>();
                    //Deal with deicer's extra header nonsense
                    if (type.Equals(0x786D63))
                    {
                        streamReader.Seek(0xC, SeekOrigin.Begin);
                        //Basically always 0x60, but some deicer files from the Alpha have 0x50...
                        int headJunkSize = streamReader.Read <int>();

                        streamReader.Seek(headJunkSize - 0x10, SeekOrigin.Current);
                        type = streamReader.Peek <int>();
                    }

                    //Seek to and read the DOC tag's
                    streamReader.Seek(0x1C, SeekOrigin.Current);
                    int cmxCount = streamReader.Read <ushort>();
                    streamReader.Seek(0x2, SeekOrigin.Current);
                    //Read tags, get id from subtag 0xFF, and assign to dictionary with that.
                    for (int i = 0; i < cmxCount; i++)
                    {
                        List <Dictionary <int, object> > data = ReadVTBFTag(streamReader, out string tagType, out int entryCount);
                        switch (tagType)
                        {
                        case "BODY":
                            parts.bodyTags.Add((int)data[0][0xFF], data);
                            break;

                        case "CARM":
                            parts.carmTags.Add((int)data[0][0xFF], data);
                            break;

                        case "CLEG":
                            parts.clegTags.Add((int)data[0][0xFF], data);
                            break;

                        case "BDP1":
                            parts.bdp1Tags.Add((int)data[0][0xFF], data);
                            break;

                        case "BDP2":
                            parts.bdp2Tags.Add((int)data[0][0xFF], data);
                            break;

                        case "FACE":
                            parts.faceTags.Add((int)data[0][0xFF], data);
                            break;

                        case "FCMN":
                            parts.fcmnTags.Add((int)data[0][0xFF], data);
                            break;

                        case "FCP1":
                            parts.fcp1Tags.Add((int)data[0][0xFF], data);
                            break;

                        case "FCP2":
                            parts.fcp2Tags.Add((int)data[0][0xFF], data);
                            break;

                        case "EYE ":
                            parts.eyeTags.Add((int)data[0][0xFF], data);
                            break;

                        case "EYEB":
                            parts.eyeBTags.Add((int)data[0][0xFF], data);
                            break;

                        case "EYEL":
                            parts.eyeLTags.Add((int)data[0][0xFF], data);
                            break;

                        case "HAIR":
                            parts.hairTags.Add((int)data[0][0xFF], data);
                            break;

                        case "COL ":
                            parts.colTags.Add((int)data[0][0xFF], data);
                            break;

                        case "BBLY":
                            parts.bblyTags.Add((int)data[0][0xFF], data);
                            break;

                        case "BCLN":
                            parts.bclnTags.Add((int)data[0][0xFF], data);
                            break;

                        case "LCLN":
                            parts.lclnTags.Add((int)data[0][0xFF], data);
                            break;

                        case "ACLN":
                            parts.aclnTags.Add((int)data[0][0xFF], data);
                            break;

                        case "ICLN":
                            parts.iclnTags.Add((int)data[0][0xFF], data);
                            break;

                        default:
                            throw new Exception($"Unexpected tag type {tagType}");
                            break;
                        }
                    }
                }
        }
Beispiel #7
0
        public static void ReadBM(this BufferedStreamReader streamReader, List <MeshDefinitions> defs, ipnbStruct tempLpnbList, List <stamData> stamList, long last__oaPos)
        {
            int             counter = 0;
            MeshDefinitions mesh    = null;
            var             bmStart = streamReader.Position();

            streamReader.Read <int>();
            var bmEnd = streamReader.Read <int>() + bmStart;

            streamReader.Seek(0x8, SeekOrigin.Current);
            while (streamReader.Position() < bmEnd)
            {
                var tag = streamReader.Peek <int>();
                switch (tag)
                {
                case ydbm:
                    if (mesh != null)
                    {
                        Debug.WriteLine(defs.Count);
                        defs.Add(mesh);
                    }
                    mesh         = new MeshDefinitions();
                    mesh.oaPos   = last__oaPos;
                    mesh.lpnbStr = tempLpnbList;
                    mesh.ydbmStr = streamReader.ReadYdbm();
                    if (stamList.Count > counter)
                    {
                        mesh.stam = stamList[counter];
                    }
                    else if (stamList.Count > 0)
                    {
                        mesh.stam = stamList[stamList.Count - 1];
                    }
                    else
                    {
                        mesh.stam = null;
                    }
                    counter++;
                    break;

                case lxdi:
                    mesh.lxdiStr = streamReader.ReadLxdi();
                    break;

                case salv:
                    mesh.salvStr = streamReader.ReadSalv();
                    mesh.vtxe    = GenerateGenericPSO2VTXE(mesh.salvStr.vertDef0, mesh.salvStr.vertDef1, mesh.salvStr.vertDef2, mesh.salvStr.vertDef3, mesh.salvStr.vertDef4, mesh.salvStr.vertLen);
                    break;

                case ipnb:
                    mesh.ipnbStr = streamReader.ReadIpnb();
                    break;

                default:
                    streamReader.SkipBasicAXSStruct();
                    break;
                }
            }
            if (mesh != null)
            {
                Debug.WriteLine(defs.Count);
                defs.Add(mesh);
            }
        }
Beispiel #8
0
        //We're not realistically going to fully convert everything, but we can get vertex data and bones if nothing else
        //Returns an aqp ready for the ConvertToNGSPSO2Mesh method
        public static AquaObject ReadAXS(string filePath, out AquaNode aqn)
        {
            AquaObject aqp = new NGSAquaObject();

            aqn = new AquaNode();

            using (Stream stream = (Stream) new FileStream(filePath, FileMode.Open))
                using (var streamReader = new BufferedStreamReader(stream, 8192))
                {
                    Debug.WriteLine(Path.GetFileName(filePath));
                    long                            last__oaPos      = 0;
                    eertStruct                      eertNodes        = null;
                    ipnbStruct                      tempLpnbList     = null;
                    List <ffubStruct>               ffubList         = new List <ffubStruct>();
                    List <XgmiStruct>               xgmiList         = new List <XgmiStruct>();
                    List <string>                   texNames         = new List <string>();
                    List <MeshDefinitions>          meshDefList      = new List <MeshDefinitions>();
                    List <stamData>                 stamList         = new List <stamData>();
                    Dictionary <string, rddaStruct> rddaList         = new Dictionary <string, rddaStruct>();
                    Dictionary <string, rddaStruct> imgRddaList      = new Dictionary <string, rddaStruct>();
                    Dictionary <string, rddaStruct> vertRddaList     = new Dictionary <string, rddaStruct>();
                    Dictionary <string, rddaStruct> faceRddaList     = new Dictionary <string, rddaStruct>();
                    Dictionary <string, int>        xgmiIdByCombined = new Dictionary <string, int>();
                    Dictionary <string, int>        xgmiIdByUnique   = new Dictionary <string, int>();
                    ffubStruct                      imgFfub          = new ffubStruct();
                    ffubStruct                      vertFfub         = new ffubStruct();
                    ffubStruct                      faceFfub         = new ffubStruct();

                    var fType = streamReader.Read <int>();

                    var fsaLen = streamReader.Read <int>();
                    streamReader.Seek(0x8, SeekOrigin.Current);
                    //Go to Vert definition, node, material, and misc data
                    while (streamReader.Position() < fsaLen)
                    {
                        var tag  = streamReader.Peek <int>();
                        var test = Encoding.UTF8.GetString(BitConverter.GetBytes(tag));
                        Debug.WriteLine(streamReader.Position().ToString("X"));
                        Debug.WriteLine(test);
                        switch (tag)
                        {
                        case __oa:
                            last__oaPos = streamReader.Position();
                            streamReader.Seek(0xD0, SeekOrigin.Current);
                            break;

                        case FIA:
                            streamReader.Seek(0x10, SeekOrigin.Current);
                            break;

                        case __lm:
                            var stam = streamReader.ReadLM();
                            if (stam != null && stam.Count > 0)
                            {
                                stamList = stam;
                            }
                            break;

                        case __bm:
                            streamReader.ReadBM(meshDefList, tempLpnbList, stamList, last__oaPos);
                            break;

                        case lpnb:
                            tempLpnbList = streamReader.ReadIpnb();
                            break;

                        case eert:
                            eertNodes = streamReader.ReadEert();
                            break;

                        //case ssem:
                        //  streamReader.SkipBasicAXSStruct(); //Maybe use for material data later. Remember to store ordered id for _bm mesh entries for this
                        // break;
                        case Xgmi:
                            var xgmiData = streamReader.ReadXgmi();
                            if (!xgmiIdByCombined.ContainsKey(xgmiData.stamCombinedId))
                            {
                                xgmiIdByCombined.Add(xgmiData.stamCombinedId, xgmiList.Count);
                            }
                            xgmiIdByUnique.Add(xgmiData.stamUniqueId, xgmiList.Count);
                            xgmiList.Add(xgmiData);
                            break;

                        default:
                            streamReader.SkipBasicAXSStruct();
                            break;
                        }
                    }

                    //Assemble aqn from eert
                    if (eertNodes != null)
                    {
                        for (int i = 0; i < eertNodes.boneCount; i++)
                        {
                            var       rttaNode = eertNodes.rttaList[i];
                            Matrix4x4 mat      = Matrix4x4.Identity;

                            mat *= Matrix4x4.CreateScale(rttaNode.scale);

                            var rotation = Matrix4x4.CreateFromQuaternion(rttaNode.quatRot);

                            mat *= rotation;

                            mat *= Matrix4x4.CreateTranslation(rttaNode.pos);

                            var parentId = rttaNode.parentNodeId;

                            //If there's a parent, multiply by it
                            if (i != 0)
                            {
                                if (rttaNode.parentNodeId == -1)
                                {
                                    parentId = 0;
                                }
                                var pn           = aqn.nodeList[parentId];
                                var parentInvTfm = new Matrix4x4(pn.m1.X, pn.m1.Y, pn.m1.Z, pn.m1.W,
                                                                 pn.m2.X, pn.m2.Y, pn.m2.Z, pn.m2.W,
                                                                 pn.m3.X, pn.m3.Y, pn.m3.Z, pn.m3.W,
                                                                 pn.m4.X, pn.m4.Y, pn.m4.Z, pn.m4.W);
                                Matrix4x4.Invert(parentInvTfm, out var invParentInvTfm);
                                mat = mat * invParentInvTfm;
                            }
                            else
                            {
                                parentId = -1;
                            }
                            rttaNode.nodeMatrix = mat;

                            //Create AQN node
                            NODE aqNode = new NODE();
                            aqNode.animatedFlag = 1;
                            aqNode.parentId     = parentId;
                            aqNode.unkNode      = -1;
                            aqNode.pos          = rttaNode.pos;
                            aqNode.eulRot       = QuaternionToEuler(rttaNode.quatRot);

                            if (Math.Abs(aqNode.eulRot.Y) > 120)
                            {
                                aqNode.scale = new Vector3(-1, -1, -1);
                            }
                            else
                            {
                                aqNode.scale = new Vector3(1, 1, 1);
                            }
                            Matrix4x4.Invert(mat, out var invMat);
                            aqNode.m1       = new Vector4(invMat.M11, invMat.M12, invMat.M13, invMat.M14);
                            aqNode.m2       = new Vector4(invMat.M21, invMat.M22, invMat.M23, invMat.M24);
                            aqNode.m3       = new Vector4(invMat.M31, invMat.M32, invMat.M33, invMat.M34);
                            aqNode.m4       = new Vector4(invMat.M41, invMat.M42, invMat.M43, invMat.M44);
                            aqNode.boneName = rttaNode.nodeName;
                            Debug.WriteLine($"{i} " + aqNode.boneName.GetString());
                            aqn.nodeList.Add(aqNode);
                        }
                    }


                    //Go to mesh buffers
                    streamReader.Seek(fsaLen, SeekOrigin.Begin);
                    if (streamReader.Position() >= stream.Length)
                    {
                        return(null);
                    }

                    var fType2 = streamReader.Read <int>();
                    //Read mesh data
                    if (fType2 != FMA)
                    {
                        Debug.WriteLine("Unexpected struct in location of FMA!");
                        return(null);
                    }
                    streamReader.Seek(0xC, SeekOrigin.Current);

                    //Skip daeh
                    int meshSettingLen = streamReader.ReadDAEH();

                    //Read ffub and rdda
                    //Count mesh count here for now and store starts and ends of data
                    long meshSettingStart = streamReader.Position();
                    while (streamReader.Position() < meshSettingStart + meshSettingLen)
                    {
                        streamReader.ReadFFUBorRDDA(ffubList, rddaList, imgRddaList, vertRddaList, faceRddaList, ref imgFfub, ref vertFfub, ref faceFfub);
                    }

                    int meshCount = meshDefList.Count;

                    //Read image data
                    var ext = Path.GetExtension(filePath);
                    for (int i = 0; i < xgmiList.Count; i++)
                    {
                        var xgmiData      = xgmiList[i];
                        var imgBufferInfo = imgRddaList[$"{xgmiData.md5_1.ToString("X")}{xgmiData.md5_2.ToString("X")}"];
                        Debug.WriteLine($"Image set {i}: " + imgBufferInfo.md5_1.ToString("X") + " " + imgBufferInfo.dataStartOffset.ToString("X") + " " + imgBufferInfo.toTagStruct.ToString("X") + " " + (meshSettingStart + imgFfub.dataStartOffset + imgBufferInfo.dataStartOffset).ToString("X"));

                        var position     = meshSettingStart + imgFfub.dataStartOffset + imgBufferInfo.dataStartOffset;
                        var buffer       = streamReader.ReadBytes(position, imgBufferInfo.dataSize);
                        var outImagePath = filePath.Replace(ext, $"_tex_{i}" + ".dds");
                        texNames.Add(Path.GetFileName(outImagePath));
                        try
                        {
                            string name = Path.GetFileName(filePath);
                            Debug.WriteLine($"{name}_xgmi_{ i}");
                            var image = AIFMethods.GetImage(xgmiData, buffer);
                            File.WriteAllBytes(filePath.Replace(ext, $"_tex_{i}" + ".dds"), image);
                        }
                        catch (Exception exc)
                        {
#if DEBUG
                            string name = Path.GetFileName(filePath);
                            Debug.WriteLine($"Extract tex {i} failed.");
                            File.WriteAllBytes($"C:\\{name}_xgmiHeader_{i}.bin", xgmiData.GetBytes());
                            File.WriteAllBytes($"C:\\{name}_xgmiBuffer_{i}.bin", buffer);
                            Debug.WriteLine(exc.Message);
#endif
                        }
                        buffer = null;
                    }


                    //Read model data - Since ffubs are initialized, they default to 0.
                    int vertFfubPadding = imgFfub.structSize;
                    int faceFfubPadding = imgFfub.structSize + vertFfub.structSize;

                    for (int i = 0; i < meshCount; i++)
                    {
                        var mesh       = meshDefList[i];
                        var nodeMatrix = Matrix4x4.Identity;
                        for (int bn = 0; bn < eertNodes.boneCount; bn++)
                        {
                            var node = eertNodes.rttaList[bn];
                            if (node.meshNodePtr == mesh.oaPos)
                            {
                                nodeMatrix = node.nodeMatrix;
                                break;
                            }
                        }
                        var vertBufferInfo = vertRddaList[$"{mesh.salvStr.md5_1.ToString("X")}{mesh.salvStr.md5_2.ToString("X")}"];
                        var faceBufferInfo = faceRddaList[$"{mesh.lxdiStr.md5_1.ToString("X")}{mesh.lxdiStr.md5_2.ToString("X")}"];
                        Debug.WriteLine($"Vert set {i}: " + vertBufferInfo.md5_1.ToString("X") + " " + vertBufferInfo.dataStartOffset.ToString("X") + " " + vertBufferInfo.toTagStruct.ToString("X") + " " + (meshSettingStart + vertFfubPadding + vertFfub.dataStartOffset + vertBufferInfo.dataStartOffset).ToString("X"));
                        Debug.WriteLine($"Face set {i}: " + faceBufferInfo.md5_1.ToString("X") + " " + faceBufferInfo.dataStartOffset.ToString("X") + " " + faceBufferInfo.toTagStruct.ToString("X") + " " + (meshSettingStart + faceFfubPadding + faceFfub.dataStartOffset + faceBufferInfo.dataStartOffset).ToString("X"));

                        //Vert data
                        var             vertCount = vertBufferInfo.dataSize / mesh.salvStr.vertLen;
                        AquaObject.VTXL vtxl      = new AquaObject.VTXL();

                        streamReader.Seek((meshSettingStart + vertFfubPadding + vertFfub.dataStartOffset + vertBufferInfo.dataStartOffset), SeekOrigin.Begin);
                        AquaObjectMethods.ReadVTXL(streamReader, mesh.vtxe, vtxl, vertCount, mesh.vtxe.vertDataTypes.Count);
                        vtxl.convertToLegacyTypes();

                        //Fix vert transforms
                        for (int p = 0; p < vtxl.vertPositions.Count; p++)
                        {
                            vtxl.vertPositions[p] = Vector3.Transform(vtxl.vertPositions[p], nodeMatrix);
                            if (vtxl.vertNormals.Count > 0)
                            {
                                vtxl.vertNormals[p] = Vector3.TransformNormal(vtxl.vertNormals[p], nodeMatrix);
                            }
                        }


                        //Handle bone indices
                        if (mesh.ipnbStr != null && mesh.ipnbStr.shortList.Count > 0)
                        {
                            vtxl.bonePalette = (mesh.ipnbStr.shortList.ConvertAll(delegate(short num) {
                                return((ushort)num);
                            }));

                            //Convert the indices based on the global bone list as pso2 will expect
                            for (int bn = 0; bn < vtxl.bonePalette.Count; bn++)
                            {
                                vtxl.bonePalette[bn] = (ushort)mesh.lpnbStr.shortList[vtxl.bonePalette[bn]];
                            }
                        }

                        aqp.vtxlList.Add(vtxl);

                        //Face data
                        AquaObject.GenericTriangles genMesh = new AquaObject.GenericTriangles();

                        int        faceIndexCount = faceBufferInfo.dataSize / 2;
                        List <int> faceList       = new List <int>();

                        streamReader.Seek((meshSettingStart + faceFfubPadding + faceFfub.dataStartOffset + faceBufferInfo.dataStartOffset), SeekOrigin.Begin);
                        int maxStep = streamReader.Read <ushort>();
                        for (int fId = 0; fId < faceIndexCount - 1; fId++)
                        {
                            faceList.Add(streamReader.Read <ushort>());
                        }

                        //Convert the data to something usable with this algorithm and then destripify it.
                        List <ushort> triList = unpackInds(inverseWatermarkTransform(faceList, maxStep)).ConvertAll(delegate(int num) {
                            return((ushort)num);
                        });
                        var tempFaceData = new AquaObject.stripData()
                        {
                            triStrips = triList, format0xC33 = true, triIdCount = triList.Count
                        };
                        genMesh.triList = tempFaceData.GetTriangles();

                        //Extra
                        genMesh.vertCount = vertCount;
                        genMesh.matIdList = new List <int>(new int[genMesh.triList.Count]);
                        for (int j = 0; j < genMesh.matIdList.Count; j++)
                        {
                            genMesh.matIdList[j] = aqp.tempMats.Count;
                        }
                        aqp.tempTris.Add(genMesh);

                        //Material
                        var mat = new AquaObject.GenericMaterial();
                        mat.texNames = GetTexNames(mesh, xgmiIdByCombined, xgmiIdByUnique, texNames);
                        aqp.tempMats.Add(mat);
                    }

                    return(aqp);
                }
        }
        //Takes in bytes of a *n.rel file from PSO
        //To convert to PSO2's units, we set the scale to 1/10th scale
        public PSONRelConvert(byte[] file, string fileName = null, float scale = 0.1f, string outFolder = null)
        {
            fileSize  = file.Length;
            rootScale = scale;
            List <dSection> dSections = new List <dSection>();

            streamReader = new BufferedStreamReader(new MemoryStream(file), 8192);

            //Get header offset
            streamReader.Seek(file.Length - 0x10, SeekOrigin.Begin);

            //Check Endianness. No offset should ever come close to half of the int max value.
            be = streamReader.PeekBigEndianPrimitiveUInt32() < streamReader.Peek <uint>();
            if (be)
            {
                MessageBox.Show("Sorry, Gamecube n.rel files are not supported at this time.");
            }
            uint tableOfs = streamReader.ReadBE <uint>(be);

            //Read header
            streamReader.Seek(tableOfs, SeekOrigin.Begin);
            var header = ReadRelHeader(streamReader, be);

            //Read draw Sections
            streamReader.Seek(header.drawOffset, SeekOrigin.Begin);
            for (int i = 0; i < header.drawCount; i++)
            {
                dSection section = new dSection();
                section.id  = streamReader.ReadBE <int>(be);
                section.pos = streamReader.ReadBEV3(be);
                var rotX = streamReader.ReadBE <int>(be);
                var rotY = streamReader.ReadBE <int>(be);
                var rotZ = streamReader.ReadBE <int>(be);
                section.rot            = new Vector3((float)(rotX * BAMSvalue), (float)(rotY * BAMSvalue), (float)(rotZ * BAMSvalue));
                section.radius         = streamReader.ReadBE <float>(be);
                section.staticOffset   = streamReader.ReadBE <uint>(be);
                section.animatedOffset = streamReader.ReadBE <uint>(be);
                section.staticCount    = streamReader.ReadBE <uint>(be);
                section.animatedCount  = streamReader.ReadBE <uint>(be);
                section.end            = streamReader.ReadBE <uint>(be);

                dSections.Add(section);
            }

            //Get texture names
            streamReader.Seek(header.nameInfoOffset, SeekOrigin.Begin);
            var nameOffset = streamReader.ReadBE <uint>(be);
            var nameCount  = streamReader.ReadBE <uint>(be);

            streamReader.Seek(nameOffset, SeekOrigin.Begin);
            List <uint> nameOffsets = new List <uint>();

            for (int i = 0; i < nameCount; i++)
            {
                nameOffsets.Add(streamReader.ReadBE <uint>(be));
                var unk0 = streamReader.ReadBE <uint>(be);
                var unk1 = streamReader.ReadBE <uint>(be);

                if (unk0 != 0)
                {
                    Console.WriteLine($"Iteration {i} unk0 == {unk0}");
                }
                if (unk1 != 0)
                {
                    Console.WriteLine($"Iteration {i} unk1 == {unk1}");
                }
            }
            foreach (uint offset in nameOffsets)
            {
                streamReader.Seek(offset, SeekOrigin.Begin);
                texNames.Add(AquaObjectMethods.ReadCString(streamReader));
            }

            //If there's an .xvm, dump that too with texture names from the .rel
            if (fileName != null)
            {
                //Naming patterns for *n.rel files are *_12n.rel for example or *n.rel  vs *.xvm. We can determine which we have, edit, and proceed
                var    basename = fileName.Substring(0, fileName.Length - 5);
                string xvmName  = null;

                if (basename.ElementAt(basename.Length - 3) == '_')
                {
                    xvmName = basename.Substring(0, basename.Length - 3) + ".xvm";
                }
                else
                {
                    xvmName = basename + ".xvm";
                }

                ExtractXVM(xvmName, texNames, outFolder);
            }



            //Create root AQN node
            NODE aqNode = new NODE();

            aqNode.animatedFlag = 1;
            aqNode.parentId     = -1;
            aqNode.unkNode      = -1;
            aqNode.pos          = new Vector3();
            aqNode.eulRot       = new Vector3();
            aqNode.scale        = new Vector3(1, 1, 1);
            aqNode.m1           = new Vector4(1, 0, 0, 0);
            aqNode.m2           = new Vector4(0, 1, 0, 0);
            aqNode.m3           = new Vector4(0, 0, 1, 0);
            aqNode.m4           = new Vector4(0, 0, 0, 1);
            aqNode.boneName.SetString("RootNode");
            nodes.Add(aqNode);

            //Loop through nodes and parse geometry
            for (int i = 0; i < dSections.Count; i++)
            {
                var matrix = Matrix4x4.Identity;

                matrix *= Matrix4x4.CreateScale(1, 1, 1);

                var rotation = Matrix4x4.CreateRotationX(dSections[i].rot.X) *
                               Matrix4x4.CreateRotationY(dSections[i].rot.Y) *
                               Matrix4x4.CreateRotationZ(dSections[i].rot.Z);

                matrix *= rotation;

                matrix *= Matrix4x4.CreateTranslation(dSections[i].pos * rootScale);

                //Read static meshes
                List <staticMeshOffset> staticMeshOffsets = new List <staticMeshOffset>();
                streamReader.Seek(dSections[i].staticOffset, SeekOrigin.Begin);
                for (int st = 0; st < dSections[i].staticCount; st++)
                {
                    staticMeshOffsets.Add(ReadStaticMeshOffset(streamReader, be));
                }
                for (int ofs = 0; ofs < staticMeshOffsets.Count; ofs++)
                {
                    streamReader.Seek(staticMeshOffsets[ofs].offset, SeekOrigin.Begin);
                    readNode(matrix, 0);
                }


                //Read animated meshes
                List <animMeshOffset> animatedMeshOffsets = new List <animMeshOffset>();
                streamReader.Seek(dSections[i].animatedOffset, SeekOrigin.Begin);
                for (int st = 0; st < dSections[i].animatedCount; st++)
                {
                    animatedMeshOffsets.Add(ReadAnimMeshOffset(streamReader, be));
                }
                for (int ofs = 0; ofs < animatedMeshOffsets.Count; ofs++)
                {
                    streamReader.Seek(animatedMeshOffsets[ofs].offset, SeekOrigin.Begin);
                    readNode(matrix, 0);
                }
            }

            //Set material names
            for (int i = 0; i < aqObj.tempMats.Count; i++)
            {
                aqObj.tempMats[i].matName = $"PSOMat {i}";
            }
        }