diff --git a/.gitignore b/.gitignore
index b7e11e554..30c2caeb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,6 @@ Source/*.Gen.*
Source/*.csproj
/Package_*/
!Source/Engine/Debug
-/Source/Platforms/Editor/Linux/Mono/etc/mono/registry
PackageEditor_Cert.command
PackageEditor_Cert.bat
PackagePlatforms_Cert.bat
diff --git a/Content/Editor/Fonts/NotoSansSC-Regular.flax b/Content/Editor/Fonts/NotoSansSC-Regular.flax
new file mode 100644
index 000000000..39964e6c5
--- /dev/null
+++ b/Content/Editor/Fonts/NotoSansSC-Regular.flax
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:19fa43eb7b31ee3936348b1f271c464c79d7020a21d33e3cdbe54f98c3b14304
+size 10560899
diff --git a/Content/Shaders/ColorGrading.flax b/Content/Shaders/ColorGrading.flax
index dae561361..ee620d1a2 100644
--- a/Content/Shaders/ColorGrading.flax
+++ b/Content/Shaders/ColorGrading.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ce60152b7076175eb50e07ad9895eacbfb4a9ed1365c1507266b21fcd3339958
-size 10925
+oid sha256:58bf83f2b334cd28a2db8a2c60a17c56f813edc9050a2d006456f8479cd05d13
+size 10629
diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings
index ff396d824..80cf8fa1e 100644
--- a/Flax.sln.DotSettings
+++ b/Flax.sln.DotSettings
@@ -73,8 +73,12 @@
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ AI
LO
+ RPC
+ SDK
VS
+ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
diff --git a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs
index 1be5330e7..76edde777 100644
--- a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs
+++ b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs
@@ -81,7 +81,7 @@ namespace FlaxEditor.Content.Create
switch (_options.Template)
{
case Templates.Empty:
- return Editor.CreateAsset(Editor.NewAssetType.ParticleEmitter, ResultUrl);
+ return Editor.CreateAsset("ParticleEmitter", ResultUrl);
case Templates.ConstantBurst:
templateName = "Constant Burst";
break;
diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs
index 259be104b..755a3a1ac 100644
--- a/Source/Editor/Content/GUI/ContentView.cs
+++ b/Source/Editor/Content/GUI/ContentView.cs
@@ -56,6 +56,9 @@ namespace FlaxEditor.Content.GUI
private float _viewScale = 1.0f;
private ContentViewType _viewType = ContentViewType.Tiles;
+ private bool _isRubberBandSpanning = false;
+ private Float2 _mousePresslocation;
+ private Rectangle _rubberBandRectangle;
#region External Events
@@ -600,6 +603,12 @@ namespace FlaxEditor.Content.GUI
{
Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
}
+
+ if (_isRubberBandSpanning)
+ {
+ Render2D.FillRectangle(_rubberBandRectangle, Color.Orange * 0.4f);
+ Render2D.DrawRectangle(_rubberBandRectangle, Color.Orange);
+ }
}
///
@@ -607,9 +616,54 @@ namespace FlaxEditor.Content.GUI
{
if (base.OnMouseDown(location, button))
return true;
+
+ if (button == MouseButton.Left)
+ {
+ _mousePresslocation = location;
+ _rubberBandRectangle = new Rectangle(_mousePresslocation, 0, 0);
+ _isRubberBandSpanning = true;
+ StartMouseCapture();
+ }
return AutoFocus && Focus(this);
}
+ ///
+ public override void OnMouseMove(Float2 location)
+ {
+ if (_isRubberBandSpanning)
+ {
+ _rubberBandRectangle.Width = location.X - _mousePresslocation.X;
+ _rubberBandRectangle.Height = location.Y - _mousePresslocation.Y;
+ }
+
+ base.OnMouseMove(location);
+ }
+
+ ///
+ public override bool OnMouseUp(Float2 location, MouseButton button)
+ {
+ if (_isRubberBandSpanning)
+ {
+ _isRubberBandSpanning = false;
+ EndMouseCapture();
+ if (_rubberBandRectangle.Width < 0 || _rubberBandRectangle.Height < 0)
+ {
+ // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner
+ var size = _rubberBandRectangle.Size;
+ _rubberBandRectangle.X = Mathf.Min(_rubberBandRectangle.X, _rubberBandRectangle.X + _rubberBandRectangle.Width);
+ _rubberBandRectangle.Y = Mathf.Min(_rubberBandRectangle.Y, _rubberBandRectangle.Y + _rubberBandRectangle.Height);
+ size.X = Mathf.Abs(size.X);
+ size.Y = Mathf.Abs(size.Y);
+ _rubberBandRectangle.Size = size;
+ }
+ var itemsInRectangle = _items.Where(t => _rubberBandRectangle.Intersects(t.Bounds)).ToList();
+ Select(itemsInRectangle, Input.GetKey(KeyboardKeys.Shift) || Input.GetKey(KeyboardKeys.Control));
+ return true;
+ }
+
+ return base.OnMouseUp(location, button);
+ }
+
///
public override bool OnMouseWheel(Float2 location, float delta)
{
diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs
index 484efb1d9..fac782cbd 100644
--- a/Source/Editor/Content/Import/ModelImportEntry.cs
+++ b/Source/Editor/Content/Import/ModelImportEntry.cs
@@ -16,6 +16,7 @@ namespace FlaxEngine.Tools
private bool ShowModel => Type == ModelType.Model || Type == ModelType.Prefab;
private bool ShowSkinnedModel => Type == ModelType.SkinnedModel || Type == ModelType.Prefab;
private bool ShowAnimation => Type == ModelType.Animation || Type == ModelType.Prefab;
+ private bool ShowRootMotion => ShowAnimation && RootMotion != RootMotionMode.None;
private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
diff --git a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs
index f714ddbeb..7efc02368 100644
--- a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs
+++ b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraphFunction, outputPath))
+ if (Editor.CreateAsset("AnimationGraphFunction", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs
index 3e6c35c6d..20d3c5a2c 100644
--- a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs
+++ b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraph, outputPath))
+ if (Editor.CreateAsset("AnimationGraph", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/AnimationProxy.cs b/Source/Editor/Content/Proxy/AnimationProxy.cs
index 2cf46d3a3..6636fccb8 100644
--- a/Source/Editor/Content/Proxy/AnimationProxy.cs
+++ b/Source/Editor/Content/Proxy/AnimationProxy.cs
@@ -47,7 +47,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.Animation, outputPath))
+ if (Editor.CreateAsset("Animation", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
index 33ad0862f..234dfaf7d 100644
--- a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
+++ b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
@@ -47,7 +47,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath))
+ if (Editor.CreateAsset("BehaviorTree", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
index 55e8c6327..df26dca75 100644
--- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs
+++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
@@ -71,7 +71,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.CollisionData, outputPath))
+ if (Editor.CreateAsset("CollisionData", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs
index ab59f11f3..ca012f70a 100644
--- a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.MaterialFunction, outputPath))
+ if (Editor.CreateAsset("MaterialFunction", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
index 331ff81c3..cd245b149 100644
--- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
@@ -43,7 +43,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.MaterialInstance, outputPath))
+ if (Editor.CreateAsset("MaterialInstance", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs
index a7fcfecc8..f7db2c83d 100644
--- a/Source/Editor/Content/Proxy/MaterialProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialProxy.cs
@@ -44,7 +44,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.Material, outputPath))
+ if (Editor.CreateAsset("Material", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs
index 3a2ed749f..aaf9445e2 100644
--- a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs
+++ b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.ParticleEmitterFunction, outputPath))
+ if (Editor.CreateAsset("ParticleEmitterFunction", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs
index 047853f0b..74f391513 100644
--- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs
+++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs
@@ -75,7 +75,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.ParticleSystem, outputPath))
+ if (Editor.CreateAsset("ParticleSystem", outputPath))
throw new Exception("Failed to create new asset.");
}
diff --git a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs
index fe31f0e34..5f7fa800a 100644
--- a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs
+++ b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs
@@ -69,7 +69,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.SceneAnimation, outputPath))
+ if (Editor.CreateAsset("SceneAnimation", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Content/Proxy/ScriptProxy.cs b/Source/Editor/Content/Proxy/ScriptProxy.cs
index f688f37df..d728dcc38 100644
--- a/Source/Editor/Content/Proxy/ScriptProxy.cs
+++ b/Source/Editor/Content/Proxy/ScriptProxy.cs
@@ -69,8 +69,7 @@ namespace FlaxEditor.Content
///
public override bool IsFileNameValid(string filename)
{
- // Scripts cannot start with digit.
- if (Char.IsDigit(filename[0]))
+ if (char.IsDigit(filename[0]))
return false;
if (filename.Equals("Script"))
return false;
diff --git a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs
index f300a4c61..560df0a7c 100644
--- a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs
+++ b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
///
public override void Create(string outputPath, object arg)
{
- if (Editor.CreateAsset(Editor.NewAssetType.SkeletonMask, outputPath))
+ if (Editor.CreateAsset("SkeletonMask", outputPath))
throw new Exception("Failed to create new asset.");
}
}
diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp
index c9317c683..f7aa5fedc 100644
--- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp
@@ -5,11 +5,463 @@
#include "WindowsPlatformTools.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/Windows/WindowsPlatformSettings.h"
+#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Editor/Utilities/EditorUtilities.h"
#include "Engine/Graphics/Textures/TextureData.h"
+#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
+#include
+
+#define MSDOS_SIGNATURE 0x5A4D
+#define PE_SIGNATURE 0x00004550
+#define PE_32BIT_SIGNATURE 0x10B
+#define PE_64BIT_SIGNATURE 0x20B
+#define PE_SECTION_UNINITIALIZED_DATA 0x00000080
+#define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2
+#define PE_IMAGE_RT_ICON 3
+
+///
+/// MS-DOS header found at the beginning in a PE format file.
+///
+struct MSDOSHeader
+{
+ uint16 signature;
+ uint16 lastSize;
+ uint16 numBlocks;
+ uint16 numReloc;
+ uint16 hdrSize;
+ uint16 minAlloc;
+ uint16 maxAlloc;
+ uint16 ss;
+ uint16 sp;
+ uint16 checksum;
+ uint16 ip;
+ uint16 cs;
+ uint16 relocPos;
+ uint16 numOverlay;
+ uint16 reserved1[4];
+ uint16 oemId;
+ uint16 oemInfo;
+ uint16 reserved2[10];
+ uint32 lfanew;
+};
+
+///
+/// COFF header found in a PE format file.
+///
+struct COFFHeader
+{
+ uint16 machine;
+ uint16 numSections;
+ uint32 timeDateStamp;
+ uint32 ptrSymbolTable;
+ uint32 numSymbols;
+ uint16 sizeOptHeader;
+ uint16 characteristics;
+};
+
+///
+/// Contains address and size of data areas in a PE image.
+///
+struct PEDataDirectory
+{
+ uint32 virtualAddress;
+ uint32 size;
+};
+
+///
+/// Optional header in a 32-bit PE format file.
+///
+struct PEOptionalHeader32
+{
+ uint16 signature;
+ uint8 majorLinkerVersion;
+ uint8 minorLinkerVersion;
+ uint32 sizeCode;
+ uint32 sizeInitializedData;
+ uint32 sizeUninitializedData;
+ uint32 addressEntryPoint;
+ uint32 baseCode;
+ uint32 baseData;
+ uint32 baseImage;
+ uint32 alignmentSection;
+ uint32 alignmentFile;
+ uint16 majorOSVersion;
+ uint16 minorOSVersion;
+ uint16 majorImageVersion;
+ uint16 minorImageVersion;
+ uint16 majorSubsystemVersion;
+ uint16 minorSubsystemVersion;
+ uint32 reserved;
+ uint32 sizeImage;
+ uint32 sizeHeaders;
+ uint32 checksum;
+ uint16 subsystem;
+ uint16 characteristics;
+ uint32 sizeStackReserve;
+ uint32 sizeStackCommit;
+ uint32 sizeHeapReserve;
+ uint32 sizeHeapCommit;
+ uint32 loaderFlags;
+ uint32 NumRvaAndSizes;
+ PEDataDirectory dataDirectory[16];
+};
+
+///
+/// Optional header in a 64-bit PE format file.
+///
+struct PEOptionalHeader64
+{
+ uint16 signature;
+ uint8 majorLinkerVersion;
+ uint8 minorLinkerVersion;
+ uint32 sizeCode;
+ uint32 sizeInitializedData;
+ uint32 sizeUninitializedData;
+ uint32 addressEntryPoint;
+ uint32 baseCode;
+ uint64 baseImage;
+ uint32 alignmentSection;
+ uint32 alignmentFile;
+ uint16 majorOSVersion;
+ uint16 minorOSVersion;
+ uint16 majorImageVersion;
+ uint16 minorImageVersion;
+ uint16 majorSubsystemVersion;
+ uint16 minorSubsystemVersion;
+ uint32 reserved;
+ uint32 sizeImage;
+ uint32 sizeHeaders;
+ uint32 checksum;
+ uint16 subsystem;
+ uint16 characteristics;
+ uint64 sizeStackReserve;
+ uint64 sizeStackCommit;
+ uint64 sizeHeapReserve;
+ uint64 sizeHeapCommit;
+ uint32 loaderFlags;
+ uint32 NumRvaAndSizes;
+ PEDataDirectory dataDirectory[16];
+};
+
+///
+/// A section header in a PE format file.
+///
+struct PESectionHeader
+{
+ char name[8];
+ uint32 virtualSize;
+ uint32 relativeVirtualAddress;
+ uint32 physicalSize;
+ uint32 physicalAddress;
+ uint8 deprecated[12];
+ uint32 flags;
+};
+
+///
+/// A resource table header within a .rsrc section in a PE format file.
+///
+struct PEImageResourceDirectory
+{
+ uint32 flags;
+ uint32 timeDateStamp;
+ uint16 majorVersion;
+ uint16 minorVersion;
+ uint16 numNamedEntries;
+ uint16 numIdEntries;
+};
+
+///
+/// A single entry in a resource table within a .rsrc section in a PE format file.
+///
+struct PEImageResourceEntry
+{
+ uint32 type;
+ uint32 offsetDirectory : 31;
+ uint32 isDirectory : 1;
+};
+
+///
+/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file.
+///
+struct PEImageResourceEntryData
+{
+ uint32 offsetData;
+ uint32 size;
+ uint32 codePage;
+ uint32 resourceHandle;
+};
+
+///
+/// Header used in icon file format.
+///
+struct IconHeader
+{
+ uint32 size;
+ int32 width;
+ int32 height;
+ uint16 planes;
+ uint16 bitCount;
+ uint32 compression;
+ uint32 sizeImage;
+ int32 xPelsPerMeter;
+ int32 yPelsPerMeter;
+ uint32 clrUsed;
+ uint32 clrImportant;
+};
+
+void UpdateIconData(uint8* iconData, const TextureData* icon)
+{
+ IconHeader* iconHeader = (IconHeader*)iconData;
+ if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32)
+ {
+ // Unsupported format
+ return;
+ }
+ uint8* iconPixels = iconData + sizeof(IconHeader);
+ const uint32 width = iconHeader->width;
+ const uint32 height = iconHeader->height / 2;
+
+ // Try to pick a proper mip (require the same size)
+ int32 srcPixelsMip = 0;
+ const int32 mipLevels = icon->GetMipLevels();
+ for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
+ {
+ const uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex);
+ const uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex);
+ if (width == iconWidth && height == iconHeight)
+ {
+ srcPixelsMip = mipIndex;
+ break;
+ }
+ }
+ const TextureMipData* srcPixels = icon->GetData(0, srcPixelsMip);
+ const Color32* srcPixelsData = (Color32*)srcPixels->Data.Get();
+ const Int2 srcPixelsSize(Math::Max(1, icon->Width >> srcPixelsMip), Math::Max(1, icon->Height >> srcPixelsMip));
+ const auto sampler = TextureTool::GetSampler(icon->Format);
+ ASSERT_LOW_LAYER(sampler);
+
+ // Write colors
+ uint32* colorData = (uint32*)iconPixels;
+ uint32 idx = 0;
+ for (int32 y = (int32)height - 1; y >= 0; y--)
+ {
+ float v = (float)y / height;
+ for (uint32 x = 0; x < width; x++)
+ {
+ float u = (float)x / width;
+ const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch);
+ colorData[idx++] = Color32(c).GetAsBGRA();
+ }
+ }
+
+ // Write AND mask
+ uint32 colorDataSize = width * height * sizeof(uint32);
+ uint8* maskData = iconPixels + colorDataSize;
+ uint32 numPackedPixels = width / 8; // One per bit in byte
+ for (int32 y = (int32)height - 1; y >= 0; y--)
+ {
+ uint8 mask = 0;
+ float v = (float)y / height;
+ for (uint32 packedX = 0; packedX < numPackedPixels; packedX++)
+ {
+ for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++)
+ {
+ uint32 x = packedX * 8 + pixelIdx;
+ float u = (float)x / width;
+ const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch);
+ if (c.A < 0.25f)
+ mask |= 1 << (7 - pixelIdx);
+ }
+ *maskData = mask;
+ maskData++;
+ }
+ }
+}
+
+void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8)
+{
+ uint32 numEntries = current->numIdEntries; // Not supporting name entries
+ PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1);
+ for (uint32 i = 0; i < numEntries; i++)
+ {
+ // Only at root does the type identify resource type
+ if (base == current && entries[i].type != PE_IMAGE_RT_ICON)
+ continue;
+
+ if (entries[i].isDirectory)
+ {
+ PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory);
+ SetIconData(base, child, imageData, sectionAddress, iconRGBA8);
+ }
+ else
+ {
+ PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory);
+ uint8* iconData = imageData + (data->offsetData - sectionAddress);
+ UpdateIconData(iconData, iconRGBA8);
+ }
+ }
+}
+
+bool UpdateExeIcon(const String& path, const TextureData& icon)
+{
+ if (!FileSystem::FileExists(path))
+ {
+ LOG(Warning, "Missing file");
+ return true;
+ }
+ if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0)
+ {
+ LOG(Warning, "Inalid icon data");
+ return true;
+ }
+
+ // Ensure that image format can be sampled
+ const TextureData* iconRGBA8 = &icon;
+ TextureData tmpData1;
+ //if (icon.Format != PixelFormat::R8G8B8A8_UNorm)
+ if (TextureTool::GetSampler(icon.Format) == nullptr)
+ {
+ if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm))
+ {
+ LOG(Warning, "Failed convert icon data.");
+ return true;
+ }
+ iconRGBA8 = &tmpData1;
+ }
+
+ // Use fixed-size input icon image
+ TextureData tmpData2;
+ if (iconRGBA8->Width != 256 || iconRGBA8->Height != 256)
+ {
+ if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256))
+ {
+ LOG(Warning, "Failed resize icon data.");
+ return true;
+ }
+ iconRGBA8 = &tmpData2;
+ }
+
+ // A PE file is structured as such:
+ // - MSDOS Header
+ // - PE Signature
+ // - COFF Header
+ // - PE Optional Header
+ // - One or multiple sections
+ // - .code
+ // - .data
+ // - ...
+ // - .rsrc
+ // - icon/cursor/etc data
+
+ std::fstream stream;
+#if PLATFORM_WINDOWS
+ stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary);
+#else
+ StringAsANSI<> pathAnsi(path.Get());
+ stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary);
+#endif
+ if (!stream.is_open())
+ {
+ LOG(Warning, "Cannot open file");
+ return true;
+ }
+
+ // First check magic number to ensure file is even an executable
+ uint16 magicNum;
+ stream.read((char*)&magicNum, sizeof(magicNum));
+ if (magicNum != MSDOS_SIGNATURE)
+ {
+ LOG(Warning, "Provided file is not a valid executable.");
+ return true;
+ }
+
+ // Read the MSDOS header and skip over it
+ stream.seekg(0);
+ MSDOSHeader msdosHeader;
+ stream.read((char*)&msdosHeader, sizeof(MSDOSHeader));
+
+ // Read PE signature
+ stream.seekg(msdosHeader.lfanew);
+ uint32 peSignature;
+ stream.read((char*)&peSignature, sizeof(peSignature));
+ if (peSignature != PE_SIGNATURE)
+ {
+ LOG(Warning, "Provided file is not in PE format.");
+ return true;
+ }
+
+ // Read COFF header
+ COFFHeader coffHeader;
+ stream.read((char*)&coffHeader, sizeof(COFFHeader));
+ if (coffHeader.sizeOptHeader == 0)
+ {
+ LOG(Warning, "Provided file is not a valid executable.");
+ return true;
+ }
+ uint32 sectionHeadersCount = coffHeader.numSections;
+
+ // Read optional header
+ auto optionalHeaderPos = stream.tellg();
+ uint16 optionalHeaderSignature;
+ stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature));
+ PEDataDirectory* dataDirectory = nullptr;
+ stream.seekg(optionalHeaderPos);
+ if (optionalHeaderSignature == PE_32BIT_SIGNATURE)
+ {
+ PEOptionalHeader32 optionalHeader;
+ stream.read((char*)&optionalHeader, sizeof(optionalHeader));
+ dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE;
+ }
+ else if (optionalHeaderSignature == PE_64BIT_SIGNATURE)
+ {
+ PEOptionalHeader64 optionalHeader;
+ stream.read((char*)&optionalHeader, sizeof(optionalHeader));
+ dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE;
+ }
+ else
+ {
+ LOG(Warning, "Unrecognized PE format.");
+ return true;
+ }
+
+ // Read section headers
+ auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader;
+ stream.seekg(sectionHeaderPos);
+ Array sectionHeaders;
+ sectionHeaders.Resize(sectionHeadersCount);
+ stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * sectionHeadersCount);
+
+ // Look for .rsrc section header
+ for (uint32 i = 0; i < sectionHeadersCount; i++)
+ {
+ PESectionHeader& sectionHeader = sectionHeaders[i];
+ if (sectionHeader.flags & PE_SECTION_UNINITIALIZED_DATA)
+ continue;
+ if (strcmp(sectionHeader.name, ".rsrc") == 0)
+ {
+ uint32 imageSize = sectionHeader.physicalSize;
+ Array imageData;
+ imageData.Resize(imageSize);
+
+ stream.seekg(sectionHeader.physicalAddress);
+ stream.read((char*)imageData.Get(), imageSize);
+
+ uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeader.relativeVirtualAddress;
+ PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset];
+
+ SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeader.relativeVirtualAddress, iconRGBA8);
+ stream.seekp(sectionHeader.physicalAddress);
+ stream.write((char*)imageData.Get(), imageSize);
+ }
+ }
+
+ stream.close();
+
+ return false;
+}
IMPLEMENT_ENGINE_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform);
@@ -50,7 +502,7 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
TextureData iconData;
if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData))
{
- if (EditorUtilities::UpdateExeIcon(files[0], iconData))
+ if (UpdateExeIcon(files[0], iconData))
{
data.Error(TEXT("Failed to change output executable file icon."));
return true;
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index a519b1da1..6ae0f0562 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -543,10 +543,11 @@ namespace FlaxEditor.CustomEditors
try
{
string text;
+ var value = Values[0];
if (ParentEditor is Dedicated.ScriptsEditor)
{
// Script
- text = JsonSerializer.Serialize(Values[0]);
+ text = JsonSerializer.Serialize(value);
// Remove properties that should be ignored when copy/pasting data
if (text == null)
@@ -576,12 +577,12 @@ namespace FlaxEditor.CustomEditors
else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type))
{
// Object reference
- text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object);
+ text = JsonSerializer.GetStringID(value as FlaxEngine.Object);
}
else
{
// Default
- text = JsonSerializer.Serialize(Values[0]);
+ text = JsonSerializer.Serialize(value, TypeUtils.GetType(Values.Type));
}
Clipboard.Text = text;
}
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index f783822a5..63374361d 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -3,6 +3,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using FlaxEditor.Actions;
using FlaxEditor.Content;
using FlaxEditor.GUI;
@@ -16,6 +18,27 @@ using Object = FlaxEngine.Object;
namespace FlaxEditor.CustomEditors.Dedicated
{
+ internal class NewScriptItem : ItemsListContextMenu.Item
+ {
+ private string _scriptName;
+
+ public string ScriptName
+ {
+ get => _scriptName;
+ set
+ {
+ _scriptName = value;
+ Name = $"Create script '{value}'";
+ }
+ }
+
+ public NewScriptItem(string scriptName)
+ {
+ ScriptName = scriptName;
+ TooltipText = "Create a new script";
+ }
+ }
+
///
/// Drag and drop scripts area control.
///
@@ -74,7 +97,43 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
}
- cm.ItemClicked += item => AddScript((ScriptType)item.Tag);
+ cm.TextChanged += text =>
+ {
+ if (!IsValidScriptName(text))
+ return;
+ if (!cm.ItemsPanel.Children.Any(x => x.Visible && x is not NewScriptItem))
+ {
+ // If there are no visible items, that means the search failed so we can find the create script button or create one if it's the first time
+ var newScriptItem = (NewScriptItem)cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem);
+ if (newScriptItem != null)
+ {
+ newScriptItem.Visible = true;
+ newScriptItem.ScriptName = text;
+ }
+ else
+ {
+ cm.AddItem(new NewScriptItem(text));
+ }
+ }
+ else
+ {
+ // Make sure to hide the create script button if there
+ var newScriptItem = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem);
+ if (newScriptItem != null)
+ newScriptItem.Visible = false;
+ }
+ };
+ cm.ItemClicked += item =>
+ {
+ if (item.Tag is ScriptType script)
+ {
+ AddScript(script);
+ }
+ else if (item is NewScriptItem newScriptItem)
+ {
+ CreateScript(newScriptItem);
+ }
+ };
cm.SortItems();
cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0));
}
@@ -113,6 +172,19 @@ namespace FlaxEditor.CustomEditors.Dedicated
return false;
}
+ private static bool IsValidScriptName(string text)
+ {
+ if (string.IsNullOrEmpty(text))
+ return false;
+ if (text.Contains(' '))
+ return false;
+ if (char.IsDigit(text[0]))
+ return false;
+ if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_'))
+ return false;
+ return Editor.Instance.ContentDatabase.GetProxy("cs").IsFileNameValid(text);
+ }
+
///
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
@@ -163,6 +235,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (_dragScripts.HasValidDrag)
{
result = _dragScripts.Effect;
+
AddScripts(_dragScripts.Objects);
}
else if (_dragAssets.HasValidDrag)
@@ -177,7 +250,43 @@ namespace FlaxEditor.CustomEditors.Dedicated
return result;
}
- private void AddScript(ScriptType item)
+ private void CreateScript(NewScriptItem item)
+ {
+ ScriptsEditor.NewScriptName = item.ScriptName;
+ var paths = Directory.GetFiles(Globals.ProjectSourceFolder, "*.Build.cs");
+
+ string moduleName = null;
+ foreach (var p in paths)
+ {
+ var file = File.ReadAllText(p);
+ if (!file.Contains("GameProjectTarget"))
+ continue; // Skip
+
+ if (file.Contains("Modules.Add(\"Game\")"))
+ {
+ // Assume Game represents the main game module
+ moduleName = "Game";
+ break;
+ }
+ }
+
+ // Ensure the path slashes are correct for the OS
+ var correctedPath = Path.GetFullPath(Globals.ProjectSourceFolder);
+ if (string.IsNullOrEmpty(moduleName))
+ {
+ var error = FileSystem.ShowBrowseFolderDialog(Editor.Instance.Windows.MainWindow, correctedPath, "Select a module folder to put the new script in", out moduleName);
+ if (error)
+ return;
+ }
+ var path = Path.Combine(Globals.ProjectSourceFolder, moduleName, item.ScriptName + ".cs");
+ Editor.Instance.ContentDatabase.GetProxy("cs").Create(path, null);
+ }
+
+ ///
+ /// Attach a script to the actor.
+ ///
+ /// The script.
+ public void AddScript(ScriptType item)
{
var list = new List(1) { item };
AddScripts(list);
@@ -224,16 +333,67 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void AddScripts(List items)
{
- var actions = new List(4);
+ var actions = new List();
for (int i = 0; i < items.Count; i++)
{
var scriptType = items[i];
+ RequireScriptAttribute scriptAttribute = null;
+ if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false))
+ {
+ foreach (var e in scriptType.GetAttributes(false))
+ {
+ if (e is not RequireScriptAttribute requireScriptAttribute)
+ continue;
+ scriptAttribute = requireScriptAttribute;
+ break;
+ }
+ }
+
+ // See if script requires a specific actor type
+ RequireActorAttribute actorAttribute = null;
+ if (scriptType.HasAttribute(typeof(RequireActorAttribute), false))
+ {
+ foreach (var e in scriptType.GetAttributes(false))
+ {
+ if (e is not RequireActorAttribute requireActorAttribute)
+ continue;
+ actorAttribute = requireActorAttribute;
+ break;
+ }
+ }
+
var actors = ScriptsEditor.ParentEditor.Values;
for (int j = 0; j < actors.Count; j++)
{
var actor = (Actor)actors[j];
+
+ // If required actor exists but is not this actor type then skip adding to actor
+ if (actorAttribute != null)
+ {
+ if (actor.GetType() != actorAttribute.RequiredType && !actor.GetType().IsSubclassOf(actorAttribute.RequiredType))
+ {
+ Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` not added to `{actor}` due to script requiring an Actor type of `{actorAttribute.RequiredType}`.");
+ continue;
+ }
+ }
+
actions.Add(AddRemoveScript.Add(actor, scriptType));
+ // Check if actor has required scripts and add them if the actor does not.
+ if (scriptAttribute != null)
+ {
+ foreach (var type in scriptAttribute.RequiredTypes)
+ {
+ if (!type.IsSubclassOf(typeof(Script)))
+ {
+ Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(type.Name)}` not added to `{actor}` due to the class not being a subclass of Script.");
+ continue;
+ }
+ if (actor.GetScript(type) != null)
+ continue;
+ actions.Add(AddRemoveScript.Add(actor, new ScriptType(type)));
+ }
+ }
}
}
@@ -440,6 +600,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
public override IEnumerable