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."); } }
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."); } }
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(); } }
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); } } }
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); } } }
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."); } }
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; } } } }
// ---- 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); } } } } }
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); } }
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 } } }