├── .editorconfig
├── .github
└── workflows
│ └── build_n_test.yml
├── .gitignore
├── LICENSE
├── Licenses
└── MeshIO.md
├── MeshSharp.Examples
├── MeshSharp.Examples.csproj
└── Program.cs
├── MeshSharp.FBX
├── Converters
│ ├── FbxConverter7400.cs
│ ├── FbxConverterBase.cs
│ ├── IFbxConverter.cs
│ ├── INodeParser.cs
│ ├── NodeParser7400.cs
│ └── NodeParserBase.cs
├── DeflateWithChecksum.cs
├── ErrorLevel.cs
├── Exceptions
│ ├── FbxConverterException.cs
│ └── FbxParserException.cs
├── FbxBinary.cs
├── FbxElementDefinition.cs
├── FbxNode.cs
├── FbxNodeCollection.cs
├── FbxProperty.cs
├── FbxPropertyFlag.cs
├── FbxProperty[T].cs
├── FbxRootNode.cs
├── FbxVersion.cs
├── MeshSharp.FBX.csproj
├── Notes.md
├── Parsers
│ ├── FbxAsciiParser.cs
│ ├── FbxBinaryParser.cs
│ └── IFbxParser.cs
├── Readers
│ ├── FbxReader.cs
│ └── IFbxReader.cs
└── Writers
│ ├── FbxAsciiWriter.cs
│ ├── FbxBinaryWriter.cs
│ ├── FbxWriter.cs
│ ├── FbxWriterOptions.cs
│ └── IFbxWriter.cs
├── MeshSharp.GLTF
├── Class1.cs
└── MeshSharp.GLTF.csproj
├── MeshSharp.OBJ
├── MeshSharp.OBJ.csproj
├── Notes.md
├── ObjAsciiWriter.cs
├── ObjMesh.cs
├── ObjReader.cs
└── ObjWriter.cs
├── MeshSharp.PLY
├── FileFormatDefinition.md
├── MeshSharp.PLY.csproj
├── Notes.md
├── PlyFileFormat.cs
├── PlyHeader.cs
├── PlyMesh.cs
├── PlyWriter.cs
└── Writers
│ ├── PlyAsciiWriter.cs
│ └── PlyWriterBase.cs
├── MeshSharp.PMX
├── Class1.cs
├── MeshSharp.PMX.csproj
└── Notes.md
├── MeshSharp.STL
├── MeshSharp.STL.csproj
├── Notes.md
├── Readers
│ ├── StlAsciiReader.cs
│ ├── StlBinaryReader.cs
│ └── StlReaderBase.cs
├── StlReader.cs
├── StlTriangle.cs
├── StlWriter.cs
└── Writers
│ ├── StlAsciiWriter.cs
│ ├── StlBinaryWriter.cs
│ └── StlWriterBase.cs
├── MeshSharp.Tests
├── Matrix4Tests.cs
├── MeshSharp.Tests.csproj
├── TransformTests.cs
├── VectorExtensionsTests.cs
├── VectorTestCaseFactory.cs
├── VectorTests.cs
├── XYZMTests.cs
└── XYZTests.cs
├── MeshSharp.Viewer
├── App.axaml
├── App.axaml.cs
├── MainWindow.axaml
├── MainWindow.axaml.cs
├── MeshSharp.Viewer.csproj
└── Program.cs
├── MeshSharp.sln
├── MeshSharp
├── Color.cs
├── Elements
│ ├── Camera.cs
│ ├── Element.cs
│ ├── Geometries
│ │ ├── Geometry.cs
│ │ ├── Layers
│ │ │ ├── LayerCollection.cs
│ │ │ ├── LayerElement.cs
│ │ │ ├── LayerElementBinormal.cs
│ │ │ ├── LayerElementEdgeCrease.cs
│ │ │ ├── LayerElementHole.cs
│ │ │ ├── LayerElementMaterial.cs
│ │ │ ├── LayerElementNormal.cs
│ │ │ ├── LayerElementPolygonGroup.cs
│ │ │ ├── LayerElementSmoothing.cs
│ │ │ ├── LayerElementSpecular.cs
│ │ │ ├── LayerElementTangent.cs
│ │ │ ├── LayerElementUV.cs
│ │ │ ├── LayerElementUserData.cs
│ │ │ ├── LayerElementVertexColor.cs
│ │ │ ├── LayerElementVertexCrease.cs
│ │ │ ├── LayerElementVisibility.cs
│ │ │ ├── LayerElementWeight.cs
│ │ │ ├── MappingMode.cs
│ │ │ └── ReferenceMode.cs
│ │ ├── Mesh.cs
│ │ └── MeshUtils.cs
│ ├── Material.cs
│ ├── Node.cs
│ └── Scene.cs
├── IO
│ ├── BinaryReaderExtensions.cs
│ ├── BinaryWriterExtensions.cs
│ ├── EndianReader.cs
│ ├── EndianWriter.cs
│ ├── IBinaryReadable.cs
│ ├── IBinaryWriteable.cs
│ ├── MeshReaderBase.cs
│ └── MeshWriterBase.cs
├── Math
│ ├── IVector.cs
│ ├── Matrix4.Operators.cs
│ ├── Matrix4.cs
│ ├── Quaternion.cs
│ ├── VectorExtensions.cs
│ ├── XY.cs
│ ├── XYZ.cs
│ └── XYZM.cs
├── MeshSharp.csproj
├── Polygon.cs
├── Properties
│ └── AssemblyInfo.cs
├── Property.cs
├── PropertyCollection.cs
├── Quad.cs
├── Transform.cs
├── Triangle.cs
└── Utils.cs
├── README.md
└── clean.sh
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # CS1591: Missing XML comment for publicly visible type or member
4 | dotnet_diagnostic.CS1591.severity = none
5 |
6 | [*.{cs,vb}]
7 | indent_style=tab
--------------------------------------------------------------------------------
/.github/workflows/build_n_test.yml:
--------------------------------------------------------------------------------
1 | name: Build&Test
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: windows-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Setup .NET
17 | uses: actions/setup-dotnet@v2
18 | with:
19 | dotnet-version: 6.0.x
20 | - name: Restore dependencies
21 | run: dotnet restore
22 | - name: Build
23 | run: dotnet build --configuration Release --no-restore
24 | - name: Test
25 | run: dotnet test --configuration Release --no-build --verbosity normal
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | obj
2 | bin
3 | .vs
4 | .idea
5 | *.csproj.user
6 | file_samples
7 | .vscode
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 ds5678
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Licenses/MeshIO.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Albert Domenech
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MeshSharp.Examples/MeshSharp.Examples.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | AssetRipper.MeshSharp.Examples
7 |
8 |
9 |
10 | false
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/MeshSharp.Examples/Program.cs:
--------------------------------------------------------------------------------
1 | using AssetRipper.MeshSharp.Elements;
2 | using AssetRipper.MeshSharp.FBX;
3 | using AssetRipper.MeshSharp.OBJ;
4 | using AssetRipper.MeshSharp.PLY;
5 | using AssetRipper.MeshSharp.STL;
6 | using System;
7 |
8 | namespace AssetRipper.MeshSharp.Examples
9 | {
10 | class Program
11 | {
12 | static void Main(string[] args)
13 | {
14 | try
15 | {
16 | //StlConversion();
17 | //StlExample();
18 | //ObjExample();
19 | PlyExample();
20 | }
21 | catch (Exception ex)
22 | {
23 | Console.WriteLine(ex.ToString());
24 | }
25 |
26 | Console.WriteLine("Program finished");
27 | Console.ReadLine();
28 | }
29 |
30 | static void FbxExample()
31 | {
32 | //string pathI = @".\..\..\..\..\file_samples\fbx\objects_ascii_2014-2015.fbx";
33 | string pathI = @".\..\..\..\..\file_samples\fbx\test_project_arq_acsii.fbx";
34 | //string pathO = @".\..\..\..\..\file_samples\fbx\objects_ascii_2014-2015_out.fbx";
35 | string pathO = @".\..\..\..\..\file_samples\fbx\test_project_arq_acsii_out.fbx";
36 |
37 | Scene scene = FbxReader.Read(pathI, ErrorLevel.Checked);
38 | FbxWriter.WriteAscii(pathO, scene);
39 | }
40 |
41 | static void StlConversion()
42 | {
43 | string pathI = @".\..\..\..\..\file_samples\stl\dev_binoculars_hudShape_1_binary.stl";
44 | string pathO = @".\..\..\..\..\file_samples\stl\dev_binoculars_hudShape_1_out.fbx";
45 | Scene scene = StlReader.ReadBinary(pathI);
46 | FbxWriter.WriteAscii(pathO, scene);
47 | //for some reason, the mesh gets rotated 90 degrees on the x - axis during this conversion
48 | }
49 |
50 | static void StlExample()
51 | {
52 | string pathI = @".\..\..\..\..\file_samples\stl\dev_binoculars_hudShape_1_binary.stl";
53 | string pathO = @".\..\..\..\..\file_samples\stl\dev_binoculars_hudShape_1_ascii.stl";
54 | Scene scene = StlReader.ReadBinary(pathI);
55 | StlWriter.WriteAscii(pathO, scene);
56 | //this does not result in rotation
57 | }
58 |
59 | static void ObjExample()
60 | {
61 | string pathI = @".\..\..\..\..\file_samples\stl\dev_binoculars_hudShape_1_binary.stl";
62 | string pathO = @".\..\..\..\..\file_samples\stl\dev_binoculars_hudShape_1_out.obj";
63 | Scene scene = StlReader.ReadBinary(pathI);
64 | ObjWriter.Write(pathO, scene);
65 | }
66 |
67 | static void PlyExample()
68 | {
69 | string pathI = @".\..\..\..\..\file_samples\stl\dev_binoculars_hudShape_1_binary.stl";
70 | string pathO = @".\..\..\..\..\file_samples\stl\dev_binoculars_hudShape_1_out.ply";
71 | Scene scene = StlReader.ReadBinary(pathI);
72 | PlyWriter.WriteAscii(pathO, scene);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/Converters/FbxConverter7400.cs:
--------------------------------------------------------------------------------
1 | using AssetRipper.MeshSharp.Elements;
2 |
3 | namespace AssetRipper.MeshSharp.FBX.Converters
4 | {
5 | public class FbxConverter7400 : FbxConverterBase
6 | {
7 | public FbxConverter7400(Scene scene) : base(scene, FbxVersion.v7400) { }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/Converters/IFbxConverter.cs:
--------------------------------------------------------------------------------
1 | namespace AssetRipper.MeshSharp.FBX.Converters
2 | {
3 | public interface IFbxConverter
4 | {
5 | FbxVersion Version { get; }
6 |
7 | FbxRootNode ToRootNode();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/Converters/INodeParser.cs:
--------------------------------------------------------------------------------
1 | using AssetRipper.MeshSharp.Elements;
2 |
3 | namespace AssetRipper.MeshSharp.FBX.Converters
4 | {
5 | public interface INodeParser
6 | {
7 | FbxVersion Version { get; }
8 |
9 | Scene ConvertScene();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/Converters/NodeParser7400.cs:
--------------------------------------------------------------------------------
1 | namespace AssetRipper.MeshSharp.FBX.Converters
2 | {
3 | ///
4 | ///
5 | /// Class to convert a node structure in the version
6 | ///
7 | public class NodeParser7400 : NodeParserBase
8 | {
9 | public NodeParser7400(FbxRootNode root) : base(root) { }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/DeflateWithChecksum.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.IO.Compression;
3 |
4 | namespace AssetRipper.MeshSharp.FBX
5 | {
6 | ///
7 | /// A wrapper for DeflateStream that calculates the Adler32 checksum of the payload
8 | ///
9 | public class DeflateWithChecksum : DeflateStream
10 | {
11 | private const int modAdler = 65521;
12 | private uint checksumA;
13 | private uint checksumB;
14 |
15 | ///
16 | /// Gets the Adler32 checksum at the current point in the stream
17 | ///
18 | public int Checksum
19 | {
20 | get
21 | {
22 | checksumA %= modAdler;
23 | checksumB %= modAdler;
24 | return (int)((checksumB << 16) | checksumA);
25 | }
26 | }
27 |
28 | ///
29 | public DeflateWithChecksum(Stream stream, CompressionMode mode) : base(stream, mode)
30 | {
31 | ResetChecksum();
32 | }
33 |
34 | ///
35 | public DeflateWithChecksum(Stream stream, CompressionMode mode, bool leaveOpen) : base(stream, mode, leaveOpen)
36 | {
37 | ResetChecksum();
38 | }
39 |
40 | // Efficiently extends the checksum with the given buffer
41 | void CalcChecksum(byte[] array, int offset, int count)
42 | {
43 | checksumA %= modAdler;
44 | checksumB %= modAdler;
45 | for (int i = offset, c = 0; i < (offset + count); i++, c++)
46 | {
47 | checksumA += array[i];
48 | checksumB += checksumA;
49 | if (c > 4000) // This is about how many iterations it takes for B to reach IntMax
50 | {
51 | checksumA %= modAdler;
52 | checksumB %= modAdler;
53 | c = 0;
54 | }
55 | }
56 | }
57 |
58 | ///
59 | public override void Write(byte[] array, int offset, int count)
60 | {
61 | base.Write(array, offset, count);
62 | CalcChecksum(array, offset, count);
63 | }
64 |
65 | ///
66 | public override int Read(byte[] array, int offset, int count)
67 | {
68 | var ret = base.Read(array, offset, count);
69 | CalcChecksum(array, offset, count);
70 | return ret;
71 | }
72 |
73 | ///
74 | /// Initializes the checksum values
75 | ///
76 | public void ResetChecksum()
77 | {
78 | checksumA = 1;
79 | checksumB = 0;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/ErrorLevel.cs:
--------------------------------------------------------------------------------
1 | namespace AssetRipper.MeshSharp.FBX
2 | {
3 | ///
4 | /// Indicates when a reader should throw errors
5 | ///
6 | public enum ErrorLevel
7 | {
8 | ///
9 | /// Ignores inconsistencies unless the parser can no longer continue
10 | ///
11 | Permissive = 0,
12 |
13 | ///
14 | /// Checks data integrity, such as checksums and end points
15 | ///
16 | Checked = 1,
17 |
18 | ///
19 | /// Checks everything, including magic bytes
20 | ///
21 | Strict = 2,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/Exceptions/FbxConverterException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AssetRipper.MeshSharp.FBX.Exceptions
4 | {
5 |
6 | [Serializable]
7 | public class FbxConverterException : Exception
8 | {
9 | public FbxConverterException() { }
10 | public FbxConverterException(string message) : base(message) { }
11 | public FbxConverterException(string message, Exception inner) : base(message, inner) { }
12 | protected FbxConverterException(
13 | System.Runtime.Serialization.SerializationInfo info,
14 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/Exceptions/FbxParserException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace AssetRipper.MeshSharp.FBX.Exceptions
5 | {
6 | ///
7 | /// An error with the FBX data input
8 | ///
9 | public class FbxException : Exception
10 | {
11 | ///
12 | /// An error at a binary stream offset
13 | ///
14 | ///
15 | ///
16 | public FbxException(long position, string
17 | message) : base($"{message}, near offset {position}") { }
18 |
19 | ///
20 | /// An error in a text file
21 | ///
22 | ///
23 | ///
24 | ///
25 | public FbxException(int line, int column, string message) :
26 | base($"{message}, near line {line} column {column}")
27 | { }
28 |
29 | ///
30 | /// An error in a node object
31 | ///
32 | ///
33 | ///
34 | ///
35 | public FbxException(Stack nodePath, int propertyID, string message)
36 | : base(message + ", at " + string.Join("/", nodePath.ToArray()) + (propertyID < 0 ? "" : $"[{propertyID}]")) { }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/FbxBinary.cs:
--------------------------------------------------------------------------------
1 | using AssetRipper.MeshSharp.FBX.Exceptions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Text;
6 |
7 | namespace AssetRipper.MeshSharp.FBX
8 | {
9 | ///
10 | /// Base class for binary stream wrappers
11 | ///
12 | internal abstract class FbxBinary : IDisposable
13 | {
14 | ///
15 | /// The size of the footer code
16 | ///
17 | protected const int footerCodeSize = 16;
18 |
19 | ///
20 | /// The namespace separator in the binary format (remember to reverse the identifiers)
21 | ///
22 | protected const string binarySeparator = "\0\x1";
23 |
24 | ///
25 | /// The namespace separator in the ASCII format and in object data
26 | ///
27 | protected const string asciiSeparator = "::";
28 |
29 | const string timePath1 = "FBXHeaderExtension";
30 | const string timePath2 = "CreationTimeStamp";
31 | static readonly Stack timePath = new Stack(new[] { timePath1, timePath2 });
32 |
33 | // Header string, found at the top of all compliant files
34 | private static readonly byte[] headerString = Encoding.ASCII.GetBytes("Kaydara FBX Binary \0\x1a\0");
35 |
36 | // This data was entirely calculated by me, honest. Turns out it works, fancy that!
37 | private static readonly byte[] sourceId = { 0x58, 0xAB, 0xA9, 0xF0, 0x6C, 0xA2, 0xD8, 0x3F, 0x4D, 0x47, 0x49, 0xA3, 0xB4, 0xB2, 0xE7, 0x3D };
38 | private static readonly byte[] key = { 0xE2, 0x4F, 0x7B, 0x5F, 0xCD, 0xE4, 0xC8, 0x6D, 0xDB, 0xD8, 0xFB, 0xD7, 0x40, 0x58, 0xC6, 0x78 };
39 | // This wasn't - it just appears at the end of every compliant file
40 | private static readonly byte[] extension = { 0xF8, 0x5A, 0x8C, 0x6A, 0xDE, 0xF5, 0xD9, 0x7E, 0xEC, 0xE9, 0x0C, 0xE3, 0x75, 0x8F, 0x29, 0x0B };
41 |
42 | // Number of null bytes between the footer code and the version
43 | private const int footerZeroes1 = 20;
44 | // Number of null bytes between the footer version and extension code
45 | private const int footerZeroes2 = 120;
46 |
47 | ///
48 | /// Checks if the first part of 'data' matches 'original'
49 | ///
50 | ///
51 | ///
52 | /// true if it does, otherwise false
53 | protected static bool CheckEqual(byte[] data, byte[] original)
54 | {
55 | for (int i = 0; i < original.Length; i++)
56 | if (data[i] != original[i])
57 | return false;
58 | return true;
59 | }
60 |
61 | ///
62 | /// Writes the FBX header string
63 | ///
64 | ///
65 | protected static void WriteHeader(Stream stream)
66 | {
67 | stream.Write(headerString, 0, headerString.Length);
68 | }
69 |
70 | ///
71 | /// Reads the FBX header string
72 | ///
73 | ///
74 | /// true if it's compliant
75 | public static bool ReadHeader(Stream stream)
76 | {
77 | var buf = new byte[headerString.Length];
78 | stream.Read(buf, 0, buf.Length);
79 | return CheckEqual(buf, headerString);
80 | }
81 |
82 | public abstract void Dispose();
83 |
84 | // Turns out this is the algorithm they use to generate the footer. Who knew!
85 | static void Encrypt(byte[] a, byte[] b)
86 | {
87 | byte c = 64;
88 | for (int i = 0; i < footerCodeSize; i++)
89 | {
90 | a[i] = (byte)(a[i] ^ (byte)(c ^ b[i]));
91 | c = a[i];
92 | }
93 | }
94 |
95 | // Gets a single timestamp component
96 | static int GetTimestampVar(FbxNode timestamp, string element)
97 | {
98 | var elementNode = timestamp[element];
99 | if (elementNode != null && elementNode.Properties.Count > 0)
100 | {
101 | var prop = elementNode.Properties[0];
102 | if (prop is int || prop is long)
103 | return (int)prop;
104 | }
105 | throw new FbxException(timePath, -1, "Timestamp has no " + element);
106 | }
107 |
108 | ///
109 | /// Generates the unique footer code based on the document's timestamp
110 | ///
111 | ///
112 | /// A 16-byte code
113 | protected static byte[] GenerateFooterCode(FbxNodeCollection document)
114 | {
115 | var timestamp = document.GetRelative(timePath1 + "/" + timePath2);
116 | if (timestamp == null)
117 | throw new FbxException(timePath, -1, "No creation timestamp");
118 | try
119 | {
120 | return GenerateFooterCode(
121 | GetTimestampVar(timestamp, "Year"),
122 | GetTimestampVar(timestamp, "Month"),
123 | GetTimestampVar(timestamp, "Day"),
124 | GetTimestampVar(timestamp, "Hour"),
125 | GetTimestampVar(timestamp, "Minute"),
126 | GetTimestampVar(timestamp, "Second"),
127 | GetTimestampVar(timestamp, "Millisecond")
128 | );
129 | }
130 | catch (ArgumentOutOfRangeException)
131 | {
132 | throw new FbxException(timePath, -1, "Invalid timestamp");
133 | }
134 | }
135 |
136 | ///
137 | /// Generates a unique footer code based on a timestamp
138 | ///
139 | ///
140 | ///
141 | ///
142 | ///
143 | ///
144 | ///
145 | ///
146 | /// A 16-byte code
147 | protected static byte[] GenerateFooterCode(
148 | int year, int month, int day,
149 | int hour, int minute, int second, int millisecond)
150 | {
151 | if (year < 0 || year > 9999)
152 | throw new ArgumentOutOfRangeException(nameof(year));
153 | if (month < 0 || month > 12)
154 | throw new ArgumentOutOfRangeException(nameof(month));
155 | if (day < 0 || day > 31)
156 | throw new ArgumentOutOfRangeException(nameof(day));
157 | if (hour < 0 || hour >= 24)
158 | throw new ArgumentOutOfRangeException(nameof(hour));
159 | if (minute < 0 || minute >= 60)
160 | throw new ArgumentOutOfRangeException(nameof(minute));
161 | if (second < 0 || second >= 60)
162 | throw new ArgumentOutOfRangeException(nameof(second));
163 | if (millisecond < 0 || millisecond >= 1000)
164 | throw new ArgumentOutOfRangeException(nameof(millisecond));
165 |
166 | var str = (byte[])sourceId.Clone();
167 | var mangledTime = $"{second:00}{month:00}{hour:00}{day:00}{(millisecond / 10):00}{year:0000}{minute:00}";
168 | var mangledBytes = Encoding.ASCII.GetBytes(mangledTime);
169 | Encrypt(str, mangledBytes);
170 | Encrypt(str, key);
171 | Encrypt(str, mangledBytes);
172 | return str;
173 | }
174 |
175 | ///
176 | /// Writes the FBX footer extension (NB - not the unique footer code)
177 | ///
178 | ///
179 | ///
180 | protected void WriteFooter(BinaryWriter stream, int version)
181 | {
182 | var zeroes = new byte[Math.Max(footerZeroes1, footerZeroes2)];
183 | stream.Write(zeroes, 0, footerZeroes1);
184 | stream.Write(version);
185 | stream.Write(zeroes, 0, footerZeroes2);
186 | stream.Write(extension, 0, extension.Length);
187 | }
188 |
189 | protected static bool AllZero(byte[] array)
190 | {
191 | foreach (var b in array)
192 | if (b != 0)
193 | return false;
194 | return true;
195 | }
196 |
197 | ///
198 | /// Reads and checks the FBX footer extension (NB - not the unique footer code)
199 | ///
200 | ///
201 | ///
202 | /// true if it's compliant
203 | protected bool CheckFooter(BinaryReader stream, FbxVersion version)
204 | {
205 | var buffer = new byte[Math.Max(footerZeroes1, footerZeroes2)];
206 | stream.Read(buffer, 0, footerZeroes1);
207 | bool correct = AllZero(buffer);
208 | var readVersion = stream.ReadInt32();
209 | correct &= (readVersion == (int)version);
210 | stream.Read(buffer, 0, footerZeroes2);
211 | correct &= AllZero(buffer);
212 | stream.Read(buffer, 0, extension.Length);
213 | correct &= CheckEqual(buffer, extension);
214 | return correct;
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/FbxElementDefinition.cs:
--------------------------------------------------------------------------------
1 | namespace AssetRipper.MeshSharp.FBX
2 | {
3 | public class FbxPropertyTemplate
4 | {
5 | public string Name { get; set; }
6 |
7 | public PropertyCollection Properties { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/MeshSharp.FBX/FbxNode.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace AssetRipper.MeshSharp.FBX
4 | {
5 | ///
6 | /// Represents a node in an FBX file
7 | ///
8 | public class FbxNode : FbxNodeCollection
9 | {
10 | ///
11 | /// The node name, which is often a class type
12 | ///
13 | ///
14 | /// The name must be smaller than 256 characters to be written to a binary stream
15 | ///
16 | public string Name { get; set; }
17 |
18 | ///
19 | /// Whether the node is empty of data
20 | ///
21 | public bool IsEmpty => string.IsNullOrEmpty(Name) && Properties.Count == 0 && Nodes.Count == 0;
22 |
23 | ///
24 | /// The list of properties associated with the node
25 | ///
26 | ///
27 | /// Supported types are primitives (apart from byte and char),arrays of primitives, and strings
28 | ///
29 | public List