diff --git a/.editorconfig b/.editorconfig
index a652278..0b2bf57 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -14,3 +14,8 @@ dotnet_diagnostic.UNT0014.severity = error
charset = utf-8
indent_size = 4
indent_style = space
+
+[*.csproj]
+charset = utf-8
+indent_size = 2
+indent_style = space
diff --git a/DependencyDeclaration~/.gitignore b/DependencyDeclaration~/.gitignore
new file mode 100644
index 0000000..1746e32
--- /dev/null
+++ b/DependencyDeclaration~/.gitignore
@@ -0,0 +1,2 @@
+bin
+obj
diff --git a/DependencyDeclaration~/DependencyDeclaration~.csproj b/DependencyDeclaration~/DependencyDeclaration~.csproj
new file mode 100644
index 0000000..813817c
--- /dev/null
+++ b/DependencyDeclaration~/DependencyDeclaration~.csproj
@@ -0,0 +1,15 @@
+
+
+
+
+ netstandard2.0
+ portable
+ ResoniteImportHelper.Internal.DependencyDeclaration
+ true
+ PackageReference
+
+
+
+
+
+
diff --git a/DependencyDeclaration~/Program.cs b/DependencyDeclaration~/Program.cs
new file mode 100644
index 0000000..ce9e473
--- /dev/null
+++ b/DependencyDeclaration~/Program.cs
@@ -0,0 +1,8 @@
+// Please run `dotnet publish` and copy them into `0ReflectionMetadata` respectively.
+namespace ResoniteImportHelper.Internal.DependencyDeclaration
+{
+ internal sealed class Program
+ {
+ private static void Main(string[] args) {}
+ }
+}
diff --git a/DependencyManager~/.gitignore b/DependencyManager~/.gitignore
new file mode 100644
index 0000000..1746e32
--- /dev/null
+++ b/DependencyManager~/.gitignore
@@ -0,0 +1,2 @@
+bin
+obj
diff --git a/DependencyManager~/DependencyManager~.csproj b/DependencyManager~/DependencyManager~.csproj
new file mode 100644
index 0000000..ff72638
--- /dev/null
+++ b/DependencyManager~/DependencyManager~.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ netstandard2.0
+ ResoniteImportHelper.Internal.DependencyManager
+ disable
+ enable
+ 13
+
+
+
+
+
+
+
diff --git a/DependencyManager~/Program.cs b/DependencyManager~/Program.cs
new file mode 100644
index 0000000..b3c1378
--- /dev/null
+++ b/DependencyManager~/Program.cs
@@ -0,0 +1,19 @@
+namespace ResoniteImportHelper.Internal.DependencyManager
+{
+ public sealed class Args(string From, string To)
+ {
+ }
+
+ public sealed class Program
+ {
+ private static void Main(string[] args)
+ {
+ Main0(new Args(args[0], args[1]));
+ }
+
+ public static void Main0(Args args)
+ {
+ // TODO: dotnet publish
+ }
+ }
+}
diff --git a/Editor/0ReflectionMetadata.meta b/Editor/0ReflectionMetadata.meta
new file mode 100644
index 0000000..a8ee96e
--- /dev/null
+++ b/Editor/0ReflectionMetadata.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c75c0b9bc1ac4903acfe5854cac5ffa2
+timeCreated: 1738982722
\ No newline at end of file
diff --git a/Editor/0ReflectionMetadata/AssemblyInfo.cs b/Editor/0ReflectionMetadata/AssemblyInfo.cs
new file mode 100644
index 0000000..c3cee86
--- /dev/null
+++ b/Editor/0ReflectionMetadata/AssemblyInfo.cs
@@ -0,0 +1 @@
+// This file is dummy!
diff --git a/Editor/0ReflectionMetadata/AssemblyInfo.cs.meta b/Editor/0ReflectionMetadata/AssemblyInfo.cs.meta
new file mode 100644
index 0000000..ecf193b
--- /dev/null
+++ b/Editor/0ReflectionMetadata/AssemblyInfo.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 03c4b506f0ef4898848d5c6011e13f78
+timeCreated: 1738982788
\ No newline at end of file
diff --git a/Editor/0ReflectionMetadata/ResoniteImportHelper.0ReflectionMetadata.asmdef b/Editor/0ReflectionMetadata/ResoniteImportHelper.0ReflectionMetadata.asmdef
new file mode 100644
index 0000000..88393e3
--- /dev/null
+++ b/Editor/0ReflectionMetadata/ResoniteImportHelper.0ReflectionMetadata.asmdef
@@ -0,0 +1,5 @@
+{
+ "name": "ResoniteImportHelper.0ReflectionMetadata",
+ "autoReferenced": false,
+ "noEngineReferences": true
+}
diff --git a/Editor/0ReflectionMetadata/ResoniteImportHelper.0ReflectionMetadata.asmdef.meta b/Editor/0ReflectionMetadata/ResoniteImportHelper.0ReflectionMetadata.asmdef.meta
new file mode 100644
index 0000000..5ca3043
--- /dev/null
+++ b/Editor/0ReflectionMetadata/ResoniteImportHelper.0ReflectionMetadata.asmdef.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e40e5567821840249692e1985c2558f0
+timeCreated: 1738982741
\ No newline at end of file
diff --git a/Editor/0ReflectionMetadata/System.Buffers.dll b/Editor/0ReflectionMetadata/System.Buffers.dll
new file mode 100644
index 0000000..c0970c0
Binary files /dev/null and b/Editor/0ReflectionMetadata/System.Buffers.dll differ
diff --git a/Editor/0ReflectionMetadata/System.Buffers.dll.meta b/Editor/0ReflectionMetadata/System.Buffers.dll.meta
new file mode 100644
index 0000000..fade708
--- /dev/null
+++ b/Editor/0ReflectionMetadata/System.Buffers.dll.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1179d56e7dd8425ea87d92f3081f4b04
+timeCreated: 1738995205
\ No newline at end of file
diff --git a/Editor/0ReflectionMetadata/System.Collections.Immutable.dll b/Editor/0ReflectionMetadata/System.Collections.Immutable.dll
new file mode 100644
index 0000000..b81b2ee
Binary files /dev/null and b/Editor/0ReflectionMetadata/System.Collections.Immutable.dll differ
diff --git a/Editor/0ReflectionMetadata/System.Collections.Immutable.dll.meta b/Editor/0ReflectionMetadata/System.Collections.Immutable.dll.meta
new file mode 100644
index 0000000..973e5fb
--- /dev/null
+++ b/Editor/0ReflectionMetadata/System.Collections.Immutable.dll.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b2c6c8e045a34d1b870fbce0af11ba60
+timeCreated: 1738995205
\ No newline at end of file
diff --git a/Editor/0ReflectionMetadata/System.Memory.dll b/Editor/0ReflectionMetadata/System.Memory.dll
new file mode 100644
index 0000000..1e6aef8
Binary files /dev/null and b/Editor/0ReflectionMetadata/System.Memory.dll differ
diff --git a/Editor/0ReflectionMetadata/System.Memory.dll.meta b/Editor/0ReflectionMetadata/System.Memory.dll.meta
new file mode 100644
index 0000000..1686241
--- /dev/null
+++ b/Editor/0ReflectionMetadata/System.Memory.dll.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 749c744de6664ab4b2de77f1634f6e2b
+timeCreated: 1738995205
\ No newline at end of file
diff --git a/Editor/0ReflectionMetadata/System.Numerics.Vectors.dll b/Editor/0ReflectionMetadata/System.Numerics.Vectors.dll
new file mode 100644
index 0000000..a808165
Binary files /dev/null and b/Editor/0ReflectionMetadata/System.Numerics.Vectors.dll differ
diff --git a/Editor/0ReflectionMetadata/System.Numerics.Vectors.dll.meta b/Editor/0ReflectionMetadata/System.Numerics.Vectors.dll.meta
new file mode 100644
index 0000000..9626c74
--- /dev/null
+++ b/Editor/0ReflectionMetadata/System.Numerics.Vectors.dll.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 80da7bb84a7c46c58aeb4c6f5a759da0
+timeCreated: 1738995205
\ No newline at end of file
diff --git a/Editor/0ReflectionMetadata/System.Reflection.Metadata.dll b/Editor/0ReflectionMetadata/System.Reflection.Metadata.dll
new file mode 100644
index 0000000..b7e6a89
Binary files /dev/null and b/Editor/0ReflectionMetadata/System.Reflection.Metadata.dll differ
diff --git a/Editor/0ReflectionMetadata/System.Reflection.Metadata.dll.meta b/Editor/0ReflectionMetadata/System.Reflection.Metadata.dll.meta
new file mode 100644
index 0000000..671d271
--- /dev/null
+++ b/Editor/0ReflectionMetadata/System.Reflection.Metadata.dll.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9977817ffec64db294e3d3b7462b30e8
+timeCreated: 1738995205
\ No newline at end of file
diff --git a/Editor/0ReflectionMetadata/System.Runtime.CompilerServices.Unsafe.dll b/Editor/0ReflectionMetadata/System.Runtime.CompilerServices.Unsafe.dll
new file mode 100644
index 0000000..491a80a
Binary files /dev/null and b/Editor/0ReflectionMetadata/System.Runtime.CompilerServices.Unsafe.dll differ
diff --git a/Editor/0ReflectionMetadata/System.Runtime.CompilerServices.Unsafe.dll.meta b/Editor/0ReflectionMetadata/System.Runtime.CompilerServices.Unsafe.dll.meta
new file mode 100644
index 0000000..37d3d77
--- /dev/null
+++ b/Editor/0ReflectionMetadata/System.Runtime.CompilerServices.Unsafe.dll.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3fdc17ab0d7b4ede9e4c2c69f75dcdd0
+timeCreated: 1738995205
\ No newline at end of file
diff --git a/Editor/Package.meta b/Editor/Package.meta
new file mode 100644
index 0000000..20a8720
--- /dev/null
+++ b/Editor/Package.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0836cbbc0af740aa8ff7def34f766bb4
+timeCreated: 1730750481
\ No newline at end of file
diff --git a/Editor/Package/Asset.meta b/Editor/Package/Asset.meta
new file mode 100644
index 0000000..e093d04
--- /dev/null
+++ b/Editor/Package/Asset.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: daeeb1234063493baf68eea4b2e718ef
+timeCreated: 1739006385
\ No newline at end of file
diff --git a/Editor/Package/Asset/Inspector.meta b/Editor/Package/Asset/Inspector.meta
new file mode 100644
index 0000000..bc3c9df
--- /dev/null
+++ b/Editor/Package/Asset/Inspector.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0740328faa0a455db6708e3106acb763
+timeCreated: 1739006424
\ No newline at end of file
diff --git a/Editor/Package/Asset/Inspector/MainTreeAssetInspector.cs b/Editor/Package/Asset/Inspector/MainTreeAssetInspector.cs
new file mode 100644
index 0000000..1878ed1
--- /dev/null
+++ b/Editor/Package/Asset/Inspector/MainTreeAssetInspector.cs
@@ -0,0 +1,32 @@
+using System.IO;
+using ResoniteImportHelper.Editor.Package.Asset.Types;
+using UnityEngine;
+using UnityEngine.UIElements;
+using CustomInspector = UnityEditor.CustomEditor;
+using UserMadeInspector = UnityEditor.Editor;
+
+namespace ResoniteImportHelper.Package.Asset.Inspector
+{
+ [CustomInspector(typeof(MainTreeAsset))]
+ public class MainTreeAssetInspector: UserMadeInspector
+ {
+ public override VisualElement CreateInspectorGUI()
+ {
+ var root = new VisualElement();
+
+ root.Add(new Label("Content"));
+ var b = new Button(() =>
+ {
+ var x = Path.GetTempFileName();
+ File.WriteAllText(x, (this.target as MainTreeAsset)!.text);
+ Debug.Log($"Wrote full text temporary file located in {x}.");
+ });
+ b.Add(new Label("Write whole text to temporary file"));
+ root.Add(b);
+
+ root.Add(new TextField() { multiline = true, value = (this.target as MainTreeAsset)!.text.Substring(0, 5000) });
+
+ return root;
+ }
+ }
+}
diff --git a/Editor/Package/Asset/Inspector/MainTreeAssetInspector.cs.meta b/Editor/Package/Asset/Inspector/MainTreeAssetInspector.cs.meta
new file mode 100644
index 0000000..bc0b9ca
--- /dev/null
+++ b/Editor/Package/Asset/Inspector/MainTreeAssetInspector.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0f5b9bda1bd44025bb5dbb6dd3dcac94
+timeCreated: 1739006610
\ No newline at end of file
diff --git a/Editor/Package/Asset/Inspector/ResoniteImportHelper.Package.Asset.Inspector.asmdef b/Editor/Package/Asset/Inspector/ResoniteImportHelper.Package.Asset.Inspector.asmdef
new file mode 100644
index 0000000..3d29ad1
--- /dev/null
+++ b/Editor/Package/Asset/Inspector/ResoniteImportHelper.Package.Asset.Inspector.asmdef
@@ -0,0 +1,7 @@
+{
+ "name": "ResoniteImportHelper.Package.Asset.Inspector",
+ "rootNamespace": "global::ResoniteImportHelper.Package.Asset.Inspector",
+ "autoReferenced": false,
+ "includePlatforms": ["Editor"],
+ "references":[ "ResoniteImportHelper.Package.Asset.Types" ]
+}
diff --git a/Editor/Package/Asset/Inspector/ResoniteImportHelper.Package.Asset.Inspector.asmdef.meta b/Editor/Package/Asset/Inspector/ResoniteImportHelper.Package.Asset.Inspector.asmdef.meta
new file mode 100644
index 0000000..4219f12
--- /dev/null
+++ b/Editor/Package/Asset/Inspector/ResoniteImportHelper.Package.Asset.Inspector.asmdef.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d363f0985d5e4a30b79223dcb9e379a1
+timeCreated: 1739006442
\ No newline at end of file
diff --git a/Editor/Package/Asset/Types.meta b/Editor/Package/Asset/Types.meta
new file mode 100644
index 0000000..46351ce
--- /dev/null
+++ b/Editor/Package/Asset/Types.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3c81fcb497654d5d9c520551672074c3
+timeCreated: 1739006417
\ No newline at end of file
diff --git a/Editor/Package/Asset/Types/MainTreeAsset.cs b/Editor/Package/Asset/Types/MainTreeAsset.cs
new file mode 100644
index 0000000..7bea5ae
--- /dev/null
+++ b/Editor/Package/Asset/Types/MainTreeAsset.cs
@@ -0,0 +1,14 @@
+
+using UnityEngine;
+
+namespace ResoniteImportHelper.Editor.Package.Asset.Types
+{
+ public class MainTreeAsset: ScriptableObject
+ {
+ public string text;
+ public MainTreeAsset(string text)
+ {
+ this.text = text;
+ }
+ }
+}
diff --git a/Editor/Package/Asset/Types/MainTreeAsset.cs.meta b/Editor/Package/Asset/Types/MainTreeAsset.cs.meta
new file mode 100644
index 0000000..20b895a
--- /dev/null
+++ b/Editor/Package/Asset/Types/MainTreeAsset.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 232cfa3213d848ab91a73f26a4cea290
+timeCreated: 1739006568
\ No newline at end of file
diff --git a/Editor/Package/Asset/Types/ResoniteImportHelper.Package.Asset.Types.asmdef b/Editor/Package/Asset/Types/ResoniteImportHelper.Package.Asset.Types.asmdef
new file mode 100644
index 0000000..1186eeb
--- /dev/null
+++ b/Editor/Package/Asset/Types/ResoniteImportHelper.Package.Asset.Types.asmdef
@@ -0,0 +1,5 @@
+{
+ "name": "ResoniteImportHelper.Package.Asset.Types",
+ "rootNamespace": "global::ResoniteImportHelper.Package.Asset.Types",
+ "autoReferenced": false
+}
diff --git a/Editor/Package/Asset/Types/ResoniteImportHelper.Package.Asset.Types.asmdef.meta b/Editor/Package/Asset/Types/ResoniteImportHelper.Package.Asset.Types.asmdef.meta
new file mode 100644
index 0000000..c26f5ee
--- /dev/null
+++ b/Editor/Package/Asset/Types/ResoniteImportHelper.Package.Asset.Types.asmdef.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 687c0e6bcb684fae8b0dad77543471d8
+timeCreated: 1739006485
\ No newline at end of file
diff --git a/Editor/Package/Import.meta b/Editor/Package/Import.meta
new file mode 100644
index 0000000..a4a52f2
--- /dev/null
+++ b/Editor/Package/Import.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 42f0889f107f4cabbf716e931570a98b
+timeCreated: 1730750504
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize.meta b/Editor/Package/Import/Deserialize.meta
new file mode 100644
index 0000000..2582e24
--- /dev/null
+++ b/Editor/Package/Import/Deserialize.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a703848c3a0e463cbb5943f416ec6e5b
+timeCreated: 1730802987
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Bitmap.meta b/Editor/Package/Import/Deserialize/Bitmap.meta
new file mode 100644
index 0000000..0de22f6
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Bitmap.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 225e25a473ad486fba210bde154695f0
+timeCreated: 1730803243
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Bitmap/types.cs b/Editor/Package/Import/Deserialize/Bitmap/types.cs
new file mode 100644
index 0000000..a6e5a8b
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Bitmap/types.cs
@@ -0,0 +1,44 @@
+using System;
+using Newtonsoft.Json;
+using ResoniteImportHelper.Package.Import.Deserialize.Metadata;
+
+namespace ResoniteImportHelper.Package.Import.Deserialize.Bitmap
+{
+ internal sealed class BitmapTag: IAssetTag {}
+
+ [Serializable]
+ public sealed class BitmapMetadata : IMetadata
+ {
+ [JsonProperty("width")]
+ public uint Width;
+
+ [JsonProperty("height")]
+ public uint Height;
+
+ [JsonProperty("mipMapCount")]
+ public uint MipMapCount;
+
+ ///
+ /// Example. `png`
+ ///
+ [JsonProperty("baseFormat")]
+ public string Format;
+
+ [JsonProperty("bitsPerPixel")]
+ public uint BitsPerPixel;
+
+ [JsonProperty("channelCount")]
+ public uint ChannelCount;
+
+ [JsonIgnore]
+ public uint BitsPerChannel => BitsPerPixel / ChannelCount;
+
+ ///
+ /// Example. `FullyOpaque`
+ ///
+ /// Example. `Alpha`
+ ///
+ [JsonProperty("alphaData")]
+ public string AlphaTreat;
+ }
+}
diff --git a/Editor/Package/Import/Deserialize/Bitmap/types.cs.meta b/Editor/Package/Import/Deserialize/Bitmap/types.cs.meta
new file mode 100644
index 0000000..22b3271
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Bitmap/types.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: db1c706255334e0a86586c69cb63c27f
+timeCreated: 1730803248
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/IAssetTag.cs b/Editor/Package/Import/Deserialize/IAssetTag.cs
new file mode 100644
index 0000000..89d4a52
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/IAssetTag.cs
@@ -0,0 +1,7 @@
+namespace ResoniteImportHelper.Package.Import.Deserialize.Metadata
+{
+ internal interface IAssetTag
+ {
+
+ }
+}
diff --git a/Editor/Package/Import/Deserialize/IAssetTag.cs.meta b/Editor/Package/Import/Deserialize/IAssetTag.cs.meta
new file mode 100644
index 0000000..25009cd
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/IAssetTag.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6951b18f0a3e426abb37191687968cae
+timeCreated: 1730803938
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/IMetadata.cs b/Editor/Package/Import/Deserialize/IMetadata.cs
new file mode 100644
index 0000000..a2e04c5
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/IMetadata.cs
@@ -0,0 +1,4 @@
+namespace ResoniteImportHelper.Package.Import.Deserialize.Metadata
+{
+ internal interface IMetadata where TAssetTag : IAssetTag {}
+}
diff --git a/Editor/Package/Import/Deserialize/IMetadata.cs.meta b/Editor/Package/Import/Deserialize/IMetadata.cs.meta
new file mode 100644
index 0000000..fa89ffd
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/IMetadata.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 59cfc29569a842c6813662999782e96d
+timeCreated: 1730803867
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Mesh.meta b/Editor/Package/Import/Deserialize/Mesh.meta
new file mode 100644
index 0000000..03e8985
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Mesh.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: fd1f4642f74a4dc499e38051d82470f7
+timeCreated: 1730803089
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Mesh/types.cs b/Editor/Package/Import/Deserialize/Mesh/types.cs
new file mode 100644
index 0000000..2c2412f
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Mesh/types.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Newtonsoft.Json;
+using ResoniteImportHelper.Package.Import.Deserialize.Metadata;
+using ResoniteImportHelper.Package.Import.Deserialize.Shape;
+
+namespace ResoniteImportHelper.Package.Import.Deserialize.Mesh
+{
+ internal sealed class MeshTag : IAssetTag {}
+
+ [Serializable]
+ internal sealed class MeshMetadata : IMetadata
+ {
+ [JsonProperty("bones")]
+ internal MeshBound Bounds;
+
+ [JsonProperty("submeshMetadata")]
+ internal List SubMeshes;
+
+ [JsonProperty("boneMetadata")]
+ internal List Bones;
+
+ [JsonProperty("approximateBoneBounds")]
+ internal List ApproximateBoneBound;
+ }
+
+ [Serializable]
+ internal sealed class MeshBound
+ {
+ [JsonProperty("min")]
+ internal Point3Mut Min;
+
+ [JsonProperty("max")]
+ internal Point3Mut Max;
+
+ public override string ToString()
+ {
+ return $"({ShortenInfinityP(Min)})..({ShortenInfinityP(Max)})";
+
+ string ShortenInfinityP(Point3Mut p) =>
+ $"{ShortenInfinity(p.X)}, {ShortenInfinity(p.Y)}, {ShortenInfinity(p.Z)}";
+
+ string ShortenInfinity(float v)
+ {
+ if (float.IsPositiveInfinity(v))
+ {
+ return "+inf";
+ }
+
+ // ReSharper disable once ConvertIfStatementToReturnStatement
+ if (float.IsNegativeInfinity(v))
+ {
+ return "-inf";
+ }
+
+ return v.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+ }
+
+ [Serializable]
+ internal sealed class SubMeshMetadata
+ {
+ [JsonProperty("elementCount")]
+ internal int Polys;
+
+ [JsonProperty("bounds")]
+ internal MeshBound Bounds = null!;
+ }
+
+ [Serializable]
+ internal sealed class BoneMetadata
+ {
+ [JsonProperty("weight0count")]
+ internal int Weight0Count;
+
+ [JsonProperty("weight1count")]
+ internal int Weight1Count;
+
+ [JsonProperty("weight2count")]
+ internal int Weight2Count;
+
+ [JsonProperty("weight3count")]
+ internal int Weight3Count;
+
+ [JsonProperty("bounds")]
+ internal MeshBound Bounds = null!;
+ }
+
+ [Serializable]
+ internal sealed class ApproximateBoneBound
+ {
+ [JsonProperty("rootBoneIndex")]
+ internal int RootBoneIndex;
+
+ [JsonProperty("bounds")]
+ internal Sphere3dMut Bounds;
+ }
+}
diff --git a/Editor/Package/Import/Deserialize/Mesh/types.cs.meta b/Editor/Package/Import/Deserialize/Mesh/types.cs.meta
new file mode 100644
index 0000000..15c5ffa
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Mesh/types.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: dcd7b22fd3484e1a924f51591793176b
+timeCreated: 1730803095
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/ResoniteImportHelper.Package.Deserialize.asmdef b/Editor/Package/Import/Deserialize/ResoniteImportHelper.Package.Deserialize.asmdef
new file mode 100644
index 0000000..23859dc
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/ResoniteImportHelper.Package.Deserialize.asmdef
@@ -0,0 +1,6 @@
+{
+ "name": "ResoniteImportHelper.Package.Deserialize",
+ "rootNamespace": "global::ResoniteImportHelper.Package.Deserialize",
+ "includePlatforms": ["Editor"]
+
+}
diff --git a/Editor/Package/Import/Deserialize/ResoniteImportHelper.Package.Deserialize.asmdef.meta b/Editor/Package/Import/Deserialize/ResoniteImportHelper.Package.Deserialize.asmdef.meta
new file mode 100644
index 0000000..a462e90
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/ResoniteImportHelper.Package.Deserialize.asmdef.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b5993fcb75a54a2babaf459e17e7fc75
+timeCreated: 1739007878
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Shape.meta b/Editor/Package/Import/Deserialize/Shape.meta
new file mode 100644
index 0000000..3d932b3
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Shape.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 56b21e5e2ab848b7a2f5cb76dc40f5ee
+timeCreated: 1730803006
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Shape/Point3Mut.cs b/Editor/Package/Import/Deserialize/Shape/Point3Mut.cs
new file mode 100644
index 0000000..f08161b
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Shape/Point3Mut.cs
@@ -0,0 +1,18 @@
+using System;
+using Newtonsoft.Json;
+
+namespace ResoniteImportHelper.Package.Import.Deserialize.Shape
+{
+ [Serializable]
+ internal struct Point3Mut
+ {
+ [JsonProperty("x")]
+ internal float X;
+
+ [JsonProperty("y")]
+ internal float Y;
+
+ [JsonProperty("z")]
+ internal float Z;
+ }
+}
diff --git a/Editor/Package/Import/Deserialize/Shape/Point3Mut.cs.meta b/Editor/Package/Import/Deserialize/Shape/Point3Mut.cs.meta
new file mode 100644
index 0000000..3934502
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Shape/Point3Mut.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 517e538ab4af4924a5bc87cc4215bba2
+timeCreated: 1730803043
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Shape/Sphere3dMut.cs b/Editor/Package/Import/Deserialize/Shape/Sphere3dMut.cs
new file mode 100644
index 0000000..befec44
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Shape/Sphere3dMut.cs
@@ -0,0 +1,15 @@
+using System;
+using Newtonsoft.Json;
+
+namespace ResoniteImportHelper.Package.Import.Deserialize.Shape
+{
+ [Serializable]
+ internal struct Sphere3dMut
+ {
+ [JsonProperty("center")]
+ internal Point3Mut Center;
+
+ [JsonProperty("radius")]
+ internal float Radius;
+ }
+}
diff --git a/Editor/Package/Import/Deserialize/Shape/Sphere3dMut.cs.meta b/Editor/Package/Import/Deserialize/Shape/Sphere3dMut.cs.meta
new file mode 100644
index 0000000..02660f9
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Shape/Sphere3dMut.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 24a1cd75a8f0451480f0ca1c4b833b35
+timeCreated: 1730803164
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Support.meta b/Editor/Package/Import/Deserialize/Support.meta
new file mode 100644
index 0000000..8d8da6e
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Support.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 560a553de9c54b6b859204e350039619
+timeCreated: 1738998104
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Support/IIdentifiable.cs b/Editor/Package/Import/Deserialize/Support/IIdentifiable.cs
new file mode 100644
index 0000000..6906718
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Support/IIdentifiable.cs
@@ -0,0 +1,7 @@
+namespace ResoniteImportHelper.Package.Import.Deserialize.Support
+{
+ public interface IIdentifiable
+ {
+ public string GetIdentifier();
+ }
+}
diff --git a/Editor/Package/Import/Deserialize/Support/IIdentifiable.cs.meta b/Editor/Package/Import/Deserialize/Support/IIdentifiable.cs.meta
new file mode 100644
index 0000000..de79394
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Support/IIdentifiable.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c799e747e83a4a6c93302992171d6579
+timeCreated: 1739029369
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Support/IdentifiableDataCell.cs b/Editor/Package/Import/Deserialize/Support/IdentifiableDataCell.cs
new file mode 100644
index 0000000..7f2de86
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Support/IdentifiableDataCell.cs
@@ -0,0 +1,8 @@
+namespace ResoniteImportHelper.Package.Import.Deserialize.Support
+{
+ public struct IdentifiableDataCell
+ {
+ public string ID;
+ public T Data;
+ }
+}
diff --git a/Editor/Package/Import/Deserialize/Support/IdentifiableDataCell.cs.meta b/Editor/Package/Import/Deserialize/Support/IdentifiableDataCell.cs.meta
new file mode 100644
index 0000000..f649763
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Support/IdentifiableDataCell.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d73700f970084c6496fc524ae8def4c6
+timeCreated: 1739007753
\ No newline at end of file
diff --git a/Editor/Package/Import/Deserialize/Support/UniquePointer.cs b/Editor/Package/Import/Deserialize/Support/UniquePointer.cs
new file mode 100644
index 0000000..35698a6
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Support/UniquePointer.cs
@@ -0,0 +1,16 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+
+namespace ResoniteImportHelper.Package.Import.Deserialize.Support
+{
+ public struct UniquePointer : IIdentifiable
+ {
+ [UsedImplicitly]
+ public string ID;
+
+ [JsonProperty("Data")]
+ public string ReferenceeID;
+
+ public string GetIdentifier() => ID;
+ }
+}
diff --git a/Editor/Package/Import/Deserialize/Support/UniquePointer.cs.meta b/Editor/Package/Import/Deserialize/Support/UniquePointer.cs.meta
new file mode 100644
index 0000000..2c28483
--- /dev/null
+++ b/Editor/Package/Import/Deserialize/Support/UniquePointer.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ae2088a4e2a24050877c054ee346a79a
+timeCreated: 1739005657
\ No newline at end of file
diff --git a/Editor/Package/Import/Importer.cs b/Editor/Package/Import/Importer.cs
new file mode 100644
index 0000000..daaa8de
--- /dev/null
+++ b/Editor/Package/Import/Importer.cs
@@ -0,0 +1,356 @@
+#nullable enable
+// TODO: そのうち Newtonsoft.Json も JsonUtility も使わない、新しいJSONパーサーを作る。
+// 設計思想:
+// 1. 型システムとアナライザーによってスキーマが論理的に間違っているときは絶対にコンパイルエラーになる。
+// 2. エンドユーザーが意識するのはジェネリックな関数である serializer.Serialize(T): CharSequence と
+// deserializer.Deserialize(CharSequence): T のみ。
+// 3. untyped なパースを行う際は dynamic 型を用い、 JSONの抽象構文木を意識させるAPI設計はしない。
+// 4. 相互運用性を最大限高めるため、入出力において UTF-8 と UTF-16 のどちらも対応する。
+// 5. 属性を用いたソースジェネレーターによってボイラープレートを最小限にしながらパフォーマンスの良いコードを生成する。
+// 6. メモリアロケーションを極力行わない。
+// 7. Unity でも動作する C# のサブセットで記述する。
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Text;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Bson;
+using ResoniteImportHelper.Editor.Package.Asset.Types;
+using ResoniteImportHelper.Editor.Package.Import.Stub;
+using ResoniteImportHelper.Package.Import.Deserialize.Bitmap;
+using ResoniteImportHelper.Package.Import.Metadata;
+using ResoniteImportHelper.Package.Import.Stub;
+using UnityEditor.AssetImporters;
+using UnityEngine;
+
+namespace ResoniteImportHelper.Package.Import
+{
+ [ScriptedImporter(1, "resonitepackage")]
+ internal class Importer : ScriptedImporter
+ {
+ private delegate void OnInitializeGameObject(GameObject target, string deserializedSlotId);
+
+ public override void OnImportAsset(AssetImportContext ctx)
+ {
+ // TODO: adjust this later
+ var rootGameObject = new GameObject();
+ ctx.AddObjectToAsset("$$main", rootGameObject);
+ ctx.SetMainObject(rootGameObject);
+
+ using var zipArchive = ZipFile.OpenRead(ctx.assetPath);
+
+ var mainRecordEntry = zipArchive.GetEntry("R-Main.record");
+ if (mainRecordEntry == null)
+ {
+ throw new FormatException("ResonitePackage must contain R-Main.record under the archive root.");
+ }
+
+ {
+ // TODO: validate only -> UTF-8 only JSON parser to faster parse?
+ var recordManifest = ReadZipArchiveContentAsUtf8Sequence(mainRecordEntry);
+ Debug.Log($"root decoded: {recordManifest}");
+
+ var pd = JsonConvert.DeserializeObject(recordManifest);
+ if (pd is null)
+ {
+ throw new FormatException("Failed to deserialize descriptor");
+ }
+
+ var manifests = pd.AssetManifest;
+
+ var metadataArchiveEntries = zipArchive.Entries.Where(a => a.FullName.StartsWith("Metadata/")).ToList();
+ var mainArchiveEntries = zipArchive.Entries.Where(a => a.FullName.StartsWith("Assets/")).ToList();
+
+ var mainDataTreeEntry =
+ mainArchiveEntries.SingleOrDefault(e => e.Name == pd.AssetUri.Replace("packdb:///", ""));
+
+ if (mainDataTreeEntry is null)
+ {
+ throw new FormatException($"Failed to find main DataTreeDirectory: {pd.AssetUri} is not contained by the package.");
+ }
+
+ var mainDataTree = ReadZipArchiveContent(mainDataTreeEntry);
+ if (!IsValidDataTreeHeader(mainDataTree[0..4]))
+ {
+ ctx.LogImportError("The main data tree do not have correct header magic.");
+ throw new System.FormatException("The main data tree do not have correct header magic.");
+ }
+
+ Debug.Log("mode");
+ // TODO: 本来ここはVarIntで読むのが正しいが、一旦無視
+ var compressionMode = mainDataTree[8];
+ Stream? stream;
+ if (compressionMode == 3)
+ {
+ Debug.Log("Main record is compressed by Brotli.");
+ stream = new BrotliStream(new MemoryStream(mainDataTree[9..]),
+ CompressionMode.Decompress);
+ } else if (compressionMode == 0)
+ {
+ Debug.Log("Main record is not compressed.");
+ stream = new MemoryStream(mainDataTree[9..]);
+ }
+ else
+ {
+ ctx.LogImportWarning($"Main record is compressed by unrecognized format. (kind: {compressionMode}");
+ stream = null;
+ }
+
+ if (stream != null)
+ {
+ using (stream)
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ using var bsonTree1 = new BsonReader(stream);
+#pragma warning restore CS0618 // Type or member is obsolete
+ var s = new JsonSerializer();
+ var rawRoot = s.Deserialize(bsonTree1);
+ var toAdd = ScriptableObject.CreateInstance();
+ toAdd.name = "DecodedMainRecord";
+ toAdd.text = rawRoot?.ToString() ?? "null";
+ ctx.AddObjectToAsset("DecodedMainRecord", toAdd);
+ }
+
+ Stream stream2;
+ if (compressionMode == 3)
+ {
+ Debug.Log("Main record is compressed by Brotli.");
+ stream2 = new BrotliStream(new MemoryStream(mainDataTree[9..]),
+ CompressionMode.Decompress);
+ } else if (compressionMode == 0)
+ {
+ Debug.Log("Main record is not compressed.");
+ stream2 = new MemoryStream(mainDataTree[9..]);
+ }
+ else
+ {
+ throw new NotImplementedException("unreachable");
+ }
+
+ using (stream2)
+ {
+ var graphRoot = DeserializeFromBsonStream(stream2);
+ if (graphRoot == null)
+ {
+ ctx.LogImportWarning("root deserialize failed");
+ }
+ else
+ {
+ ConstructBaseHierarchy(graphRoot, rootGameObject, (go, id) =>
+ {
+ Debug.Log($"{go} was initialized! ID: {id}");
+ ctx.AddObjectToAsset($"GameObject_{id}", go);
+ });
+ ImportTexture(ctx, graphRoot, rootGameObject, mainArchiveEntries);
+ ImportMesh(ctx, graphRoot, rootGameObject);
+ ImportMaterial(ctx, graphRoot, rootGameObject);
+ ConstructRenderers(ctx, graphRoot, rootGameObject);
+ var animator = ConstructAnimator(ctx, graphRoot, rootGameObject);
+ ConstructAnimation(ctx, graphRoot, rootGameObject, animator);
+ }
+ }
+ }
+ }
+ }
+
+ private static TValue? DeserializeFromBsonStream(Stream stream)
+ {
+ var s = new JsonSerializer();
+#pragma warning disable CS0618 // Type or member is obsolete
+ using var bsonTree = new BsonReader(stream);
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ var value = s.Deserialize(bsonTree);
+
+ return value;
+ }
+
+ private static void ConstructBaseHierarchy(GraphRoot root, GameObject rootGo, OnInitializeGameObject onInitializeGameObject)
+ {
+ Debug.Log($"Software build: {root.VersionNumber}");
+ Debug.Log($"Feature Flags: {Prettify(root.FeatureFlags)}");
+ Debug.Log($"Types: {Prettify(root.Types)}");
+ Debug.Log($"TypeVersions: {Prettify(root.TypeVersions)}");
+ Debug.Log($"Object: {root.RootSlot}");
+ Debug.Log($"Assets: {Prettify(root.ContainedAssets)}");
+
+ AttachSlotRecursively(new Dictionary(), root.RootSlot, rootGo, onInitializeGameObject, true);
+ }
+
+ private static void AttachSlotRecursively(Dictionary versions, Slot slot, GameObject go, OnInitializeGameObject onInitializeGameObject, bool isRoot)
+ {
+ var slotName = slot.Name.Data;
+ if (versions.TryGetValue(slotName, out var previousVersion))
+ {
+ var currentVersion = previousVersion + 1;
+ Debug.Log($"Duplicated name: {slotName} on ID={slot.ID}; version: {previousVersion} -> {currentVersion}");
+ versions[slotName] = currentVersion;
+ slotName += $".{currentVersion}";
+ }
+ else
+ {
+ versions[slotName] = 0;
+ }
+ // go.nameはヒエラルキー上で一意である必要があるらしい。知るか!
+ go.name = slotName;
+ go.transform.SetLocalPositionAndRotation(slot.Position, slot.Rotation);
+ go.transform.localScale = slot.Scale;
+ if (!isRoot)
+ {
+ onInitializeGameObject(go, slot.ID);
+ }
+
+ foreach (var child in slot.Children)
+ {
+ var childGo = new GameObject();
+ childGo.transform.SetParent(go.transform);
+ AttachSlotRecursively(versions, child, childGo, onInitializeGameObject, false);
+ }
+ }
+
+ private static void ImportTexture(AssetImportContext ctx, GraphRoot root, GameObject rootGo, IMetadataAccessor metadataAccessor, IAssetVariantAccessor assetVariantAccessor)
+ {
+ var staticTexture2DProviderCandidate = root.Types
+ .Select((e, i) => (e.Parse(), i))
+ .Cast<((AssemblyName Assembly, TypeName typeName) parsed, int i)?>()
+ .FirstOrDefault(t => t!.Value.parsed.Assembly.FullName == "FrooxEngine" && t!.Value.parsed.typeName.FullName == "FrooxEngine.StaticTexture2D");
+
+ if (!staticTexture2DProviderCandidate.HasValue)
+ {
+ ctx.LogImportWarning("This target do not have StaticTexture2D, skipping.");
+ return;
+ }
+
+ var staticTexture2DProviderIndex = staticTexture2DProviderCandidate.Value.i;
+
+ root.ContainedAssets
+ .Where(a => a.ComponentTableIndex == staticTexture2DProviderIndex)
+ .Select(a => a.AsTyped())
+ .Select(st => new
+ {
+ st.GetComponentData().IsNormalMap,
+ AssetIdentifier = st.GetComponentData().GetAssetIdentifier(),
+ WrapModeU = st.GetComponentData().GetWrapModeU(),
+ WrapModeV = st.GetComponentData().GetWrapModeV(),
+ Metadata = metadataAccessor.GetAndDeserialize(
+ $"{st.GetComponentData().GetAssetIdentifier()}.bitmap"),
+ TextureFormat = assetVariantAccessor.GetByKey(st.GetComponentData().GetAssetIdentifier()).First((_) => true).FileName.Split('&').Select(w => w.Split('=', 2)).First(a => a[0] == "compression")[1]
+ })
+ .Select(data =>
+ {
+ new Texture2D((int)data.Metadata.Width, (int)data.Metadata.Height, TextureFormat, 0, true, true);
+ });
+ // TODO: 今後Slotにあるコンポーネントを死ぬほど反復するが、それはO(nm)になって遅くないか?
+ // 何らかのデータ構造の導入を検討するべき。
+
+ /*
+ TODO:
+ Get variants and load largest one.
+ -- https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Texture2D.LoadRawTextureData.html
+ How can we handle mips=True case?
+ * BC3 (w/ C.C.) => DXT5_Crunched
+ * BC3 (w/o C.C.) => DXT5
+ * BC1 (w/ C.C.) => DXT1_Crunched
+ * BC1 (w/o C.C.) => DXT1
+ * RawRGBA => RGBA32
+ https://www.webtech.co.jp/blog/optpix_labs/format/6993/
+ If there's no one, fallback to PNG and upload it with initial properties.
+ Considering DXT7 on fallback-ed import?
+ */
+
+ ctx.LogImportWarning("This phase (ImportTexture) is not fully-implemented yet.");
+ }
+
+ private static void ImportMesh(AssetImportContext ctx, GraphRoot root, GameObject rootGo)
+ {
+ // TODO: Read Metadata/*.mesh (represented as JSON).
+ ctx.LogImportWarning("This phase (ImportMesh) is not implemented yet.");
+ }
+
+ private static void ImportMaterial(AssetImportContext ctx, GraphRoot root, GameObject rootGo)
+ {
+ // TODO: Branch to control whether import as lilToon (or any toon shader that supports Matcap) or Standard Material (to emulate PBS).
+ ctx.LogImportWarning("This phase (ImportMaterial) is not implemented yet.");
+ }
+
+ private static void ConstructRenderers(AssetImportContext ctx, GraphRoot root, GameObject rootGo)
+ {
+ // TODO: Read both MeshRenderer and SkinnedMeshRenderer.
+ ctx.LogImportWarning("This phase (ConstructRenderers) is not implemented yet.");
+ }
+
+ private static Animator ConstructAnimator(AssetImportContext ctx, GraphRoot root, GameObject rootGo)
+ {
+ var animator = rootGo.AddComponent();
+ // TODO: read bones from VRIK component.
+ ctx.LogImportWarning("This phase (ConstructAnimator) is not fully-implemented yet.");
+ return animator;
+ }
+
+ private static void ConstructAnimation(AssetImportContext ctx, GraphRoot root, GameObject rootGo, Animator animator)
+ {
+ ctx.LogImportWarning("This phase(ConstructAnimation) is not implemented yet.");
+ }
+
+ private static string Prettify(IEnumerable> dictionary)
+ {
+ return string.Join(",\n", dictionary.Select((a) => $"{a.Key}: {(a.Value)}"));
+ }
+
+ private static string Prettify(IEnumerable enumerable)
+ {
+ return string.Join(",\n", enumerable);
+ }
+
+ private static bool IsValidDataTreeHeader(ReadOnlySpan header)
+ {
+ return header.Length == 4 && header[0] == 'F' && header[1] == 'r' && header[2] == 'D' && header[3] == 'T';
+ }
+
+ private static byte[] ReadZipArchiveContent(ZipArchiveEntry entry)
+ {
+ var content = new byte[entry.Length];
+ {
+ using var meta = entry.Open();
+ using var s = new BufferedStream(meta);
+
+ s.Read(content);
+ }
+
+ return content;
+ }
+
+ private static string ReadZipArchiveContentAsUtf8Sequence(ZipArchiveEntry entry)
+ {
+ return Encoding.UTF8.GetString(ReadZipArchiveContent(entry));
+ }
+
+ internal sealed class FormatException : Exception
+ {
+ internal FormatException(string message) : base(message) {}
+ }
+
+ [Serializable]
+ internal sealed class PartialDescriptor
+ {
+ [JsonProperty("assetUri")]
+ internal string AssetUri;
+ [JsonProperty("assetManifest")]
+ internal List AssetManifest;
+ }
+
+ [Serializable]
+ internal sealed class AssetStatistic
+ {
+ [JsonProperty("hash")]
+ internal string Hash;
+ [JsonProperty("bytes")]
+ internal int Length;
+ }
+ }
+}
diff --git a/Editor/Package/Import/Importer.cs.meta b/Editor/Package/Import/Importer.cs.meta
new file mode 100644
index 0000000..2c968b1
--- /dev/null
+++ b/Editor/Package/Import/Importer.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 152ee6b4f0cb4f0db0df22317f9d5843
+timeCreated: 1730750560
\ No newline at end of file
diff --git a/Editor/Package/Import/Metadata.meta b/Editor/Package/Import/Metadata.meta
new file mode 100644
index 0000000..b5af802
--- /dev/null
+++ b/Editor/Package/Import/Metadata.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9edd1b9487cb4f55b68695748810034b
+timeCreated: 1730803858
\ No newline at end of file
diff --git a/Editor/Package/Import/Metadata/GraphRoot.cs b/Editor/Package/Import/Metadata/GraphRoot.cs
new file mode 100644
index 0000000..abb7105
--- /dev/null
+++ b/Editor/Package/Import/Metadata/GraphRoot.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+using ResoniteImportHelper.Package.Import.Stub;
+
+namespace ResoniteImportHelper.Package.Import.Metadata
+{
+ [Serializable]
+ public sealed class GraphRoot
+ {
+ // ReSharper disable once InconsistentNaming
+ public string VersionNumber;
+ public Dictionary FeatureFlags;
+ [JsonProperty("Types")]
+ private string[] _types;
+
+ [JsonIgnore]
+ private TypeRef[] _typesCache;
+
+ [JsonIgnore]
+ public TypeRef[] Types => (_typesCache ??= _types.Select(type => new TypeRef(type)).ToArray());
+
+ [JsonProperty("TypeVersions")]
+ private Dictionary _typeVersions;
+
+ [JsonIgnore]
+ private Dictionary _typeVersionsCache;
+
+ [JsonIgnore]
+ public Dictionary TypeVersions =>
+ _typeVersionsCache ??=
+ _typeVersions
+ .Select(entry => KeyValuePair.Create(new TypeRef(entry.Key), entry.Value))
+ .ToDictionary(entry => entry.Key, entry => entry.Value);
+
+ [JsonProperty("Object")]
+ public Slot RootSlot;
+
+ [JsonProperty("Assets")]
+ public UntypedComponentReference[] ContainedAssets;
+ }
+}
diff --git a/Editor/Package/Import/Metadata/GraphRoot.cs.meta b/Editor/Package/Import/Metadata/GraphRoot.cs.meta
new file mode 100644
index 0000000..b988523
--- /dev/null
+++ b/Editor/Package/Import/Metadata/GraphRoot.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 61314c334dc54b07b685720de7f2987a
+timeCreated: 1738982426
\ No newline at end of file
diff --git a/Editor/Package/Import/Metadata/IAssetVariantAccessor.cs b/Editor/Package/Import/Metadata/IAssetVariantAccessor.cs
new file mode 100644
index 0000000..f694990
--- /dev/null
+++ b/Editor/Package/Import/Metadata/IAssetVariantAccessor.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+
+namespace ResoniteImportHelper.Package.Import.Metadata
+{
+ public interface IAssetVariantAccessor
+ {
+ public IEnumerable GetByKey(string Key);
+ }
+
+ public sealed class LazyLoadEntry
+ {
+ public readonly string FileName;
+ private ZipArchiveEntry _entry;
+
+ public LazyLoadEntry(ZipArchiveEntry entry)
+ {
+ _entry = entry;
+ FileName = entry.Name;
+ }
+
+ public byte[] Decompress()
+ {
+ var len = this._entry.Length;
+ if (len > int.MaxValue)
+ {
+ throw new Exception("too large entry");
+ }
+
+ using var ms = new MemoryStream();
+ using (var w = _entry.Open())
+ {
+ w.CopyTo(ms);
+ }
+
+ return ms.GetBuffer();
+ }
+ }
+}
diff --git a/Editor/Package/Import/Metadata/IAssetVariantAccessor.cs.meta b/Editor/Package/Import/Metadata/IAssetVariantAccessor.cs.meta
new file mode 100644
index 0000000..e5ae41d
--- /dev/null
+++ b/Editor/Package/Import/Metadata/IAssetVariantAccessor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: dadb6527de4e4692809f03ff4aa2219f
+timeCreated: 1739096966
diff --git a/Editor/Package/Import/Metadata/IComponentReference.cs b/Editor/Package/Import/Metadata/IComponentReference.cs
new file mode 100644
index 0000000..a9952f0
--- /dev/null
+++ b/Editor/Package/Import/Metadata/IComponentReference.cs
@@ -0,0 +1,9 @@
+namespace ResoniteImportHelper.Package.Import.Metadata
+{
+ public interface IComponentReference where T: notnull
+ {
+ public int GetComponentTableIndex();
+
+ public T GetComponentData();
+ }
+}
diff --git a/Editor/Package/Import/Metadata/IComponentReference.cs.meta b/Editor/Package/Import/Metadata/IComponentReference.cs.meta
new file mode 100644
index 0000000..eb4df19
--- /dev/null
+++ b/Editor/Package/Import/Metadata/IComponentReference.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 42100ebf89ef4fa0aaf43ae8e4c4c199
+timeCreated: 1739094698
\ No newline at end of file
diff --git a/Editor/Package/Import/Metadata/IMetadataAccessor.cs b/Editor/Package/Import/Metadata/IMetadataAccessor.cs
new file mode 100644
index 0000000..083d021
--- /dev/null
+++ b/Editor/Package/Import/Metadata/IMetadataAccessor.cs
@@ -0,0 +1,7 @@
+namespace ResoniteImportHelper.Package.Import.Metadata
+{
+ public interface IMetadataAccessor
+ {
+ public T GetAndDeserialize(string key);
+ }
+}
diff --git a/Editor/Package/Import/Metadata/IMetadataAccessor.cs.meta b/Editor/Package/Import/Metadata/IMetadataAccessor.cs.meta
new file mode 100644
index 0000000..0e78101
--- /dev/null
+++ b/Editor/Package/Import/Metadata/IMetadataAccessor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 73c891857dd449beacaf7bd42c826015
+timeCreated: 1739096059
diff --git a/Editor/Package/Import/Metadata/ResoniteImportHelper.Package.Import.Metadata.asmdef b/Editor/Package/Import/Metadata/ResoniteImportHelper.Package.Import.Metadata.asmdef
new file mode 100644
index 0000000..a672894
--- /dev/null
+++ b/Editor/Package/Import/Metadata/ResoniteImportHelper.Package.Import.Metadata.asmdef
@@ -0,0 +1,6 @@
+{
+ "name": "ResoniteImportHelper.Package.Import.Metadata",
+ "rootNamespace": "global::ResoniteImportHelper.Package.Import.Metadata",
+ "includePlatforms": ["Editor"],
+ "references": ["ResoniteImportHelper.Package.Import.Stub", "ResoniteImportHelper.0ReflectionMetadata"]
+}
diff --git a/Editor/Package/Import/Metadata/ResoniteImportHelper.Package.Import.Metadata.asmdef.meta b/Editor/Package/Import/Metadata/ResoniteImportHelper.Package.Import.Metadata.asmdef.meta
new file mode 100644
index 0000000..ede88c0
--- /dev/null
+++ b/Editor/Package/Import/Metadata/ResoniteImportHelper.Package.Import.Metadata.asmdef.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ad964f63120f45a9aac389bdb19e4cad
+timeCreated: 1739008029
\ No newline at end of file
diff --git a/Editor/Package/Import/Metadata/TypeRef.cs b/Editor/Package/Import/Metadata/TypeRef.cs
new file mode 100644
index 0000000..ec7f0ba
--- /dev/null
+++ b/Editor/Package/Import/Metadata/TypeRef.cs
@@ -0,0 +1,96 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Text.RegularExpressions;
+
+namespace ResoniteImportHelper.Package.Import.Metadata
+{
+ [Serializable]
+ public sealed class TypeRef: IEquatable, IEqualityComparer
+ {
+ public string Raw { get; }
+ private bool? _hasCorrectSyntax;
+ private Exception? _cachedException;
+ private AssemblyName? _cachedAssemblyName;
+ private TypeName? _cachedTypeName;
+
+ public TypeRef(string raw)
+ {
+ this.Raw = raw;
+ }
+
+ public (AssemblyName assembly, TypeName typeName) Parse()
+ {
+ switch (this._hasCorrectSyntax)
+ {
+ case false:
+ throw this._cachedException!;
+ case true:
+ return (this._cachedAssemblyName!, this._cachedTypeName!);
+ }
+
+ var regex = new Regex(@"^\[(?[^\]]+)\](?.+)$");
+ var matchResult = regex.Match(this.Raw);
+ if (matchResult.Success)
+ {
+ this._hasCorrectSyntax = true;
+ var assemblyNameCaptureResult = matchResult.Groups["assemblyName"];
+ if (!assemblyNameCaptureResult.Success)
+ {
+ InvalidateAndThrow();
+ }
+
+ this._cachedAssemblyName = new AssemblyName(assemblyNameCaptureResult.Value);
+
+ var typeNameCaptureResult = matchResult.Groups["typeName"];
+ if (!typeNameCaptureResult.Success)
+ {
+ InvalidateAndThrow();
+ }
+
+ if (!TypeName.TryParse(typeNameCaptureResult.Value, out var parsedTypeName))
+ {
+ InvalidateAndThrow();
+ }
+
+ this._cachedTypeName = parsedTypeName;
+
+ // Debug.Log($"asm: {_cachedAssemblyName.FullName}, ty: {_cachedTypeName.FullName}");
+ return (_cachedAssemblyName, _cachedTypeName);
+ }
+ else
+ {
+ InvalidateAndThrow();
+ }
+
+ throw new InvalidOperationException("unreachable.");
+ }
+
+ [DoesNotReturn]
+ private void InvalidateAndThrow()
+ {
+ this._hasCorrectSyntax = false;
+ var exception = new Exception($"Malformed TypeRef: {Raw}");
+ this._cachedException = exception;
+ throw exception;
+ }
+
+ public bool Equals(TypeRef other) => other != null && this.Raw == other.Raw;
+
+ public bool Equals(TypeRef x, TypeRef y) =>
+ (x, y) switch
+ {
+ (null, null) => true,
+ (_, null) => false,
+ (null, _) => false,
+ (_, _) => x.Equals(y)
+ };
+
+ public int GetHashCode(TypeRef obj) => obj.Raw.GetHashCode();
+
+ public override string ToString() => $"TypeRef({Raw})";
+ }
+}
diff --git a/Editor/Package/Import/Metadata/TypeRef.cs.meta b/Editor/Package/Import/Metadata/TypeRef.cs.meta
new file mode 100644
index 0000000..a02b11e
--- /dev/null
+++ b/Editor/Package/Import/Metadata/TypeRef.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6d5e1f74f6de4ed99aaef3566b343b8f
+timeCreated: 1738982616
\ No newline at end of file
diff --git a/Editor/Package/Import/Metadata/TypedComponentReference.cs b/Editor/Package/Import/Metadata/TypedComponentReference.cs
new file mode 100644
index 0000000..75c1c4d
--- /dev/null
+++ b/Editor/Package/Import/Metadata/TypedComponentReference.cs
@@ -0,0 +1,16 @@
+using Newtonsoft.Json;
+
+namespace ResoniteImportHelper.Package.Import.Metadata
+{
+ public sealed class TypedComponentReference : IComponentReference where T : notnull
+ {
+ [JsonProperty("Type")]
+ public int ComponentTableIndex;
+ [JsonProperty("Data")]
+ public T ComponentProperties;
+
+ public int GetComponentTableIndex() => ComponentTableIndex;
+
+ public T GetComponentData() => ComponentProperties;
+ }
+}
diff --git a/Editor/Package/Import/Metadata/TypedComponentReference.cs.meta b/Editor/Package/Import/Metadata/TypedComponentReference.cs.meta
new file mode 100644
index 0000000..9181bda
--- /dev/null
+++ b/Editor/Package/Import/Metadata/TypedComponentReference.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: fc3c3a5f16854debb0064a9abd003cef
+timeCreated: 1739094679
\ No newline at end of file
diff --git a/Editor/Package/Import/Metadata/UntypedComponentReference.cs b/Editor/Package/Import/Metadata/UntypedComponentReference.cs
new file mode 100644
index 0000000..0437bb8
--- /dev/null
+++ b/Editor/Package/Import/Metadata/UntypedComponentReference.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace ResoniteImportHelper.Package.Import.Metadata
+{
+ public sealed class UntypedComponentReference : IComponentReference>
+ {
+ [JsonProperty("Type")]
+ public int ComponentTableIndex;
+ [JsonProperty("Data")]
+ public Dictionary UntypedComponentProperties;
+
+ public int GetComponentTableIndex() => ComponentTableIndex;
+ public Dictionary GetComponentData() => UntypedComponentProperties;
+
+ public TypedComponentReference AsTyped()
+ {
+ return new TypedComponentReference
+ {
+ ComponentTableIndex = ComponentTableIndex,
+ ComponentProperties = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(UntypedComponentProperties))
+ };
+ }
+ }
+}
diff --git a/Editor/Package/Import/Metadata/UntypedComponentReference.cs.meta b/Editor/Package/Import/Metadata/UntypedComponentReference.cs.meta
new file mode 100644
index 0000000..1bc1d86
--- /dev/null
+++ b/Editor/Package/Import/Metadata/UntypedComponentReference.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e3a9fd84bce44bf28a1913b66b467a5c
+timeCreated: 1739094573
\ No newline at end of file
diff --git a/Editor/Package/Import/ResoniteImportHelper.Package.Import.asmdef b/Editor/Package/Import/ResoniteImportHelper.Package.Import.asmdef
new file mode 100644
index 0000000..e87e0a0
--- /dev/null
+++ b/Editor/Package/Import/ResoniteImportHelper.Package.Import.asmdef
@@ -0,0 +1,6 @@
+{
+ "name": "ResoniteImportHelper.Package.Import",
+ "rootNamespace": "global::ResoniteImportHelper.Package.Import",
+ "includePlatforms": ["Editor"],
+ "references":[ "ResoniteImportHelper.0ReflectionMetadata", "ResoniteImportHelper.Package.Import.Stub", "ResoniteImportHelper.Package.Asset.Types", "ResoniteImportHelper.Package.Import.Metadata", "ResoniteImportHelper.Package.Deserialize" ]
+}
diff --git a/Editor/Package/Import/ResoniteImportHelper.Package.Import.asmdef.meta b/Editor/Package/Import/ResoniteImportHelper.Package.Import.asmdef.meta
new file mode 100644
index 0000000..b4f0501
--- /dev/null
+++ b/Editor/Package/Import/ResoniteImportHelper.Package.Import.asmdef.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 8db65e83cd19478ca4d494ee98716064
+timeCreated: 1730750524
\ No newline at end of file
diff --git a/Editor/Package/Import/Stub.meta b/Editor/Package/Import/Stub.meta
new file mode 100644
index 0000000..79bfe82
--- /dev/null
+++ b/Editor/Package/Import/Stub.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f330b92d2fbb49518478beaaf700be87
+timeCreated: 1730805177
\ No newline at end of file
diff --git a/Editor/Package/Import/Stub/ResoniteImportHelper.Package.Import.Stub.asmdef b/Editor/Package/Import/Stub/ResoniteImportHelper.Package.Import.Stub.asmdef
new file mode 100644
index 0000000..0f2d0f2
--- /dev/null
+++ b/Editor/Package/Import/Stub/ResoniteImportHelper.Package.Import.Stub.asmdef
@@ -0,0 +1,5 @@
+{
+ "name": "ResoniteImportHelper.Package.Import.Stub",
+ "rootNamespace": "global::ResoniteImportHelper.Package.Import.Stub",
+ "references":[ "GUID:da758e96819041b2b57ad6379d1c0c58", "ResoniteImportHelper.Package.Deserialize" ]
+}
diff --git a/Editor/Package/Import/Stub/ResoniteImportHelper.Package.Import.Stub.asmdef.meta b/Editor/Package/Import/Stub/ResoniteImportHelper.Package.Import.Stub.asmdef.meta
new file mode 100644
index 0000000..964a2dc
--- /dev/null
+++ b/Editor/Package/Import/Stub/ResoniteImportHelper.Package.Import.Stub.asmdef.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ec6a53d6b953460588f6cbd0313f0275
+timeCreated: 1730805193
\ No newline at end of file
diff --git a/Editor/Package/Import/Stub/Slot.cs b/Editor/Package/Import/Stub/Slot.cs
new file mode 100644
index 0000000..fbad696
--- /dev/null
+++ b/Editor/Package/Import/Stub/Slot.cs
@@ -0,0 +1,39 @@
+using Newtonsoft.Json;
+using ResoniteImportHelper.Package.Import.Deserialize.Support;
+using UnityEngine;
+
+namespace ResoniteImportHelper.Package.Import.Stub
+{
+ public sealed class Slot : IIdentifiable
+ {
+ public string ID;
+ public IdentifiableDataCell Name;
+
+ [JsonProperty("Position")]
+ private IdentifiableDataCell _position;
+
+ [JsonIgnore]
+ private Vector3? _cachedPosition;
+
+ [JsonIgnore] public Vector3 Position => _cachedPosition ??= new Vector3(_position.Data[0], _position.Data[1], _position.Data[2]);
+
+ [JsonProperty("Rotation")]
+ private IdentifiableDataCell _rotation;
+
+ [JsonIgnore]
+ private Quaternion? _cachedRotation;
+
+ [JsonIgnore] public Quaternion Rotation => _cachedRotation ??= new Quaternion(_rotation.Data[0], _rotation.Data[1], _rotation.Data[2], _rotation.Data[3]);
+
+ [JsonProperty("Scale")]
+ private IdentifiableDataCell _scale;
+
+ [JsonIgnore]
+ private Vector3? _cachedScale;
+
+ [JsonIgnore] public Vector3 Scale => _cachedScale ??= new Vector3(_scale.Data[0], _scale.Data[1], _scale.Data[2]);
+
+ public Slot[] Children;
+ public string GetIdentifier() => ID;
+ }
+}
diff --git a/Editor/Package/Import/Stub/Slot.cs.meta b/Editor/Package/Import/Stub/Slot.cs.meta
new file mode 100644
index 0000000..0b12042
--- /dev/null
+++ b/Editor/Package/Import/Stub/Slot.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 03461696fb614364b7511d5e44279dcc
+timeCreated: 1739005586
\ No newline at end of file
diff --git a/Editor/Package/Import/Stub/StaticTexture2D.cs b/Editor/Package/Import/Stub/StaticTexture2D.cs
new file mode 100644
index 0000000..4cf8604
--- /dev/null
+++ b/Editor/Package/Import/Stub/StaticTexture2D.cs
@@ -0,0 +1,43 @@
+using System;
+using ResoniteImportHelper.Package.Import.Deserialize.Support;
+using UnityEngine;
+
+namespace ResoniteImportHelper.Editor.Package.Import.Stub
+{
+ public sealed class StaticTexture2D : IIdentifiable
+ {
+ public string ID;
+ public IdentifiableDataCell Enabled;
+ public IdentifiableDataCell URL;
+ public IdentifiableDataCell IsNormalMap;
+ public IdentifiableDataCell WrapModeU;
+ public IdentifiableDataCell WrapModeV;
+ public IdentifiableDataCell CrunchCompressed;
+ public IdentifiableDataCell MipMapFilter;
+
+ public string GetAssetIdentifier()
+ {
+ const string PACKDB_PREFIX = "@packdb:///";
+ var url = URL.Data;
+ if (!url.StartsWith("@")) throw new Exception("URL does not start with at-mark.");
+ if (!url.StartsWith(PACKDB_PREFIX)) throw new Exception("URL does not start with packdb prefix.");
+
+ return url[PACKDB_PREFIX.Length..];
+ }
+
+ public TextureWrapMode GetWrapModeU() => DeserializeToWrapMode(WrapModeU.Data);
+
+ public TextureWrapMode GetWrapModeV() => DeserializeToWrapMode(WrapModeV.Data);
+
+ private TextureWrapMode DeserializeToWrapMode(string raw)
+ {
+ return raw switch
+ {
+ "Repeat" => TextureWrapMode.Repeat,
+ _ => throw new IndexOutOfRangeException($"{WrapModeU} is not supported.")
+ };
+ }
+
+ public string GetIdentifier() => ID;
+ }
+}
diff --git a/Editor/Package/Import/Stub/StaticTexture2D.cs.meta b/Editor/Package/Import/Stub/StaticTexture2D.cs.meta
new file mode 100644
index 0000000..72fce76
--- /dev/null
+++ b/Editor/Package/Import/Stub/StaticTexture2D.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e2e9e7d934b346eabb7c9235919bf10e
+timeCreated: 1730805229
\ No newline at end of file