diff --git a/Plugins/Fab/Config/BaseFab.ini b/Plugins/Fab/Config/BaseFab.ini
new file mode 100644
index 0000000..b2bb2d3
--- /dev/null
+++ b/Plugins/Fab/Config/BaseFab.ini
@@ -0,0 +1,2 @@
+[/Script/Fab.MegascansMaterialParentSettings]
+MaterialParents=((Base3D, (StandardMaterial="/Fab/Materials/Standard/M_MS_Base.M_MS_Base",VTMaterial="/Fab/Materials/VT/M_MS_Base_VT.M_MS_Base_VT")),(Fuzz3D, (StandardMaterial="/Fab/Materials/Standard/M_MS_Base_Fuzz.M_MS_Base_Fuzz",VTMaterial="/Fab/Materials/VT/M_MS_Base_Fuzz_VT.M_MS_Base_Fuzz_VT")),(Transmission3D, (StandardMaterial="/Fab/Materials/Standard/M_MS_Base_Trm.M_MS_Base_Trm",VTMaterial="/Fab/Materials/VT/M_MS_Base_Trm_VT.M_MS_Base_Trm_VT")),(Glass3D, (StandardMaterial="/Fab/Materials/Standard/M_MS_Glass.M_MS_Glass",VTMaterial="/Fab/Materials/VT/M_MS_Glass_VT.M_MS_Glass_VT")),(Surface, (StandardMaterial="/Fab/Materials/Standard/M_MS_Srf.M_MS_Srf",VTMaterial="/Fab/Materials/VT/M_MS_Srf_VT.M_MS_Srf_VT")),(TransmissionSurface, (StandardMaterial="/Fab/Materials/Standard/M_MS_Base_Trm.M_MS_Base_Trm",VTMaterial="/Fab/Materials/VT/M_MS_Base_Trm_VT.M_MS_Base_Trm_VT")),(FabricSurface, (StandardMaterial="/Fab/Materials/Standard/M_MS_FabricMasked.M_MS_FabricMasked",VTMaterial="/Fab/Materials/VT/M_MS_FabricMasked_VT.M_MS_FabricMasked_VT")),(Decal, (StandardMaterial="/Fab/Materials/Standard/M_MS_Decal.M_MS_Decal",VTMaterial="/Fab/Materials/VT/M_MS_Decal_VT.M_MS_Decal_VT")),(Plant, (StandardMaterial="/Fab/Materials/Standard/M_MS_Foliage.M_MS_Foliage",VTMaterial="/Fab/Materials/VT/M_MS_Foliage_VT.M_MS_Foliage_VT")),(PlantBillboard, (StandardMaterial="/Fab/Materials/Standard/M_MS_Billboard.M_MS_Billboard",VTMaterial="/Fab/Materials/VT/M_MS_Billboard_VT.M_MS_Billboard_VT")))
diff --git a/Plugins/Fab/Content/Actors/GlobalFoliageActor/BP_GlobalFoliageActor_UE5.uasset b/Plugins/Fab/Content/Actors/GlobalFoliageActor/BP_GlobalFoliageActor_UE5.uasset
new file mode 100644
index 0000000..61aacac
--- /dev/null
+++ b/Plugins/Fab/Content/Actors/GlobalFoliageActor/BP_GlobalFoliageActor_UE5.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f1bd9251f114fa94e5cc3f9eb7ea2f4b872e42081ad79a13b0ec6ba56d19cea5
+size 238399
diff --git a/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Arrow.uasset b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Arrow.uasset
new file mode 100644
index 0000000..4a64e5c
--- /dev/null
+++ b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Arrow.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:16ec483610368d4bdfcc45d537e867092adc1ab6b10fe7c0cfa149941a418c68
+size 70260
diff --git a/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Arrow_Test.uasset b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Arrow_Test.uasset
new file mode 100644
index 0000000..b3143be
--- /dev/null
+++ b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Arrow_Test.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d6ec6fc47b56d6f91b68cb1d32acf464282e6289aa9b9f1918d471f6b4f39eb7
+size 40508
diff --git a/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Base.uasset b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Base.uasset
new file mode 100644
index 0000000..9338db4
--- /dev/null
+++ b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Base.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:af5b506f0f83de76ccf23251be4c7b397caae7b37509fb614fae989775524d81
+size 141196
diff --git a/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Sock.uasset b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Sock.uasset
new file mode 100644
index 0000000..5d317e9
--- /dev/null
+++ b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Geometry/Icon_Sock.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:99e9860d7ce26bb9b1b534399cd9fad084868c03c4589433bb3e7083fc5ae737
+size 49553
diff --git a/Plugins/Fab/Content/Actors/GlobalFoliageActor/Materials/MA_UI_Element.uasset b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Materials/MA_UI_Element.uasset
new file mode 100644
index 0000000..d96f31f
--- /dev/null
+++ b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Materials/MA_UI_Element.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1a3c15afda2aea3c4940ceb93c2a8048a342cf4255425c4288fe7ecb5766d32f
+size 14538
diff --git a/Plugins/Fab/Content/Actors/GlobalFoliageActor/Materials/MA_UI_Element_Inst.uasset b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Materials/MA_UI_Element_Inst.uasset
new file mode 100644
index 0000000..0db30b0
--- /dev/null
+++ b/Plugins/Fab/Content/Actors/GlobalFoliageActor/Materials/MA_UI_Element_Inst.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aec3e99a351f06c46d5a03106cb558196ab667e9f23a04239e6f51efbbd039cb
+size 10170
diff --git a/Plugins/Fab/Content/Data/FabEos.uasset b/Plugins/Fab/Content/Data/FabEos.uasset
new file mode 100644
index 0000000..ba1d134
--- /dev/null
+++ b/Plugins/Fab/Content/Data/FabEos.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:242a84a8eb88c2bd5c475573891153c80f1decf0c90deeb01781b4c4a351c3ae
+size 2663
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_AOAdjustments.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_AOAdjustments.uasset
new file mode 100644
index 0000000..c555b5e
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_AOAdjustments.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9692a39b1f3b30fbf7e543546c0c64c841d905273ad9ae1bc36fc07d535b4d8f
+size 11799
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_AdjustTwoSidedBaseColor.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_AdjustTwoSidedBaseColor.uasset
new file mode 100644
index 0000000..52ae490
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_AdjustTwoSidedBaseColor.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb542c4e14c2b9bd736e24efa4c01eba87ff831f5eeba82e52eb2e5810b46b47
+size 60294
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_AdjustTwoSidedNormal.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_AdjustTwoSidedNormal.uasset
new file mode 100644
index 0000000..a3ab061
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_AdjustTwoSidedNormal.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1d708a9a9f3f312c2096a1a836e9cd401b9d289d25d264e1edbdb5993ed552bc
+size 11166
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_AlbedoAdjustments.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_AlbedoAdjustments.uasset
new file mode 100644
index 0000000..8857861
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_AlbedoAdjustments.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:90571bb918d56b396ecfe12d5eb296d2a782db812107f5dcecb750d97f9d9e8c
+size 13971
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_DistanceBasedGradient.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_DistanceBasedGradient.uasset
new file mode 100644
index 0000000..2b83b13
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_DistanceBasedGradient.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bfc4ea1218776c07d2314a2dadb28cd6a0e806ee9207d5ec573858cd07d515f0
+size 10009
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_DistanceBasedOpacity.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_DistanceBasedOpacity.uasset
new file mode 100644
index 0000000..5966f6e
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_DistanceBasedOpacity.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3bd5387dddd437efb8e7063968400a414bf8cbd9a3291fc986cb2f486ac7eed0
+size 15597
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_FabricFuzz.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_FabricFuzz.uasset
new file mode 100644
index 0000000..41927f1
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_FabricFuzz.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1b59539ad91c3ca7afc20bf8290d75319e4cb71ecdadd2395afe0d411fdcad4c
+size 12711
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_FabricMasked.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_FabricMasked.uasset
new file mode 100644
index 0000000..1d050d2
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_FabricMasked.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6c7308838aee523ee2864cc2ee91298132a2030a8c9d1c03b64c6c6e9d8c3f29
+size 12505
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_FabricSSAdjustments.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_FabricSSAdjustments.uasset
new file mode 100644
index 0000000..849e15d
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_FabricSSAdjustments.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d2aa5a2733aab0bf582c7953c1c791d22ad7a193903bc7fc88c3222293de137e
+size 13069
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_FabricSpecular.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_FabricSpecular.uasset
new file mode 100644
index 0000000..b83353e
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_FabricSpecular.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:69a180b285ef2977982e6bc20823c1d46c526a25e91b96623bf0bfae9a016754
+size 14173
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_FrameBlend.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_FrameBlend.uasset
new file mode 100644
index 0000000..696308b
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_FrameBlend.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:89ba635ec12e203954060bf6e22ee2ce1e7e031c492ced051a1b9730d664b7c1
+size 35154
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_Fuzz.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_Fuzz.uasset
new file mode 100644
index 0000000..346cc8e
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_Fuzz.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ae0e1a6f45d4f9c4a41eaa57b38eb1bbaae86585d5b3c2956af96c128c4161c6
+size 16254
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_GenerateBranchMask_Plants.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_GenerateBranchMask_Plants.uasset
new file mode 100644
index 0000000..196da25
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_GenerateBranchMask_Plants.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7ca292cf00f9d58189aa54d1ae4d53fd3870e89819257ee1d1175dc100e2384e
+size 18569
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_GenerateTranslucency.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_GenerateTranslucency.uasset
new file mode 100644
index 0000000..603f429
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_GenerateTranslucency.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a2f9b6425d075740ebeb626a058187aa0a6f8bf9256ae20e8a90ab932b1b135b
+size 49431
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_GenerateTwoSidedSpecular.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_GenerateTwoSidedSpecular.uasset
new file mode 100644
index 0000000..b368764
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_GenerateTwoSidedSpecular.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e7b57e86e968378dcc8874916ddeed6585e830ec7d6e65288df502fe6be61123
+size 20403
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_GrowthEffect.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_GrowthEffect.uasset
new file mode 100644
index 0000000..df79c75
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_GrowthEffect.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4996a360a578825202e40163515f11a24d59fe1dfbfbb3909fb87d20a8f79e91
+size 39539
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_HealthSeason.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_HealthSeason.uasset
new file mode 100644
index 0000000..84db6e4
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_HealthSeason.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4a099a1be577f432919a5b8e2492467c44caf1ac63ef5fd7f12b53bc6669c81a
+size 48607
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_NormalAdjustments.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_NormalAdjustments.uasset
new file mode 100644
index 0000000..8670b26
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_NormalAdjustments.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7d90ed8bc39cafca5408d010fd9015d89e07642638e5c2ca29870bbbc8ebf203
+size 13020
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_PerInstanceActorRandom.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_PerInstanceActorRandom.uasset
new file mode 100644
index 0000000..016c169
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_PerInstanceActorRandom.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9afe54538a4233d0e3a739cc65ed2f9fb5359bed0d26d99c4d513acf578be364
+size 12865
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_Refraction.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_Refraction.uasset
new file mode 100644
index 0000000..e364298
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_Refraction.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3bbd53e06e1004f4ab96a5c198f18c7fceeb57d2f1441926d502273654f28df1
+size 17830
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_RoughnessAdjustments.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_RoughnessAdjustments.uasset
new file mode 100644
index 0000000..f7dd9d7
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_RoughnessAdjustments.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e53a305d27484461482513a7eb99d1f7de3f4a1a01e54e2569a6164190988214
+size 11040
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_Tiling.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_Tiling.uasset
new file mode 100644
index 0000000..eb63940
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_Tiling.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bcb71010d05cdf863516a4360354485865f229758c1e26f438e106ab9c55c06b
+size 14206
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_Translucency.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_Translucency.uasset
new file mode 100644
index 0000000..8df9ee1
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_Translucency.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ec85a127847815d28ef916398885ee8ae89a561118bc1bf77bb2157202bf55d5
+size 13033
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_Transmission.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_Transmission.uasset
new file mode 100644
index 0000000..a971b26
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_Transmission.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1a77c8194ac14f291d24b6926d101f6b251db44b76cc03bd325ff7f6d121b37c
+size 16879
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_TwoSidedRoughness.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_TwoSidedRoughness.uasset
new file mode 100644
index 0000000..9fbbb13
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_TwoSidedRoughness.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:883be76f348e0b285e3268055515f189ff7fe6709539e32d3d3d7d94124861b8
+size 37195
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_WindAnimation.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_WindAnimation.uasset
new file mode 100644
index 0000000..19201b7
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_WindAnimation.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d95181c0a56a50f5224f161233d9a01fc2f813587f7bc23aed77edfbb74ae9f7
+size 58125
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_WindGust.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_WindGust.uasset
new file mode 100644
index 0000000..4eb4257
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_WindGust.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8d8852f82313efacf5f61ee627bce0a725738aefa464a30a1fa1fb15a2d24d08
+size 24692
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_WindowImperfection.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_WindowImperfection.uasset
new file mode 100644
index 0000000..fc2a8fe
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_WindowImperfection.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9c256bcc41e005d1490406c0ec5f655d31b296a6626c52de6c0acc607a810ccf
+size 20053
diff --git a/Plugins/Fab/Content/MaterialFunctions/QMF_WorldspaceColorVariation.uasset b/Plugins/Fab/Content/MaterialFunctions/QMF_WorldspaceColorVariation.uasset
new file mode 100644
index 0000000..bf3bc01
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialFunctions/QMF_WorldspaceColorVariation.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6233e89efb8bfdafce1a65436a8f73befc4c6f4a636ea235fe05bb24c31b0e41
+size 30360
diff --git a/Plugins/Fab/Content/MaterialParameterCollection/QMPC_GlobalFoliageActor.uasset b/Plugins/Fab/Content/MaterialParameterCollection/QMPC_GlobalFoliageActor.uasset
new file mode 100644
index 0000000..d6693e4
--- /dev/null
+++ b/Plugins/Fab/Content/MaterialParameterCollection/QMPC_GlobalFoliageActor.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4630f3c80861ab7e9c684cbff2da57704bb3471fc0fdfbf199383877e16fddf2
+size 4657
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_Base.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_Base.uasset
new file mode 100644
index 0000000..056ec75
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_Base.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1249c6a07859920cf11572987d10f879b10b921efbc8e95aa27b44179acf175f
+size 42704
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_Base_Fuzz.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_Base_Fuzz.uasset
new file mode 100644
index 0000000..6ff9266
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_Base_Fuzz.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0d2c6003626f1246062ed2543ad5f5ce952d1deced704c427f16e234b18c102f
+size 50229
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_Base_Trm.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_Base_Trm.uasset
new file mode 100644
index 0000000..6dfe4f3
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_Base_Trm.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cf7fa1c47c7fd6561174f4788a1bca917257d2184038194637a38a39807247b
+size 59909
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_Billboard.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_Billboard.uasset
new file mode 100644
index 0000000..00845b4
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_Billboard.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aff443702ea03d0f45778c61104807a62985ed4c53ac3d008c012ce72c81e7d1
+size 82316
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_Decal.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_Decal.uasset
new file mode 100644
index 0000000..a06d47b
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_Decal.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:de405b473ccd30505651b82aec98c2bf9a5dff1aa0afa4b22a4df40ef5d49255
+size 55531
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_FabricMasked.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_FabricMasked.uasset
new file mode 100644
index 0000000..39e5e5d
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_FabricMasked.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ad5ced1b1a61feff4c588ba5fae72ed33bc0a4976d0338cf41aac3c48acb3142
+size 75743
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_FabricOpaque.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_FabricOpaque.uasset
new file mode 100644
index 0000000..43507f9
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_FabricOpaque.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9bed13f5997f40fb4c2f8a4feb8524a94039370c4b8b1b6f536fc96ea2a7f444
+size 71118
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_Foliage.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_Foliage.uasset
new file mode 100644
index 0000000..4bbf6aa
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_Foliage.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6fdeaacf968686cd677837e6262cbceec94cda4ee262494e5b87e3d22fd38c65
+size 64333
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_Glass.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_Glass.uasset
new file mode 100644
index 0000000..f645c8e
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_Glass.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b4c2ecff4fb2d922cae9d0ca2752255dd5fd5206e8d09bb0361b2400aedb83b9
+size 66378
diff --git a/Plugins/Fab/Content/Materials/Standard/M_MS_Srf.uasset b/Plugins/Fab/Content/Materials/Standard/M_MS_Srf.uasset
new file mode 100644
index 0000000..2d5c0d9
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/Standard/M_MS_Srf.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d6604490760a6246e8dac58abbbfb2c05b5fcbceb04ba23a27e2cf4095047af5
+size 48792
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_Base_Fuzz_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_Base_Fuzz_VT.uasset
new file mode 100644
index 0000000..4b1ddc3
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_Base_Fuzz_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3d6d61979ab7f9777ddfcce6f58b739bf9375280816e5901c43958b471f22443
+size 50423
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_Base_Trm_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_Base_Trm_VT.uasset
new file mode 100644
index 0000000..1c3cdf0
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_Base_Trm_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1f54ea969783ce6d321a59e77ccd6bb70c20a22045618becd5e4948a6a37d2ac
+size 60121
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_Base_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_Base_VT.uasset
new file mode 100644
index 0000000..d416bbf
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_Base_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab17cf577beed50cde52522816706f7af8ca942180f8d01c1336219ea2126a3b
+size 42878
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_Billboard_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_Billboard_VT.uasset
new file mode 100644
index 0000000..76d9ac0
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_Billboard_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5ea1d68a3870b5292bd4ffd8f52a4730fde3ba5d47ec01501530443e6d18bbf8
+size 82403
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_Decal_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_Decal_VT.uasset
new file mode 100644
index 0000000..056aa83
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_Decal_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:027310a697a3bb16e92555efad9c40b641633a421536e1a20febb54d27fe114d
+size 55623
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_FabricMasked_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_FabricMasked_VT.uasset
new file mode 100644
index 0000000..fe2377d
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_FabricMasked_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:960a102872dcb67edc36993641c16a03a7bde18167009a9e9b29e4b749399d46
+size 75840
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_FabricOpaque_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_FabricOpaque_VT.uasset
new file mode 100644
index 0000000..5cff2b1
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_FabricOpaque_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9435537788255d8fac7fe1c8cca9bc42b51127b734e80143ee1aad073a04a230
+size 71260
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_Foliage_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_Foliage_VT.uasset
new file mode 100644
index 0000000..512f347
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_Foliage_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:31067054c5d04a35d7c7debd1224562c2656ae9eb258113d51504886243762a8
+size 64542
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_Glass_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_Glass_VT.uasset
new file mode 100644
index 0000000..f563546
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_Glass_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4c157dc1db20ef01848f05114cf44ccaaa1176eea57ed1c2ea163d8889ed902b
+size 66552
diff --git a/Plugins/Fab/Content/Materials/VT/M_MS_Srf_VT.uasset b/Plugins/Fab/Content/Materials/VT/M_MS_Srf_VT.uasset
new file mode 100644
index 0000000..2fa09f8
--- /dev/null
+++ b/Plugins/Fab/Content/Materials/VT/M_MS_Srf_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c50c00946a321b885602fa0d7fdd6ee866dfff54af920757977b382d5c663608
+size 48966
diff --git a/Plugins/Fab/Content/Placeholders/DecalPlaceholder.uasset b/Plugins/Fab/Content/Placeholders/DecalPlaceholder.uasset
new file mode 100644
index 0000000..86b9402
--- /dev/null
+++ b/Plugins/Fab/Content/Placeholders/DecalPlaceholder.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ca1f74f78f887e85f1ab7f682700e40ee1c90b2c12f9daa5e46f959307d77425
+size 9932
diff --git a/Plugins/Fab/Content/Placeholders/FabLogo.uasset b/Plugins/Fab/Content/Placeholders/FabLogo.uasset
new file mode 100644
index 0000000..fa1952d
--- /dev/null
+++ b/Plugins/Fab/Content/Placeholders/FabLogo.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:870f959619ded9c2c16a020ca5bdfd4676561da4787b38997787d230e153bc29
+size 112285
diff --git a/Plugins/Fab/Content/Placeholders/FabMeshPlaceholderMaterial.uasset b/Plugins/Fab/Content/Placeholders/FabMeshPlaceholderMaterial.uasset
new file mode 100644
index 0000000..ac674a6
--- /dev/null
+++ b/Plugins/Fab/Content/Placeholders/FabMeshPlaceholderMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:334a2b71e287801edf3599f3ebcadc12017b75f963852a7c8a68b715f6147d40
+size 24186
diff --git a/Plugins/Fab/Content/Placeholders/FabMeshPlaceholderMaterialInstance.uasset b/Plugins/Fab/Content/Placeholders/FabMeshPlaceholderMaterialInstance.uasset
new file mode 100644
index 0000000..55a68fa
--- /dev/null
+++ b/Plugins/Fab/Content/Placeholders/FabMeshPlaceholderMaterialInstance.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bf5320144827119c8978ba49753bb0a68c89d950ad48dc4028463fb3d07e9947
+size 7711
diff --git a/Plugins/Fab/Content/Placeholders/MeshPlaceholder.uasset b/Plugins/Fab/Content/Placeholders/MeshPlaceholder.uasset
new file mode 100644
index 0000000..2d547a3
--- /dev/null
+++ b/Plugins/Fab/Content/Placeholders/MeshPlaceholder.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5c2db7c2243b747aaacad7d5d221c3c8a2425625856282ab01ed3a48c4d7584a
+size 15847
diff --git a/Plugins/Fab/Content/Textures/Standard/T_DefaultColor.uasset b/Plugins/Fab/Content/Textures/Standard/T_DefaultColor.uasset
new file mode 100644
index 0000000..b9dc0de
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/Standard/T_DefaultColor.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6d0b502382576988aba9c30fdccab1d072e4740c5aba966fee1180a39caef5dd
+size 3924
diff --git a/Plugins/Fab/Content/Textures/Standard/T_DefaultDisplacement.uasset b/Plugins/Fab/Content/Textures/Standard/T_DefaultDisplacement.uasset
new file mode 100644
index 0000000..be639df
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/Standard/T_DefaultDisplacement.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:05e1260e980cb9d9aa038b4cc39d702279304239ce636f49e8beaf5895a02802
+size 4157
diff --git a/Plugins/Fab/Content/Textures/Standard/T_DefaultMasks.uasset b/Plugins/Fab/Content/Textures/Standard/T_DefaultMasks.uasset
new file mode 100644
index 0000000..5b64670
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/Standard/T_DefaultMasks.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:676c0547d32b59db0a466206d1c75b93d94bcd41a3899559fdd789428672587f
+size 4104
diff --git a/Plugins/Fab/Content/Textures/Standard/T_DefaultNormal.uasset b/Plugins/Fab/Content/Textures/Standard/T_DefaultNormal.uasset
new file mode 100644
index 0000000..3f4798c
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/Standard/T_DefaultNormal.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2561c0b0e24d107da9d9be7936d6b6f1fffc28e5672024b4b39397584b593710
+size 4688
diff --git a/Plugins/Fab/Content/Textures/Standard/T_DefaultOther.uasset b/Plugins/Fab/Content/Textures/Standard/T_DefaultOther.uasset
new file mode 100644
index 0000000..ad36191
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/Standard/T_DefaultOther.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ce671bf9a53106b82a22554dd8977f2b59c7e19d0ecae4875b9f4e0d0de48c4b
+size 3985
diff --git a/Plugins/Fab/Content/Textures/Standard/T_Default_WindNoise.uasset b/Plugins/Fab/Content/Textures/Standard/T_Default_WindNoise.uasset
new file mode 100644
index 0000000..03d7cc1
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/Standard/T_Default_WindNoise.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f6b47a337d15a781f3a1594a9d9442dace7b53f489118e51a4c9c55646b01483
+size 6107148
diff --git a/Plugins/Fab/Content/Textures/VT/T_DefaultColor_VT.uasset b/Plugins/Fab/Content/Textures/VT/T_DefaultColor_VT.uasset
new file mode 100644
index 0000000..671940d
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/VT/T_DefaultColor_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:595f1566a1479bcf33de2b3fcfb67476f9cb2a721cd169834d868a221ee85f65
+size 4005
diff --git a/Plugins/Fab/Content/Textures/VT/T_DefaultDisplacement_VT.uasset b/Plugins/Fab/Content/Textures/VT/T_DefaultDisplacement_VT.uasset
new file mode 100644
index 0000000..a4ba5ef
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/VT/T_DefaultDisplacement_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e82bb6893f6fe6a7acf335b9deca69dae1ee709ffad6fbce39f39330adb3f471
+size 4217
diff --git a/Plugins/Fab/Content/Textures/VT/T_DefaultMasks_VT.uasset b/Plugins/Fab/Content/Textures/VT/T_DefaultMasks_VT.uasset
new file mode 100644
index 0000000..4b73d1a
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/VT/T_DefaultMasks_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:28744fb4be612de2c70934018872b881c6a5382103cabf57124e0a3f161aa7a2
+size 4164
diff --git a/Plugins/Fab/Content/Textures/VT/T_DefaultNormal_VT.uasset b/Plugins/Fab/Content/Textures/VT/T_DefaultNormal_VT.uasset
new file mode 100644
index 0000000..bb81639
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/VT/T_DefaultNormal_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ca043da2cccf43abdf01e3b0244f8731b895485f83e4fb4d04f5fb0d41c0a6d3
+size 4748
diff --git a/Plugins/Fab/Content/Textures/VT/T_DefaultOther_VT.uasset b/Plugins/Fab/Content/Textures/VT/T_DefaultOther_VT.uasset
new file mode 100644
index 0000000..73c0128
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/VT/T_DefaultOther_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c60fbcc74af894ea95d9bfca0d3607e7a79613c6bb3d55f1602a4650abfff64f
+size 4045
diff --git a/Plugins/Fab/Content/Textures/VT/T_Default_WindNoise_VT.uasset b/Plugins/Fab/Content/Textures/VT/T_Default_WindNoise_VT.uasset
new file mode 100644
index 0000000..97a64d0
--- /dev/null
+++ b/Plugins/Fab/Content/Textures/VT/T_Default_WindNoise_VT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:443b7daaee2ab5ff03c52958adf314f9bed57b4dbf79ec090e1807d1070cbe50
+size 6107208
diff --git a/Plugins/Fab/Fab.uplugin b/Plugins/Fab/Fab.uplugin
new file mode 100644
index 0000000..c8d3824
--- /dev/null
+++ b/Plugins/Fab/Fab.uplugin
@@ -0,0 +1,42 @@
+{
+ "FileVersion": 3,
+ "Version": 3,
+ "VersionName": "0.0.3",
+ "FriendlyName": "Fab",
+ "Description": "Fab Plugin",
+ "Category": "Other",
+ "CreatedBy": "Epic Games, Inc.",
+ "CreatedByURL": "http://epicgames.com",
+ "DocsURL": "",
+ "MarketplaceURL": "",
+ "SupportURL": "",
+ "EngineVersion": "5.5.0",
+ "EnabledByDefault": true,
+ "CanContainContent": true,
+ "SupportedTargetPlatforms": [
+ "Win64",
+ "Mac",
+ "Linux"
+ ],
+ "Modules": [
+ {
+ "Name": "Fab",
+ "Type": "Editor",
+ "LoadingPhase": "PostDefault"
+ }
+ ],
+ "Plugins": [
+ {
+ "Name": "Interchange",
+ "Enabled": true
+ },
+ {
+ "Name": "EditorScriptingUtilities",
+ "Enabled": true
+ },
+ {
+ "Name": "EOSShared",
+ "Enabled": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Plugins/Fab/Resources/ButtonIcon_40x.png b/Plugins/Fab/Resources/ButtonIcon_40x.png
new file mode 100644
index 0000000..1682773
--- /dev/null
+++ b/Plugins/Fab/Resources/ButtonIcon_40x.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6004a8bf51c1c990c8a8af7a8b305d2b7fca828828b8d69281bbdaffb4178812
+size 734
diff --git a/Plugins/Fab/Resources/FabLogo.svg b/Plugins/Fab/Resources/FabLogo.svg
new file mode 100644
index 0000000..fafbe36
--- /dev/null
+++ b/Plugins/Fab/Resources/FabLogo.svg
@@ -0,0 +1,9 @@
+
diff --git a/Plugins/Fab/Resources/FabLogoAlternate.svg b/Plugins/Fab/Resources/FabLogoAlternate.svg
new file mode 100644
index 0000000..7131995
--- /dev/null
+++ b/Plugins/Fab/Resources/FabLogoAlternate.svg
@@ -0,0 +1,4 @@
+
diff --git a/Plugins/Fab/Resources/Icon128.png b/Plugins/Fab/Resources/Icon128.png
new file mode 100644
index 0000000..627f1c1
--- /dev/null
+++ b/Plugins/Fab/Resources/Icon128.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3e596571c9c904bcae5f625028a0ca33b7594ff1c581f25347efb72b58d93404
+size 2444
diff --git a/Plugins/Fab/Resources/Icon40x40.png b/Plugins/Fab/Resources/Icon40x40.png
new file mode 100644
index 0000000..1682773
--- /dev/null
+++ b/Plugins/Fab/Resources/Icon40x40.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6004a8bf51c1c990c8a8af7a8b305d2b7fca828828b8d69281bbdaffb4178812
+size 734
diff --git a/Plugins/Fab/Resources/Logo32x32.png b/Plugins/Fab/Resources/Logo32x32.png
new file mode 100644
index 0000000..ed11e11
--- /dev/null
+++ b/Plugins/Fab/Resources/Logo32x32.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2a44cada5f98766770e0ce41cbee1deffe1ab747ac3e25f5dcbc7bf53100d7a3
+size 593
diff --git a/Plugins/Fab/Resources/Logo80x80.png b/Plugins/Fab/Resources/Logo80x80.png
new file mode 100644
index 0000000..b0f44ee
--- /dev/null
+++ b/Plugins/Fab/Resources/Logo80x80.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab0bf8f21d3b8d12675acb7da62e782a90810aea538390237464568ebe0ba312
+size 1484
diff --git a/Plugins/Fab/Source/Fab/Fab.Build.cs b/Plugins/Fab/Source/Fab/Fab.Build.cs
new file mode 100644
index 0000000..0a34a5c
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Fab.Build.cs
@@ -0,0 +1,88 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+using System.IO;
+using UnrealBuildTool;
+
+public class Fab : ModuleRules
+{
+ public Fab(ReadOnlyTargetRules Target) : base(Target)
+ {
+ PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
+ IWYUSupport = IWYUSupport.None;
+
+ PublicIncludePaths.AddRange(
+ new string[]
+ {
+ // ... add public include paths required here ...
+ // Path.Combine(ModuleDirectory, "ThirdParty")
+ }
+ );
+
+ PrivateIncludePaths.AddRange(
+ new string[]
+ {
+ // ... add other private include paths required here ...
+ }
+ );
+
+ PublicDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "Core",
+ // ... add other public dependencies that you statically link with here ...
+ }
+ );
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "ApplicationCore",
+ "AssetTools",
+ "BuildPatchServices",
+ "ContentBrowser",
+ "CoreUObject",
+ "DesktopWidgets",
+ "EditorScriptingUtilities",
+ "EditorStyle",
+ "EditorSubsystem",
+ "Engine",
+ "EOSSDK",
+ "EOSShared",
+ "FileUtilities",
+ "Foliage",
+ "GameProjectGeneration",
+ "HTTP",
+ "InputCore",
+ "InterchangeCore",
+ "InterchangeEngine",
+ "InterchangePipelines",
+ "Json",
+ "JsonUtilities",
+ "LevelEditor",
+ "MainFrame",
+ "PlacementMode",
+ "Projects",
+ "RenderCore",
+ "Slate",
+ "SlateCore",
+ "ToolMenus",
+ "ToolWidgets",
+ "UMG",
+ "UnrealEd",
+ "WebBrowser",
+ "InterchangeImport",
+ "InterchangeNodes",
+ "InterchangeFactoryNodes",
+ "DeveloperSettings"
+ // ... add private dependencies that you statically link with here ...
+ }
+ );
+
+ DynamicallyLoadedModuleNames.AddRange(
+ new string[]
+ {
+ // ... add any modules that your module loads dynamically here ...
+ }
+ );
+ }
+}
diff --git a/Plugins/Fab/Source/Fab/Private/FabAuthentication.cpp b/Plugins/Fab/Source/Fab/Private/FabAuthentication.cpp
new file mode 100644
index 0000000..0a785cf
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabAuthentication.cpp
@@ -0,0 +1,415 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "FabAuthentication.h"
+
+#include "FabBrowser.h"
+#include "FabLog.h"
+#include "FabSettings.h"
+#include "IEOSSDKManager.h"
+
+#include "Async/Async.h"
+
+#include "Misc/CommandLine.h"
+
+namespace FabAuthentication
+{
+ void Init()
+ {
+ IEOSSDKManager* SDKManager = IEOSSDKManager::Get();
+ if (SDKManager && SDKManager->IsInitialized())
+ {
+ const UFabSettings* FabSettings = GetDefault();
+ FString ProductId;
+ FString SandboxId;
+ FString DeploymentId;
+ FString ClientId;
+ FString ClientSecret;
+ FString EncryptionKey;
+ const UEosConstants* const Constants = Cast(FSoftObjectPath("/Fab/Data/FabEos.FabEos").TryLoad());
+ switch (FabSettings->Environment)
+ {
+ case EFabEnvironment::Prod:
+ {
+ ProductId = Constants->Prod.ProductId;
+ SandboxId = Constants->Prod.SandboxId;
+ DeploymentId = Constants->Prod.DeploymentId;
+ ClientId = Constants->Prod.ClientCredentialsId;
+ ClientSecret = Constants->Prod.ClientCredentialsSecret;
+ EncryptionKey = Constants->Prod.EncryptionKey;
+ break;
+ }
+ case EFabEnvironment::Gamedev:
+ {
+ ProductId = Constants->GameDev.ProductId;
+ SandboxId = Constants->GameDev.SandboxId;
+ DeploymentId = Constants->GameDev.DeploymentId;
+ ClientId = Constants->GameDev.ClientCredentialsId;
+ ClientSecret = Constants->GameDev.ClientCredentialsSecret;
+ EncryptionKey = Constants->GameDev.EncryptionKey;
+ break;
+ }
+ default:
+ {
+ ProductId = Constants->Prod.ProductId;
+ SandboxId = Constants->Prod.SandboxId;
+ DeploymentId = Constants->Prod.DeploymentId;
+ ClientId = Constants->Prod.ClientCredentialsId;
+ ClientSecret = Constants->Prod.ClientCredentialsSecret;
+ EncryptionKey = Constants->Prod.EncryptionKey;
+ }
+ }
+
+ const FTCHARToUTF8 Utf8ProductId(*ProductId);
+ const FTCHARToUTF8 Utf8SandboxId(*SandboxId);
+ const FTCHARToUTF8 Utf8ClientId(*ClientId);
+ const FTCHARToUTF8 Utf8ClientSecret(*ClientSecret);
+ const FTCHARToUTF8 Utf8EncryptionKey(*EncryptionKey);
+ const FTCHARToUTF8 Utf8DeploymentId(*DeploymentId);
+ //const FTCHARToUTF8 Utf8CacheDirectory(*CacheDirectory);
+
+ EOS_Platform_Options PlatformOptions = {};
+ PlatformOptions.ApiVersion = EOS_PLATFORM_OPTIONS_API_LATEST;
+
+ PlatformOptions.ClientCredentials.ClientId = Utf8ClientId.Get();
+ PlatformOptions.ClientCredentials.ClientSecret = Utf8ClientSecret.Get();
+ PlatformOptions.ProductId = Utf8ProductId.Get();
+ PlatformOptions.DeploymentId = Utf8DeploymentId.Get();
+ PlatformOptions.SandboxId = Utf8SandboxId.Get();
+ PlatformOptions.EncryptionKey = Utf8EncryptionKey.Get();
+ // PlatformOptions.CacheDirectory = Utf8CacheDirectory.Get();
+ PlatformOptions.bIsServer = EOS_FALSE;
+ PlatformOptions.Flags = 0;
+ PlatformOptions.Flags |= EOS_PF_DISABLE_OVERLAY;
+ PlatformOptions.Reserved = nullptr;
+ PlatformOptions.TickBudgetInMilliseconds = 0;
+ PlatformOptions.IntegratedPlatformOptionsContainerHandle = nullptr;
+
+ if (FabSettings->Environment == EFabEnvironment::Gamedev)
+ {
+ static struct FReservedOptions
+ {
+ int32_t ApiVersion;
+ const char* BackendEnvironment;
+ }
+ ReservedOptions = {1, "GameDev"};
+ PlatformOptions.Reserved = &ReservedOptions;
+ }
+
+ PlatformHandle = SDKManager->CreatePlatform(PlatformOptions);
+
+ // Sequence of events
+ // 1. Login using persist
+ // 2. If that fails - login using the exchange code
+ LoginUsingPersist();
+ }
+ }
+
+ void Shutdown()
+ {
+ PlatformHandle.Reset();
+ }
+
+ void EOS_CALL ExchangeCodeLoginCompleteCallbackFn(const EOS_Auth_LoginCallbackInfo* Data)
+ {
+ if (Data->ResultCode == EOS_EResult::EOS_Success)
+ {
+ FAB_LOG("User logged in");
+ const int32_t AccountsCount = EOS_Auth_GetLoggedInAccountsCount(AuthHandle);
+ for (int32_t AccountIdx = 0; AccountIdx < AccountsCount; ++AccountIdx)
+ {
+ const EOS_EpicAccountId AccountId = EOS_Auth_GetLoggedInAccountByIndex(AuthHandle, AccountIdx);
+ EOS_ELoginStatus LoginStatus = EOS_Auth_GetLoginStatus(AuthHandle, Data->LocalUserId);
+ EpicAccountId = AccountId;
+ }
+
+ LoggedIn();
+ return;
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_Auth_PinGrantCode)
+ {
+ FAB_LOG_ERROR("Login pin grant code");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_Auth_MFARequired)
+ {
+ FAB_LOG_ERROR("Login MFA required");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_InvalidUser)
+ {
+ FAB_LOG_ERROR("Invalid user");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_Auth_AccountFeatureRestricted)
+ {
+ FAB_LOG_ERROR("Login failed, account is restricted");
+ }
+ else if (EOS_EResult_IsOperationComplete(Data->ResultCode))
+ {
+ const FString Code = EOS_EResult_ToString(Data->ResultCode);
+ FAB_LOG_ERROR("Login failed - error code: %s", *Code);
+ }
+ else
+ {
+ const FString Code = EOS_EResult_ToString(Data->ResultCode);
+ FAB_LOG_ERROR("Login failed - error code: %s", *Code);
+ }
+ }
+
+ void EOS_CALL PersistLoginCompleteCallbackFn(const EOS_Auth_LoginCallbackInfo* Data)
+ {
+ if (Data->ResultCode == EOS_EResult::EOS_Success)
+ {
+ FAB_LOG("User logged in");
+ const int32_t AccountsCount = EOS_Auth_GetLoggedInAccountsCount(AuthHandle);
+ for (int32_t AccountIdx = 0; AccountIdx < AccountsCount; ++AccountIdx)
+ {
+ const EOS_EpicAccountId AccountId = EOS_Auth_GetLoggedInAccountByIndex(AuthHandle, AccountIdx);
+ EOS_ELoginStatus LoginStatus = EOS_Auth_GetLoginStatus(AuthHandle, Data->LocalUserId);
+ EpicAccountId = AccountId;
+ }
+
+ LoggedIn();
+ return;
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_Auth_PinGrantCode)
+ {
+ FAB_LOG_ERROR("Login pin grant code");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_Auth_MFARequired)
+ {
+ FAB_LOG_ERROR("Login MFA required");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_InvalidUser)
+ {
+ FAB_LOG_ERROR("Invalid user");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_Auth_AccountFeatureRestricted)
+ {
+ FAB_LOG_ERROR("Login failed, account is restricted");
+ }
+ else if (EOS_EResult_IsOperationComplete(Data->ResultCode))
+ {
+ const FString Code = EOS_EResult_ToString(Data->ResultCode);
+ FAB_LOG_ERROR("Login failed - error code: %s", *Code);
+ }
+ else
+ {
+ const FString Code = EOS_EResult_ToString(Data->ResultCode);
+ FAB_LOG_ERROR("Login failed - error code: %s", *Code);
+ }
+
+ // Fallback login using exchange code
+ // Sending empty code reads from commandline
+ LoginUsingExchangeCode("");
+ }
+
+ void EOS_CALL AccountPortalLoginCompleteCallbackFn(const EOS_Auth_LoginCallbackInfo* Data)
+ {
+ if (Data->ResultCode == EOS_EResult::EOS_Success)
+ {
+ FAB_LOG("User logged in");
+ const int32_t AccountsCount = EOS_Auth_GetLoggedInAccountsCount(AuthHandle);
+ for (int32_t AccountIdx = 0; AccountIdx < AccountsCount; ++AccountIdx)
+ {
+ const EOS_EpicAccountId AccountId = EOS_Auth_GetLoggedInAccountByIndex(AuthHandle, AccountIdx);
+ EOS_ELoginStatus LoginStatus = EOS_Auth_GetLoginStatus(AuthHandle, Data->LocalUserId);
+ EpicAccountId = AccountId;
+ }
+
+ LoggedIn();
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_Auth_PinGrantCode)
+ {
+ FAB_LOG_ERROR("Login pin grant code");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_Auth_MFARequired)
+ {
+ FAB_LOG_ERROR("Login MFA required");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_InvalidUser)
+ {
+ FAB_LOG_ERROR("Invalid user");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_Auth_AccountFeatureRestricted)
+ {
+ FAB_LOG_ERROR("Login failed, account is restricted");
+ }
+ else if (EOS_EResult_IsOperationComplete(Data->ResultCode))
+ {
+ const FString Code = EOS_EResult_ToString(Data->ResultCode);
+ FAB_LOG_ERROR("Login failed - error code: %s", *Code);
+ }
+ else
+ {
+ const FString Code = EOS_EResult_ToString(Data->ResultCode);
+ FAB_LOG_ERROR("Login failed - error code: %s", *Code);
+ }
+ }
+
+ bool LoginUsingExchangeCode(FString ExchangeCode)
+ {
+ FAB_LOG("Logging in using exchange code");
+
+ if (ExchangeCode.IsEmpty()) // Read exchange code from commandline if it is not passed in
+ {
+ FAB_LOG("Reading exchange code from commandline");
+ FString AuthType;
+ if (FParse::Value(FCommandLine::Get(), TEXT("AUTH_TYPE="), AuthType) && AuthType == TEXT("exchangecode"))
+ {
+ FParse::Value(FCommandLine::Get(), TEXT("AUTH_PASSWORD="), ExchangeCode);
+ }
+ }
+
+ AuthHandle = EOS_Platform_GetAuthInterface(*PlatformHandle);
+
+ EOS_Auth_Credentials Credentials = {0};
+ Credentials.ApiVersion = EOS_AUTH_CREDENTIALS_API_LATEST;
+
+ Credentials.Type = EOS_ELoginCredentialType::EOS_LCT_ExchangeCode;
+ Credentials.Id = "";
+
+ const FTCHARToUTF8 UTF8Token(*ExchangeCode);
+ Credentials.Token = UTF8Token.Get();
+
+ EOS_Auth_LoginOptions LoginOptions = {0};
+ LoginOptions.ApiVersion = EOS_AUTH_LOGIN_API_LATEST;
+ LoginOptions.ScopeFlags = EOS_EAuthScopeFlags::EOS_AS_BasicProfile;
+ LoginOptions.Credentials = &Credentials;
+
+ EOS_Auth_Login(AuthHandle, &LoginOptions, nullptr, ExchangeCodeLoginCompleteCallbackFn);
+
+ return true;
+ }
+
+ bool LoginUsingPersist()
+ {
+ FAB_LOG("Logging in using persist");
+
+ AuthHandle = EOS_Platform_GetAuthInterface(*PlatformHandle);
+
+ EOS_Auth_Credentials Credentials = {0};
+ Credentials.ApiVersion = EOS_AUTH_CREDENTIALS_API_LATEST;
+
+ Credentials.Type = EOS_ELoginCredentialType::EOS_LCT_PersistentAuth;
+
+ EOS_Auth_LoginOptions LoginOptions = {0};
+ LoginOptions.ApiVersion = EOS_AUTH_LOGIN_API_LATEST;
+ LoginOptions.ScopeFlags = EOS_EAuthScopeFlags::EOS_AS_BasicProfile;
+ LoginOptions.Credentials = &Credentials;
+
+ EOS_Auth_Login(AuthHandle, &LoginOptions, nullptr, PersistLoginCompleteCallbackFn);
+
+ return true;
+ }
+
+ bool LoginUsingAccountPortal()
+ {
+ FAB_LOG("Logging in using account portal");
+
+ AuthHandle = EOS_Platform_GetAuthInterface(*PlatformHandle);
+
+ EOS_Auth_Credentials Credentials = {0};
+ Credentials.ApiVersion = EOS_AUTH_CREDENTIALS_API_LATEST;
+
+ Credentials.Type = EOS_ELoginCredentialType::EOS_LCT_AccountPortal;
+
+ EOS_Auth_LoginOptions LoginOptions = {0};
+ LoginOptions.ApiVersion = EOS_AUTH_LOGIN_API_LATEST;
+ LoginOptions.ScopeFlags = EOS_EAuthScopeFlags::EOS_AS_BasicProfile;
+ LoginOptions.Credentials = &Credentials;
+
+ EOS_Auth_Login(AuthHandle, &LoginOptions, nullptr, AccountPortalLoginCompleteCallbackFn);
+
+ return true;
+ }
+
+ void EOS_CALL DeletePersistentAuthCompleteCallbackFn(const EOS_Auth_DeletePersistentAuthCallbackInfo* Data)
+ {
+ if (Data->ResultCode == EOS_EResult::EOS_Success)
+ {
+ FAB_LOG("Persistent auth deleted");
+ }
+ else if (Data->ResultCode == EOS_EResult::EOS_NotFound)
+ {
+ FAB_LOG("Persistent auth not found - unable to delete");
+ }
+ else
+ {
+ FAB_LOG_ERROR("Unable to delete persistent auth");
+ }
+ }
+
+ void PrintAuthToken(const EOS_Auth_Token* InAuthToken)
+ {
+ FAB_LOG("User client id: %s", *FString(InAuthToken->ClientId));
+ // FAB_LOG("User access token: %s", *FString(InAuthToken->AccessToken));
+ // FAB_LOG("User refresh token: %s", *FString(InAuthToken->RefreshToken));
+ }
+
+ void LoggedIn()
+ {
+ EOS_Auth_Token* UserAuthToken = nullptr;
+
+ EOS_Auth_CopyUserAuthTokenOptions CopyTokenOptions = {0};
+ CopyTokenOptions.ApiVersion = EOS_AUTH_COPYUSERAUTHTOKEN_API_LATEST;
+
+ if (EOS_Auth_CopyUserAuthToken(AuthHandle, &CopyTokenOptions, EpicAccountId, &UserAuthToken) == EOS_EResult::EOS_Success)
+ {
+ const FString AccessToken = FString(UserAuthToken->AccessToken);
+ PrintAuthToken(UserAuthToken);
+ EOS_Auth_Token_Release(UserAuthToken);
+ FFabBrowser::LoggedIn(AccessToken);
+ }
+ else
+ {
+ FAB_LOG_ERROR("User auth token is invalid");
+ }
+ }
+
+ void DeletePersistentAuth()
+ {
+ FAB_LOG("Delete persist auth");
+
+ EOS_Auth_DeletePersistentAuthOptions Options = {};
+ Options.ApiVersion = EOS_AUTH_DELETEPERSISTENTAUTH_API_LATEST;
+ EOS_Auth_DeletePersistentAuth(AuthHandle, &Options, nullptr, DeletePersistentAuthCompleteCallbackFn);
+ }
+
+ FString GetAuthToken()
+ {
+ EOS_Auth_Token* UserAuthToken = nullptr;
+
+ EOS_Auth_CopyUserAuthTokenOptions CopyTokenOptions = {0};
+ CopyTokenOptions.ApiVersion = EOS_AUTH_COPYUSERAUTHTOKEN_API_LATEST;
+
+ if (EOS_Auth_CopyUserAuthToken(AuthHandle, &CopyTokenOptions, EpicAccountId, &UserAuthToken) == EOS_EResult::EOS_Success)
+ {
+ const FString AccessToken = FString(UserAuthToken->AccessToken);
+ EOS_Auth_Token_Release(UserAuthToken);
+ return AccessToken;
+ }
+ else
+ {
+ FAB_LOG_ERROR("User auth token is invalid - unable to get auth token");
+ return "";
+ }
+ }
+
+ FString GetRefreshToken()
+ {
+ EOS_Auth_Token* UserAuthToken = nullptr;
+
+ EOS_Auth_CopyUserAuthTokenOptions CopyTokenOptions = {0};
+ CopyTokenOptions.ApiVersion = EOS_AUTH_COPYUSERAUTHTOKEN_API_LATEST;
+
+ if (EOS_Auth_CopyUserAuthToken(AuthHandle, &CopyTokenOptions, EpicAccountId, &UserAuthToken) == EOS_EResult::EOS_Success)
+ {
+ const FString RefreshToken = FString(UserAuthToken->RefreshToken);
+ EOS_Auth_Token_Release(UserAuthToken);
+ return RefreshToken;
+ }
+ else
+ {
+ FAB_LOG_ERROR("User auth token is invalid - unable to get refresh token");
+ return "";
+ }
+ }
+}
diff --git a/Plugins/Fab/Source/Fab/Private/FabAuthentication.h b/Plugins/Fab/Source/Fab/Private/FabAuthentication.h
new file mode 100644
index 0000000..7775b8a
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabAuthentication.h
@@ -0,0 +1,157 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "eos_auth.h"
+#include "eos_common.h"
+#include "eos_sdk.h"
+
+#include "Engine/DataAsset.h"
+
+#include "FabAuthentication.generated.h"
+
+USTRUCT()
+struct FEosConstantsGameDev
+{
+ GENERATED_BODY()
+
+ /** The product id for the running application, found on the dev portal */
+ UPROPERTY()
+ FString ProductId;
+
+ /** The sandbox id for the running application, found on the dev portal */
+ UPROPERTY()
+ FString SandboxId;
+
+ /** The deployment id for the running application, found on the dev portal */
+ UPROPERTY()
+ FString DeploymentId;
+
+ /** Client id of the service permissions entry, found on the dev portal */
+ UPROPERTY()
+ FString ClientCredentialsId;
+
+ /** Client secret for accessing the set of permissions, found on the dev portal */
+ UPROPERTY()
+ FString ClientCredentialsSecret;
+
+ /** Game name */
+ UPROPERTY()
+ FString GameName;
+
+ /** Encryption key. */
+ UPROPERTY()
+ FString EncryptionKey;
+
+ /** Product Version. */
+ UPROPERTY()
+ FString ProductVersion;
+};
+
+USTRUCT()
+struct FEosConstantsProd
+{
+ GENERATED_BODY()
+
+ /** The product id for the running application, found on the dev portal */
+ UPROPERTY()
+ FString ProductId;
+
+ /** The sandbox id for the running application, found on the dev portal */
+ UPROPERTY()
+ FString SandboxId;
+
+ /** The deployment id for the running application, found on the dev portal */
+ UPROPERTY()
+ FString DeploymentId;
+
+ /** Client id of the service permissions entry, found on the dev portal */
+ UPROPERTY()
+ FString ClientCredentialsId;
+
+ /** Client secret for accessing the set of permissions, found on the dev portal */
+ UPROPERTY()
+ FString ClientCredentialsSecret;
+
+ /** Game name */
+ UPROPERTY()
+ FString GameName;
+
+ /** Encryption key. */
+ UPROPERTY()
+ FString EncryptionKey;
+
+ /** Product Version. */
+ UPROPERTY()
+ FString ProductVersion;
+};
+
+UCLASS()
+class UEosConstants : public UDataAsset
+{
+ GENERATED_BODY()
+
+public:
+ UPROPERTY()
+ FEosConstantsGameDev GameDev;
+
+ UPROPERTY()
+ FEosConstantsProd Prod;
+};
+
+using IEOSPlatformHandlePtr = TSharedPtr;
+
+/**
+* Manages all user authentication
+*/
+namespace FabAuthentication
+{
+ inline IEOSPlatformHandlePtr PlatformHandle;
+ inline EOS_HAuth AuthHandle;
+ inline EOS_EpicAccountId EpicAccountId;
+
+ void Init();
+ void Shutdown();
+
+ bool LoginUsingExchangeCode(FString ExchangeCode);
+ bool LoginUsingRefreshToken(FString RefreshToken);
+ bool LoginUsingAccountPortal();
+ bool LoginUsingPersist();
+
+ FString GetAuthToken();
+ FString GetRefreshToken();
+
+ /**
+ * Deletes any locally stored persistent auth credentials for the currently logged in user of the local device.
+ */
+ void DeletePersistentAuth();
+
+ /**
+ * Utility for printing auth token info
+ */
+ void PrintAuthToken(const EOS_Auth_Token* InAuthToken);
+
+ /** Called when successfully logged in */
+ void LoggedIn();
+
+ /**
+ * Callback that is fired when the login operation completes, either successfully or in error
+ */
+ static void EOS_CALL ExchangeCodeLoginCompleteCallbackFn(const EOS_Auth_LoginCallbackInfo* Data);
+
+ /**
+ * Callback that is fired when the login operation completes, either successfully or in error
+ */
+ static void EOS_CALL AccountPortalLoginCompleteCallbackFn(const EOS_Auth_LoginCallbackInfo* Data);
+
+ /**
+ * Callback that is fired when the login operation completes, either successfully or in error
+ */
+ static void EOS_CALL PersistLoginCompleteCallbackFn(const EOS_Auth_LoginCallbackInfo* Data);
+
+ /**
+ * Callback that is fired when the delete persistent auth operation completes, either successfully or in error
+ */
+ static void EOS_CALL DeletePersistentAuthCompleteCallbackFn(const EOS_Auth_DeletePersistentAuthCallbackInfo* Data);
+}
diff --git a/Plugins/Fab/Source/Fab/Private/FabBrowser.cpp b/Plugins/Fab/Source/Fab/Private/FabBrowser.cpp
new file mode 100644
index 0000000..13c1ac9
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabBrowser.cpp
@@ -0,0 +1,443 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "FabBrowser.h"
+
+#include "ContentBrowserModule.h"
+#include "FabBrowserApi.h"
+#include "FabLog.h"
+#include "FabSettings.h"
+#include "FabSettingsWindow.h"
+#include "IWebBrowserPopupFeatures.h"
+#include "IWebBrowserWindow.h"
+#include "JsonObjectConverter.h"
+#include "LevelEditor.h"
+#include "ToolMenu.h"
+#include "ToolMenuEntry.h"
+#include "ToolMenuSection.h"
+#include "ToolMenus.h"
+#include "WebBrowserModule.h"
+
+#include "Framework/Application/SlateApplication.h"
+#include "Framework/MultiBox/MultiBoxExtender.h"
+
+#include "HAL/FileManager.h"
+#include "HAL/PlatformFileManager.h"
+
+#include "Interfaces/IMainFrameModule.h"
+#include "Interfaces/IPluginManager.h"
+
+#include "Math/Vector2D.h"
+
+#include "Misc/FileHelper.h"
+#include "Misc/MessageDialog.h"
+
+#include "Styling/CoreStyle.h"
+#include "Styling/SlateStyle.h"
+#include "Styling/SlateStyleRegistry.h"
+
+#include "UObject/GCObject.h"
+
+#include "Utilities/FabLocalAssets.h"
+
+#include "Widgets/Images/SImage.h"
+#include "Widgets/SBoxPanel.h"
+#include "Widgets/Docking/SDockTab.h"
+
+#define LOCTEXT_NAMESPACE "Fab"
+
+TSharedPtr FFabBrowser::WebBrowserInstance = nullptr;
+
+TObjectPtr FFabBrowser::JavascriptApi = nullptr;
+
+TSharedPtr FFabBrowser::DockTab = nullptr;
+TUniquePtr FFabBrowser::SlateStyleSet = nullptr;
+TSharedPtr FFabBrowser::WebBrowserWindow = nullptr;
+TObjectPtr FFabBrowser::FabPluginSettings = nullptr;
+
+const FName FFabBrowser::TabId = TEXT("FabTab");
+
+const FText FFabBrowser::FabLabel = LOCTEXT("Fab.Label", "Fab");
+const FText FFabBrowser::FabTooltip = LOCTEXT("Fab.Tooltip", "Get content from Fab");
+const FName FFabBrowser::FabMenuIconName = TEXT("Fab.MenuIcon");
+const FName FFabBrowser::FabAssetIconName = TEXT("Fab.AssetIcon");
+const FName FFabBrowser::FabToolbarIconName = TEXT("Fab.ToolbarIcon");
+
+void FFabBrowser::Init()
+{
+ RegisterSlateStyle();
+ RegisterNomadTab();
+ SetupEntryPoints();
+ ExtendContextMenuInContentBrowser();
+}
+
+void FFabBrowser::ExtendContextMenuInContentBrowser()
+{
+ FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser");
+ TArray& MenuExtenders = ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
+ FAssetViewExtraStateGenerator StateGenerator(
+ FOnGenerateAssetViewExtraStateIndicators::CreateStatic(&FFabBrowser::OnFabAssetIconGenerate),
+ FOnGenerateAssetViewExtraStateIndicators()
+ );
+ ContentBrowserModule.AddAssetViewExtraStateGenerator(StateGenerator);
+ MenuExtenders.Add(FContentBrowserMenuExtender_SelectedAssets::CreateStatic(&FFabBrowser::OnExtendContentBrowserAssetSelectionMenu));
+}
+
+void FFabBrowser::RegisterSlateStyle()
+{
+ SlateStyleSet = MakeUnique(TEXT("FabStyle"));
+ SlateStyleSet->SetContentRoot(IPluginManager::Get().FindPlugin(TEXT("Fab"))->GetBaseDir() / TEXT("Resources"));
+
+ const FString IconPath = SlateStyleSet->RootToContentDir(TEXT("FabLogo.svg"));
+ const FString AlternateIconPath = SlateStyleSet->RootToContentDir(TEXT("FabLogoAlternate.svg"));
+ SlateStyleSet->Set(FabMenuIconName, new FSlateVectorImageBrush(IconPath, CoreStyleConstants::Icon16x16));
+ SlateStyleSet->Set(FabAssetIconName, new FSlateVectorImageBrush(AlternateIconPath, CoreStyleConstants::Icon20x20));
+ SlateStyleSet->Set(FabToolbarIconName, new FSlateVectorImageBrush(IconPath, CoreStyleConstants::Icon20x20));
+
+ FSlateStyleRegistry::RegisterSlateStyle(*SlateStyleSet);
+
+ FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
+}
+
+void FFabBrowser::SetupEntryPoints()
+{
+ const FUIAction InvokeTabAction = FUIAction(
+ FExecuteAction::CreateLambda(
+ [LevelEditorModuleName = FName("LevelEditor")]()
+ {
+ FModuleManager::GetModuleChecked(LevelEditorModuleName).GetLevelEditorTabManager()->TryInvokeTab(TabId);
+ }
+ ),
+ FCanExecuteAction()
+ );
+
+ FToolMenuEntry& ToolMenuEntry = UToolMenus::Get()->ExtendMenu("ContentBrowser.Toolbar")->FindOrAddSection("New").AddEntry(
+ FToolMenuEntry::InitToolBarButton(
+ "OpenFabWindow",
+ InvokeTabAction,
+ FabLabel,
+ FabTooltip,
+ FSlateIcon(SlateStyleSet->GetStyleSetName(), FabToolbarIconName),
+ EUserInterfaceActionType::Button
+ )
+ );
+ ToolMenuEntry.StyleNameOverride = FName("CalloutToolbar");
+
+ UToolMenu* WindowMenu = UToolMenus::Get()->ExtendMenu("MainFrame.MainMenu.Window");
+
+ FToolMenuSection* ContentSectionPtr = WindowMenu->FindSection("GetContent");
+ if (!ContentSectionPtr)
+ {
+ ContentSectionPtr = &WindowMenu->AddSection("GetContent", NSLOCTEXT("MainAppMenu", "GetContentHeader", "Get Content"));
+ }
+
+ ContentSectionPtr->AddMenuEntry(
+ TEXT("OpenFabTab"),
+ LOCTEXT("OpenFabTab_Label", "Fab"),
+ LOCTEXT("OpenFabTab_Desc", "Opens the Fab Plugin."),
+ FSlateIcon(SlateStyleSet->GetStyleSetName(), FabMenuIconName),
+ InvokeTabAction
+ );
+
+ // Add a Fab entry to the Content Browser's Add popup menu
+ UToolMenus::Get()->ExtendMenu(TEXT("ContentBrowser.AddNewContextMenu"))->AddSection(TEXT("ContentBrowserGetContent"), LOCTEXT("GetContentText", "Get Content")).AddEntry(
+ FToolMenuEntry::InitMenuEntry(TEXT("OpenFabWindow"), FabLabel, FabTooltip, FSlateIcon(SlateStyleSet->GetStyleSetName(), FabMenuIconName), InvokeTabAction)
+ );
+}
+
+// Extend the context menu to view listings in Fab
+TSharedRef FFabBrowser::OnExtendContentBrowserAssetSelectionMenu(const TArray& SelectedAssets)
+{
+ TSharedRef Extender = MakeShared();
+
+ if (SelectedAssets.Num() != 1)
+ {
+ return Extender;
+ }
+ const FAssetData AssetData = SelectedAssets[0];
+ const FString ObjectPath = AssetData.GetObjectPathString();
+ FString FabListingId;
+ UFabLocalAssets::GetListingID(ObjectPath, FabListingId);
+
+ if (FabListingId.IsEmpty())
+ {
+ return Extender;
+ }
+
+ Extender->AddMenuExtension(
+ "CommonAssetActions",
+ EExtensionHook::After,
+ nullptr,
+ FMenuExtensionDelegate::CreateLambda(
+ [FabListingId](FMenuBuilder& MenuBuilder)
+ {
+ MenuBuilder.AddMenuEntry(
+ FText::FromString("View in Fab"),
+ FText::FromString("View the asset in Fab plugin"),
+ FSlateIcon(SlateStyleSet->GetStyleSetName(), FabMenuIconName),
+ FUIAction(
+ FExecuteAction::CreateLambda(
+ [FabListingId]()
+ {
+ FFabBrowser::OpenURL(GetUrl() / "listings" / FabListingId);
+ }
+ )
+ )
+ );
+ }
+ )
+ );
+ return Extender;
+}
+
+TSharedRef FFabBrowser::OnFabAssetIconGenerate(const FAssetData& AssetData)
+{
+ const FSlateBrush* FabImage = nullptr;
+
+ const FString ObjectPath = AssetData.GetObjectPathString();
+ FString FabListingId;
+ UFabLocalAssets::GetListingID(ObjectPath, FabListingId);
+
+ if (!FabListingId.IsEmpty())
+ {
+ FabImage = SlateStyleSet->GetBrush(FabAssetIconName);
+ }
+
+ return SNew(SBox)
+ .Padding(4.0f, 4.0f, 0.0f, 0.0f)
+ .IsEnabled(FabImage != nullptr)
+ [
+ SNew(SImage)
+ .Image(FabImage)
+ .ToolTipText(FText::FromString("Imported from FAB"))
+ ];
+}
+
+void FFabBrowser::RegisterNomadTab()
+{
+ auto RegisterSpawner = [](TSharedPtr)
+ {
+ FGlobalTabmanager::Get()->RegisterNomadTabSpawner(TabId, FOnSpawnTab::CreateStatic(OpenTab)).SetAutoGenerateMenuEntry(false).SetDisplayName(FabLabel).
+ SetTooltipTextAttribute(FabTooltip).SetIcon(FSlateIcon(SlateStyleSet->GetStyleSetName(), FabMenuIconName));
+ };
+
+ FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor");
+ if (LevelEditorModule.GetLevelEditorInstance().IsValid())
+ {
+ RegisterSpawner(LevelEditorModule.GetLevelEditorInstance().Pin());
+ }
+ else
+ {
+ LevelEditorModule.OnLevelEditorCreated().AddLambda(RegisterSpawner);
+ }
+}
+
+FString FFabBrowser::GetUrl()
+{
+ const FString ProdUrl = TEXT("https://www.fab.com/plugins/ue5");
+ if (FabPluginSettings == nullptr)
+ {
+ return ProdUrl;
+ }
+
+ switch (FabPluginSettings->Environment)
+ {
+ case EFabEnvironment::Prod:
+ {
+ return ProdUrl;
+ }
+ case EFabEnvironment::Gamedev:
+ {
+ return TEXT("https://fab.cceb.dev.use1a.on.epicgames.com/plugins/ue5");
+ }
+ case EFabEnvironment::Test:
+ {
+ return TEXT("https://fab.daec.live.use1a.on.epicgames.com/plugins/ue5");
+ }
+ case EFabEnvironment::CustomUrl:
+ {
+ return FabPluginSettings->CustomUrl;
+ }
+ default:
+ {
+ return ProdUrl;
+ }
+ }
+}
+
+TSharedRef FFabBrowser::OpenTab(const FSpawnTabArgs&)
+{
+ LogEvent(
+ {
+ "Open Tab",
+ "Plugin",
+ "Click"
+ }
+ );
+
+ FabPluginSettings = GetDefault();
+
+ JavascriptApi = NewObject();
+ JavascriptApi->AddToRoot(); //Don't garbage collect
+
+ IWebBrowserModule& WebBrowserModule = IWebBrowserModule::Get();
+ if (!IWebBrowserModule::IsAvailable() || !WebBrowserModule.IsWebModuleAvailable())
+ {
+ FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Failed to load the plugin. Please enable Web WebBrowserWindow in the plugin manager to use Emporium."));
+ return SNew(SDockTab).TabRole(ETabRole::NomadTab);
+ }
+
+ FCreateBrowserWindowSettings WindowSettings;
+
+ FString PluginPath = IPluginManager::Get().FindPlugin(TEXT("Fab"))->GetBaseDir();
+ FString IndexUrl = FPaths::ConvertRelativePathToFull(FPaths::Combine(PluginPath, TEXT("ThirdParty"), TEXT("index.html")));
+ FString FinalUrl = FPaths::Combine(TEXT("file:///"), IndexUrl);
+ WindowSettings.InitialURL = FinalUrl;
+ WindowSettings.BrowserFrameRate = 60;
+
+ IWebBrowserSingleton* WebBrowserSingleton = WebBrowserModule.GetSingleton();
+ WebBrowserSingleton->SetDevToolsShortcutEnabled(true);
+
+ WebBrowserWindow = WebBrowserSingleton->CreateBrowserWindow(WindowSettings);
+ WebBrowserWindow->OnUnhandledKeyUp().BindLambda([](const FKeyEvent&) { return true; });
+ WebBrowserWindow->OnUnhandledKeyDown().BindLambda([](const FKeyEvent&) { return true; });
+ WebBrowserWindow->OnUrlChanged().AddLambda(
+ [](const FString& Url)
+ {
+ if (FabPluginSettings->Environment != EFabEnvironment::Prod)
+ return;
+
+ FString Domain, Protocol;
+ Url.Split("://", &Protocol, &Domain);
+ if (!Protocol.Contains("http"))
+ {
+ return;
+ }
+
+ if (int32 PathIndex; Domain.FindChar('/', PathIndex))
+ {
+ Domain = Domain.Left(PathIndex);
+ }
+
+ Domain = Domain.Replace(TEXT("www."), TEXT(""));
+
+ if (!Domain.Contains("fab.com"))
+ {
+ FAB_LOG_ERROR("Trying to access thirdparty url [%s] in plugin browser. Redirecting back to fab.com", *Url);
+ WebBrowserWindow->LoadURL(GetUrl());
+ FPlatformProcess::LaunchURL(*Url, nullptr, nullptr);
+ }
+ }
+ );
+
+ if (FabPluginSettings->bEnableDebugOptions)
+ {
+ WebBrowserWindow.Get()->OnCreateWindow().BindLambda(
+ [](const TWeakPtr& NewBrowserWindow, const TWeakPtr& PopupFeatures)
+ {
+ const TSharedRef DialogMainWindow = SNew(SWindow)
+ .ClientSize(FVector2D(700, 700))
+ .SupportsMaximize(true)
+ .SupportsMinimize(true)
+ [
+ SNew(SVerticalBox)
+ + SVerticalBox::Slot().HAlign(HAlign_Fill).VAlign(VAlign_Fill)
+ [
+ SNew(SWebBrowser, NewBrowserWindow.Pin())
+ ]
+ ];
+ FSlateApplication::Get().AddWindow(DialogMainWindow);
+ return true;
+ }
+ );
+ }
+
+ bool bShowAddressBar = FabPluginSettings->Environment == EFabEnvironment::CustomUrl;
+
+ SAssignNew(WebBrowserInstance, SWebBrowser, WebBrowserWindow).ShowAddressBar(bShowAddressBar).ShowControls(bShowAddressBar);
+
+ WebBrowserInstance->BindUObject(TEXT("fab"), Cast(JavascriptApi), true);
+ WebBrowserWindow->Reload();
+
+ SAssignNew(DockTab, SDockTab).TabRole(ETabRole::NomadTab).OnTabClosed_Lambda(
+ [](TSharedRef InParentTab)
+ {
+ WebBrowserInstance->UnbindUObject(TEXT("fab"), Cast(JavascriptApi), true);
+ WebBrowserInstance.Reset();
+ WebBrowserWindow.Reset();
+ DockTab.Reset();
+ }
+ )
+ [
+ WebBrowserInstance.ToSharedRef()
+ ];
+
+ return DockTab.ToSharedRef();
+}
+
+void FFabBrowser::ExecuteJavascript(const FString& InSrcScript)
+{
+ if (WebBrowserInstance == nullptr)
+ {
+ return;
+ }
+
+ WebBrowserInstance->ExecuteJavascript(InSrcScript);
+}
+
+void FFabBrowser::Shutdown()
+{
+ WebBrowserInstance.Reset();
+ WebBrowserWindow.Reset();
+ DockTab.Reset();
+ FSlateStyleRegistry::UnRegisterSlateStyle(*SlateStyleSet);
+ FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(TabId);
+}
+
+void FFabBrowser::LoggedIn(const FString& InAccessToken)
+{
+ ExecuteJavascript(FString::Printf(TEXT("window.ue.fab.onLoginSuccessful('%s');"), *InAccessToken));
+}
+
+void FFabBrowser::GetSignedUrl(const FString& AssetId, const int32 Tier)
+{
+ ExecuteJavascript(FString::Printf(TEXT("window.ue.fab.getSignedUrl('%s', %d)"), *AssetId, Tier));
+}
+
+void FFabBrowser::LogEvent(const FFabAnalyticsPayload& Payload)
+{
+ FString JSONPayload;
+ FJsonObjectConverter::UStructToJsonObjectString(Payload, JSONPayload, 0, 0);
+ FAB_LOG("%s", *JSONPayload);
+
+ // ExecuteJavascript(FString::Printf(TEXT("window.ue.fab.onDownloaded()")));
+}
+
+void FFabBrowser::ShowSettings()
+{
+ const TSharedRef Window = SNew(SWindow)
+ .Title(LOCTEXT("FabSettingsLabel", "Fab Settings"))
+ .ClientSize(FVector2D(600.f, 300.f))
+ .SizingRule(ESizingRule::UserSized);
+
+ TSharedPtr SettingsWindow;
+ Window->SetContent(SAssignNew(SettingsWindow, SFabSettingsWindow).WidgetWindow(Window));
+ TSharedPtr ParentWindow;
+ if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
+ {
+ const IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame");
+ ParentWindow = MainFrame.GetParentWindow();
+ }
+
+ FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false);
+}
+
+void FFabBrowser::OpenURL(const FString& InURL)
+{
+ FModuleManager::GetModuleChecked("LevelEditor").GetLevelEditorTabManager()->TryInvokeTab(TabId);
+ if (WebBrowserWindow.Get()->GetUrl() != InURL)
+ {
+ WebBrowserWindow.Get()->LoadURL(InURL);
+ }
+}
+
+#undef LOCTEXT_NAMESPACE
diff --git a/Plugins/Fab/Source/Fab/Private/FabBrowser.h b/Plugins/Fab/Source/Fab/Private/FabBrowser.h
new file mode 100644
index 0000000..bf773e8
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabBrowser.h
@@ -0,0 +1,70 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "ContentBrowserDelegates.h"
+#include "FabBrowser.generated.h"
+
+class SDockTab;
+class SWebBrowser;
+class FSpawnTabArgs;
+class IWebBrowserWindow;
+class UFabBrowserApi;
+class UFabSettings;
+class FSlateStyleSet;
+class FExtender;
+
+USTRUCT()
+struct FFabAnalyticsPayload
+{
+ GENERATED_BODY()
+
+ UPROPERTY()
+ FString InteractionType;
+
+ UPROPERTY()
+ FString EventCategory;
+
+ UPROPERTY()
+ FString EventAction;
+};
+
+class FFabBrowser
+{
+private:
+ static TSharedPtr WebBrowserInstance;
+ static TObjectPtr JavascriptApi;
+ static TSharedPtr DockTab;
+ static TUniquePtr SlateStyleSet;
+ static TSharedPtr WebBrowserWindow;
+ static TObjectPtr FabPluginSettings;
+
+ static const FName TabId;
+ static const FText FabLabel;
+ static const FText FabTooltip;
+ static const FName FabMenuIconName;
+ static const FName FabAssetIconName;
+ static const FName FabToolbarIconName;
+
+ static void RegisterSlateStyle();
+ static void RegisterNomadTab();
+ static void ExtendContextMenuInContentBrowser();
+ static void SetupEntryPoints();
+ static void ExecuteJavascript(const FString& InSrcScript);
+ static TSharedRef OpenTab(const FSpawnTabArgs& InArgs);
+ static TSharedRef OnExtendContentBrowserAssetSelectionMenu(const TArray& SelectedAssets);
+ static TSharedRef OnFabAssetIconGenerate(const FAssetData& AssetData);
+
+public:
+ static void Init();
+ static void Shutdown();
+
+ static void LogEvent(const FFabAnalyticsPayload& Payload);
+ static void LoggedIn(const FString& InAccessToken);
+ static void GetSignedUrl(const FString& AssetId, const int32 Tier);
+ static TObjectPtr GetBrowserApi() { return JavascriptApi; }
+ static void ShowSettings();
+ static void OpenURL(const FString& InURL = GetUrl());
+ static FString GetUrl();
+};
diff --git a/Plugins/Fab/Source/Fab/Private/FabBrowserApi.cpp b/Plugins/Fab/Source/Fab/Private/FabBrowserApi.cpp
new file mode 100644
index 0000000..2c9a3b3
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabBrowserApi.cpp
@@ -0,0 +1,315 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "FabBrowserApi.h"
+#include "FabAuthentication.h"
+#include "FabBrowser.h"
+#include "FabSettings.h"
+#include "FabLog.h"
+#include "HAL/PlatformApplicationMisc.h"
+#include "Interfaces/IPluginManager.h"
+#include "Misc/EngineVersion.h"
+#include "Workflows/GenericImportWorkflow.h"
+#include "Workflows/PackImportWorkflow.h"
+#include "Workflows/QuixelImportWorkflow.h"
+#include "Workflows/GenericDragDropWorkflow.h"
+#include "Workflows/QuixelDragDropWorkflow.h"
+
+void UFabBrowserApi::CompleteWorkflow(const FString& Id)
+{
+ TSharedPtr CompletedWorkflow;
+ for (const TSharedPtr& ActiveWorkflow : this->ActiveWorkflows)
+ {
+ if (ActiveWorkflow->AssetId == Id)
+ {
+ CompletedWorkflow = ActiveWorkflow;
+ break;
+ }
+ }
+
+ this->ActiveWorkflows.Remove(CompletedWorkflow);
+}
+
+void UFabBrowserApi::AddToProject(const FString& DownloadUrl, const FFabAssetMetadata& AssetMetadata)
+{
+ // Check if the listing is already being downloaded
+ for (const TSharedPtr& ActiveWorkflow : this->ActiveWorkflows)
+ {
+ if (ActiveWorkflow->AssetId == AssetMetadata.AssetId)
+ {
+ FAB_LOG("The listing with Id %s is already being processed.", *AssetMetadata.AssetId);
+ return;
+ }
+ }
+
+ FAB_LOG("Asset Type = %s", *AssetMetadata.AssetType);
+ FAB_LOG("Is Quixel = %d", AssetMetadata.IsQuixel);
+
+ if (AssetMetadata.AssetType == "unreal-engine")
+ {
+ const FString BaseUrls = FString::Join(AssetMetadata.DistributionPointBaseUrls, TEXT(","));
+ FAB_LOG("Base Url %s", *BaseUrls);
+
+ const TSharedPtr PackImportWorkflow = MakeShared(AssetMetadata.AssetId, AssetMetadata.AssetName, DownloadUrl, BaseUrls);
+ PackImportWorkflow->OnFabWorkflowComplete().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ CompleteWorkflow(AssetId);
+ }
+ );
+ PackImportWorkflow->OnFabWorkflowCancel().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ CompleteWorkflow(AssetId);
+ }
+ );
+ PackImportWorkflow->Execute();
+ this->ActiveWorkflows.Add(PackImportWorkflow);
+ return;
+ }
+
+ if (AssetMetadata.IsQuixel)
+ {
+ const TSharedPtr QuixelImportWorkflow = MakeShared(AssetMetadata.AssetId, AssetMetadata.AssetName, DownloadUrl);
+ QuixelImportWorkflow->Execute();
+ QuixelImportWorkflow->OnFabWorkflowComplete().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ CompleteWorkflow(AssetId);
+ }
+ );
+ QuixelImportWorkflow->OnFabWorkflowCancel().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ CompleteWorkflow(AssetId);
+ }
+ );
+ this->ActiveWorkflows.Add(QuixelImportWorkflow);
+ return;
+ }
+
+ if (AssetMetadata.AssetType == "gltf" || AssetMetadata.AssetType == "glb" || AssetMetadata.AssetType == "fbx")
+ {
+ const TSharedPtr InterchangeImportWorkflow = MakeShared(AssetMetadata.AssetId, AssetMetadata.AssetName, DownloadUrl);
+ InterchangeImportWorkflow->OnFabWorkflowComplete().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ CompleteWorkflow(AssetId);
+ }
+ );
+ InterchangeImportWorkflow->OnFabWorkflowCancel().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ CompleteWorkflow(AssetId);
+ }
+ );
+ InterchangeImportWorkflow->Execute();
+ this->ActiveWorkflows.Add(InterchangeImportWorkflow);
+ return;
+ }
+
+ FAB_LOG_ERROR("Asset type not handled %s", *AssetMetadata.AssetType);
+}
+
+void UFabBrowserApi::DragStart(const FFabAssetMetadata& AssetMetadata)
+{
+ // Check if the listing is already being downloaded
+ for (const TSharedPtr& ActiveWorkflow : this->ActiveWorkflows)
+ {
+ if (ActiveWorkflow->AssetId == AssetMetadata.AssetId)
+ {
+ FAB_LOG("The listing with Id %s is already being processed.", *AssetMetadata.AssetId);
+ return;
+ }
+ }
+
+ FAB_LOG("Listing Type = %s", *AssetMetadata.ListingType);
+ FAB_LOG("Is Quixel = %d", AssetMetadata.IsQuixel);
+ if (AssetMetadata.IsQuixel)
+ {
+ const TSharedPtr QuixelDragDropWorkflow = MakeShared(
+ AssetMetadata.AssetId,
+ AssetMetadata.AssetName,
+ AssetMetadata.ListingType
+ );
+ QuixelDragDropWorkflow->OnFabWorkflowComplete().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ FAB_LOG("Quixel Drag workflow completed!");
+ CompleteWorkflow(AssetId);
+ }
+ );
+ QuixelDragDropWorkflow->OnFabWorkflowCancel().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ FAB_LOG("Quixel Drag workflow cancelled!");
+ CompleteWorkflow(AssetId);
+ }
+ );
+ this->ActiveWorkflows.Add(QuixelDragDropWorkflow);
+ QuixelDragDropWorkflow->Execute();
+ }
+ else
+ {
+ const TSharedPtr DragDropWorkflow = MakeShared(AssetMetadata.AssetId, AssetMetadata.AssetName);
+ DragDropWorkflow->OnFabWorkflowComplete().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ FAB_LOG("Drag workflow completed!");
+ CompleteWorkflow(AssetId);
+ }
+ );
+ DragDropWorkflow->OnFabWorkflowCancel().BindLambda(
+ [this, AssetId = AssetMetadata.AssetId]()
+ {
+ FAB_LOG("Drag workflow cancelled!");
+ CompleteWorkflow(AssetId);
+ }
+ );
+ this->ActiveWorkflows.Add(DragDropWorkflow);
+ DragDropWorkflow->Execute();
+ }
+}
+
+void UFabBrowserApi::OnDragInfoSuccess(const FString& DownloadUrl, const FFabAssetMetadata& AssetMetadata)
+{
+ OnSignedUrlGenerated().Broadcast(DownloadUrl, AssetMetadata);
+}
+
+void UFabBrowserApi::OnDragInfoFailure(const FString& AssetId)
+{
+ FAB_LOG_ERROR("Drag drop failure for asset id %s", *AssetId);
+ // Get the drag workflow
+ FFabAssetMetadata Metadata;
+ Metadata.AssetId = AssetId;
+ OnSignedUrlGenerated().Broadcast("", Metadata);
+}
+
+FDelegateHandle UFabBrowserApi::AddSignedUrlCallback(TFunction Callback)
+{
+ return OnSignedUrlGenerated().AddLambda(Callback);
+}
+
+void UFabBrowserApi::Login()
+{
+ FabAuthentication::LoginUsingAccountPortal();
+}
+
+void UFabBrowserApi::Logout()
+{
+ FabAuthentication::DeletePersistentAuth();
+}
+
+void UFabBrowserApi::OpenPluginSettings()
+{
+ FAB_LOG("Open plugin settings");
+ FFabBrowser::ShowSettings();
+}
+
+FFabFrontendSettings UFabBrowserApi::GetSettings()
+{
+ const UFabSettings* FabSettings = GetDefault();
+
+ FFabFrontendSettings FrontendSettings;
+ if (FabSettings->PreferredDefaultFormat == EFabPreferredFormats::GLTF)
+ {
+ FrontendSettings.PreferredFormat = "gltf";
+ }
+ else if (FabSettings->PreferredDefaultFormat == EFabPreferredFormats::FBX)
+ {
+ FrontendSettings.PreferredFormat = "fbx";
+ }
+ if (FabSettings->PreferredQualityTier == EFabPreferredQualityTier::Low)
+ {
+ FrontendSettings.PreferredQuality = "low";
+ }
+ else if (FabSettings->PreferredQualityTier == EFabPreferredQualityTier::Medium)
+ {
+ FrontendSettings.PreferredQuality = "medium";
+ }
+ else if (FabSettings->PreferredQualityTier == EFabPreferredQualityTier::High)
+ {
+ FrontendSettings.PreferredQuality = "high";
+ }
+ else if (FabSettings->PreferredQualityTier == EFabPreferredQualityTier::Raw)
+ {
+ FrontendSettings.PreferredQuality = "raw";
+ }
+
+ return FrontendSettings;
+}
+
+void UFabBrowserApi::SetPreferredQualityTier(const FString& PreferredQuality)
+{
+ UFabSettings* FabSettings = GetMutableDefault();
+ if (PreferredQuality == "low")
+ {
+ FabSettings->PreferredQualityTier = EFabPreferredQualityTier::Low;
+ }
+ else if (PreferredQuality == "medium")
+ {
+ FabSettings->PreferredQualityTier = EFabPreferredQualityTier::Medium;
+ }
+ else if (PreferredQuality == "high")
+ {
+ FabSettings->PreferredQualityTier = EFabPreferredQualityTier::High;
+ }
+ else if (PreferredQuality == "raw")
+ {
+ FabSettings->PreferredQualityTier = EFabPreferredQualityTier::Raw;
+ }
+
+ FabSettings->SaveConfig();
+}
+
+FFabApiVersion UFabBrowserApi::GetApiVersion()
+{
+ FFabApiVersion ApiVersion;
+ const FEngineVersion EngineVersion = FEngineVersion::Current();
+ const uint16 MajorVersion = EngineVersion.GetMajor();
+ const uint16 MinorVersion = EngineVersion.GetMinor();
+
+ ApiVersion.Ue = FString::FromInt(MajorVersion) + "." + FString::FromInt(MinorVersion);
+ ApiVersion.Api = "1.0.0";
+
+ if (const TSharedPtr Plugin = IPluginManager::Get().FindPlugin("Fab"); Plugin.IsValid())
+ {
+ const FPluginDescriptor& PluginDescriptor = Plugin->GetDescriptor();
+
+ const FString PluginVersion = PluginDescriptor.VersionName;
+ ApiVersion.PluginVersion = PluginVersion;
+ }
+
+ return ApiVersion;
+}
+
+void UFabBrowserApi::OpenUrlInBrowser(const FString& Url)
+{
+ FPlatformProcess::LaunchURL(*Url, nullptr, nullptr);
+}
+
+void UFabBrowserApi::CopyToClipboard(const FString& Content)
+{
+ FPlatformApplicationMisc::ClipboardCopy(*Content);
+}
+
+FString UFabBrowserApi::GetUrl()
+{
+ return FFabBrowser::GetUrl();
+}
+
+FString UFabBrowserApi::GetAuthToken()
+{
+ const UFabSettings* FabSettings = GetDefault();
+ if (!FabSettings->CustomAuthToken.IsEmpty())
+ {
+ FAB_LOG("Returning custom auth token: %s", *FabSettings->CustomAuthToken);
+ return FabSettings->CustomAuthToken;
+ }
+
+ return FabAuthentication::GetAuthToken();
+}
+
+FString UFabBrowserApi::GetRefreshToken()
+{
+ return FabAuthentication::GetRefreshToken();
+}
diff --git a/Plugins/Fab/Source/Fab/Private/FabBrowserApi.h b/Plugins/Fab/Source/Fab/Private/FabBrowserApi.h
new file mode 100644
index 0000000..e6b898d
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabBrowserApi.h
@@ -0,0 +1,127 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+
+#include "FabBrowserApi.generated.h"
+
+class IFabWorkflow;
+
+USTRUCT()
+struct FFabAssetMetadata
+{
+ GENERATED_BODY()
+
+ UPROPERTY()
+ FString AssetId;
+
+ UPROPERTY()
+ FString AssetName;
+
+ UPROPERTY()
+ FString AssetType;
+
+ UPROPERTY()
+ FString ListingType;
+
+ UPROPERTY()
+ bool IsQuixel = false;
+
+ UPROPERTY()
+ FString AssetNamespace;
+
+ UPROPERTY()
+ TArray DistributionPointBaseUrls;
+};
+
+USTRUCT()
+struct FFabApiVersion
+{
+ GENERATED_BODY()
+
+ UPROPERTY()
+ FString Ue;
+
+ UPROPERTY()
+ FString Api;
+
+ UPROPERTY()
+ FString PluginVersion;
+};
+
+USTRUCT()
+struct FFabFrontendSettings
+{
+ GENERATED_BODY()
+
+ UPROPERTY()
+ FString PreferredFormat;
+
+ UPROPERTY()
+ FString PreferredQuality;
+};
+
+UCLASS()
+class UFabBrowserApi : public UObject
+{
+ GENERATED_BODY()
+ DECLARE_MULTICAST_DELEGATE_TwoParams(FOnSignedUrlGenerated, const FString& /*DownloadUrl*/, FFabAssetMetadata /*Metadata*/);
+
+private:
+ FOnSignedUrlGenerated OnSignedUrlGeneratedDelegate;
+ void CompleteWorkflow(const FString& Id);
+
+public:
+ TArray> ActiveWorkflows;
+
+public:
+ UFUNCTION()
+ void AddToProject(const FString& DownloadUrl, const FFabAssetMetadata& AssetMetadata);
+
+ UFUNCTION()
+ void DragStart(const FFabAssetMetadata& AssetMetadata);
+
+ UFUNCTION()
+ void OnDragInfoSuccess(const FString& DownloadUrl, const FFabAssetMetadata& AssetMetadata);
+
+ UFUNCTION()
+ void OnDragInfoFailure(const FString& AssetId);
+
+ UFUNCTION()
+ void Login();
+
+ UFUNCTION()
+ void Logout();
+
+ UFUNCTION()
+ FString GetAuthToken();
+
+ UFUNCTION()
+ FString GetRefreshToken();
+
+ UFUNCTION()
+ void OpenPluginSettings();
+
+ UFUNCTION()
+ FFabFrontendSettings GetSettings();
+
+ UFUNCTION()
+ void SetPreferredQualityTier(const FString& PreferredQuality);
+
+ UFUNCTION()
+ FFabApiVersion GetApiVersion();
+
+ UFUNCTION()
+ void OpenUrlInBrowser(const FString& Url);
+
+ UFUNCTION()
+ void CopyToClipboard(const FString& Content);
+
+ UFUNCTION()
+ FString GetUrl();
+
+ FDelegateHandle AddSignedUrlCallback(TFunction Callback);
+ FOnSignedUrlGenerated& OnSignedUrlGenerated() { return OnSignedUrlGeneratedDelegate; }
+ void RemoveSignedUrlHandle(const FDelegateHandle& Handle) { OnSignedUrlGeneratedDelegate.Remove(Handle); }
+};
diff --git a/Plugins/Fab/Source/Fab/Private/FabConsoleCommands.cpp b/Plugins/Fab/Source/Fab/Private/FabConsoleCommands.cpp
new file mode 100644
index 0000000..9f43e2c
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabConsoleCommands.cpp
@@ -0,0 +1,70 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "FabAuthentication.h"
+#include "FabBrowser.h"
+#include "FabLog.h"
+#include "FabSettings.h"
+
+#include "HAL/IConsoleManager.h"
+#include "Utilities/FabAssetsCache.h"
+
+static FAutoConsoleCommand ConsoleCmd_FabShowSettings(
+ TEXT("Fab.ShowSettings"),
+ TEXT("Display the Fab settings window"),
+ FConsoleCommandDelegate::CreateStatic(&FFabBrowser::ShowSettings)
+);
+
+static FAutoConsoleCommand ConsoleCmd_FabLogout(
+ TEXT("Fab.Logout"),
+ TEXT("Trigger a manual logout for Fab plugin"),
+ FConsoleCommandDelegate::CreateLambda([]()
+ {
+ FabAuthentication::DeletePersistentAuth();
+ })
+);
+
+static FAutoConsoleCommand ConsoleCmd_FabLogin(
+ TEXT("Fab.Login"),
+ TEXT("Trigger a manual login for Fab plugin"),
+ FConsoleCommandDelegate::CreateLambda([]()
+ {
+ FabAuthentication::LoginUsingAccountPortal();
+ })
+);
+
+static FAutoConsoleCommand ConsoleCmd_FabClearCache(
+ TEXT("Fab.ClearCache"),
+ TEXT("Clear download cache for Fab plugin"),
+ FConsoleCommandDelegate::CreateStatic(&FFabAssetsCache::ClearCache)
+);
+
+static FAutoConsoleCommand ConsoleCmd_FabSetEnvironment(
+ TEXT("Fab.SetEnvironment"),
+ TEXT("Set Fab plugin environment"),
+ FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray& Args)
+ {
+ if (Args.IsEmpty())
+ {
+ FAB_LOG("Need to provide a valid environment arg");
+ return;
+ }
+
+ const FString Environment = Args[0];
+ UFabSettings* FabSettings = GetMutableDefault();
+ if (Environment == "prod")
+ {
+ FabSettings->Environment = EFabEnvironment::Prod;
+ }
+ else if (Environment == "gamedev")
+ {
+ FabSettings->Environment = EFabEnvironment::Gamedev;
+ }
+ else if (Environment == "test")
+ {
+ FabSettings->Environment = EFabEnvironment::Test;
+ }
+
+ FabAuthentication::DeletePersistentAuth();
+ FabSettings->SaveConfig();
+ })
+);
diff --git a/Plugins/Fab/Source/Fab/Private/FabDownloader.cpp b/Plugins/Fab/Source/Fab/Private/FabDownloader.cpp
new file mode 100644
index 0000000..8f3f9bf
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabDownloader.cpp
@@ -0,0 +1,350 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "FabDownloader.h"
+
+#include "FabLog.h"
+#include "HttpModule.h"
+
+#include "Importers/BuildPatchInstallerLibHelper.h"
+
+#include "Interfaces/IHttpResponse.h"
+#include "Interfaces/IPluginManager.h"
+
+#include "Misc/DateTime.h"
+#include "Misc/Timespan.h"
+#include "Misc/Paths.h"
+#include "Misc/FileHelper.h"
+
+#include "Runtime/Launch/Resources/Version.h"
+
+#include "Utilities/FabAssetsCache.h"
+
+TUniquePtr FFabDownloadRequest::BuildPatchServices;
+FTSTicker::FDelegateHandle FFabDownloadRequest::BpsTickerHandle;
+
+FFabDownloadRequest::FFabDownloadRequest(const FString& InAssetID, const FString& InDownloadURL, const FString& InDownloadLocation, EFabDownloadType InDownloadType)
+ : AssetID(InAssetID)
+ , DownloadURL(InDownloadURL)
+ , DownloadLocation(InDownloadLocation)
+ , DownloadType(InDownloadType)
+{}
+
+bool FFabDownloadRequest::LoadBuildPatchServices()
+{
+ if (BuildPatchServices)
+ {
+ return true;
+ }
+
+ constexpr auto WinLibName = TEXT("BuildPatchInstallerLib.dll");
+ constexpr auto LinuxLibName = TEXT("libBuildPatchInstallerLib.so");
+ constexpr auto MacArmLibName = TEXT("BuildPatchInstallerLib-arm.dylib");
+ constexpr auto Macx86LibName = TEXT("BuildPatchInstallerLib-x86.dylib");
+ constexpr auto DLLName = PLATFORM_WINDOWS ? WinLibName : PLATFORM_LINUX ? LinuxLibName : PLATFORM_MAC_ARM64 ? MacArmLibName : PLATFORM_MAC_X86 ? Macx86LibName : nullptr;
+ if constexpr (DLLName == nullptr)
+ {
+ return false;
+ }
+
+ const FString PluginPath = IPluginManager::Get().FindPlugin(TEXT("Fab"))->GetBaseDir();
+ const FString LibPath = FPaths::ConvertRelativePathToFull(FPaths::Combine(PluginPath, TEXT("ThirdParty"), DLLName));
+
+ BuildPatchServices = BpiLib::FBpiLibHelperFactory::Create(LibPath);
+ if (BuildPatchServices)
+ {
+ BpsTickerHandle = FTSTicker::GetCoreTicker().AddTicker(
+ FTickerDelegate::CreateLambda(
+ [](const float Delta)
+ {
+ BuildPatchServices->Tick(Delta);
+ return true;
+ }
+ )
+ );
+ }
+
+ return BuildPatchServices != nullptr;
+}
+
+void FFabDownloadRequest::ShutdownBpsModule()
+{
+ if (BpsTickerHandle.IsValid())
+ {
+ FTSTicker::GetCoreTicker().RemoveTicker(BpsTickerHandle);
+ }
+ if (BuildPatchServices.IsValid())
+ {
+ BuildPatchServices.Reset();
+ }
+}
+
+void FFabDownloadRequest::ExecuteHTTPRequest()
+{
+ const FString FullFileName = GetFilenameFromURL(DownloadURL);
+ const FString SaveFilename = DownloadLocation / GetFilenameFromURL(DownloadURL);
+
+ FHttpModule& HTTPModule = FHttpModule::Get();
+
+ DownloadRequest = HTTPModule.CreateRequest().ToSharedPtr();
+ DownloadRequest->SetURL(DownloadURL);
+ DownloadRequest->OnHeaderReceived().BindLambda(
+ [this, FullFileName](FHttpRequestPtr Request, const FString& HeaderName, const FString& HeaderValue)
+ {
+ if (HeaderName == "Content-Length")
+ {
+ DownloadStats.TotalBytes = FCString::Atoi(*HeaderValue);
+ const FString CachedAssetId = AssetID / FullFileName;
+ OnDownloadProgressDelegate.Broadcast(this, DownloadStats);
+
+ if (FFabAssetsCache::IsCached(CachedAssetId, DownloadStats.TotalBytes))
+ {
+ DownloadStats.PercentComplete = 100.0f;
+ DownloadStats.DownloadCompletedAt = FDateTime::Now().ToUnixTimestamp();
+ DownloadStats.bIsSuccess = true;
+ DownloadStats.CompletedBytes = DownloadStats.TotalBytes;
+ DownloadStats.DownloadedFiles = {
+ FFabAssetsCache::GetCachedFile(CachedAssetId)
+ };
+
+ Request->CancelRequest();
+ }
+ }
+ }
+ );
+ #if (ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION <=3)
+ DownloadRequest->OnRequestProgress().BindLambda(
+ [this](FHttpRequestPtr Request, uint32 UploadedBytes, uint32 DownloadedBytes)
+ #else
+ DownloadRequest->OnRequestProgress64().BindLambda(
+ [this](FHttpRequestPtr Request, uint64 UploadedBytes, uint64 DownloadedBytes)
+ #endif
+ {
+ DownloadStats.CompletedBytes = static_cast(DownloadedBytes);
+ DownloadStats.PercentComplete = 100.0f * static_cast(DownloadStats.CompletedBytes) / static_cast(DownloadStats.TotalBytes);
+
+ // TODO: Calculate Download Speed
+ OnDownloadProgressDelegate.Broadcast(this, DownloadStats);
+ }
+ );
+ DownloadRequest->OnProcessRequestComplete().BindLambda(
+ [this, SaveFilename](FHttpRequestPtr Request, FHttpResponsePtr Response, const bool bRequestComplete)
+ {
+ if (bRequestComplete)
+ {
+ const TArray& Data = Response->GetContent();
+ DownloadStats.bIsSuccess = FFileHelper::SaveArrayToFile(Data, *SaveFilename);
+ DownloadStats.DownloadCompletedAt = FDateTime::Now().ToUnixTimestamp();
+ if (DownloadStats.bIsSuccess)
+ {
+ DownloadStats.DownloadedFiles = {
+ SaveFilename
+ };
+ }
+ }
+
+ OnDownloadCompleteDelegate.Broadcast(this, DownloadStats);
+ }
+ );
+
+ DownloadStats.DownloadStartedAt = FDateTime::Now().ToUnixTimestamp();
+ DownloadRequest->ProcessRequest();
+}
+
+void FFabDownloadRequest::ExecuteBuildPatchRequest()
+{
+ DownloadStats.DownloadStartedAt = FDateTime::Now().ToUnixTimestamp();
+ if (!LoadBuildPatchServices())
+ {
+ FAB_LOG_ERROR("Failed to load BuildPatchServicesModule");
+ DownloadStats.bIsSuccess = false;
+ OnDownloadCompleteDelegate.Broadcast(this, DownloadStats);
+ return;
+ }
+
+ FString ManifestURL, BaseURL;
+ DownloadURL.Split(",", &ManifestURL, &BaseURL, ESearchCase::CaseSensitive);
+ FHttpModule& HTTPModule = FHttpModule::Get();
+
+ DownloadRequest = HTTPModule.CreateRequest().ToSharedPtr();
+ DownloadRequest->SetURL(ManifestURL);
+ DownloadRequest->OnProcessRequestComplete().BindLambda(
+ [this, BaseURL](FHttpRequestPtr Request, FHttpResponsePtr Response, const bool bRequestComplete)
+ {
+ if (bRequestComplete)
+ {
+ ManifestData = Response->GetContent();
+ OnManifestDownloaded(BaseURL);
+ }
+ else
+ {
+ DownloadStats.bIsSuccess = false;
+ OnDownloadCompleteDelegate.Broadcast(this, DownloadStats);
+ }
+ }
+ );
+
+ DownloadRequest->ProcessRequest();
+}
+
+void FFabDownloadRequest::OnManifestDownloaded(const FString& BaseURL)
+{
+ if (bPendingCancel)
+ {
+ DownloadStats.bIsSuccess = false;
+ OnDownloadCompleteDelegate.Broadcast(this, DownloadStats);
+ return;
+ }
+
+ BuildPatchServices::FBuildInstallerConfiguration BuildInstallerConfiguration({});
+ BuildInstallerConfiguration.InstallDirectory = DownloadLocation;
+ BuildInstallerConfiguration.StagingDirectory = FFabAssetsCache::GetCacheLocation() / AssetID;
+ BuildInstallerConfiguration.InstallMode = BuildPatchServices::EInstallMode::NonDestructiveInstall;
+ BaseURL.ParseIntoArray(BuildInstallerConfiguration.CloudDirectories, TEXT(","));
+
+ auto Manifest = BuildPatchServices->MakeManifestFromData(ManifestData);
+ if (!Manifest.IsValid())
+ {
+ FAB_LOG_ERROR("Invalid Manifest");
+ DownloadStats.bIsSuccess = false;
+ OnDownloadCompleteDelegate.Broadcast(this, DownloadStats);
+ return;
+ }
+
+ DownloadStats.DownloadedFiles = Manifest.GetBuildFileList();
+ if (DownloadStats.DownloadedFiles.ContainsByPredicate(
+ [](const FString& File)
+ {
+ const FString Ext = FPaths::GetExtension(File);
+ return Ext == "uproject" || Ext == "uplugin";
+ }
+ ))
+ {
+ FAB_LOG_ERROR("Invalid pack - either contains a uproject or a uplugin file");
+ DownloadStats.bIsSuccess = false;
+ DownloadStats.DownloadedFiles.Empty();
+ OnDownloadCompleteDelegate.Broadcast(this, DownloadStats);
+ return;
+ }
+
+ auto OnComplete = FBuildPatchInstallerDelegate::CreateLambda(
+ [this](const IBuildInstallerRef& Installer)
+ {
+ DownloadStats.DownloadCompletedAt = FDateTime::Now().ToUnixTimestamp();
+ DownloadStats.PercentComplete = 100.0f;
+ DownloadStats.bIsSuccess = true;
+ if (BpsProgressTickerHandle.IsValid())
+ {
+ FTSTicker::GetCoreTicker().RemoveTicker(BpsProgressTickerHandle);
+ }
+ OnDownloadCompleteDelegate.Broadcast(this, DownloadStats);
+ }
+ );
+ BpsInstaller = BuildPatchServices->CreateInstaller(Manifest, MoveTemp(BuildInstallerConfiguration), MoveTemp(OnComplete)).ToSharedPtr();
+ BpsInstaller->StartInstallation();
+
+ auto OnProgress = FTickerDelegate::CreateLambda(
+ [this, Installer = BpsInstaller.ToSharedRef()](const float Delta)
+ {
+ const int64 TotalDownloaded = BuildPatchServices->GetTotalDownloaded(Installer);
+ // const float UpdateProgress = BuildPatchServices->GetUpdateProgress(Installer);
+ const int64 TotalDownloadRequired = BuildPatchServices->GetTotalDownloadRequired(Installer);
+ DownloadStats.CompletedBytes = TotalDownloaded;
+ DownloadStats.TotalBytes = TotalDownloadRequired;
+ DownloadStats.PercentComplete = (static_cast(DownloadStats.CompletedBytes) / DownloadStats.TotalBytes) * 100.0f;
+
+
+ OnDownloadProgressDelegate.Broadcast(this, DownloadStats);
+ return true;
+ }
+ );
+ BpsProgressTickerHandle = FTSTicker::GetCoreTicker().AddTicker(MoveTemp(OnProgress), 1.0f);
+}
+
+FString FFabDownloadRequest::GetFilenameFromURL(const FString& URL)
+{
+ int32 SlashIndex = -1;
+ if (!URL.FindLastChar('/', SlashIndex) || SlashIndex == -1)
+ {
+ SlashIndex = 0; // Local file without scheme maybe?
+ }
+
+ int32 QuestionIndex = -1;
+ if (!URL.FindChar('?', QuestionIndex) || QuestionIndex == -1)
+ {
+ QuestionIndex = URL.Len();
+ }
+ return URL.Mid(SlashIndex + 1, QuestionIndex - 1 - SlashIndex);
+}
+
+void FFabDownloadRequest::StartDownload()
+{
+ if (bPendingCancel)
+ {
+ DownloadStats.bIsSuccess = false;
+ OnDownloadCompleteDelegate.Broadcast(this, DownloadStats);
+ return;
+ }
+ if (DownloadType == EFabDownloadType::HTTP)
+ {
+ ExecuteHTTPRequest();
+ }
+ else if (DownloadType == EFabDownloadType::BuildPatchRequest)
+ {
+ ExecuteBuildPatchRequest();
+ }
+}
+
+void FFabDownloadRequest::ExecuteRequest()
+{
+ FFabDownloadQueue::AddDownloadToQueue(this);
+}
+
+void FFabDownloadRequest::Cancel()
+{
+ bool bWasCancelled = false;
+ if (DownloadRequest.IsValid() && DownloadRequest->GetStatus() == EHttpRequestStatus::Processing)
+ {
+ DownloadStats.bIsSuccess = false;
+ DownloadStats.DownloadedFiles.Empty();
+ DownloadRequest->CancelRequest();
+ bWasCancelled = true;
+ }
+ if (BpsInstaller.IsValid() && !BpsInstaller->IsComplete() && !BpsInstaller->IsCanceled())
+ {
+ DownloadStats.bIsSuccess = false;
+ DownloadStats.DownloadedFiles.Empty();
+ BuildPatchServices->CancelInstall(BpsInstaller.ToSharedRef());
+ bWasCancelled = true;
+ }
+ if (!bWasCancelled)
+ {
+ bPendingCancel = true;
+ }
+}
+
+int32 FFabDownloadQueue::DownloadQueueLimit = 2;
+TSet FFabDownloadQueue::DownloadQueue;
+TQueue FFabDownloadQueue::WaitingQueue;
+
+void FFabDownloadQueue::AddDownloadToQueue(FFabDownloadRequest* DownloadRequest)
+{
+ if (DownloadQueue.Num() >= DownloadQueueLimit)
+ {
+ WaitingQueue.Enqueue(DownloadRequest);
+ }
+ else
+ {
+ DownloadQueue.Add(DownloadRequest);
+ DownloadRequest->OnDownloadComplete().AddLambda(
+ [DownloadRequest](const FFabDownloadRequest* Request, const FFabDownloadStats& Stats)
+ {
+ DownloadQueue.Remove(DownloadRequest);
+ if (FFabDownloadRequest* NewRequest = nullptr; WaitingQueue.Dequeue(NewRequest))
+ {
+ AddDownloadToQueue(NewRequest);
+ }
+ }
+ );
+ DownloadRequest->StartDownload();
+ }
+}
diff --git a/Plugins/Fab/Source/Fab/Private/FabDownloader.h b/Plugins/Fab/Source/Fab/Private/FabDownloader.h
new file mode 100644
index 0000000..ad3c081
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabDownloader.h
@@ -0,0 +1,107 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Containers/Queue.h"
+
+#include "Containers/Ticker.h"
+
+#include "Importers/BuildPatchInstallerLibHelper.h"
+
+#include "Interfaces/IHttpRequest.h"
+
+enum class EFabDownloadType
+{
+ // Download asset using HTTP
+ HTTP,
+
+ // Download asset using BuildPatchServices (for Unreal Engine Marketplace Assets)
+ BuildPatchRequest
+};
+
+struct FFabDownloadStats
+{
+ float PercentComplete = 0.0f;
+
+ uint64 CompletedBytes = 0;
+ uint64 TotalBytes = 0;
+
+ uint64 DownloadStartedAt = 0;
+ uint64 DownloadCompletedAt = 0;
+
+ float DownloadSpeed = 0.0f;
+
+ bool bIsSuccess = false;
+
+ TArray DownloadedFiles;
+};
+
+class FFabDownloadRequest
+{
+ DECLARE_MULTICAST_DELEGATE_TwoParams(FOnDownloadProgress, const FFabDownloadRequest*, const FFabDownloadStats&);
+ DECLARE_MULTICAST_DELEGATE_TwoParams(FOnDownloadComplete, const FFabDownloadRequest*, const FFabDownloadStats&);
+
+private:
+ FString GetFilenameFromURL(const FString& URL);
+
+ void ExecuteHTTPRequest();
+
+ static bool LoadBuildPatchServices();
+ void ExecuteBuildPatchRequest();
+ void OnManifestDownloaded(const FString& BaseURL);
+
+ void StartDownload();
+
+public:
+ FFabDownloadRequest(const FString& AssetID, const FString& InDownloadURL, const FString& InDownloadLocation, EFabDownloadType InDownloadType = EFabDownloadType::HTTP);
+
+ ~FFabDownloadRequest() = default;
+
+ void ExecuteRequest();
+ void Cancel();
+
+ static void ShutdownBpsModule();
+
+ const FFabDownloadStats& GetDownloadStats() { return DownloadStats; }
+
+ FOnDownloadProgress& OnDownloadProgress() { return OnDownloadProgressDelegate; }
+ FOnDownloadComplete& OnDownloadComplete() { return OnDownloadCompleteDelegate; }
+
+private:
+ FString AssetID;
+
+ FString DownloadURL;
+
+ FString DownloadLocation;
+
+ EFabDownloadType DownloadType;
+
+ FFabDownloadStats DownloadStats;
+
+ FOnDownloadProgress OnDownloadProgressDelegate;
+ FOnDownloadComplete OnDownloadCompleteDelegate;
+
+ FHttpRequestPtr DownloadRequest;
+ IBuildInstallerPtr BpsInstaller;
+
+ bool bPendingCancel = false;
+
+ TArray ManifestData;
+ FTSTicker::FDelegateHandle BpsProgressTickerHandle;
+ static FTSTicker::FDelegateHandle BpsTickerHandle;
+ static TUniquePtr BuildPatchServices;
+
+ friend class FFabDownloadQueue;
+};
+
+class FFabDownloadQueue
+{
+private:
+ static int32 DownloadQueueLimit;
+ static TSet DownloadQueue;
+ static TQueue WaitingQueue;
+
+public:
+ static void AddDownloadToQueue(FFabDownloadRequest* DownloadRequest);
+};
diff --git a/Plugins/Fab/Source/Fab/Private/FabLog.h b/Plugins/Fab/Source/Fab/Private/FabLog.h
new file mode 100644
index 0000000..8cd878b
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabLog.h
@@ -0,0 +1,11 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "Logging/LogMacros.h"
+
+DECLARE_LOG_CATEGORY_EXTERN(LogFab, Log, All);
+
+#define FAB_LOG(Format, ...) UE_LOG(LogFab, Display, TEXT(Format), ##__VA_ARGS__)
+#define FAB_LOG_ERROR(Format, ...) UE_LOG(LogFab, Error, TEXT(Format), ##__VA_ARGS__)
+#define FAB_LOG_VERBOSE(Format, ...) UE_LOG(LogFab, Display, TEXT(Format), ##__VA_ARGS__)
diff --git a/Plugins/Fab/Source/Fab/Private/FabModule.cpp b/Plugins/Fab/Source/Fab/Private/FabModule.cpp
new file mode 100644
index 0000000..1b50952
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabModule.cpp
@@ -0,0 +1,90 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "FabModule.h"
+
+#include "Engine.h"
+
+#include "FabAuthentication.h"
+#include "FabBrowser.h"
+#include "FabDownloader.h"
+#include "FabLog.h"
+#include "FabSettingsCustomization.h"
+#include "InterchangeManager.h"
+
+#include "PropertyEditorModule.h"
+#include "Engine/RendererSettings.h"
+#include "Modules/ModuleManager.h"
+
+#include "Pipelines/Factories/InterchangeInstancedFoliageTypeFactory.h"
+
+#include "Runtime/Launch/Resources/Version.h"
+
+#if (ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION <=3)
+
+#include "Settings/EditorExperimentalSettings.h"
+
+#endif
+
+DEFINE_LOG_CATEGORY(LogFab)
+
+class FFabModule : public IFabModule
+{
+public:
+ virtual void StartupModule() override
+ {
+ if (GIsEditor)
+ {
+ URendererSettings* RendererSettings = GetMutableDefault();
+ RendererSettings->bEnableVirtualTextureOpacityMask = true;
+ RendererSettings->PostEditChange();
+
+#if (ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION <=3)
+ {
+ UEditorExperimentalSettings* EditorSettings = GetMutableDefault();
+ EditorSettings->bEnableAsyncTextureCompilation = false;
+ EditorSettings->PostEditChange();
+ }
+#endif
+ }
+
+ if (GIsEditor && !IsRunningCommandlet())
+ {
+ FFabBrowser::Init();
+ FabAuthentication::Init();
+
+ FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor");
+ PropertyModule.RegisterCustomClassLayout("FabSettings", FOnGetDetailCustomizationInstance::CreateStatic(&FFabSettingsCustomization::MakeInstance));
+
+ auto RegisterItems = []()
+ {
+ UInterchangeManager::GetInterchangeManager().RegisterFactory(UInterchangeInstancedFoliageTypeFactory::StaticClass());
+ };
+
+ if (GEngine)
+ {
+ RegisterItems();
+ }
+ else
+ {
+ FCoreDelegates::OnPostEngineInit.AddLambda(RegisterItems);
+ }
+ }
+ }
+
+ virtual void ShutdownModule() override
+ {
+ if (GIsEditor && !IsRunningCommandlet())
+ {
+ if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
+ {
+ FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor");
+ PropertyModule.UnregisterCustomClassLayout("FabSettings");
+ }
+ FabAuthentication::Shutdown();
+ FFabBrowser::Shutdown();
+ FFabDownloadRequest::ShutdownBpsModule();
+ }
+ }
+};
+
+IMPLEMENT_MODULE(FFabModule, Fab);
diff --git a/Plugins/Fab/Source/Fab/Private/FabSettings.cpp b/Plugins/Fab/Source/Fab/Private/FabSettings.cpp
new file mode 100644
index 0000000..4f7b530
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabSettings.cpp
@@ -0,0 +1,56 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "FabSettings.h"
+
+#include "FabAuthentication.h"
+#include "FabBrowser.h"
+#include "Misc/Paths.h"
+#include "UObject/UnrealType.h"
+
+UFabSettings::UFabSettings()
+{
+ #if WITH_EDITOR
+ for (TFieldIterator It(GetClass()); It; ++It)
+ {
+ FProperty* Property = *It;
+ if (Property && Property->GetMetaData(TEXT("DevOnly")).ToBool())
+ {
+ Property->SetMetaData(TEXT("Category"), TEXT("HiddenProperties"));
+ }
+ }
+ #endif
+}
+
+void UFabSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
+{
+ Super::PostEditChangeProperty(PropertyChangedEvent);
+ if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
+ {
+ if (PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(UFabSettings, CacheDirectoryPath))
+ {
+ if (!FPaths::DirectoryExists(CacheDirectoryPath.Path) || FPaths::IsRelative(CacheDirectoryPath.Path))
+ {
+ CacheDirectoryPath = FDirectoryPath { FPlatformProcess::UserTempDir() / FString("FabLibrary") };
+ }
+ }
+
+ SaveConfig();
+
+ if (PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(UFabSettings, Environment))
+ {
+ FabAuthentication::DeletePersistentAuth();
+ FabAuthentication::Init();
+ if (Environment != EFabEnvironment::CustomUrl)
+ {
+ FFabBrowser::OpenURL();
+ }
+ }
+ else if (PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(UFabSettings, CustomUrl))
+ {
+ if (Environment == EFabEnvironment::CustomUrl)
+ {
+ FFabBrowser::OpenURL();
+ }
+ }
+ }
+}
diff --git a/Plugins/Fab/Source/Fab/Private/FabSettings.h b/Plugins/Fab/Source/Fab/Private/FabSettings.h
new file mode 100644
index 0000000..dc4c31f
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabSettings.h
@@ -0,0 +1,82 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "HAL/PlatformProcess.h"
+#include "Misc/Paths.h"
+#include "UObject/SoftObjectPath.h"
+
+#include "FabSettings.generated.h"
+
+UENUM()
+enum class EFabEnvironment : uint8
+{
+ Prod UMETA(DisplayName = "Prod"),
+ Gamedev UMETA(DisplayName = "Gamedev"),
+ Test UMETA(DisplayName = "Test"),
+ CustomUrl UMETA(DisplayName = "Custom URL"),
+};
+
+UENUM()
+enum class EFabPreferredFormats : uint8
+{
+ GLTF UMETA(DisplayName = "gltf / glb"),
+ FBX UMETA(DisplayName = "fbx"),
+};
+
+UENUM()
+enum class EFabPreferredQualityTier : uint8
+{
+ Low UMETA(DisplayName = "low"),
+ Medium UMETA(DisplayName = "medium"),
+ High UMETA(DisplayName = "high"),
+ Raw UMETA(DisplayName = "raw")
+};
+
+UCLASS(config=EditorPerProjectUserSettings, hideCategories=HiddenProperties)
+class UFabSettings : public UObject
+{
+ GENERATED_BODY()
+
+public:
+ UFabSettings();
+
+ /** Frontend used by the Fab plugin (reopen the tab to see the change) */
+ UPROPERTY(config, EditAnywhere, Category=Frontend, meta=(DevOnly=true))
+ EFabEnvironment Environment = EFabEnvironment::Prod;
+
+ /** URL used when the [Fab (custom)] frontend is selected */
+ UPROPERTY(config, EditAnywhere, Category=Frontend, meta=(DevOnly=true))
+ FString CustomUrl;
+
+ /** Custom auth token used when it's non empty */
+ UPROPERTY(config, EditAnywhere, Category=Frontend, meta=(DevOnly=true))
+ FString CustomAuthToken;
+
+ /** Enable chrome debug options - default is false */
+ UPROPERTY(config, EditAnywhere, Category = General)
+ bool bEnableDebugOptions = false;
+
+ /** Path to the local library */
+ UPROPERTY(config, EditAnywhere, Category = General)
+ FDirectoryPath CacheDirectoryPath { FPlatformProcess::UserTempDir() / FString("FabLibrary") };
+
+ /** Cache directory */
+ UPROPERTY(config, VisibleAnywhere, Category = General)
+ FString CacheDirectorySize;
+
+ /** Preferred default format */
+ /* The preferred format will always be selected, if not available, the best available format for the product will be chosen. */
+ UPROPERTY(config, VisibleAnywhere, Category = ProductFormats, meta=(DevOnly=true))
+ FString ProductFormatsSectionSubText = "";
+
+ /** Preferred default format */
+ UPROPERTY(config, EditAnywhere, Category = ProductFormats, meta=(DevOnly=true))
+ EFabPreferredFormats PreferredDefaultFormat = EFabPreferredFormats::GLTF;
+
+ /** Preferred default quality for MS assets */
+ UPROPERTY(config, EditAnywhere, Category = Megascans)
+ EFabPreferredQualityTier PreferredQualityTier = EFabPreferredQualityTier::Medium;
+
+ virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
+};
diff --git a/Plugins/Fab/Source/Fab/Private/FabSettingsCustomization.cpp b/Plugins/Fab/Source/Fab/Private/FabSettingsCustomization.cpp
new file mode 100644
index 0000000..fdce61b
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabSettingsCustomization.cpp
@@ -0,0 +1,101 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "FabSettingsCustomization.h"
+
+#include "DetailCategoryBuilder.h"
+#include "DetailLayoutBuilder.h"
+#include "FabSettings.h"
+#include "DetailWidgetRow.h"
+
+#include "Widgets/Input/SButton.h"
+#include "Widgets/Text/STextBlock.h"
+#include "Widgets/Input/SEditableTextBox.h"
+#include "Widgets/Layout/SBox.h"
+
+#include "PropertyEditing.h"
+
+#include "Utilities/FabAssetsCache.h"
+
+TSharedRef FFabSettingsCustomization::MakeInstance()
+{
+ return MakeShareable(new FFabSettingsCustomization);
+}
+
+void FFabSettingsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
+{
+ IDetailCategoryBuilder& GeneralCategory = DetailBuilder.EditCategory("General");
+ IDetailCategoryBuilder& FormatsCategory = DetailBuilder.EditCategory("ProductFormats");
+
+ // General section modifications
+
+ DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UFabSettings, CacheDirectorySize));
+ TArray> GeneralProperties;
+ GeneralCategory.GetDefaultProperties(GeneralProperties);
+ for (const TSharedRef& PropertyHandle : GeneralProperties)
+ {
+ // Skip the already customized NonEditableString property
+ if (PropertyHandle->GetProperty()->GetFName() == GET_MEMBER_NAME_CHECKED(UFabSettings, CacheDirectorySize))
+ {
+ continue;
+ }
+
+ // Add the property
+ GeneralCategory.AddProperty(PropertyHandle);
+ }
+
+ const TSharedRef CacheSizeStringHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UFabSettings, CacheDirectorySize));
+ GeneralCategory.AddCustomRow(CacheSizeStringHandle->GetPropertyDisplayName())
+ .NameContent()
+ [
+ CacheSizeStringHandle->CreatePropertyNameWidget()
+ ]
+ .ValueContent()
+ [
+ SNew(SBox)
+ .MinDesiredWidth(1400.f)
+ .HAlign(HAlign_Fill)
+ [
+ SNew(SOverlay)
+ .FlowDirectionPreference(EFlowDirectionPreference::LeftToRight)
+
+ + SOverlay::Slot()
+ .HAlign(HAlign_Left)
+ [
+ SNew(SEditableTextBox)
+ .Text_Static(&FFabAssetsCache::GetCacheSizeString)
+ .IsReadOnly(true)
+ ]
+
+ + SOverlay::Slot()
+ .HAlign(HAlign_Right)
+ [
+ SNew(SButton).Text(FText::FromString("Clean Directory"))
+ .OnClicked(FOnClicked::CreateRaw(this, &FFabSettingsCustomization::OnButtonClick))
+ ]
+ ]
+ ];
+
+ // Product format section modifications
+
+ // DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UFabSettings, ProductFormatsSectionSubText));
+ // const TSharedRef ProductFormatSubTextHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UFabSettings, ProductFormatsSectionSubText));
+ // FormatsCategory.AddCustomRow(ProductFormatSubTextHandle->GetPropertyDisplayName())
+ // .WholeRowContent()
+ // [
+ // SNew(SHorizontalBox)
+ // + SHorizontalBox::Slot()
+ // .HAlign(HAlign_Fill)
+ // .FillWidth(1.0f)
+ // [
+ // SNew(STextBlock)
+ // .Text(FText::FromString("The preferred format will always be selected, if not available, the best available format for the product will be chosen."))
+ // ]
+ // ]
+ // .ValueContent();
+}
+
+FReply FFabSettingsCustomization::OnButtonClick()
+{
+ FFabAssetsCache::ClearCache();
+ return FReply::Handled();
+}
diff --git a/Plugins/Fab/Source/Fab/Private/FabSettingsCustomization.h b/Plugins/Fab/Source/Fab/Private/FabSettingsCustomization.h
new file mode 100644
index 0000000..a7a4462
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabSettingsCustomization.h
@@ -0,0 +1,18 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Input/Reply.h"
+#include "IDetailCustomization.h"
+
+class FFabSettingsCustomization : public IDetailCustomization
+{
+public:
+ static TSharedRef MakeInstance();
+
+ virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
+
+private:
+ FReply OnButtonClick();
+};
diff --git a/Plugins/Fab/Source/Fab/Private/FabSettingsWindow.h b/Plugins/Fab/Source/Fab/Private/FabSettingsWindow.h
new file mode 100644
index 0000000..e15a2cb
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/FabSettingsWindow.h
@@ -0,0 +1,48 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "FabSettings.h"
+#include "DetailCategoryBuilder.h"
+#include "Widgets/Layout/SBox.h"
+#include "Modules/ModuleManager.h"
+#include "PropertyEditorModule.h"
+
+TObjectPtr FabPluginSettings = nullptr;
+
+struct SFabSettingsWindow : public SCompoundWidget
+{
+ SLATE_BEGIN_ARGS(SFabSettingsWindow)
+ {
+ }
+
+ SLATE_ARGUMENT(TSharedPtr, WidgetWindow)
+ SLATE_END_ARGS()
+
+ void Construct(const FArguments& InArgs)
+ {
+ Window = InArgs._WidgetWindow;
+
+ TSharedPtr DetailsViewBox;
+ ChildSlot[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(2)[SAssignNew(DetailsViewBox, SBox).MaxDesiredHeight(450.0f).MinDesiredWidth(550.0f)]];
+
+ FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor");
+ FDetailsViewArgs DetailsViewArgs;
+ DetailsViewArgs.bAllowSearch = false;
+ DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
+ TSharedRef DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
+ DetailsViewBox->SetContent(DetailsView);
+
+ if (FabPluginSettings == nullptr)
+ {
+ FabPluginSettings = GetMutableDefault();
+ }
+
+ DetailsView->SetObject(FabPluginSettings, true);
+ }
+
+ virtual bool SupportsKeyboardFocus() const override { return true; }
+
+ TWeakPtr Window;
+};
diff --git a/Plugins/Fab/Source/Fab/Private/Importers/ActorSpawner.cpp b/Plugins/Fab/Source/Fab/Private/Importers/ActorSpawner.cpp
new file mode 100644
index 0000000..146cefb
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Importers/ActorSpawner.cpp
@@ -0,0 +1,170 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "ActorSpawner.h"
+
+#include "MaterialTypes.h"
+#include "StaticMeshResources.h"
+
+#include "Animation/AnimBlueprint.h"
+#include "Animation/AnimBlueprintGeneratedClass.h"
+#include "Animation/SkeletalMeshActor.h"
+
+#include "Components/DecalComponent.h"
+#include "Components/SkeletalMeshComponent.h"
+#include "Components/StaticMeshComponent.h"
+
+#include "Engine/DecalActor.h"
+#include "Engine/SkeletalMesh.h"
+#include "Engine/StaticMesh.h"
+#include "Engine/StaticMeshActor.h"
+
+#include "Materials/Material.h"
+#include "Materials/MaterialInstance.h"
+#include "Materials/MaterialInstanceConstant.h"
+
+UFabPlaceholderSpawner::UFabPlaceholderSpawner(const FObjectInitializer& Initializer)
+ : Super(Initializer)
+{
+ DisplayName = FText::FromString("Fab Placeholder Factory");
+ NewActorClass = AActor::StaticClass();
+ bUseSurfaceOrientation = true;
+}
+
+UFabStaticMeshPlaceholderSpawner::UFabStaticMeshPlaceholderSpawner(const FObjectInitializer& Initializer)
+ : Super(Initializer)
+{
+ DisplayName = FText::FromString("Fab Static Mesh Placeholder Factory");
+ NewActorClass = AStaticMeshActor::StaticClass();
+ bUseSurfaceOrientation = true;
+}
+
+bool UFabStaticMeshPlaceholderSpawner::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg)
+{
+ if (!AssetData.IsValid() || !AssetData.IsInstanceOf(UStaticMesh::StaticClass()))
+ {
+ OutErrorMsg = NSLOCTEXT("CanCreateActor", "InvalidAsset", "A Static Mesh should be supplied.");
+ return false;
+ }
+ return true;
+}
+
+void UFabStaticMeshPlaceholderSpawner::PostSpawnActor(UObject* Asset, AActor* NewActor)
+{
+ Super::PostSpawnActor(Asset, NewActor);
+
+ const AStaticMeshActor* StaticMeshActor = Cast(NewActor);
+ UStaticMeshComponent* StaticMeshComponent = StaticMeshActor->GetStaticMeshComponent();
+ UStaticMesh* StaticMesh = Cast(Asset);
+
+ StaticMeshComponent->UnregisterComponent();
+ StaticMeshComponent->SetStaticMesh(StaticMesh);
+ if (StaticMesh->GetRenderData())
+ StaticMeshComponent->StaticMeshDerivedDataKey = StaticMesh->GetRenderData()->DerivedDataKey;
+ StaticMeshComponent->RegisterComponent();
+ this->OnActorSpawn().ExecuteIfBound(NewActor);
+}
+
+UObject* UFabStaticMeshPlaceholderSpawner::GetAssetFromActorInstance(AActor* Instance)
+{
+ check(Instance->IsA(NewActorClass));
+ const AStaticMeshActor* StaticMeshActor = CastChecked(Instance);
+ check(StaticMeshActor->GetStaticMeshComponent());
+ return StaticMeshActor->GetStaticMeshComponent()->GetStaticMesh();
+}
+
+UFabSkeletalMeshPlaceholderSpawner::UFabSkeletalMeshPlaceholderSpawner(const FObjectInitializer& Initializer)
+ : Super(Initializer)
+{
+ DisplayName = FText::FromString("Fab Skeletal Mesh Placeholder Factory");
+ NewActorClass = ASkeletalMeshActor::StaticClass();
+ bUseSurfaceOrientation = true;
+}
+
+bool UFabSkeletalMeshPlaceholderSpawner::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg)
+{
+ if (!AssetData.IsValid() || !AssetData.IsInstanceOf(USkeletalMesh::StaticClass()))
+ {
+ OutErrorMsg = NSLOCTEXT("CanCreateActor", "InvalidAsset", "A Skeletal Mesh should be supplied.");
+ return false;
+ }
+ return true;
+}
+
+void UFabSkeletalMeshPlaceholderSpawner::PostSpawnActor(UObject* Asset, AActor* NewActor)
+{
+ Super::PostSpawnActor(Asset, NewActor);
+
+ ASkeletalMeshActor* SkeletalMeshActor = Cast(NewActor);
+ USkeletalMeshComponent* SkeletalMeshComponent = SkeletalMeshActor->GetSkeletalMeshComponent();
+ USkeletalMesh* SkeletalMesh = Cast(Asset);
+
+ SkeletalMeshComponent->UnregisterComponent();
+ SkeletalMeshComponent->SetSkeletalMesh(SkeletalMesh);
+ if (SkeletalMeshActor->GetWorld()->IsGameWorld())
+ SkeletalMeshActor->ReplicatedMesh = SkeletalMesh;
+ SkeletalMeshComponent->RegisterComponent();
+
+ this->OnActorSpawn().ExecuteIfBound(NewActor);
+}
+
+UObject* UFabSkeletalMeshPlaceholderSpawner::GetAssetFromActorInstance(AActor* Instance)
+{
+ check(Instance->IsA(NewActorClass));
+ const ASkeletalMeshActor* SkeletalMeshActor = CastChecked(Instance);
+ check(SkeletalMeshActor->GetSkeletalMeshComponent());
+ return SkeletalMeshActor->GetSkeletalMeshComponent()->GetSkeletalMeshAsset();
+}
+
+UFabDecalPlaceholderSpawner::UFabDecalPlaceholderSpawner(const FObjectInitializer& Initializer)
+ : Super(Initializer)
+{
+ DisplayName = FText::FromString("Fab Decal Placeholder Factory");
+ NewActorClass = ADecalActor::StaticClass();
+ bUseSurfaceOrientation = true;
+}
+
+bool UFabDecalPlaceholderSpawner::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg)
+{
+ if (!AssetData.IsValid() || !AssetData.IsInstanceOf(UMaterialInstanceConstant::StaticClass()))
+ {
+ OutErrorMsg = NSLOCTEXT("CanCreateActor", "InvalidAsset", "A Material Instance Constant should be supplied.");
+ return false;
+ }
+ if (FString TagValue; AssetData.GetTagValue(GET_MEMBER_NAME_CHECKED(UMaterialInstanceConstant, Parent), TagValue))
+ {
+ if (!TagValue.Contains("M_MS_Decal", ESearchCase::CaseSensitive, ESearchDir::FromEnd))
+ {
+ OutErrorMsg = NSLOCTEXT("CanCreateActor", "InvalidAsset", "A Fab Deferred Decal Material Instance Constant should be supplied.");
+ return false;
+ }
+ }
+ return true;
+}
+
+void UFabDecalPlaceholderSpawner::PostSpawnActor(UObject* Asset, AActor* NewActor)
+{
+ Super::PostSpawnActor(Asset, NewActor);
+ if (!Asset->IsA())
+ {
+ return;
+ }
+
+ if (const ADecalActor* DecalActor = Cast(NewActor))
+ {
+ UDecalComponent* DecalComponent = DecalActor->GetDecal();
+ UMaterialInstanceConstant* Decal = Cast(Asset);
+
+ DecalComponent->UnregisterComponent();
+ DecalComponent->SetDecalMaterial(Decal);
+ DecalComponent->RegisterComponent();
+ this->OnActorSpawn().ExecuteIfBound(NewActor);
+ }
+}
+
+UObject* UFabDecalPlaceholderSpawner::GetAssetFromActorInstance(AActor* Instance)
+{
+ check(Instance->IsA(NewActorClass));
+ const ADecalActor* DecalActor = CastChecked(Instance);
+ check(DecalActor->GetDecal());
+ return DecalActor->GetDecal()->GetDecalMaterial();
+}
diff --git a/Plugins/Fab/Source/Fab/Private/Importers/ActorSpawner.h b/Plugins/Fab/Source/Fab/Private/Importers/ActorSpawner.h
new file mode 100644
index 0000000..3266957
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Importers/ActorSpawner.h
@@ -0,0 +1,54 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+
+#include "ActorFactories/ActorFactory.h"
+
+#include "AssetRegistry/AssetData.h"
+
+#include "ActorSpawner.generated.h"
+
+UCLASS()
+class UFabPlaceholderSpawner : public UActorFactory
+{
+ GENERATED_UCLASS_BODY()
+
+public:
+ DECLARE_DELEGATE_OneParam(FOnActorSpawn, AActor*);
+ FOnActorSpawn& OnActorSpawn() { return this->OnActorSpawnDelegate; }
+
+protected:
+ FOnActorSpawn OnActorSpawnDelegate;
+};
+
+UCLASS()
+class UFabStaticMeshPlaceholderSpawner : public UFabPlaceholderSpawner
+{
+ GENERATED_UCLASS_BODY()
+
+ virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override;
+ virtual void PostSpawnActor(UObject* Asset, AActor* NewActor) override;
+ virtual UObject* GetAssetFromActorInstance(AActor* Instance) override;
+};
+
+UCLASS()
+class UFabSkeletalMeshPlaceholderSpawner : public UFabPlaceholderSpawner
+{
+ GENERATED_UCLASS_BODY()
+
+ virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override;
+ virtual void PostSpawnActor(UObject* Asset, AActor* NewActor) override;
+ virtual UObject* GetAssetFromActorInstance(AActor* Instance) override;
+};
+
+UCLASS()
+class UFabDecalPlaceholderSpawner : public UFabPlaceholderSpawner
+{
+ GENERATED_UCLASS_BODY()
+
+ virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override;
+ virtual void PostSpawnActor(UObject* Asset, AActor* NewActor) override;
+ virtual UObject* GetAssetFromActorInstance(AActor* Instance) override;
+};
diff --git a/Plugins/Fab/Source/Fab/Private/Importers/BuildPatchInstallerLibHelper.h b/Plugins/Fab/Source/Fab/Private/Importers/BuildPatchInstallerLibHelper.h
new file mode 100644
index 0000000..33111a1
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Importers/BuildPatchInstallerLibHelper.h
@@ -0,0 +1,809 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Interfaces/IBuildInstaller.h"
+#include "Interfaces/IBuildManifest.h"
+#include "Interfaces/IBuildPatchServicesModule.h"
+
+#include "HAL/PlatformProcess.h"
+#include "Misc/CommandLine.h"
+#include "Misc/Paths.h"
+
+#if PLATFORM_LINUX || PLATFORM_MAC
+#include
+
+#ifndef RTLD_DEEPBIND
+#define RTLD_DEEPBIND 0
+#endif
+#endif
+
+#if PLATFORM_WINDOWS == 0 && PLATFORM_LINUX == 0 && PLATFORM_MAC == 0
+#error "not supported platform"
+#endif
+
+namespace BpiLib
+{
+ namespace BpiLibHelpers
+ {
+ class FBpiLibHelper;
+ }
+
+ class FManifestStorage
+ {
+ public:
+ FManifestStorage(const BpiLibHelpers::FBpiLibHelper& InLibRef, const TArray& Data);
+ ~FManifestStorage();
+
+ IBuildManifestPtr operator->() const { return *ManifestPtr; }
+ IBuildManifestPtr* GetManifestPtr() const { return ManifestPtr; }
+
+ bool IsValid() const { return ManifestPtr != nullptr; }
+
+ bool SaveToFile(const FString& Filename) const;
+
+ TArray GetBuildFileList() const;
+ FString GetCustomStringField(const FString& Name) const;
+ FString GetAppName() const;
+
+ void SetCustomField(const FString& FieldName, const FString& Value) const;
+ void SetCustomField(const FString& FieldName, const double& Value) const;
+ void SetCustomField(const FString& FieldName, const int64& Value) const;
+
+ private:
+ const BpiLibHelpers::FBpiLibHelper& LibRef;
+ IBuildManifestPtr* ManifestPtr;
+ };
+
+ class IBpiLib
+ {
+ public:
+ virtual ~IBpiLib() = default;
+ virtual bool IsValid() const = 0;
+ virtual IBuildInstallerRef CreateInstaller(FManifestStorage& ManifestStorage, const BuildPatchServices::FBuildInstallerConfiguration& Configuration, FBuildPatchInstallerDelegate CompleteDelegate) const = 0;
+ virtual void CancelInstall(const IBuildInstallerRef& Installer) const = 0;
+ virtual FManifestStorage MakeManifestFromData(const TArray& Data) const = 0;
+ virtual bool Tick(float) = 0;
+ virtual FBuildInstallStats GetBuildStats(const IBuildInstallerRef& Installer) const = 0;
+ virtual int64 GetTotalDownloaded(const IBuildInstallerRef& Installer) const = 0;
+ virtual int64 GetState(const IBuildInstallerRef& Installer) const = 0;
+ virtual float GetUpdateProgress(const IBuildInstallerRef& Installer) const = 0;
+ virtual double GetDownloadSpeed(const IBuildInstallerRef& Installer) const = 0;
+ virtual int64 GetTotalDownloadRequired(const IBuildInstallerRef& Installer) const = 0;
+ };
+
+ class FBpiLibHelperFactory
+ {
+ public:
+ static TUniquePtr Create(const FString& FilePath);
+ };
+
+ struct FBpiBuildInstallerConfiguration
+ {
+ static const int MaxCloudDirs = 20;
+
+ // TArray InstallerActions;
+ const TCHAR* InstallDirectory = nullptr;
+ const TCHAR* StagingDirectory = nullptr;
+ const TCHAR* BackupDirectory = nullptr;
+ // TArray ChunkDatabaseFiles;
+ const TCHAR* CloudDirectories[MaxCloudDirs];
+ int32 CloudDirectoriesNum = 0;
+ int32 InstallMode = int32(BuildPatchServices::EInstallMode::NonDestructiveInstall);
+ int32 VerifyMode = int32(BuildPatchServices::EVerifyMode::ShaVerifyAllFiles);
+ int32 DeltaPolicy = int32(BuildPatchServices::EDeltaPolicy::Skip);
+ bool bRunRequiredPrereqs = true;
+ bool bSkipPrereqIfAlreadyRan = true;
+ bool bAllowConcurrentExecution = false;
+ uint64 DownloadRateLimitBps = 0;
+ bool bStageWithRawFilenames = false;
+ bool bRejectSymlinks = false;
+
+ static FBpiBuildInstallerConfiguration Create(const BuildPatchServices::FBuildInstallerConfiguration& InCfg)
+ {
+ FBpiBuildInstallerConfiguration Out;
+
+ Out.InstallDirectory = InCfg.InstallDirectory.IsEmpty() ? nullptr : GetData(InCfg.InstallDirectory);
+ Out.StagingDirectory = InCfg.StagingDirectory.IsEmpty() ? nullptr : GetData(InCfg.StagingDirectory);
+ Out.BackupDirectory = InCfg.BackupDirectory.IsEmpty() ? nullptr : GetData(InCfg.BackupDirectory);
+
+ ensure(InCfg.CloudDirectories.Num() < FBpiBuildInstallerConfiguration::MaxCloudDirs);
+ Out.CloudDirectoriesNum = InCfg.CloudDirectories.Num();
+ for (int32 i = 0; i < Out.CloudDirectoriesNum; i++)
+ {
+ Out.CloudDirectories[i] = GetData(InCfg.CloudDirectories[i]);
+ }
+
+ Out.InstallMode = int32(InCfg.InstallMode);
+ Out.VerifyMode = int32(InCfg.VerifyMode);
+ Out.DeltaPolicy = int32(InCfg.DeltaPolicy);
+
+ Out.bRunRequiredPrereqs = InCfg.bRunRequiredPrereqs;
+ // Out.bSkipPrereqIfAlreadyRan = InCfg.bSkipPrereqIfAlreadyRan;
+ Out.bAllowConcurrentExecution = InCfg.bAllowConcurrentExecution;
+ // Out.DownloadRateLimitBps = InCfg.DownloadRateLimitBps;
+ // Out.bStageWithRawFilenames = InCfg.bStageWithRawFilenames;
+ // Out.bRejectSymlinks = InCfg.bRejectSymlinks;
+
+ return Out;
+ }
+ };
+
+ struct FBpiBuildInstallStats
+ {
+ uint32 NumFilesInBuild = 0;
+ uint32 NumFilesOutdated = 0;
+ uint32 NumFilesToRemove = 0;
+ uint32 NumChunksRequired = 0;
+ uint32 ChunksQueuedForDownload = 0;
+ uint32 ChunksLocallyAvailable = 0;
+ uint32 ChunksInChunkDbs = 0;
+ uint32 NumChunksDownloaded = 0;
+ uint32 NumChunksRecycled = 0;
+ uint32 NumChunksReadFromChunkDbs = 0;
+ uint32 NumFailedDownloads = 0;
+ uint32 NumBadDownloads = 0;
+ uint32 NumAbortedDownloads = 0;
+ uint32 NumRecycleFailures = 0;
+ uint32 NumDriveStoreChunkLoads = 0;
+ uint32 NumDriveStoreLoadFailures = 0;
+ uint32 NumChunkDbChunksFailed = 0;
+ uint64 TotalDownloadedData = 0;
+ uint32 ActiveRequestCountPeak = 0;
+ double AverageDownloadSpeed = 0.0;
+ double PeakDownloadSpeed = 0.0;
+ double FinalDownloadSpeed = 0;
+ float TheoreticalDownloadTime = 0.f;
+ uint64 TotalReadData = 0;
+ double AverageDiskReadSpeed = 0.0;
+ double PeakDiskReadSpeed = 0.0;
+ uint64 TotalWrittenData = 0;
+ double AverageDiskWriteSpeed = 0.0;
+ double PeakDiskWriteSpeed = 0.0;
+ uint32 NumFilesConstructed = 0;
+ float InitializeTime = 0.f;
+ float ConstructTime = 0.f;
+ float UninstallActionTime = 0.f;
+ float MoveFromStageTime = 0.f;
+ float FileAttributesTime = 0.f;
+ float VerifyTime = 0.f;
+ float CleanUpTime = 0.f;
+ float PrereqTime = 0.f;
+ float ProcessPausedTime = 0.f;
+ float ProcessActiveTime = 0.f;
+ float ProcessExecuteTime = 0.f;
+ bool ProcessSuccess = false;
+ uint32 NumInstallRetries = 0;
+ int32 FailureType = 0;
+ int32* RetryFailureTypes = nullptr;
+ int32 RetryFailureTypesNum = 0;
+ const TCHAR* ErrorCode = nullptr;
+ const TCHAR** RetryErrorCodes = nullptr;
+ int32 RetryErrorCodesNum = 0;
+ const TCHAR* FailureReasonText = nullptr;
+ float FinalProgress = 0.f;
+ float OverallRequestSuccessRate = 0.f;
+ float ExcellentDownloadHealthTime = 0.f;
+ float GoodDownloadHealthTime = 0.f;
+ float OkDownloadHealthTime = 0.f;
+ float PoorDownloadHealthTime = 0.f;
+ float DisconnectedDownloadHealthTime = 0.f;
+ uint64 ProcessRequiredDiskSpace = 0;
+ uint64 ProcessAvailableDiskSpace = 0;
+ uint32 DriveStorePeakBytes = 0;
+ uint32 NumDriveStoreLostChunks = 0;
+ uint64 MemoryStoreSizePeakBytes = 0;
+ uint64 MemoryStoreSizeLimitBytes = 0;
+ };
+
+ namespace BpiLibHelpers
+ {
+ template
+ FuncType ImportFunction(const FString& Name, void* DllHandle)
+ {
+ if (DllHandle != nullptr)
+ {
+ if (auto* Func = (FuncType)FPlatformProcess::GetDllExport(DllHandle, *Name))
+ {
+ return Func;
+ }
+ }
+ return nullptr;
+ }
+
+ template
+ class TExportedFunc
+ {
+ public:
+ TExportedFunc(void* DllHandle)
+ : Ptr(ImportFunction(TTraits::GetName(), DllHandle))
+ {
+ ensure(Ptr != nullptr);
+ }
+
+ bool IsValid() const { return Ptr != nullptr; }
+
+ template
+ auto operator()(ArgsType&& ...Args) const
+ {
+ return (*Ptr)(Forward(Args)...);
+ }
+
+ typename TTraits::FFuncType Ptr = nullptr;
+ };
+
+ namespace FuncTraits
+ {
+ struct FInit
+ {
+ using FFuncType = int (*)(const TCHAR*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?Init@Helpers@@YAHPEB_W@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers4InitEPKDs");
+#endif
+ }
+ };
+
+ struct FShutdown
+ {
+ using FFuncType = void (*)();
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?Shutdown@Helpers@@YAXXZ");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers8ShutdownEv");
+#endif
+ }
+ };
+
+ struct FTick
+ {
+ using FFuncType = bool (*)(float);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?Tick@Helpers@@YA_NM@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers4TickEf");
+#endif
+ }
+ };
+
+ struct FFree
+ {
+ using FFuncType = void (*)(void*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?Free@Helpers@@YAXPEAX@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers4FreeEPv");
+#endif
+ }
+ };
+
+ struct FFreeArray
+ {
+ using FFuncType = void (*)(void**, int32);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?FreeArray@Helpers@@YAXPEAPEAXH@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers9FreeArrayEPPvi");
+#endif
+ }
+ };
+
+ struct FCreateMakeInstall
+ {
+ using FFuncType = IBuildInstallerRef(*)(
+ IBuildManifestPtr*,
+ const FBpiBuildInstallerConfiguration*,
+ const void*,
+ void(*)(const IBuildInstallerRef&, const void*));
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?CreateMakeInstall@Helpers@@YA?AV?$TSharedRef@VIBuildInstaller@@$00@@PEAV?$TSharedPtr@VIBuildManifest@@$00@@PEBUFBpiBuildInstallerConfiguration@1@PEBXP6AXAEBV2@2@Z@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers17CreateMakeInstallEP10TSharedPtrI14IBuildManifestL7ESPMode1EEPKNS_31FBpiBuildInstallerConfigurationEPKvPFvRK10TSharedRefI15IBuildInstallerLS2_1EES9_E");
+#endif
+
+ }
+ };
+
+ struct FCancelInstall
+ {
+ using FFuncType = void (*)(IBuildInstaller*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?CancelInstall@Helpers@@YAXPEAVIBuildInstaller@@@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers13CancelInstallEP15IBuildInstaller");
+#endif
+ }
+ };
+
+ struct FMakeManifestFromData
+ {
+ using FFuncType = IBuildManifestPtr * (*)(const uint8*, int32);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?MakeManifestFromData@Helpers@@YAPEAV?$TSharedPtr@VIBuildManifest@@$00@@PEBEH@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers20MakeManifestFromDataEPKhi");
+#endif
+ }
+ };
+
+ struct FDeleteManifest
+ {
+ using FFuncType = void (*)(IBuildManifestPtr*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?DeleteManifest@Helpers@@YAXPEAV?$TSharedPtr@VIBuildManifest@@$00@@@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers14DeleteManifestEP10TSharedPtrI14IBuildManifestL7ESPMode1EE");
+#endif
+ }
+ };
+
+ struct FSaveManifestToFile
+ {
+ using FFuncType = bool (*)(IBuildManifestPtr*, const TCHAR*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?SaveManifestToFile@Helpers@@YA_NPEAV?$TSharedPtr@VIBuildManifest@@$00@@PEB_W@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers18SaveManifestToFileEP10TSharedPtrI14IBuildManifestL7ESPMode1EEPKDs");
+#endif
+ }
+ };
+
+ struct FGetBuildFileList
+ {
+ using FFuncType = TCHAR * *(*)(IBuildManifestPtr*, int32*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?GetBuildFileList@Helpers@@YAPEAPEA_WPEAV?$TSharedPtr@VIBuildManifest@@$00@@PEAH@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers16GetBuildFileListEP10TSharedPtrI14IBuildManifestL7ESPMode1EEPi");
+#endif
+ }
+ };
+
+ struct FGetCustomStringField
+ {
+ using FFuncType = TCHAR * (*)(IBuildManifestPtr*, const TCHAR*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?GetCustomStringField@Helpers@@YAPEA_WPEAV?$TSharedPtr@VIBuildManifest@@$00@@PEB_W@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers20GetCustomStringFieldEP10TSharedPtrI14IBuildManifestL7ESPMode1EEPKDs");
+#endif
+ }
+ };
+
+ struct FSetCustomStringField
+ {
+ using FFuncType = TCHAR * (*)(IBuildManifestPtr*, const TCHAR*, const TCHAR*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?SetCustomStringField@Helpers@@YAXPEAV?$TSharedPtr@VIBuildManifest@@$00@@PEB_W1@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers20SetCustomStringFieldEP10TSharedPtrI14IBuildManifestL7ESPMode1EEPKDsS6_");
+#endif
+ }
+ };
+
+ struct FSetCustomDoubleField
+ {
+ using FFuncType = TCHAR * (*)(IBuildManifestPtr*, const TCHAR*, double);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?SetCustomDoubleField@Helpers@@YAXPEAV?$TSharedPtr@VIBuildManifest@@$00@@PEB_WN@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers20SetCustomDoubleFieldEP10TSharedPtrI14IBuildManifestL7ESPMode1EEPKDsd");
+#endif
+ }
+ };
+
+ struct FSetCustomIntField
+ {
+ using FFuncType = TCHAR * (*)(IBuildManifestPtr*, const TCHAR*, int64);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?SetCustomIntField@Helpers@@YAXPEAV?$TSharedPtr@VIBuildManifest@@$00@@PEB_W_J@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers17SetCustomIntFieldEP10TSharedPtrI14IBuildManifestL7ESPMode1EEPKDsx");
+#endif
+ }
+ };
+
+ struct FGetAppName
+ {
+ using FFuncType = TCHAR * (*)(IBuildManifestPtr* Manifest);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?GetAppName@Helpers@@YAPEA_WPEAV?$TSharedPtr@VIBuildManifest@@$00@@@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers10GetAppNameEP10TSharedPtrI14IBuildManifestL7ESPMode1EE");
+#endif
+ }
+ };
+
+ struct FGetBuildStats
+ {
+ using FFuncType = FBpiBuildInstallStats * (*)(IBuildInstaller* Installer);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?GetBuildStats@Helpers@@YAPEAUFBpiBuildInstallStats@1@PEAVIBuildInstaller@@@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers13GetBuildStatsEP15IBuildInstaller");
+#endif
+ }
+ };
+
+ struct FGetTotalDownloaded
+ {
+ using FFuncType = int64(*)(IBuildInstaller*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?GetTotalDownloaded@Helpers@@YA_JPEAVIBuildInstaller@@@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers18GetTotalDownloadedEP15IBuildInstaller");
+#endif
+ }
+ };
+
+ struct FGetState
+ {
+ using FFuncType = int64(*)(IBuildInstaller*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?GetState@Helpers@@YA_JPEAVIBuildInstaller@@@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers8GetStateEP15IBuildInstaller");
+#endif
+ }
+ };
+
+ struct FGetUpdateProgress
+ {
+ using FFuncType = float(*)(IBuildInstaller*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?GetUpdateProgress@Helpers@@YAMPEAVIBuildInstaller@@@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers17GetUpdateProgressEP15IBuildInstaller");
+#endif
+ }
+ };
+
+ struct FGetDownloadSpeed
+ {
+ using FFuncType = double(*)(IBuildInstaller*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?GetDownloadSpeed@Helpers@@YANPEAVIBuildInstaller@@@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers16GetDownloadSpeedEP15IBuildInstaller");
+#endif
+ }
+ };
+
+ struct FGetTotalDownloadRequired
+ {
+ using FFuncType = int64(*)(IBuildInstaller*);
+ static const TCHAR* GetName()
+ {
+#if PLATFORM_WINDOWS
+ return TEXT("?GetTotalDownloadRequired@Helpers@@YA_JPEAVIBuildInstaller@@@Z");
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ return TEXT("_ZN7Helpers24GetTotalDownloadRequiredEP15IBuildInstaller");
+#endif
+ }
+ };
+ }
+
+ class FBpiLibHelper : public IBpiLib
+ {
+ struct FCallbackStorage
+ {
+ FCallbackStorage(FBuildPatchInstallerDelegate&& InCompleteDelegate) :
+ CompleteDelegate(MoveTemp(InCompleteDelegate)) {}
+
+ FBuildPatchInstallerDelegate CompleteDelegate;
+ };
+ public:
+ FBpiLibHelper(void* InDllHandle)
+ : DllHandle(InDllHandle)
+ {
+ ensure(DllHandle != nullptr);
+ if (FuncFInit.IsValid())
+ {
+ bIsInited = FuncFInit(FCommandLine::Get()) == 0;
+ }
+ }
+
+ virtual ~FBpiLibHelper()
+ {
+ if (FuncFShutdown.IsValid())
+ {
+ FuncFShutdown();
+ }
+ FPlatformProcess::FreeDllHandle(DllHandle);
+ }
+
+ virtual bool IsValid() const override { return bIsInited; }
+
+ virtual IBuildInstallerRef CreateInstaller(
+ FManifestStorage& ManifestStorage,
+ const BuildPatchServices::FBuildInstallerConfiguration& Configuration,
+ FBuildPatchInstallerDelegate InCompleteDelegate) const override
+ {
+ FCallbackStorage* CbStorage = new FCallbackStorage(MoveTemp(InCompleteDelegate));
+ FBpiBuildInstallerConfiguration LibCfg = FBpiBuildInstallerConfiguration::Create(Configuration);
+ return FuncFCreateMakeInstall(ManifestStorage.GetManifestPtr(),
+ &LibCfg,
+ CbStorage,
+ [](const IBuildInstallerRef& Installer, const void* UserPtr) {
+ if (UserPtr)
+ {
+ const FCallbackStorage* ExtCbStorage = static_cast(UserPtr);
+ ExtCbStorage->CompleteDelegate.ExecuteIfBound(Installer);
+ delete ExtCbStorage;
+ }
+ }
+ );
+ }
+
+ virtual FManifestStorage MakeManifestFromData(const TArray& Data) const override
+ {
+ return FManifestStorage(*this, Data);
+ }
+
+ virtual bool Tick(float Delta) override { return FuncFTick(Delta); }
+
+ virtual FBuildInstallStats GetBuildStats(const IBuildInstallerRef& Installer) const override
+ {
+ FBuildInstallStats Out;
+ FBpiBuildInstallStats* InStats = FuncFGetBuildStats(&Installer.Get());
+
+#define COPY(__x__) Out.__x__ = InStats->__x__
+
+ COPY(NumFilesInBuild);
+ COPY(NumFilesOutdated);
+ COPY(NumFilesToRemove);
+ COPY(NumChunksRequired);
+ COPY(ChunksQueuedForDownload);
+ COPY(ChunksLocallyAvailable);
+ COPY(ChunksInChunkDbs);
+ COPY(NumChunksDownloaded);
+ COPY(NumChunksRecycled);
+ COPY(NumChunksReadFromChunkDbs);
+ COPY(NumFailedDownloads);
+ COPY(NumBadDownloads);
+ COPY(NumAbortedDownloads);
+ COPY(NumRecycleFailures);
+ COPY(NumDriveStoreChunkLoads);
+ COPY(NumDriveStoreLoadFailures);
+ COPY(NumChunkDbChunksFailed);
+ COPY(TotalDownloadedData);
+ COPY(ActiveRequestCountPeak);
+ COPY(AverageDownloadSpeed);
+ COPY(PeakDownloadSpeed);
+ COPY(FinalDownloadSpeed);
+ COPY(TheoreticalDownloadTime);
+ COPY(TotalReadData);
+ COPY(AverageDiskReadSpeed);
+ COPY(PeakDiskReadSpeed);
+ COPY(TotalWrittenData);
+ COPY(AverageDiskWriteSpeed);
+ COPY(PeakDiskWriteSpeed);
+ COPY(NumFilesConstructed);
+ COPY(InitializeTime);
+ COPY(ConstructTime);
+ //COPY(UninstallActionTime); uncomment when merged to ue5
+ COPY(MoveFromStageTime);
+ COPY(FileAttributesTime);
+ COPY(VerifyTime);
+ COPY(CleanUpTime);
+ COPY(PrereqTime);
+ COPY(ProcessPausedTime);
+ COPY(ProcessActiveTime);
+ COPY(ProcessExecuteTime);
+ COPY(ProcessSuccess);
+ COPY(NumInstallRetries);
+
+ Out.FailureType = EBuildPatchInstallError(InStats->FailureType);
+
+ // Out.RetryFailureTypes = ConvertAndFree>(InStats->RetryFailureTypes, InStats->RetryFailureTypesNum);
+
+ // Out.ErrorCode = ConvertAndFree(InStats->ErrorCode);
+
+ // Out.RetryErrorCodes = ConvertAndFree>(InStats->RetryErrorCodes, InStats->RetryErrorCodesNum);
+ // Out.FailureReasonText = FText::FromString(ConvertAndFree(InStats->FailureReasonText));
+
+ COPY(FinalProgress);
+ COPY(OverallRequestSuccessRate);
+ COPY(ExcellentDownloadHealthTime);
+ COPY(GoodDownloadHealthTime);
+ COPY(OkDownloadHealthTime);
+ COPY(PoorDownloadHealthTime);
+ COPY(DisconnectedDownloadHealthTime);
+ COPY(ProcessRequiredDiskSpace);
+ COPY(ProcessAvailableDiskSpace);
+ // COPY(DriveStorePeakBytes);
+ // COPY(NumDriveStoreLostChunks);
+ // COPY(MemoryStoreSizePeakBytes);
+ // COPY(MemoryStoreSizeLimitBytes);
+#undef COPY
+ return Out;
+ }
+
+ virtual void CancelInstall(const IBuildInstallerRef& Installer) const override { FuncFCancelInstall(&Installer.Get()); }
+
+ virtual int64 GetTotalDownloaded(const IBuildInstallerRef& Installer) const override { return FuncFGetTotalDownloaded(&Installer.Get()); }
+ virtual int64 GetState(const IBuildInstallerRef& Installer) const override { return FuncFGetState(&Installer.Get()); }
+ virtual float GetUpdateProgress(const IBuildInstallerRef& Installer) const override { return FuncFGetUpdateProgress(&Installer.Get()); }
+ virtual double GetDownloadSpeed(const IBuildInstallerRef& Installer) const override { return FuncFGetDownloadSpeed(&Installer.Get()); }
+ virtual int64 GetTotalDownloadRequired(const IBuildInstallerRef& Installer) const override { return FuncFGetTotalDownloadRequired(&Installer.Get()); }
+
+ template
+ Out ConvertAndFree(In Data, int32 Num) const
+ {
+ Out Result;
+ if (Data != nullptr)
+ {
+ Result.Reserve(Num);
+ for (int32 i = 0; i < Num; i++)
+ {
+ Result.Emplace(typename Out::ElementType(Data[i]));
+ }
+ FuncFFreeArray((void**)Data, Num);
+ }
+ return Result;
+
+ }
+
+ FString ConvertAndFree(const TCHAR* Data) const
+ {
+ FString Result;
+ int32 Num = 0;
+ if (Data != nullptr)
+ {
+ Result = Data;
+ FuncFFree((void*)Data);
+ }
+ return Result;
+ }
+
+ private:
+ bool bIsInited = false;
+ void* DllHandle = nullptr;
+
+ public:
+ // declare function by a class name
+#define DECLFUNC(__x__) TExportedFunc Func##__x__{ DllHandle }
+
+ DECLFUNC(FInit);
+ DECLFUNC(FTick);
+ DECLFUNC(FShutdown);
+ DECLFUNC(FFreeArray);
+ DECLFUNC(FFree);
+
+ DECLFUNC(FCreateMakeInstall);
+ DECLFUNC(FMakeManifestFromData);
+ DECLFUNC(FDeleteManifest);
+ DECLFUNC(FSaveManifestToFile);
+ DECLFUNC(FGetBuildFileList);
+ DECLFUNC(FGetCustomStringField);
+
+ DECLFUNC(FSetCustomStringField);
+ DECLFUNC(FSetCustomDoubleField);
+ DECLFUNC(FSetCustomIntField);
+
+ DECLFUNC(FGetAppName);
+ DECLFUNC(FGetBuildStats);
+ DECLFUNC(FGetTotalDownloaded);
+ DECLFUNC(FGetState);
+ DECLFUNC(FGetUpdateProgress);
+ DECLFUNC(FGetDownloadSpeed);
+ DECLFUNC(FGetTotalDownloadRequired);
+
+ DECLFUNC(FCancelInstall);
+
+#undef DECLFUNC
+ };
+ }
+
+ inline TUniquePtr FBpiLibHelperFactory::Create(const FString& FilePath)
+ {
+ if (FPaths::FileExists(FilePath))
+ {
+#if PLATFORM_WINDOWS
+ if (void* DllHandle = FPlatformProcess::GetDllHandle(*FilePath))
+#elif PLATFORM_LINUX || PLATFORM_MAC
+ FString AbsolutePath = FPaths::ConvertRelativePathToFull(FilePath);
+ if (void* DllHandle = dlopen(TCHAR_TO_UTF8(*AbsolutePath), RTLD_NOW | RTLD_DEEPBIND))
+#endif
+ {
+ auto BptLib = MakeUnique(DllHandle);
+ return BptLib.IsValid() ? MoveTemp(BptLib) : nullptr;
+ }
+ }
+ return nullptr;
+ }
+
+ inline FManifestStorage::FManifestStorage(const BpiLibHelpers::FBpiLibHelper& InLibRef, const TArray& Data)
+ : LibRef(InLibRef)
+ {
+ ManifestPtr = LibRef.FuncFMakeManifestFromData(Data.GetData(), Data.Num());
+ }
+
+ inline FManifestStorage::~FManifestStorage()
+ {
+ LibRef.FuncFDeleteManifest(ManifestPtr);
+ }
+
+ inline bool FManifestStorage::SaveToFile(const FString& Filename) const
+ {
+ return LibRef.FuncFSaveManifestToFile(ManifestPtr, *Filename);
+ }
+
+ inline TArray FManifestStorage::GetBuildFileList() const
+ {
+ int32 Num = 0;
+ TCHAR** Data = LibRef.FuncFGetBuildFileList(ManifestPtr, &Num);
+ return LibRef.ConvertAndFree>(Data, Num);
+ }
+
+ inline FString FManifestStorage::GetCustomStringField(const FString& Name) const
+ {
+ TCHAR* Data = LibRef.FuncFGetCustomStringField(ManifestPtr, *Name);
+ return LibRef.ConvertAndFree(Data);
+ }
+
+ inline void FManifestStorage::SetCustomField(const FString& FieldName, const FString& Value) const
+ {
+ LibRef.FuncFSetCustomStringField(ManifestPtr, *FieldName, *Value);
+ }
+
+ inline void FManifestStorage::SetCustomField(const FString& FieldName, const double& Value) const
+ {
+ LibRef.FuncFSetCustomDoubleField(ManifestPtr, *FieldName, Value);
+ }
+
+ inline void FManifestStorage::SetCustomField(const FString& FieldName, const int64& Value) const
+ {
+ LibRef.FuncFSetCustomIntField(ManifestPtr, *FieldName, Value);
+ }
+
+ inline FString FManifestStorage::GetAppName() const
+ {
+ TCHAR* Data = LibRef.FuncFGetAppName(ManifestPtr);
+ return LibRef.ConvertAndFree(Data);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Fab/Source/Fab/Private/Importers/FabDragDropOp.cpp b/Plugins/Fab/Source/Fab/Private/Importers/FabDragDropOp.cpp
new file mode 100644
index 0000000..f33864d
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Importers/FabDragDropOp.cpp
@@ -0,0 +1,181 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "FabDragDropOp.h"
+
+#include "ActorSpawner.h"
+#include "ClassIconFinder.h"
+#include "Editor.h"
+
+#include "ActorFactories/ActorFactoryBasicShape.h"
+
+#include "Engine/DecalActor.h"
+#include "Engine/SkeletalMesh.h"
+
+#include "Materials/Material.h"
+
+#include "Widgets/Colors/SColorBlock.h"
+#include "Widgets/Layout/SBox.h"
+
+FFabDragDropOp::FFabDragDropOp(const EDragAssetType InDragAssetType)
+ : SpawnedActor(nullptr)
+ , DragAssetType(InDragAssetType)
+{}
+
+FFabDragDropOp::~FFabDragDropOp()
+{
+ Cancel();
+}
+
+TSharedPtr FFabDragDropOp::GetDefaultDecorator() const
+{
+ const FSlateBrush* Image = nullptr;
+ if (DragAssetType == EDragAssetType::Mesh)
+ {
+ Image = FClassIconFinder::FindThumbnailForClass(UStaticMesh::StaticClass());
+ }
+ else if (DragAssetType == EDragAssetType::Material)
+ {
+ Image = FClassIconFinder::FindThumbnailForClass(UMaterial::StaticClass());
+ }
+ else if (DragAssetType == EDragAssetType::Decal)
+ {
+ Image = FClassIconFinder::FindThumbnailForClass(ADecalActor::StaticClass());
+ }
+
+ return SNew(SBorder)
+ [
+ SNew(SBox)
+ .HeightOverride(80)
+ .WidthOverride(80)
+ [
+ SNew(SOverlay)
+ + SOverlay::Slot()
+ [
+ SNew(SColorBlock)
+ .Color(FColor(32, 32, 36).ReinterpretAsLinear())
+ .Size(FVector2D(80.0f, 80.0f))
+ .UseSRGB(false)
+ .AlphaDisplayMode(EColorBlockAlphaDisplayMode::Ignore)
+ .CornerRadius(FVector4(10.0f))
+ ]
+ + SOverlay::Slot()
+ .Padding(10.0f)
+ [
+ SNew(SImage)
+ .Image(Image)
+ ]
+ ]
+ ];
+}
+
+void FFabDragDropOp::OnDragged(const FDragDropEvent& DragDropEvent)
+{
+ if (CursorDecoratorWindow.IsValid())
+ {
+ CursorDecoratorWindow->MoveWindowTo(DragDropEvent.GetScreenSpacePosition());
+ }
+}
+
+void FFabDragDropOp::Construct()
+{
+ MouseCursor = EMouseCursor::GrabHandClosed;
+ if (DragAssetType == EDragAssetType::Material)
+ {
+ EditorApplyHandle = FEditorDelegates::OnApplyObjectToActor.AddLambda(
+ [this](const UObject* Object, AActor* Actor)
+ {
+ if (GetAssets()[0].GetAsset() == Object)
+ SpawnedActor = Actor;
+ }
+ );
+ }
+ FDragDropOperation::Construct();
+}
+
+void FFabDragDropOp::OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent)
+{
+ if (EditorApplyHandle.IsValid())
+ {
+ FEditorDelegates::OnApplyObjectToActor.Remove(EditorApplyHandle);
+ EditorApplyHandle.Reset();
+ }
+ DestroyCursorDecoratorWindow();
+ if (!bDropWasHandled)
+ SpawnedActor = nullptr;
+ else if (OnDropDelegate.IsBound())
+ OnDropDelegate.Execute();
+}
+
+void FFabDragDropOp::DestroyWindow()
+{
+ DestroyCursorDecoratorWindow();
+}
+
+void FFabDragDropOp::DestroySpawnedActor()
+{
+ if (SpawnedActor && (DragAssetType == EDragAssetType::Mesh || DragAssetType == EDragAssetType::Decal))
+ {
+ SpawnedActor->Destroy();
+ SpawnedActor = nullptr;
+ }
+}
+
+void FFabDragDropOp::Cancel()
+{
+ if (EditorApplyHandle.IsValid())
+ {
+ FEditorDelegates::OnApplyObjectToActor.Remove(EditorApplyHandle);
+ EditorApplyHandle.Reset();
+ }
+ if (UFabPlaceholderSpawner* PlaceholderFactory = Cast(GetActorFactory()))
+ PlaceholderFactory->OnActorSpawn().Unbind();
+ if (OnDropDelegate.IsBound())
+ OnDropDelegate.Unbind();
+ DestroyWindow();
+}
+
+TSharedPtr FFabDragDropOp::New(FAssetData Asset, EDragAssetType InDragAssetType)
+{
+ TSharedPtr Operation = MakeShared(InDragAssetType);
+
+ UFabPlaceholderSpawner* ActorFactory = nullptr;
+ if (InDragAssetType == EDragAssetType::Mesh)
+ {
+ const UClass* AssetClass = Asset.GetAsset()->GetClass();
+ if (AssetClass->IsChildOf())
+ {
+ ActorFactory = Cast(GEditor->FindActorFactoryByClass(UFabStaticMeshPlaceholderSpawner::StaticClass()));
+ }
+ else if (AssetClass->IsChildOf())
+ {
+ ActorFactory = Cast(GEditor->FindActorFactoryByClass(UFabSkeletalMeshPlaceholderSpawner::StaticClass()));
+ }
+ }
+ else if (InDragAssetType == EDragAssetType::Decal)
+ {
+ ActorFactory = Cast(GEditor->FindActorFactoryByClass(UFabDecalPlaceholderSpawner::StaticClass()));
+ }
+
+ if (ActorFactory)
+ {
+ ActorFactory->OnActorSpawn().BindLambda(
+ [Operation](AActor* Actor)
+ {
+ if (Actor->IsActorBeingDestroyed())
+ Operation->SpawnedActor = nullptr;
+ else
+ Operation->SpawnedActor = Actor;
+ }
+ );
+ }
+
+ Operation->Init(
+ {
+ Asset
+ },
+ TArray(),
+ ActorFactory
+ );
+ Operation->Construct();
+ return Operation;
+}
diff --git a/Plugins/Fab/Source/Fab/Private/Importers/FabDragDropOp.h b/Plugins/Fab/Source/Fab/Private/Importers/FabDragDropOp.h
new file mode 100644
index 0000000..dfd81cb
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Importers/FabDragDropOp.h
@@ -0,0 +1,57 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+
+#include "AssetRegistry/AssetData.h"
+
+#include "DragAndDrop/AssetDragDropOp.h"
+
+enum class EDragAssetType
+{
+ Mesh,
+ Material,
+ Decal
+};
+
+class FFabDragDropOp : public FAssetDragDropOp
+{
+public:
+ DECLARE_DELEGATE(FOnDrop);
+ DRAG_DROP_OPERATOR_TYPE(FFabDragDropOp, FAssetDragDropOp)
+
+public:
+ static TSharedPtr New(FAssetData Asset, EDragAssetType InDragAssetType);
+
+public:
+ FFabDragDropOp(const EDragAssetType InDragAssetType);
+ virtual ~FFabDragDropOp() override;
+
+ virtual TSharedPtr GetDefaultDecorator() const override;
+ FOnDrop& OnDrop() { return this->OnDropDelegate; }
+
+ void SetCanDropHere(bool bCanDropHere)
+ {
+ MouseCursor = bCanDropHere ? EMouseCursor::GrabHandClosed : EMouseCursor::SlashedCircle;
+ }
+
+ virtual void Construct() override;
+
+ void Cancel();
+ void DestroyWindow();
+ void DestroySpawnedActor();
+
+ virtual void OnDragged(const FDragDropEvent& DragDropEvent) override;
+ virtual void OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) override;
+
+public:
+ TObjectPtr SpawnedActor;
+
+protected:
+ FOnDrop OnDropDelegate;
+
+private:
+ EDragAssetType DragAssetType;
+ FDelegateHandle EditorApplyHandle;
+};
diff --git a/Plugins/Fab/Source/Fab/Private/Importers/GenericAssetImporter.cpp b/Plugins/Fab/Source/Fab/Private/Importers/GenericAssetImporter.cpp
new file mode 100644
index 0000000..39669e2
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Importers/GenericAssetImporter.cpp
@@ -0,0 +1,190 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "GenericAssetImporter.h"
+
+#include "AssetImportTask.h"
+#include "IAssetTools.h"
+#include "InterchangeGenericAssetsPipeline.h"
+#include "InterchangeGenericMaterialPipeline.h"
+#include "InterchangeGenericMeshPipeline.h"
+#include "InterchangeGenericTexturePipeline.h"
+#include "InterchangeProjectSettings.h"
+
+#include "Runtime/Launch/Resources/Version.h"
+
+#if (ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION <=3)
+#include "InterchangeglTFPipeline.h"
+#endif
+
+#include "Async/Async.h"
+
+#include "Factories/FbxImportUI.h"
+#include "Factories/FbxStaticMeshImportData.h"
+
+UObject* FFabGenericImporter::GetImportOptions(const FString& SourceFile, UObject* const OptionsOuter)
+{
+ check(OptionsOuter != nullptr);
+
+ const UInterchangeManager& InterchangeManager = UInterchangeManager::GetInterchangeManager();
+ const UInterchangeSourceData* SourceData = UInterchangeManager::CreateSourceData(SourceFile);
+ if (InterchangeManager.IsInterchangeImportEnabled() && InterchangeManager.CanTranslateSourceData(SourceData))
+ {
+ const FName PipelineStackName = FInterchangeProjectSettingsUtils::GetDefaultPipelineStackName(false, *SourceData);
+ const FInterchangeImportSettings& InterchangeImportSettings = FInterchangeProjectSettingsUtils::GetDefaultImportSettings(false);
+ if (const FInterchangePipelineStack* const PipelineStack = InterchangeImportSettings.PipelineStacks.Find(PipelineStackName))
+ {
+ const TArray* Pipelines = &PipelineStack->Pipelines;
+ UE::Interchange::FScopedTranslator ScopedTranslator(SourceData);
+ for (const FInterchangeTranslatorPipelines& TranslatorPipelines : PipelineStack->PerTranslatorPipelines)
+ {
+ const UClass* TranslatorClass = TranslatorPipelines.Translator.LoadSynchronous();
+ if (ScopedTranslator.GetTranslator() && ScopedTranslator.GetTranslator()->IsA(TranslatorClass))
+ {
+ Pipelines = &TranslatorPipelines.Pipelines;
+ break;
+ }
+ }
+
+ UInterchangePipelineStackOverride* StackOverride = NewObject(OptionsOuter);
+
+ for (const FSoftObjectPath& Pipeline : *Pipelines)
+ {
+ UInterchangePipelineBase* const DefaultPipeline = Cast(Pipeline.TryLoad());
+ if (!DefaultPipeline)
+ {
+ continue;
+ }
+ if (UInterchangePipelineBase* GeneratedPipeline = UE::Interchange::GeneratePipelineInstance(Pipeline))
+ {
+ GeneratedPipeline->TransferAdjustSettings(DefaultPipeline);
+ GeneratedPipeline->AddToRoot();
+ if (UInterchangeGenericAssetsPipeline* const GenericAssetsPipeline = Cast(GeneratedPipeline))
+ {
+ GenericAssetsPipeline->MeshPipeline->bImportStaticMeshes = true;
+ GenericAssetsPipeline->MeshPipeline->bImportSkeletalMeshes = true;
+ GenericAssetsPipeline->MeshPipeline->bCombineStaticMeshes = true;
+ GenericAssetsPipeline->MeshPipeline->SkeletalMeshImportContentType = EInterchangeSkeletalMeshContentType::All;
+ GenericAssetsPipeline->MeshPipeline->bGenerateLightmapUVs = true;
+ GenericAssetsPipeline->MeshPipeline->bBuildNanite = false;
+ GenericAssetsPipeline->MaterialPipeline->bImportMaterials = true;
+ GenericAssetsPipeline->MaterialPipeline->TexturePipeline->bImportTextures = true;
+
+ GenericAssetsPipeline->MaterialPipeline->MaterialImport = EInterchangeMaterialImportOption::ImportAsMaterialInstances;
+ GenericAssetsPipeline->CommonMeshesProperties->bRecomputeNormals = false;
+ GenericAssetsPipeline->CommonMeshesProperties->bComputeWeightedNormals = false;
+ GenericAssetsPipeline->CommonMeshesProperties->VertexColorImportOption = EInterchangeVertexColorImportOption::IVCIO_Replace;
+ }
+ if (UInterchangeGenericTexturePipeline* const GenericTexturePipeline = Cast(GeneratedPipeline))
+ {
+ GenericTexturePipeline->bAllowNonPowerOfTwo = true;
+ GenericTexturePipeline->bDetectNormalMapTexture = true;
+ }
+ #if (ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION <=3)
+ {
+ if (UInterchangeGLTFPipeline* const GLTFGeneratedPipeline = Cast(GeneratedPipeline))
+ {
+ GLTFGeneratedPipeline->bUseGLTFMaterialInstanceLibrary = true;
+ }
+ }
+ #endif
+
+ StackOverride->OverridePipelines.Add(GeneratedPipeline);
+ }
+ }
+ return StackOverride;
+ }
+ return nullptr;
+ }
+ if (FPaths::GetExtension(SourceFile).ToLower() == "fbx")
+ {
+ UFbxImportUI* ImportOptions = NewObject(OptionsOuter);
+
+ ImportOptions->bIsReimport = false;
+ ImportOptions->bImportMesh = true;
+ ImportOptions->bImportAnimations = true;
+ ImportOptions->bImportMaterials = true;
+ ImportOptions->bImportTextures = true;
+ ImportOptions->bImportAsSkeletal = false;
+ ImportOptions->StaticMeshImportData->bCombineMeshes = true;
+ ImportOptions->StaticMeshImportData->bBuildNanite = false;
+ ImportOptions->StaticMeshImportData->bGenerateLightmapUVs = false;
+ ImportOptions->StaticMeshImportData->bAutoGenerateCollision = false;
+ ImportOptions->StaticMeshImportData->VertexColorImportOption = EVertexColorImportOption::Replace;
+ ImportOptions->StaticMeshImportData->NormalImportMethod = FBXNIM_ImportNormalsAndTangents;
+
+ return ImportOptions;
+ }
+
+ return nullptr;
+}
+
+void FFabGenericImporter::CleanImportOptions(UObject* const Options)
+{
+ if (const UInterchangePipelineStackOverride* const InterchangeOptions = Cast(Options))
+ {
+ for (const FSoftObjectPath& OverridePipeline : InterchangeOptions->OverridePipelines)
+ {
+ if (UObject* const LoadedPipeline = OverridePipeline.TryLoad())
+ LoadedPipeline->RemoveFromRoot();
+ }
+ }
+}
+
+void FFabGenericImporter::ImportAsset(const TArray& Sources, const FString& Destination, const TFunction&)>& Callback)
+{
+ TSharedPtr> MeshImportTasks = MakeShared>();
+
+ for (const FString& Source : Sources)
+ {
+ UAssetImportTask* MeshImportTask = NewObject();
+ MeshImportTask->AddToRoot();
+
+ MeshImportTask->bAutomated = true;
+ MeshImportTask->bSave = false;
+ MeshImportTask->bAsync = true;
+ MeshImportTask->Filename = Source;
+
+ MeshImportTask->DestinationPath = Destination;
+ MeshImportTask->bReplaceExisting = true;
+ MeshImportTask->Options = GetImportOptions(Source, MeshImportTask);
+
+ MeshImportTasks->Add(MeshImportTask);
+ }
+
+ IAssetTools::Get().ImportAssetTasks(*MeshImportTasks);
+
+ TSharedPtr> ImportedObjects = MakeShared>();
+ Async(
+ EAsyncExecution::Thread,
+ [MeshImportTasks, ImportedObjects]()
+ {
+ for (const UAssetImportTask* const MeshImportTask : *MeshImportTasks)
+ {
+ if (MeshImportTask->AsyncResults.IsValid())
+ {
+ FPlatformProcess::ConditionalSleep([=]() { return MeshImportTask->IsAsyncImportComplete(); }, 0.25f);
+ ImportedObjects->Append(MeshImportTask->AsyncResults->GetImportedObjects());
+ }
+ else
+ {
+ ImportedObjects->Append(MeshImportTask->GetObjects());
+ }
+ }
+ },
+ [MeshImportTasks, ImportedObjects, Callback]()
+ {
+ Async(
+ EAsyncExecution::TaskGraphMainThread,
+ [MeshImportTasks, ImportedObjects, Callback]()
+ {
+ Callback(*ImportedObjects);
+ for (UAssetImportTask* MeshImportTask : *MeshImportTasks)
+ {
+ CleanImportOptions(MeshImportTask->Options);
+ MeshImportTask->RemoveFromRoot();
+ }
+ }
+ );
+ }
+ );
+}
diff --git a/Plugins/Fab/Source/Fab/Private/Importers/GenericAssetImporter.h b/Plugins/Fab/Source/Fab/Private/Importers/GenericAssetImporter.h
new file mode 100644
index 0000000..8a088a5
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Importers/GenericAssetImporter.h
@@ -0,0 +1,18 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+
+class UFbxImportUI;
+class UInterchangePipelineStackOverride;
+
+class FFabGenericImporter
+{
+private:
+ static UObject* GetImportOptions(const FString& SourceFile, UObject* const OptionsOuter);
+ static void CleanImportOptions(UObject* const Options);
+
+public:
+ static void ImportAsset(const TArray& Sources, const FString& Destination, const TFunction&)>& Callback);
+};
diff --git a/Plugins/Fab/Source/Fab/Private/Importers/QuixelGLTFImporter.cpp b/Plugins/Fab/Source/Fab/Private/Importers/QuixelGLTFImporter.cpp
new file mode 100644
index 0000000..20f318c
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Importers/QuixelGLTFImporter.cpp
@@ -0,0 +1,398 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "QuixelGLTFImporter.h"
+
+#include "Editor.h"
+#include "FoliageType_InstancedStaticMesh.h"
+#include "InterchangeGenericAssetsPipeline.h"
+#include "InterchangeGenericMaterialPipeline.h"
+#include "InterchangeGenericMeshPipeline.h"
+#include "InterchangeManager.h"
+
+#if (ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION <=3)
+#include "InterchangeglTFPipeline.h"
+#endif
+
+#include "InterchangeGenericTexturePipeline.h"
+#include "MaterialTypes.h"
+
+#include "AssetRegistry/AssetRegistryModule.h"
+
+#include "Dom/JsonObject.h"
+#include "Dom/JsonValue.h"
+
+#include "Engine/Texture.h"
+#include "Engine/Texture2D.h"
+
+#include "Kismet2/KismetEditorUtilities.h"
+
+#include "Materials/Material.h"
+#include "Materials/MaterialInstance.h"
+
+#include "Misc/FileHelper.h"
+
+#include "Pipelines/InterchangeMegascansPipeline.h"
+
+#include "Serialization/JsonReader.h"
+#include "Serialization/JsonSerializer.h"
+#include "Serialization/JsonTypes.h"
+
+#include "UObject/SoftObjectPath.h"
+
+void FQuixelGltfImporter::SetupGlobalFoliageActor(const FString& ImportPath)
+{
+ const FString GlobalFoliageActorPackageName = "BP_GlobalFoliageActor";
+ const FString GlobalFoliageActorDestinationPath = FPaths::GetPath(FPaths::GetPath(ImportPath)) / GlobalFoliageActorPackageName;
+
+ if (FindPackage(nullptr, *GlobalFoliageActorDestinationPath))
+ {
+ return;
+ }
+
+ const FString GlobalFoliageActorClass = "BP_GlobalFoliageActor_UE5.BP_GlobalFoliageActor_UE5_C";
+ const FString GlobalFoliageActorClassPath = "/Fab/Actors/GlobalFoliageActor" / GlobalFoliageActorClass;
+
+ if (UPackage* Package = CreatePackage(*GlobalFoliageActorDestinationPath))
+ {
+ UClass* ParentClass = Cast(FSoftObjectPath(GlobalFoliageActorClassPath).TryLoad());
+ UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(
+ ParentClass,
+ Package,
+ *GlobalFoliageActorPackageName,
+ BPTYPE_Const,
+ UBlueprint::StaticClass(),
+ UBlueprintGeneratedClass::StaticClass()
+ );
+
+ if (Blueprint)
+ {
+ FAssetRegistryModule::AssetCreated(Blueprint);
+ Package->MarkPackageDirty();
+ }
+ }
+}
+
+TArray FQuixelGltfImporter::GetPipelinesForSourceData(const UInterchangeSourceData* InSourceData)
+{
+ TArray ImportPipelines;
+ ImportPipelines.Add(FSoftObjectPath("/Interchange/Pipelines/DefaultGLTFAssetsPipeline.DefaultGLTFAssetsPipeline"));
+ ImportPipelines.Add(FSoftObjectPath("/Interchange/Pipelines/DefaultGLTFPipeline.DefaultGLTFPipeline"));
+ return ImportPipelines;
+}
+
+void FQuixelGltfImporter::GeneratePipelines(const TArray& OriginalPipelines, TArray& GeneratedPipelines)
+{
+ if (!GeneratedPipelines.IsEmpty())
+ GeneratedPipelines.Empty();
+
+ for (const FSoftObjectPath& Pipeline : OriginalPipelines)
+ {
+ UInterchangePipelineBase* const DefaultPipeline = Cast(Pipeline.TryLoad());
+ if (!DefaultPipeline)
+ {
+ continue;
+ }
+ if (UInterchangePipelineBase* const GeneratedPipeline = UE::Interchange::GeneratePipelineInstance(Pipeline))
+ {
+ GeneratedPipeline->TransferAdjustSettings(DefaultPipeline);
+ GeneratedPipeline->AddToRoot();
+ GeneratedPipelines.Add(GeneratedPipeline);
+ #if (ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION <=3)
+ {
+ if (UInterchangeGLTFPipeline* const GLTFGeneratedPipeline = Cast(GeneratedPipeline))
+ {
+ GLTFGeneratedPipeline->bUseGLTFMaterialInstanceLibrary = true;
+ }
+ }
+ #endif
+ }
+ }
+
+ UInterchangeMegascansPipeline* MegascansPipeline = NewObject();
+ MegascansPipeline->AddToRoot();
+ GeneratedPipelines.Add(MegascansPipeline);
+}
+
+UInterchangeGenericAssetsPipeline* FQuixelGltfImporter::GetGenericAssetPipeline(const TArray& GeneratedPipelines)
+{
+ if (UInterchangePipelineBase* const* const AssetPipeline = GeneratedPipelines.FindByPredicate(
+ [](const UInterchangePipelineBase* Pipeline) { return Pipeline->IsA(); }
+ ))
+ {
+ return Cast(*AssetPipeline);
+ }
+ return nullptr;
+}
+
+UInterchangeMegascansPipeline* FQuixelGltfImporter::GetMegascanPipeline(const TArray& GeneratedPipelines)
+{
+ if (UInterchangePipelineBase* const* const AssetPipeline = GeneratedPipelines.FindByPredicate(
+ [](const UInterchangePipelineBase* Pipeline) { return Pipeline->IsA(); }
+ ))
+ {
+ return Cast(*AssetPipeline);
+ }
+ return nullptr;
+}
+
+void FQuixelGltfImporter::ImportGltfDecalAsset(const FString& SourcePath, const FString& DestinationPath, TFunction&)> OnDone)
+{
+ const UInterchangeSourceData* InSourceData = UInterchangeManager::CreateSourceData(SourcePath);
+
+ TArray GeneratedPipelines;
+ GeneratePipelines(GetPipelinesForSourceData(InSourceData), GeneratedPipelines);
+ UInterchangeGenericAssetsPipeline* AssetPipeline = GetGenericAssetPipeline(GeneratedPipelines);
+ if (AssetPipeline)
+ {
+ AssetPipeline->MeshPipeline->bImportStaticMeshes = false;
+ AssetPipeline->MeshPipeline->bImportSkeletalMeshes = false;
+ AssetPipeline->MaterialPipeline->MaterialImport = EInterchangeMaterialImportOption::ImportAsMaterialInstances;
+ }
+
+ UInterchangeMegascansPipeline* MegascanPipeline = GetMegascanPipeline(GeneratedPipelines);
+ if (MegascanPipeline)
+ {
+ MegascanPipeline->MegascanImportType = EMegascanImportType::Decal;
+ }
+
+ FImportAssetParameters ImportAssetParameters;
+ ImportAssetParameters.bIsAutomated = true;
+ ImportAssetParameters.OverridePipelines = TArray(GeneratedPipelines);
+
+ UInterchangeManager& InterchangeManager = UInterchangeManager::GetInterchangeManager();
+ const UE::Interchange::FAssetImportResultRef Result = InterchangeManager.ImportAssetAsync(DestinationPath, InSourceData, ImportAssetParameters);
+ Result->OnDone(
+ [OnDone, Pipelines = MoveTemp(GeneratedPipelines)](const UE::Interchange::FImportResult& Result)
+ {
+ const UE::Interchange::FImportResult::EStatus Status = Result.GetStatus();
+ if (Status == UE::Interchange::FImportResult::EStatus::Done)
+ {
+ OnDone(Result.GetImportedObjects());
+ }
+ else if (Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ OnDone({});
+ }
+ if (Status == UE::Interchange::FImportResult::EStatus::Done || Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ for (UInterchangePipelineBase* const Pipeline : Pipelines)
+ {
+ Pipeline->RemoveFromRoot();
+ }
+ }
+ }
+ );
+}
+
+void FQuixelGltfImporter::ImportGltfImperfectionAsset(const FString& SourcePath, const FString& DestinationPath, TFunction&)> OnDone)
+{
+ const UInterchangeSourceData* InSourceData = UInterchangeManager::CreateSourceData(SourcePath);
+
+ TArray GeneratedPipelines;
+ GeneratePipelines(GetPipelinesForSourceData(InSourceData), GeneratedPipelines);
+ UInterchangeGenericAssetsPipeline* AssetPipeline = GetGenericAssetPipeline(GeneratedPipelines);
+ if (AssetPipeline)
+ {
+ AssetPipeline->MeshPipeline->bImportStaticMeshes = false;
+ AssetPipeline->MeshPipeline->bImportSkeletalMeshes = false;
+ AssetPipeline->MaterialPipeline->MaterialImport = EInterchangeMaterialImportOption::ImportAsMaterialInstances;
+ }
+
+ UInterchangeMegascansPipeline* MegascanPipeline = GetMegascanPipeline(GeneratedPipelines);
+ if (MegascanPipeline)
+ {
+ MegascanPipeline->MegascanImportType = EMegascanImportType::Imperfection;
+ }
+
+ FImportAssetParameters ImportAssetParameters;
+ ImportAssetParameters.bIsAutomated = true;
+ ImportAssetParameters.OverridePipelines = TArray(GeneratedPipelines);
+
+ UInterchangeManager& InterchangeManager = UInterchangeManager::GetInterchangeManager();
+ const UE::Interchange::FAssetImportResultRef Result = InterchangeManager.ImportAssetAsync(DestinationPath, InSourceData, ImportAssetParameters);
+ Result->OnDone(
+ [OnDone, Pipelines = MoveTemp(GeneratedPipelines)](const UE::Interchange::FImportResult& Result)
+ {
+ const UE::Interchange::FImportResult::EStatus Status = Result.GetStatus();
+ if (Status == UE::Interchange::FImportResult::EStatus::Done)
+ {
+ OnDone(Result.GetImportedObjects());
+ }
+ else if (Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ OnDone({});
+ }
+ if (Status == UE::Interchange::FImportResult::EStatus::Done || Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ for (UInterchangePipelineBase* const Pipeline : Pipelines)
+ {
+ Pipeline->RemoveFromRoot();
+ }
+ }
+ }
+ );
+}
+
+void FQuixelGltfImporter::ImportGltfSurfaceAsset(const FString& SourcePath, const FString& DestinationPath, TFunction&)> OnDone)
+{
+ const UInterchangeSourceData* InSourceData = UInterchangeManager::CreateSourceData(SourcePath);
+
+ TArray GeneratedPipelines;
+ GeneratePipelines(GetPipelinesForSourceData(InSourceData), GeneratedPipelines);
+ UInterchangeGenericAssetsPipeline* AssetPipeline = GetGenericAssetPipeline(GeneratedPipelines);
+ if (AssetPipeline)
+ {
+ AssetPipeline->MeshPipeline->bImportStaticMeshes = false;
+ AssetPipeline->MeshPipeline->bImportSkeletalMeshes = false;
+ AssetPipeline->MaterialPipeline->MaterialImport = EInterchangeMaterialImportOption::ImportAsMaterialInstances;
+ };
+
+ UInterchangeMegascansPipeline* MegascanPipeline = GetMegascanPipeline(GeneratedPipelines);
+ if (MegascanPipeline)
+ {
+ MegascanPipeline->MegascanImportType = EMegascanImportType::Surface;
+ }
+
+ FImportAssetParameters ImportAssetParameters;
+ ImportAssetParameters.bIsAutomated = true;
+ ImportAssetParameters.OverridePipelines = TArray(GeneratedPipelines);
+
+ UInterchangeManager& InterchangeManager = UInterchangeManager::GetInterchangeManager();
+ const UE::Interchange::FAssetImportResultRef Result = InterchangeManager.ImportAssetAsync(DestinationPath, InSourceData, ImportAssetParameters);
+ Result->OnDone(
+ [OnDone, Pipelines = MoveTemp(GeneratedPipelines)](const UE::Interchange::FImportResult& Result)
+ {
+ const UE::Interchange::FImportResult::EStatus Status = Result.GetStatus();
+ if (Status == UE::Interchange::FImportResult::EStatus::Done)
+ {
+ OnDone(Result.GetImportedObjects());
+ }
+ else if (Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ OnDone({});
+ }
+ if (Status == UE::Interchange::FImportResult::EStatus::Done || Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ for (UInterchangePipelineBase* const Pipeline : Pipelines)
+ {
+ Pipeline->RemoveFromRoot();
+ }
+ }
+ }
+ );
+}
+
+void FQuixelGltfImporter::ImportGltfPlantAsset(const FString& SourcePath, const FString& DestinationPath, const bool bBuildNanite, TFunction&)> OnDone)
+{
+ const UInterchangeSourceData* InSourceData = UInterchangeManager::CreateSourceData(SourcePath);
+
+ TArray GeneratedPipelines;
+ GeneratePipelines(GetPipelinesForSourceData(InSourceData), GeneratedPipelines);
+ UInterchangeGenericAssetsPipeline* AssetPipeline = GetGenericAssetPipeline(GeneratedPipelines);
+ if (AssetPipeline)
+ {
+ AssetPipeline->MeshPipeline->bImportStaticMeshes = true;
+ AssetPipeline->MeshPipeline->bImportSkeletalMeshes = false;
+ AssetPipeline->MeshPipeline->bBuildNanite = bBuildNanite;
+ AssetPipeline->CommonMeshesProperties->bRecomputeNormals = true;
+ AssetPipeline->CommonMeshesProperties->bComputeWeightedNormals = true;
+ AssetPipeline->MaterialPipeline->MaterialImport = EInterchangeMaterialImportOption::ImportAsMaterialInstances;
+ AssetPipeline->MaterialPipeline->TexturePipeline->bFlipNormalMapGreenChannel = true;
+
+ #if (ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION >= 5)
+ AssetPipeline->MeshPipeline->bCollision = false;
+ #else
+ AssetPipeline->MeshPipeline->bImportCollision = false;
+ #endif
+ }
+
+ UInterchangeMegascansPipeline* MegascanPipeline = GetMegascanPipeline(GeneratedPipelines);
+ if (MegascanPipeline)
+ {
+ MegascanPipeline->MegascanImportType = EMegascanImportType::Plant;
+ }
+
+ FImportAssetParameters ImportAssetParameters;
+ ImportAssetParameters.bIsAutomated = true;
+ ImportAssetParameters.OverridePipelines = TArray(GeneratedPipelines);
+ ImportAssetParameters.OnAssetsImportDoneNative.BindLambda(
+ [DestinationPath](const TArray& ImportedObjects)
+ {
+ SetupGlobalFoliageActor(DestinationPath);
+ }
+ );
+
+ UInterchangeManager& InterchangeManager = UInterchangeManager::GetInterchangeManager();
+ const UE::Interchange::FAssetImportResultRef Result = InterchangeManager.ImportAssetAsync(DestinationPath, InSourceData, ImportAssetParameters);
+ Result->OnDone(
+ [OnDone, Pipelines = MoveTemp(GeneratedPipelines)](const UE::Interchange::FImportResult& Result)
+ {
+ const UE::Interchange::FImportResult::EStatus Status = Result.GetStatus();
+ if (Status == UE::Interchange::FImportResult::EStatus::Done)
+ {
+ OnDone(Result.GetImportedObjects());
+ }
+ else if (Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ OnDone({});
+ }
+ if (Status == UE::Interchange::FImportResult::EStatus::Done || Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ for (UInterchangePipelineBase* const Pipeline : Pipelines)
+ {
+ Pipeline->RemoveFromRoot();
+ }
+ }
+ }
+ );
+}
+
+void FQuixelGltfImporter::ImportGltf3DAsset(const FString& SourcePath, const FString& DestinationPath, const bool bBuildNanite, TFunction&)> OnDone)
+{
+ const UInterchangeSourceData* InSourceData = UInterchangeManager::CreateSourceData(SourcePath);
+
+ TArray GeneratedPipelines;
+ GeneratePipelines(GetPipelinesForSourceData(InSourceData), GeneratedPipelines);
+ UInterchangeGenericAssetsPipeline* AssetPipeline = GetGenericAssetPipeline(GeneratedPipelines);
+ if (AssetPipeline)
+ {
+ AssetPipeline->MeshPipeline->bImportStaticMeshes = true;
+ AssetPipeline->MeshPipeline->bImportSkeletalMeshes = false;
+ AssetPipeline->MeshPipeline->bBuildNanite = bBuildNanite;
+ AssetPipeline->MaterialPipeline->MaterialImport = EInterchangeMaterialImportOption::ImportAsMaterialInstances;
+ }
+
+ UInterchangeMegascansPipeline* MegascanPipeline = GetMegascanPipeline(GeneratedPipelines);
+ if (MegascanPipeline)
+ {
+ MegascanPipeline->MegascanImportType = EMegascanImportType::Model3D;
+ }
+
+ FImportAssetParameters ImportAssetParameters;
+ ImportAssetParameters.bIsAutomated = true;
+ ImportAssetParameters.OverridePipelines = TArray(GeneratedPipelines);
+
+ UInterchangeManager& InterchangeManager = UInterchangeManager::GetInterchangeManager();
+ const UE::Interchange::FAssetImportResultRef Result = InterchangeManager.ImportAssetAsync(DestinationPath, InSourceData, ImportAssetParameters);
+ Result->OnDone(
+ [OnDone, Pipelines = MoveTemp(GeneratedPipelines)](const UE::Interchange::FImportResult& Result)
+ {
+ const UE::Interchange::FImportResult::EStatus Status = Result.GetStatus();
+ if (Status == UE::Interchange::FImportResult::EStatus::Done)
+ {
+ OnDone(Result.GetImportedObjects());
+ }
+ else if (Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ OnDone({});
+ }
+ if (Status == UE::Interchange::FImportResult::EStatus::Done || Status == UE::Interchange::FImportResult::EStatus::Invalid)
+ {
+ for (UInterchangePipelineBase* const Pipeline : Pipelines)
+ {
+ Pipeline->RemoveFromRoot();
+ }
+ }
+ }
+ );
+}
diff --git a/Plugins/Fab/Source/Fab/Private/Importers/QuixelGLTFImporter.h b/Plugins/Fab/Source/Fab/Private/Importers/QuixelGLTFImporter.h
new file mode 100644
index 0000000..9d271c4
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Importers/QuixelGLTFImporter.h
@@ -0,0 +1,24 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "InterchangeManager.h"
+
+class FQuixelGltfImporter
+{
+private:
+ static void SetupGlobalFoliageActor(const FString& ImportPath);
+
+ static TArray GetPipelinesForSourceData(const UInterchangeSourceData* InSourceData);
+ static void GeneratePipelines(const TArray& OriginalPipelines, TArray& GeneratedPipelines);
+ static class UInterchangeGenericAssetsPipeline* GetGenericAssetPipeline(const TArray& GeneratedPipelines);
+ static class UInterchangeMegascansPipeline* GetMegascanPipeline(const TArray& GeneratedPipelines);
+
+public:
+ static void ImportGltfDecalAsset(const FString& SourcePath, const FString& DestinationPath, TFunction&)> OnDone);
+ static void ImportGltfImperfectionAsset(const FString& SourcePath, const FString& DestinationPath, TFunction&)> OnDone);
+ static void ImportGltfSurfaceAsset(const FString& SourcePath, const FString& DestinationPath, TFunction&)> OnDone);
+ static void ImportGltfPlantAsset(const FString& SourcePath, const FString& DestinationPath, const bool bBuildNanite, TFunction&)> OnDone);
+ static void ImportGltf3DAsset(const FString& SourcePath, const FString& DestinationPath, const bool bBuildNanite, TFunction&)> OnDone);
+};
diff --git a/Plugins/Fab/Source/Fab/Private/NotificationProgressWidget.h b/Plugins/Fab/Source/Fab/Private/NotificationProgressWidget.h
new file mode 100644
index 0000000..995d4a5
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/NotificationProgressWidget.h
@@ -0,0 +1,103 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+
+#include "Styling/SlateStyleMacros.h"
+
+#include "Widgets/DeclarativeSyntaxSupport.h"
+#include "Widgets/SBoxPanel.h"
+#include "Widgets/SCompoundWidget.h"
+#include "Widgets/SOverlay.h"
+#include "Widgets/Images/SImage.h"
+#include "Widgets/Input/SButton.h"
+#include "Widgets/Layout/SBox.h"
+#include "Widgets/Notifications/INotificationWidget.h"
+#include "Widgets/Notifications/SProgressBar.h"
+#include "Widgets/Text/STextBlock.h"
+
+class SNotificationProgressWidget : public SCompoundWidget, public INotificationWidget
+{
+public:
+ SLATE_BEGIN_ARGS(SNotificationProgressWidget)
+ : _ProgressText(FText::FromString("Downloading Content"))
+ , _HasButton(false)
+ {}
+
+ SLATE_ARGUMENT(FText, ProgressText);
+ SLATE_ARGUMENT(bool, HasButton);
+ SLATE_ARGUMENT(FText, ButtonText);
+ SLATE_ARGUMENT(FText, ButtonToolTip);
+ SLATE_EVENT(FOnClicked, OnButtonClicked);
+ SLATE_END_ARGS()
+
+public:
+ void Construct(const FArguments& InArgs)
+ {
+ ChildSlot[
+ SNew(SVerticalBox)
+ + SVerticalBox::Slot()
+ .AutoHeight()
+ [
+ SNew(STextBlock)
+ .Text(InArgs._ProgressText)
+ .AutoWrapText(true)
+ .Font(FAppStyle::Get().GetFontStyle("NotificationList.FontBold"))
+ ]
+ + SVerticalBox::Slot()
+ [
+ SNew(SHorizontalBox)
+ + SHorizontalBox::Slot()
+ .FillWidth(9)
+ [
+ SNew(SOverlay)
+ + SOverlay::Slot()
+ .VAlign(VAlign_Center)
+ [
+ SAssignNew(ProgressBar, SProgressBar)
+ .Percent(100.0f)
+ ]
+ + SOverlay::Slot()
+ .HAlign(HAlign_Center)
+ .VAlign(VAlign_Center)
+ [
+ SAssignNew(PercentText, STextBlock)
+ ]
+ ]
+ + SHorizontalBox::Slot()
+ .AutoWidth()
+ .Padding(4.0f, 0.0f)
+ .HAlign(HAlign_Right)
+ .VAlign(VAlign_Top)
+ [
+ SNew(SButton)
+ .Text(InArgs._ButtonText)
+ .IsEnabled(InArgs._HasButton)
+ .ToolTipText(InArgs._ButtonToolTip)
+ .OnClicked(InArgs._OnButtonClicked)
+ ]
+ ]
+ ];
+ }
+
+ void SetProgressPercent(const float Percent)
+ {
+ ProgressBar->SetPercent(Percent / 100);
+ PercentText->SetText(FText::AsPercent(Percent / 100.0f));
+ PercentText->SetColorAndOpacity(Percent <= 55.0f ? FLinearColor::White : FLinearColor::Black);
+ }
+
+ /** INotificationWidget interface */
+ virtual void OnSetCompletionState(SNotificationItem::ECompletionState) override
+ {}
+
+ virtual TSharedRef AsWidget() override
+ {
+ return AsShared();
+ }
+
+private:
+ TSharedPtr ProgressBar;
+ TSharedPtr PercentText;
+};
diff --git a/Plugins/Fab/Source/Fab/Private/Pipelines/Factories/InterchangeInstancedFoliageTypeFactory.cpp b/Plugins/Fab/Source/Fab/Private/Pipelines/Factories/InterchangeInstancedFoliageTypeFactory.cpp
new file mode 100644
index 0000000..bf35a2a
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Pipelines/Factories/InterchangeInstancedFoliageTypeFactory.cpp
@@ -0,0 +1,123 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "InterchangeInstancedFoliageTypeFactory.h"
+
+#include "FoliageType_InstancedStaticMesh.h"
+#include "InterchangeSourceData.h"
+#include "InterchangeStaticMeshFactoryNode.h"
+
+#include "Nodes/InterchangeBaseNodeContainer.h"
+
+#include "Pipelines/Nodes/InterchangeInstancedFoliageTypeFactoryNode.h"
+
+UClass* UInterchangeInstancedFoliageTypeFactory::GetFactoryClass() const
+{
+ return UFoliageType_InstancedStaticMesh::StaticClass();
+}
+
+UInterchangeFactoryBase::FImportAssetResult UInterchangeInstancedFoliageTypeFactory::BeginImportAsset_GameThread(const FImportAssetObjectParams& Arguments)
+{
+ TRACE_CPUPROFILER_EVENT_SCOPE(UInterchangeInstancedFoliageTypeFactory::BeginImportAsset_GameThread);
+
+ Super::BeginImportAsset_GameThread(Arguments);
+ FImportAssetResult ImportAssetResult;
+
+ UFoliageType_InstancedStaticMesh* ImportedObject = nullptr;
+
+ auto CouldNotCreateDemoObjectLog = [this, &Arguments, &ImportAssetResult](const FString& Info)
+ {
+ UInterchangeResultError_Generic* Message = AddMessage();
+ Message->SourceAssetName = Arguments.SourceData->GetFilename();
+ Message->DestinationAssetName = Arguments.AssetName;
+ Message->AssetType = GetFactoryClass();
+ Message->Text = FText::FromString(
+ FString::Printf(TEXT("UInterchangeInstancedFoliageTypeFactory: Could not create UFoliageType_InstancedStaticMesh asset %s. Reason: %s"), *Arguments.AssetName, *Info)
+ );
+ ImportAssetResult.bIsFactorySkipAsset = true;
+ };
+
+ const UInterchangeInstancedFoliageTypeFactoryNode* DemoObjectFactoryNode = Cast(Arguments.AssetNode);
+ if (!DemoObjectFactoryNode)
+ {
+ CouldNotCreateDemoObjectLog(TEXT("Asset node parameter is null."));
+ return ImportAssetResult;
+ }
+
+ const UClass* InstancedFoliageTypeClass = Arguments.AssetNode->GetObjectClass();
+ if (!InstancedFoliageTypeClass || !InstancedFoliageTypeClass->IsChildOf(UFoliageType_InstancedStaticMesh::StaticClass()))
+ {
+ CouldNotCreateDemoObjectLog(TEXT("Asset node parameter class doesn't derive from UFoliageType_InstancedStaticMesh."));
+ return ImportAssetResult;
+ }
+
+ FSoftObjectPath ReferenceObject;
+ if (Arguments.AssetNode->GetCustomReferenceObject(ReferenceObject))
+ {
+ ImportedObject = Cast(ReferenceObject.TryLoad());
+ }
+ if (!ImportedObject)
+ {
+ ImportedObject = NewObject(Arguments.Parent, InstancedFoliageTypeClass, *Arguments.AssetName, RF_Public | RF_Standalone);
+ }
+
+ if (!ImportedObject)
+ {
+ CouldNotCreateDemoObjectLog(TEXT("UFoliageType_InstancedStaticMeshObject creation fail."));
+ return ImportAssetResult;
+ }
+
+ ImportAssetResult.ImportedObject = ImportedObject;
+
+ return ImportAssetResult;
+}
+
+void UInterchangeInstancedFoliageTypeFactory::SetupObject_GameThread(const FSetupObjectParams& Arguments)
+{
+ TRACE_CPUPROFILER_EVENT_SCOPE(UInterchangeInstancedFoliageTypeFactory::SetupObject_GameThread);
+
+ check(IsInGameThread());
+
+ UFoliageType_InstancedStaticMesh* InstancedFoliageType = Cast(Arguments.ImportedObject);
+ if (!InstancedFoliageType)
+ {
+ return;
+ }
+
+ #if WITH_EDITOR
+ {
+ InstancedFoliageType->PreEditChange(nullptr);
+
+ const UInterchangeFactoryBaseNode* FactoryNode = Arguments.FactoryNode;
+ FactoryNode->ApplyAllCustomAttributeToObject(InstancedFoliageType);
+ if (const UInterchangeInstancedFoliageTypeFactoryNode* InstancedFoliageTypeFactoryNode = Cast(FactoryNode))
+ {
+ if (FString StaticMeshNodeUid; InstancedFoliageTypeFactoryNode->GetCustomStaticMesh(StaticMeshNodeUid))
+ {
+ if (const UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode = Cast(Arguments.NodeContainer->GetNode(StaticMeshNodeUid)))
+ {
+ if (FSoftObjectPath StaticMeshPath; StaticMeshFactoryNode->GetCustomReferenceObject(StaticMeshPath))
+ {
+ InstancedFoliageType->SetStaticMesh(Cast(StaticMeshPath.TryLoad()));
+ }
+ }
+ }
+
+ FVector2f Scale;
+ if (InstancedFoliageTypeFactoryNode->GetCustomScaleX(Scale))
+ {
+ InstancedFoliageType->ScaleX = FFloatInterval(Scale.X, Scale.Y);
+ }
+ if (InstancedFoliageTypeFactoryNode->GetCustomScaleY(Scale))
+ {
+ InstancedFoliageType->ScaleY = FFloatInterval(Scale.X, Scale.Y);
+ }
+ if (InstancedFoliageTypeFactoryNode->GetCustomScaleZ(Scale))
+ {
+ InstancedFoliageType->ScaleZ = FFloatInterval(Scale.X, Scale.Y);
+ }
+ }
+ }
+ #endif
+
+ Super::SetupObject_GameThread(Arguments);
+}
diff --git a/Plugins/Fab/Source/Fab/Private/Pipelines/Factories/InterchangeInstancedFoliageTypeFactory.h b/Plugins/Fab/Source/Fab/Private/Pipelines/Factories/InterchangeInstancedFoliageTypeFactory.h
new file mode 100644
index 0000000..5efcfff
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Pipelines/Factories/InterchangeInstancedFoliageTypeFactory.h
@@ -0,0 +1,21 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+
+#include "InterchangeFactoryBase.h"
+
+#include "InterchangeInstancedFoliageTypeFactory.generated.h"
+
+UCLASS()
+class UInterchangeInstancedFoliageTypeFactory : public UInterchangeFactoryBase
+{
+ GENERATED_BODY()
+
+public:
+ virtual UClass* GetFactoryClass() const override;
+ virtual EInterchangeFactoryAssetType GetFactoryAssetType() override { return EInterchangeFactoryAssetType::Meshes; }
+ virtual FImportAssetResult BeginImportAsset_GameThread(const FImportAssetObjectParams& Arguments) override;
+ virtual void SetupObject_GameThread(const FSetupObjectParams& Arguments) override;
+};
diff --git a/Plugins/Fab/Source/Fab/Private/Pipelines/InterchangeMegascansPipeline.cpp b/Plugins/Fab/Source/Fab/Private/Pipelines/InterchangeMegascansPipeline.cpp
new file mode 100644
index 0000000..56e5852
--- /dev/null
+++ b/Plugins/Fab/Source/Fab/Private/Pipelines/InterchangeMegascansPipeline.cpp
@@ -0,0 +1,678 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "InterchangeMegascansPipeline.h"
+
+#include "FoliageType.h"
+#include "InterchangeMaterialFactoryNode.h"
+#include "InterchangeMaterialInstanceNode.h"
+#include "InterchangeMeshFactoryNode.h"
+#include "InterchangeMeshNode.h"
+#include "InterchangePipelineMeshesUtilities.h"
+#include "InterchangeSceneNode.h"
+#include "InterchangeShaderGraphNode.h"
+#include "InterchangeStaticMeshFactoryNode.h"
+#include "InterchangeStaticMeshLodDataNode.h"
+#include "InterchangeTextureFactoryNode.h"
+
+#include "Dom/JsonObject.h"
+#include "Dom/JsonValue.h"
+
+#include "Materials/MaterialInstanceConstant.h"
+
+#include "Nodes/InterchangeInstancedFoliageTypeFactoryNode.h"
+#include "Nodes/InterchangeUserDefinedAttribute.h"
+
+#include "Serialization/JsonReader.h"
+#include "Serialization/JsonSerializer.h"
+#include "Serialization/JsonTypes.h"
+
+#define MEGASCAN_BASE_KEY TEXT("Megascan")
+
+#define MEGASCAN_MATERIAL_KEY MEGASCAN_BASE_KEY TEXT(".Material")
+
+#define MEGASCAN_MATERIAL_TYPE_KEY MEGASCAN_MATERIAL_KEY TEXT(".Type")
+
+#define MEGASCAN_MATERIAL_BLEND_MODE_KEY MEGASCAN_MATERIAL_KEY TEXT(".BlendMode")
+#define MEGASCAN_MATERIAL_BLEND_MODE_VALUE_KEY MEGASCAN_MATERIAL_BLEND_MODE_KEY TEXT(".Value")
+#define MEGASCAN_MATERIAL_BLEND_MODE_OVERRIDE_KEY MEGASCAN_MATERIAL_BLEND_MODE_KEY TEXT(".Override")
+
+#define MEGASCAN_MATERIAL_DISPLACEMENT_KEY MEGASCAN_MATERIAL_KEY TEXT(".Displacement")
+#define MEGASCAN_MATERIAL_DISPLACEMENT_OVERRIDE_KEY MEGASCAN_MATERIAL_DISPLACEMENT_KEY TEXT(".Override")
+#define MEGASCAN_MATERIAL_DISPLACEMENT_MAGNITUDE_KEY MEGASCAN_MATERIAL_DISPLACEMENT_KEY TEXT(".Magnitude")
+#define MEGASCAN_MATERIAL_DISPLACEMENT_CENTER_KEY MEGASCAN_MATERIAL_DISPLACEMENT_KEY TEXT(".Center")
+
+#define MEGASCAN_MESH_KEY MEGASCAN_BASE_KEY TEXT(".Mesh")
+#define MEGASCAN_MESH_GENERATE_DISTANCE_FIELD_KEY MEGASCAN_MESH_KEY TEXT(".GenerateDistanceField")
+#define MEGASCAN_MESH_AUTO_COMPUTE_LOD_SCREEN_SIZE_KEY MEGASCAN_MESH_KEY TEXT(".AutoComputeLODScreenSize")
+#define MEGASCAN_MESH_NANITE_SETTINGS_KEY MEGASCAN_MESH_KEY TEXT(".Nanite")
+#define MEGASCAN_MESH_NANITE_PRESERVE_AREA_KEY MEGASCAN_MESH_NANITE_SETTINGS_KEY TEXT(".PreserveArea")
+
+UInterchangeMegascansPipeline::UInterchangeMegascansPipeline()
+ : MegascanImportType(EMegascanImportType::Model3D)
+ , MegascansMaterialParentSettings(GetMutableDefault())
+{}
+
+void UInterchangeMegascansPipeline::ExecutePipeline(
+ UInterchangeBaseNodeContainer* NodeContainer,
+ const TArray& SourceDatas
+ #if ENGINE_MAJOR_VERSION== 5 && ENGINE_MINOR_VERSION > 3
+ ,
+ const FString& ContentBasePath
+ #endif
+)
+{
+ Super::ExecutePipeline(
+ NodeContainer,
+ SourceDatas
+ #if ENGINE_MAJOR_VERSION== 5 && ENGINE_MINOR_VERSION > 3
+ ,
+ ContentBasePath
+ #endif
+ );
+
+ BaseNodeContainer = NodeContainer;
+
+ const UInterchangeSourceData* const* GltfSourceData = SourceDatas.FindByPredicate(
+ [](const UInterchangeSourceData* SourceData)
+ {
+ return FPaths::GetExtension(SourceData->GetFilename()) == "gltf";
+ }
+ );
+ if (!GltfSourceData)
+ {
+ return;
+ }
+
+ if (!LoadGltfSource((*GltfSourceData)->GetFilename()))
+ {
+ return;
+ }
+
+ if (const TSharedPtr* GltfExtras; GltfJson->TryGetObjectField(TEXT("extras"), GltfExtras))
+ {
+ GltfExtras->Get()->TryGetNumberField(TEXT("tier"), reinterpret_cast(MegascanAssetTier));
+ }
+
+ TextureFactoryNodes = GetNodesOfType();
+ StaticMeshFactoryNodes = GetNodesOfType();
+ MaterialInstanceFactoryNodes = GetNodesOfType();
+
+ ForEachGltfTexture(
+ [&](const FString& TextureName, const TSharedPtr& Texture)
+ {
+ UInterchangeTextureFactoryNode* TextureFactoryNode = FindTextureFactoryNodeByName(TextureName);
+ if (TextureFactoryNode == nullptr)
+ {
+ return;
+ }
+
+ if (const TSharedPtr* TextureExtras; Texture->TryGetObjectField(TEXT("extras"), TextureExtras))
+ {
+ SetupTextureParams(TextureFactoryNode, *TextureExtras);
+ }
+ }
+ );
+
+ ForEachGltfMaterial(
+ [&](const FString& MaterialName, const TSharedPtr& Material)
+ {
+ FString MaterialType;
+ if (MegascanImportType == EMegascanImportType::Plant && MaterialName.EndsWith("_Billboard", ESearchCase::CaseSensitive))
+ MaterialType = "billboard";
+
+ UInterchangeMaterialInstanceFactoryNode* MaterialInstanceFactoryNode = FindMaterialInstanceFactoryNodeByName(MaterialName);
+ if (MaterialInstanceFactoryNode == nullptr)
+ {
+ return;
+ }
+
+ SetupMaterial(MaterialInstanceFactoryNode);
+ if (const TSharedPtr* MaterialExtras; Material->TryGetObjectField(TEXT("extras"), MaterialExtras))
+ {
+ MaterialExtras->Get()->TryGetStringField(TEXT("type"), MaterialType);
+ SetupMaterialParams(MaterialInstanceFactoryNode, *MaterialExtras);
+ }
+ SetupMaterialParents(MaterialInstanceFactoryNode, MaterialType);
+ }
+ );
+
+ ForEachGltfMesh(
+ [&](const FString& MeshName, const TSharedPtr& Mesh)
+ {
+ UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode = FindStaticMeshFactoryNodeByName(MeshName);
+ if (StaticMeshFactoryNode == nullptr)
+ {
+ return;
+ }
+
+ SetupStaticMesh(StaticMeshFactoryNode);
+ if (const TSharedPtr* MeshExtras; Mesh->TryGetObjectField(TEXT("extras"), MeshExtras))
+ {
+ SetupStaticMeshParams(StaticMeshFactoryNode, *MeshExtras);
+ }
+ }
+ );
+
+ StaticMeshFactoryNodes = GetNodesOfType();
+ for (UInterchangeStaticMeshFactoryNode* MeshFactoryNode : StaticMeshFactoryNodes)
+ {
+ UE::Interchange::MeshesUtilities::ReorderSlotMaterialDependencies(*MeshFactoryNode, *BaseNodeContainer);
+ }
+}
+
+void UInterchangeMegascansPipeline::ExecutePostFactoryPipeline(
+ const UInterchangeBaseNodeContainer* NodeContainer,
+ const FString& NodeKey,
+ UObject* CreatedAsset,
+ const bool bIsAReimport
+)
+{
+ Super::ExecutePostFactoryPipeline(NodeContainer, NodeKey, CreatedAsset, bIsAReimport);
+
+ if (MegascanImportType == EMegascanImportType::Plant && MegascanAssetTier > EMegascanImportTier::Raw)
+ {
+ if (UStaticMesh* ImportedMesh = Cast(CreatedAsset))
+ {
+ const float BillboardScreenSizes[] = {
+ 0.025f,
+ 0.085f,
+ 0.125f
+ };
+ int32 Index = 0;
+ for (const int32 MaxIndex = ImportedMesh->GetNumSourceModels(); Index < MaxIndex; ++Index)
+ {
+ ImportedMesh->GetSourceModel(Index).ScreenSize = FMath::Pow(0.75f, Index);
+ }
+ if (const int ScreenSizeIndex = static_cast(MegascanAssetTier) - 1; ScreenSizeIndex >= 0)
+ ImportedMesh->GetSourceModel(Index - 1).ScreenSize = BillboardScreenSizes[ScreenSizeIndex];
+ }
+ }
+
+ if (bVirtualTexturesImported)
+ {
+ return;
+ }
+
+ if (const UTexture* ImportedTexture = Cast(CreatedAsset))
+ {
+ bVirtualTexturesImported |= ImportedTexture->VirtualTextureStreaming;
+ }
+
+ if (bVirtualTexturesImported)
+ {
+ for (UInterchangeMaterialInstanceFactoryNode* MaterialInstanceFactoryNode : MaterialInstanceFactoryNodes)
+ {
+ UpdateParentMaterial(MaterialInstanceFactoryNode, true, true);
+ }
+ }
+}
+
+bool UInterchangeMegascansPipeline::LoadGltfSource(const FString& SourceFile)
+{
+ if (FString GltfFileData; FFileHelper::LoadFileToString(GltfFileData, *SourceFile))
+ {
+ GltfJson = MakeShareable(new FJsonObject);
+ return FJsonSerializer::Deserialize(TJsonReaderFactory::Create(GltfFileData), GltfJson);
+ }
+ return false;
+}
+
+void UInterchangeMegascansPipeline::ForEachGltfMaterial(const TFunction&)>& Callback) const
+{
+ if (GltfJson == nullptr) { return; }
+ const TArray>& Materials = GltfJson->GetArrayField(TEXT("materials"));
+ for (const TSharedPtr& Material : Materials)
+ {
+ const TSharedPtr& MaterialObject = Material->AsObject();
+ const FString MaterialName = MaterialObject->GetStringField(TEXT("name"));
+ Callback(MaterialName, MaterialObject);
+ }
+}
+
+void UInterchangeMegascansPipeline::ForEachGltfTexture(const TFunction&)>& Callback)
+{
+ if (GltfJson == nullptr) { return; }
+ const TArray>& Images = GltfJson->GetArrayField(TEXT("images"));
+ for (const TSharedPtr& Image : Images)
+ {
+ const TSharedPtr& ImageObject = Image->AsObject();
+ const FString ImageName = ImageObject->GetStringField(TEXT("name"));
+ Callback(ImageName, ImageObject);
+ }
+}
+
+void UInterchangeMegascansPipeline::ForEachGltfMesh(const TFunction&)>& Callback)
+{
+ if (GltfJson == nullptr) { return; }
+ const TArray>& MeshNodes = GltfJson->GetArrayField(TEXT("nodes"));
+ for (const TSharedPtr& MeshNode : MeshNodes)
+ {
+ const TSharedPtr& MeshNodeObject = MeshNode->AsObject();
+ const FString MeshNodeName = MeshNodeObject->GetStringField(TEXT("name"));
+ Callback(MeshNodeName, MeshNodeObject);
+ }
+}
+
+UInterchangeTextureFactoryNode* UInterchangeMegascansPipeline::FindTextureFactoryNodeByName(const FString& DisplayName) const
+{
+ UInterchangeTextureFactoryNode* const* const FoundNode = TextureFactoryNodes.FindByPredicate(
+ [&DisplayName](const UInterchangeTextureFactoryNode* Node)
+ {
+ return Node->GetDisplayLabel() == DisplayName;
+ }
+ );
+ return FoundNode ? *FoundNode : nullptr;
+}
+
+UInterchangeStaticMeshFactoryNode* UInterchangeMegascansPipeline::FindStaticMeshFactoryNodeByName(const FString& DisplayName) const
+{
+ UInterchangeStaticMeshFactoryNode* const* const FoundNode = StaticMeshFactoryNodes.FindByPredicate(
+ [&DisplayName](const UInterchangeStaticMeshFactoryNode* Node)
+ {
+ return Node->GetDisplayLabel() == DisplayName;
+ }
+ );
+ return FoundNode ? *FoundNode : nullptr;
+}
+
+UInterchangeMaterialInstanceFactoryNode* UInterchangeMegascansPipeline::FindMaterialInstanceFactoryNodeByName(const FString& DisplayName) const
+{
+ UInterchangeMaterialInstanceFactoryNode* const* const FoundNode = MaterialInstanceFactoryNodes.FindByPredicate(
+ [&DisplayName](const UInterchangeMaterialInstanceFactoryNode* Node)
+ {
+ return Node->GetDisplayLabel() == DisplayName;
+ }
+ );
+ return FoundNode ? *FoundNode : nullptr;
+}
+
+const TSharedPtr* UInterchangeMegascansPipeline::GetMaterialAtIndex(uint32 Index) const
+{
+ TSharedPtr* Material = nullptr;
+ const TArray> Materials = GltfJson->GetArrayField(TEXT("materials"));
+ if (Materials.IsValidIndex(Index))
+ {
+ Materials[Index]->TryGetObject(Material);
+ }
+ return Material;
+}
+
+EMegascanMaterialType UInterchangeMegascansPipeline::GetMegascanMaterialType(const UInterchangeMaterialInstanceFactoryNode* MaterialInstanceFactoryNode) const
+{
+ int32 MaterialType = 0;
+ MaterialInstanceFactoryNode->GetInt32Attribute(MEGASCAN_MATERIAL_TYPE_KEY, MaterialType);
+
+ return static_cast(MaterialType);
+}
+
+bool UInterchangeMegascansPipeline::SetMegascanMaterialType(UInterchangeMaterialInstanceFactoryNode* MaterialInstanceFactoryNode, EMegascanMaterialType MaterialType) const
+{
+ return MaterialInstanceFactoryNode->AddInt32Attribute(MEGASCAN_MATERIAL_TYPE_KEY, static_cast(MaterialType));
+}
+
+bool UInterchangeMegascansPipeline::UpdateParentMaterial(UInterchangeMaterialInstanceFactoryNode* MaterialInstanceFactoryNode, bool bVTMaterial, bool bUpdateReferencedObject)
+{
+ const EMegascanMaterialType MaterialType = GetMegascanMaterialType(MaterialInstanceFactoryNode);
+ if (MegascansMaterialParentSettings)
+ {
+ if (const FMegascanMaterialPair* const ParentMaterialPair = MegascansMaterialParentSettings->MaterialParents.Find(MaterialType))
+ {
+ const TSoftObjectPtr ParentMaterial = bVTMaterial ? ParentMaterialPair->VTMaterial : ParentMaterialPair->StandardMaterial;
+
+ if (bUpdateReferencedObject)
+ {
+ if (FSoftObjectPath MaterialInstancePath; MaterialInstanceFactoryNode->GetCustomReferenceObject(MaterialInstancePath))
+ {
+ UObject* ImportedObject = MaterialInstancePath.TryLoad();
+ if (UMaterialInstanceConstant* Material = Cast(ImportedObject))
+ {
+ Material->SetParentEditorOnly(ParentMaterial.LoadSynchronous());
+ }
+ }
+ }
+
+ return MaterialInstanceFactoryNode->SetCustomParent(ParentMaterial.ToString());
+ }
+ }
+ return false;
+}
+
+FString UInterchangeMegascansPipeline::GetStaticMeshLodDataNodeUid(const UInterchangeMeshFactoryNode* MeshFactoryNode, int32 LodIndex)
+{
+ const FString MeshFactoryUid = MeshFactoryNode->GetUniqueID();
+ const FString LODDataPrefix = TEXT("\\LodData") + (LodIndex > 0 ? FString::FromInt(LodIndex) : TEXT(""));
+ return LODDataPrefix + MeshFactoryUid;
+}
+
+FString UInterchangeMegascansPipeline::GetStaticMeshLodDataNodeDisplayName(int32 LodIndex)
+{
+ return TEXT("LodData") + FString::FromInt(LodIndex);
+}
+
+void UInterchangeMegascansPipeline::SetupMeshLod(UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode, const UInterchangeSceneNode* SceneNode, int32 LodIndex)
+{
+ const FString StaticMeshFactoryUid = StaticMeshFactoryNode->GetUniqueID();
+ const FString StaticMeshLodDataNodeUid = GetStaticMeshLodDataNodeUid(StaticMeshFactoryNode, LodIndex);
+
+ UInterchangeStaticMeshLodDataNode* StaticMeshLodDataNode = Cast(BaseNodeContainer->GetFactoryNode(StaticMeshLodDataNodeUid));
+ if (StaticMeshLodDataNode == nullptr)
+ {
+ StaticMeshLodDataNode = NewObject(BaseNodeContainer, NAME_None);
+ StaticMeshLodDataNode->InitializeNode(StaticMeshLodDataNodeUid, GetStaticMeshLodDataNodeDisplayName(LodIndex), EInterchangeNodeContainerType::FactoryData);
+ BaseNodeContainer->AddNode(StaticMeshLodDataNode);
+ BaseNodeContainer->SetNodeParentUid(StaticMeshLodDataNodeUid, StaticMeshFactoryUid);
+ StaticMeshFactoryNode->AddLodDataUniqueId(StaticMeshLodDataNodeUid);
+ }
+
+ const FString SceneNodeUid = SceneNode->GetUniqueID();
+
+ // UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(SceneNode, StaticMeshFactoryNode, true);
+ StaticMeshFactoryNode->AddTargetNodeUid(SceneNodeUid);
+ StaticMeshLodDataNode->AddMeshUid(SceneNodeUid);
+ SceneNode->AddTargetNodeUid(StaticMeshFactoryUid);
+
+ FString MeshNodeUid;
+ SceneNode->GetCustomAssetInstanceUid(MeshNodeUid);
+ if (const UInterchangeMeshNode* MeshNode = Cast