public static njMeshGC ReadNjMeshGC(BufferedStreamReader streamReader, bool active)
        {
            var mesh = new njMeshGC();

            mesh.vertexInfoListOffset = streamReader.ReadBE <uint>(active);
            mesh.int_04 = streamReader.ReadBE <uint>(active);
            mesh.triangleStripListAOffset = streamReader.ReadBE <uint>(active);
            mesh.triangleStripListBOffset = streamReader.ReadBE <uint>(active);

            mesh.triangleStripListACount = streamReader.ReadBE <ushort>(active);
            mesh.triangleStripListBCount = streamReader.ReadBE <ushort>(active);

            mesh.center = streamReader.ReadBEV3(active);
            mesh.radius = streamReader.ReadBE <float>(active);

            return(mesh);
        }
        //Read njNode
        public void readNode(Matrix4x4 parentMatrix, int parentId)
        {
            njNode node = new njNode();

            node.flags      = streamReader.ReadBE <uint>(be);
            node.meshOffset = streamReader.ReadBE <uint>(be);
            node.pos        = streamReader.ReadBEV3(be);
            var rotX = streamReader.ReadBE <int>(be);
            var rotY = streamReader.ReadBE <int>(be);
            var rotZ = streamReader.ReadBE <int>(be);

            node.rot           = new Vector3((float)(rotX * BAMSvalue), (float)(rotY * BAMSvalue), (float)(rotZ * BAMSvalue));
            node.scl           = streamReader.ReadBEV3(be);
            node.childOffset   = streamReader.ReadBE <uint>(be);
            node.siblingOffset = streamReader.ReadBE <uint>(be);

            Matrix4x4 mat = Matrix4x4.Identity;

            mat *= Matrix4x4.CreateScale(node.scl);

            var rotation = Matrix4x4.CreateRotationX(node.rot.X) *
                           Matrix4x4.CreateRotationY(node.rot.Y) *
                           Matrix4x4.CreateRotationZ(node.rot.Z);

            mat *= rotation;

            mat *= Matrix4x4.CreateTranslation(node.pos * rootScale);

            //If there's a parent, multiply by it
            if (parentMatrix != null)
            {
                mat = mat * parentMatrix;
            }

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

            aqNode.animatedFlag = 1;
            aqNode.parentId     = parentId;
            aqNode.unkNode      = -1;
            aqNode.pos          = node.pos;
            aqNode.eulRot       = new Vector3((float)((rotX * BAMSvalue) * 180 / Math.PI), (float)((rotY * BAMSvalue) * 180 / Math.PI), (float)((rotZ * BAMSvalue) * 180 / Math.PI));

            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.SetString("Node " + nodes.Count);
            nodes.Add(aqNode);

            //Not sure what it means when these happen, but sometimes they do. Maybe hardcoded logic?
            if (node.meshOffset > fileSize || node.siblingOffset > fileSize || node.childOffset > fileSize)
            {
                return;
            }

            //Read the attached Mesh
            if (node.meshOffset != 0)
            {
                streamReader.Seek(node.meshOffset, SeekOrigin.Begin);
                readMesh(mat);
            }

            //Read the child
            if (node.childOffset != 0)
            {
                streamReader.Seek(node.childOffset, SeekOrigin.Begin);
                readNode(mat, nodes.Count - 1);
            }

            //Read the sibling
            if (node.siblingOffset != 0)
            {
                streamReader.Seek(node.siblingOffset, SeekOrigin.Begin);
                readNode(parentMatrix, parentId);
            }
        }
        //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}";
            }
        }