Пример #1
0
        private Offset WriteValue(BinaryDataWriter writer, dynamic value)
        {
            // Only reserve and return an offset for the complex value contents, write simple values directly.
            ByamlNodeType type = GetNodeType(value);

            switch (type)
            {
            case ByamlNodeType.StringIndex:
                WriteStringIndexNode(writer, value);
                return(null);

            case ByamlNodeType.PathIndex:
                WritePathIndexNode(writer, value);
                return(null);

            case ByamlNodeType.Dictionary:
            case ByamlNodeType.Array:
                foreach (var d in alreadyWrittenNodes.Keys.Where(x => x is IEnumerable && x.Count == value.Count))
                {
                    if (IEnumerableCompare.IsEqual(d, value))
                    {
                        writer.Write(alreadyWrittenNodes[d]);
                        return(null);
                    }
                }
                return(writer.ReserveOffset());

            case ByamlNodeType.Boolean:
                writer.Write(value ? 1 : 0);
                return(null);

            case ByamlNodeType.Integer:
            case ByamlNodeType.Float:
            case ByamlNodeType.Uinteger:
                writer.Write(value);
                return(null);

            case ByamlNodeType.Double:
            case ByamlNodeType.ULong:
            case ByamlNodeType.Long:
                return(writer.ReserveOffset());

            case ByamlNodeType.Null:
                writer.Write(0x0);
                return(null);

            default:
                throw new ByamlException($"{type} not supported as value node.");
            }
        }
Пример #2
0
 private Offset WriteValue(BinaryDataWriter writer, dynamic value)
 {
     // Only reserve and return an offset for the complex value contents, write simple values directly.
     ByamlNodeType type = GetNodeType(value);
     switch (type)
     {
         case ByamlNodeType.StringIndex:
             WriteStringIndexNode(writer, value);
             return null;
         case ByamlNodeType.PathIndex:
             WritePathIndexNode(writer, value);
             return null;
         case ByamlNodeType.Dictionary:
         case ByamlNodeType.Array:
             if (alreadyWrittenNodes.ContainsKey(value))
             {
                 writer.Write(alreadyWrittenNodes[value]);
                 return null;
             }
             else return writer.ReserveOffset();
         case ByamlNodeType.Boolean:
             writer.Write(value ? 1 : 0);
             return null;
         case ByamlNodeType.Integer:
         case ByamlNodeType.Float:
             writer.Write((float)value);
             return null;
         case ByamlNodeType.Null:
             writer.Write(0x0);
             return null;
         default:
             throw new ByamlException($"{type} not supported as value node.");
     }
 }
Пример #3
0
        private void WriteV2(BinaryDataWriter writer)
        {
            writer.ByteOrder = ByteOrder.BigEndian;
            writer.Write((uint)Version);
            writer.ByteOrder = this.ByteOrder;

            Offset octreeOffset           = writer.ReserveOffset();
            Offset modelOffsetArrayOffset = writer.ReserveOffset();

            writer.Write(Models.Count);
            writer.Write(MinCoordinate);
            writer.Write(MaxCoordinate);
            writer.Write(CoordinateShift);
            writer.Write(PrismCount);

            // Write the model octree.
            octreeOffset.Satisfy();
            foreach (ModelOctreeNode rootChild in ModelOctreeRoot)
            {
                rootChild.Write(writer);
            }

            int branchKey = 8;

            foreach (ModelOctreeNode rootChild in ModelOctreeRoot)
            {
                rootChild.WriteChildren(writer, ref branchKey);
            }

            // Write the model offsets.
            modelOffsetArrayOffset.Satisfy();
            Offset[] modelOffsets = writer.ReserveOffset(Models.Count);

            // Write the models.
            for (int i = 0; i < Models.Count; i++)
            {
                modelOffsets[i].Satisfy();
                Models[i].Write(writer, Version);
                writer.Align(4);
            }
        }
        /// <summary>
        /// Saves the data into the given <paramref name="stream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
        public void Save(Stream stream)
        {
            using (BinaryDataWriter writer = new BinaryDataWriter(stream, Encoding.ASCII, true))
            {
                writer.ByteOrder = ByteOrder;

                // Write the file header.
                writer.Write(Identifier, BinaryStringFormat.NoPrefixOrTermination);
                Offset fileSizeOffset = writer.ReserveOffset();
                writer.Write((short)Count);
                writer.Write(Unknown);
                writer.Write(Version);
                Offset[] sectionOffsets = writer.ReserveOffset(Count);
                int      headerLength   = (int)writer.Position;

                // Write all the sections.
                int sectionIndex = 0;
                foreach (SectionBase section in this)
                {
                    // Fill in the offset to this section.
                    sectionOffsets[sectionIndex].Satisfy((int)writer.Position - headerLength);

                    // Write the section header.
                    writer.Write(section.Name, BinaryStringFormat.NoPrefixOrTermination);
                    writer.Write(section.ElementCount);
                    writer.Write((short)section.Count);
                    writer.Write(section.SectionType, false);

                    // Write the section groups, each of them is 4-byte aligned.
                    foreach (GroupBase group in section)
                    {
                        group.Save(writer);
                        writer.Align(4);
                    }

                    sectionIndex++;
                }

                fileSizeOffset.Satisfy();
            }
        }
Пример #5
0
        private void Write(BinaryDataWriter writer, object obj)
        {
            // Write the header, specifying magic bytes, version and main node offsets.
            writer.Write(_magicBytes);
            writer.Write((short)Settings.Version);
            Offset nameArrayOffset   = writer.ReserveOffset();
            Offset stringArrayOffset = writer.ReserveOffset();
            Offset pathArrayOffset   = Settings.SupportPaths ? writer.ReserveOffset() : null;
            Offset rootOffset        = writer.ReserveOffset();

            // Write the name array.
            WriteStringArray(writer, nameArrayOffset, _nameArray);

            // Write the string array.
            if (_stringArray.Count == 0)
            {
                writer.Write(0);
            }
            else
            {
                WriteStringArray(writer, stringArrayOffset, _stringArray);
            }

            // Write the path array (if requested).
            if (Settings.SupportPaths)
            {
                if (_pathArray.Count == 0)
                {
                    writer.Write(0);
                }
                else
                {
                    WritePathArray(writer, pathArrayOffset, _pathArray);
                }
            }

            // Write the root node.
            WriteArrayOrDictionary(writer, rootOffset, obj);
        }
        /// <summary>
        /// Saves the data into the given <paramref name="stream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
        public void Save(Stream stream)
        {
            using (BinaryDataWriter writer = new BinaryDataWriter(stream, true)
            {
                ByteOrder = ByteOrder.BigEndian
            })
            {
                // Write the header.
                writer.Write(_signature);
                Offset octreeOffset           = writer.ReserveOffset();
                Offset modelOffsetArrayOffset = writer.ReserveOffset();
                writer.Write(Models.Count);
                writer.Write(MinCoordinate);
                writer.Write(MaxCoordinate);
                writer.Write(CoordinateShift);
                writer.Write(Unknown);

                // Write the model octree.
                octreeOffset.Satisfy();
                foreach (CourseOctreeNode rootChild in CourseOctreeRoot)
                {
                    rootChild.Save(writer);
                }

                // Write the model offsets.
                modelOffsetArrayOffset.Satisfy();
                Offset[] modelOffsets = writer.ReserveOffset(Models.Count);

                // Write the models.
                int i = 0;
                foreach (KclModel model in Models)
                {
                    modelOffsets[i++].Satisfy();
                    model.Save(stream);
                    writer.Align(4);
                }
            }
        }
Пример #7
0
        private void WriteArray(BinaryDataWriter writer, IList array)
        {
            WriteTypeAndElementCount(writer, ByamlNodeType.Array, array.Count);

            // Write the element types (which must be the same for each to be supported for serialization).
            ByamlNodeType nodeType = GetNodeType(array.GetType().GetTypeInfo().GetElementType());

            for (int i = 0; i < array.Count; i++)
            {
                writer.Write((byte)nodeType);
            }

            // Write the elements, which begin after a padding to the next 4 bytes.
            writer.Align(4);
            if (nodeType == ByamlNodeType.Array || nodeType == ByamlNodeType.Dictionary)
            {
                // Arrays or dictionaries are referenced by offsets pointing behind the array.
                Offset[] offsets = new Offset[array.Count];
                for (int i = 0; i < array.Count; i++)
                {
                    offsets[i] = writer.ReserveOffset();
                }
                // Behind the offsets, write the array or dictionary contents and satisfy the 4-byte aligned offsets.
                for (int i = 0; i < array.Count; i++)
                {
                    WriteArrayOrDictionary(writer, offsets[i], array[i]);
                }
            }
            else
            {
                // Primitive values are stored directly rather than being referenced by offsets.
                foreach (object element in array)
                {
                    WritePrimitiveType(writer, nodeType, element);
                }
            }
        }
Пример #8
0
        private Offset WriteValue(BinaryDataWriter writer, dynamic value)
        {
            // Only reserve and return an offset for the complex value contents, write simple values directly.
            ByamlNodeType type = GetNodeType(value);

            switch (type)
            {
            case ByamlNodeType.StringIndex:
                WriteStringIndexNode(writer, value);
                return(null);

            case ByamlNodeType.PathIndex:
                WritePathIndexNode(writer, value);
                return(null);

            case ByamlNodeType.Dictionary:
            case ByamlNodeType.Array:
                return(writer.ReserveOffset());

            case ByamlNodeType.Boolean:
                writer.Write(value ? 1 : 0);
                return(null);

            case ByamlNodeType.Integer:
            case ByamlNodeType.Float:
            case ByamlNodeType.CRCHash:
                writer.Write(value);
                return(null);

            case ByamlNodeType.Null:
                writer.Write(0x0);
                return(null);

            default:
                throw new ByamlException($"{type} not supported as value node.");
            }
        }
Пример #9
0
        protected void WriteContent(object rootReferenceKey)
        {
            using (_writer)
            {
                // Write the header, specifying magic bytes, version and main node offsets.
                _writer.Write(BYAML_MAGIC);
                _writer.Write(_version);
                Offset nameArrayOffset   = _writer.ReserveOffset();
                Offset stringArrayOffset = _writer.ReserveOffset();
                Offset pathArrayOffset   = _supportPaths ? _writer.ReserveOffset() : null;
                Offset rootOffset        = _writer.ReserveOffset();

                // Write the main nodes.
                _writer.Align(4);
                nameArrayOffset.Satisfy();
                WriteStringArrayNode(_writer, _nameArray);
                if (_stringArray.Length == 0)
                {
                    stringArrayOffset.Satisfy(0);
                }
                else
                {
                    _writer.Align(4);
                    stringArrayOffset.Satisfy();
                    WriteStringArrayNode(_writer, _stringArray);
                }

                // Include a path array offset if requested.
                if (_supportPaths)
                {
                    if (_pathArray.Count == 0)
                    {
                        pathArrayOffset.Satisfy(0);
                    }
                    else
                    {
                        _writer.Align(4);
                        pathArrayOffset.Satisfy();
                        WritePathArrayNode(_writer, _pathArray);
                    }
                }

                _writer.Align(4);

                //write value stack (Dictionary, Array, long, uint, double)
                int valStackPos = (int)_writer.BaseStream.Position;

                //write all dictionaries
                foreach (KeyValuePair <object, ByamlDict> keyValuePair in _dictionaries)
                {
                    _writer.Seek(valStackPos + keyValuePair.Value.offset, SeekOrigin.Begin);

                    if (keyValuePair.Key == rootReferenceKey)
                    {
                        rootOffset.Satisfy();
                    }

                    if (_byteOrder == ByteOrder.BigEndian)
                    {
                        _writer.Write((uint)ByamlNodeType.Dictionary << 24 | (uint)keyValuePair.Value.entries.Length);
                    }
                    else
                    {
                        _writer.Write((uint)ByamlNodeType.Dictionary | (uint)keyValuePair.Value.entries.Length << 8);
                    }

                    foreach ((string key, Entry entry) in keyValuePair.Value.entries)
                    {
                        if (_byteOrder == ByteOrder.BigEndian)
                        {
                            _writer.Write(Array.IndexOf(_nameArray, key) << 8 | (byte)entry.type);
                        }
                        else
                        {
                            _writer.Write(Array.IndexOf(_nameArray, key) | (byte)entry.type << 24);
                        }

                        WriteValue(entry);
                    }
                }

                //write all arrays
                foreach (KeyValuePair <object, ByamlArr> keyValuePair in _arrays)
                {
                    _writer.Seek(valStackPos + keyValuePair.Value.offset, SeekOrigin.Begin);

                    if (keyValuePair.Key == rootReferenceKey)
                    {
                        rootOffset.Satisfy();
                    }

                    if (_byteOrder == ByteOrder.BigEndian)
                    {
                        _writer.Write((uint)ByamlNodeType.Array << 24 | (uint)keyValuePair.Value.entries.Length);
                    }
                    else
                    {
                        _writer.Write((uint)ByamlNodeType.Array | (uint)keyValuePair.Value.entries.Length << 8);
                    }


                    foreach (Entry entry in keyValuePair.Value.entries)
                    {
                        _writer.Write((byte)entry.type);
                    }

                    _writer.Align(4);

                    foreach (Entry entry in keyValuePair.Value.entries)
                    {
                        WriteValue(entry);
                    }
                }

                //write all 8 byte values
                foreach (var keyValuePair in _eightByteValues)
                {
                    _writer.Seek(valStackPos + keyValuePair.Value, SeekOrigin.Begin);
                    _writer.Write(keyValuePair.Key);
                }

                void WriteValue(Entry entry)
                {
                    // Only write the offset for the complex value contents, write simple values directly.
                    switch (entry.type)
                    {
                    case ByamlNodeType.StringIndex:
                        _writer.Write((uint)Array.IndexOf(_stringArray, entry.value));
                        break;

                    case ByamlNodeType.PathIndex:
                        _writer.Write(_pathArray.IndexOf(entry.value));
                        break;

                    case ByamlNodeType.Dictionary:
                        _writer.Write(valStackPos + _dictionaries[(object)entry.value].offset);
                        break;

                    case ByamlNodeType.Array:
                        _writer.Write(valStackPos + _arrays[(object)entry.value].offset);
                        break;

                    case ByamlNodeType.Boolean:
                        _writer.Write(entry.value ? 1 : 0);
                        break;

                    case ByamlNodeType.Integer:
                    case ByamlNodeType.Float:
                    case ByamlNodeType.UInteger:
                        _writer.Write(entry.value);
                        break;

                    case ByamlNodeType.Double:
                    case ByamlNodeType.ULong:
                    case ByamlNodeType.Long:
                        _writer.Write(valStackPos + _eightByteValues[entry.value]);
                        return;

                    case ByamlNodeType.Null:
                        _writer.Write(0);
                        break;
                    }
                }
            }
        }
Пример #10
0
        // ---- Saving ----

        private void Write(Stream stream, object root)
        {
            // Check if the root is of the correct type.
            if (root == null)
            {
                throw new ByamlException("Root node must not be null.");
            }
            else if (!(root is IDictionary <string, dynamic> || root is IEnumerable))
            {
                throw new ByamlException($"Type '{root.GetType()}' is not supported as a BYAML root node.");
            }

            // Generate the name, string and path array nodes.
            _nameArray   = new List <string>();
            _stringArray = new List <string>();
            _pathArray   = new List <List <ByamlPathPoint> >();
            CollectNodeArrayContents(root);
            _nameArray.Sort(StringComparer.Ordinal);
            _stringArray.Sort(StringComparer.Ordinal);

            // Open a writer on the given stream.
            using (BinaryDataWriter writer = new BinaryDataWriter(stream, Encoding.UTF8, true))
            {
                writer.ByteOrder = _byteOrder;

                // Write the header, specifying magic bytes, version and main node offsets.
                writer.Write(_magicBytes);
                writer.Write((short)0x0001);
                Offset nameArrayOffset   = writer.ReserveOffset();
                Offset stringArrayOffset = writer.ReserveOffset();
                Offset pathArrayOffset   = _supportPaths ? writer.ReserveOffset() : null;
                Offset rootOffset        = writer.ReserveOffset();

                // Write the main nodes.
                WriteValueContents(writer, nameArrayOffset, ByamlNodeType.StringArray, _nameArray);
                if (_stringArray.Count == 0)
                {
                    writer.Write(0);
                }
                else
                {
                    WriteValueContents(writer, stringArrayOffset, ByamlNodeType.StringArray, _stringArray);
                }

                // Include a path array offset if requested.
                if (_supportPaths)
                {
                    if (_pathArray.Count == 0)
                    {
                        writer.Write(0);
                    }
                    else
                    {
                        WriteValueContents(writer, pathArrayOffset, ByamlNodeType.PathArray, _pathArray);
                    }
                }

                // Write the root node.
                WriteValueContents(writer, rootOffset, GetNodeType(root), root);
            }
        }
        /// <summary>
        /// Saves the data into the given <paramref name="stream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
        public void Save(Stream stream)
        {
            using (BinaryDataWriter writer = new BinaryDataWriter(stream, true)
            {
                ByteOrder = ByteOrder.BigEndian
            })
            {
                long modelPosition = writer.Position;

                // Write the header.
                Offset positionArrayOffset = writer.ReserveOffset();
                Offset normalArrayOffset   = writer.ReserveOffset();
                Offset triangleArrayOffset = writer.ReserveOffset();
                Offset octreeOffset        = writer.ReserveOffset();
                writer.Write(Unknown1);
                writer.Write(MinCoordinate);
                writer.Write(CoordinateMask);
                writer.Write(CoordinateShift);
                writer.Write(Unknown2);

                // Write the positions.
                positionArrayOffset.Satisfy((int)(writer.Position - modelPosition));
                writer.Write(Positions);

                // Write the normals.
                normalArrayOffset.Satisfy((int)(writer.Position - modelPosition));
                writer.Write(Normals);

                // Write the triangles.
                triangleArrayOffset.Satisfy((int)(writer.Position - modelPosition));
                writer.Write(Triangles);

                // Write the octree.
                int octreeOffsetValue = (int)(writer.Position - modelPosition);
                octreeOffset.Satisfy(octreeOffsetValue);
                // Write the node keys, and compute the correct offsets into the triangle lists or to child nodes.
                // Nintendo writes child nodes behind the current node, so the children need to be queued.
                // In this implementation, empty triangle lists point to the same terminator behind the last node.
                // This could be further optimized by reusing equal parts of lists as Nintendo apparently did it.
                int emptyListPos    = GetNodeCount(ModelOctreeRoots) * sizeof(uint);
                int triangleListPos = emptyListPos + sizeof(ushort);
                Queue <ModelOctreeNode[]> queuedNodes = new Queue <ModelOctreeNode[]>();
                queuedNodes.Enqueue(ModelOctreeRoots);
                while (queuedNodes.Count > 0)
                {
                    ModelOctreeNode[] nodes = queuedNodes.Dequeue();
                    long offset             = writer.Position - modelPosition - octreeOffsetValue;
                    foreach (ModelOctreeNode node in nodes)
                    {
                        if (node.Children == null)
                        {
                            // Node is a leaf and points to triangle index list.
                            int listPos;
                            if (node.TriangleIndices.Count == 0)
                            {
                                listPos = emptyListPos;
                            }
                            else
                            {
                                listPos          = triangleListPos;
                                triangleListPos += (node.TriangleIndices.Count + 1) * sizeof(ushort);
                            }
                            node.Key = (uint)ModelOctreeNode.Flags.Values | (uint)(listPos - offset - sizeof(ushort));
                        }
                        else
                        {
                            // Node is a branch and points to 8 children.
                            node.Key = (uint)(nodes.Length + queuedNodes.Count * 8) * sizeof(uint);
                            queuedNodes.Enqueue(node.Children);
                        }
                        writer.Write(node.Key);
                    }
                }
                // Iterate through the nodes again and write their triangle lists now.
                writer.Write((ushort)0xFFFF); // Terminator for all empty lists.
                queuedNodes.Enqueue(ModelOctreeRoots);
                while (queuedNodes.Count > 0)
                {
                    ModelOctreeNode[] nodes = queuedNodes.Dequeue();
                    foreach (ModelOctreeNode node in nodes)
                    {
                        if (node.Children == null)
                        {
                            if (node.TriangleIndices.Count > 0)
                            {
                                // Node is a leaf and points to triangle index list.
                                writer.Write(node.TriangleIndices);
                                writer.Write((ushort)0xFFFF);
                            }
                        }
                        else
                        {
                            // Node is a branch and points to 8 children.
                            queuedNodes.Enqueue(node.Children);
                        }
                    }
                }
            }
        }
Пример #12
0
        private void WriteDictionary(BinaryDataWriter writer, object obj)
        {
            // Create a string-object dictionary out of the members.
            Dictionary <string, object> dictionary = new Dictionary <string, object>();
            // Add the custom members if any have been created when collecting node contents previously.
            Dictionary <string, object> customMembers;

            if (_customMembers.TryGetValue(obj, out customMembers))
            {
                foreach (KeyValuePair <string, object> customMember in customMembers)
                {
                    dictionary.Add(customMember.Key, customMember.Value);
                }
            }
            // Add the ByamlMemberAttribute decorated members.
            ByamlObjectInfo objectInfo = _byamlObjectInfos[obj.GetType()];

            foreach (KeyValuePair <string, ByamlMemberInfo> member in objectInfo.Members)
            {
                object value = member.Value.GetValue(obj);
                if (value != null || !member.Value.Optional)
                {
                    dictionary.Add(member.Key, value);
                }
            }
            // Dictionaries need to be sorted ordinally by key.
            var sortedDict = dictionary.Values.Zip(dictionary.Keys, (Value, Key) => new { Key, Value })
                             .OrderBy(x => x.Key, StringComparer.Ordinal).ToList();

            WriteTypeAndElementCount(writer, ByamlNodeType.Dictionary, dictionary.Count);

            // Write the key-value pairs.
            Dictionary <Offset, object> offsetElements = new Dictionary <Offset, object>();

            foreach (var keyValuePair in sortedDict)
            {
                string key     = keyValuePair.Key;
                object element = keyValuePair.Value;

                // Get the index of the key string in the file's name array and write it together with the type.
                uint          keyIndex = (uint)_nameArray.IndexOf(key);
                ByamlNodeType nodeType = element == null ? ByamlNodeType.Null : GetNodeType(element.GetType());
                if (Settings.ByteOrder == ByteOrder.BigEndian)
                {
                    writer.Write(keyIndex << 8 | (uint)nodeType);
                }
                else
                {
                    writer.Write(keyIndex | (uint)nodeType << 24);
                }

                // Write the elements. Complex types are just offsets, primitive types are directly written as values.
                if (nodeType == ByamlNodeType.Array || nodeType == ByamlNodeType.Dictionary)
                {
                    offsetElements.Add(writer.ReserveOffset(), element);
                }
                else
                {
                    WritePrimitiveType(writer, nodeType, element);
                }
            }

            // Write the array or dictionary elements and satisfy their offsets.
            foreach (KeyValuePair <Offset, object> offsetElement in offsetElements)
            {
                WriteArrayOrDictionary(writer, offsetElement.Key, offsetElement.Value);
            }
        }
Пример #13
0
        internal void Write(BinaryDataWriter writer, FileVersion version)
        {
            long modelPosition = writer.Position;

            Offset positionArrayOffset = writer.ReserveOffset();
            Offset normalArrayOffset   = writer.ReserveOffset();
            Offset triangleArrayOffset = writer.ReserveOffset();
            Offset octreeOffset        = writer.ReserveOffset();

            if (version == FileVersion.VersionDS)
            {
                writer.WriteFx32(PrismThickness);
                writer.WriteVector3Fx32(MinCoordinate);
                writer.Write(CoordinateMask);
                writer.Write(CoordinateShift);
                writer.WriteFx32(SphereRadius);

                // Write the positions.
                positionArrayOffset.Satisfy((int)(writer.Position - modelPosition));
                writer.WriteVector3Fx32s(Positions.ToArray());

                // Write the normals.
                normalArrayOffset.Satisfy((int)(writer.Position - modelPosition));
                writer.WriteVector3Fx16s(Normals.ToArray());

                // Write the triangles.
                triangleArrayOffset.Satisfy((int)(writer.Position - modelPosition - 0x10));
                writer.Write(Prisms, version);
            }
            else
            {
                writer.Write(PrismThickness);
                writer.Write(MinCoordinate);
                writer.Write(CoordinateMask);
                writer.Write(CoordinateShift);
                if (version > FileVersion.VersionGC)
                {
                    writer.Write(SphereRadius);
                }

                // Write the positions.
                positionArrayOffset.Satisfy((int)(writer.Position - modelPosition));
                writer.Write(Positions.ToArray());

                // Write the normals.
                normalArrayOffset.Satisfy((int)(writer.Position - modelPosition));
                writer.Write(Normals.ToArray());

                // Write the triangles.
                if (version < FileVersion.Version2)
                {
                    triangleArrayOffset.Satisfy((int)(writer.Position - modelPosition - 0x10));
                }
                else
                {
                    triangleArrayOffset.Satisfy((int)(writer.Position - modelPosition));
                }
                writer.Write(Prisms, version);
            }

            // Write the octree.
            int octreeOffsetValue = (int)(writer.Position - modelPosition);

            octreeOffset.Satisfy(octreeOffsetValue);

            // Write the node keys, and compute the correct offsets into the triangle lists or to child nodes.
            // Nintendo writes child nodes behind the current node, so the children need to be queued.
            // In this implementation, empty triangle lists point to the same terminator behind the last node.
            int triangleListPos = GetNodeCount(PolygonOctreeRoots) * sizeof(uint);
            Queue <PolygonOctree[]>    queuedNodes = new Queue <PolygonOctree[]>();
            Dictionary <ushort[], int> indexPool   = CreateIndexBuffer(queuedNodes);

            queuedNodes.Enqueue(PolygonOctreeRoots);
            while (queuedNodes.Count > 0)
            {
                PolygonOctree[] nodes  = queuedNodes.Dequeue();
                long            offset = writer.Position - modelPosition - octreeOffsetValue;
                foreach (PolygonOctree node in nodes)
                {
                    if (node.Children == null)
                    {
                        // Node is a leaf and points to triangle index list.
                        ushort[] indices = node.TriangleIndices.ToArray();
                        int      listPos = triangleListPos + indexPool[indices];
                        node.Key = (uint)ModelOctreeNode.Flags.Values | (uint)(listPos - offset - sizeof(ushort));
                    }
                    else
                    {
                        // Node is a branch and points to 8 children.
                        node.Key = (uint)(nodes.Length + queuedNodes.Count * 8) * sizeof(uint);
                        queuedNodes.Enqueue(node.Children);
                    }
                    writer.Write(node.Key);
                }
            }

            foreach (var ind in indexPool)
            {
                //Last value skip. Uses terminator of previous index list
                if (ind.Key.Length == 0)
                {
                    break;
                }

                //Save the index lists and terminator
                if (version < FileVersion.Version2)
                {
                    for (int i = 0; i < ind.Key.Length; i++)
                    {
                        writer.Write((ushort)(ind.Key[i] + 1)); //-1 indexed
                    }
                    writer.Write((ushort)0);                    // Terminator
                }
                else
                {
                    writer.Write(ind.Key);
                    writer.Write((ushort)0xFFFF); // Terminator
                }
            }
        }