diff --git a/build/gamesource/textures/decal/decals.ini b/build/engine/config/decals/decals.cfg
similarity index 87%
rename from build/gamesource/textures/decal/decals.ini
rename to build/engine/config/decals/decals.cfg
index 438710c7aed537951bd0844fc34a9322d07eba3c..5054a8a4802bee12ac478bb04d96107770bbc8da 100644
--- a/build/gamesource/textures/decal/decals.ini
+++ b/build/engine/config/decals/decals.cfg
@@ -2,7 +2,7 @@
 [decal_0]
 id=0
 base_scale=2.0
-tex=decal_test.dds
+tex=decal_test
 tex0=[0,0,64,64]
 tex1=[64,0,128,64]
 tex2=[0,64,64,128]
@@ -12,7 +12,7 @@ tex3=[64,64,128,128]
 [decal_1]
 id=1
 base_scale=2.0
-tex=decal_test.dds
+tex=decal_test
 tex0=[128,0,192,64]
 tex1=[192,0,256,64]
 tex2=[128,64,192,128]
@@ -20,9 +20,9 @@ tex3=[192,64,256,128]
 
 ;wood
 [decal_2]
-id=2
+id=4
 base_scale=2.0
-tex=decal_test.dds
+tex=decal_test
 tex0=[256,0,320,64]
 tex1=[320,0,384,64]
 tex2=[256,64,320,128]
@@ -31,9 +31,9 @@ tex4=[384,0,448,64]
 
 ;glass
 [decal_3]
-id=3
+id=2
 base_scale=2.0
-tex=decal_test.dds
+tex=decal_test
 tex0=[0,128,128,256]
 tex1=[128,128,256,256]
 tex2=[256,128,384,256]
@@ -44,7 +44,7 @@ tex4=[128,256,256,384]
 [decal_4]
 id=7
 base_scale=8.0
-tex=decal_test.dds
+tex=decal_test
 tex0=[256,256,384,384]
 tex1=[384,256,512,384]
 tex2=[384,128,512,256]
diff --git a/build/gamesource/textures/decal/decal_test.dds b/build/engine/textures/decal/decal_test.dds
similarity index 100%
rename from build/gamesource/textures/decal/decal_test.dds
rename to build/engine/textures/decal/decal_test.dds
diff --git a/build/engine/textures/decal/decal_test2.dds b/build/engine/textures/decal/decal_test2.dds
new file mode 100644
index 0000000000000000000000000000000000000000..497ec0a645bbe369790dd9d2d78db10214bb2d5e
Binary files /dev/null and b/build/engine/textures/decal/decal_test2.dds differ
diff --git a/build/engine/textures/decal/decal_test3.dds b/build/engine/textures/decal/decal_test3.dds
new file mode 100644
index 0000000000000000000000000000000000000000..b068b6500463898c431ee6069992113c5d9bc5de
Binary files /dev/null and b/build/engine/textures/decal/decal_test3.dds differ
diff --git a/docs/gen-entity.js b/docs/gen-entity.js
index 95ba19a9209b1a6bffd67fe2eb9df10021af47ee..6641926e08179d5d94270d297b48a0a3cea78f36 100644
--- a/docs/gen-entity.js
+++ b/docs/gen-entity.js
@@ -19,6 +19,7 @@ g_mFieldTypes = {
 	"DEFINE_FIELD_STRING": "string",
 	"DEFINE_FIELD_ANGLES": "angles",
 	"DEFINE_FIELD_INT": "int",
+	"DEFINE_FIELD_UINT": "uint",
 	"DEFINE_FIELD_ENUM": null,
 	"DEFINE_FIELD_FLOAT": "float",
 	"DEFINE_FIELD_BOOL": "bool",
@@ -29,6 +30,7 @@ g_mFieldTypes = {
 g_mInputTypes = {
 	"PDF_NONE": "none",
 	"PDF_INT": "int",
+	"PDF_UINT": "uint",
 	"PDF_FLOAT": "float",
 	"PDF_VECTOR": "float3",
 	"PDF_VECTOR4": "float4",
diff --git a/proj/SkyXEngine/vs2013/SkyXEngine.sln b/proj/SkyXEngine/vs2013/SkyXEngine.sln
index 4cf1c805fac086c50d43701e9f488031d4b3b861..7bf5b3d524ba84aadc75a4a31ec74f515e256973 100644
--- a/proj/SkyXEngine/vs2013/SkyXEngine.sln
+++ b/proj/SkyXEngine/vs2013/SkyXEngine.sln
@@ -8,6 +8,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SkyXEngine", "SkyXEngine.vc
 		{7C0C8205-BDD3-44A3-AA3A-7855C7EFC88E} = {7C0C8205-BDD3-44A3-AA3A-7855C7EFC88E}
 		{349DE810-592A-42AF-9440-A0B3F46A9229} = {349DE810-592A-42AF-9440-A0B3F46A9229}
 		{236F4A16-78D8-42E4-86C0-30265CA2D84D} = {236F4A16-78D8-42E4-86C0-30265CA2D84D}
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E} = {8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}
 		{99BF8838-DB9E-4CFE-83F7-ACAA8A97304B} = {99BF8838-DB9E-4CFE-83F7-ACAA8A97304B}
 		{CD470440-2CC2-42BA-8B94-2B1C72125FE3} = {CD470440-2CC2-42BA-8B94-2B1C72125FE3}
 		{B9656841-7734-4D0B-8619-1BED5E2ED7AE} = {B9656841-7734-4D0B-8619-1BED5E2ED7AE}
@@ -370,6 +371,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pngplugin", "..\..\pngplugi
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xParticles", "..\..\xParticles\vs2013\xParticles.vcxproj", "{6CB8C7D4-F3F9-418D-8939-BB07C488FF01}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fbxplugin", "..\..\fbxplugin\vs2013\fbxplugin.vcxproj", "{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Win32 = Debug|Win32
@@ -780,6 +783,14 @@ Global
 		{6CB8C7D4-F3F9-418D-8939-BB07C488FF01}.Release|Win32.Build.0 = Release|Win32
 		{6CB8C7D4-F3F9-418D-8939-BB07C488FF01}.Release|x64.ActiveCfg = Release|x64
 		{6CB8C7D4-F3F9-418D-8939-BB07C488FF01}.Release|x64.Build.0 = Release|x64
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}.Debug|Win32.ActiveCfg = Debug|Win32
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}.Debug|Win32.Build.0 = Debug|Win32
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}.Debug|x64.ActiveCfg = Debug|x64
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}.Debug|x64.Build.0 = Debug|x64
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}.Release|Win32.ActiveCfg = Release|Win32
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}.Release|Win32.Build.0 = Release|Win32
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}.Release|x64.ActiveCfg = Release|x64
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}.Release|x64.Build.0 = Release|x64
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -855,6 +866,7 @@ Global
 		{D61CDBAB-DD14-4380-B400-12268DB975E0} = {7C1F0E50-7A19-4AB4-B559-11EF078F4787}
 		{99BF8838-DB9E-4CFE-83F7-ACAA8A97304B} = {D61CDBAB-DD14-4380-B400-12268DB975E0}
 		{6CB8C7D4-F3F9-418D-8939-BB07C488FF01} = {E6B16854-D4A4-4B56-8E1C-482DD523F205}
+		{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E} = {7C1F0E50-7A19-4AB4-B559-11EF078F4787}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {B3433050-1AA4-4375-8CC4-42BB0954D80A}
diff --git a/proj/fbxplugin/vs2013/fbxplugin.vcxproj b/proj/fbxplugin/vs2013/fbxplugin.vcxproj
new file mode 100644
index 0000000000000000000000000000000000000000..7c6ccb034f19e316a93ab2e63598b13ab00cd26a
--- /dev/null
+++ b/proj/fbxplugin/vs2013/fbxplugin.vcxproj
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\source\fbxplugin\dllmain.cpp" />
+    <ClCompile Include="..\..\..\source\fbxplugin\libdeflate.c" />
+    <ClCompile Include="..\..\..\source\fbxplugin\ModelLoader.cpp" />
+    <ClCompile Include="..\..\..\source\fbxplugin\plugin_main.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\source\fbxplugin\fbx.h" />
+    <ClInclude Include="..\..\..\source\fbxplugin\libdeflate.h" />
+    <ClInclude Include="..\..\..\source\fbxplugin\ModelLoader.h" />
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{8B15BF1C-323B-4F4E-86D4-C65986FEDD3E}</ProjectGuid>
+    <Keyword>Win32Proj</Keyword>
+    <RootNamespace>fbxplugin</RootNamespace>
+    <ProjectName>fbxplugin</ProjectName>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v120_xp</PlatformToolset>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v120_xp</PlatformToolset>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v120_xp</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v120_xp</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>../../../build/bin/plugins/</OutDir>
+    <IncludePath>$(IncludePath);../../../sdks/;$(WindowsSdk_IncludePath);../../../source;</IncludePath>
+    <LibraryPath>;../../../libs;$(LibraryPath)</LibraryPath>
+    <SourcePath>../../../source;$(SourcePath)</SourcePath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <IncludePath>$(IncludePath);../../../sdks/;$(WindowsSdk_IncludePath);../../../source;</IncludePath>
+    <LibraryPath>;../../../libs64;$(LibraryPath)</LibraryPath>
+    <SourcePath>../../../source;$(SourcePath)</SourcePath>
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>../../../build/bin64/plugins/</OutDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>../../../build/bin/plugins/</OutDir>
+    <IncludePath>$(IncludePath);../../../sdks/;$(WindowsSdk_IncludePath);../../../source;</IncludePath>
+    <LibraryPath>;../../../libs;$(LibraryPath)</LibraryPath>
+    <SourcePath>../../../source;$(SourcePath)</SourcePath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <IncludePath>$(IncludePath);../../../sdks/;$(WindowsSdk_IncludePath);../../../source;</IncludePath>
+    <LibraryPath>;../../../libs64;$(LibraryPath)</LibraryPath>
+    <SourcePath>../../../source;$(SourcePath)</SourcePath>
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>../../../build/bin64/plugins/</OutDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;_WINDOWS;SX_LIB_NAME="FBX";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <TreatSpecificWarningsAsErrors>4316</TreatSpecificWarningsAsErrors>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ImportLibrary>../../../libs/$(TargetName).lib</ImportLibrary>
+      <ProgramDatabaseFile>$(ProjectDir)../../../pdb/$(TargetName).pdb</ProgramDatabaseFile>
+      <Profile>true</Profile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN64;_CRT_SECURE_NO_WARNINGS;_DEBUG;_WINDOWS;SX_LIB_NAME="FBX";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <TreatSpecificWarningsAsErrors>4316</TreatSpecificWarningsAsErrors>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ImportLibrary>../../../libs/$(TargetName).lib</ImportLibrary>
+      <ProgramDatabaseFile>$(ProjectDir)../../../pdb64/$(TargetName).pdb</ProgramDatabaseFile>
+      <Profile>true</Profile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;_WINDOWS;SX_LIB_NAME="FBX";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatSpecificWarningsAsErrors>4316</TreatSpecificWarningsAsErrors>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <ProgramDatabaseFile>$(ProjectDir)../../../pdb/$(TargetName).pdb</ProgramDatabaseFile>
+      <ImportLibrary>../../../libs/$(TargetName).lib</ImportLibrary>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN64;_CRT_SECURE_NO_WARNINGS;NDEBUG;_WINDOWS;SX_LIB_NAME="FBX";%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <TreatSpecificWarningsAsErrors>4316</TreatSpecificWarningsAsErrors>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <ProgramDatabaseFile>$(ProjectDir)../../../pdb64/$(TargetName).pdb</ProgramDatabaseFile>
+      <ImportLibrary>../../../libs/$(TargetName).lib</ImportLibrary>
+    </Link>
+  </ItemDefinitionGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/proj/fbxplugin/vs2013/fbxplugin.vcxproj.filters b/proj/fbxplugin/vs2013/fbxplugin.vcxproj.filters
new file mode 100644
index 0000000000000000000000000000000000000000..598161164eb6a2b11261644a4e688e14f0ab9ff9
--- /dev/null
+++ b/proj/fbxplugin/vs2013/fbxplugin.vcxproj.filters
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4F9F3B4E-F189-4263-BB44-FD10D756B9AB}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{3CE894A3-1008-4F79-80BB-FD47268A2DF7}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{52E3E84A-363D-49C5-ADE7-E24E930CEB53}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\source\fbxplugin\dllmain.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\source\fbxplugin\ModelLoader.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\source\fbxplugin\plugin_main.cpp" />
+    <ClCompile Include="..\..\..\source\fbxplugin\libdeflate.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\source\fbxplugin\ModelLoader.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\fbxplugin\fbx.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\fbxplugin\libdeflate.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/proj/msDSEExporter/vs2013/msDSEExporter.vcxproj b/proj/msDSEExporter/vs2013/msDSEExporter.vcxproj
index 0dac5ef96e6c519fb1c3f7150a6c402168ccc6db..4b40fd426bed100e74fd160e21ec1b7174f2e13f 100644
--- a/proj/msDSEExporter/vs2013/msDSEExporter.vcxproj
+++ b/proj/msDSEExporter/vs2013/msDSEExporter.vcxproj
@@ -19,12 +19,6 @@
     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
-      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\exporter_base\dllmain.cpp" />
     <ClCompile Include="..\..\..\source\exporter_base\Exporter.cpp" />
     <ClCompile Include="..\..\..\source\exporter_base\Extended.cpp" />
diff --git a/proj/msDSEExporter/vs2013/msDSEExporter.vcxproj.filters b/proj/msDSEExporter/vs2013/msDSEExporter.vcxproj.filters
index acedeb6a4c88463f61e8b2361083c36eb5a24ffa..ecdf20a8dd791b5aa33ea3fd297ce776efc901d0 100644
--- a/proj/msDSEExporter/vs2013/msDSEExporter.vcxproj.filters
+++ b/proj/msDSEExporter/vs2013/msDSEExporter.vcxproj.filters
@@ -27,9 +27,6 @@
     <ClCompile Include="..\..\..\source\msDSEExporter\Plugin.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\msDSEExporter\Provider.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/oggplugin/vs2013/oggplugin.vcxproj b/proj/oggplugin/vs2013/oggplugin.vcxproj
index f6a2a648c33f2523b8556fe55c0be7e5e4e3de57..f279cf87cff38e3bf9deb34224b8a7dcc3488450 100644
--- a/proj/oggplugin/vs2013/oggplugin.vcxproj
+++ b/proj/oggplugin/vs2013/oggplugin.vcxproj
@@ -174,7 +174,6 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\oggplugin\AudioCodecOgg.cpp" />
     <ClCompile Include="..\..\..\source\oggplugin\dllmain.cpp" />
     <ClCompile Include="..\..\..\source\oggplugin\plugin_main.cpp" />
diff --git a/proj/oggplugin/vs2013/oggplugin.vcxproj.filters b/proj/oggplugin/vs2013/oggplugin.vcxproj.filters
index 6811cfc3b9cc4bd1581c9f7ef7e21703e0f942e5..2c8cb6fb66b79b802bbd2acf17ab351a3b38c1c8 100644
--- a/proj/oggplugin/vs2013/oggplugin.vcxproj.filters
+++ b/proj/oggplugin/vs2013/oggplugin.vcxproj.filters
@@ -22,9 +22,6 @@
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\..\..\source\oggplugin\plugin_main.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\oggplugin\AudioCodecOgg.h">
diff --git a/proj/sxae/vs2013/sxae.vcxproj b/proj/sxae/vs2013/sxae.vcxproj
index bbc11f73bfccf06ac6d7055b0a799dc83ec23c16..dc75b53c244946c011681a7ca9c26ebaf96237e3 100644
--- a/proj/sxae/vs2013/sxae.vcxproj
+++ b/proj/sxae/vs2013/sxae.vcxproj
@@ -96,7 +96,6 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\skyxengine.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
diff --git a/proj/sxae/vs2013/sxae.vcxproj.filters b/proj/sxae/vs2013/sxae.vcxproj.filters
index 3c5b935d6c7164cdeaf3e484c093209403ffd38e..0b9f9cf9ebb2c393ea804c239b927a3e369f4005 100644
--- a/proj/sxae/vs2013/sxae.vcxproj.filters
+++ b/proj/sxae/vs2013/sxae.vcxproj.filters
@@ -57,9 +57,6 @@
     <ClCompile Include="..\..\..\source\sxae\TabHitboxes.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\skyxengine.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxambient/vs2013/sxambient.vcxproj b/proj/sxambient/vs2013/sxambient.vcxproj
index 49c13dc6fb4431addb1c7056e8f38054b6ebd3aa..dc8ccf2c8b57c350b9e924218831963f767ef2da 100644
--- a/proj/sxambient/vs2013/sxambient.vcxproj
+++ b/proj/sxambient/vs2013/sxambient.vcxproj
@@ -186,12 +186,6 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
-      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
-      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
-      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\common\string_utils.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
diff --git a/proj/sxambient/vs2013/sxambient.vcxproj.filters b/proj/sxambient/vs2013/sxambient.vcxproj.filters
index 35b20d5a991f78d4a1e714122c148f5e2185af71..69dfb63bc45c337ce29bd993d822e06e6661a177 100644
--- a/proj/sxambient/vs2013/sxambient.vcxproj.filters
+++ b/proj/sxambient/vs2013/sxambient.vcxproj.filters
@@ -30,9 +30,6 @@
     <ClCompile Include="..\..\..\source\level\sxlevel.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxanim/vs2013/sxanim.vcxproj b/proj/sxanim/vs2013/sxanim.vcxproj
index 5db828bb30b68066bab323134d8852248abe2bd3..e20becf019bca210c7f7fcd838fbbad66df5363a 100644
--- a/proj/sxanim/vs2013/sxanim.vcxproj
+++ b/proj/sxanim/vs2013/sxanim.vcxproj
@@ -181,27 +181,36 @@
     <ClCompile Include="..\..\..\source\anim\AnimatedModel.cpp" />
     <ClCompile Include="..\..\..\source\anim\AnimatedModelProvider.cpp" />
     <ClCompile Include="..\..\..\source\anim\AnimatedModelShared.cpp" />
+    <ClCompile Include="..\..\..\source\anim\Decal.cpp" />
+    <ClCompile Include="..\..\..\source\anim\DecalProvider.cpp" />
     <ClCompile Include="..\..\..\source\anim\dllmain.cpp" />
     <ClCompile Include="..\..\..\source\anim\DynamicModel.cpp" />
     <ClCompile Include="..\..\..\source\anim\DynamicModelProvider.cpp" />
     <ClCompile Include="..\..\..\source\anim\DynamicModelShared.cpp" />
+    <ClCompile Include="..\..\..\source\anim\ModelOverlay.cpp" />
     <ClCompile Include="..\..\..\source\anim\plugin_main.cpp" />
     <ClCompile Include="..\..\..\source\anim\Renderable.cpp" />
     <ClCompile Include="..\..\..\source\anim\RenderableVisibility.cpp" />
+    <ClCompile Include="..\..\..\source\anim\StaticModelProvider.cpp" />
     <ClCompile Include="..\..\..\source\anim\Updatable.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\anim\AnimatedModel.h" />
     <ClInclude Include="..\..\..\source\anim\AnimatedModelProvider.h" />
     <ClInclude Include="..\..\..\source\anim\AnimatedModelShared.h" />
+    <ClInclude Include="..\..\..\source\anim\Decal.h" />
+    <ClInclude Include="..\..\..\source\anim\DecalProvider.h" />
     <ClInclude Include="..\..\..\source\anim\DynamicModel.h" />
     <ClInclude Include="..\..\..\source\anim\DynamicModelProvider.h" />
     <ClInclude Include="..\..\..\source\anim\DynamicModelShared.h" />
+    <ClInclude Include="..\..\..\source\anim\ModelOverlay.h" />
     <ClInclude Include="..\..\..\source\anim\Renderable.h" />
     <ClInclude Include="..\..\..\source\anim\RenderableVisibility.h" />
+    <ClInclude Include="..\..\..\source\anim\StaticModelProvider.h" />
     <ClInclude Include="..\..\..\source\anim\Updatable.h" />
     <ClInclude Include="..\..\..\source\xcommon\IXScene.h" />
+    <ClInclude Include="..\..\..\source\xcommon\resource\IXDecal.h" />
+    <ClInclude Include="..\..\..\source\xcommon\resource\IXDecalProvider.h" />
     <ClInclude Include="..\..\..\source\xcommon\resource\IXModelProvider.h" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
diff --git a/proj/sxanim/vs2013/sxanim.vcxproj.filters b/proj/sxanim/vs2013/sxanim.vcxproj.filters
index 54b74f26893854001719bc2841ccf10a4ee93e18..e907c6c64e5b8546f8410d5adccef4efb1c75ec3 100644
--- a/proj/sxanim/vs2013/sxanim.vcxproj.filters
+++ b/proj/sxanim/vs2013/sxanim.vcxproj.filters
@@ -18,9 +18,6 @@
     </Filter>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\anim\Renderable.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -52,6 +49,18 @@
     <ClCompile Include="..\..\..\source\anim\dllmain.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\source\anim\DecalProvider.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\source\anim\ModelOverlay.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\source\anim\Decal.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\source\anim\StaticModelProvider.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\anim\Renderable.h">
@@ -87,5 +96,23 @@
     <ClInclude Include="..\..\..\source\xcommon\IXScene.h">
       <Filter>Header Files\xcommon</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\xcommon\resource\IXDecalProvider.h">
+      <Filter>Header Files\xcommon</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\xcommon\resource\IXDecal.h">
+      <Filter>Header Files\xcommon</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\anim\DecalProvider.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\anim\ModelOverlay.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\anim\Decal.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\anim\StaticModelProvider.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/proj/sxcore/vs2013/sxcore.vcxproj b/proj/sxcore/vs2013/sxcore.vcxproj
index 44886dbddb45c65b3f818ae0d822c7444be8ec85..76c5c2bbd0d4a0be8d2b811f7abb7563e6af5e31 100644
--- a/proj/sxcore/vs2013/sxcore.vcxproj
+++ b/proj/sxcore/vs2013/sxcore.vcxproj
@@ -21,7 +21,6 @@
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
     <ClCompile Include="..\..\..\source\common\guid.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\core\AsyncFileReader.cpp" />
     <ClCompile Include="..\..\..\source\core\AsyncTaskRunner.cpp" />
diff --git a/proj/sxcore/vs2013/sxcore.vcxproj.filters b/proj/sxcore/vs2013/sxcore.vcxproj.filters
index 9faae7aa26d1072dcd4f4ca0899f5578841cb7fe..7a2f4723f5d59147eebf1bd6a9253f413c71b672 100644
--- a/proj/sxcore/vs2013/sxcore.vcxproj.filters
+++ b/proj/sxcore/vs2013/sxcore.vcxproj.filters
@@ -25,9 +25,6 @@
     <ClCompile Include="..\..\..\source\core\Config.cpp">
       <Filter>Sources</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Sources</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp">
       <Filter>Sources</Filter>
     </ClCompile>
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj b/proj/sxgame/vs2013/sxgame.vcxproj
index 8aa785a032493670f5738a00d3c180ec7ba75c9a..719b07cf19316d384ea0279356325d1deea0ab73 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj
+++ b/proj/sxgame/vs2013/sxgame.vcxproj
@@ -177,7 +177,6 @@
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
     <ClCompile Include="..\..\..\source\common\guid.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\game\BaseHandle.cpp" />
     <ClCompile Include="..\..\..\source\game\BaseLight.cpp" />
@@ -213,6 +212,7 @@
     <ClCompile Include="..\..\..\source\game\GUICraftController.cpp" />
     <ClCompile Include="..\..\..\source\game\GUIInventoryController.cpp" />
     <ClCompile Include="..\..\..\source\game\HUDcontroller.cpp" />
+    <ClCompile Include="..\..\..\source\game\InfoOverlay.cpp" />
     <ClCompile Include="..\..\..\source\game\InfoParticlePlayer.cpp" />
     <ClCompile Include="..\..\..\source\game\LadderMovementController.cpp" />
     <ClCompile Include="..\..\..\source\game\LightDirectional.cpp" />
@@ -302,6 +302,7 @@
     <ClInclude Include="..\..\..\source\game\HUDcontroller.h" />
     <ClInclude Include="..\..\..\source\game\IGameState.h" />
     <ClInclude Include="..\..\..\source\game\IMovementController.h" />
+    <ClInclude Include="..\..\..\source\game\InfoOverlay.h" />
     <ClInclude Include="..\..\..\source\game\InfoParticlePlayer.h" />
     <ClInclude Include="..\..\..\source\game\LadderMovementController.h" />
     <ClInclude Include="..\..\..\source\game\LightSun.h" />
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj.filters b/proj/sxgame/vs2013/sxgame.vcxproj.filters
index 9a6014e06dfe003fc72058e0290c008fab2d51ca..f3634cd064470e2b0836ab30f2e7f289d71bef05 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj.filters
+++ b/proj/sxgame/vs2013/sxgame.vcxproj.filters
@@ -138,9 +138,6 @@
     <ClCompile Include="..\..\..\source\game\EntityFactory.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\game\EntityManager.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -390,6 +387,9 @@
     <ClCompile Include="..\..\..\source\game\DogholeMovementController.cpp">
       <Filter>Source Files\character_movement</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\source\game\InfoOverlay.cpp">
+      <Filter>Source Files\ents\info</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\game\sxgame.h">
@@ -680,6 +680,9 @@
     <ClInclude Include="..\..\..\source\game\DogholeMovementController.h">
       <Filter>Header Files\character_movement</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\game\InfoOverlay.h">
+      <Filter>Header Files\ents\info</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\..\source\game\sxgame.rc">
diff --git a/proj/sxgenpreview/vs2013/sxgenpreview.vcxproj b/proj/sxgenpreview/vs2013/sxgenpreview.vcxproj
index 7f122d3621569fe301cef423e7034c54410888d9..ab005f3e7f5d66103b5e21ff0eb1eb528610511a 100644
--- a/proj/sxgenpreview/vs2013/sxgenpreview.vcxproj
+++ b/proj/sxgenpreview/vs2013/sxgenpreview.vcxproj
@@ -94,7 +94,6 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\skyxengine.cpp" />
     <ClCompile Include="..\..\..\source\sxgenpreview\sxgenpreview.cpp" />
diff --git a/proj/sxgenpreview/vs2013/sxgenpreview.vcxproj.filters b/proj/sxgenpreview/vs2013/sxgenpreview.vcxproj.filters
index a5742b1b70341c23aef02f681aa3f9f712a015a8..a8dd1d5fc23cbf0237f88190495660ab527db1e3 100644
--- a/proj/sxgenpreview/vs2013/sxgenpreview.vcxproj.filters
+++ b/proj/sxgenpreview/vs2013/sxgenpreview.vcxproj.filters
@@ -21,9 +21,6 @@
     <ClCompile Include="..\..\..\source\skyxengine.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxgreen/sxgreen/sxgreen.vcxproj b/proj/sxgreen/sxgreen/sxgreen.vcxproj
index 2eda957b846263c65e6cb8bac30e74b871f2ef4e..ec21b341c0d7433680f2b6cb50305c195a402820 100644
--- a/proj/sxgreen/sxgreen/sxgreen.vcxproj
+++ b/proj/sxgreen/sxgreen/sxgreen.vcxproj
@@ -178,7 +178,6 @@
     <ClInclude Include="..\..\..\source\green\sxgreen.h" />
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\green\green.cpp" />
     <ClCompile Include="..\..\..\source\green\sxgreen.cpp" />
     <ClCompile Include="..\..\..\source\green\sxgreen_dll.cpp" />
diff --git a/proj/sxgreen/sxgreen/sxgreen.vcxproj.filters b/proj/sxgreen/sxgreen/sxgreen.vcxproj.filters
index 7d086b127ffbbab6271208a016119431e18a6694..3cd9959933e57417d1361f033ef6d51cba1b557f 100644
--- a/proj/sxgreen/sxgreen/sxgreen.vcxproj.filters
+++ b/proj/sxgreen/sxgreen/sxgreen.vcxproj.filters
@@ -35,8 +35,5 @@
     <ClCompile Include="..\..\..\source\green\sxgreen.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/proj/sxgui/vs2013/sxgui.vcxproj b/proj/sxgui/vs2013/sxgui.vcxproj
index 20a5301f425a9c32355ab91a0027d9be8697c584..79c65b41f962166be7a1dac4290f838ffb58e207 100644
--- a/proj/sxgui/vs2013/sxgui.vcxproj
+++ b/proj/sxgui/vs2013/sxgui.vcxproj
@@ -241,7 +241,6 @@
     <ClInclude Include="..\..\..\source\gui\ITD.h" />
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\gui\CSSstyle.cpp" />
     <ClCompile Include="..\..\..\source\gui\IBODY.cpp" />
     <ClCompile Include="..\..\..\source\gui\IBR.cpp" />
diff --git a/proj/sxgui/vs2013/sxgui.vcxproj.filters b/proj/sxgui/vs2013/sxgui.vcxproj.filters
index e742664b72bf81314a9ef98e9d50d83b04fb9b4a..2e3bc8f4b214a279cc0794838ed0d10535611e32 100644
--- a/proj/sxgui/vs2013/sxgui.vcxproj.filters
+++ b/proj/sxgui/vs2013/sxgui.vcxproj.filters
@@ -308,9 +308,6 @@
     <ClCompile Include="..\..\..\source\gui\VideoRenderer.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\gui\ICSSstyle.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxgui2/vs2013/sxgui2.vcxproj b/proj/sxgui2/vs2013/sxgui2.vcxproj
index be80b15a6962ba50b12f80faf7dafd25e3dd3416..4a60a42afaf36331691553762f13f693126daf1d 100644
--- a/proj/sxgui2/vs2013/sxgui2.vcxproj
+++ b/proj/sxgui2/vs2013/sxgui2.vcxproj
@@ -19,7 +19,6 @@
     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\gui2\CSSLexer.cpp" />
     <ClCompile Include="..\..\..\source\gui2\CSSProperty.cpp" />
     <ClCompile Include="..\..\..\source\gui2\Desktop.cpp" />
diff --git a/proj/sxgui2/vs2013/sxgui2.vcxproj.filters b/proj/sxgui2/vs2013/sxgui2.vcxproj.filters
index 147e6315d56037744af0ea984a2ef01e62d7d420..d91712645e1df7be0fd982e715f5cf7ae87da1cf 100644
--- a/proj/sxgui2/vs2013/sxgui2.vcxproj.filters
+++ b/proj/sxgui2/vs2013/sxgui2.vcxproj.filters
@@ -49,9 +49,6 @@
     <ClCompile Include="..\..\..\source\gui2\FontManager.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\xcommon\gui\IXDesktop.h">
diff --git a/proj/sxguiwinapi/vs2013/sxguiwinapi.vcxproj b/proj/sxguiwinapi/vs2013/sxguiwinapi.vcxproj
index d5b37e03af67340b1980fa2482d0c9d2c281af6e..d9702a8d08c44dc30716d06adc2caf1290da215d 100644
--- a/proj/sxguiwinapi/vs2013/sxguiwinapi.vcxproj
+++ b/proj/sxguiwinapi/vs2013/sxguiwinapi.vcxproj
@@ -12,7 +12,6 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\SXGUIWinApi\base.cpp" />
     <ClCompile Include="..\..\..\source\SXGUIWinApi\base_wnd.cpp" />
diff --git a/proj/sxguiwinapi/vs2013/sxguiwinapi.vcxproj.filters b/proj/sxguiwinapi/vs2013/sxguiwinapi.vcxproj.filters
index 346c7ffdd25cb87feb4a2aeefb7544f1feb33b85..f4bd08eb752d7a9999cc36f61138b62724b3b1ad 100644
--- a/proj/sxguiwinapi/vs2013/sxguiwinapi.vcxproj.filters
+++ b/proj/sxguiwinapi/vs2013/sxguiwinapi.vcxproj.filters
@@ -84,9 +84,6 @@
     <ClCompile Include="..\..\..\source\SXGUIWinApi\dialog_select_dir.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\SXGUIWinApi\dialog_select_file.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxleveleditor/vs2013/sxleveleditor.vcxproj b/proj/sxleveleditor/vs2013/sxleveleditor.vcxproj
index 54637a3266e5d534c6d9610df14fc78ccf4b3f5d..b761091d68ce1c7a42c6ae3037bc016fa3d2c693 100644
--- a/proj/sxleveleditor/vs2013/sxleveleditor.vcxproj
+++ b/proj/sxleveleditor/vs2013/sxleveleditor.vcxproj
@@ -94,7 +94,6 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\editors_utils\axes_helper.cpp" />
     <ClCompile Include="..\..\..\source\skyxengine.cpp" />
diff --git a/proj/sxleveleditor/vs2013/sxleveleditor.vcxproj.filters b/proj/sxleveleditor/vs2013/sxleveleditor.vcxproj.filters
index 2f1709240240c769f6ba82ee927746722f75048b..a280298b7a9b10aee3a5929938c6843aa5ac79d0 100644
--- a/proj/sxleveleditor/vs2013/sxleveleditor.vcxproj.filters
+++ b/proj/sxleveleditor/vs2013/sxleveleditor.vcxproj.filters
@@ -42,9 +42,6 @@
     <ClCompile Include="..\..\..\source\editors_utils\axes_helper.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxmaterialeditor/vs2013/sxmaterialeditor.vcxproj b/proj/sxmaterialeditor/vs2013/sxmaterialeditor.vcxproj
index 4ba27d1f2e7a1d62207bef5384024f6cfd5985fb..54d8769a14cb6ce0ca8c55ec59fdea8eb26b5e87 100644
--- a/proj/sxmaterialeditor/vs2013/sxmaterialeditor.vcxproj
+++ b/proj/sxmaterialeditor/vs2013/sxmaterialeditor.vcxproj
@@ -94,7 +94,6 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\skyxengine.cpp" />
     <ClCompile Include="..\..\..\source\sxmaterialeditor\buttons_callback.cpp">
diff --git a/proj/sxmaterialeditor/vs2013/sxmaterialeditor.vcxproj.filters b/proj/sxmaterialeditor/vs2013/sxmaterialeditor.vcxproj.filters
index 47a31e8a5fe410f28aca68eebb98daf36e1c510f..2e82289cd82e1e122a27ce48fb392b06731e4b6e 100644
--- a/proj/sxmaterialeditor/vs2013/sxmaterialeditor.vcxproj.filters
+++ b/proj/sxmaterialeditor/vs2013/sxmaterialeditor.vcxproj.filters
@@ -42,9 +42,6 @@
     <ClCompile Include="..\..\..\source\skyxengine.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxmtrl/vs2013/sxmtrl.vcxproj b/proj/sxmtrl/vs2013/sxmtrl.vcxproj
index 2defc0c718b37879a513f90bb9e6e160a3391f6b..126b3c9978385cdbf59f6538583b5989e9b9d66b 100644
--- a/proj/sxmtrl/vs2013/sxmtrl.vcxproj
+++ b/proj/sxmtrl/vs2013/sxmtrl.vcxproj
@@ -171,7 +171,6 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\mtrl\LogicExpression.cpp" />
     <ClCompile Include="..\..\..\source\mtrl\MaterialSystem.cpp" />
     <ClCompile Include="..\..\..\source\mtrl\plugin_main.cpp" />
diff --git a/proj/sxmtrl/vs2013/sxmtrl.vcxproj.filters b/proj/sxmtrl/vs2013/sxmtrl.vcxproj.filters
index 556583a8857c31bf00d8b6c851c7af52b894ebb6..a2a53a7ee64ba10ed2267e94f501bd990291a2af 100644
--- a/proj/sxmtrl/vs2013/sxmtrl.vcxproj.filters
+++ b/proj/sxmtrl/vs2013/sxmtrl.vcxproj.filters
@@ -18,9 +18,6 @@
     </Filter>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\mtrl\MaterialSystem.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxparticles/vs2013/sxparticles.vcxproj b/proj/sxparticles/vs2013/sxparticles.vcxproj
index db6a76ae81b48065d7f2f9b58698e8c10a036d1b..af0ac95fe58206fea125c6d5b7b3b9490c9a33be 100644
--- a/proj/sxparticles/vs2013/sxparticles.vcxproj
+++ b/proj/sxparticles/vs2013/sxparticles.vcxproj
@@ -178,7 +178,6 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\particles\effect.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
diff --git a/proj/sxparticles/vs2013/sxparticles.vcxproj.filters b/proj/sxparticles/vs2013/sxparticles.vcxproj.filters
index c5fa3d4d23452be16bddf05e3390dfb05c6278e6..0759bd9e4c9d24258d7fe596d9b744e76861575e 100644
--- a/proj/sxparticles/vs2013/sxparticles.vcxproj.filters
+++ b/proj/sxparticles/vs2013/sxparticles.vcxproj.filters
@@ -27,9 +27,6 @@
     <ClCompile Include="..\..\..\source\particles\effect.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxparticleseditor/vs2013/sxparticleseditor.vcxproj b/proj/sxparticleseditor/vs2013/sxparticleseditor.vcxproj
index 0a8a4b7329bbf3988677cc1ded3d0470851e907a..4a34bbcefcac880f5656c5cf9b5bf1f0435b34a4 100644
--- a/proj/sxparticleseditor/vs2013/sxparticleseditor.vcxproj
+++ b/proj/sxparticleseditor/vs2013/sxparticleseditor.vcxproj
@@ -94,7 +94,6 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\skyxengine.cpp" />
     <ClCompile Include="..\..\..\source\sxparticleseditor\callback_common.cpp">
diff --git a/proj/sxparticleseditor/vs2013/sxparticleseditor.vcxproj.filters b/proj/sxparticleseditor/vs2013/sxparticleseditor.vcxproj.filters
index 9f8f4897b5e8f8c5c0e46d8d06d05fe249b669a9..6cac98a6e83d43a2229ed9b0aba58f918b1a8d66 100644
--- a/proj/sxparticleseditor/vs2013/sxparticleseditor.vcxproj.filters
+++ b/proj/sxparticleseditor/vs2013/sxparticleseditor.vcxproj.filters
@@ -33,9 +33,6 @@
     <ClCompile Include="..\..\..\source\sxparticleseditor\callback_list.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\skyxengine.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxphysics/vs2013/sxphysics.vcxproj b/proj/sxphysics/vs2013/sxphysics.vcxproj
index c063e34acc98f937e4ee88fbfa1db8cf2f33fd86..e327650bbc2b9b720baab939ebd0143a64feddaf 100644
--- a/proj/sxphysics/vs2013/sxphysics.vcxproj
+++ b/proj/sxphysics/vs2013/sxphysics.vcxproj
@@ -174,7 +174,6 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\physics\CharacterController.cpp" />
     <ClCompile Include="..\..\..\source\physics\CollisionObject.cpp" />
diff --git a/proj/sxphysics/vs2013/sxphysics.vcxproj.filters b/proj/sxphysics/vs2013/sxphysics.vcxproj.filters
index 9d75b2bfebf68daf795f56ef25a725b4f4e3aa6c..cb4b3c0159fa69f91530cdab25993550b707889a 100644
--- a/proj/sxphysics/vs2013/sxphysics.vcxproj.filters
+++ b/proj/sxphysics/vs2013/sxphysics.vcxproj.filters
@@ -24,9 +24,6 @@
     <ClCompile Include="..\..\..\source\physics\PhyWorld.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/sxrender/vs2013/sxrender.vcxproj b/proj/sxrender/vs2013/sxrender.vcxproj
index 20a0cf87981bda29025bc776cf49e70128096b7e..9a60037fa2eb809a7dac88820c18c209c52db90e 100644
--- a/proj/sxrender/vs2013/sxrender.vcxproj
+++ b/proj/sxrender/vs2013/sxrender.vcxproj
@@ -224,7 +224,6 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\guid.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\render\BaseTarget.cpp" />
     <ClCompile Include="..\..\..\source\render\Camera.cpp" />
diff --git a/proj/sxrender/vs2013/sxrender.vcxproj.filters b/proj/sxrender/vs2013/sxrender.vcxproj.filters
index 94b82a6504b8b9370cb1ed598cb52e58f4441274..48693c71cca4da1427a1733da49964c1a0b4a135 100644
--- a/proj/sxrender/vs2013/sxrender.vcxproj.filters
+++ b/proj/sxrender/vs2013/sxrender.vcxproj.filters
@@ -116,9 +116,6 @@
     </ClInclude>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\render\RenderableVisibility.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/terrax/vs2013/terrax.vcxproj b/proj/terrax/vs2013/terrax.vcxproj
index 3714e7454c5446f0fc1fa17a452affecb38d0674..461cd97712f84454376cb5f6e7a277ec2993bd4f 100644
--- a/proj/terrax/vs2013/terrax.vcxproj
+++ b/proj/terrax/vs2013/terrax.vcxproj
@@ -195,7 +195,6 @@
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp" />
     <ClCompile Include="..\..\..\source\common\guid.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\common\string_utils.cpp" />
     <ClCompile Include="..\..\..\source\skyxengine.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@@ -245,6 +244,7 @@
     <ClCompile Include="..\..\..\source\terrax\MaterialEditor.cpp" />
     <ClCompile Include="..\..\..\source\terrax\PropertyWindow.cpp" />
     <ClCompile Include="..\..\..\source\terrax\ProxyObject.cpp" />
+    <ClCompile Include="..\..\..\source\terrax\ResourceBrowser.cpp" />
     <ClCompile Include="..\..\..\source\terrax\SceneTreeWindow.cpp" />
     <ClCompile Include="..\..\..\source\terrax\ScrollBar.cpp" />
     <ClCompile Include="..\..\..\source\terrax\terrax.cpp" />
@@ -304,6 +304,7 @@
     <ClInclude Include="..\..\..\source\terrax\MaterialEditor.h" />
     <ClInclude Include="..\..\..\source\terrax\PropertyWindow.h" />
     <ClInclude Include="..\..\..\source\terrax\ProxyObject.h" />
+    <ClInclude Include="..\..\..\source\terrax\ResourceBrowser.h" />
     <ClInclude Include="..\..\..\source\terrax\SceneTreeWindow.h" />
     <ClInclude Include="..\..\..\source\terrax\ScrollBar.h" />
     <ClInclude Include="..\..\..\source\terrax\TextureViewGraphNode.h" />
diff --git a/proj/terrax/vs2013/terrax.vcxproj.filters b/proj/terrax/vs2013/terrax.vcxproj.filters
index c7f15f1fc8863e186cc481aa6a876468a8a27a9d..71fef577fe56b1114d70f818b5cdfa5b198c4d30 100644
--- a/proj/terrax/vs2013/terrax.vcxproj.filters
+++ b/proj/terrax/vs2013/terrax.vcxproj.filters
@@ -57,9 +57,6 @@
     <ClCompile Include="..\..\..\source\skyxengine.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\common\file_utils.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -216,6 +213,9 @@
     <ClCompile Include="..\..\..\source\terrax\CommandUngroup.cpp">
       <Filter>Source Files\cmd</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\source\terrax\ResourceBrowser.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\..\source\terrax\terrax.rc">
@@ -418,6 +418,9 @@
     <ClInclude Include="..\..\..\source\terrax\CommandUngroup.h">
       <Filter>Header Files\cmd</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\terrax\ResourceBrowser.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Image Include="..\..\..\source\terrax\resource\new.bmp">
diff --git a/proj/wadplugin/vs2013/wadplugin.vcxproj b/proj/wadplugin/vs2013/wadplugin.vcxproj
index 9d6ee1bb6a89ff9b9d535b00baa28fcb7fcf2811..ee4f5c663f7f3a674c83bc2096d8a27f5af8ac68 100644
--- a/proj/wadplugin/vs2013/wadplugin.vcxproj
+++ b/proj/wadplugin/vs2013/wadplugin.vcxproj
@@ -19,7 +19,6 @@
     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\wadplugin\dllmain.cpp" />
     <ClCompile Include="..\..\..\source\wadplugin\plugin_main.cpp" />
     <ClCompile Include="..\..\..\source\wadplugin\TextureLoader.cpp" />
diff --git a/proj/wadplugin/vs2013/wadplugin.vcxproj.filters b/proj/wadplugin/vs2013/wadplugin.vcxproj.filters
index 8ac55cf8a9ebc7c1e31108fc3297b1e848c49f39..b6a8ceb9fe868a17d7c796e73c04c43b065a742e 100644
--- a/proj/wadplugin/vs2013/wadplugin.vcxproj.filters
+++ b/proj/wadplugin/vs2013/wadplugin.vcxproj.filters
@@ -25,9 +25,6 @@
     <ClCompile Include="..\..\..\source\wadplugin\TextureProxy.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\wadplugin\TextureLoader.h">
diff --git a/proj/wavplugin/vs2013/wavplugin.vcxproj b/proj/wavplugin/vs2013/wavplugin.vcxproj
index fe9aa5dc348677274e033ecd7ceee81c298c6dc2..4c34641e261a1b8f9d166b8d87ac6abcfdf4ecee 100644
--- a/proj/wavplugin/vs2013/wavplugin.vcxproj
+++ b/proj/wavplugin/vs2013/wavplugin.vcxproj
@@ -159,7 +159,6 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\wavplugin\AudioCodecWave.cpp" />
     <ClCompile Include="..\..\..\source\wavplugin\dllmain.cpp" />
     <ClCompile Include="..\..\..\source\wavplugin\plugin_main.cpp" />
diff --git a/proj/wavplugin/vs2013/wavplugin.vcxproj.filters b/proj/wavplugin/vs2013/wavplugin.vcxproj.filters
index d92acb38921c4cfda2b03602b2fffb7d23a3c857..7a0ba1c846bc88201af3eb514cd4b420626bbc0b 100644
--- a/proj/wavplugin/vs2013/wavplugin.vcxproj.filters
+++ b/proj/wavplugin/vs2013/wavplugin.vcxproj.filters
@@ -22,9 +22,6 @@
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\..\..\source\wavplugin\plugin_main.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\wavplugin\AudioCodecWave.h">
diff --git a/proj/xCSG/vs2013/xCSG.vcxproj b/proj/xCSG/vs2013/xCSG.vcxproj
index 446f41f63324bf7344c0a9fa6bea21a7822c3eea..7b9eef946222811d2a8b593eb20e42d5646f1642 100644
--- a/proj/xCSG/vs2013/xCSG.vcxproj
+++ b/proj/xCSG/vs2013/xCSG.vcxproj
@@ -20,7 +20,6 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\guid.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\xcsg\BrushCreatorBox.cpp" />
     <ClCompile Include="..\..\..\source\xcsg\BrushCreatorFree.cpp" />
     <ClCompile Include="..\..\..\source\xcsg\BrushMesh.cpp" />
diff --git a/proj/xCSG/vs2013/xCSG.vcxproj.filters b/proj/xCSG/vs2013/xCSG.vcxproj.filters
index 85c4409a86facd4e7ef75266a0b099bb9e6ea545..a8202fa58e86b78b7e2e72e743c3b0423d02786d 100644
--- a/proj/xCSG/vs2013/xCSG.vcxproj.filters
+++ b/proj/xCSG/vs2013/xCSG.vcxproj.filters
@@ -37,9 +37,6 @@
     <ClCompile Include="..\..\..\source\xcsg\Editable.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\xcsg\Outline.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/xEngine/vs2013/xEngine.vcxproj b/proj/xEngine/vs2013/xEngine.vcxproj
index 2595ac7211d9f1f8e67f69753504403fb29c3263..94f72003f741e0d4ce6b04a0f9e94a5058cf18b4 100644
--- a/proj/xEngine/vs2013/xEngine.vcxproj
+++ b/proj/xEngine/vs2013/xEngine.vcxproj
@@ -179,7 +179,6 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\xEngine\CommandLineToArgvA.cpp" />
     <ClCompile Include="..\..\..\source\xEngine\dllmain.cpp" />
     <ClCompile Include="..\..\..\source\xEngine\Engine.cpp" />
diff --git a/proj/xEngine/vs2013/xEngine.vcxproj.filters b/proj/xEngine/vs2013/xEngine.vcxproj.filters
index 67b9b20d6e90515e4f392356200d8a411bbd61dc..56c14a0234483508abc5b4ff860c2aef9d465cc5 100644
--- a/proj/xEngine/vs2013/xEngine.vcxproj.filters
+++ b/proj/xEngine/vs2013/xEngine.vcxproj.filters
@@ -24,9 +24,6 @@
     <ClCompile Include="..\..\..\source\xEngine\CommandLineToArgvA.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\xEngine\Engine.h">
diff --git a/proj/xParticles/vs2013/xParticles.vcxproj b/proj/xParticles/vs2013/xParticles.vcxproj
index 3e9a04dee83388d9f52c60c677b38cfb7dc5ce65..6735b27297947599759b328aa81284464185e0cf 100644
--- a/proj/xParticles/vs2013/xParticles.vcxproj
+++ b/proj/xParticles/vs2013/xParticles.vcxproj
@@ -19,7 +19,6 @@
     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\xParticles\dllmain.cpp" />
     <ClCompile Include="..\..\..\source\xParticles\Editable.cpp" />
     <ClCompile Include="..\..\..\source\xParticles\EditorExtension.cpp" />
diff --git a/proj/xParticles/vs2013/xParticles.vcxproj.filters b/proj/xParticles/vs2013/xParticles.vcxproj.filters
index 8eebf1833b0a49d001f79604922a70f5d887a2d4..be26e103897bdab0e0b6a1a615ac18bf78fd2b1d 100644
--- a/proj/xParticles/vs2013/xParticles.vcxproj.filters
+++ b/proj/xParticles/vs2013/xParticles.vcxproj.filters
@@ -46,9 +46,6 @@
     <ClCompile Include="..\..\..\source\xParticles\EffectLoader.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\xParticles\Updatable.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/proj/xSpecs/vs2013/xSpecs.vcxproj b/proj/xSpecs/vs2013/xSpecs.vcxproj
index d27e69f1fc24204e7fd7a9f912f0388df061d431..c3d94c88d558374ac95d70ee08f2eab8aeed3250 100644
--- a/proj/xSpecs/vs2013/xSpecs.vcxproj
+++ b/proj/xSpecs/vs2013/xSpecs.vcxproj
@@ -19,7 +19,6 @@
     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\xSpecs\dllmain.cpp" />
     <ClCompile Include="..\..\..\source\xSpecs\MaterialLoader.cpp" />
     <ClCompile Include="..\..\..\source\xSpecs\MaterialProxy.cpp" />
diff --git a/proj/xSpecs/vs2013/xSpecs.vcxproj.filters b/proj/xSpecs/vs2013/xSpecs.vcxproj.filters
index 4db0e36bc9a014bb36631418704927b4717f659e..e438dc681ca5d4f2465dd9b0ffbf25010cc3383f 100644
--- a/proj/xSpecs/vs2013/xSpecs.vcxproj.filters
+++ b/proj/xSpecs/vs2013/xSpecs.vcxproj.filters
@@ -28,9 +28,6 @@
     <ClCompile Include="..\..\..\source\xSpecs\MaterialLoader.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\xSpecs\TextureProxy.h">
diff --git a/proj/xUI/vs2013/xUI.vcxproj b/proj/xUI/vs2013/xUI.vcxproj
index f925267b00c5fe54af197a4c64cbce3fa361d9e1..033aaf44d7457d0418dee7c9befc1db2a3e167b4 100644
--- a/proj/xUI/vs2013/xUI.vcxproj
+++ b/proj/xUI/vs2013/xUI.vcxproj
@@ -69,7 +69,6 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\common\guid.cpp" />
-    <ClCompile Include="..\..\..\source\common\string.cpp" />
     <ClCompile Include="..\..\..\source\xUI\CurvePreviewGraphNode.cpp" />
     <ClCompile Include="..\..\..\source\xUI\CurvePreviewGraphNodeData.cpp" />
     <ClCompile Include="..\..\..\source\xUI\GradientPreviewGraphNodeData.cpp" />
diff --git a/proj/xUI/vs2013/xUI.vcxproj.filters b/proj/xUI/vs2013/xUI.vcxproj.filters
index 531c6e555bc477605ec655dbce043a12ab5d2cde..5912ed89b5b765a42e2e110c41bb4a2a1375a16f 100644
--- a/proj/xUI/vs2013/xUI.vcxproj.filters
+++ b/proj/xUI/vs2013/xUI.vcxproj.filters
@@ -173,9 +173,6 @@
     <ClCompile Include="..\..\..\source\xUI\UIButton.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\common\string.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\xUI\UITextBox.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/sdks/graphix b/sdks/graphix
index 27fedecbe4e3921dd1da215c65a6886755c1a498..d8eeb9ae81fae1256034680abeb5b3dc14608770 160000
--- a/sdks/graphix
+++ b/sdks/graphix
@@ -1 +1 @@
-Subproject commit 27fedecbe4e3921dd1da215c65a6886755c1a498
+Subproject commit d8eeb9ae81fae1256034680abeb5b3dc14608770
diff --git a/sdks/mital b/sdks/mital
index c814073bb3bce76b111684d7afb7d74d303f55bf..384aa665b3e46cb12a4cc2f2e0b418f925b8bbd1 160000
--- a/sdks/mital
+++ b/sdks/mital
@@ -1 +1 @@
-Subproject commit c814073bb3bce76b111684d7afb7d74d303f55bf
+Subproject commit 384aa665b3e46cb12a4cc2f2e0b418f925b8bbd1
diff --git a/source/SkyXEngine_Build/SkyXEngine_Build.cpp b/source/SkyXEngine_Build/SkyXEngine_Build.cpp
index 27dd74683de49304a6a630046cc34335b1fa2241..f5c616e0d03a0b3f46aa4a41f3a2c6012d946f0b 100644
--- a/source/SkyXEngine_Build/SkyXEngine_Build.cpp
+++ b/source/SkyXEngine_Build/SkyXEngine_Build.cpp
@@ -154,7 +154,7 @@ int main(int argc, char **argv)
 	pEngine->getCore()->getConsole()->execCommand("exec ../config_game.cfg");
 	pEngine->getCore()->getConsole()->execCommand("exec ../config_game_user.cfg");
 
-#if 1
+#if 0
 
 	IFileSystem *pFS = pEngine->getCore()->getFileSystem();
 	IFile *pFile1 = pFS->openFile("dir/test.txt", FILE_MODE_READ);
diff --git a/source/anim/AnimatedModelProvider.cpp b/source/anim/AnimatedModelProvider.cpp
index 7265f05afe21f7cc46a3741aacdbd90febda6034..9147e9e896e73633729ddec5f844894268343fb8 100644
--- a/source/anim/AnimatedModelProvider.cpp
+++ b/source/anim/AnimatedModelProvider.cpp
@@ -202,6 +202,11 @@ void CAnimatedModelProvider::bindVertexFormat()
 	m_pMaterialSystem->bindVS(m_pVertexShaderHandler);
 }
 
+bool CAnimatedModelProvider::hasPendingOps()
+{
+	return(!m_queueGPUinitModel.empty() || !m_queueGPUinitShared.empty());
+}
+
 void CAnimatedModelProvider::render(CRenderableVisibility *pVisibility)
 {
 	XPROFILE_FUNCTION();
diff --git a/source/anim/AnimatedModelProvider.h b/source/anim/AnimatedModelProvider.h
index a9242e6352a19a3fd0edb65edaa17fee6974dfb9..4789f1b6483a20fd8d9f5ca619c67c5886ce0af3 100644
--- a/source/anim/AnimatedModelProvider.h
+++ b/source/anim/AnimatedModelProvider.h
@@ -37,6 +37,8 @@ public:
 
 	void bindVertexFormat();
 
+	bool hasPendingOps();
+
 protected:
 	AssotiativeArray<IXResourceModelAnimated*, Array<CAnimatedModelShared*>> m_mModels;
 
diff --git a/source/anim/Decal.cpp b/source/anim/Decal.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..18c808f591f1dc7419b80d62252a1c0d9c76d326
--- /dev/null
+++ b/source/anim/Decal.cpp
@@ -0,0 +1,657 @@
+#include "Decal.h"
+#include "DynamicModel.h"
+#include "DecalProvider.h"
+
+CDecal::CDecal(IXSceneQuery *pSceneQuery, IXRender *pRender, CDecalProvider *pProvider):
+	m_pSceneQuery(pSceneQuery),
+	m_pRender(pRender),
+	m_pProvider(pProvider)
+{
+}
+
+CDecal::~CDecal()
+{
+	removeOverlays();
+	mem_release(m_pMaterial);
+}
+
+bool XMETHODCALLTYPE CDecal::isEnabled() const
+{
+	return(m_isEnabled);
+}
+void XMETHODCALLTYPE CDecal::enable(bool yesNo)
+{
+	m_isEnabled = yesNo;
+	TODO("Change state");
+}
+
+float3 XMETHODCALLTYPE CDecal::getPosition() const
+{
+	return(m_vPos);
+}
+void XMETHODCALLTYPE CDecal::setPosition(const float3 &vPos)
+{
+	m_vPos = vPos;
+	setDirty();
+}
+
+SMQuaternion XMETHODCALLTYPE CDecal::getOrientation() const
+{
+	return(m_qRot);
+}
+void XMETHODCALLTYPE CDecal::setOrientation(const SMQuaternion &qRot)
+{
+	m_qRot = qRot;
+	setDirty();
+}
+
+float3 XMETHODCALLTYPE CDecal::getLocalBoundMin() const
+{
+	float3 vBound;
+	for(UINT i = 0; i < DECAL_POINTS; ++i)
+	{
+		if(i == 0)
+		{
+			vBound = m_qRot * float3(m_avCorners[0], 0.0f);
+		}
+		else
+		{
+			vBound = SMVectorMin(vBound, m_qRot * float3(m_avCorners[0], 0.0f));
+		}
+		vBound = SMVectorMin(vBound, m_qRot * float3(m_avCorners[0], m_fHeight));
+	}
+
+	return(vBound);
+}
+float3 XMETHODCALLTYPE CDecal::getLocalBoundMax() const
+{
+	float3 vBound;
+	for(UINT i = 0; i < DECAL_POINTS; ++i)
+	{
+		if(i == 0)
+		{
+			vBound = m_qRot * float3(m_avCorners[0], 0.0f);
+		}
+		else
+		{
+			vBound = SMVectorMax(vBound, m_qRot * float3(m_avCorners[0], 0.0f));
+		}
+		vBound = SMVectorMax(vBound, m_qRot * float3(m_avCorners[0], m_fHeight));
+	}
+
+	return(vBound);
+}
+SMAABB XMETHODCALLTYPE CDecal::getLocalBound() const
+{
+	return(SMAABB(getLocalBoundMin(), getLocalBoundMax()));
+}
+
+bool XMETHODCALLTYPE CDecal::rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut, float3 *pvNormal)
+{
+	fora(i, m_aOverlays)
+	{
+		const Overlay &overlay = m_aOverlays[i];
+		const Array<XResourceModelStaticVertex> &aVertices = overlay.pOverlay->getVertices();
+
+		float3 vRayStart = vStart;
+		float3 vRayEnd = vEnd;
+
+		if(!overlay.pModel->isStatic())
+		{
+			// transform ray to local space
+			float3 vPos = overlay.pModel->getPosition();
+			SMQuaternion qRot = overlay.pModel->getOrientation();
+
+			vRayStart = qRot.Conjugate() * vRayStart - vPos;
+			vRayEnd = qRot.Conjugate() * vRayEnd - vPos;
+		}
+
+		for(UINT j = 0, jl = aVertices.size(); j < jl; j += 4)
+		{
+			// 012 213
+			if(SMTriangleIntersectLine(aVertices[j].vPos, aVertices[j + 1].vPos, aVertices[j + 2].vPos, vRayStart, vRayEnd, pvOut))
+			{
+				if(pvNormal)
+				{
+					*pvNormal = SMVector3Normalize(SMVector3Cross(aVertices[j + 1].vPos - aVertices[j].vPos, aVertices[j + 2].vPos - aVertices[j].vPos));
+				}
+				return(true);
+			}
+
+			if(!SMIsZero(SMVector3Length2(aVertices[j + 3].vPos - aVertices[j + 2].vPos)) && SMTriangleIntersectLine(aVertices[j + 2].vPos, aVertices[j + 1].vPos, aVertices[j + 3].vPos, vRayStart, vRayEnd, pvOut))
+			{
+				if(pvNormal)
+				{
+					*pvNormal = SMVector3Normalize(SMVector3Cross(aVertices[j + 1].vPos - aVertices[j + 2].vPos, aVertices[j + 3].vPos - aVertices[j + 2].vPos));
+				}
+				return(true);
+			}
+		}
+	}
+	return(false);
+}
+
+void XMETHODCALLTYPE CDecal::setLayer(UINT uLayer)
+{
+	m_uLayer = uLayer;
+	setDirty();
+}
+UINT XMETHODCALLTYPE CDecal::getLayer()
+{
+	return(m_uLayer);
+}
+
+void XMETHODCALLTYPE CDecal::setHeight(float fHeight)
+{
+	m_fHeight = fHeight;
+	setDirty();
+}
+float XMETHODCALLTYPE CDecal::getHeight()
+{
+	return(m_fHeight);
+}
+
+void XMETHODCALLTYPE CDecal::setTextureRangeU(const float2_t &vRange)
+{
+	m_vTexRangeU = vRange;
+}
+float2_t XMETHODCALLTYPE CDecal::getTextureRangeU()
+{
+	return(m_vTexRangeU);
+}
+
+void XMETHODCALLTYPE CDecal::setTextureRangeV(const float2_t &vRange)
+{
+	m_vTexRangeV = vRange;
+}
+float2_t XMETHODCALLTYPE CDecal::getTextureRangeV()
+{
+	return(m_vTexRangeV);
+}
+
+void XMETHODCALLTYPE CDecal::setMaterial(const char *szMaterial)
+{
+	mem_release(m_pMaterial);
+	m_pProvider->getMaterialSystem()->loadMaterial(szMaterial, &m_pMaterial);
+
+	setDirty();
+}
+
+void CDecal::setMaterial(IXMaterial *pMaterial)
+{
+	mem_release(m_pMaterial);
+	m_pMaterial = pMaterial;
+	add_ref(m_pMaterial);
+
+	setDirty();
+}
+void XMETHODCALLTYPE CDecal::setCorner(UINT uCorner, const float2_t &vCorner)
+{
+	assert(uCorner < DECAL_POINTS);
+	if(uCorner < DECAL_POINTS)
+	{
+		m_avCorners[uCorner] = vCorner;
+		setDirty();
+	}
+}
+
+void CDecal::update(
+#ifdef DECAL_DEBUG_DRAW
+	IXGizmoRenderer *pDebugRenderer
+#endif
+)
+{
+	if(m_isDirty)
+	{
+		// cleanup old overlays
+		removeOverlays();
+
+		// query for objects
+		
+		SMPLANE aPlanes[] = {
+			SMPlaneNormalize(SMPLANE(float3(m_avCorners[0], 0.0f), float3(m_avCorners[1], 0.0f), float3(m_avCorners[1], m_fHeight))),
+			SMPlaneNormalize(SMPLANE(float3(m_avCorners[2], 0.0f), float3(m_avCorners[3], 0.0f), float3(m_avCorners[3], m_fHeight))),
+
+			SMPlaneNormalize(SMPLANE(float3(m_avCorners[1], 0.0f), float3(m_avCorners[2], 0.0f), float3(m_avCorners[2], m_fHeight))),
+			SMPlaneNormalize(SMPLANE(float3(m_avCorners[3], 0.0f), float3(m_avCorners[0], 0.0f), float3(m_avCorners[0], m_fHeight))),
+
+			SMPLANE(0.0f, 0.0f, 1.0f, m_fHeight),
+			SMPLANE(0.0f, 0.0f, -1.0f, m_fHeight),
+		};
+				
+		//SMMATRIX mWorld = SMMatrixTranspose(m_qRot.GetMatrix() * SMMatrixTranslation(m_vPos));
+		//SMMATRIX mWorld = m_qRot.GetMatrix();
+		for(UINT i = 0; i < ARRAYSIZE(aPlanes); ++i)
+		{
+			//aPlanes[i] = SMPlaneTransformTI(aPlanes[i], mWorld);
+			aPlanes[i] = SMPLANE(m_qRot * float3(aPlanes[i]), aPlanes[i].w);
+			aPlanes[i].w -= SMVector3Dot(aPlanes[i], m_vPos);
+		}
+
+		IXFrustum *pFrustum;
+		m_pRender->newFrustum(&pFrustum);
+		
+		pFrustum->update(aPlanes, false);
+
+#ifdef DECAL_DEBUG_DRAW
+		pDebugRenderer->setColor(float4(1.0f, 1.0f, 0.0f, 1.0f));
+		for(UINT i = 0; i < 8; ++i)
+		{
+			pDebugRenderer->drawPoint(pFrustum->getPoint(i));
+		}
+		pDebugRenderer->setColor(float4(1.0f, 1.0f, 0.0f, 0.2f));
+#endif
+
+		CDynamicModel **ppObjects = NULL;
+		m_pSceneQuery->setLayerMask(1 << m_uLayer);
+		UINT uCount = m_pSceneQuery->execute(pFrustum, (void***)&ppObjects);
+
+		SMPLANE aPlanesObjectSpace[ARRAYSIZE(aPlanes)];
+
+		// build overlays
+		for(UINT i = 0; i < uCount; ++i)
+		{
+			TODO("Check ppObjects[i]->receivingDecals()");
+
+			for(UINT j = 0; j < ARRAYSIZE(aPlanes); ++j)
+			{
+				aPlanesObjectSpace[j] = SMPLANE(ppObjects[i]->getOrientation().Conjugate() * float3(aPlanes[j]), aPlanes[j].w + SMVector3Dot(aPlanes[j], ppObjects[i]->getPosition()));
+			}
+			pFrustum->update(aPlanesObjectSpace, false);
+
+			spawnOverlayForModel(ppObjects[i], pFrustum
+#ifdef DECAL_DEBUG_DRAW
+				, pDebugRenderer
+#endif
+			);
+		}
+
+		mem_release(pFrustum);
+
+		m_isDirty = false;
+	}
+}
+
+void CDecal::onOverlayRemoved(CModelOverlay *pOverlay)
+{
+	int idx = m_aOverlays.indexOf(pOverlay, [](const Overlay &a, CModelOverlay *pB){
+		return(a.pOverlay == pB);
+	});
+	assert(idx >= 0);
+	if(idx >= 0)
+	{
+		m_aOverlays.erase(idx);
+		m_pProvider->freeOverlay(pOverlay);
+		if(!m_aOverlays.size())
+		{
+			m_pProvider->onDecalEmptied(this);
+		}
+	}
+}
+
+template<typename T>
+static T ArrGet(T *pArr, UINT uSize, UINT uIdx, int iStartOffset = 0)
+{
+	return(pArr[((int)uIdx + iStartOffset) % uSize]);
+}
+
+template<typename T>
+static void ArrDel(T *pArr, UINT &uSize, UINT uIdx, int &iStartOffset)
+{
+	UINT uStartIndex = ((int)uIdx + iStartOffset) % uSize;
+	--uSize;
+	if(uStartIndex != uSize)
+	{
+		for(UINT i = uStartIndex; i < uSize; ++i)
+		{
+			pArr[i] = pArr[i + 1];
+		}
+	}
+	if(uStartIndex < iStartOffset)
+	{
+		--iStartOffset;
+	}
+}
+
+template<typename T>
+static void ArrIns(const T &val, T *pArr, UINT &uSize, UINT uIdx, int &iStartOffset)
+{
+	UINT uStartIndex = ((int)uIdx + iStartOffset) % uSize;
+	if(uStartIndex == 0)
+	{
+		uStartIndex = uSize;
+	}
+	else
+	{
+		for(UINT i = uSize; i > uStartIndex; --i)
+		{
+			pArr[i] = pArr[i - 1];
+		}
+	}
+	assert(uStartIndex < 10);
+	pArr[uStartIndex] = val;
+	++uSize;
+
+	if(uStartIndex < iStartOffset)
+	{
+		++iStartOffset;
+	}
+}
+
+template<typename T>
+static void ArrSet(const T &val, T *pArr, UINT &uSize, UINT uIdx, int iStartOffset = 0)
+{
+	pArr[((int)uIdx + iStartOffset) % uSize] = val;
+}
+
+static bool IsInside(const SMPLANE &plane, const float3_t &vPos)
+{
+	return(SMVector4Dot(plane, float4(vPos, 1.0f)) > 0.0f);
+}
+
+void XMETHODCALLTYPE CDecal::FinalRelease()
+{
+	m_pProvider->onDecalReleased(this);
+}
+
+void CDecal::spawnOverlayForModel(CDynamicModel *pModel, IXFrustum *pFrustum
+#ifdef DECAL_DEBUG_DRAW
+	, IXGizmoRenderer *pDebugRenderer
+#endif
+)
+{
+	const IXResourceModelStatic *pResource = pModel->getResource()->asStatic();
+	if(!pResource)
+	{
+		return;
+	}
+
+	if(pResource->getPrimitiveTopology() != XPT_TRIANGLELIST)
+	{
+		LogError("CDecal::spawnOverlayForModel(): Unsupported primitive topology");
+	}
+
+	SMQuaternion qDecalToModel = m_qRot * pModel->getOrientation().Conjugate();
+	float3 vS = qDecalToModel * float3(1.0f, 0.0f, 0.0f);
+	float3 vT = qDecalToModel * float3(0.0f, 1.0f, 0.0f);
+	float3 vN = qDecalToModel * float3(0.0f, 0.0f, 1.0f);
+
+	float2_t sBound;
+	float2_t tBound;
+	// center point
+	float3 vModelSpaceDecalCenter = pModel->getOrientation().Conjugate() * (m_vPos - pModel->getPosition());
+	
+	for(UINT j = 0; j < DECAL_POINTS; ++j)
+	{
+		float s = m_avCorners[j].x;
+		float t = m_avCorners[j].y;
+		if(j == 0)
+		{
+			sBound.x = s;
+			sBound.y = s;
+			tBound.x = t;
+			tBound.y = t;
+		}
+		else
+		{
+			if(sBound.x > s)
+			{
+				sBound.x = s;
+			}
+			if(sBound.y < s)
+			{
+				sBound.y = s;
+			}
+			if(tBound.x > t)
+			{
+				tBound.x = t;
+			}
+			if(tBound.y < t)
+			{
+				tBound.y = t;
+			}
+		}
+	}
+	float2_t vInvBoundRange = float2(1.0f, 1.0f) / float2(sBound.y - sBound.x, tBound.y - tBound.x);
+	XResourceModelStaticVertex vtx;
+	vtx.vTangent = vS;
+	vtx.vBinorm = vT;
+	vtx.vNorm = vN;
+
+	float3_t aTempVertices[10]; // 9 - max vertex count of clipped triangle + 1 temp vertex
+	UINT uVertexCount;
+
+	Array<XResourceModelStaticVertex> aOverlayVertices;
+
+	float fCosThreshold = m_isLeakAllowed ? -0.1f : 0.2f;
+
+	UINT uSubsets = pResource->getSubsetCount(0);
+	for(UINT uSubset = 0; uSubset < uSubsets; ++uSubset)
+	{
+		TODO("Separate transparent subsets");
+		const XResourceModelStaticSubset *pSubset = pResource->getSubset(0, uSubset);
+		for(UINT i = 0; i < pSubset->iIndexCount; i += 3)
+		{
+			float3_t &a = pSubset->pVertices[pSubset->pIndices[i]].vPos;
+			float3_t &b = pSubset->pVertices[pSubset->pIndices[i + 1]].vPos;
+			float3_t &c = pSubset->pVertices[pSubset->pIndices[i + 2]].vPos;
+
+			float3 vTriN = SMVector3Normalize(SMVector3Cross(b - a, c - a));
+			float fCos = SMVector3Dot(vN, vTriN);
+
+			if(fCos > fCosThreshold && pFrustum->polyInFrustum(a, b, c))
+			{
+				aTempVertices[0] = a;
+				aTempVertices[1] = b;
+				aTempVertices[2] = c;
+				uVertexCount = 3;
+
+				//pDebugRenderer->drawPoly(a, b, c);
+
+				// clip by frustum planes
+				for(UINT j = 0, jl = pFrustum->getPlaneCount(); j < jl && uVertexCount >= 3/* && j < 4*/; ++j)
+				{
+					const SMPLANE &plane = pFrustum->getPlaneAt(j);
+					int iInsideVertex = -1;
+					// - find inside vertex
+					for(UINT k = 0; k < uVertexCount; ++k)
+					{
+						if(IsInside(plane, aTempVertices[k]))
+						{
+							iInsideVertex = (int)k;
+							break;
+						}
+					}
+
+					if(iInsideVertex < 0)
+					{
+						// all vertices outside clipping plane
+						uVertexCount = 0;
+						break;
+					}
+					// - rotate array to make inside vertex first
+					// - clip
+
+					for(UINT k = 0; k < uVertexCount; ++k)
+					{
+						float3_t vCur = ArrGet(aTempVertices, uVertexCount, k, iInsideVertex);
+						float3_t vNext = ArrGet(aTempVertices, uVertexCount, k + 1, iInsideVertex);
+						bool bCurInside = IsInside(plane, vCur);
+						bool bNextInside = IsInside(plane, vNext);
+						if(bCurInside)
+						{
+							if(bNextInside)
+							{
+								continue;
+							}
+							else
+							{
+								// clip
+								float3 vPt;
+								bool b = plane.intersectLine(&vPt, vCur, vNext);
+								assert(b);
+								// insert after cur
+								ArrIns((float3_t)vPt, aTempVertices, uVertexCount, k + 1, iInsideVertex);
+								++k;
+							}
+						}
+						else
+						{
+							if(bNextInside)
+							{
+								// clip
+								float3 vPt;
+								bool b = plane.intersectLine(&vPt, vCur, vNext);
+								assert(b);
+								// replace cur
+								ArrSet((float3_t)vPt, aTempVertices, uVertexCount, k, iInsideVertex);
+							}
+							else
+							{
+								// drop cur
+								ArrDel(aTempVertices, uVertexCount, k, iInsideVertex);
+								--k;
+							}
+						}
+					}
+				}
+
+				// put into overlay
+				if(uVertexCount >= 3)
+				{
+					if(pModel->isStatic())
+					{
+						// transform to world pos
+						float3 vPos = pModel->getPosition();
+						SMQuaternion qRot = pModel->getOrientation();
+						for(UINT j = 0; j < uVertexCount; ++j)
+						{
+							aTempVertices[j] = qRot * aTempVertices[j] + vPos;
+						}
+					}
+
+					UINT uStartVtx = aOverlayVertices.size();
+					for(UINT begin = 0, end = uVertexCount - 1; begin + 1 <= end - 1; ++begin, --end)
+					{
+						vtx.vPos = aTempVertices[end];
+						aOverlayVertices.push_back(vtx);
+
+						vtx.vPos = aTempVertices[begin];
+						aOverlayVertices.push_back(vtx);
+
+						vtx.vPos = aTempVertices[end - 1];
+						aOverlayVertices.push_back(vtx);
+
+						vtx.vPos = aTempVertices[begin + 1];
+						aOverlayVertices.push_back(vtx);
+					}
+
+					float3 vTriS = vS;
+					float3 vTriT = vT;
+
+					if(fCos < 0.2f)
+					{
+						float3 vRotAxis = SMVector3Cross(vTriN, vN);
+
+						float fTempS = SMVector3Dot(vRotAxis, vS);
+						float fTempT = SMVector3Dot(vRotAxis, vT);
+						
+						float fSign = max(fTempS, fTempT);
+						float fAngle = safe_acosf(fCos);
+						if(fSign < 0.0f)
+						{
+							fAngle = SM_2PI - fAngle;
+						}
+
+						SMQuaternion q(vRotAxis, fAngle);
+						vTriS = q * vS;
+						vTriT = q * vT;
+					}
+
+					for(UINT j = uStartVtx, jl = aOverlayVertices.size(); j < jl; ++j)
+					{
+						XResourceModelStaticVertex &vt = aOverlayVertices[j];
+
+						float3 vPos = vt.vPos - vModelSpaceDecalCenter;
+						float s = SMVector3Dot(vPos, vTriS);
+						float t = SMVector3Dot(vPos, vTriT);
+						vt.vPos = vt.vPos + vTriN * 0.0001f;
+
+						vt.vTex.x = lerpf(m_vTexRangeU.x, m_vTexRangeU.y, clampf((s - sBound.x) * vInvBoundRange.x, 0.0f, 1.0f));
+						vt.vTex.y = lerpf(m_vTexRangeV.x, m_vTexRangeV.y, clampf((t - tBound.x) * vInvBoundRange.y, 0.0f, 1.0f));
+					}
+				}
+			}
+		}
+	}
+
+	// aOverlayVertices
+	CModelOverlay *pOverlay = m_pProvider->allocOverlay(this, m_pMaterial, aOverlayVertices, vN);
+	m_aOverlays.push_back({pModel, pOverlay});
+
+	CModelOverlay *pLastOverlay = pModel->getOverlay();
+	if(!pLastOverlay)
+	{
+		pModel->setOverlay(pOverlay);
+	}
+	else
+	{
+		while(pLastOverlay->getNextOverlay())
+		{
+			pLastOverlay = pLastOverlay->getNextOverlay();
+		}
+		pLastOverlay->setNextOverlay(pOverlay);
+	}
+}
+
+void CDecal::removeOverlays()
+{
+	fora(i, m_aOverlays)
+	{
+		Overlay &ovl = m_aOverlays[i];
+
+		CModelOverlay *pPrev = NULL;
+		CModelOverlay *pCur = ovl.pModel->getOverlay();
+		while(pCur)
+		{
+			if(pCur == ovl.pOverlay)
+			{
+				if(pPrev)
+				{
+					pPrev->setNextOverlay(pCur->getNextOverlay());
+				}
+				else
+				{
+					ovl.pModel->setOverlay(pCur->getNextOverlay());
+				}
+
+				break;
+			}
+
+			pPrev = pCur;
+			pCur = pCur->getNextOverlay();
+		}
+
+		m_pProvider->freeOverlay(ovl.pOverlay);
+	}
+
+	m_aOverlays.clearFast();
+}
+
+void CDecal::setDirty()
+{
+	if(!m_isDirty)
+	{
+		m_isDirty = true;
+		m_pProvider->updateDecal(this);
+	}
+}
+
+void XMETHODCALLTYPE CDecal::setLeakAllowed(bool yesNo)
+{
+	if(!!yesNo != !!m_isLeakAllowed)
+	{
+		m_isLeakAllowed = yesNo;
+		setDirty();
+	}
+}
diff --git a/source/anim/Decal.h b/source/anim/Decal.h
new file mode 100644
index 0000000000000000000000000000000000000000..da5016ff7958b6f6d8c081341235125a3d3e55eb
--- /dev/null
+++ b/source/anim/Decal.h
@@ -0,0 +1,110 @@
+#ifndef __DECAL_H
+#define __DECAL_H
+
+#include <xcommon/resource/IXDecal.h>
+#include <xcommon/IXScene.h>
+#include <xcommon/render/IXRender.h>
+#include "ModelOverlay.h"
+
+#define DECAL_POINTS 4
+
+// #define DECAL_DEBUG_DRAW
+
+class CDynamicModel;
+class CDecalProvider;
+
+class CDecal final: public IXUnknownImplementation<IXDecal>
+{
+public:
+	CDecal(IXSceneQuery *pSceneQuery, IXRender *pRender, CDecalProvider *pProvider);
+	~CDecal();
+
+	bool XMETHODCALLTYPE isEnabled() const override;
+	void XMETHODCALLTYPE enable(bool yesNo) override;
+
+	float3 XMETHODCALLTYPE getPosition() const override;
+	void XMETHODCALLTYPE setPosition(const float3 &vPos) override;
+
+	SMQuaternion XMETHODCALLTYPE getOrientation() const override;
+	void XMETHODCALLTYPE setOrientation(const SMQuaternion &qRot) override;
+
+	float3 XMETHODCALLTYPE getLocalBoundMin() const override;
+	float3 XMETHODCALLTYPE getLocalBoundMax() const override;
+	SMAABB XMETHODCALLTYPE getLocalBound() const override;
+
+	bool XMETHODCALLTYPE rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut = NULL, float3 *pvNormal = NULL) override;
+
+	void XMETHODCALLTYPE setLayer(UINT uLayer) override;
+	UINT XMETHODCALLTYPE getLayer() override;
+
+	void XMETHODCALLTYPE setHeight(float fHeight) override;
+	float XMETHODCALLTYPE getHeight() override;
+
+	void XMETHODCALLTYPE setTextureRangeU(const float2_t &vRange) override;
+	float2_t XMETHODCALLTYPE getTextureRangeU() override;
+
+	void XMETHODCALLTYPE setTextureRangeV(const float2_t &vRange) override;
+	float2_t XMETHODCALLTYPE getTextureRangeV() override;
+
+	void XMETHODCALLTYPE setMaterial(const char *szMaterial) override;
+
+	void setMaterial(IXMaterial *pMaterial);
+	void XMETHODCALLTYPE setCorner(UINT uCorner, const float2_t &vCorner) override;
+
+	void XMETHODCALLTYPE setLeakAllowed(bool yesNo) override;
+
+	void update(
+#ifdef DECAL_DEBUG_DRAW
+		IXGizmoRenderer *pDebugRenderer
+#endif
+	);
+
+	// Must be called only for overlays removed with model
+	void onOverlayRemoved(CModelOverlay *pOverlay);
+
+private:
+	IXSceneQuery *m_pSceneQuery = NULL;
+	IXRender *m_pRender = NULL;
+	CDecalProvider *m_pProvider = NULL;
+
+	IXMaterial *m_pMaterial = NULL;
+	
+	float2_t m_avCorners[4];
+
+	SMQuaternion m_qRot;
+	float3_t m_vPos;
+	
+	float m_fHeight = 0.1f;
+
+	UINT m_uLayer = 0;
+
+	float2_t m_vTexRangeU = float2_t(0.0f, 1.0f);
+	float2_t m_vTexRangeV = float2_t(0.0f, 1.0f);
+
+	struct Overlay
+	{
+		CDynamicModel *pModel;
+		CModelOverlay *pOverlay;
+	};
+	Array<Overlay> m_aOverlays;
+
+	bool m_isEnabled = true;
+
+	bool m_isDirty = false;
+
+	bool m_isLeakAllowed = true;
+
+private:
+	void XMETHODCALLTYPE FinalRelease() override;
+	void spawnOverlayForModel(CDynamicModel *pModel, IXFrustum *pFrustum
+#ifdef DECAL_DEBUG_DRAW
+		, IXGizmoRenderer *pDebugRenderer
+#endif
+	);
+
+	void removeOverlays();
+
+	void setDirty();
+};
+
+#endif
diff --git a/source/anim/DecalProvider.cpp b/source/anim/DecalProvider.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1aaec25f9fe51e9aaccd002b98c58724e9f0e827
--- /dev/null
+++ b/source/anim/DecalProvider.cpp
@@ -0,0 +1,619 @@
+#include "DecalProvider.h"
+//#include <xcommon/IPluginManager.h>
+//#include <core/sxcore.h>
+#include "ModelOverlay.h"
+#include "DynamicModel.h"
+#include "DynamicModelProvider.h"
+#include <xcommon/IPluginManager.h>
+#include "Decal.h"
+
+CDecalProvider::CDecalProvider(IXCore *pCore, CDynamicModelProvider *pProviderDynamic):
+	m_pCore(pCore),
+	m_pProviderDynamic(pProviderDynamic)
+{
+	m_pMaterialSystem = (IXMaterialSystem*)pCore->getPluginManager()->getInterface(IXMATERIALSYSTEM_GUID);
+	m_pRenderUtils = (IXRenderUtils*)pCore->getPluginManager()->getInterface(IXRENDERUTILS_GUID);
+	m_pRender = (IXRender*)pCore->getPluginManager()->getInterface(IXRENDER_GUID);
+
+	IXScene *pScene = (IXScene*)pCore->getPluginManager()->getInterface(IXSCENE_GUID);
+	IXSceneObjectType *pType = pScene->getObjectType("xDynamic");
+	m_pQuery = pType->newQuery();
+	mem_release(pType);
+	
+	pCore->getConsole()->registerCVar("r_maxdecals", 300, "Max temp decals");
+	//pCore->getConsole()->registerCVar("r_max_overlapped_decals", 4, "Max overlapped decals");
+	
+#ifdef DECAL_DEBUG_DRAW
+	m_pRenderUtils->newGizmoRenderer(&m_pDebugRenderer);
+#endif
+
+	m_pMaterialSystem->loadMaterial("dev_null", &m_pNullMaterial);
+
+	loadDecals();
+}
+
+CDecalProvider::~CDecalProvider()
+{
+	mem_release(m_pNullMaterial);
+	fora(i, m_aTempDecals)
+	{
+		mem_release(m_aTempDecals[i]);
+	}
+	for(UINT i = 0; i < XDT__COUNT; ++i)
+	{
+		mem_release(m_aDecalTypes[i].pMaterial);
+	}
+	mem_release(m_pQuery);
+	mem_release(m_pBlendState);
+	mem_release(m_pDSState);
+	mem_release(m_pWorldBuffer);
+#ifdef DECAL_DEBUG_DRAW
+	mem_release(m_pDebugRenderer);
+#endif
+}
+
+void CDecalProvider::render(CRenderableVisibility *pVisibility)
+{
+	XPROFILE_FUNCTION();
+
+	CRenderableVisibility::OverlayData &overlayData = pVisibility->getOverlayData();
+	
+	UINT uVertexCount = overlayData.aVertices.size();
+
+	if(uVertexCount)
+	{
+		if(overlayData.uVertexBufferAllocSize < uVertexCount)
+		{
+			overlayData.uVertexBufferAllocSize = uVertexCount;
+			mem_release(overlayData.pRB);
+			mem_release(overlayData.pVB);
+			mem_release(overlayData.pIB);
+
+			overlayData.pVB = m_pDevice->createVertexBuffer(sizeof(XResourceModelStaticVertexGPU) * uVertexCount, GXBUFFER_USAGE_STREAM);
+			overlayData.pRB = m_pDevice->createRenderBuffer(1, &overlayData.pVB, m_pProviderDynamic->getVertexDeclaration());
+			m_pRenderUtils->getQuadIndexBuffer(uVertexCount / 4, &overlayData.pIB);
+		}
+
+		XResourceModelStaticVertexGPU *pVertices;
+		if(overlayData.pVB->lock((void**)&pVertices, GXBL_WRITE))
+		{
+			memcpy(pVertices, overlayData.aVertices, sizeof(XResourceModelStaticVertexGPU) * uVertexCount);
+			overlayData.pVB->unlock();
+		}
+
+		m_pProviderDynamic->bindVertexFormat();
+
+		IGXContext *pCtx = m_pDevice->getThreadContext();
+		pCtx->setRenderBuffer(overlayData.pRB);
+		pCtx->setIndexBuffer(overlayData.pIB);
+		pCtx->setPrimitiveTopology(GXPT_TRIANGLELIST);
+		IGXBlendState *pOldBlendState = pCtx->getBlendState();
+		pCtx->setBlendState(m_pBlendState);
+		m_pDevice->getThreadContext()->setVSConstant(m_pWorldBuffer, 1 /* SCR_OBJECT */);
+		IGXDepthStencilState *pOldDSState = pCtx->getDepthStencilState();
+		pCtx->setDepthStencilState(m_pDSState);
+
+		fora(i, overlayData.aSubsets)
+		{
+			auto &ss = overlayData.aSubsets[i];
+			m_pMaterialSystem->bindMaterial(ss.pMaterial);
+			pCtx->drawIndexed(ss.uQuadCount * 4, ss.uQuadCount * 2, ss.uStartIndex);
+		}
+
+		pCtx->setBlendState(pOldBlendState);
+		mem_release(pOldBlendState);
+
+		pCtx->setDepthStencilState(pOldDSState);
+		mem_release(pOldDSState);
+	}
+
+#ifdef DECAL_DEBUG_DRAW
+	m_pDebugRenderer->render(false, false);
+#endif
+}
+
+bool XMETHODCALLTYPE CDecalProvider::newDecal(IXDecal **ppDecal)
+{
+	CDecal *pDecal = allocDecal();
+	pDecal->setMaterial(m_pNullMaterial);
+
+	*ppDecal = pDecal;
+
+	return(true);
+}
+
+void XMETHODCALLTYPE CDecalProvider::shootDecal(XDECAL_TYPE type, const float3 &vWorldPos, const float3 &vNormal, float fScale, const float3 *pvSAxis)
+{
+	//type = XDT_GLASS;
+	const DecalType *pType = getDecalType(type);
+	if(!pType || !pType->pMaterial)
+	{
+		return;
+	}
+
+	fScale *= pType->fBaseScale * 0.0008f;
+
+	IXTexture *pTex = pType->pMaterial->getTexture("txBase");
+	if(!pTex)
+	{
+		IKeyIterator *pIter = pType->pMaterial->getTexturesIterator();
+		if(pIter)
+		{
+			pTex = pType->pMaterial->getTexture(pIter->getCurrent());
+		}
+		mem_release(pIter);
+	}
+
+	UINT uTexWidth = pTex->getWidth();
+	UINT uTexHeight = pTex->getHeight();
+
+	DecalTexRange texRange = {0, 0, uTexWidth, uTexHeight};
+	if(pType->aTextures.size())
+	{
+		texRange = pType->aTextures[rand() % pType->aTextures.size()];
+	}
+	
+	float2_t vSize((float)(texRange.xmax - texRange.xmin) * fScale, (float)(texRange.ymax - texRange.ymin) * fScale);
+
+	float2_t sBound(-vSize.x * 0.5f, vSize.x * 0.5f);
+	float2_t tBound(-vSize.y * 0.5f, vSize.y * 0.5f);
+
+	//compute basis
+	SMMATRIX mBasis;
+	computeBasis(&mBasis, SMVector3Normalize(vNormal), pvSAxis);
+
+	SMQuaternion qOrient = SMQuaternion(mBasis.r[0], mBasis.r[1], mBasis.r[2]).Normalize();
+
+	//vWorldPos;
+	//qOrient;
+	//pType->pMaterial;
+	float2_t avCorners[] = {
+		float2_t(sBound.x, tBound.x),
+		float2_t(sBound.x, tBound.y),
+		float2_t(sBound.y, tBound.y),
+		float2_t(sBound.y, tBound.x),
+	};
+	float fHeight = min(vSize.x, vSize.y) * 0.5f + 0.1f;
+
+#ifdef DECAL_DEBUG_DRAW
+	m_pDebugRenderer->setPointSize(0.01f);
+	m_pDebugRenderer->setLineWidth(0.01f);
+	m_pDebugRenderer->setColor(float4(1.0f, 0.0f, 0.0f, 1.0f));
+	m_pDebugRenderer->jumpTo(vWorldPos);
+	m_pDebugRenderer->lineTo(vWorldPos + mBasis.r[0]);
+	m_pDebugRenderer->setColor(float4(0.0f, 1.0f, 0.0f, 1.0f));
+	m_pDebugRenderer->jumpTo(vWorldPos);
+	m_pDebugRenderer->lineTo(vWorldPos + mBasis.r[1]);
+	m_pDebugRenderer->setColor(float4(0.0f, 0.0f, 1.0f, 1.0f));
+	m_pDebugRenderer->jumpTo(vWorldPos);
+	m_pDebugRenderer->lineTo(vWorldPos + mBasis.r[2]);
+
+	m_pDebugRenderer->setColor(float4(1.0f, 1.0f, 1.0f, 1.0f));
+	/*for(UINT i = 0; i < ARRAYSIZE(avCorners); ++i)
+	{
+		m_pDebugRenderer->drawPoint(qOrient * float3(avCorners[i], -fHeight) + vWorldPos);
+		m_pDebugRenderer->drawPoint(qOrient * float3(avCorners[i], fHeight) + vWorldPos);
+	}*/
+#endif
+
+	// create CDecal
+	CDecal *pDecal = allocDecal();
+	pDecal->setMaterial(pType->pMaterial);
+	for(UINT i = 0; i < DECAL_POINTS; ++i)
+	{
+		pDecal->setCorner(i, avCorners[i]);
+	}
+	pDecal->setPosition(vWorldPos);
+	pDecal->setOrientation(qOrient);
+	pDecal->setHeight(fHeight);
+	pDecal->setTextureRangeU(float2((float)texRange.xmin, (float)texRange.xmax) / (float)uTexWidth);
+	pDecal->setTextureRangeV(float2((float)texRange.ymin, (float)texRange.ymax) / (float)uTexHeight);
+
+	addTempDecal(pDecal);
+}
+
+void CDecalProvider::computeVisibility(const float3 &vHintDir, CRenderableVisibility *pVisibility)
+{
+	Array<CDynamicModel*> &aRenderList = pVisibility->getRenderList();
+
+	struct TmpOverlay
+	{
+		CDynamicModel *pModel;
+		CModelOverlay *pOverlay;
+	};
+
+	Array<TmpOverlay> aOverlays;
+
+	bool useHintDir = !SMIsZero(SMVector3Length2(vHintDir));
+
+	UINT uVertices = 0;
+	fora(i, aRenderList)
+	{
+		CDynamicModel *pMdl = aRenderList[i];
+		CModelOverlay *pOverlay = pMdl->getOverlay();
+		while(pOverlay)
+		{
+			// skip backface overlays
+			bool bRender = !useHintDir || pOverlay->getMaterial()->isTwoSided();
+			if(!bRender)
+			{
+				float3 vNormal = pOverlay->getNormal();
+				if(!pMdl->isStatic())
+				{
+					vNormal = pMdl->getOrientation() * vNormal;
+				}
+				bRender = SMVector3Dot(vNormal, vHintDir) < 0.0f;
+			}
+			if(bRender)
+			{
+				aOverlays.push_back({pMdl, pOverlay});
+
+				uVertices += pOverlay->getVertices().size();
+			}
+
+			pOverlay = pOverlay->getNextOverlay();
+		}
+	}
+
+	CRenderableVisibility::OverlayData &overlayData = pVisibility->getOverlayData();
+
+	Array<XResourceModelStaticVertexGPU> &aVertices = overlayData.aVertices;
+	aVertices.clearFast();
+	aVertices.reserve(uVertices);
+	Array<CRenderableVisibility::OverlaySubset>& aSubsets = overlayData.aSubsets;
+	aSubsets.clearFast();
+	
+	aOverlays.quickSort([](const TmpOverlay &a, const TmpOverlay &b){
+		return(a.pOverlay->getMaterial() < b.pOverlay->getMaterial());
+	});
+
+	XResourceModelStaticVertex tempVertex;
+	XResourceModelStaticVertexGPU tempGpuVertex;
+	CRenderableVisibility::OverlaySubset curSubset = {};
+	fora(i, aOverlays)
+	{
+		TmpOverlay &overlay = aOverlays[i];
+
+		if(curSubset.pMaterial != overlay.pOverlay->getMaterial())
+		{
+			if(curSubset.uStartVertex != aVertices.size())
+			{
+				curSubset.uQuadCount = (aVertices.size() - curSubset.uStartVertex) / 4;
+				aSubsets.push_back(curSubset);
+			}
+			curSubset.pMaterial = overlay.pOverlay->getMaterial();
+
+			curSubset.uStartVertex = aVertices.size();
+			curSubset.uStartIndex = aVertices.size() / 4 * 6;
+		}
+
+		const Array<XResourceModelStaticVertex>& aOverlayVertices = overlay.pOverlay->getVertices();
+		bool bNeedTransform = !overlay.pModel->isStatic();
+
+		float3 vPos = overlay.pModel->getPosition();
+		SMQuaternion qRot = overlay.pModel->getOrientation();
+
+		fora(j, aOverlayVertices)
+		{
+			tempVertex = aOverlayVertices[j];
+			if(bNeedTransform)
+			{
+				// transform vertices
+				tempVertex.vPos = qRot * tempVertex.vPos + vPos;
+				tempVertex.vNorm = qRot * tempVertex.vNorm;
+				tempVertex.vTangent = qRot * tempVertex.vTangent;
+				tempVertex.vBinorm = qRot * tempVertex.vBinorm;
+			}
+			
+#define TO_SHORT(v) ((short)((v) * 32767.0f))
+			auto &dst = tempGpuVertex;
+			auto &src = tempVertex;
+			dst.vPos = src.vPos;
+			dst.vTex = src.vTex;
+			dst.vNorm[0] = TO_SHORT(src.vNorm.x);
+			dst.vNorm[1] = TO_SHORT(src.vNorm.y);
+			dst.vNorm[2] = TO_SHORT(src.vNorm.z);
+			dst.vTangent[0] = TO_SHORT(src.vTangent.x);
+			dst.vTangent[1] = TO_SHORT(src.vTangent.y);
+			dst.vTangent[2] = TO_SHORT(src.vTangent.z);
+			dst.vBinorm[0] = TO_SHORT(src.vBinorm.x);
+			dst.vBinorm[1] = TO_SHORT(src.vBinorm.y);
+			dst.vBinorm[2] = TO_SHORT(src.vBinorm.z);
+#undef TO_SHORT
+
+			aVertices.push_back(tempGpuVertex);
+		}	
+	}
+
+	if(curSubset.uStartVertex != aVertices.size())
+	{
+		curSubset.uQuadCount = (aVertices.size() - curSubset.uStartVertex) / 4;
+		aSubsets.push_back(curSubset);
+	}
+}
+
+void CDecalProvider::setDevice(IGXDevice *pDevice)
+{
+	m_pDevice = pDevice;
+
+	GXBlendDesc blendDesc;
+	memset(&blendDesc, 0, sizeof(blendDesc));
+	blendDesc.renderTarget[0].u8RenderTargetWriteMask = GXCOLOR_WRITE_ENABLE_RED | GXCOLOR_WRITE_ENABLE_GREEN | GXCOLOR_WRITE_ENABLE_BLUE;
+	blendDesc.renderTarget[0].blendSrcColor = GXBLEND_DEST_COLOR;
+	blendDesc.renderTarget[0].blendDestColor = GXBLEND_SRC_COLOR;
+	blendDesc.renderTarget[0].blendSrcAlpha = GXBLEND_SRC_ALPHA;
+	blendDesc.renderTarget[0].blendDestAlpha = GXBLEND_INV_SRC_ALPHA;
+	blendDesc.renderTarget[0].blendOpColor = GXBLEND_OP_ADD;
+	blendDesc.renderTarget[0].blendOpAlpha = GXBLEND_OP_ADD;
+
+	/*blendDesc.renderTarget[0].blendSrcColor = GXBLEND_DEST_COLOR;
+	blendDesc.renderTarget[0].blendDestColor = GXBLEND_SRC_COLOR;
+	blendDesc.renderTarget[0].blendSrcAlpha = GXBLEND_DEST_ALPHA;
+	blendDesc.renderTarget[0].blendDestAlpha = GXBLEND_SRC_ALPHA;
+	blendDesc.renderTarget[0].blendOpColor = GXBLEND_OP_ADD;
+	blendDesc.renderTarget[0].blendOpAlpha = GXBLEND_OP_ADD;*/
+
+	blendDesc.renderTarget[0].useBlend = TRUE;
+
+	blendDesc.renderTarget[1].blendSrcColor = GXBLEND_ZERO;
+	blendDesc.renderTarget[1].blendDestColor = GXBLEND_ONE;
+
+	blendDesc.renderTarget[2].blendSrcColor = GXBLEND_ONE;
+	blendDesc.renderTarget[2].blendDestColor = GXBLEND_ZERO;
+
+	blendDesc.renderTarget[3].blendSrcColor = GXBLEND_ONE;
+	blendDesc.renderTarget[3].blendDestColor = GXBLEND_ZERO;
+
+	blendDesc.renderTarget[1].useBlend = FALSE;
+	blendDesc.renderTarget[2].useBlend = FALSE;
+	blendDesc.renderTarget[3].useBlend = FALSE;
+
+	blendDesc.useIndependentBlend = TRUE;
+
+	m_pBlendState = pDevice->createBlendState(&blendDesc);
+
+
+	GXDepthStencilDesc dsDesc = {};
+	dsDesc.cmpFuncDepth = GXCMP_GREATER_EQUAL;
+	dsDesc.useDepthWrite = FALSE;
+	m_pDSState = pDevice->createDepthStencilState(&dsDesc);
+
+
+	m_pWorldBuffer = pDevice->createConstantBuffer(sizeof(SMMATRIX));
+	m_pWorldBuffer->update(&SMMatrixIdentity());
+}
+
+void CDecalProvider::onDecalReleased(CDecal *pDecal)
+{
+	ScopedSpinLock lock(m_slMemDecals);
+	m_memDecals.Delete(pDecal);
+}
+
+void CDecalProvider::onDecalEmptied(CDecal *pDecal)
+{
+	int idx = -1;
+	{
+		ScopedSpinLock lock(m_slTempDecals);
+
+		idx = m_aTempDecals.indexOf(pDecal);
+		if(idx >= 0)
+		{
+			m_aTempDecals.erase(idx);
+		}
+	}
+
+	if(idx >= 0)
+	{
+		mem_release(pDecal);
+	}
+}
+
+void CDecalProvider::updateDecal(CDecal *pDecal)
+{
+	add_ref(pDecal);
+	m_qDecalsForUpdate.push(pDecal);
+}
+
+void CDecalProvider::update()
+{
+	if(!m_isEnabled)
+	{
+		return;
+	}
+
+	CDecal *pDecal;
+	while(m_qDecalsForUpdate.pop(&pDecal))
+	{
+		pDecal->update(
+#ifdef DECAL_DEBUG_DRAW
+			m_pDebugRenderer
+#endif
+		);
+		mem_release(pDecal);
+	}
+}
+
+CDecal* CDecalProvider::allocDecal()
+{
+	ScopedSpinLock lock(m_slMemDecals);
+	return(m_memDecals.Alloc(m_pQuery, m_pRender, this));
+}
+
+CModelOverlay* CDecalProvider::allocOverlay(CDecal *pDecal, IXMaterial *pMaterial, Array<XResourceModelStaticVertex> &aVertices, const float3_t &vNormal)
+{
+	ScopedSpinLock lock(m_slMemOverlays);
+	return(m_memOverlays.Alloc(pDecal, pMaterial, aVertices, vNormal));
+}
+void CDecalProvider::freeOverlay(CModelOverlay *pOverlay)
+{
+	ScopedSpinLock lock(m_slMemOverlays);
+	m_memOverlays.Delete(pOverlay);
+}
+
+IXMaterialSystem* CDecalProvider::getMaterialSystem()
+{
+	return(m_pMaterialSystem);
+}
+
+void CDecalProvider::setEnabled(bool yesNo)
+{
+	m_isEnabled = yesNo;
+}
+
+void CDecalProvider::loadDecals()
+{
+	IXConfig *pConfig = m_pCore->newConfig();
+	if(pConfig->open("config/decals/decals.cfg"))
+	{
+		UINT uSections = pConfig->getSectionCount();
+		for(UINT i = 0; i < uSections; ++i)
+		{
+			const char *szSection = pConfig->getSectionName(i);
+
+			int id;
+			if(pConfig->keyExists(szSection, "id") && sscanf(pConfig->getKey(szSection, "id"), "%d", &id))
+			{
+				if(id < 0 || id >= XDT__COUNT)
+				{
+					LogError("Incorrect decal type id '%s'\n", szSection);
+					continue;
+				}
+			}
+			else
+			{
+				LogError("Unable to read decal id '%s'\n", szSection);
+				continue;
+			}
+
+			const char *szTex = pConfig->getKey(szSection, "tex");
+			if(!szTex)
+			{
+				LogError("Unable to read decal tex '%s'\n", szSection);
+				continue;
+			}
+
+			const char *szScale = pConfig->getKey(szSection, "base_scale");
+			if(szScale)
+			{
+				sscanf(szScale, "%f", &m_aDecalTypes[id].fBaseScale);
+			}
+
+			
+			int j = 0;
+
+			DecalTexRange rng;
+			char key[64];
+			while(sprintf(key, "tex%d", j) && pConfig->keyExists(szSection, key))
+			{
+				if(sscanf(pConfig->getKey(szSection, key), "[%d,%d,%d,%d]", &rng.xmin, &rng.ymin, &rng.xmax, &rng.ymax) != 4)
+				{
+					LogError("Unable to read decal tex coords \"%s\" \"%s\"\n", pConfig->getKey(szSection, key), szSection);
+				}
+				else
+				{
+					m_aDecalTypes[id].aTextures.push_back(rng);
+				}
+				j++;
+			}
+
+			m_pMaterialSystem->loadMaterial(szTex, &m_aDecalTypes[id].pMaterial);
+		}
+	}
+
+	mem_release(pConfig);
+}
+
+const CDecalProvider::DecalType* CDecalProvider::getDecalType(XDECAL_TYPE type)
+{
+	if(type < 0 || type >= XDT__COUNT)
+	{
+		LogError("Incorrect decal type %d\n", type);
+		return(NULL);
+	}
+	return(&m_aDecalTypes[type]);
+}
+
+void CDecalProvider::computeBasis(SMMATRIX *pmOut, const float3_t &vSurfaceNormal, const float3 *pvSAxis)
+{
+	// s, t, textureSpaceNormal (S cross T = textureSpaceNormal(N))
+	//         
+	//      +---->S
+	//     /| 
+	//	  /	|  
+	//   N  |T    
+	//
+	// S = textureSpaceBasis[0]
+	// T = textureSpaceBasis[1]
+	// N = textureSpaceBasis[2]
+
+	// Get the surface normal.
+	pmOut->r[2] = vSurfaceNormal;
+
+	if(pvSAxis)
+	{
+		// T = N cross S
+		pmOut->r[1] = SMVector3Cross(pmOut->r[2], *pvSAxis);
+
+		// Name sure they aren't parallel or antiparallel
+		// In that case, fall back to the normal algorithm.
+		if(SMVector3Dot(pmOut->r[1], pmOut->r[1]) > 1e-6)
+		{
+			// S = T cross N
+			pmOut->r[0] = SMVector3Cross(pmOut->r[1], pmOut->r[2]);
+
+			pmOut->r[0] = SMVector3Normalize(pmOut->r[0]);
+			pmOut->r[1] = SMVector3Normalize(pmOut->r[1]);
+			return;
+		}
+
+		// Fall through to the standard algorithm for parallel or antiparallel
+	}
+
+	// floor/ceiling?
+	if(fabs(vSurfaceNormal.y) > SIN_45_DEGREES)
+	{
+		pmOut->r[0] = float4(1.0f, 0.0f, 0.0f, 0.0f);
+
+		// T = N cross S
+		pmOut->r[1] = SMVector3Cross(pmOut->r[2], pmOut->r[0]);
+
+		// S = T cross N
+		pmOut->r[0] = SMVector3Cross(pmOut->r[1], pmOut->r[2]);
+	}
+	// wall
+	else
+	{
+		pmOut->r[1] = float4(0.0f, -1.0f, 0.0f, 0.0f);
+
+		// S = T cross N
+		pmOut->r[0] = SMVector3Cross(pmOut->r[1], pmOut->r[2]);
+		// T = N cross S
+		pmOut->r[1] = SMVector3Cross(pmOut->r[2], pmOut->r[0]);
+	}
+
+	pmOut->r[0] = SMVector3Normalize(pmOut->r[0]);
+	pmOut->r[1] = SMVector3Normalize(pmOut->r[1]);
+}
+
+void CDecalProvider::addTempDecal(CDecal *pDecal)
+{
+	ScopedSpinLock lock(m_slTempDecals);
+
+	static const int *r_maxdecals = m_pCore->getConsole()->getPCVarInt("r_maxdecals");
+	if(m_aTempDecals.size() > *r_maxdecals)
+	{
+		for(UINT i = (UINT)*r_maxdecals, l = m_aTempDecals.size(); i < l; ++i)
+		{
+			mem_release(m_aTempDecals[i]);
+		}
+	}
+	m_aTempDecals.reserve(*r_maxdecals);
+
+	TODO("Use ring array for that");
+	if(m_aTempDecals.size() == *r_maxdecals)
+	{
+		mem_release(m_aTempDecals[0]);
+		m_aTempDecals.erase(0);
+	}
+	m_aTempDecals.push_back(pDecal);
+}
diff --git a/source/anim/DecalProvider.h b/source/anim/DecalProvider.h
new file mode 100644
index 0000000000000000000000000000000000000000..58580748fa70f2bdb7161c697fee0b133a41d625
--- /dev/null
+++ b/source/anim/DecalProvider.h
@@ -0,0 +1,114 @@
+#ifndef __DECALPROVIDER_H
+#define __DECALPROVIDER_H
+
+#include <xcommon/resource/IXDecalProvider.h>
+#include <xcommon/IXCore.h>
+#include "RenderableVisibility.h"
+//#include <mtrl/IXMaterialSystem.h>
+//#include <common/ConcurrentQueue.h>
+//#include <xcommon/XEvents.h>
+#include <xcommon/IXScene.h>
+#include <common/queue.h>
+#include "Decal.h"
+
+#define SIN_45_DEGREES 0.70710678118654752440084436210485f
+
+class CDecalProvider: public IXUnknownImplementation<IXDecalProvider>
+{
+public:
+	CDecalProvider(IXCore *pCore, CDynamicModelProvider *pProviderDynamic);
+	~CDecalProvider();
+
+	void render(CRenderableVisibility *pVisibility);
+
+	bool XMETHODCALLTYPE newDecal(IXDecal **ppDecal) override;
+
+	void XMETHODCALLTYPE shootDecal(XDECAL_TYPE type, const float3 &vWorldPos, const float3 &vNormal, float fScale = 1.0f, const float3 *pvSAxis = NULL) override;
+
+	void computeVisibility(const float3 &vHintDir, CRenderableVisibility *pVisibility);
+
+	void setDevice(IGXDevice *pDevice);
+
+	void onDecalReleased(CDecal *pDecal);
+
+	void onDecalEmptied(CDecal *pDecal);
+
+	void updateDecal(CDecal *pDecal);
+
+	void update();
+
+	CModelOverlay* allocOverlay(CDecal *pDecal, IXMaterial *pMaterial, Array<XResourceModelStaticVertex> &aVertices, const float3_t &vNormal);
+	void freeOverlay(CModelOverlay *pOverlay);
+
+	IXMaterialSystem* getMaterialSystem();
+
+	void setEnabled(bool yesNo);
+
+private:
+	struct DecalTexRange
+	{
+		int xmin;
+		int ymin;
+		int xmax;
+		int ymax;
+	};
+
+	struct DecalType
+	{
+		Array<DecalTexRange> aTextures;
+		float fBaseScale = 1.0f;
+		IXMaterial *pMaterial = NULL;
+	};
+
+	/*struct TempDecal
+	{
+		CDecal *pDecal;
+	};*/
+
+private:
+	IXCore *m_pCore = NULL;
+	CDynamicModelProvider *m_pProviderDynamic = NULL;
+	IGXDevice *m_pDevice = NULL;
+	IXMaterialSystem *m_pMaterialSystem = NULL;
+	IXRender *m_pRender = NULL;
+	IXRenderUtils *m_pRenderUtils = NULL;
+	IXSceneQuery *m_pQuery = NULL;
+
+	IXMaterial *m_pNullMaterial = NULL;
+
+	IGXConstantBuffer *m_pWorldBuffer = NULL;
+
+	IGXBlendState *m_pBlendState = NULL;
+	IGXDepthStencilState *m_pDSState = NULL;
+
+	DecalType m_aDecalTypes[XDT__COUNT];
+
+#ifdef DECAL_DEBUG_DRAW
+	IXGizmoRenderer *m_pDebugRenderer = NULL;
+#endif
+
+	MemAlloc<CDecal> m_memDecals;
+	MemAlloc<CModelOverlay> m_memOverlays;
+	SpinLock m_slMemDecals;
+	SpinLock m_slMemOverlays;
+
+	Array<CDecal*> m_aTempDecals;
+	SpinLock m_slTempDecals;
+
+	Queue<CDecal*> m_qDecalsForUpdate;
+
+	bool m_isEnabled = true;
+
+private:
+	void loadDecals();
+
+	const DecalType* getDecalType(XDECAL_TYPE type);
+
+	void computeBasis(SMMATRIX *pmOut, const float3_t &vSurfaceNormal, const float3 *pvSAxis = NULL);
+
+	CDecal* allocDecal();
+
+	void addTempDecal(CDecal *pDecal);
+};
+
+#endif
diff --git a/source/anim/DynamicModel.cpp b/source/anim/DynamicModel.cpp
index 0a79dbe82ff68b47fe0a024e1a8761ada11a8a6d..85ad2e562d43aec535b0cc217b183e07db86198f 100644
--- a/source/anim/DynamicModel.cpp
+++ b/source/anim/DynamicModel.cpp
@@ -31,6 +31,8 @@ CDynamicModel::~CDynamicModel()
 	mem_release(m_pWorldBuffer);
 	mem_release(m_pColorBuffer);
 	mem_release(m_pSceneObject);
+
+	SAFE_CALL(m_pOverlay, onModelRemoved);
 }
 
 void CDynamicModel::initGPUresources()
@@ -80,7 +82,7 @@ IXDynamicModel * XMETHODCALLTYPE CDynamicModel::asDynamicModel()
 }
 IXStaticModel * XMETHODCALLTYPE CDynamicModel::asStaticModel()
 {
-	return(NULL);
+	return(isStatic() ? this : NULL);
 }
 
 float3 XMETHODCALLTYPE CDynamicModel::getPosition() const
@@ -209,11 +211,11 @@ void CDynamicModel::_updateAABB() const
 	m_vLocalMin = (float3)SMVectorMin(
 		SMVectorMin(SMVectorMin(vCurrent[0], vCurrent[1]), SMVectorMin(vCurrent[2], vCurrent[3])),
 		SMVectorMin(SMVectorMin(vCurrent[4], vCurrent[5]), SMVectorMin(vCurrent[6], vCurrent[7]))
-		);
+	);
 	m_vLocalMax = (float3)SMVectorMax(
 		SMVectorMax(SMVectorMax(vCurrent[0], vCurrent[1]), SMVectorMax(vCurrent[2], vCurrent[3])),
 		SMVectorMax(SMVectorMax(vCurrent[4], vCurrent[5]), SMVectorMax(vCurrent[6], vCurrent[7]))
-		);
+	);
 
 	m_isLocalAABBvalid = true;
 }
@@ -466,3 +468,23 @@ UINT XMETHODCALLTYPE CDynamicModel::getLayer()
 {
 	return(m_uLayer);
 }
+
+CModelOverlay* CDynamicModel::getOverlay()
+{
+	return(m_pOverlay);
+}
+
+void CDynamicModel::setOverlay(CModelOverlay *pOverlay)
+{
+	m_pOverlay = pOverlay;
+}
+
+void CDynamicModel::setStatic(bool yesNo)
+{
+	m_isStatic = yesNo;
+}
+
+bool CDynamicModel::isStatic()
+{
+	return(m_isStatic);
+}
diff --git a/source/anim/DynamicModel.h b/source/anim/DynamicModel.h
index 836ebdda06dc00c7aafa6efa8f93dd7fe363cd42..97612f2543b3fec1422acfc786f337b60bd9aeea 100644
--- a/source/anim/DynamicModel.h
+++ b/source/anim/DynamicModel.h
@@ -4,6 +4,7 @@
 #include <xcommon/resource/IXModel.h>
 #include <xcommon/IXScene.h>
 #include "DynamicModelShared.h"
+#include "ModelOverlay.h"
 
 class CDynamicModel final: public IXUnknownImplementation<IXDynamicModel>
 {
@@ -58,7 +59,14 @@ public:
 
 	void XMETHODCALLTYPE setLayer(UINT uLayer) override;
 	UINT XMETHODCALLTYPE getLayer() override;
-protected:
+
+	CModelOverlay* getOverlay();
+	void setOverlay(CModelOverlay *pOverlay);
+
+	void setStatic(bool yesNo);
+	bool isStatic();
+
+private:
 	CDynamicModelProvider *m_pProvider;
 	CDynamicModelShared *m_pShared;
 	IGXDevice *m_pDevice;
@@ -75,17 +83,21 @@ protected:
 	UINT m_uSkin = 0;
 	float4_t m_vColor{1.0f, 1.0f, 1.0f, 1.0f};
 	bool m_isEnabled = true;
+	bool m_isStatic = false;
 	float m_fScale = 1.0f;
 
 	mutable bool m_isLocalAABBvalid = false;
 	mutable float3_t m_vLocalMin;
 	mutable float3_t m_vLocalMax;
 
+	UINT m_uLayer = 0;
+
+	CModelOverlay *m_pOverlay = NULL;
+
+private:
 	void _updateAABB() const;
 
 	void XMETHODCALLTYPE FinalRelease() override;
-
-	UINT m_uLayer = 0;
 };
 
 #endif
diff --git a/source/anim/DynamicModelProvider.cpp b/source/anim/DynamicModelProvider.cpp
index 4ce0af11d6c8855f0963e177aa8165be14fbcbe7..36af3b00e4a7f2d4c05454888f9b305f7f49e4cf 100644
--- a/source/anim/DynamicModelProvider.cpp
+++ b/source/anim/DynamicModelProvider.cpp
@@ -541,6 +541,8 @@ void CDynamicModelProvider::computeVisibility(const IXFrustum *pFrustum, const f
 {
 	XPROFILE_FUNCTION();
 
+	TODO("Make some hints to not to compute stages is not required for render. eg selfillum stage for shadows");
+
 	if(pCamera && pCamera->getProjectionMode() == XCPM_PERSPECTIVE)
 	{
 		FIXME("Use actual target width!");
@@ -806,3 +808,8 @@ void CDynamicModelProvider::enqueueModelDelete(CDynamicModel* pModel)
 {
 	m_qModelDelete.push(pModel);
 }
+
+bool CDynamicModelProvider::hasPendingOps()
+{
+	return(!m_queueGPUinitModel.empty() || !m_queueGPUinitShared.empty());
+}
diff --git a/source/anim/DynamicModelProvider.h b/source/anim/DynamicModelProvider.h
index 3b126cae9b99e10c9e4de2f924fea8fa1b8af757..ce66cf3ea02d6d24e9fea5c88a0ffa2df4b4b933 100644
--- a/source/anim/DynamicModelProvider.h
+++ b/source/anim/DynamicModelProvider.h
@@ -70,6 +70,9 @@ public:
 	IXSceneFeature* getFeature(XMODEL_FEATURE bmFeature);
 
 	void enqueueModelDelete(CDynamicModel* pModel);
+
+	bool hasPendingOps();
+
 protected:
 	void onMaterialEmissivityChanged(const IXMaterial *pMaterial);
 	void onMaterialTransparencyChanged(const IXMaterial *pMaterial);
diff --git a/source/anim/ModelOverlay.cpp b/source/anim/ModelOverlay.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ead2048997a2aea32fff6996aa5b413880429bf5
--- /dev/null
+++ b/source/anim/ModelOverlay.cpp
@@ -0,0 +1,49 @@
+#include "ModelOverlay.h"
+#include "Decal.h"
+
+CModelOverlay::CModelOverlay(CDecal *pDecal, IXMaterial *pMaterial, Array<XResourceModelStaticVertex> &aVertices, const float3_t &vNormal):
+	m_pDecal(pDecal),
+	m_pMaterial(pMaterial),
+	m_vNormal(vNormal)
+{
+	m_aVertices.swap(aVertices);
+	add_ref(m_pMaterial);
+}
+
+CModelOverlay::~CModelOverlay()
+{
+	mem_release(m_pMaterial);
+}
+
+
+const Array<XResourceModelStaticVertex>& CModelOverlay::getVertices()
+{
+	return(m_aVertices);
+}
+
+CModelOverlay* CModelOverlay::getNextOverlay()
+{
+	return(m_pNextOverlay);
+}
+void CModelOverlay::setNextOverlay(CModelOverlay *pOverlay)
+{
+	m_pNextOverlay = pOverlay;
+}
+
+IXMaterial* CModelOverlay::getMaterial()
+{
+	return(m_pMaterial);
+}
+
+const float3_t& CModelOverlay::getNormal()
+{
+	return(m_vNormal);
+}
+
+void CModelOverlay::onModelRemoved()
+{
+	SAFE_CALL(m_pNextOverlay, onModelRemoved);
+
+	//notify decal
+	m_pDecal->onOverlayRemoved(this);
+}
diff --git a/source/anim/ModelOverlay.h b/source/anim/ModelOverlay.h
new file mode 100644
index 0000000000000000000000000000000000000000..4404a20c1a65f57d4472ef95f4bb4bf5efea15ef
--- /dev/null
+++ b/source/anim/ModelOverlay.h
@@ -0,0 +1,43 @@
+#ifndef __MODELOVERLAY_H
+#define __MODELOVERLAY_H
+
+#include <xcommon/resource/IXModel.h>
+#include <graphix/graphix.h>
+#include <mtrl/IXMaterial.h>
+
+class IXMaterialSystem;
+class CDecal;
+
+class CModelOverlay final
+{
+public:
+	CModelOverlay(CDecal *pDecal, IXMaterial *pMaterial, Array<XResourceModelStaticVertex> &aVertices, const float3_t &vNormal);
+	~CModelOverlay();
+
+	const Array<XResourceModelStaticVertex>& getVertices();
+
+	CModelOverlay* getNextOverlay();
+	void setNextOverlay(CModelOverlay *pOverlay);
+
+	IXMaterial* getMaterial();
+
+	const float3_t& getNormal();
+
+	void onModelRemoved();
+
+private:
+	// Model's overlays stored as linked list
+	CModelOverlay *m_pNextOverlay = NULL;
+	CDecal *m_pDecal;
+
+	TODO("Use memory pool");
+	Array<XResourceModelStaticVertex> m_aVertices;
+
+	IXMaterial *m_pMaterial = NULL;
+
+	float3_t m_vNormal;
+
+	bool m_isTransparent = false;
+};
+
+#endif
diff --git a/source/anim/Renderable.cpp b/source/anim/Renderable.cpp
index 81ac9ab42dbb1d20f0597036e2df28da16bcfc76..52ad958bccb76178cbb80d6718cf70d4f22c2fed 100644
--- a/source/anim/Renderable.cpp
+++ b/source/anim/Renderable.cpp
@@ -1,10 +1,12 @@
 #include "Renderable.h"
 #include "RenderableVisibility.h"
+#include "DecalProvider.h"
 
-CRenderable::CRenderable(ID idPlugin, CAnimatedModelProvider *pProviderAnimated, CDynamicModelProvider *pProviderDynamic):
+CRenderable::CRenderable(ID idPlugin, CAnimatedModelProvider *pProviderAnimated, CDynamicModelProvider *pProviderDynamic, CDecalProvider *pProviderDecal):
 	m_idPlugin(idPlugin),
 	m_pAnimatedModelProvider(pProviderAnimated),
-	m_pDynamicModelProvider(pProviderDynamic)
+	m_pDynamicModelProvider(pProviderDynamic),
+	m_pDecalProvider(pProviderDecal)
 {
 }
 
@@ -40,6 +42,7 @@ void XMETHODCALLTYPE CRenderable::renderStage(X_RENDER_STAGE stage, IXRenderable
 	case XRS_GBUFFER:
 		m_pAnimatedModelProvider->render(pVis);
 		m_pDynamicModelProvider->render(false, pVis);
+		m_pDecalProvider->render(pVis);
 		break;
 	case XRS_SHADOWS:
 		m_pAnimatedModelProvider->render(pVis);
@@ -89,6 +92,7 @@ void XMETHODCALLTYPE CRenderable::startup(IXRender *pRender, IXMaterialSystem *p
 
 	m_pAnimatedModelProvider->setDevice(m_pDevice);
 	m_pDynamicModelProvider->setDevice(m_pDevice);
+	m_pDecalProvider->setDevice(m_pDevice);
 }
 void XMETHODCALLTYPE CRenderable::shutdown()
 {
@@ -96,7 +100,7 @@ void XMETHODCALLTYPE CRenderable::shutdown()
 
 void XMETHODCALLTYPE CRenderable::newVisData(IXRenderableVisibility **ppVisibility)
 {
-	*ppVisibility = new CRenderableVisibility(m_idPlugin, m_pAnimatedModelProvider, m_pDynamicModelProvider);
+	*ppVisibility = new CRenderableVisibility(m_idPlugin, m_pAnimatedModelProvider, m_pDynamicModelProvider, m_pDecalProvider);
 }
 
 IXMaterialSystem* CRenderable::getMaterialSystem()
diff --git a/source/anim/Renderable.h b/source/anim/Renderable.h
index cafcc2fbfc260553f1c8f3cf5f857b0b7919a2e8..85f03cc5139e0e8d2d3a4b34f849466849376c73 100644
--- a/source/anim/Renderable.h
+++ b/source/anim/Renderable.h
@@ -9,7 +9,7 @@
 class CRenderable: public IXUnknownImplementation<IXRenderable>
 {
 public:
-	CRenderable(ID idPlugin, CAnimatedModelProvider *pProviderAnimated, CDynamicModelProvider *pProviderDynamic);
+	CRenderable(ID idPlugin, CAnimatedModelProvider *pProviderAnimated, CDynamicModelProvider *pProviderDynamic, CDecalProvider *pProviderDecal);
 
 	XIMPLEMENT_VERSION(IXRENDERABLE_VERSION);
 
@@ -56,6 +56,7 @@ protected:
 
 	CAnimatedModelProvider *m_pAnimatedModelProvider = NULL;
 	CDynamicModelProvider *m_pDynamicModelProvider = NULL;
+	CDecalProvider *m_pDecalProvider = NULL;
 };
 
 #endif
diff --git a/source/anim/RenderableVisibility.cpp b/source/anim/RenderableVisibility.cpp
index d37373b47f114ba2fe3b7f3101020a0965ec53a3..f012c9831787a31e0f85b2e09e058b7df230044b 100644
--- a/source/anim/RenderableVisibility.cpp
+++ b/source/anim/RenderableVisibility.cpp
@@ -1,11 +1,13 @@
 #include "RenderableVisibility.h"
 #include "AnimatedModelProvider.h"
 #include "DynamicModelProvider.h"
+#include "DecalProvider.h"
 
-CRenderableVisibility::CRenderableVisibility(ID idPlugin, CAnimatedModelProvider *pProviderAnimated, CDynamicModelProvider *pProviderDynamic):
+CRenderableVisibility::CRenderableVisibility(ID idPlugin, CAnimatedModelProvider *pProviderAnimated, CDynamicModelProvider *pProviderDynamic, CDecalProvider *pProviderDecal):
 	m_idPlugin(idPlugin),
 	m_pProviderAnimated(pProviderAnimated),
-	m_pProviderDynamic(pProviderDynamic)
+	m_pProviderDynamic(pProviderDynamic),
+	m_pProviderDecal(pProviderDecal)
 {
 }
 
@@ -38,6 +40,7 @@ void CRenderableVisibility::updateForCamera(IXCamera *pCamera, const IXRenderabl
 	IXFrustum *pFrustum = pCamera->getFrustum();
 	m_pProviderAnimated->computeVisibility(pFrustum, this, pCamera->getLayerMask(), pRef);
 	m_pProviderDynamic->computeVisibility(pFrustum, pCamera->getLook(), this, pCamera->getLayerMask(), pRef, pCamera);
+	m_pProviderDecal->computeVisibility(pCamera->getLook(), this);
 	mem_release(pFrustum);
 }
 
@@ -52,6 +55,7 @@ void CRenderableVisibility::updateForFrustum(const IXFrustum *pFrustum, UINT bmL
 
 	m_pProviderAnimated->computeVisibility(pFrustum, this, bmLayers, pRef);
 	m_pProviderDynamic->computeVisibility(pFrustum, float3(), this, bmLayers, pRef);
+	m_pProviderDecal->computeVisibility(float3(), this);
 }
 
 static void SortRenderList(Array<CDynamicModel*> &aList)
@@ -139,7 +143,8 @@ void CRenderableVisibility::append(const IXRenderableVisibility *pOther_)
 	MergeArrays(m_aSelfillumList, pOther->m_aSelfillumList);
 	SortRenderList(m_aSelfillumList);
 
-	//! @todo implement for transparency too!
+	TODO("Implement for transparency too!");
+	TODO("Implement for decals too!");
 }
 
 void CRenderableVisibility::substract(const IXRenderableVisibility *pOther_)
@@ -159,7 +164,8 @@ void CRenderableVisibility::substract(const IXRenderableVisibility *pOther_)
 		}
 	}
 
-	//! @todo implement for transparency too!
+	TODO("Implement for transparency too!");
+	TODO("Implement for decals too!");
 }
 
 void CRenderableVisibility::setItemCount(UINT uCount)
@@ -257,3 +263,8 @@ Array<CDynamicModel*>& CRenderableVisibility::getTransparentList()
 {
 	return(m_aTransparentList);
 }
+
+CRenderableVisibility::OverlayData& CRenderableVisibility::getOverlayData()
+{
+	return(m_overlayData);
+}
diff --git a/source/anim/RenderableVisibility.h b/source/anim/RenderableVisibility.h
index 6d96b4998ce3e48771a923a55da214e68c183b2e..8032e469b6fd1bdb59f61f1b95513719956feac8 100644
--- a/source/anim/RenderableVisibility.h
+++ b/source/anim/RenderableVisibility.h
@@ -2,14 +2,16 @@
 #define __RENDERABLE_VISIBILITY_H
 
 #include <xcommon/IXRenderable.h>
+#include <xcommon/resource/IXResourceModel.h>
 
 class CAnimatedModelProvider;
 class CDynamicModelProvider;
+class CDecalProvider;
 class CDynamicModel;
 class CRenderableVisibility final: public IXUnknownImplementation<IXRenderableVisibility>
 {
 public:
-	CRenderableVisibility(ID idPlugin, CAnimatedModelProvider *m_pProviderAnimated, CDynamicModelProvider *m_pProviderDynamic);
+	CRenderableVisibility(ID idPlugin, CAnimatedModelProvider *pProviderAnimated, CDynamicModelProvider *pProviderDynamic, CDecalProvider *pProviderDecal);
 	~CRenderableVisibility();
 
 	ID getPluginId() const override;
@@ -40,6 +42,24 @@ public:
 		UINT uLod;
 		IXMaterial *pMaterial;
 	};
+	
+	struct OverlaySubset
+	{
+		IXMaterial *pMaterial;
+		UINT uStartVertex;
+		UINT uStartIndex;
+		UINT uQuadCount;
+	};
+
+	struct OverlayData
+	{
+		Array<XResourceModelStaticVertexGPU> aVertices;
+		Array<OverlaySubset> aSubsets;
+		IGXVertexBuffer *pVB = NULL;
+		IGXRenderBuffer *pRB = NULL;
+		IGXIndexBuffer *pIB = NULL;
+		UINT uVertexBufferAllocSize = 0;
+	};
 
 	void setItemCount(UINT uCount);
 	item_s* getItem(UINT uIndex);
@@ -57,16 +77,18 @@ public:
 	Array<CDynamicModel*>& getRenderList();
 	Array<CDynamicModel*>& getTransparentList();
 	Array<CDynamicModel*>& getSelfillumList();
+	OverlayData& getOverlayData();
 
 	IXOcclusionCuller* getCuller()
 	{
 		return(m_pOcclusionCuller);
 	}
 
-protected:
+private:
 	ID m_idPlugin;
 	CAnimatedModelProvider *m_pProviderAnimated;
 	CDynamicModelProvider *m_pProviderDynamic;
+	CDecalProvider *m_pProviderDecal;
 	IXOcclusionCuller *m_pOcclusionCuller = NULL;
 
 	Array<item_s> m_aItems;
@@ -75,6 +97,8 @@ protected:
 	Array<CDynamicModel*> m_aRenderList;
 	Array<CDynamicModel*> m_aTransparentList;
 	Array<CDynamicModel*> m_aSelfillumList;
+	
+	OverlayData m_overlayData;
 };
 
 #endif
diff --git a/source/anim/StaticModelProvider.cpp b/source/anim/StaticModelProvider.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..99cbb517abaa174b2d4e455bbe092bf52944648f
--- /dev/null
+++ b/source/anim/StaticModelProvider.cpp
@@ -0,0 +1,20 @@
+#include "StaticModelProvider.h"
+
+CStaticModelProvider::CStaticModelProvider(CDynamicModelProvider *pDynamicModelProvider):
+	m_pDynamicModelProvider(pDynamicModelProvider)
+{
+}
+
+bool XMETHODCALLTYPE CStaticModelProvider::createModel(IXResourceModelStatic *pResource, IXStaticModel **ppModel)
+{
+	IXDynamicModel *pModel;
+	if(m_pDynamicModelProvider->createModel(pResource, &pModel))
+	{
+		((CDynamicModel*)pModel)->setStatic(true);
+
+		*ppModel = pModel;
+		return(true);
+	}
+
+	return(false);
+}
diff --git a/source/anim/StaticModelProvider.h b/source/anim/StaticModelProvider.h
new file mode 100644
index 0000000000000000000000000000000000000000..37fd0b31938fa825400c5cf9bb52e85f312e9d8d
--- /dev/null
+++ b/source/anim/StaticModelProvider.h
@@ -0,0 +1,18 @@
+#ifndef __STATICMODELPROVIDER_H
+#define __STATICMODELPROVIDER_H
+
+#include <xcommon/resource/IXModelProvider.h>
+#include "DynamicModelProvider.h"
+
+class CStaticModelProvider final: public IXUnknownImplementation<IXStaticModelProvider>
+{
+public:
+	CStaticModelProvider(CDynamicModelProvider *pDynamicModelProvider);
+
+	bool XMETHODCALLTYPE createModel(IXResourceModelStatic *pResource, IXStaticModel **ppModel) override;
+
+private:
+	CDynamicModelProvider *m_pDynamicModelProvider;
+};
+
+#endif
diff --git a/source/anim/Updatable.cpp b/source/anim/Updatable.cpp
index d04c08cd13b194c8bac23e7493ff8084328d2471..2a48fd286fed63e6c019b14c508c3ed413305dd1 100644
--- a/source/anim/Updatable.cpp
+++ b/source/anim/Updatable.cpp
@@ -1,8 +1,9 @@
 #include "Updatable.h"
 
-CUpdatable::CUpdatable(CAnimatedModelProvider *pAnimatedModelProvider, CDynamicModelProvider *pDynamicModelProvider):
+CUpdatable::CUpdatable(CAnimatedModelProvider *pAnimatedModelProvider, CDynamicModelProvider *pDynamicModelProvider, CDecalProvider *pDecalProvider):
 	m_pAnimatedModelProvider(pAnimatedModelProvider),
-	m_pDynamicModelProvider(pDynamicModelProvider)
+	m_pDynamicModelProvider(pDynamicModelProvider),
+	m_pDecalProvider(pDecalProvider)
 {
 
 }
@@ -23,6 +24,8 @@ ID CUpdatable::run(float fDelta)
 
 	m_pDynamicModelProvider->update();
 
+	m_pDecalProvider->update();
+
 	return(-1);
 }
 
@@ -30,4 +33,5 @@ void CUpdatable::sync()
 {
 	m_pAnimatedModelProvider->sync();
 	m_pDynamicModelProvider->sync();
+	//m_pDecalProvider->update();
 }
diff --git a/source/anim/Updatable.h b/source/anim/Updatable.h
index 7123026acff06ca4a1edf9f3efeabeeec7ecc1e1..54089837a4775ebad5c966dc60b1d1c316b9545d 100644
--- a/source/anim/Updatable.h
+++ b/source/anim/Updatable.h
@@ -4,11 +4,12 @@
 #include <xcommon/IXUpdatable.h>
 #include "AnimatedModelProvider.h"
 #include "DynamicModelProvider.h"
+#include "DecalProvider.h"
 
 class CUpdatable: public IXUnknownImplementation<IXUpdatable>
 {
 public:
-	CUpdatable(CAnimatedModelProvider *pAnimatedModelProvider, CDynamicModelProvider *pDynamicModelProvider);
+	CUpdatable(CAnimatedModelProvider *pAnimatedModelProvider, CDynamicModelProvider *pDynamicModelProvider, CDecalProvider *pDecalProvider);
 
 	UINT startup() override;
 	void shutdown() override;
@@ -19,6 +20,7 @@ public:
 protected:
 	CAnimatedModelProvider *m_pAnimatedModelProvider;
 	CDynamicModelProvider *m_pDynamicModelProvider;
+	CDecalProvider *m_pDecalProvider;
 };
 
 #endif
diff --git a/source/anim/plugin_main.cpp b/source/anim/plugin_main.cpp
index 307928b5c6c0bcc1ad61feda2a988a7dda710a94..c0c068c21cb63c94a496f3b9c67af7987c57fcca 100644
--- a/source/anim/plugin_main.cpp
+++ b/source/anim/plugin_main.cpp
@@ -4,6 +4,8 @@
 #include "Updatable.h"
 #include "AnimatedModelProvider.h"
 #include "DynamicModelProvider.h"
+#include "StaticModelProvider.h"
+#include "DecalProvider.h"
 
 class CLevelSizeEventListener final: public IEventListener<XEventLevelSize>
 {
@@ -27,8 +29,11 @@ protected:
 class CLoadLevelEventListener final: public IEventListener<XEventLevel>
 {
 public:
-	CLoadLevelEventListener(CRenderable *pRenderable):
-		m_pRenderable(pRenderable)
+	CLoadLevelEventListener(CRenderable *pRenderable, CAnimatedModelProvider *pAnimatedModelProvider, CDynamicModelProvider *pDynamicModelProvider, CDecalProvider *pDecalProvider):
+		m_pRenderable(pRenderable),
+		m_pAnimatedModelProvider(pAnimatedModelProvider),
+		m_pDynamicModelProvider(pDynamicModelProvider),
+		m_pDecalProvider(pDecalProvider)
 	{}
 	void onEvent(const XEventLevel *pData)
 	{
@@ -36,9 +41,18 @@ public:
 		{
 		case XEventLevel::TYPE_LOAD_BEGIN:
 			m_pRenderable->setEnabled(false);
+			m_pDecalProvider->setEnabled(false);
 			break;
 		case XEventLevel::TYPE_LOAD_END:
 			m_pRenderable->setEnabled(true);
+			m_pDecalProvider->setEnabled(true);
+			break;
+
+		case XEventLevel::TYPE_LOAD_WAIT_ASYNC_TASKS:
+			while(m_pAnimatedModelProvider->hasPendingOps() && m_pDynamicModelProvider->hasPendingOps())
+			{
+				Sleep(100);
+			}
 			break;
 
 		default:
@@ -48,6 +62,9 @@ public:
 
 protected:
 	CRenderable *m_pRenderable;
+	CAnimatedModelProvider *m_pAnimatedModelProvider;
+	CDynamicModelProvider *m_pDynamicModelProvider;
+	CDecalProvider *m_pDecalProvider;
 };
 
 class CDSEPlugin: public IXUnknownImplementation<IXPlugin>
@@ -57,10 +74,12 @@ public:
 	{
 		m_pAnimatedModelProvider = new CAnimatedModelProvider(m_pCore);
 		m_pDynamicModelProvider = new CDynamicModelProvider(m_pCore);
-		m_pRenderable = new CRenderable(getID(), m_pAnimatedModelProvider, m_pDynamicModelProvider);
-		m_pUpdatable = new CUpdatable(m_pAnimatedModelProvider, m_pDynamicModelProvider);
+		m_pStaticModelProvider = new CStaticModelProvider(m_pDynamicModelProvider);
+		m_pDecalProvider = new CDecalProvider(m_pCore, m_pDynamicModelProvider);
+		m_pRenderable = new CRenderable(getID(), m_pAnimatedModelProvider, m_pDynamicModelProvider, m_pDecalProvider);
+		m_pUpdatable = new CUpdatable(m_pAnimatedModelProvider, m_pDynamicModelProvider, m_pDecalProvider);
 		m_pLevelSizeEventListener = new CLevelSizeEventListener(m_pAnimatedModelProvider, m_pDynamicModelProvider);
-		m_pLevelLoadEventListener = new CLoadLevelEventListener(m_pRenderable);
+		m_pLevelLoadEventListener = new CLoadLevelEventListener(m_pRenderable, m_pAnimatedModelProvider, m_pDynamicModelProvider, m_pDecalProvider);
 
 		m_pCore->getEventChannel<XEventLevelSize>(EVENT_LEVEL_GET_SIZE_GUID)->addListener(m_pLevelSizeEventListener);
 		m_pCore->getEventChannel<XEventLevel>(EVENT_LEVEL_GUID)->addListener(m_pLevelLoadEventListener);
@@ -77,16 +96,22 @@ public:
 		{
 			m_pCore->getEventChannel<XEventLevelSize>(EVENT_LEVEL_GET_SIZE_GUID)->removeListener(m_pLevelSizeEventListener);
 		}
+		if(m_pLevelLoadEventListener)
+		{
+			m_pCore->getEventChannel<XEventLevel>(EVENT_LEVEL_GUID)->removeListener(m_pLevelLoadEventListener);
+		}
 		mem_delete(m_pLevelSizeEventListener);
 		mem_delete(m_pRenderable);
 		mem_delete(m_pUpdatable);
 		mem_delete(m_pAnimatedModelProvider);
+		mem_delete(m_pStaticModelProvider);
 		mem_delete(m_pDynamicModelProvider);
+//		mem_delete(m_pDecalProvider);
 	}
 
 	UINT XMETHODCALLTYPE getInterfaceCount() override
 	{
-		return(4);
+		return(6);
 	}
 	const XGUID* XMETHODCALLTYPE getInterfaceGUID(UINT id) override
 	{
@@ -105,6 +130,12 @@ public:
 		case 3:
 			s_guid = IXDYNAMICMODELPROVIDER_GUID;
 			break;
+		case 4:
+			s_guid = IXDECALPROVIDER_GUID;
+			break;
+		case 5:
+			s_guid = IXSTATICMODELPROVIDER_GUID;
+			break;
 		default:
 			return(NULL);
 		}
@@ -149,6 +180,24 @@ public:
 			add_ref(m_pDynamicModelProvider);
 			*ppOut = m_pDynamicModelProvider;
 			break;
+
+		case 4:
+			if(!m_pDecalProvider)
+			{
+				init();
+			}
+			add_ref(m_pDecalProvider);
+			*ppOut = m_pDecalProvider;
+			break;
+
+		case 5:
+			if(!m_pStaticModelProvider)
+			{
+				init();
+			}
+			add_ref(m_pStaticModelProvider);
+			*ppOut = m_pStaticModelProvider;
+			break;
 		
 		default:
 			*ppOut = NULL;
@@ -161,6 +210,8 @@ protected:
 	IXCore *m_pCore = NULL;
 	CAnimatedModelProvider *m_pAnimatedModelProvider = NULL;
 	CDynamicModelProvider *m_pDynamicModelProvider = NULL;
+	CStaticModelProvider *m_pStaticModelProvider = NULL;
+	CDecalProvider *m_pDecalProvider = NULL;
 	CLevelSizeEventListener *m_pLevelSizeEventListener = NULL;
 	CLoadLevelEventListener *m_pLevelLoadEventListener = NULL;
 };
diff --git a/source/common b/source/common
index fab676418d9c07fcbb558bca8becc9e7b835f62d..6ac114eeb7c088e7560e26475ad41f5aded8a047 160000
--- a/source/common
+++ b/source/common
@@ -1 +1 @@
-Subproject commit fab676418d9c07fcbb558bca8becc9e7b835f62d
+Subproject commit 6ac114eeb7c088e7560e26475ad41f5aded8a047
diff --git a/source/core/BaseFileIterator.cpp b/source/core/BaseFileIterator.cpp
index fb4ab2fe15bf608e513f5afb0d4df4ddbc92bad2..97884dc6494e6c8aec8b822984862ec8015bc4dc 100644
--- a/source/core/BaseFileIterator.cpp
+++ b/source/core/BaseFileIterator.cpp
@@ -1,49 +1,66 @@
 #include "BaseFileIterator.h"
 
-void CBaseFileIterator::canonizePath(String &sPath)
+void CBaseFileIterator::canonizePath(char *szPath)
 {
-	char symbol = sPath[sPath.length() - 1];
+	char symbol = szPath[strlen(szPath) - 1];
 
 	/*Дело в том что абсолютный путь к файлу может не иметь символ "/"
 	или "\\" на конце строки, и, если его не будет путь будет некорректен*/
-	if (symbol != '\\' && symbol != '/')
+	if(symbol != '\\' && symbol != '/')
 	{
-		sPath += '/';
+		strcat(szPath, "/");
+	}
+}
+
+void CBaseFileIterator::canonizePath(String *pPath)
+{
+	char symbol = (*pPath)[pPath->length() - 1];
+
+	/*Дело в том что абсолютный путь к файлу может не иметь символ "/"
+	или "\\" на конце строки, и, если его не будет путь будет некорректен*/
+	if(symbol != '\\' && symbol != '/')
+	{
+		(*pPath) += '/';
 	}
 }
 
 void CBaseFileIterator::canonizePaths(Array<String> &paths)
 {
-	for (int i = 0, I = paths.size(); i < I; ++i)
+	for(int i = 0, I = paths.size(); i < I; ++i)
 	{
-		canonizePath(paths[i]);
+		canonizePath(&paths[i]);
 	}
 }
 
-void CBaseFileIterator::fillExtensionsArray(Array<String> &extsArray, const char **exts, int iExtsSize)
+void CBaseFileIterator::fillExtensionsArray(Array<AAString> &extsArray, const char **exts, int iExtsSize)
 {
-	for (int i = 0; i < iExtsSize; ++i)
+	for(int i = 0; i < iExtsSize; ++i)
 	{
-		if (exts[i])
+		if(exts[i])
 		{
-			extsArray.push_back(exts[i]);
+			extsArray[i].setName(exts[i]);
 		}
 	}
 }
 
-bool CBaseFileIterator::findExtensionsInPath(const char *szPath, const Array<String> &exts)
+bool CBaseFileIterator::findExtensionsInPath(const char *szPath, const Array<AAString> &exts)
 {
-	for (int i = 0, I = exts.size(); i < I; ++i)
+	size_t len = strlen(szPath), extLen = 0;
+	char szExt[32];
+
+	for(int i = 0, I = exts.size(); i < I; ++i)
 	{
-		if (strstr(szPath, exts[i].c_str()) != NULL)
+		extLen = strlen(exts[i].getName()) + 1;
+		sprintf(szExt, ".%s", exts[i].getName());
+		if(!strcmp(szPath + len - extLen, szExt))
 		{
-			return true;
+			return(true);
 		}
 	}
-	return !exts.size();
+	return(!exts.size());
 }
 
 bool CBaseFileIterator::emptyOrRepeatPath(const char * szPath)
 {
-	return !strcmp(szPath, "..") || !strcmp(szPath, ".") || !strcmp(szPath, "");
+	return(!strcmp(szPath, "..") || !strcmp(szPath, ".") || !strcmp(szPath, ""));
 }
\ No newline at end of file
diff --git a/source/core/BaseFileIterator.h b/source/core/BaseFileIterator.h
index 0110c448c2307e8a392a571f4f49f3dedca2d468..43fee541479fb8aab75c06f1983fc0fadb3b9a07 100644
--- a/source/core/BaseFileIterator.h
+++ b/source/core/BaseFileIterator.h
@@ -2,17 +2,20 @@
 #define __CBASE_FILE_ITERATOR_
 
 #include "FileSystem.h"
+#include <common/AAString.h>
 
 class CBaseFileIterator : public IXUnknownImplementation<IFileIterator>
 {
 public:
-	void canonizePath(String &sPath);
+	void canonizePath(char *szPath);
+
+	void canonizePath(String *pPath);
 
 	void canonizePaths(Array<String> &paths);
 
-	void fillExtensionsArray(Array<String> &extsArray, const char **exts, int iExtsSize);
+	void fillExtensionsArray(Array<AAString> &extsArray, const char **exts, int iExtsSize);
 
-	bool findExtensionsInPath(const char *szPath, const Array<String> &exts);
+	bool findExtensionsInPath(const char *szPath, const Array<AAString> &exts);
 
 	bool emptyOrRepeatPath(const char *szPath);
 };
diff --git a/source/core/Config.cpp b/source/core/Config.cpp
index fa9a82e091a16fa29d5966cb2e72efab2459036b..d8a5b37a5d0e1ca78e52a7693f75eeacb31574e2 100644
--- a/source/core/Config.cpp
+++ b/source/core/Config.cpp
@@ -152,7 +152,7 @@ int CConfig::parse(const char* file)
 					{
 						for(AssotiativeArray<CConfigString, CValue>::Iterator i = m_mSections[s3].mValues.begin(); i; ++i)
 						{
-							m_mSections[s2].mValues[i.first].val = i.second->val;
+							m_mSections[s2].mValues[*i.first].val = i.second->val;
 						}
 					}
 				}
@@ -187,7 +187,7 @@ int CConfig::parse(const char* file)
 					{
 						for(AssotiativeArray<CConfigString, CValue>::Iterator i = m_mSections[s3].mValues.begin(); i; ++i)
 						{
-							m_mSections[s2].mValues[i.first].val = i.second->val;
+							m_mSections[s2].mValues[*i.first].val = i.second->val;
 						}
 					}
 				}
@@ -332,20 +332,20 @@ void CConfig::modify(AssotiativeArray<CConfigString, CSection> & sections, Assot
 {
 	for(AssotiativeArray<CConfigString, CValue>::Iterator i = m_mFinalValues.begin(); i; ++i)
 	{
-		values[i.first].val = i.second->val;
+		values[*i.first].val = i.second->val;
 	}
 
 	for(AssotiativeArray<CConfigString, CSection>::Iterator i = m_mSections.begin(); i; ++i)
 	{
-		if(!sections.KeyExists(i.first))
+		if(!sections.KeyExists(*i.first))
 		{
-			sections[i.first].parent = i.second->parent;
-			sections[i.first].native = false;
-			sections[i.first].Include = IncName;
+			sections[*i.first].parent = i.second->parent;
+			sections[*i.first].native = false;
+			sections[*i.first].Include = IncName;
 		}
-		for(AssotiativeArray<CConfigString, CValue>::Iterator j = m_mSections[i.first].mValues.begin(); j; ++j)
+		for(AssotiativeArray<CConfigString, CValue>::Iterator j = m_mSections[*i.first].mValues.begin(); j; ++j)
 		{
-			sections[i.first].mValues[j.first].val = j.second->val;
+			sections[*i.first].mValues[*j.first].val = j.second->val;
 		}
 	}
 }
@@ -479,275 +479,243 @@ void CConfig::set(const char * sectionp, const char * key, const char * val)
 
 int CConfig::save()
 {
-	static const bool *s_pbDebug = GET_PCVAR_BOOL("dbg_config_save");
-	if(*s_pbDebug)
-	{
-		printf(COLOR_GRAY "====== " COLOR_CYAN "CConfig::save() " COLOR_GRAY "======" COLOR_RESET "\n");
-	}
 	int terror = 0;
 	for(AssotiativeArray<CConfigString, CSection>::Iterator i = m_mSections.begin(); i; ++i)
 	{
-		if(*s_pbDebug)
-		{
-			printf("Testing section: " COLOR_LGREEN "%s" COLOR_RESET "...", i.first->c_str());
-		}
 		if(i.second->isModified)
 		{
-			if(*s_pbDebug)
-			{
-				printf(COLOR_YELLOW " modified" COLOR_RESET "\n");
-			}
 			for(AssotiativeArray<CConfigString, CValue>::Iterator j = i.second->mValues.begin(); j; ++j)
 			{
-				if(*s_pbDebug)
-				{
-					printf("  testing key: " COLOR_LGREEN "%s" COLOR_RESET "...", j.first->c_str());
-				}
 				if(j.second->isModified)
 				{
-					if(*s_pbDebug)
-					{
-						printf(COLOR_YELLOW " modified" COLOR_RESET "\n");
-					}
 					if(i.second->native) // Write to BaseFile
 					{
-						if(*s_pbDebug)
-						{
-							printf("    writing to base file " COLOR_CYAN "%s" COLOR_RESET "...\n", BaseFile.c_str());
-						}
 						terror = writeFile(BaseFile, *i.first, *j.first, j.second->val);
 						if(terror != 0)
 							goto end;
 					}
 					else // Write to i.second->Include
 					{
-						if(*s_pbDebug)
-						{
-							printf("    writing to include file " COLOR_CYAN "%s" COLOR_RESET "...\n", i.second->Include.c_str());
-						}
 						terror = writeFile(i.second->Include, *i.first, *j.first, j.second->val);
 						if(terror != 0)
 							goto end;
 					}
 				}
-				else
-				{
-					if(*s_pbDebug)
-					{
-						printf(COLOR_GRAY " not modified" COLOR_RESET "\n");
-					}
-				}
-			}
-		}
-		else
-		{
-			if(*s_pbDebug)
-			{
-				printf(COLOR_GRAY " not modified" COLOR_RESET "\n");
 			}
 		}
 	}
+
+	terror = commitFiles();
 end:
-	if(*s_pbDebug)
-	{
-		printf(COLOR_GRAY "=============================" COLOR_RESET "\n");
-	}
 	return(terror);
 }
 
 int CConfig::writeFile(const CConfigString & name, CConfigString section, CConfigString key, const CConfigString & val)
 {
-	static const bool *s_pbDebug = GET_PCVAR_BOOL("dbg_config_save");
-	//printf("W: %s\t[%s]: %s = %s\n", name.c_str(), section.c_str(), key.c_str(), val.c_str());
-	FILE * pF = fopen(name.c_str(), "rb");
-	if(pF)
+	const AssotiativeArray<String, String>::Node *pUncommittedNode;
+	
+	if(!m_mapUncommitted.KeyExists(name, &pUncommittedNode))
 	{
-		if(*s_pbDebug)
-		{
-			printf("    file opened\n");
-		}
+		pUncommittedNode = m_mapUncommitted.insert(name, "");
 
-		fseek(pF, 0, SEEK_END);
-		UINT fl = ftell(pF);
-		fseek(pF, 0, SEEK_SET);
-		char * szData = new char[fl + 1];
-		if(!szData)
+		// read file
+		IFile *pFile = m_pFS->openFile(name.c_str());
+		if(pFile)
 		{
-			printf(COLOR_LRED "Unable to allocate memory (%d) in CConfig::writeFile()" COLOR_RESET "\n", fl + 1);
-			fclose(pF);
-			return(-1);
+			size_t sizeFile = pFile->getSize();
+
+			pUncommittedNode->Val->resize(sizeFile);
+			char *szPtr = &((*(pUncommittedNode->Val))[0]);
+			pFile->readBin(szPtr, sizeFile);
+			szPtr[sizeFile] = 0;
+
+			mem_release(pFile);
 		}
-		fread(szData, 1, fl, pF);
-		szData[fl] = 0;
-		fclose(pF);
-		UINT sl = section.length();
-		UINT kl = key.length();
-		bool sf = false;
-		bool kf = false;
-		bool se = false;
-		UINT sp = 0;
-		for(UINT i = 0; i < fl; ++i)
+	}
+
+	String &sFileData = *(pUncommittedNode->Val);
+	
+
+	UINT fl = sFileData.length();
+	UINT sl = section.length();
+	UINT kl = key.length();
+	bool sf = false;
+	bool kf = false;
+	bool se = false;
+	UINT sp = 0;
+	const char *szData = sFileData.c_str();
+	for(UINT i = 0; i < fl; ++i)
+	{
+		if(szData[i] == '[' && ((i > 0 && (szData[i - 1] == '\r' || szData[i - 1] == '\n')) || i == 0))
 		{
-			if(szData[i] == '[' && ((i > 0 && (szData[i - 1] == '\r' || szData[i - 1] == '\n')) || i == 0))
+			bool cmp = true;
+			UINT j;
+			for(j = i + 1; j < fl - 1 && j - i - 1 < sl; ++j)
+			{
+				if(szData[j] != section[j - i - 1])
+				{
+					cmp = false;
+					break;
+				}
+			}
+			if(cmp && szData[j] == ']')//Section Found!
 			{
-				bool cmp = true;
-				UINT j;
-				for(j = i + 1; j < fl - 1 && j - i - 1 < sl; ++j)
+				sf = true;
+				i = j;
+				for(; i < fl; ++i)
 				{
-					if(szData[j] != section[j - i - 1])
+					if(szData[i] == '\r' || szData[i] == '\n')
 					{
-						cmp = false;
 						break;
 					}
 				}
-				if(cmp && szData[j] == ']')//Section Found!
+				while(i < fl && (szData[i] == '\r' || szData[i] == '\n'))
 				{
-					sf = true;
-					i = j;
-					for(; i < fl; ++i)
+					++i;
+				}
+				sp = i;
+				//We are inside the section
+				//So, let's find the key
+				while(i < fl)
+				{
+					if(szData[i] == '[') // New section begin. There is no our key, so, add it!
 					{
-						if(szData[i] == '\r' || szData[i] == '\n')
-						{
-							break;
-						}
+						kf = false;
+						se = true;
+						break;
 					}
-					while(i < fl && (szData[i] == '\r' || szData[i] == '\n'))
+					while(i < fl && (szData[i] == ' ' || szData[i] == '\t'))
 					{
 						++i;
 					}
-					sp = i;
-					//We are inside the section
-					//So, let's find the key
-					while(i < fl)
+					bool f = true;
+					for(j = i; j < fl && j - i < kl; ++j)
 					{
-						if(szData[i] == '[') // New section begin. There is no our key, so, add it!
+						if(szData[j] != key[j - i])
 						{
-							kf = false;
-							se = true;
+							f = false;
 							break;
 						}
-						while(i < fl && (szData[i] == ' ' || szData[i] == '\t'))
-						{
-							++i;
-						}
-						bool f = true;
-						for(j = i; j < fl && j - i < kl; ++j)
+					}
+					if(f && (isspace((unsigned char)szData[j]) || szData[j] == '='))//KeyFound!
+					{
+						i = j;
+						kf = true;
+						for(j = i; j < fl; ++j)
 						{
-							if(szData[j] != key[j - i])
+							if(szData[j] == '\n' || szData[j] == '\r' || szData[j] == ';')
 							{
-								f = false;
+								//f = false;
 								break;
 							}
 						}
-						if(f && (isspace((unsigned char)szData[j]) || szData[j] == '='))//KeyFound!
+						//while(szData[j] == '\r' || szData[j] == '\n')
+						//{
+						//	++j;
+						//}
+						//Let's write the file
+
+						if(i != j)
 						{
-							i = j;
-							kf = true;
-							for(j = i; j < fl; ++j)
-							{
-								if(szData[j] == '\n' || szData[j] == '\r' || szData[j] == ';')
-								{
-									//f = false;
-									break;
-								}
-							}
-							//while(szData[j] == '\r' || szData[j] == '\n')
-							//{
-							//	++j;
-							//}
-							//Let's write the file
-							FILE * pF = fopen(name.c_str(), "wb");
-							if(!pF)
-							{
-								ErrorFile = name;
-								mem_delete_a(szData);
-								return -1;
-							}
-							fwrite(szData, 1, i, pF); // First file part, including key
-							fwrite(" = ", 1, 3, pF);
-							fwrite(val.c_str(), 1, val.length(), pF);
-							//fwrite("\n", 1, 1, pF);
-							fwrite(&szData[j], 1, fl - j, pF);
-							fclose(pF);
+							sFileData.remove(i, j - i);
+						}
+
+						sFileData.insert(i, String(" = ") + val);
+
+						/*
+						FILE * pF = fopen(name.c_str(), "wb");
+						if(!pF)
+						{
+							ErrorFile = name;
 							mem_delete_a(szData);
-							return 0;
+							return -1;
 						}
-						else // Skip current row
+						fwrite(szData, 1, i, pF); // First file part, including key
+						fwrite(" = ", 1, 3, pF);
+						fwrite(val.c_str(), 1, val.length(), pF);
+						//fwrite("\n", 1, 1, pF);
+						fwrite(&szData[j], 1, fl - j, pF);
+						fclose(pF);
+						mem_delete_a(szData);*/
+						return 0;
+					}
+					else // Skip current row
+					{
+						for(; i < fl; ++i)
 						{
-							for(; i < fl; ++i)
-							{
-								if(szData[i] == '\n' || szData[i] == '\r')
-								{
-									//f = false;
-									break;
-								}
-							}
-							while(i < fl && (szData[i] == '\r' || szData[i] == '\n'))
+							if(szData[i] == '\n' || szData[i] == '\r')
 							{
-								++i;
+								//f = false;
+								break;
 							}
 						}
-					}
-					//
-					if(se)
-					{
-						break;
+						while(i < fl && (szData[i] == '\r' || szData[i] == '\n'))
+						{
+							++i;
+						}
 					}
 				}
+				//
+				if(se)
+				{
+					break;
+				}
 			}
 		}
-		if(sf && !kf)
-		{
-			if(*s_pbDebug)
-			{
-				printf("    adding key to section(sp=" COLOR_LCYAN "%d" COLOR_RESET ")\n", sp);
-			}
-			FILE * pF = fopen(name.c_str(), "wb");
-			if(!pF)
-			{
-				ErrorFile = name;
-				mem_delete_a(szData);
-				return -1;
-			}
-			fwrite(szData, 1, sp, pF); // First file part
-			fwrite(key.c_str(), 1, key.length(), pF);
-			fwrite(" = ", 1, 3, pF);
-			fwrite(val.c_str(), 1, val.length(), pF);
-			fwrite("\n", 1, 1, pF);
-			fwrite(&szData[sp], 1, fl - sp, pF);
-			fclose(pF);
-			mem_delete_a(szData);
-			return 0;
-		}
-		if(!sf)//!(Section found) == Add new
-		{
-			FILE * pF = fopen(name.c_str(), "ab");
-			if(!pF)
-			{
-				ErrorFile = name;
-				return -1;
-			}
-			fwrite((CConfigString("\n[") + section + "]\n" + key + " = " + val + "\n").c_str(), sizeof(char), section.length() + key.length() + val.length() + 8, pF);
-			fclose(pF);
-			mem_delete_a(szData);
-			return 0;
-		}
 	}
-	else
+	if(sf && !kf)
 	{
-		FILE * pF = fopen(name.c_str(), "wb");
-		if(!pF)
-		{
-			ErrorFile = name;
-			return -1;
-		}
-		fwrite((CConfigString("[") + section + "]\n" + key + " = " + val + "\n").c_str(), sizeof(char), section.length() + key.length() + val.length() + 7, pF);
-		fclose(pF);
+		sFileData.insert(sp, key + " = " + val + '\n');
+
+		return 0;
+	}
+	if(!sf)//!(Section found) == Add new
+	{
+		sFileData += '\n';
+		sFileData += '[';
+		sFileData += section;
+		sFileData += ']';
+		sFileData += '\n';
+		sFileData += key;
+		sFileData += " = ";
+		sFileData += val;
+		sFileData += '\n';
+
 		return 0;
 	}
+	
 	return 0;
 }
 
+int CConfig::commitFiles()
+{
+	for(AssotiativeArray<String, String>::Iterator i = m_mapUncommitted.begin(); i; ++i)
+	{
+		//String sTempName = (*i.first) + ".new";
+		const String &sName = (*i.first);
+		IFile *pFile = m_pFS->openFile(sName.c_str(), FILE_MODE_WRITE);
+		if(!pFile)
+		{
+			ErrorFile = sName;
+			return(-1);
+		}
+
+		if(i.second->length() != pFile->writeBin(i.second->c_str(), i.second->length()))
+		{
+			ErrorFile = sName;
+			pFile->close();
+			return(-2);
+		}
+		pFile->close();
+
+		//m_pFS->rename
+	}
+
+	m_mapUncommitted.clear();
+	
+	return(0);
+}
+
 int CConfig::getSectionCount()
 {
 	return(m_mSections.Size());
@@ -804,7 +772,6 @@ void CConfig::clear()
 	for(int i = 0; i < size; ++i)
 	{
 		mem_release(m_vIncludes[i].pParser);
-		mem_delete(m_vIncludes[i].pParser);
 	}
 	m_vIncludes.clear();
 	m_mFinalValues.clear();
diff --git a/source/core/Config.h b/source/core/Config.h
index 8b5587560ac1b3ee0487703f08c19646ecf65853..a2455e8582c2d464e8a3d23a6672e757511c07be 100644
--- a/source/core/Config.h
+++ b/source/core/Config.h
@@ -18,60 +18,27 @@ See the license in LICENSE
 class CConfigString: public String
 {
 public:
-	CConfigString() : String(){}
-	CConfigString(const char * str) : String(str){}
-	CConfigString(const String & str) : String(str){}
-	CConfigString(const CConfigString & str) : String(str){}
-	CConfigString(const CConfigString * str) : String(str){}
-	CConfigString(const char sym) : String(sym){}
-	~CConfigString() {}
-	
-	const char & operator[](const unsigned long & num) const
-	{
-		return(m_szString[num]);
-	}
-
-	char & operator[](const unsigned long & num)
-	{
-		return(m_szString[num]);
-	}
+	CConfigString(): String(){}
+	CConfigString(const char *str): String(str){}
+	CConfigString(const String &str): String(str){}
+	CConfigString(const CConfigString &str): String(str){}
+	CConfigString(char sym): String(sym){}
 
-	CConfigString & operator=(const CConfigString & str)
+	CConfigString& operator=(const CConfigString &str)
 	{
-		release();
-		m_szString = new char[str.length() + 1];
-		memcpy(m_szString, str.c_str(), str.length() + 1);
-		return(*this);
-	}
+		String::operator=(str);
 
-	CConfigString & operator=(const CConfigString * str)
-	{
-		release();
-		m_szString = new char[str->length() + 1];
-		memcpy(m_szString, str->c_str(), str->length() + 1);
 		return(*this);
 	}
 
 	bool operator==(const CConfigString &str) const
 	{
-		return (strcasecmp(str.m_szString, m_szString) == 0);
-	}
-
-	bool operator==(const CConfigString * str) const
-	{
-		return (strcasecmp(str->m_szString, m_szString) == 0);
-	}
-
-	CConfigString & operator+=(const char &	sym)
-	{
-		char * newstring = new char[length() + 2];
-		sprintf(newstring, "%s%c", m_szString, sym);
-		mem_delete_a(m_szString);
-		m_szString = newstring;
-		return(*this);
+		return(strcasecmp(str.c_str(), c_str()) == 0);
 	}
 };
 
+//##########################################################################
+
 class CConfig: public ISXConfig
 {
 public:
@@ -152,8 +119,16 @@ protected:
 	CConfigString baseName(CConfigString dir);
 
 	void modify(AssotiativeArray<CConfigString, CSection> & sections, AssotiativeArray<CConfigString, CValue> & values, CConfigString IncName);
+
+private:
+	int commitFiles();
+
+private:
+	AssotiativeArray<String, String> m_mapUncommitted;
 };
 
+//##########################################################################
+
 class CXConfig: public IXUnknownImplementation<IXConfig>
 {
 public:
diff --git a/source/core/Core.cpp b/source/core/Core.cpp
index 701dbefd9fe74d6521b702d63a072f39c1fa7952..567a43d4b214155d1aee509d45d86f0e63e5694e 100644
--- a/source/core/Core.cpp
+++ b/source/core/Core.cpp
@@ -35,7 +35,6 @@ CCore::CCore(const char *szName)
 
 	Core_0RegisterCVarBool("g_time_run", true, "Запущено ли игровое время?", FCVAR_NOTIFY_OLD);
 	Core_0RegisterCVarFloat("g_time_speed", 1.f, "Скорость/соотношение течения игрового времени", FCVAR_NOTIFY_OLD);
-	Core_0RegisterCVarBool("dbg_config_save", false, "Отладочный вывод процесса сохранения конфига");
 	Core_0RegisterCVarInt("r_stats", 0, "Показывать ли статистику? 0 - нет, 1 - fps и игровое время, 2 - показать полностью");
 
 	Core_0RegisterConcmd("on_g_time_run_change", []()
diff --git a/source/core/FileExtsIterator.cpp b/source/core/FileExtsIterator.cpp
index 6ae242e5005b65422069072861c6f06af2a38b19..67ab1aeefc2b7712e338f8fcf372f3c1ebc385ce 100644
--- a/source/core/FileExtsIterator.cpp
+++ b/source/core/FileExtsIterator.cpp
@@ -1,13 +1,13 @@
 #include "FileExtsIterator.h"
 
-CFileExtsIterator::CFileExtsIterator(Array<String> &paths, String &sBasePath, const char **szExt, int iExtSize)
+CFileExtsIterator::CFileExtsIterator(Array<String> &paths, const char *szBasePath, const char **szExt, int iExtSize)
 {
-	this->canonizePaths(paths);
-	this->canonizePath(sBasePath);
-	this->fillExtensionsArray(m_exts, szExt, iExtSize);
+	m_paths.swap(paths);
+	strcpy(m_szBasePath, szBasePath);
 
-	this->m_paths = paths;
-	this->m_sBasePath = sBasePath;
+	canonizePaths(m_paths);
+	canonizePath(m_szBasePath);
+	fillExtensionsArray(m_exts, szExt, iExtSize);
 }
 
 const char *CFileExtsIterator::next()
@@ -20,51 +20,55 @@ const char *CFileExtsIterator::next()
 	int size = m_paths.size();
 	int sizeExt = m_exts.size();
 
-	while (index < size)
+	char szFileName[SIZE_PATH];
+
+	while(index < size)
 	{
-		const String &fileName = (m_paths[index] + "*.*");
+		sprintf(szFileName, "%s*.*", m_paths[index].c_str());
 
 		//Проверяем указатель, если m_handle пустой, то ищем первый файл с расширением szExts
-		hf = INVALID_OR_NULL(m_handle) ? FindFirstFileW(CMB2WC(fileName.c_str()), &FindFileData) : m_handle;
+		hf = INVALID_OR_NULL(m_handle) ? FindFirstFileW(CMB2WC(szFileName), &FindFileData) : m_handle;
 
-		if (hf != INVALID_HANDLE_VALUE)
+		if(hf != INVALID_HANDLE_VALUE)
 		{
 			do {
 				//Сохраняем HANDLE файла, что бы можно было продожлить с того места
 				m_handle = hf;
 
-				m_pathStr = m_paths[index] + CWC2MB(FindFileData.cFileName);
+				sprintf(m_szPathStr, "%s%s", m_paths[index].c_str(), (const char*)CWC2MB(FindFileData.cFileName));
 
-				DWORD flag = GetFileAttributesW(CMB2WC(m_pathStr.c_str()));
+				DWORD flag = GetFileAttributesW(CMB2WC(m_szPathStr));
 
-				if (flag != INVALID_FILE_ATTRIBUTES && !(flag & FILE_ATTRIBUTE_DIRECTORY))
+				if(flag != INVALID_FILE_ATTRIBUTES && !(flag & FILE_ATTRIBUTE_DIRECTORY))
 				{
-					if (!findExtensionsInPath(CWC2MB(FindFileData.cFileName), m_exts))
+					if(!findExtensionsInPath(CWC2MB(FindFileData.cFileName), m_exts))
 					{
 						continue;
 					}
 
-					m_pathStr = (m_sBasePath + CWC2MB(FindFileData.cFileName));
-					if (m_mapExistPath.KeyExists(m_pathStr))
+					sprintf(m_szPathStr, "%s%s", m_szBasePath, (const char*)CWC2MB(FindFileData.cFileName));
+
+					if(m_mapExistPath.KeyExists(m_szPathStr))
 					{
 						continue;
 					}
 					else
 					{
 						//Возвращаем относительный путь, вместе с именем файла и расширением
-						m_mapExistPath[m_pathStr] = index;
-						return m_pathStr.c_str();
+						m_mapExistPath[m_szPathStr] = index;
+						return(m_szPathStr);
 					}
 				}
 				//Если указатель на файл валидный, то проверяем все отфильтрованные файлы по порядку
-			} while (FindNextFileW(hf, &FindFileData) != 0);
+			}
+			while(FindNextFileW(hf, &FindFileData) != 0);
 		}
 		++index;
 		FIND_CLOSE(m_handle);
 	}
 
 	//Если вообще не нашли файлов возвращаем nullptr
-	return nullptr;
+	return(NULL);
 }
 
 void CFileExtsIterator::reset()
diff --git a/source/core/FileExtsIterator.h b/source/core/FileExtsIterator.h
index 6fcfc213808ebe18225fedff68bac67450d44b00..bc020156ef7335054963f805fc718563ab15afcd 100644
--- a/source/core/FileExtsIterator.h
+++ b/source/core/FileExtsIterator.h
@@ -12,9 +12,9 @@ class CFileExtsIterator final : public CBaseFileIterator
 {
 private:
 	Array<String> m_paths;
-	String m_pathStr;
-	String m_sBasePath;
-	Array<String> m_exts;
+	char m_szPathStr[SIZE_PATH];
+	char m_szBasePath[SIZE_PATH];
+	Array<AAString> m_exts;
 	AssotiativeArray<String, int> m_mapExistPath;
 
 	int index = 0;
@@ -24,7 +24,7 @@ private:
 	int m_currentExt = 0;
 
 public:
-	CFileExtsIterator::CFileExtsIterator(Array<String> &paths, String &sBasePath, const char **szExt, int iExtSize);
+	CFileExtsIterator::CFileExtsIterator(Array<String> &paths, const char *szBasePath, const char **szExt, int iExtSize);
 
 	const char* XMETHODCALLTYPE next() override;
 
diff --git a/source/core/FileRecursiveExtPathsIterator.cpp b/source/core/FileRecursiveExtPathsIterator.cpp
index 83c61563a51141dd68c334aca38103b99fea833c..ab45a930589d41a75bacba31d031ab7e2d510473 100644
--- a/source/core/FileRecursiveExtPathsIterator.cpp
+++ b/source/core/FileRecursiveExtPathsIterator.cpp
@@ -1,51 +1,59 @@
 #include "FileRecursiveExtPathsIterator.h"
 
-CFileRecursiveExtPathsIterator::CFileRecursiveExtPathsIterator(Array<String> &paths, String &sBasePath, const char **szExts, int iExtSize)
+CFileRecursiveExtPathsIterator::CFileRecursiveExtPathsIterator(Array<String> &paths, const char *szBasePath, const char **szExts, int iExtSize)
 {
-	this->canonizePaths(paths);
-	this->canonizePath(sBasePath);
-	this->fillExtensionsArray(m_exts, szExts, iExtSize);
+	strcpy(m_szBasePath, szBasePath);
+	m_aPaths.swap(paths);
 
-	this->m_sPaths = paths;
-	this->m_sBasePath = sBasePath;
+	canonizePaths(m_aPaths);
+	canonizePath(m_szBasePath);
+
+	fillExtensionsArray(m_exts, szExts, iExtSize);
 }
 
 const char *CFileRecursiveExtPathsIterator::next()
 {
+	char szFileName[SIZE_PATH];
+	char szFullName[SIZE_PATH];
+
 	WIN32_FIND_DATAW FindFileData;
 	HANDLE hf;
+	UINT maxPathIndex = m_aPaths.size();
 
 	FindFileData.cFileName[0] = '\0';
 
-	UINT maxPathIndex = m_sPaths.size();
 	while (pathIndex < maxPathIndex)
 	{
-		m_currentFullPath = !m_currentFullPath.length() ? m_sPaths[pathIndex] : m_currentFullPath;
+		if(strlen(m_szCurrentFullPath) > 0)
+		{
+			strcpy(m_szCurrentFullPath, m_aPaths[pathIndex].c_str());
+		}
+
 		do {
-			String fileName = m_sPaths[pathIndex] + "*.*";
+			sprintf(szFileName, "%s*.*", m_aPaths[pathIndex].c_str());
 
 			//Проверяем указатель, если m_handle пустой, то ищем первый файл с расширением szExts
-			hf = INVALID_OR_NULL(m_handle) ? FindFirstFileW(CMB2WC(fileName.c_str()), &FindFileData) : m_handle;
+			hf = INVALID_OR_NULL(m_handle) ? FindFirstFileW(CMB2WC(szFileName), &FindFileData) : m_handle;
 
 			if (hf != INVALID_HANDLE_VALUE)
 			{
-				
 				do {
 					//Сохраняем HANDLE файла, что бы можно было продожлить с того места
 					m_handle = hf;
 
-					String fullName = m_sPaths[pathIndex] + CWC2MB(FindFileData.cFileName);
-
-					DWORD flag = GetFileAttributesW(CMB2WC(fullName.c_str()));
-
 					if (emptyOrRepeatPath(CWC2MB(FindFileData.cFileName)))
 					{
 						continue;
 					}
 
+					sprintf(szFullName, "%s%s", m_aPaths[pathIndex].c_str(), (const char*)CWC2MB(FindFileData.cFileName));
+
+					DWORD flag = GetFileAttributesW(CMB2WC(szFullName));
+
 					if (flag != INVALID_FILE_ATTRIBUTES && (flag & FILE_ATTRIBUTE_DIRECTORY))
 					{
-						m_folderList.push_back(fullName + '/');
+						strcat(szFullName, "/");
+						m_aFolders.push_back(szFullName);
 						continue;
 					}
 
@@ -56,49 +64,55 @@ const char *CFileRecursiveExtPathsIterator::next()
 							continue;
 						}
 						//Если это файл - получаем относительный путь и ищем его в списке
-						m_pathStr = strstr(fullName.c_str(), m_sBasePath.c_str());
+						char *pos = strstr(szFullName, m_szBasePath);
+
+						if(pos)
+						{
+							strcpy(m_szPathStr, pos);
+						}
 
-						if (m_mapExistPath.KeyExists(m_pathStr))
+						if(m_mapExistPath.KeyExists(m_szPathStr))
 						{
 							continue;
 						} 
 						else
 						{
-							m_mapExistPath[m_pathStr] = pathIndex;
-							return m_pathStr.c_str();
+							m_mapExistPath[m_szPathStr] = pathIndex;
+							return(m_szPathStr);
 						}
 					}
 					//Если указатель на файл валидный, то проверяем все отфильтрованные файлы по порядку
 				} while (FindNextFileW(hf, &FindFileData) != 0);
 
-				if (m_folderList.size() != 0)
+				if (m_aFolders.size() != 0)
 				{
 					UINT index = 0;
-					m_sPaths[pathIndex] = m_folderList[index];
-					m_folderList.erase(index);	
+					m_aPaths[pathIndex] = m_aFolders[index];
+					m_aFolders.erase(index);	
 					m_handle = NULL;
 				}
 				else
 				{
-					m_sPaths[pathIndex] = m_currentFullPath;
+					m_aPaths[pathIndex] = m_szCurrentFullPath;
 				}
 			}
-		} while (m_sPaths[pathIndex] != m_currentFullPath);
+		}
+		while(m_aPaths[pathIndex] != m_szCurrentFullPath);
 		++pathIndex;
-		m_currentFullPath.release();
+		m_szCurrentFullPath[0] = 0;
 		m_handle = NULL;
 	}
 
 	//Если вообще не нашли файлов возвращаем nullptr
-	return nullptr;
+	return(NULL);
 }
 
 void CFileRecursiveExtPathsIterator::reset()
 {
-	if (m_sPaths.size() < pathIndex) 
-		m_sPaths[pathIndex] = m_currentFullPath;
+	if(m_aPaths.size() < pathIndex)
+		m_aPaths[pathIndex] = m_szCurrentFullPath;
 
-	m_currentFullPath.release();
+	m_szCurrentFullPath[0] = 0;
 	m_mapExistPath.clear();
 	pathIndex = 0;
 	CLOSE_HANDLE(m_handle);
diff --git a/source/core/FileRecursiveExtPathsIterator.h b/source/core/FileRecursiveExtPathsIterator.h
index 68d855c257d40aaca1d5930f623561740383674e..83a8d83fc13975b2d5480971c4a7b7f5ec4f4b13 100644
--- a/source/core/FileRecursiveExtPathsIterator.h
+++ b/source/core/FileRecursiveExtPathsIterator.h
@@ -11,14 +11,14 @@ See the license in LICENSE
 class CFileRecursiveExtPathsIterator final : public CBaseFileIterator
 {
 private:
-	Array<String> m_folderList;
-	Array<String> m_sPaths;
-	Array<String> m_exts;
+	Array<String> m_aFolders;
+	Array<String> m_aPaths;
+	Array<AAString> m_exts;
 	AssotiativeArray<String, int> m_mapExistPath;
 
-	String m_sBasePath;
-	String m_currentFullPath;
-    String m_pathStr;
+	char m_szBasePath[SIZE_PATH];
+	char m_szCurrentFullPath[SIZE_PATH];
+	char m_szPathStr[SIZE_PATH];
 	
     const char *m_szExt;
 	UINT pathIndex = 0;
@@ -26,7 +26,7 @@ private:
     HANDLE m_handle = nullptr;
 
 public:
-	CFileRecursiveExtPathsIterator(Array<String> &paths, String &sBasePath, const char **szExts, int iExtSize);
+	CFileRecursiveExtPathsIterator(Array<String> &paths, const char *szBasePath, const char **szExts, int iExtSize);
 
     const char* XMETHODCALLTYPE next() override;
 
diff --git a/source/core/FileSystem.cpp b/source/core/FileSystem.cpp
index a55a924115a431d9f0be246534152e526084bc39..0d2df98e80c5742d4dd6fde652cc07d2847bb68b 100644
--- a/source/core/FileSystem.cpp
+++ b/source/core/FileSystem.cpp
@@ -10,63 +10,64 @@ template <typename T>
 IFileIterator *CFileSystem::getListIterator(const char *szPath, const char **szExts, int extsCount)
 {
 	Array<String> paths;
-	String basePath(szPath);
+	char szBasePath[SIZE_PATH];
 
-	if (!isAbsolutePath(szPath))
+	strcpy(szBasePath, szPath);
+
+	if(!isAbsolutePath(szPath))
 	{
-		getAllvariantsCanonizePath(szPath, paths);
+		getAllvariantsCanonizePath(szPath, &paths);
 	}
 	else
 	{
 		paths.push_back(szPath);
 	}
 
-	return paths.size() ? new T(paths, basePath, szExts, extsCount) : nullptr;
+	return paths.size() ? new T(paths, szBasePath, szExts, extsCount) : nullptr;
 }
 
 void CFileSystem::addPathInPriorityArray(int id, int iPriority)
 {
-	Pair newElement{ iPriority, id };
+	Pair newElement{iPriority, id};
 
 	//Если приоритет по умолчанию и нет элементов - задаем значение 0 (потому что первый)
 	//Если элементов больше чем 0 то тогда ставим самый большой приоритет из возможных
-	if (iPriority == -1)
+	if(iPriority == -1)
 	{
 		UINT size = m_priorityArray.size();
 		newElement.priority = size > 0 ? m_priorityArray[0].priority + 1 : 0;
 	}
 
-	m_priorityArray.insert(newElement, [](const Pair &obj, const Pair &obj2) -> bool {return obj.priority <= obj2.priority; });
+	m_priorityArray.insert(newElement, [](const Pair &obj, const Pair &obj2) -> bool { return obj.priority <= obj2.priority; });
 }
 
 bool CFileSystem::isFileOrDirectory(const char *szPath, bool isFile)
 {
-    char path[SIZE_PATH];
-    getAbsoluteCanonizePath(szPath, path, SIZE_PATH);
+	char path[SIZE_PATH];
+	getAbsoluteCanonizePath(szPath, path, SIZE_PATH);
 
-    if (CHECK_CORRECT_PATH(path))
-    {
-        return false;
-    }
+	if(CHECK_CORRECT_PATH(path))
+	{
+		return false;
+	}
 
-    DWORD flag = GetFileAttributesW(CMB2WC(path));
+	DWORD flag = GetFileAttributesW(CMB2WC(path));
 
-    //Проверка на то куда имено ведет путь - к файлу или папке
-    return (flag != INVALID_FILE_ATTRIBUTES) && (isFile ? !(flag & FILE_ATTRIBUTE_DIRECTORY) : (flag & FILE_ATTRIBUTE_DIRECTORY));
+	//Проверка на то куда имено ведет путь - к файлу или папке
+	return (flag != INVALID_FILE_ATTRIBUTES) && (isFile ? !(flag & FILE_ATTRIBUTE_DIRECTORY) : (flag & FILE_ATTRIBUTE_DIRECTORY));
 }
 
-void CFileSystem::getAllvariantsCanonizePath(const char *szPath, Array<String> &container)
+void CFileSystem::getAllvariantsCanonizePath(const char *szPath, Array<String> *container)
 {
-	for (int i = 0, I = m_filePaths.size(); i < I; ++i)
+	char szBuff[SIZE_PATH];
+
+	for(int i = 0, I = m_filePaths.size(); i < I; ++i)
 	{
-		String buff = m_filePaths[i];
-		buff += '/';
-		buff += szPath;
-		buff += '/'; // <- оптимизация buffObj
+		sprintf(szBuff, "%s/%s/", m_filePaths[i].c_str(), szPath);
 
-		if (isDirectory(buff.c_str()))
+		if(isDirectory(szBuff))
 		{
-			container.push_back(buff);
+			container->push_back(szBuff);
 		}
 	}
 }
@@ -75,86 +76,85 @@ void CFileSystem::getNormalPath(const char *szPath, char *outBuff, int iOutMax)
 {
 	if(szPath != outBuff)
 	{
-    size_t len = strlen(szPath) + 1;
-
-		if(iOutMax < len)
-    {
-        MEMCCPY_ERROR(outBuff);
-        return;
-    }
+		if(iOutMax < strlen(szPath) + 1)
+		{
+			MEMCCPY_ERROR(outBuff);
+			return;
+		}
 
-    memcpy(outBuff, szPath, len);
+		strcpy(outBuff, szPath);
 	}
 
-    do
-    {
-        *outBuff = *outBuff == '/' ? '\\' : *outBuff;
-    } while (*outBuff++ != '\0');
+	do
+	{
+		*outBuff = *outBuff == '/' ? '\\' : *outBuff;
+	}
+	while(*outBuff++ != '\0');
 }
 
 bool CFileSystem::isAbsolutePathInRoot(const char *szPath)
 {
-    if (!isAbsolutePath(szPath))
-    {
-        return false;
-    }
+	if(!isAbsolutePath(szPath))
+	{
+		return false;
+	}
 
-    char rootPath[SIZE_PATH];
-    getFullPathToBuild(rootPath, SIZE_PATH);
-    const char *pos = strstr(szPath, rootPath);
+	char rootPath[SIZE_PATH];
+	getFullPathToBuild(rootPath, SIZE_PATH);
+	const char *pos = strstr(szPath, rootPath);
 
-    return pos != nullptr;
+	return pos != nullptr;
 }
 
 void CFileSystem::getAbsoluteCanonizePath(const char *szPath, char *outPath, int iOutMax)
 {
-    bool absolute = isAbsolutePath(szPath);
-    bool correctPath = true;
-
-    size_t len = absolute ? strlen(szPath) + 1 : SIZE_PATH;
-
-    if (absolute) 
-    {
-        memcpy(outPath, szPath, len);
-    }
-    else
-    { 
-        correctPath = resolvePath(szPath, outPath, len);
-    }
-
-    //Во время поиска пути могут произойти ошибки - путь может быть не найден, или слишком маленький буфер для записи
-    if (correctPath)
-    {
-        //Если все корректно прошло, то путь можно канонизировать
-        canonize_path(outPath);
-    }
-    else
-    {
-        //Если что то пошло не так записываем '\0' в память, потом это значение можно проверить
-        MEMCCPY_ERROR(outPath);
-    }
+	bool absolute = isAbsolutePath(szPath);
+	bool correctPath = true;
+
+	size_t len = absolute ? strlen(szPath) + 1 : SIZE_PATH;
+
+	if(absolute)
+	{
+		memcpy(outPath, szPath, len);
+	}
+	else
+	{
+		correctPath = resolvePath(szPath, outPath, len);
+	}
+
+	//Во время поиска пути могут произойти ошибки - путь может быть не найден, или слишком маленький буфер для записи
+	if(correctPath)
+	{
+		//Если все корректно прошло, то путь можно канонизировать
+		canonize_path(outPath);
+	}
+	else
+	{
+		//Если что то пошло не так записываем '\0' в память, потом это значение можно проверить
+		MEMCCPY_ERROR(outPath);
+	}
 }
 
 void CFileSystem::getFullPathToBuild(char *buff, int iSize)
 {
-    GetModuleFileName(nullptr, buff, iSize);
-    dirname(buff);
-    dirname(buff);
-    canonize_path(buff);
+	GetModuleFileName(nullptr, buff, iSize);
+	dirname(buff);
+	dirname(buff);
+	canonize_path(buff);
 }
 
 void CFileSystem::getFileName(const char *name, char *outName, int iOutBuff)
 {
-    WIN32_FIND_DATAW wfd;
+	WIN32_FIND_DATAW wfd;
 	HANDLE hFind = FindFirstFileW(CMB2WC(name), &wfd);
 
-    if (hFind != INVALID_HANDLE_VALUE)
-    {
-        FIND_CLOSE(hFind);
+	if(hFind != INVALID_HANDLE_VALUE)
+	{
+		FIND_CLOSE(hFind);
 
-        //Если размера буфера хватает - то записываем имя файла, если нет то записываем в [0] '\0'
+		//Если размера буфера хватает - то записываем имя файла, если нет то записываем в [0] '\0'
 		iOutBuff > MAX_PATH ? memcpy(outName, CWC2MB(wfd.cFileName), MAX_PATH) : MEMCCPY_ERROR(outName);
-    }
+	}
 }
 
 time_t CFileSystem::filetimeToTime_t(const FILETIME& ft)
@@ -163,7 +163,7 @@ time_t CFileSystem::filetimeToTime_t(const FILETIME& ft)
 	ull.LowPart = ft.dwLowDateTime;
 	ull.HighPart = ft.dwHighDateTime;
 
-    return ull.QuadPart / 10000000ULL - 11644473600ULL;
+	return ull.QuadPart / 10000000ULL - 11644473600ULL;
 }
 
 HANDLE CFileSystem::getFileHandle(const char *szPath)
@@ -179,135 +179,134 @@ HANDLE CFileSystem::getFileHandle(const char *szPath)
 
 bool CFileSystem::isAbsolutePath(const char *szPath)
 {
-    while (*szPath != '\0')
-    {
-        //Для корректности нужна проверка на разные слеши, ведь на вход может прийти путь не с /
-        if (*szPath == ':' && (*(szPath + 1) == '/' || *(szPath + 1) == '\\'))
-        {
-            return true;
-        }
-        ++szPath;
-    }
-    return false;
+	while(*szPath != '\0')
+	{
+		//Для корректности нужна проверка на разные слеши, ведь на вход может прийти путь не с /
+		if(*szPath == ':' && (*(szPath + 1) == '/' || *(szPath + 1) == '\\'))
+		{
+			return true;
+		}
+		++szPath;
+	}
+	return false;
 }
 
-String *CFileSystem::copyFile(const char* szPath)
+void CFileSystem::copyFile(const char* szPath, char *szOut)
 {
-    createDirectory(m_filePaths[m_writableRoot].c_str());
-
-    char fn[MAX_PATH];
-    getFileName(szPath, fn, MAX_PATH);
-    String *newFilePath = new String(m_filePaths[m_writableRoot] + '/' + fn);
-	CopyFileW(CMB2WC(szPath), CMB2WC(newFilePath->c_str()), false);
+	createDirectory(m_filePaths[m_writableRoot].c_str());
 
-    return newFilePath;
+	char fn[SIZE_PATH];
+	getFileName(szPath, fn, SIZE_PATH);
+	sprintf(szOut, "%s/%s", m_filePaths[m_writableRoot].c_str(), fn);
+	CopyFileW(CMB2WC(szPath), CMB2WC(szOut), false);
 }
 
 CFileSystem::CFileSystem()
 {
-    getFullPathToBuild(m_pathToBuild, SIZE_PATH);
+	getFullPathToBuild(m_pathToBuild, SIZE_PATH);
 }
 
 UINT CFileSystem::addRoot(const char *szPath, int iPriority)
 {
-    String str;
-
-    //Если путь не абсолютный - то прибавляем к нему часть пути к папке build
-    if (!isAbsolutePath(szPath))
-    {
-        str += m_pathToBuild;
-    }
+	char szBuff[SIZE_PATH];
 
-    str += szPath; // <--- Оптимизация для того что бы не создавать временных объектов
+	//Если путь не абсолютный - то прибавляем к нему часть пути к папке build
+	if(!isAbsolutePath(szPath))
+	{
+		sprintf(szBuff, "%s%s", m_pathToBuild, szPath);
+	}
+	else
+	{
+		sprintf(szBuff, "%s", szPath);
+	}
 
-    m_filePaths.push_back(str);
-    addPathInPriorityArray(m_filePaths.size() - 1, iPriority);
+	m_filePaths.push_back(szBuff);
+	addPathInPriorityArray(m_filePaths.size() - 1, iPriority);
 
-    //Если у нас некорректный путь для записи и путь не является архивным
-    if (m_writableRoot == -1 && *szPath != '@')
-    {
-        m_writableRoot = m_filePaths.size() - 1;
-    }
+	//Если у нас некорректный путь для записи и путь не является архивным
+	if(m_writableRoot == -1 && *szPath != '@')
+	{
+		m_writableRoot = m_filePaths.size() - 1;
+	}
 
-    return m_filePaths.size() - 1;
+	return m_filePaths.size() - 1;
 }
 
-UINT CFileSystem::getRootCount() const 
+UINT CFileSystem::getRootCount() const
 {
-    return m_filePaths.size();
+	return m_filePaths.size();
 }
 
 const char *CFileSystem::getRoot(UINT id) const
 {
-    FILEID_CHECKED(m_filePaths.size());
+	FILEID_CHECKED(m_filePaths.size());
 
-    return m_filePaths[id].c_str();
+	return m_filePaths[id].c_str();
 }
 
 void CFileSystem::setWritableRoot(UINT id)
 {
-    FILEID_CHECKED(m_filePaths.size());
+	FILEID_CHECKED(m_filePaths.size());
 
-    m_writableRoot = id;
+	m_writableRoot = id;
 }
 
 bool CFileSystem::resolvePath(const char *szPath, char *szOut, size_t iOutMax)
 {
-    size_t len = 0;
+	size_t len = 0;
 
-    if (isAbsolutePath(szPath))
-    {
-        len = strlen(szPath) + 1;
+	if(isAbsolutePath(szPath))
+	{
+		len = strlen(szPath) + 1;
 
-        CHECK_SIZE(len, iOutMax);
+		CHECK_SIZE(len, iOutMax);
 
-        memcpy(szOut, szPath, len);
-        return true;
-    }
-    
-    String buff;
+		memcpy(szOut, szPath, len);
+		return(true);
+	}
 
-    for (UINT i = 0, l = m_priorityArray.size(); i < l; ++i)
-    {
-        int id = m_priorityArray[i].pathId;
-        buff = (m_filePaths[id] + '/' + szPath);
+	char szBuff[SIZE_PATH];
 
-        if (fileExists(buff.c_str())/* && isFile(buff.c_str())*/)
-        {
-            CHECK_SIZE(len, iOutMax);
+	for(UINT i = 0, l = m_priorityArray.size(); i < l; ++i)
+	{
+		int id = m_priorityArray[i].pathId;
+		len = sprintf(szBuff, "%s/%s", m_filePaths[id].c_str(), szPath) + 1;
 
-            len = buff.length() + 1;
-            memcpy(szOut, buff.c_str(), len);
-            return true;
-        }
-    }
+		if(fileExists(szBuff)/* && isFile(buff.c_str())*/)
+		{
+			CHECK_SIZE(len, iOutMax);
 
-    return false;
+			strcpy(szOut, szBuff);
+			return(true);
+		}
+	}
+
+	return(false);
 }
 
 bool CFileSystem::fileExists(const char *szPath)
 {
-    char path[SIZE_PATH];
-    getAbsoluteCanonizePath(szPath, path, SIZE_PATH);
+	char path[SIZE_PATH];
+	getAbsoluteCanonizePath(szPath, path, SIZE_PATH);
 
-    if (CHECK_CORRECT_PATH(path))
-    {
-        //Если не удалось найти полный путь - на выход
-        return false;
-    }
+	if(CHECK_CORRECT_PATH(path))
+	{
+		//Если не удалось найти полный путь - на выход
+		return false;
+	}
 
-    return fileGetSize(path) != FILE_NOT_FOUND;
+	return(fileGetSize(path) != FILE_NOT_FOUND);
 }
 
 size_t CFileSystem::fileGetSize(const char *szPath)
 {
-    char path[SIZE_PATH];
-    getAbsoluteCanonizePath(szPath, path, SIZE_PATH);
+	char path[SIZE_PATH];
+	getAbsoluteCanonizePath(szPath, path, SIZE_PATH);
 
-    if (CHECK_CORRECT_PATH(path))
-    {
-        return FILE_NOT_FOUND;
-    }
+	if(CHECK_CORRECT_PATH(path))
+	{
+		return FILE_NOT_FOUND;
+	}
 
 	WIN32_FILE_ATTRIBUTE_DATA lpFileInformation;
 
@@ -315,234 +314,211 @@ size_t CFileSystem::fileGetSize(const char *szPath)
 
 	//Преобразование размера из старших и младших бит
 	ULONGLONG FileSize = (static_cast<ULONGLONG>(lpFileInformation.nFileSizeHigh) <<
-        sizeof(lpFileInformation.nFileSizeLow) * NUM_BITS_IN_BYTE) |
+		sizeof(lpFileInformation.nFileSizeLow) * NUM_BITS_IN_BYTE) |
 		lpFileInformation.nFileSizeLow;
 
 	//Если result != 0 то все хорошо, если 0 то файл не найден
-	return result != 0 ? FileSize : FILE_NOT_FOUND;
+	return(result != 0 ? FileSize : FILE_NOT_FOUND);
 }
 
 bool CFileSystem::isFile(const char *szPath)
 {
-    return isFileOrDirectory(szPath, true);
+	return(isFileOrDirectory(szPath, true));
 }
 
 bool CFileSystem::isDirectory(const char *szPath)
 {
-    return isFileOrDirectory(szPath, false);
+	return(isFileOrDirectory(szPath, false));
 }
 
 time_t CFileSystem::getFileModifyTime(const char *szPath)
 {
-    char path[SIZE_PATH];
-    getAbsoluteCanonizePath(szPath, path, SIZE_PATH);
+	char path[SIZE_PATH];
+	getAbsoluteCanonizePath(szPath, path, SIZE_PATH);
 
-    if (CHECK_CORRECT_PATH(path))
-    {
-        return 0;
-    }
+	if(CHECK_CORRECT_PATH(path))
+	{
+		return(0);
+	}
 
-    WIN32_FILE_ATTRIBUTE_DATA lpFileInformation;
+	WIN32_FILE_ATTRIBUTE_DATA lpFileInformation;
 
 	GetFileAttributesExW(CMB2WC(path), GetFileExInfoStandard, &lpFileInformation);
 
-	return filetimeToTime_t(lpFileInformation.ftLastWriteTime);
+	return(filetimeToTime_t(lpFileInformation.ftLastWriteTime));
 }
 
 IFileIterator *CFileSystem::getFolderList(const char *szPath)
 {
 	Array<String> paths;
-	String basePath(szPath);
+	char szBasePath[SIZE_PATH];
+
+	strcpy(szBasePath, szPath);
 
-	if (!isAbsolutePath(szPath)) 
+	if(!isAbsolutePath(szPath))
 	{
-		getAllvariantsCanonizePath(szPath, paths);
-	} 
+		getAllvariantsCanonizePath(szPath, &paths);
+	}
 	else
 	{
 		paths.push_back(szPath);
 	}
 
-	return paths.size() ? new CFolderPathsIterator(paths, basePath) : nullptr;
+	return(paths.size() ? new CFolderPathsIterator(paths, szBasePath) : NULL);
 }
 
 IFileIterator *CFileSystem::getFileList(const char *szPath, const char *szExt)
 {
-	const char *exts[] = { szExt };
-	return getFileList(szPath, exts, 1);
+	const char *exts[] = {szExt};
+	return(getFileList(szPath, exts, 1));
 }
 
 IFileIterator *CFileSystem::getFileList(const char *szPath, const char **szExts, int extsCount)
 {
-	return getListIterator<CFileExtsIterator>(szPath, szExts, extsCount);
+	return(getListIterator<CFileExtsIterator>(szPath, szExts, extsCount));
 }
 
 IFileIterator *CFileSystem::getFileListRecursive(const char *szPath, const char *szExt)
 {
-	const char *exts[] = { szExt };
-	return getFileListRecursive(szPath, exts, 1);
+	const char *exts[] = {szExt};
+	return(getFileListRecursive(szPath, exts, 1));
 }
 
 IFileIterator *CFileSystem::getFileListRecursive(const char *szPath, const char **szExts, int extsCount)
 {
-	return getListIterator<CFileRecursiveExtPathsIterator>(szPath, szExts, extsCount);
+	return(getListIterator<CFileRecursiveExtPathsIterator>(szPath, szExts, extsCount));
 }
 
 bool CFileSystem::createDirectory(const char *szPath)
 {
-    char path[SIZE_PATH];
-    getNormalPath(szPath, path, SIZE_PATH);
-    if(!isAbsolutePath(path))
-    {
-        char szBuf[MAX_PATH];
-        szBuf[0] = 0;
-        strcat(szBuf, m_filePaths[m_writableRoot].c_str());
-        strcat(szBuf, "/");
-        strcat(szBuf, szPath);
-        char *tmp = szBuf;
-        while(*tmp)
-        {
-            if(*tmp == '/')
-            {
-                *tmp = '\\';
-            }
-            ++tmp;
-        }
-        return(SHCreateDirectoryExW(nullptr, CMB2WC(szBuf), nullptr) == NO_ERROR);
-    }
+	char path[SIZE_PATH];
+	getNormalPath(szPath, path, SIZE_PATH);
+	if(!isAbsolutePath(path))
+	{
+		char szBuf[SIZE_PATH];
+		sprintf(szBuf, "%s/%s", m_filePaths[m_writableRoot].c_str(), szPath);
+		char *tmp = szBuf;
+		while(*tmp)
+		{
+			if(*tmp == '/')
+			{
+				*tmp = '\\';
+			}
+			++tmp;
+		}
+		return(SHCreateDirectoryExW(nullptr, CMB2WC(szBuf), nullptr) == NO_ERROR);
+	}
 
 	return(SHCreateDirectoryExW(nullptr, CMB2WC(path), nullptr) == NO_ERROR);
 }
 
 bool CFileSystem::deleteDirectory(const char *szPath)
 {
-    char path[SIZE_PATH];
+	char path[SIZE_PATH];
 	getAbsoluteCanonizePath(szPath, path, SIZE_PATH);
 	getNormalPath(path, path, SIZE_PATH);
 
-    CMB2WC wszPath(path);
-    size_t len = wcslen(wszPath);
-    wchar_t *pBuf = (wchar_t*)alloca(sizeof(wchar_t) * (len + 2));
-    memcpy(pBuf, wszPath, sizeof(wchar_t) * (len + 1));
-    pBuf[len + 1] = 0;
-
-    SHFILEOPSTRUCTW file_op = {
-        NULL,
-        FO_DELETE,
-        pBuf,
-        L"",
-        FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT,
-        false,
-        0,
-        L"" 
-    };
-
-    // Если вернуло не 0, то все плохо
-    return(SHFileOperationW(&file_op) == NO_ERROR);
+	CMB2WC wszPath(path);
+	size_t len = wcslen(wszPath);
+	wchar_t *pBuf = (wchar_t*)alloca(sizeof(wchar_t) * (len + 2));
+	memcpy(pBuf, wszPath, sizeof(wchar_t) * (len + 1));
+	pBuf[len + 1] = 0;
+
+	SHFILEOPSTRUCTW file_op = {
+		NULL,
+		FO_DELETE,
+		pBuf,
+		L"",
+		FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT,
+		false,
+		0,
+		L""
+	};
+
+	// Если вернуло не 0, то все плохо
+	return(SHFileOperationW(&file_op) == NO_ERROR);
 }
 
 IFile *CFileSystem::openFile(const char *szPath, FILE_OPEN_MODE mode = FILE_MODE_READ)
 {
-    //Выходим если режим открытия - не для чтения и нет пути для записи
-    if (m_writableRoot == -1 && mode != FILE_MODE_READ)
-    {
-        return nullptr;
-    }
-
-    //Зарезервированная строка, если вдруг не хватит SIZE_PATH
-    char reserveStr[SIZE_PATH * 2];
-    bool useReserveStr = false;
-
-    char fullPath[SIZE_PATH];
-    getAbsoluteCanonizePath(szPath, fullPath, SIZE_PATH);
-
-    //Если по каким либо причинам нельзя вернуть полный путь - на выход
-    if (CHECK_CORRECT_PATH(fullPath) && mode == FILE_MODE_READ)
-    {
-        return nullptr;
-    }
-
-    IFile *file = new CFile;
-
-    if (mode == FILE_MODE_READ)
-    {
-        //Если открываем только на чтение - то копирование не нужно (следовательно и выделение памяти тоже лишняя операция)
-        if (file->open(fullPath, CORE_FILE_BIN) != 0)
-        {
-            mem_delete(file);
-        }
-        return file;
-    }
-
-    String *newFileName;
-
-    if (CHECK_CORRECT_PATH(fullPath))
-    {
-        newFileName = new String(m_filePaths[m_writableRoot].c_str());
-        *newFileName += '/';
-        *newFileName += szPath;
-
-        getAbsoluteCanonizePath(newFileName->c_str(), fullPath, SIZE_PATH);
-
-        mem_delete(newFileName);
-    }
-
-    bool inRoot = isAbsolutePathInRoot(fullPath);
-
-    //Если путь в корне, и файла не существует - создаем его
-    if (inRoot && !fileExists(fullPath))
-    {
-        size_t len = strlen(fullPath) + 1;
-        char dirName[SIZE_PATH];
-
-        memcpy(dirName, fullPath, len);
-        dirname(dirName);
-        len = strlen(dirName);
-        dirName[len - 1] = '\0';
-        createDirectory(dirName);
-    }
-    //Если путь не в корне и его не существует - на выход
-    else if (!fileExists(fullPath))
-    {
-        mem_delete(file);
-        return nullptr;
-    }
-    //Если путь вне корня - тогда копируем в корень
-    else if (!inRoot)
-    {
-        newFileName = copyFile(fullPath);
-        size_t lenPath = strlen(fullPath) + 1;
-        
-        if (lenPath < newFileName->length())
-        {
-            memcpy(reserveStr, newFileName->c_str(), newFileName->length() + 1);
-            useReserveStr = true;
-        }
-
-        memcpy(fullPath, newFileName->c_str(), newFileName->length() + 1);
-        mem_delete(newFileName);
-    }
-
-    int res = 0;
-
-    //Определяем использован ли дополнительный путь для записи в файловой системе
-    char* correctPath = useReserveStr ? reserveStr : fullPath;
-
-    switch (mode)
-    {
-    case FILE_MODE_WRITE:
-        res = file->create(correctPath, CORE_FILE_BIN);
-        break;
-
-    case FILE_MODE_APPEND:
-        res = file->add(correctPath, CORE_FILE_BIN);
-        break;
-    }
-
-    if (res != 0)
-    {
-        mem_delete(file);
-    }
-
-    return file;
+	//Выходим если режим открытия - не для чтения и нет пути для записи
+	if(m_writableRoot == -1 && mode != FILE_MODE_READ)
+	{
+		return(NULL);
+	}
+
+	char fullPath[SIZE_PATH];
+	getAbsoluteCanonizePath(szPath, fullPath, SIZE_PATH);
+
+	//Если по каким либо причинам нельзя вернуть полный путь - на выход
+	if(CHECK_CORRECT_PATH(fullPath) && mode == FILE_MODE_READ)
+	{
+		return(NULL);
+	}
+
+	IFile *file = new CFile;
+
+	if(mode == FILE_MODE_READ)
+	{
+		//Если открываем только на чтение - то копирование не нужно (следовательно и выделение памяти тоже лишняя операция)
+		if(file->open(fullPath, CORE_FILE_BIN) != 0)
+		{
+			mem_delete(file);
+		}
+		return(file);
+	}
+
+	char szNewFileName[SIZE_PATH];
+
+	if(CHECK_CORRECT_PATH(fullPath))
+	{
+		sprintf(szNewFileName, "%s/%s", m_filePaths[m_writableRoot].c_str(), szPath);
+
+		getAbsoluteCanonizePath(szNewFileName, fullPath, SIZE_PATH);
+	}
+
+	bool inRoot = isAbsolutePathInRoot(fullPath);
+
+	//Если путь в корне, и файла не существует - создаем его
+	if(inRoot && !fileExists(fullPath))
+	{
+		char dirName[SIZE_PATH];
+
+		strcpy(dirName, fullPath);
+		dirname(dirName);
+		createDirectory(dirName);
+	}
+	//Если путь не в корне и его не существует - на выход
+	else if(!fileExists(fullPath))
+	{
+		mem_delete(file);
+		return(NULL);
+	}
+	//Если путь вне корня - тогда копируем в корень
+	else if(!inRoot)
+	{
+		copyFile(fullPath, szNewFileName);
+
+		strcpy(fullPath, szNewFileName);
+	}
+
+	int res = 0;
+
+	switch(mode)
+	{
+	case FILE_MODE_WRITE:
+		res = file->create(fullPath, CORE_FILE_BIN);
+		break;
+
+	case FILE_MODE_APPEND:
+		res = file->add(fullPath, CORE_FILE_BIN);
+		break;
+	}
+
+	if(res != 0)
+	{
+		mem_delete(file);
+	}
+
+	return(file);
 }
\ No newline at end of file
diff --git a/source/core/FileSystem.h b/source/core/FileSystem.h
index 5bcfcb4536f716d7124ef8eed633e424743fa070..fa1942b52f03b124d6839d50a7b50dbe88c192fe 100644
--- a/source/core/FileSystem.h
+++ b/source/core/FileSystem.h
@@ -43,7 +43,7 @@ See the license in LICENSE
 
 #define MEMCCPY_ERROR(buff) memcpy(buff, "\0", 1);
 
-#define SIZE_PATH 4096
+#define SIZE_PATH 2048
 
 #define INVALID_OR_NULL(handle) ((handle) == NULL || (handle) == INVALID_HANDLE_VALUE)
 
@@ -106,7 +106,7 @@ private:
 	//! Метод делает проверку, ведет ли путь к файлу или папке
 	bool isFileOrDirectory(const char *szPath, bool isFile);
 
-	void getAllvariantsCanonizePath(const char *szPath, Array<String> &container);
+	void getAllvariantsCanonizePath(const char *szPath, Array<String> *container);
 
 	//!Превращает канонизированный путь в неканонизированный
 	void getNormalPath(const char *szPath, char *outBuff, int iOutMax);
@@ -128,7 +128,7 @@ private:
 
 	bool isAbsolutePath(const char* szPath);
 
-	String *copyFile(const char* szPath);
+	void copyFile(const char* szPath, char *szOut);
 
 	//!корневые пути и приоритет
 	Array<String> m_filePaths;
diff --git a/source/core/FolderPathsIterator.cpp b/source/core/FolderPathsIterator.cpp
index 54e2b9cc752293849f0cdbe6572269becbdbe4e8..dd271e77cbcebc1dc26ba6f432f3adc5530d35bd 100644
--- a/source/core/FolderPathsIterator.cpp
+++ b/source/core/FolderPathsIterator.cpp
@@ -1,73 +1,75 @@
 #include "FolderPathsIterator.h"
 
-CFolderPathsIterator::CFolderPathsIterator(Array<String> &paths, String &sBasePath)
+CFolderPathsIterator::CFolderPathsIterator(Array<String> &paths, const char *szBasePath)
 {
-	this->canonizePaths(paths);
-	this->canonizePath(sBasePath);
+	m_paths.swap(paths);
+	strcpy(m_szBasePath, szBasePath);
 
-	this->m_paths = paths;
-	this->m_sBasePath = sBasePath;
+	canonizePaths(m_paths);
+	canonizePath(m_szBasePath);
 }
 
 const char *CFolderPathsIterator::next()
 {
-    WIN32_FIND_DATAW FindFileData;
-    HANDLE hf;
+	WIN32_FIND_DATAW FindFileData;
+	HANDLE hf;
 
 	FindFileData.cFileName[0] = '\0';
 
 	UINT size = m_paths.size();
 
-    while (index < size)
-    {
+	while(index < size)
+	{
 		hf = INVALID_OR_NULL(m_handle) ? FindFirstFileW(CMB2WC((m_paths[index] + "*.*").c_str()), &FindFileData) : m_handle;
 
-        if (hf != INVALID_HANDLE_VALUE)
-        {
-            do {
-                m_handle = hf;
+		if(hf != INVALID_HANDLE_VALUE)
+		{
+			do {
+				m_handle = hf;
 
-				m_pathStr = (m_paths)[index] + CWC2MB(FindFileData.cFileName);
+				sprintf(m_szPathStr, "%s%s", (m_paths)[index].c_str(), (const char*)CWC2MB(FindFileData.cFileName));
 
-				DWORD flag = GetFileAttributesW(CMB2WC(m_pathStr.c_str()));
+				DWORD flag = GetFileAttributesW(CMB2WC(m_szPathStr));
 
-				if (emptyOrRepeatPath(CWC2MB(FindFileData.cFileName)))
-                {
-                    continue;
-                }
+				if(emptyOrRepeatPath(CWC2MB(FindFileData.cFileName)))
+				{
+					continue;
+				}
 
-                //Берет только имена директорий
-                if (flag != INVALID_FILE_ATTRIBUTES && flag & FILE_ATTRIBUTE_DIRECTORY)
-                {
-					m_pathStr = (m_sBasePath + CWC2MB(FindFileData.cFileName));
-					if (m_mapExistPath.KeyExists(m_pathStr))
+				//Берет только имена директорий
+				if(flag != INVALID_FILE_ATTRIBUTES && flag & FILE_ATTRIBUTE_DIRECTORY)
+				{
+					sprintf(m_szPathStr, "%s%s", m_szBasePath, (const char*)CWC2MB(FindFileData.cFileName));
+
+					if(m_mapExistPath.KeyExists(m_szPathStr))
 					{
 						continue;
 					}
 					else
 					{
 						//Возвращаем относительный путь к директории
-						m_mapExistPath[m_pathStr] = index;
-						return m_pathStr.c_str();
+						m_mapExistPath[m_szPathStr] = index;
+						return(m_szPathStr);
 					}
-                }
-			} while (FindNextFileW(hf, &FindFileData) != 0);
-        }
+				}
+			}
+			while(FindNextFileW(hf, &FindFileData) != 0);
+		}
 		FIND_CLOSE(m_handle);
 		++index;
-    }
+	}
 
-    //Если вообще не нашли файлов возвращаем nullptr
-    return nullptr;
+	//Если вообще не нашли файлов возвращаем nullptr
+	return(NULL);
 }
 
 void CFolderPathsIterator::reset()
 {
-    index = 0;
-    FIND_CLOSE(m_handle);
+	index = 0;
+	FIND_CLOSE(m_handle);
 }
 
 CFolderPathsIterator::~CFolderPathsIterator()
 {
-    FIND_CLOSE(m_handle);
+	FIND_CLOSE(m_handle);
 }
diff --git a/source/core/FolderPathsIterator.h b/source/core/FolderPathsIterator.h
index ae74e5a345dd64722b56c9171d9821faedfdf021..ebf4359fdbbda7887b6e047e04a45e2443bafd62 100644
--- a/source/core/FolderPathsIterator.h
+++ b/source/core/FolderPathsIterator.h
@@ -13,8 +13,8 @@ class CFolderPathsIterator final : public CBaseFileIterator
 private:
 
     Array<String> m_paths;
-	String m_sBasePath;
-    String m_pathStr;
+	char m_szBasePath[SIZE_PATH];
+	char m_szPathStr[SIZE_PATH];
 	AssotiativeArray<String, int> m_mapExistPath;
 
     int index = 0;
@@ -22,7 +22,7 @@ private:
     HANDLE m_handle = nullptr;
 
 public:
-	CFolderPathsIterator(Array<String> &paths, String &sBasePath);
+	CFolderPathsIterator(Array<String> &paths, const char *szBasePath);
 
     const char* XMETHODCALLTYPE next() override;
 
diff --git a/source/core/JSON.cpp b/source/core/JSON.cpp
index 01cf57087512654df973e57ae71807a483decf4f..c25acd44e60fe9529a69ea96a0fa640453d535e7 100644
--- a/source/core/JSON.cpp
+++ b/source/core/JSON.cpp
@@ -1,5 +1,9 @@
 #include "JSON.h"
 
+MemAlloc<CJSONValue> CJSON::ms_memValues;
+MemAlloc<CJSONArray> CJSON::ms_memArrays;
+MemAlloc<CJSONObject> CJSON::ms_memObjects;
+SpinLock CJSON::ms_slMem;
 
 bool XMETHODCALLTYPE CJSON::parse(const char *szString, IXJSONItem **ppOut, void *pReserved) const
 {
@@ -25,7 +29,7 @@ bool CJSON::Parse(const char **str, IXJSONItem **ppOut)
 
 	if(**str == '{')
 	{
-		CJSONObject *o = new CJSONObject();
+		CJSONObject *o = AllocObject();
 		if(!o->load(str))
 		{
 			mem_release(o);
@@ -35,7 +39,7 @@ bool CJSON::Parse(const char **str, IXJSONItem **ppOut)
 	}
 	else if(**str == '[')
 	{
-		CJSONArray *o = new CJSONArray();
+		CJSONArray *o = AllocArray();
 		if(!o->load(str))
 		{
 			mem_release(o);
@@ -45,7 +49,7 @@ bool CJSON::Parse(const char **str, IXJSONItem **ppOut)
 	}
 	else
 	{
-		CJSONValue *o = new CJSONValue();
+		CJSONValue *o = AllocValue();
 		if(!o->load(str))
 		{
 			mem_release(o);
@@ -56,16 +60,40 @@ bool CJSON::Parse(const char **str, IXJSONItem **ppOut)
 	return(true);
 }
 
-//##########################################################################
+CJSONValue* CJSON::AllocValue()
+{
+	ScopedSpinLock lock(ms_slMem);
+	return(ms_memValues.Alloc());
+}
+CJSONArray* CJSON::AllocArray()
+{
+	ScopedSpinLock lock(ms_slMem);
+	return(ms_memArrays.Alloc());
+}
+CJSONObject* CJSON::AllocObject()
+{
+	ScopedSpinLock lock(ms_slMem);
+	return(ms_memObjects.Alloc());
+}
 
-CJSONArray::~CJSONArray()
+void CJSON::ReleaseItem(CJSONValue *pItem)
 {
-	for(UINT i = 0, l = m_aItems.size(); i < l; ++i)
-	{
-		mem_release(m_aItems[i]);
-	}
+	ScopedSpinLock lock(ms_slMem);
+	ms_memValues.Delete(pItem);
+}
+void CJSON::ReleaseItem(CJSONArray *pItem)
+{
+	ScopedSpinLock lock(ms_slMem);
+	ms_memArrays.Delete(pItem);
+}
+void CJSON::ReleaseItem(CJSONObject *pItem)
+{
+	ScopedSpinLock lock(ms_slMem);
+	ms_memObjects.Delete(pItem);
 }
 
+//##########################################################################
+
 UINT XMETHODCALLTYPE CJSONArray::size() const
 {
 	return(m_aItems.size());
@@ -122,16 +150,18 @@ bool CJSONArray::loadArr(const char **str)
 	return(true);
 }
 
-//##########################################################################
-
-CJSONObject::~CJSONObject()
+void XMETHODCALLTYPE CJSONArray::FinalRelease()
 {
-	for(UINT i = 0, l = m_aPairs.size(); i < l; ++i)
+	for(UINT i = 0, l = m_aItems.size(); i < l; ++i)
 	{
-		mem_release(m_aPairs[i].pVal);
+		mem_release(m_aItems[i]);
 	}
+
+	CJSON::ReleaseItem(this);
 }
 
+//##########################################################################
+
 UINT XMETHODCALLTYPE CJSONObject::size() const
 {
 	return(m_aPairs.size());
@@ -216,3 +246,21 @@ bool CJSONObject::loadObj(const char **str)
 	++*str;
 	return(true);
 }
+
+void XMETHODCALLTYPE CJSONObject::FinalRelease()
+{
+	for(UINT i = 0, l = m_aPairs.size(); i < l; ++i)
+	{
+		mem_release(m_aPairs[i].pVal);
+	}
+
+	CJSON::ReleaseItem(this);
+}
+
+//##########################################################################
+
+void XMETHODCALLTYPE CJSONValue::FinalRelease()
+{
+	CJSON::ReleaseItem(this);
+}
+
diff --git a/source/core/JSON.h b/source/core/JSON.h
index 6f72cb87c4856de359c8661a6a542f8ad6602977..92b7d5f534b5ba6f9bf0e38091b4740cff9b80f0 100644
--- a/source/core/JSON.h
+++ b/source/core/JSON.h
@@ -62,7 +62,7 @@ public:
 			return(true);
 		case XJI_STRING:
 			//! FIXME add some checks!
-			*pOut = m_sVal.toDouble();
+			*pOut = m_sVal.toFloat();
 			return(true);
 		
 		case XJI_NULL:
@@ -83,7 +83,7 @@ public:
 		case XJI_STRING:
 			//! FIXME add some checks!
 			//! FIXME fix type!
-			*pOut = m_sVal.toLongInt();
+			*pOut = m_sVal.toInt64();
 			return(true);
 
 		case XJI_NULL:
@@ -195,8 +195,10 @@ public:
 private:
 	bool loadString(const char **prm)
 	{
+		m_sVal = "";
+
 		m_type = XJI_STRING;
-		Array<char> tmp;
+		//Array<char> tmp;
 		++*prm;
 		while(**prm && **prm != '"')
 		{
@@ -208,22 +210,22 @@ private:
 				case L'"':
 				case L'\\':
 				case L'/':
-					tmp.push_back(**prm);
+					m_sVal += **prm;
 					break;
 				case L'b':
-					tmp.push_back('\b');
+					m_sVal += '\b';
 					break;
 				case L'f':
-					tmp.push_back('\f');
+					m_sVal += '\f';
 					break;
 				case L'n':
-					tmp.push_back('\n');
+					m_sVal += '\n';
 					break;
 				case L'r':
-					tmp.push_back('\r');
+					m_sVal += '\r';
 					break;
 				case L't':
-					tmp.push_back('\t');
+					m_sVal += '\t';
 					break;
 				case L'u':
 				{
@@ -249,7 +251,7 @@ private:
 							return(false);
 						}
 					}
-					writeChar(code, &tmp);
+					writeChar(code);
 				}
 				break;
 				default:
@@ -258,7 +260,7 @@ private:
 			}
 			else
 			{
-				tmp.push_back(**prm);
+				m_sVal += **prm;
 			}
 			++*prm;
 		}
@@ -267,8 +269,6 @@ private:
 			return(false);
 		}
 		++*prm;
-		tmp.push_back(0);
-		m_sVal = tmp;
 		return(true);
 	}
 	bool loadNum(const char **prm)
@@ -399,7 +399,7 @@ private:
 		return(false);
 	}
 
-	void writeChar(UINT c, Array<char> *pOut)
+	void writeChar(UINT c)
 	{
 		UINT codepoint = 0;
 		short *in = (short*)&c;
@@ -422,28 +422,28 @@ private:
 
 				if(codepoint <= 0x7f)
 				{
-					pOut->push_back(codepoint);
+					m_sVal += (char)codepoint;
 					break;
 				}
 				else if(codepoint <= 0x7ff)
 				{
-					pOut->push_back(0xc0 | ((codepoint >> 6) & 0x1f));
-					pOut->push_back(0x80 | (codepoint & 0x3f));
+					m_sVal += (char)(0xc0 | ((codepoint >> 6) & 0x1f));
+					m_sVal += (char)(0x80 | (codepoint & 0x3f));
 					break;
 				}
 				else if(codepoint <= 0xffff)
 				{
-					pOut->push_back(0xe0 | ((codepoint >> 12) & 0x0f));
-					pOut->push_back(0x80 | ((codepoint >> 6) & 0x3f));
-					pOut->push_back(0x80 | (codepoint & 0x3f));
+					m_sVal += (char)(0xe0 | ((codepoint >> 12) & 0x0f));
+					m_sVal += (char)(0x80 | ((codepoint >> 6) & 0x3f));
+					m_sVal += (char)(0x80 | (codepoint & 0x3f));
 					break;
 				}
 				else
 				{
-					pOut->push_back(0xf0 | ((codepoint >> 18) & 0x07));
-					pOut->push_back(0x80 | ((codepoint >> 12) & 0x3f));
-					pOut->push_back(0x80 | ((codepoint >> 6) & 0x3f));
-					pOut->push_back(0x80 | (codepoint & 0x3f));
+					m_sVal += (char)(0xf0 | ((codepoint >> 18) & 0x07));
+					m_sVal += (char)(0x80 | ((codepoint >> 12) & 0x3f));
+					m_sVal += (char)(0x80 | ((codepoint >> 6) & 0x3f));
+					m_sVal += (char)(0x80 | (codepoint & 0x3f));
 					break;
 				}
 			}
@@ -458,15 +458,19 @@ protected:
 	mutable String m_sVal;
 };
 
+//##########################################################################
+
 class CJSONValue: public CJSONItem<IXJSONItem>
 {
+private:
+	void XMETHODCALLTYPE FinalRelease() override;
 };
 
+//##########################################################################
+
 class CJSONArray: public CJSONItem<IXJSONArray>
 {
 public:
-	~CJSONArray();
-
 	UINT XMETHODCALLTYPE size() const override;
 	IXJSONItem* XMETHODCALLTYPE at(UINT idx) const override;
 
@@ -481,16 +485,18 @@ public:
 
 private:
 	bool loadArr(const char **str) override;
+	
+	void XMETHODCALLTYPE FinalRelease() override;
 
 private:
 	Array<IXJSONItem*> m_aItems;
 };
 
+//##########################################################################
+
 class CJSONObject: public CJSONItem<IXJSONObject>
 {
 public:
-	~CJSONObject();
-
 	UINT XMETHODCALLTYPE size() const override;
 	IXJSONItem* XMETHODCALLTYPE at(UINT idx) const override;
 	
@@ -509,7 +515,10 @@ public:
 
 private:
 	bool loadObj(const char **str) override;
-	
+
+	void XMETHODCALLTYPE FinalRelease() override;
+
+private:
 	struct KeyValue
 	{
 		String sKey;
@@ -519,12 +528,30 @@ private:
 	Array<KeyValue> m_aPairs;
 };
 
+//##########################################################################
+
 class CJSON: public IXUnknownImplementation<IXJSON>
 {
 public:
 	bool XMETHODCALLTYPE parse(const char *szString, IXJSONItem **ppOut, void *pReserved = NULL) const override;
 
 	static bool Parse(const char **str, IXJSONItem **ppOut);
+
+	static void ReleaseItem(CJSONValue *pItem);
+	static void ReleaseItem(CJSONArray *pItem);
+	static void ReleaseItem(CJSONObject *pItem);
+
+private:
+	static MemAlloc<CJSONValue> ms_memValues;
+	static MemAlloc<CJSONArray> ms_memArrays;
+	static MemAlloc<CJSONObject> ms_memObjects;
+	
+	static SpinLock ms_slMem;
+
+private:
+	static CJSONValue* AllocValue();
+	static CJSONArray* AllocArray();
+	static CJSONObject* AllocObject();
 };
 
 #endif
diff --git a/source/fbxplugin/ModelLoader.cpp b/source/fbxplugin/ModelLoader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f8870422b1f16fd239e0f4b1a8a545bae3691415
--- /dev/null
+++ b/source/fbxplugin/ModelLoader.cpp
@@ -0,0 +1,1186 @@
+#include "ModelLoader.h"
+#include "libdeflate.h"
+
+CModelLoader::CModelLoader(IFileSystem *pFileSystem):
+m_pFileSystem(pFileSystem)
+{
+}
+
+UINT XMETHODCALLTYPE CModelLoader::getExtCount() const
+{
+	return(1);
+}
+const char* XMETHODCALLTYPE CModelLoader::getExt(UINT uIndex) const
+{
+	assert(uIndex < getExtCount());
+	switch(uIndex)
+	{
+	case 0:
+		return("fbx");
+	}
+	return(NULL);
+}
+const char* XMETHODCALLTYPE CModelLoader::getExtText(UINT uIndex) const
+{
+	assert(uIndex < getExtCount());
+	switch(uIndex)
+	{
+	case 0:
+		return("Kaydara model");
+	}
+	return(NULL);
+}
+const char* XMETHODCALLTYPE CModelLoader::getAuthor() const
+{
+	return("EyeGuy @ Thunderfront Studio");
+}
+const char* XMETHODCALLTYPE CModelLoader::getCopyright() const
+{
+	return("Copyright © Ivan Dokunov, Evgeny Danilovich, 2025");
+}
+const char* XMETHODCALLTYPE CModelLoader::getDescription() const
+{
+	return("FBX model loader");
+}
+
+void XMETHODCALLTYPE CModelLoader::getInfo(XModelInfo *pModelInfo)
+{
+	*pModelInfo = {};
+
+	Array<FBXNodeRecord*> aNodes;
+	Array<FBXNodeRecord*> aBufferNodes;
+
+	getNodeByName("Model", &aNodes);
+
+	const size_t lodeSize = 10;
+	int iBestIndex = -1;
+	bool aLodExist[lodeSize] = {};
+	int32_t aLodIndex[lodeSize] = {};
+
+	Array<int32_t> aMaterialsIndexes;
+	Array<FBXNodeRecord*> aMaterials;
+
+	fora(i, aNodes)
+	{
+		int index = 0;
+		if(aNodes[i]->aProps.size() > 1 && aNodes[i]->aProps[1].cType == 'S')
+		{
+			FBXPropertyRecord &props = aNodes[i]->aProps[1];
+			size_t len = strlen(props.data.pc);
+			// len > strlen(LOD) word
+			// после props.data.pc есть еще одна строка (размер strlen(props.data.pc) и props.uArrSize будет разным)
+			if(len > 5)
+			{
+				if(
+					tolower(props.data.pc[len - 4]) == 'l' &&
+					tolower(props.data.pc[len - 3]) == 'o' &&
+					tolower(props.data.pc[len - 2]) == 'd' &&
+					isdigit(props.data.pc[len - 1]))
+				{
+					index = props.data.pc[len - 1] - '0';
+				}
+			}
+		}
+		aLodExist[index] = true;
+
+		if(index < iBestIndex || iBestIndex == -1)
+		{
+			iBestIndex = index;
+
+			aMaterials.clearFast();
+
+			pModelInfo->uVertexCount = 0;
+			pModelInfo->uIndexCount = 0;
+		}
+
+		if(index == iBestIndex)
+		{
+			aBufferNodes.clearFast();
+
+			FBXNodeRecord *pGeomNode = getChildNode("Geometry", aNodes[i]);
+
+			getNodeByName("Vertices", &aBufferNodes, pGeomNode);
+			FBXPropertyRecord *pProp = &aBufferNodes[0]->aProps[0];
+
+			assert(pProp->cType == 'd');
+
+			pModelInfo->uVertexCount += pProp->uArrSize / 3;
+
+			aBufferNodes.clearFast();
+			getNodeByName("PolygonVertexIndex", &aBufferNodes, pGeomNode);
+			pProp = &aBufferNodes[0]->aProps[0];
+			
+			assert(pProp->cType == 'i');
+
+			pModelInfo->uIndexCount += getCountIndexes(pProp->data.pi, pProp->uArrSize);
+
+			aBufferNodes.clearFast();
+			getNodeByName("LayerElementMaterial", &aBufferNodes, pGeomNode);
+			pProp = &aBufferNodes[0]->aProps[0];
+
+			FBXNodeRecord *pLayerMaterial = aBufferNodes[0];
+
+			aBufferNodes.clearFast();
+			getNodeByName("Materials", &aBufferNodes, pLayerMaterial);
+			pProp = &aBufferNodes[0]->aProps[0];
+
+			assert(pProp->cType == 'i');
+
+			uint32_t uSize = pProp->uArrSize;
+
+			for(int j = 0; j < uSize; ++j)
+			{
+				if(aMaterialsIndexes.indexOf(pProp->data.pc[j]) < 0)
+				{
+					aMaterialsIndexes.push_back(pProp->data.pc[j]);
+				}
+			}
+
+			fora(j, aMaterialsIndexes)
+			{
+				FBXNodeRecord *pMaterialNode = getChildNode("Material", aNodes[i], aMaterialsIndexes[j]);
+
+				if(aMaterials.indexOf(pMaterialNode) < 0)
+				{
+					aMaterials.push_back(pMaterialNode);
+				}
+			}
+
+			aMaterialsIndexes.clearFast();
+		}
+	}
+
+	pModelInfo->uSubsetsCount = aMaterials.size();
+
+	pModelInfo->uLodCount = 0;
+	for(int i = 0; i < ARRAYSIZE(aLodExist); ++i)
+	{
+		if(aLodExist[i])
+		{
+			++pModelInfo->uLodCount;
+		}
+	}
+
+	Array<FBXNodeRecord*> pGeomArray;
+	FBXPropertyRecord *pProp = NULL;
+
+	float3 vmin, vmax;
+
+	getNodeByName("Geometry", &pGeomArray, &m_rootNode);
+
+	fora(i, pGeomArray)
+	{
+		aBufferNodes.clearFast();
+
+		getNodeByName("Vertices", &aBufferNodes, pGeomArray[i]);
+		pProp = &aBufferNodes[0]->aProps[0];
+
+		assert(pProp->cType == 'd');
+
+		for(int j = 0; j < pProp->uArrSize; j += 3)
+		{
+			float3 vertex((float)pProp->data.pd[j], (float)pProp->data.pd[j + 1], (float)pProp->data.pd[j + 2]);
+
+			if(j == 0 && i == 0)
+			{
+				vmin = vmax = vertex;
+			}
+			else
+			{
+				vmin = SMVectorMin(vmin, vertex);
+				vmax = SMVectorMax(vmax, vertex);
+			}
+		}
+	}
+
+	pGeomArray.clearFast();
+	getNodeByName("GlobalSettings", &pGeomArray, &m_rootNode);
+
+	FBXNodeRecord *pGlobal = pGeomArray[0];
+
+	pGeomArray.clearFast();
+	getNodeByName("P", &pGeomArray, pGlobal);
+
+	fora(i, pGeomArray)
+	{
+		assert(pGeomArray[i]->aProps[0].cType == 'S');
+
+		if(strcmp(pGeomArray[i]->aProps[0].data.pc, "UnitScaleFactor") == 0)
+		{
+			assert(pGeomArray[i]->aProps[4].cType == 'D');
+			m_fUnitScaleFactor = (float)pGeomArray[i]->aProps[4].data.d;
+			break;
+		}
+	}
+
+	pModelInfo->vDimensions = (vmax - vmin) / 100.0f * m_fUnitScaleFactor;
+}
+
+bool XMETHODCALLTYPE CModelLoader::open(const char *szFileName)
+{
+	assert(!m_pCurrentFile && "File already opened!");
+	if(m_pCurrentFile)
+	{
+		return(false);
+	}
+
+	m_pCurrentFile = m_pFileSystem->openFile(szFileName);
+	if(!m_pCurrentFile)
+	{
+		return(false);
+	}
+
+	m_sFileName = szFileName;
+
+	FBXHeader hdr = {};
+
+	m_pCurrentFile->readBin(&hdr, sizeof(hdr));
+
+	if(strcmp(hdr.szMagic, FBX_MAGIC) != 0)
+	{
+		LogError("Invalid FBX magick\n");
+		mem_release(m_pCurrentFile);
+		return(false);
+	}
+
+	m_isOldVer = false;
+
+	if(hdr.uVersion < FBX_VERSION)
+	{
+		//LogError("Unsupported FBX version %u\n", hdr.uVersion);
+		//mem_release(m_pCurrentFile);
+		//return(false);
+		m_isOldVer = true;
+	}
+
+	FBXNodeRecordHeader recHdr;
+
+	while(!m_pCurrentFile->isEOF())
+	{
+		if(m_isOldVer)
+		{
+			FBXNodeRecordHeaderOld recHdrOld;
+			m_pCurrentFile->readBin(&recHdrOld, sizeof(recHdrOld));
+
+			recHdr.u8NameLen = recHdrOld.u8NameLen;
+			recHdr.uEndOffset = recHdrOld.uEndOffset;
+			recHdr.uNumProperties = recHdrOld.uNumProperties;
+			recHdr.uPropertyListLen = recHdrOld.uPropertyListLen;
+		}
+		else
+		{
+			m_pCurrentFile->readBin(&recHdr, sizeof(recHdr));
+		}
+
+		if(isNullNode(&recHdr))
+		{
+			break;
+		}
+
+		FBXNodeRecord &rec = m_rootNode.aNodes[m_rootNode.aNodes.size()];
+		rec.hdr = recHdr;
+
+		if(!readNode(&rec))
+		{
+			close();
+			return(false);
+		}
+	}
+
+	printNode(&m_rootNode, 0);
+
+	XModelInfo x;
+
+	getInfo(&x);
+
+	return(true);
+}
+
+XMODELTYPE XMETHODCALLTYPE CModelLoader::getType() const
+{
+	return(XMT_STATIC);//return((m_hdr.iFlags & MODEL_FLAG_STATIC) ? XMT_STATIC : XMT_ANIMATED);
+}
+
+bool XMETHODCALLTYPE CModelLoader::loadAsStatic(IXResourceModelStatic *pResource)
+{
+	//if(!loadGeneric(pResource))
+	//{
+	//	return(false);
+	//}
+
+	pResource->setPrimitiveTopology(XPT_TRIANGLELIST);
+
+	Array<FBXNodeRecord*> aNodes;
+	Array<FBXNodeRecord*> aBufferNodes;
+
+	getNodeByName("Model", &aNodes);
+
+	const size_t lodeSize = 10;
+	int32_t aLodIndex[lodeSize] = {};
+
+	Array<Array<FBXNodeRecord*>> aLodModels;
+
+	fora(i, aNodes)
+	{
+		int index = 0;
+		if(aNodes[i]->aProps.size() > 1 && aNodes[i]->aProps[1].cType == 'S')
+		{
+			FBXPropertyRecord &props = aNodes[i]->aProps[1];
+			size_t len = strlen(props.data.pc);
+			// len > strlen(LOD) word
+			// после props.data.pc есть еще одна строка (размер strlen(props.data.pc) и props.uArrSize будет разным)
+			if(len > 5)
+			{
+				if(
+					tolower(props.data.pc[len - 4]) == 'l' &&
+					tolower(props.data.pc[len - 3]) == 'o' &&
+					tolower(props.data.pc[len - 2]) == 'd' &&
+					isdigit(props.data.pc[len - 1]))
+				{
+					index = props.data.pc[len - 1] - '0';
+				}
+			}
+		}
+
+		aLodModels[index].push_back(aNodes[i]);
+	}
+
+	aBufferNodes.clearFast();
+	getNodeByName("Pose", &aBufferNodes, &m_rootNode);
+
+	FBXNodeRecord *pPose = aBufferNodes.size() ? aBufferNodes[0] : NULL;
+
+
+	aBufferNodes.clearFast();
+	getNodeByName("GlobalSettings", &aBufferNodes, &m_rootNode);
+
+	FBXNodeRecord *pGlobal = aBufferNodes[0];
+
+	aBufferNodes.clearFast();
+	getNodeByName("P", &aBufferNodes, pGlobal);
+
+	fora(i, aBufferNodes)
+	{
+		assert(aBufferNodes[i]->aProps[0].cType == 'S');
+
+		if(strcmp(aBufferNodes[i]->aProps[0].data.pc, "UnitScaleFactor") == 0)
+		{
+			assert(aBufferNodes[i]->aProps[4].cType == 'D');
+			m_fUnitScaleFactor = (float)aBufferNodes[i]->aProps[4].data.d;
+			break;
+		}
+	}
+
+	Array<Array<XResourceModelStaticVertex>> aVerteces;
+	Array<Array<uint32_t>> aIndeces;
+
+	Array<UINT> aMaterialMap;
+	Array<FBXNodeRecord*> aMaterials;
+
+	getNodeByName("Material", &aMaterials, &m_rootNode);
+
+	pResource->setMaterialCount(aMaterials.size(), 1);
+	
+	char szDirName[512];
+	char szFileName[256];
+
+	strcpy(szDirName, m_sFileName.c_str());
+	dirname(szDirName);
+	szDirName[strlen(szDirName) - 1] = 0;
+
+	fora(i, aMaterials)
+	{
+		FBXNodeRecord *pTextureNode = getChildNode("Texture", aMaterials[i]);
+
+		if(pTextureNode)
+		{
+			aBufferNodes.clearFast();
+			getNodeByName("FileName", &aBufferNodes, pTextureNode);
+			FBXPropertyRecord *pProp = &aBufferNodes[0]->aProps[0];
+
+			assert(pProp->cType == 'S');
+
+			sprintf(szFileName, "%s_%s", basename(szDirName), basename(pProp->data.pc));
+
+			UINT uIndex = 0;
+			int iLastPos = -1;
+
+			while(szFileName[uIndex++])
+			{
+				if(szFileName[uIndex] == '.')
+				{
+					iLastPos = uIndex;
+				}
+			}
+
+			if(iLastPos > 0)
+			{
+				szFileName[iLastPos] = 0;
+			}
+
+			pResource->setMaterial(i, 0, szFileName);
+		}
+	}
+
+	m_sFileName.release();
+
+	VerticesData vd = {};
+
+	FBXNodeRecord *pMaterialNode;
+	UINT uMaterialCounter = 0;
+
+	fora(i, aLodModels)
+	{
+		aVerteces.clearFast();
+		aIndeces.clearFast();
+
+		fora(j, aLodModels[i])
+		{
+			FBXNodeRecord *pModel = aLodModels[i][j];
+
+			uMaterialCounter = 0;
+			aMaterialMap.clearFast();
+			while((pMaterialNode = getChildNode("Material", pModel, uMaterialCounter++)))
+			{
+				aMaterialMap.push_back(aMaterials.indexOf(pMaterialNode));
+			}
+
+			FBXNodeRecord *pMatrixNode = NULL;
+
+			if(pPose)
+			{
+				aBufferNodes.clearFast();
+				getNodeByName("PoseNode", &aBufferNodes, pPose);
+				
+				fora(k, aBufferNodes)
+				{
+					if(pModel->aProps[0].data.i64 == aBufferNodes[k]->aNodes[0].aProps[0].data.i64)
+					{
+						pMatrixNode = &(aBufferNodes[k]->aNodes[1]);
+					}
+				}
+			}
+
+			vd.pMatrixNode = pMatrixNode;
+			vd.qPreRotation = SMQuaternion();
+
+			aBufferNodes.clearFast();
+			getNodeByName("Properties70", &aBufferNodes, pModel);
+			if(aBufferNodes.size())
+			{
+				FBXNodeRecord *pModelPropsNode = aBufferNodes[0];
+
+				aBufferNodes.clearFast();
+				getNodeByName("P", &aBufferNodes, pModelPropsNode);
+
+				fora(k, aBufferNodes)
+				{
+					if(!strcmp(aBufferNodes[k]->aProps[0].data.pc, "PreRotation"))
+					{
+						float fX = SMToRadian(aBufferNodes[k]->aProps[4].data.d);
+						float fY = SMToRadian(aBufferNodes[k]->aProps[5].data.d);
+						float fZ = SMToRadian(aBufferNodes[k]->aProps[6].data.d);
+						vd.qPreRotation = SMQuaternion(fX, 'x') * SMQuaternion(fY, 'y') * SMQuaternion(fZ, 'z');
+						break;
+					}
+				}
+			}
+
+			FBXNodeRecord *pGeomNode = getChildNode("Geometry", pModel);
+
+			if(!pGeomNode)
+			{
+				continue;
+			}
+
+			aBufferNodes.clearFast();
+			getNodeByName("Vertices", &aBufferNodes, pGeomNode);
+			FBXPropertyRecord *pProp = &aBufferNodes[0]->aProps[0];
+
+			vd.pVerteces = pProp->data.pd;
+
+			aBufferNodes.clearFast();
+			getNodeByName("PolygonVertexIndex", &aBufferNodes, pGeomNode);
+			pProp = &aBufferNodes[0]->aProps[0];
+
+			vd.pIndexes = pProp->data.pi;
+			vd.uSizeIndexes = pProp->uArrSize;
+			
+			aBufferNodes.clearFast();
+			getNodeByName("LayerElementUV", &aBufferNodes, pGeomNode);
+			if(!aBufferNodes.size())
+			{
+				LogError("Model " COLOR_LCYAN "%s" COLOR_LRED " has no texture coords\n", pModel->aProps[1].data.pc);
+				continue;
+			}
+
+			FBXNodeRecord *pLayerElUV = aBufferNodes[0];
+
+			{
+				aBufferNodes.clearFast();
+				getNodeByName("MappingInformationType", &aBufferNodes, pLayerElUV);
+				EnumReflector::Enumerator item = EnumReflector::Get<FBX_MAPPING_INFORMATION_TYPE>().find(aBufferNodes[0]->aProps[0].data.pc);
+				assert(item.isValid());
+				vd.uvMappingInfoType = (FBX_MAPPING_INFORMATION_TYPE)item.getValue();
+			}
+
+			{
+				aBufferNodes.clearFast();
+				getNodeByName("ReferenceInformationType", &aBufferNodes, pLayerElUV);
+				EnumReflector::Enumerator item = EnumReflector::Get<FBX_REFERENCE_INFORMATION_TYPE>().find(aBufferNodes[0]->aProps[0].data.pc);
+				assert(item.isValid());
+				vd.uvRefInfoType = (FBX_REFERENCE_INFORMATION_TYPE)item.getValue();
+			}
+
+			aBufferNodes.clearFast();
+			getNodeByName("UV", &aBufferNodes, pLayerElUV);
+			pProp = &aBufferNodes[0]->aProps[0];
+			assert(pProp->cType == 'd');
+
+			vd.pUV = pProp->data.pd;
+			vd.uSizeUV = pProp->uArrSize;
+
+			aBufferNodes.clearFast();
+			getNodeByName("UVIndex", &aBufferNodes, pLayerElUV);
+
+			vd.pUVIndexes = NULL;
+			vd.uSizeUVIndexes = NULL;
+
+			if(aBufferNodes.size())
+			{
+				pProp = &aBufferNodes[0]->aProps[0];
+				assert(pProp->cType == 'i');
+
+				vd.pUVIndexes = pProp->data.pi;
+				vd.uSizeUVIndexes = pProp->uArrSize;
+			}
+
+			aBufferNodes.clearFast();
+			getNodeByName("LayerElementNormal", &aBufferNodes, pGeomNode);
+			FBXNodeRecord *pLayerNormals = aBufferNodes[0];
+
+			{
+				aBufferNodes.clearFast();
+				getNodeByName("MappingInformationType", &aBufferNodes, pLayerNormals);
+				EnumReflector::Enumerator item = EnumReflector::Get<FBX_MAPPING_INFORMATION_TYPE>().find(aBufferNodes[0]->aProps[0].data.pc);
+				assert(item.isValid());
+				vd.normalMappingInfoType = (FBX_MAPPING_INFORMATION_TYPE)item.getValue();
+			}
+
+			{
+				aBufferNodes.clearFast();
+				getNodeByName("ReferenceInformationType", &aBufferNodes, pLayerNormals);
+				EnumReflector::Enumerator item = EnumReflector::Get<FBX_REFERENCE_INFORMATION_TYPE>().find(aBufferNodes[0]->aProps[0].data.pc);
+				assert(item.isValid());
+				vd.normalRefInfoType = (FBX_REFERENCE_INFORMATION_TYPE)item.getValue();
+			}
+
+			aBufferNodes.clearFast();
+			getNodeByName("Normals", &aBufferNodes, pLayerNormals);
+			pProp = &aBufferNodes[0]->aProps[0];
+			assert(pProp->cType == 'd');
+
+			vd.pNormal = pProp->data.pd;
+
+			aBufferNodes.clearFast();
+			getNodeByName("NormalsIndex", &aBufferNodes, pLayerNormals);
+
+			vd.pNormalIndexes = NULL;
+
+			if(aBufferNodes.size())
+			{
+				pProp = &aBufferNodes[0]->aProps[0];
+				assert(pProp->cType == 'i');
+
+				vd.pNormalIndexes = pProp->data.pi;
+			}
+
+			aBufferNodes.clearFast();
+			getNodeByName("LayerElementMaterial", &aBufferNodes, pGeomNode);
+			FBXNodeRecord *pLayerMaterial = aBufferNodes[0];
+
+			{
+				aBufferNodes.clearFast();
+				getNodeByName("MappingInformationType", &aBufferNodes, pLayerMaterial);
+				EnumReflector::Enumerator item = EnumReflector::Get<FBX_MAPPING_INFORMATION_TYPE>().find(aBufferNodes[0]->aProps[0].data.pc);
+				assert(item.isValid());
+				vd.materialMappingInfoType = (FBX_MAPPING_INFORMATION_TYPE)item.getValue();
+			}
+
+			{
+				aBufferNodes.clearFast();
+				getNodeByName("ReferenceInformationType", &aBufferNodes, pLayerMaterial);
+				EnumReflector::Enumerator item = EnumReflector::Get<FBX_REFERENCE_INFORMATION_TYPE>().find(aBufferNodes[0]->aProps[0].data.pc);
+				assert(item.isValid());
+				vd.materialRefInfoType = (FBX_REFERENCE_INFORMATION_TYPE)item.getValue();
+			}
+
+			aBufferNodes.clearFast();
+			getNodeByName("Materials", &aBufferNodes, pLayerMaterial);
+			pProp = &aBufferNodes[0]->aProps[0];
+			assert(pProp->cType == 'i');
+
+			vd.pMaterials = pProp->data.pi;
+			vd.pMaterialMapArray = &aMaterialMap;
+
+			getVertices(vd, &aVerteces, &aIndeces);
+		}
+
+		if(aVerteces.size())
+		{
+			UINT *s = (UINT*)alloca(aVerteces.size() * sizeof(UINT));
+			UINT *s2 = (UINT*)alloca(aVerteces.size() * sizeof(UINT));
+			UINT uIndex = 0;
+
+			fora(j, aVerteces)
+			{
+				if(aVerteces[j].size() > 0)
+				{
+					s[uIndex] = aVerteces[j].size();
+					s2[uIndex] = aIndeces[j].size();
+					++uIndex;
+				}
+			}
+
+			UINT uLodIndex = pResource->addLod(uIndex, s, s2);
+			uIndex = 0;
+
+			fora(j, aVerteces)
+			{
+				if(aVerteces[j].size() > 0)
+				{
+					auto sub = pResource->getSubset(uLodIndex, uIndex);
+
+					sub->iMaterialID = j;
+					memcpy(sub->pVertices, aVerteces[j], aVerteces[j].size() * sizeof(XResourceModelStaticVertex));
+					memcpy(sub->pIndices, aIndeces[j], aIndeces[j].size() * sizeof(UINT));
+
+					++uIndex;
+				}
+			}
+
+			break;
+		}
+	}
+
+	return(true);
+}
+
+bool XMETHODCALLTYPE CModelLoader::loadAsAnimated(IXResourceModelAnimated *pResource)
+{
+	return(false);
+}
+
+void XMETHODCALLTYPE CModelLoader::close()
+{
+	m_rootNode.aNodes.clearFast();
+	m_rootNode.aProps.clearFast();
+	mem_release(m_pCurrentFile);
+}
+
+bool CModelLoader::loadGeneric(IXResourceModel *pResource)
+{
+	return(false);
+}
+
+bool CModelLoader::readNode(FBXNodeRecord *pNode)
+{
+	assert(pNode);
+
+	m_pCurrentFile->readBin(pNode->szName, pNode->hdr.u8NameLen);
+
+	pNode->szName[pNode->hdr.u8NameLen] = 0;
+
+	pNode->aProps.reserve(pNode->hdr.uNumProperties);
+
+	for(UINT i = 0; i < pNode->hdr.uNumProperties; ++i)
+	{
+		FBXPropertyRecord &prop = pNode->aProps[i];
+		m_pCurrentFile->readBin(&prop.cType, sizeof(prop.cType));
+
+		if(isupper(prop.cType))
+		{
+			switch(prop.cType)
+			{
+			case 'Y':
+				m_pCurrentFile->readBin(&prop.data.i16, sizeof(prop.data.i16));
+				break;
+			case 'C':
+				m_pCurrentFile->readBin(&prop.data.b, sizeof(prop.data.b));
+				break;
+			case 'I':
+				m_pCurrentFile->readBin(&prop.data.i, sizeof(prop.data.i));
+				break;
+			case 'F':
+				m_pCurrentFile->readBin(&prop.data.f, sizeof(prop.data.f));
+				break;
+			case 'D':
+				m_pCurrentFile->readBin(&prop.data.d, sizeof(prop.data.d));
+				break;
+			case 'L':
+				m_pCurrentFile->readBin(&prop.data.i64, sizeof(prop.data.i64));
+				break;
+			case 'S':
+				m_pCurrentFile->readBin(&prop.uArrSize, sizeof(prop.uArrSize));
+				prop.data.pc = new char[prop.uArrSize + 1];
+				m_pCurrentFile->readBin(prop.data.pc, prop.uArrSize);
+				prop.data.pc[prop.uArrSize] = 0;
+				break;
+			case 'R':
+				m_pCurrentFile->readBin(&prop.uArrSize, sizeof(prop.uArrSize));
+				prop.data.pRaw = new byte[prop.uArrSize];
+				m_pCurrentFile->readBin(prop.data.pRaw, prop.uArrSize);
+				break;
+			default:
+				LogError("Unsupported field type value %c\n", prop.cType);
+				return(false);
+			}
+		}
+		else
+		{
+			FBXArrayHeader ahdr = {};
+			m_pCurrentFile->readBin(&ahdr, sizeof(ahdr));
+
+			prop.uArrSize = ahdr.uLength;
+
+			Array<byte> aBytes;
+
+			void *pOut = NULL;
+			size_t size = 0;
+			size_t outSize = 0;
+
+			if(ahdr.uEncoding != FBXCT_UNCOMPRESSED && ahdr.uEncoding != FBXCT_DEFLATE)
+			{
+				LogError("Unsupported compression type %u\n", ahdr.uEncoding);
+				return(false);
+			}
+
+			switch(prop.cType)
+			{
+			case 'f':
+				pOut = prop.data.pf = new float[prop.uArrSize];
+				size = sizeof(*prop.data.pf) * prop.uArrSize;
+				break;
+			case 'd':
+				pOut = prop.data.pd = new double[prop.uArrSize];
+				size = sizeof(*prop.data.pd) * prop.uArrSize;
+				break;
+			case 'l':
+				pOut = prop.data.pi64 = new int64_t[prop.uArrSize];
+				size = sizeof(*prop.data.pi64) * prop.uArrSize;
+				break;
+			case 'i':
+				pOut = prop.data.pi = new int32_t[prop.uArrSize];
+				size = sizeof(*prop.data.pi) * prop.uArrSize;
+				break;
+			case 'b':
+				pOut = prop.data.pb = new uint8_t[prop.uArrSize];
+				size = sizeof(*prop.data.pb) * prop.uArrSize;
+				break;
+			default:
+				LogError("Unsupported field type value %c\n", prop.cType);
+				return(false);
+			}
+
+			if(ahdr.uEncoding == FBXCT_UNCOMPRESSED)
+			{
+				m_pCurrentFile->readBin(pOut, size);
+			}
+
+			if(ahdr.uEncoding == FBXCT_DEFLATE)
+			{
+				aBytes.resizeFast(ahdr.uCompressedLength);
+				m_pCurrentFile->readBin(aBytes, ahdr.uCompressedLength);
+
+				libdeflate_decompressor *pDecompressor = libdeflate_alloc_decompressor();
+				// 2 first bytes is a zlib trash header
+				libdeflate_result res = libdeflate_deflate_decompress(pDecompressor, aBytes + 2, aBytes.size() - 2, pOut, size, &outSize);
+				libdeflate_free_decompressor(pDecompressor);
+
+				if(res != LIBDEFLATE_SUCCESS)
+				{
+					LogError("libdeflate_deflate_decompress(): returned error %d\n", res);
+				}
+			}
+		}
+	}
+
+	FBXNodeRecordHeader hdr;
+
+	while(m_pCurrentFile->getPos() < pNode->hdr.uEndOffset)
+	{
+		if(m_isOldVer)
+		{
+			FBXNodeRecordHeaderOld hdrOld;
+			m_pCurrentFile->readBin(&hdrOld, sizeof(hdrOld));
+
+			hdr.u8NameLen = hdrOld.u8NameLen;
+			hdr.uEndOffset = hdrOld.uEndOffset;
+			hdr.uNumProperties = hdrOld.uNumProperties;
+			hdr.uPropertyListLen = hdrOld.uPropertyListLen;
+		}
+		else
+		{
+			m_pCurrentFile->readBin(&hdr, sizeof(hdr));
+		}
+
+		if(isNullNode(&hdr))
+		{
+			break;
+		}
+
+		FBXNodeRecord &rec = pNode->aNodes[pNode->aNodes.size()];
+		rec.hdr = hdr;
+		if(!readNode(&rec))
+		{
+			return(false);
+		}
+	}
+
+	return(true);
+}
+
+bool CModelLoader::isNullNode(FBXNodeRecordHeader *pHdr)
+{
+	return(pHdr->uPropertyListLen == 0 && pHdr->uNumProperties == 0 && pHdr->uEndOffset == 0 && pHdr->u8NameLen == 0);
+}
+
+void CModelLoader::printNode(FBXNodeRecord *pNode, UINT uLevel)
+{
+	for(UINT i = 0; i < uLevel; ++i)
+	{
+		printf("\t");
+	}
+
+	printf("%s:", pNode->szName);
+
+	fora(i, pNode->aProps)
+	{
+		const FBXPropertyRecord &prop = pNode->aProps[i];
+
+		switch(prop.cType)
+		{
+		case 'f':
+			printf(" array<float>(%u)", prop.uArrSize);
+			break;
+		case 'd':
+			printf(" array<double>(%u)", prop.uArrSize);
+			break;
+		case 'l':
+			printf(" array<int64>(%u)", prop.uArrSize);
+			break;
+		case 'i':
+			printf(" array<int32>(%u)", prop.uArrSize);
+			break;
+		case 'b':
+			printf(" array<uint8>(%u)", prop.uArrSize);
+			break;
+		case 'S':
+			printf(" \"%s\"", prop.data.pc);
+			break;
+		case 'R':
+			printf(" array<byte>(%u)", prop.uArrSize);
+			break;
+		case 'Y':
+			printf(" Y:%hd", prop.data.i16);
+			break;
+		case 'C':
+			printf(" C:%s", (prop.data.b ? "true" : "false"));
+			break;
+		case 'I':
+			printf(" I:%d", prop.data.i);
+			break;
+		case 'F':
+			printf(" F:%f", prop.data.f);
+			break;
+		case 'D':
+			printf(" D:%f", prop.data.d);
+			break;
+		case 'L':
+			printf(" L:%ld", prop.data.i64);
+			break;
+		default:
+			break;
+		}
+	}
+
+	printf("\n");
+
+	fora(i, pNode->aNodes)
+	{
+		printNode(&pNode->aNodes[i], uLevel + 1);
+	}
+}
+
+void CModelLoader::getNodeByName(const char *szName, Array<FBXNodeRecord*> *pArray, FBXNodeRecord *pNode)
+{
+	if(!pNode)
+	{
+		pNode = &m_rootNode;
+	}
+
+	fora(i, pNode->aNodes)
+	{
+		getNodeByName(szName, pArray, &pNode->aNodes[i]);
+	}
+
+	if(!strcmp(pNode->szName, szName))
+	{
+		pArray->push_back(pNode);
+	}
+}
+
+FBXNodeRecord *CModelLoader::getChildNode(const char *szName, const FBXNodeRecord *pNode, UINT uIndex)
+{
+	assert(pNode->aProps[0].cType == 'L');
+
+	int64_t id = pNode->aProps[0].data.i64;
+
+	Array<FBXNodeRecord*> array;
+
+	getNodeByName(szName, &array);
+
+	if(!array.size())
+	{
+		return(NULL);
+	}
+
+	fora(i, m_rootNode.aNodes)
+	{
+		if(!strcmp(m_rootNode.aNodes[i].szName, "Connections"))
+		{
+			fora(j, m_rootNode.aNodes[i].aNodes)
+			{
+				FBXNodeRecord &node = m_rootNode.aNodes[i].aNodes[j];
+
+				assert(node.aProps[2].cType == 'L');
+
+				if(node.aProps[2].data.i64 == id)
+				{
+					fora(k, array)
+					{
+						assert(node.aProps[1].cType == 'L');
+						assert(array[k]->aProps[0].cType == 'L');
+
+						if(array[k]->aProps[0].data.i64 == node.aProps[1].data.i64)
+						{
+							if(uIndex-- == 0)
+							{
+								return(array[k]);
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	return(NULL);
+}
+
+UINT CModelLoader::getCountIndexes(int32_t *pIndexes, uint32_t uSize)
+{
+	UINT uResult = 0;
+	int32_t *pIterator = pIndexes;
+
+	Array<int32_t> array;
+
+	while(pIterator < pIndexes + uSize)
+	{
+		array.push_back(*pIterator);
+
+		if(*pIterator < 0)
+		{
+			array[array.size() - 1] = -(*pIterator) - 1;
+		
+			//assert(array.size() == 3 || array.size() == 4);
+
+			fora(i, array)
+			{
+				if(array[i] == array[(i + 1) % il])
+				{
+					array.erase(i);
+					break;
+				}
+			}
+
+			uResult += (array.size() - 2) * 3;
+
+			array.clearFast();
+		}
+		++pIterator;
+	}
+	return(uResult);
+}
+
+void CModelLoader::getVertices(const VerticesData &verticesData, Array<Array<XResourceModelStaticVertex>> *pOutArray, Array<Array<uint32_t>> *pOutIndices)
+{
+	struct Index
+	{
+		int32_t iValue;
+		UINT uIndex;
+	};
+
+	XResourceModelStaticVertex vtx = {};
+	const int32_t *pIndexes = verticesData.pIndexes;
+	const uint32_t uSize = verticesData.uSizeIndexes;
+	const int32_t *pIterator = pIndexes;
+	const double *pVerteces = verticesData.pVerteces;
+
+	uint32_t uStartVertex = 0;
+
+	Array<Index> array;
+
+	Array<UINT> uUVIndex;
+	Array<UINT> uNormalsIndex;
+	UINT uPolyCounter = 0;
+	UINT uMaterialIndex = 0;
+
+	SMMATRIX matrix;
+
+	if(verticesData.pMatrixNode)
+	{
+		double *m = verticesData.pMatrixNode->aProps[0].data.pd;
+
+		matrix = SMMATRIX(m[0], m[1], m[2], m[3],
+			m[4], m[5], m[6], m[7],
+			m[8], m[9], m[10], m[11],
+			m[12], m[13], m[14], m[15]);
+
+		float fDet = SMMatrixDeterminant(matrix);
+		if(fDet < 0.0f)
+		{
+			matrix.r[0] = -matrix.r[0];
+		}
+	}
+	
+	matrix = matrix * verticesData.qPreRotation.Conjugate().GetMatrix();
+
+	while(pIterator < pIndexes + uSize)
+	{
+		array.push_back({*pIterator, pIterator - pIndexes});
+
+		if(*pIterator < 0)
+		{
+			array[array.size() - 1].iValue = -(*pIterator) - 1;
+
+			fora(i, array)
+			{
+				if(array[i].iValue == array[(i + 1) % il].iValue)
+				{
+					array.erase(i);
+					break;
+				}
+			}
+
+			fora(i, array)
+			{
+				switch(verticesData.uvMappingInfoType)
+				{
+				case FBX_ByPolygonVertex:
+					uUVIndex[i] = array[i].uIndex;
+					break;
+				case FBX_ByVertex:
+					uUVIndex[i] = array[i].iValue;
+					break;
+				default:
+					assert(!"Unsupported maping information type");
+					break;
+				}
+
+				switch(verticesData.uvRefInfoType)
+				{
+				case FBX_Direct:
+					// No need actions
+					break;
+				case FBX_IndexToDirect:
+					uUVIndex[i] = verticesData.pUVIndexes[uUVIndex[i]];
+					break;
+
+				default:
+					assert(!"Unsupported reference information type");
+					break;
+				}
+
+				switch(verticesData.normalMappingInfoType)
+				{
+				case FBX_ByPolygon:
+					uNormalsIndex[i] = uPolyCounter;
+					break;
+				case FBX_ByPolygonVertex:
+					uNormalsIndex[i] = array[i].uIndex;
+					break;
+				case FBX_ByVertex:
+					uNormalsIndex[i] = array[i].iValue;
+					break;
+				default:
+					assert(!"Unsupported maping information type");
+					break;
+				}
+
+				switch(verticesData.normalRefInfoType)
+				{
+				case FBX_Direct:
+					// No need actions
+					break;
+				case FBX_IndexToDirect:
+					uNormalsIndex[i] = verticesData.pNormalIndexes[uNormalsIndex[i]];
+					break;
+
+				default:
+					assert(!"Unsupported reference information type");
+					break;
+				}
+			}
+
+			switch(verticesData.materialMappingInfoType)
+			{
+			case FBX_ByPolygon:
+				uMaterialIndex = uPolyCounter;
+				break;
+			case FBX_AllSame:
+				uMaterialIndex = 0;
+				break;
+			default:
+				assert(!"Unsupported maping information type");
+				break;
+			}
+
+			switch(verticesData.materialRefInfoType)
+			{
+			case FBX_Direct:
+				// No need actions
+				break;
+			case FBX_IndexToDirect:
+				uMaterialIndex = verticesData.pMaterials[uMaterialIndex];
+				break;
+
+			default:
+				assert(!"Unsupported reference information type");
+				break;
+			}
+
+			uMaterialIndex = (*verticesData.pMaterialMapArray)[uMaterialIndex];
+
+			uStartVertex = (*pOutArray)[uMaterialIndex].size();
+			fora(i, array)
+			{
+				vtx.vPos = matrix * float3_t(pVerteces[array[i].iValue * 3], pVerteces[array[i].iValue * 3 + 1], pVerteces[array[i].iValue * 3 + 2]) / 100.0f * m_fUnitScaleFactor;
+				vtx.vTex = float2_t(verticesData.pUV[uUVIndex[i] * 2], 1.0f - verticesData.pUV[uUVIndex[i] * 2 + 1]);
+				vtx.vNorm = float3_t(verticesData.pNormal[uNormalsIndex[i] * 3], verticesData.pNormal[uNormalsIndex[i] * 3 + 1], verticesData.pNormal[uNormalsIndex[i] * 3 + 2]);
+				(*pOutArray)[uMaterialIndex].push_back(vtx);
+			}
+
+			// (0-5) 012 023 034 045 ... 
+			for(UINT i = 1, l = array.size() - 1; i < l; ++i)
+			{
+				(*pOutIndices)[uMaterialIndex].push_back(uStartVertex);
+				(*pOutIndices)[uMaterialIndex].push_back(uStartVertex + i);
+				(*pOutIndices)[uMaterialIndex].push_back(uStartVertex + i + 1);
+			}
+
+			array.clearFast();
+			++uPolyCounter;
+		}
+		++pIterator;
+	}
+}
\ No newline at end of file
diff --git a/source/fbxplugin/ModelLoader.h b/source/fbxplugin/ModelLoader.h
new file mode 100644
index 0000000000000000000000000000000000000000..d8a81986c336b7501608775e923852a87f5ad248
--- /dev/null
+++ b/source/fbxplugin/ModelLoader.h
@@ -0,0 +1,88 @@
+#ifndef __MODELLOADER_H
+#define __MODELLOADER_H
+
+#include <xcommon/IXModelLoader.h>
+#include <xcommon/IFileSystem.h>
+#include "fbx.h"
+
+class CModelLoader final: public IXUnknownImplementation<IXModelLoader>
+{
+public:
+	CModelLoader(IFileSystem *pFileSystem);
+	XIMPLEMENT_VERSION(IXMODELLOADER_VERSION);
+
+	UINT XMETHODCALLTYPE getExtCount() const override;
+	const char* XMETHODCALLTYPE getExt(UINT uIndex) const override;
+	const char* XMETHODCALLTYPE getExtText(UINT uIndex) const override;
+	const char* XMETHODCALLTYPE getAuthor() const override;
+	const char* XMETHODCALLTYPE getCopyright() const override;
+	const char* XMETHODCALLTYPE getDescription() const override;
+
+	bool XMETHODCALLTYPE open(const char *szFileName) override;
+	XMODELTYPE XMETHODCALLTYPE getType() const override;
+	bool XMETHODCALLTYPE loadAsStatic(IXResourceModelStatic *pResource) override;
+	bool XMETHODCALLTYPE loadAsAnimated(IXResourceModelAnimated *pResource) override;
+	void XMETHODCALLTYPE getInfo(XModelInfo *pModelInfo) override;
+	void XMETHODCALLTYPE close() override;
+
+
+	bool loadGeneric(IXResourceModel *pResource);
+
+private:
+	bool readNode(FBXNodeRecord *pNode);
+	bool isNullNode(FBXNodeRecordHeader *pHdr);
+	void printNode(FBXNodeRecord *pNode, UINT uLevel);
+	void getNodeByName(const char *szName, Array<FBXNodeRecord*> *pArray, FBXNodeRecord *pNode = NULL);
+
+	FBXNodeRecord *getChildNode(const char *szName, const FBXNodeRecord *pNode, UINT uIndex = 0);
+	UINT getCountIndexes(int32_t *pIndexes, uint32_t uSize);
+
+	struct VerticesData
+	{
+		int32_t *pIndexes;
+		uint32_t uSizeIndexes;
+
+		double *pVerteces;
+
+		FBX_MAPPING_INFORMATION_TYPE uvMappingInfoType;
+		FBX_REFERENCE_INFORMATION_TYPE uvRefInfoType;
+
+		double *pUV;
+		uint32_t uSizeUV;
+
+		int32_t *pUVIndexes;
+		uint32_t uSizeUVIndexes;
+
+		double *pNormal;
+		int32_t *pNormalIndexes;
+
+		FBX_MAPPING_INFORMATION_TYPE normalMappingInfoType;
+		FBX_REFERENCE_INFORMATION_TYPE normalRefInfoType;
+
+		int32_t *pMaterials;
+
+		FBX_MAPPING_INFORMATION_TYPE materialMappingInfoType;
+		FBX_REFERENCE_INFORMATION_TYPE materialRefInfoType;
+
+		const Array<UINT> *pMaterialMapArray;
+
+		FBXNodeRecord *pMatrixNode;
+
+		SMQuaternion qPreRotation;
+	};
+
+	void getVertices(const VerticesData &verticesData, Array<Array<XResourceModelStaticVertex>> *pOutArray, Array<Array<uint32_t>> *pOutIndices);
+
+	IFileSystem *m_pFileSystem;
+	IFile *m_pCurrentFile = NULL;
+
+	float m_fUnitScaleFactor = 1.0f;
+
+	String m_sFileName;
+
+	bool m_isOldVer = false;
+
+	FBXNodeRecord m_rootNode;
+};
+
+#endif
diff --git a/source/fbxplugin/dllmain.cpp b/source/fbxplugin/dllmain.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..955d268b01c7c34e230e19cda81db4babeee39ba
--- /dev/null
+++ b/source/fbxplugin/dllmain.cpp
@@ -0,0 +1,24 @@
+
+/***********************************************************
+Copyright © Ivan Dokunov, Evgeny Danilovich, 2025
+See the license in LICENSE
+***********************************************************/
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+BOOL APIENTRY DllMain(HMODULE hModule,
+	DWORD  ul_reason_for_call,
+	LPVOID lpReserved
+	)
+{
+	switch(ul_reason_for_call)
+	{
+	case DLL_PROCESS_ATTACH:
+	case DLL_THREAD_ATTACH:
+	case DLL_THREAD_DETACH:
+	case DLL_PROCESS_DETACH:
+		break;
+	}
+	return TRUE;
+}
diff --git a/source/fbxplugin/fbx.h b/source/fbxplugin/fbx.h
new file mode 100644
index 0000000000000000000000000000000000000000..cadf5911665560dc6408ac38cb6f46d3f4afb2df
--- /dev/null
+++ b/source/fbxplugin/fbx.h
@@ -0,0 +1,126 @@
+#ifndef __FBX_H
+#define __FBX_H
+
+#include <gdefines.h>
+
+#define FBX_MAGIC "Kaydara FBX Binary  "
+#define FBX_VERSION 7500
+
+enum FBX_COMPRESSION_TYPE
+{
+	FBXCT_UNCOMPRESSED = 0,
+	FBXCT_DEFLATE = 1
+};
+
+XENUM(FBX_MAPPING_INFORMATION_TYPE,
+	FBX_ByPolygon,
+	FBX_ByPolygonVertex,
+	FBX_ByVertex,
+	FBX_ByVertice = FBX_ByVertex,
+	FBX_ByEdge,
+	FBX_AllSame
+);
+
+XENUM(FBX_REFERENCE_INFORMATION_TYPE,
+	FBX_Direct,
+	FBX_IndexToDirect,
+	FBX_Index = FBX_IndexToDirect
+);
+
+#pragma pack(push, 1)
+
+struct FBXHeader
+{
+	char szMagic[21];
+	uint16_t _unknown;
+	uint32_t uVersion;
+};
+
+struct FBXNodeRecordHeaderOld
+{
+	uint32_t uEndOffset;
+	uint32_t uNumProperties;
+	uint32_t uPropertyListLen;
+	uint8_t u8NameLen;
+};
+
+struct FBXNodeRecordHeader
+{
+	uint64_t uEndOffset;
+	uint64_t uNumProperties;
+	uint64_t uPropertyListLen;
+	uint8_t u8NameLen;
+};
+
+struct FBXPropertyRecord
+{
+	char cType;
+	union 
+	{
+		int16_t i16;
+		uint8_t b;
+		int32_t i;
+		float f;
+		double d;
+		int64_t i64;
+
+		float *pf;
+		double *pd;
+		int64_t *pi64;
+		int32_t *pi;
+		uint8_t *pb;
+
+		char *pc;
+		byte *pRaw;
+	} data;
+	uint32_t uArrSize;
+
+	~FBXPropertyRecord()
+	{
+		switch(cType)
+		{
+		case 'f':
+			mem_delete_a(data.pf);
+			break;
+		case 'd':
+			mem_delete_a(data.pd);
+			break;
+		case 'l':
+			mem_delete_a(data.pi64);
+			break;
+		case 'i':
+			mem_delete_a(data.pi);
+			break;
+		case 'b':
+			mem_delete_a(data.pb);
+			break;
+		case 'S':
+			mem_delete_a(data.pc);
+			break;
+		case 'R':
+			mem_delete_a(data.pRaw);
+			break;
+		default:
+			break;
+		}
+	}
+};
+
+struct FBXArrayHeader
+{
+	uint32_t uLength;
+	uint32_t uEncoding;
+	uint32_t uCompressedLength;
+};
+
+struct FBXNodeRecord
+{
+	FBXNodeRecordHeader hdr;
+	char szName[256];
+	Array<FBXPropertyRecord> aProps;
+	Array<FBXNodeRecord> aNodes;
+};
+
+#pragma pack(pop)
+
+#endif
diff --git a/source/fbxplugin/libdeflate.c b/source/fbxplugin/libdeflate.c
new file mode 100644
index 0000000000000000000000000000000000000000..e421d7911345c57cee506a375d2950a1d552643c
--- /dev/null
+++ b/source/fbxplugin/libdeflate.c
@@ -0,0 +1,4193 @@
+// ofbx changes : removed unused code, single .h and .c
+/*
+ * Copyright 2016 Eric Biggers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ---------------------------------------------------------------------------
+ *
+ * This is a highly optimized DEFLATE decompressor.  It is much faster than
+ * vanilla zlib, typically well over twice as fast, though results vary by CPU.
+ *
+ * Why this is faster than vanilla zlib:
+ *
+ * - Word accesses rather than byte accesses when reading input
+ * - Word accesses rather than byte accesses when copying matches
+ * - Faster Huffman decoding combined with various DEFLATE-specific tricks
+ * - Larger bitbuffer variable that doesn't need to be refilled as often
+ * - Other optimizations to remove unnecessary branches
+ * - Only full-buffer decompression is supported, so the code doesn't need to
+ *   support stopping and resuming decompression.
+ * - On x86_64, a version of the decompression routine is compiled with BMI2
+ *   instructions enabled and is used automatically at runtime when supported.
+ */
+
+/*
+ * lib_common.h - internal header included by all library code
+ */
+
+#ifndef LIB_LIB_COMMON_H
+#define LIB_LIB_COMMON_H
+
+#ifdef LIBDEFLATE_H
+ /*
+  * When building the library, LIBDEFLATEAPI needs to be defined properly before
+  * including libdeflate.h.
+  */
+#  error "lib_common.h must always be included before libdeflate.h"
+#endif
+
+#if defined(LIBDEFLATE_DLL) && (defined(_WIN32) || defined(__CYGWIN__))
+#  define LIBDEFLATE_EXPORT_SYM  __declspec(dllexport)
+#elif defined(__GNUC__)
+#  define LIBDEFLATE_EXPORT_SYM  __attribute__((visibility("default")))
+#else
+#  define LIBDEFLATE_EXPORT_SYM
+#endif
+
+/*
+ * On i386, gcc assumes that the stack is 16-byte aligned at function entry.
+ * However, some compilers (e.g. MSVC) and programming languages (e.g. Delphi)
+ * only guarantee 4-byte alignment when calling functions.  This is mainly an
+ * issue on Windows, but it has been seen on Linux too.  Work around this ABI
+ * incompatibility by realigning the stack pointer when entering libdeflate.
+ * This prevents crashes in SSE/AVX code.
+ */
+#if defined(__GNUC__) && defined(__i386__)
+#  define LIBDEFLATE_ALIGN_STACK  __attribute__((force_align_arg_pointer))
+#else
+#  define LIBDEFLATE_ALIGN_STACK
+#endif
+
+#define LIBDEFLATEAPI	LIBDEFLATE_EXPORT_SYM LIBDEFLATE_ALIGN_STACK
+
+/*
+ * common_defs.h
+ *
+ * Copyright 2016 Eric Biggers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef COMMON_DEFS_H
+#define COMMON_DEFS_H
+
+#include "libdeflate.h"
+
+#include <stdbool.h>
+#include <stddef.h>	/* for size_t */
+#include <stdint.h>
+#ifdef _MSC_VER
+#  include <intrin.h>	/* for _BitScan*() and other intrinsics */
+#  include <stdlib.h>	/* for _byteswap_*() */
+   /* Disable MSVC warnings that are expected. */
+   /* /W2 */
+#  pragma warning(disable : 4146) /* unary minus on unsigned type */
+   /* /W3 */
+#  pragma warning(disable : 4018) /* signed/unsigned mismatch */
+#  pragma warning(disable : 4244) /* possible loss of data */
+#  pragma warning(disable : 4267) /* possible loss of precision */
+#  pragma warning(disable : 4310) /* cast truncates constant value */
+   /* /W4 */
+#  pragma warning(disable : 4100) /* unreferenced formal parameter */
+#  pragma warning(disable : 4127) /* conditional expression is constant */
+#  pragma warning(disable : 4189) /* local variable initialized but not referenced */
+#  pragma warning(disable : 4232) /* nonstandard extension used */
+#  pragma warning(disable : 4245) /* conversion from 'int' to 'unsigned int' */
+#  pragma warning(disable : 4295) /* array too small to include terminating null */
+#endif
+#ifndef FREESTANDING
+#  include <string.h>	/* for memcpy() */
+#endif
+
+/* ========================================================================== */
+/*                             Target architecture                            */
+/* ========================================================================== */
+
+/* If possible, define a compiler-independent ARCH_* macro. */
+#undef ARCH_X86_64
+#undef ARCH_X86_32
+#undef ARCH_ARM64
+#undef ARCH_ARM32
+#ifdef _MSC_VER
+#  if defined(_M_X64)
+#    define ARCH_X86_64
+#  elif defined(_M_IX86)
+#    define ARCH_X86_32
+#  elif defined(_M_ARM64)
+#    define ARCH_ARM64
+#  elif defined(_M_ARM)
+#    define ARCH_ARM32
+#  endif
+#else
+#  if defined(__x86_64__)
+#    define ARCH_X86_64
+#  elif defined(__i386__)
+#    define ARCH_X86_32
+#  elif defined(__aarch64__)
+#    define ARCH_ARM64
+#  elif defined(__arm__)
+#    define ARCH_ARM32
+#  endif
+#endif
+
+/* ========================================================================== */
+/*                              Type definitions                              */
+/* ========================================================================== */
+
+/* Fixed-width integer types */
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int32_t s32;
+typedef int64_t s64;
+
+/* ssize_t, if not available in <sys/types.h> */
+#ifdef _MSC_VER
+#  ifdef _WIN64
+     typedef long long ssize_t;
+#  else
+     typedef long ssize_t;
+#  endif
+#endif
+
+/*
+ * Word type of the target architecture.  Use 'size_t' instead of
+ * 'unsigned long' to account for platforms such as Windows that use 32-bit
+ * 'unsigned long' on 64-bit architectures.
+ */
+typedef size_t machine_word_t;
+
+/* Number of bytes in a word */
+#define WORDBYTES	((int)sizeof(machine_word_t))
+
+/* Number of bits in a word */
+#define WORDBITS	(8 * WORDBYTES)
+
+/* ========================================================================== */
+/*                         Optional compiler features                         */
+/* ========================================================================== */
+
+/* Compiler version checks.  Only use when absolutely necessary. */
+#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
+#  define GCC_PREREQ(major, minor)		\
+	(__GNUC__ > (major) ||			\
+	 (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor)))
+#else
+#  define GCC_PREREQ(major, minor)	0
+#endif
+#ifdef __clang__
+#  ifdef __apple_build_version__
+#    define CLANG_PREREQ(major, minor, apple_version)	\
+	(__apple_build_version__ >= (apple_version))
+#  else
+#    define CLANG_PREREQ(major, minor, apple_version)	\
+	(__clang_major__ > (major) ||			\
+	 (__clang_major__ == (major) && __clang_minor__ >= (minor)))
+#  endif
+#else
+#  define CLANG_PREREQ(major, minor, apple_version)	0
+#endif
+
+/*
+ * Macros to check for compiler support for attributes and builtins.  clang
+ * implements these macros, but gcc doesn't, so generally any use of one of
+ * these macros must also be combined with a gcc version check.
+ */
+#ifndef __has_attribute
+#  define __has_attribute(attribute)	0
+#endif
+#ifndef __has_builtin
+#  define __has_builtin(builtin)	0
+#endif
+
+/* inline - suggest that a function be inlined */
+#ifdef _MSC_VER
+#  define inline		__inline
+#endif /* else assume 'inline' is usable as-is */
+
+/* forceinline - force a function to be inlined, if possible */
+#if defined(__GNUC__) || __has_attribute(always_inline)
+#  define forceinline		inline __attribute__((always_inline))
+#elif defined(_MSC_VER)
+#  define forceinline		__forceinline
+#else
+#  define forceinline		inline
+#endif
+
+/* MAYBE_UNUSED - mark a function or variable as maybe unused */
+#if defined(__GNUC__) || __has_attribute(unused)
+#  define MAYBE_UNUSED		__attribute__((unused))
+#else
+#  define MAYBE_UNUSED
+#endif
+
+/*
+ * restrict - hint that writes only occur through the given pointer.
+ *
+ * Don't use MSVC's __restrict, since it has nonstandard behavior.
+ * Standard restrict is okay, if it is supported.
+ */
+#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 201112L)
+#  if defined(__GNUC__) || defined(__clang__)
+#    define restrict		__restrict__
+#  else
+#    define restrict
+#  endif
+#endif /* else assume 'restrict' is usable as-is */
+
+/* likely(expr) - hint that an expression is usually true */
+#if defined(__GNUC__) || __has_builtin(__builtin_expect)
+#  define likely(expr)		__builtin_expect(!!(expr), 1)
+#else
+#  define likely(expr)		(expr)
+#endif
+
+/* unlikely(expr) - hint that an expression is usually false */
+#if defined(__GNUC__) || __has_builtin(__builtin_expect)
+#  define unlikely(expr)	__builtin_expect(!!(expr), 0)
+#else
+#  define unlikely(expr)	(expr)
+#endif
+
+/* prefetchr(addr) - prefetch into L1 cache for read */
+#undef prefetchr
+#if defined(__GNUC__) || __has_builtin(__builtin_prefetch)
+#  define prefetchr(addr)	__builtin_prefetch((addr), 0)
+#elif defined(_MSC_VER)
+#  if defined(ARCH_X86_32) || defined(ARCH_X86_64)
+#    define prefetchr(addr)	_mm_prefetch((addr), _MM_HINT_T0)
+#  elif defined(ARCH_ARM64)
+#    define prefetchr(addr)	__prefetch2((addr), 0x00 /* prfop=PLDL1KEEP */)
+#  elif defined(ARCH_ARM32)
+#    define prefetchr(addr)	__prefetch(addr)
+#  endif
+#endif
+#ifndef prefetchr
+#  define prefetchr(addr)
+#endif
+
+/* prefetchw(addr) - prefetch into L1 cache for write */
+#undef prefetchw
+#if defined(__GNUC__) || __has_builtin(__builtin_prefetch)
+#  define prefetchw(addr)	__builtin_prefetch((addr), 1)
+#elif defined(_MSC_VER)
+#  if defined(ARCH_X86_32) || defined(ARCH_X86_64)
+#    define prefetchw(addr)	_m_prefetchw(addr)
+#  elif defined(ARCH_ARM64)
+#    define prefetchw(addr)	__prefetch2((addr), 0x10 /* prfop=PSTL1KEEP */)
+#  elif defined(ARCH_ARM32)
+#    define prefetchw(addr)	__prefetchw(addr)
+#  endif
+#endif
+#ifndef prefetchw
+#  define prefetchw(addr)
+#endif
+
+/*
+ * _aligned_attribute(n) - declare that the annotated variable, or variables of
+ * the annotated type, must be aligned on n-byte boundaries.
+ */
+#undef _aligned_attribute
+#if defined(__GNUC__) || __has_attribute(aligned)
+#  define _aligned_attribute(n)	__attribute__((aligned(n)))
+#elif defined(_MSC_VER)
+#  define _aligned_attribute(n)	__declspec(align(n))
+#endif
+
+/*
+ * _target_attribute(attrs) - override the compilation target for a function.
+ *
+ * This accepts one or more comma-separated suffixes to the -m prefix jointly
+ * forming the name of a machine-dependent option.  On gcc-like compilers, this
+ * enables codegen for the given targets, including arbitrary compiler-generated
+ * code as well as the corresponding intrinsics.  On other compilers this macro
+ * expands to nothing, though MSVC allows intrinsics to be used anywhere anyway.
+ */
+#if GCC_PREREQ(4, 4) || __has_attribute(target)
+#  define _target_attribute(attrs)	__attribute__((target(attrs)))
+#  define COMPILER_SUPPORTS_TARGET_FUNCTION_ATTRIBUTE	1
+#else
+#  define _target_attribute(attrs)
+#  define COMPILER_SUPPORTS_TARGET_FUNCTION_ATTRIBUTE	0
+#endif
+
+/* ========================================================================== */
+/*                          Miscellaneous macros                              */
+/* ========================================================================== */
+
+#define ARRAY_LEN(A)		(sizeof(A) / sizeof((A)[0]))
+#define MIN(a, b)		((a) <= (b) ? (a) : (b))
+#define MAX(a, b)		((a) >= (b) ? (a) : (b))
+#define DIV_ROUND_UP(n, d)	(((n) + (d) - 1) / (d))
+#define STATIC_ASSERT(expr)	((void)sizeof(char[1 - 2 * !(expr)]))
+#define ALIGN(n, a)		(((n) + (a) - 1) & ~((a) - 1))
+#define ROUND_UP(n, d)		((d) * DIV_ROUND_UP((n), (d)))
+
+/* ========================================================================== */
+/*                           Endianness handling                              */
+/* ========================================================================== */
+
+/*
+ * CPU_IS_LITTLE_ENDIAN() - 1 if the CPU is little endian, or 0 if it is big
+ * endian.  When possible this is a compile-time macro that can be used in
+ * preprocessor conditionals.  As a fallback, a generic method is used that
+ * can't be used in preprocessor conditionals but should still be optimized out.
+ */
+#if defined(__BYTE_ORDER__) /* gcc v4.6+ and clang */
+#  define CPU_IS_LITTLE_ENDIAN()  (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#elif defined(_MSC_VER)
+#  define CPU_IS_LITTLE_ENDIAN()  true
+#else
+static forceinline bool CPU_IS_LITTLE_ENDIAN(void)
+{
+	union {
+		u32 w;
+		u8 b;
+	} u;
+
+	u.w = 1;
+	return u.b;
+}
+#endif
+
+/* bswap16(v) - swap the bytes of a 16-bit integer */
+static forceinline u16 bswap16(u16 v)
+{
+#if GCC_PREREQ(4, 8) || __has_builtin(__builtin_bswap16)
+	return __builtin_bswap16(v);
+#elif defined(_MSC_VER)
+	return _byteswap_ushort(v);
+#else
+	return (v << 8) | (v >> 8);
+#endif
+}
+
+/* bswap32(v) - swap the bytes of a 32-bit integer */
+static forceinline u32 bswap32(u32 v)
+{
+#if GCC_PREREQ(4, 3) || __has_builtin(__builtin_bswap32)
+	return __builtin_bswap32(v);
+#elif defined(_MSC_VER)
+	return _byteswap_ulong(v);
+#else
+	return ((v & 0x000000FF) << 24) |
+	       ((v & 0x0000FF00) << 8) |
+	       ((v & 0x00FF0000) >> 8) |
+	       ((v & 0xFF000000) >> 24);
+#endif
+}
+
+/* bswap64(v) - swap the bytes of a 64-bit integer */
+static forceinline u64 bswap64(u64 v)
+{
+#if GCC_PREREQ(4, 3) || __has_builtin(__builtin_bswap64)
+	return __builtin_bswap64(v);
+#elif defined(_MSC_VER)
+	return _byteswap_uint64(v);
+#else
+	return ((v & 0x00000000000000FF) << 56) |
+	       ((v & 0x000000000000FF00) << 40) |
+	       ((v & 0x0000000000FF0000) << 24) |
+	       ((v & 0x00000000FF000000) << 8) |
+	       ((v & 0x000000FF00000000) >> 8) |
+	       ((v & 0x0000FF0000000000) >> 24) |
+	       ((v & 0x00FF000000000000) >> 40) |
+	       ((v & 0xFF00000000000000) >> 56);
+#endif
+}
+
+#define le16_bswap(v) (CPU_IS_LITTLE_ENDIAN() ? (v) : bswap16(v))
+#define le32_bswap(v) (CPU_IS_LITTLE_ENDIAN() ? (v) : bswap32(v))
+#define le64_bswap(v) (CPU_IS_LITTLE_ENDIAN() ? (v) : bswap64(v))
+#define be16_bswap(v) (CPU_IS_LITTLE_ENDIAN() ? bswap16(v) : (v))
+#define be32_bswap(v) (CPU_IS_LITTLE_ENDIAN() ? bswap32(v) : (v))
+#define be64_bswap(v) (CPU_IS_LITTLE_ENDIAN() ? bswap64(v) : (v))
+
+/* ========================================================================== */
+/*                          Unaligned memory accesses                         */
+/* ========================================================================== */
+
+/*
+ * UNALIGNED_ACCESS_IS_FAST() - 1 if unaligned memory accesses can be performed
+ * efficiently on the target platform, otherwise 0.
+ */
+#if (defined(__GNUC__) || defined(__clang__)) && \
+	(defined(ARCH_X86_64) || defined(ARCH_X86_32) || \
+	 defined(__ARM_FEATURE_UNALIGNED) || defined(__powerpc64__) || \
+	 /*
+	  * For all compilation purposes, WebAssembly behaves like any other CPU
+	  * instruction set. Even though WebAssembly engine might be running on
+	  * top of different actual CPU architectures, the WebAssembly spec
+	  * itself permits unaligned access and it will be fast on most of those
+	  * platforms, and simulated at the engine level on others, so it's
+	  * worth treating it as a CPU architecture with fast unaligned access.
+	  */ defined(__wasm__))
+#  define UNALIGNED_ACCESS_IS_FAST	1
+#elif defined(_MSC_VER)
+#  define UNALIGNED_ACCESS_IS_FAST	1
+#else
+#  define UNALIGNED_ACCESS_IS_FAST	0
+#endif
+
+/*
+ * Implementing unaligned memory accesses using memcpy() is portable, and it
+ * usually gets optimized appropriately by modern compilers.  I.e., each
+ * memcpy() of 1, 2, 4, or WORDBYTES bytes gets compiled to a load or store
+ * instruction, not to an actual function call.
+ *
+ * We no longer use the "packed struct" approach to unaligned accesses, as that
+ * is nonstandard, has unclear semantics, and doesn't receive enough testing
+ * (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94994).
+ *
+ * arm32 with __ARM_FEATURE_UNALIGNED in gcc 5 and earlier is a known exception
+ * where memcpy() generates inefficient code
+ * (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67366).  However, we no longer
+ * consider that one case important enough to maintain different code for.
+ * If you run into it, please just use a newer version of gcc (or use clang).
+ */
+
+#ifdef FREESTANDING
+#  define MEMCOPY	__builtin_memcpy
+#else
+#  define MEMCOPY	memcpy
+#endif
+
+/* Unaligned loads and stores without endianness conversion */
+
+#define DEFINE_UNALIGNED_TYPE(type)				\
+static forceinline type						\
+load_##type##_unaligned(const void *p)				\
+{								\
+	type v;							\
+								\
+	MEMCOPY(&v, p, sizeof(v));				\
+	return v;						\
+}								\
+								\
+static forceinline void						\
+store_##type##_unaligned(type v, void *p)			\
+{								\
+	MEMCOPY(p, &v, sizeof(v));				\
+}
+
+DEFINE_UNALIGNED_TYPE(u16)
+DEFINE_UNALIGNED_TYPE(u32)
+DEFINE_UNALIGNED_TYPE(u64)
+DEFINE_UNALIGNED_TYPE(machine_word_t)
+
+#undef MEMCOPY
+
+#define load_word_unaligned	load_machine_word_t_unaligned
+#define store_word_unaligned	store_machine_word_t_unaligned
+
+/* Unaligned loads with endianness conversion */
+
+static forceinline u16
+get_unaligned_le16(const u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST)
+		return le16_bswap(load_u16_unaligned(p));
+	else
+		return ((u16)p[1] << 8) | p[0];
+}
+
+static forceinline u16
+get_unaligned_be16(const u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST)
+		return be16_bswap(load_u16_unaligned(p));
+	else
+		return ((u16)p[0] << 8) | p[1];
+}
+
+static forceinline u32
+get_unaligned_le32(const u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST)
+		return le32_bswap(load_u32_unaligned(p));
+	else
+		return ((u32)p[3] << 24) | ((u32)p[2] << 16) |
+			((u32)p[1] << 8) | p[0];
+}
+
+static forceinline u32
+get_unaligned_be32(const u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST)
+		return be32_bswap(load_u32_unaligned(p));
+	else
+		return ((u32)p[0] << 24) | ((u32)p[1] << 16) |
+			((u32)p[2] << 8) | p[3];
+}
+
+static forceinline u64
+get_unaligned_le64(const u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST)
+		return le64_bswap(load_u64_unaligned(p));
+	else
+		return ((u64)p[7] << 56) | ((u64)p[6] << 48) |
+			((u64)p[5] << 40) | ((u64)p[4] << 32) |
+			((u64)p[3] << 24) | ((u64)p[2] << 16) |
+			((u64)p[1] << 8) | p[0];
+}
+
+static forceinline machine_word_t
+get_unaligned_leword(const u8 *p)
+{
+	STATIC_ASSERT(WORDBITS == 32 || WORDBITS == 64);
+	if (WORDBITS == 32)
+		return get_unaligned_le32(p);
+	else
+		return get_unaligned_le64(p);
+}
+
+/* Unaligned stores with endianness conversion */
+
+static forceinline void
+put_unaligned_le16(u16 v, u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST) {
+		store_u16_unaligned(le16_bswap(v), p);
+	} else {
+		p[0] = (u8)(v >> 0);
+		p[1] = (u8)(v >> 8);
+	}
+}
+
+static forceinline void
+put_unaligned_be16(u16 v, u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST) {
+		store_u16_unaligned(be16_bswap(v), p);
+	} else {
+		p[0] = (u8)(v >> 8);
+		p[1] = (u8)(v >> 0);
+	}
+}
+
+static forceinline void
+put_unaligned_le32(u32 v, u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST) {
+		store_u32_unaligned(le32_bswap(v), p);
+	} else {
+		p[0] = (u8)(v >> 0);
+		p[1] = (u8)(v >> 8);
+		p[2] = (u8)(v >> 16);
+		p[3] = (u8)(v >> 24);
+	}
+}
+
+static forceinline void
+put_unaligned_be32(u32 v, u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST) {
+		store_u32_unaligned(be32_bswap(v), p);
+	} else {
+		p[0] = (u8)(v >> 24);
+		p[1] = (u8)(v >> 16);
+		p[2] = (u8)(v >> 8);
+		p[3] = (u8)(v >> 0);
+	}
+}
+
+static forceinline void
+put_unaligned_le64(u64 v, u8 *p)
+{
+	if (UNALIGNED_ACCESS_IS_FAST) {
+		store_u64_unaligned(le64_bswap(v), p);
+	} else {
+		p[0] = (u8)(v >> 0);
+		p[1] = (u8)(v >> 8);
+		p[2] = (u8)(v >> 16);
+		p[3] = (u8)(v >> 24);
+		p[4] = (u8)(v >> 32);
+		p[5] = (u8)(v >> 40);
+		p[6] = (u8)(v >> 48);
+		p[7] = (u8)(v >> 56);
+	}
+}
+
+static forceinline void
+put_unaligned_leword(machine_word_t v, u8 *p)
+{
+	STATIC_ASSERT(WORDBITS == 32 || WORDBITS == 64);
+	if (WORDBITS == 32)
+		put_unaligned_le32(v, p);
+	else
+		put_unaligned_le64(v, p);
+}
+
+/* ========================================================================== */
+/*                         Bit manipulation functions                         */
+/* ========================================================================== */
+
+/*
+ * Bit Scan Reverse (BSR) - find the 0-based index (relative to the least
+ * significant end) of the *most* significant 1 bit in the input value.  The
+ * input value must be nonzero!
+ */
+
+static forceinline unsigned
+bsr32(u32 v)
+{
+#if defined(__GNUC__) || __has_builtin(__builtin_clz)
+	return 31 - __builtin_clz(v);
+#elif defined(_MSC_VER)
+	unsigned long i;
+
+	_BitScanReverse(&i, v);
+	return i;
+#else
+	unsigned i = 0;
+
+	while ((v >>= 1) != 0)
+		i++;
+	return i;
+#endif
+}
+
+static forceinline unsigned
+bsr64(u64 v)
+{
+#if defined(__GNUC__) || __has_builtin(__builtin_clzll)
+	return 63 - __builtin_clzll(v);
+#elif defined(_MSC_VER) && defined(_WIN64)
+	unsigned long i;
+
+	_BitScanReverse64(&i, v);
+	return i;
+#else
+	unsigned i = 0;
+
+	while ((v >>= 1) != 0)
+		i++;
+	return i;
+#endif
+}
+
+static forceinline unsigned
+bsrw(machine_word_t v)
+{
+	STATIC_ASSERT(WORDBITS == 32 || WORDBITS == 64);
+	if (WORDBITS == 32)
+		return bsr32(v);
+	else
+		return bsr64(v);
+}
+
+/*
+ * Bit Scan Forward (BSF) - find the 0-based index (relative to the least
+ * significant end) of the *least* significant 1 bit in the input value.  The
+ * input value must be nonzero!
+ */
+
+static forceinline unsigned
+bsf32(u32 v)
+{
+#if defined(__GNUC__) || __has_builtin(__builtin_ctz)
+	return __builtin_ctz(v);
+#elif defined(_MSC_VER)
+	unsigned long i;
+
+	_BitScanForward(&i, v);
+	return i;
+#else
+	unsigned i = 0;
+
+	for (; (v & 1) == 0; v >>= 1)
+		i++;
+	return i;
+#endif
+}
+
+static forceinline unsigned
+bsf64(u64 v)
+{
+#if defined(__GNUC__) || __has_builtin(__builtin_ctzll)
+	return __builtin_ctzll(v);
+#elif defined(_MSC_VER) && defined(_WIN64)
+	unsigned long i;
+
+	_BitScanForward64(&i, v);
+	return i;
+#else
+	unsigned i = 0;
+
+	for (; (v & 1) == 0; v >>= 1)
+		i++;
+	return i;
+#endif
+}
+
+static forceinline unsigned
+bsfw(machine_word_t v)
+{
+	STATIC_ASSERT(WORDBITS == 32 || WORDBITS == 64);
+	if (WORDBITS == 32)
+		return bsf32(v);
+	else
+		return bsf64(v);
+}
+
+/*
+ * rbit32(v): reverse the bits in a 32-bit integer.  This doesn't have a
+ * fallback implementation; use '#ifdef rbit32' to check if this is available.
+ */
+#undef rbit32
+#if (defined(__GNUC__) || defined(__clang__)) && defined(ARCH_ARM32) && \
+	(__ARM_ARCH >= 7 || (__ARM_ARCH == 6 && defined(__ARM_ARCH_6T2__)))
+static forceinline u32
+rbit32(u32 v)
+{
+	__asm__("rbit %0, %1" : "=r" (v) : "r" (v));
+	return v;
+}
+#define rbit32 rbit32
+#elif (defined(__GNUC__) || defined(__clang__)) && defined(ARCH_ARM64)
+static forceinline u32
+rbit32(u32 v)
+{
+	__asm__("rbit %w0, %w1" : "=r" (v) : "r" (v));
+	return v;
+}
+#define rbit32 rbit32
+#endif
+
+#endif /* COMMON_DEFS_H */
+
+
+typedef void *(*malloc_func_t)(size_t);
+typedef void (*free_func_t)(void *);
+
+extern malloc_func_t libdeflate_default_malloc_func;
+extern free_func_t libdeflate_default_free_func;
+
+void *libdeflate_aligned_malloc(malloc_func_t malloc_func,
+				size_t alignment, size_t size);
+void libdeflate_aligned_free(free_func_t free_func, void *ptr);
+
+#ifdef FREESTANDING
+/*
+ * With -ffreestanding, <string.h> may be missing, and we must provide
+ * implementations of memset(), memcpy(), memmove(), and memcmp().
+ * See https://gcc.gnu.org/onlinedocs/gcc/Standards.html
+ *
+ * Also, -ffreestanding disables interpreting calls to these functions as
+ * built-ins.  E.g., calling memcpy(&v, p, WORDBYTES) will make a function call,
+ * not be optimized to a single load instruction.  For performance reasons we
+ * don't want that.  So, declare these functions as macros that expand to the
+ * corresponding built-ins.  This approach is recommended in the gcc man page.
+ * We still need the actual function definitions in case gcc calls them.
+ */
+void *memset(void *s, int c, size_t n);
+#define memset(s, c, n)		__builtin_memset((s), (c), (n))
+
+void *memcpy(void *dest, const void *src, size_t n);
+#define memcpy(dest, src, n)	__builtin_memcpy((dest), (src), (n))
+
+void *memmove(void *dest, const void *src, size_t n);
+#define memmove(dest, src, n)	__builtin_memmove((dest), (src), (n))
+
+int memcmp(const void *s1, const void *s2, size_t n);
+#define memcmp(s1, s2, n)	__builtin_memcmp((s1), (s2), (n))
+
+#undef LIBDEFLATE_ENABLE_ASSERTIONS
+#else
+#include <string.h>
+#endif
+
+/*
+ * Runtime assertion support.  Don't enable this in production builds; it may
+ * hurt performance significantly.
+ */
+#ifdef LIBDEFLATE_ENABLE_ASSERTIONS
+void libdeflate_assertion_failed(const char *expr, const char *file, int line);
+#define ASSERT(expr) { if (unlikely(!(expr))) \
+	libdeflate_assertion_failed(#expr, __FILE__, __LINE__); }
+#else
+#define ASSERT(expr) (void)(expr)
+#endif
+
+#define CONCAT_IMPL(a, b)	a##b
+#define CONCAT(a, b)		CONCAT_IMPL(a, b)
+#define ADD_SUFFIX(name)	CONCAT(name, SUFFIX)
+
+#endif /* LIB_LIB_COMMON_H */
+
+/*
+ * deflate_constants.h - constants for the DEFLATE compression format
+ */
+
+#ifndef LIB_DEFLATE_CONSTANTS_H
+#define LIB_DEFLATE_CONSTANTS_H
+
+/* Valid block types  */
+#define DEFLATE_BLOCKTYPE_UNCOMPRESSED		0
+#define DEFLATE_BLOCKTYPE_STATIC_HUFFMAN	1
+#define DEFLATE_BLOCKTYPE_DYNAMIC_HUFFMAN	2
+
+/* Minimum and maximum supported match lengths (in bytes)  */
+#define DEFLATE_MIN_MATCH_LEN			3
+#define DEFLATE_MAX_MATCH_LEN			258
+
+/* Maximum supported match offset (in bytes) */
+#define DEFLATE_MAX_MATCH_OFFSET		32768
+
+/* log2 of DEFLATE_MAX_MATCH_OFFSET */
+#define DEFLATE_WINDOW_ORDER			15
+
+/* Number of symbols in each Huffman code.  Note: for the literal/length
+ * and offset codes, these are actually the maximum values; a given block
+ * might use fewer symbols.  */
+#define DEFLATE_NUM_PRECODE_SYMS		19
+#define DEFLATE_NUM_LITLEN_SYMS			288
+#define DEFLATE_NUM_OFFSET_SYMS			32
+
+/* The maximum number of symbols across all codes  */
+#define DEFLATE_MAX_NUM_SYMS			288
+
+/* Division of symbols in the literal/length code  */
+#define DEFLATE_NUM_LITERALS			256
+#define DEFLATE_END_OF_BLOCK			256
+#define DEFLATE_FIRST_LEN_SYM			257
+
+/* Maximum codeword length, in bits, within each Huffman code  */
+#define DEFLATE_MAX_PRE_CODEWORD_LEN		7
+#define DEFLATE_MAX_LITLEN_CODEWORD_LEN		15
+#define DEFLATE_MAX_OFFSET_CODEWORD_LEN		15
+
+/* The maximum codeword length across all codes  */
+#define DEFLATE_MAX_CODEWORD_LEN		15
+
+/* Maximum possible overrun when decoding codeword lengths  */
+#define DEFLATE_MAX_LENS_OVERRUN		137
+
+/*
+ * Maximum number of extra bits that may be required to represent a match
+ * length or offset.
+ */
+#define DEFLATE_MAX_EXTRA_LENGTH_BITS		5
+#define DEFLATE_MAX_EXTRA_OFFSET_BITS		13
+
+#endif /* LIB_DEFLATE_CONSTANTS_H */
+
+/*
+ * cpu_features_common.h - code shared by all lib/$arch/cpu_features.c
+ *
+ * Copyright 2020 Eric Biggers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef LIB_CPU_FEATURES_COMMON_H
+#define LIB_CPU_FEATURES_COMMON_H
+
+#if defined(TEST_SUPPORT__DO_NOT_USE) && !defined(FREESTANDING)
+   /* for strdup() and strtok_r() */
+#  undef _ANSI_SOURCE
+#  ifndef __APPLE__
+#    undef _GNU_SOURCE
+#    define _GNU_SOURCE
+#  endif
+#  include <stdio.h>
+#  include <stdlib.h>
+#  include <string.h>
+#endif
+
+struct cpu_feature {
+	u32 bit;
+	const char *name;
+};
+
+#if defined(TEST_SUPPORT__DO_NOT_USE) && !defined(FREESTANDING)
+/* Disable any features that are listed in $LIBDEFLATE_DISABLE_CPU_FEATURES. */
+static inline void
+disable_cpu_features_for_testing(u32 *features,
+				 const struct cpu_feature *feature_table,
+				 size_t feature_table_length)
+{
+	char *env_value, *strbuf, *p, *saveptr = NULL;
+	size_t i;
+
+	env_value = getenv("LIBDEFLATE_DISABLE_CPU_FEATURES");
+	if (!env_value)
+		return;
+	strbuf = strdup(env_value);
+	if (!strbuf)
+		abort();
+	p = strtok_r(strbuf, ",", &saveptr);
+	while (p) {
+		for (i = 0; i < feature_table_length; i++) {
+			if (strcmp(p, feature_table[i].name) == 0) {
+				*features &= ~feature_table[i].bit;
+				break;
+			}
+		}
+		if (i == feature_table_length) {
+			fprintf(stderr,
+				"unrecognized feature in LIBDEFLATE_DISABLE_CPU_FEATURES: \"%s\"\n",
+				p);
+			abort();
+		}
+		p = strtok_r(NULL, ",", &saveptr);
+	}
+	free(strbuf);
+}
+#else /* TEST_SUPPORT__DO_NOT_USE */
+static inline void
+disable_cpu_features_for_testing(u32 *features,
+				 const struct cpu_feature *feature_table,
+				 size_t feature_table_length)
+{
+}
+#endif /* !TEST_SUPPORT__DO_NOT_USE */
+
+#endif /* LIB_CPU_FEATURES_COMMON_H */
+
+/*
+ * x86/cpu_features.h - feature detection for x86 CPUs
+ *
+ * Copyright 2016 Eric Biggers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef LIB_X86_CPU_FEATURES_H
+#define LIB_X86_CPU_FEATURES_H
+
+#define HAVE_DYNAMIC_X86_CPU_FEATURES	0
+
+#if defined(ARCH_X86_32) || defined(ARCH_X86_64)
+
+#if COMPILER_SUPPORTS_TARGET_FUNCTION_ATTRIBUTE || defined(_MSC_VER)
+#  undef HAVE_DYNAMIC_X86_CPU_FEATURES
+#  define HAVE_DYNAMIC_X86_CPU_FEATURES	1
+#endif
+
+#define X86_CPU_FEATURE_SSE2		0x00000001
+#define X86_CPU_FEATURE_PCLMUL		0x00000002
+#define X86_CPU_FEATURE_AVX		0x00000004
+#define X86_CPU_FEATURE_AVX2		0x00000008
+#define X86_CPU_FEATURE_BMI2		0x00000010
+
+#define HAVE_SSE2(features)	(HAVE_SSE2_NATIVE     || ((features) & X86_CPU_FEATURE_SSE2))
+#define HAVE_PCLMUL(features)	(HAVE_PCLMUL_NATIVE   || ((features) & X86_CPU_FEATURE_PCLMUL))
+#define HAVE_AVX(features)	(HAVE_AVX_NATIVE      || ((features) & X86_CPU_FEATURE_AVX))
+#define HAVE_AVX2(features)	(HAVE_AVX2_NATIVE     || ((features) & X86_CPU_FEATURE_AVX2))
+#define HAVE_BMI2(features)	(HAVE_BMI2_NATIVE     || ((features) & X86_CPU_FEATURE_BMI2))
+
+#if HAVE_DYNAMIC_X86_CPU_FEATURES
+#define X86_CPU_FEATURES_KNOWN		0x80000000
+extern volatile u32 libdeflate_x86_cpu_features;
+
+void libdeflate_init_x86_cpu_features(void);
+
+static inline u32 get_x86_cpu_features(void)
+{
+	if (libdeflate_x86_cpu_features == 0)
+		libdeflate_init_x86_cpu_features();
+	return libdeflate_x86_cpu_features;
+}
+#else /* HAVE_DYNAMIC_X86_CPU_FEATURES */
+static inline u32 get_x86_cpu_features(void) { return 0; }
+#endif /* !HAVE_DYNAMIC_X86_CPU_FEATURES */
+
+/*
+ * Prior to gcc 4.9 (r200349) and clang 3.8 (r239883), x86 intrinsics not
+ * available in the main target couldn't be used in 'target' attribute
+ * functions.  Unfortunately clang has no feature test macro for this, so we
+ * have to check its version.
+ */
+#if HAVE_DYNAMIC_X86_CPU_FEATURES && \
+	(GCC_PREREQ(4, 9) || CLANG_PREREQ(3, 8, 7030000) || defined(_MSC_VER))
+#  define HAVE_TARGET_INTRINSICS	1
+#else
+#  define HAVE_TARGET_INTRINSICS	0
+#endif
+
+/* SSE2 */
+#if defined(__SSE2__) || \
+	(defined(_MSC_VER) && \
+	 (defined(ARCH_X86_64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)))
+#  define HAVE_SSE2_NATIVE	1
+#else
+#  define HAVE_SSE2_NATIVE	0
+#endif
+#define HAVE_SSE2_INTRIN	(HAVE_SSE2_NATIVE || HAVE_TARGET_INTRINSICS)
+
+/* PCLMUL */
+#if defined(__PCLMUL__) || (defined(_MSC_VER) && defined(__AVX2__))
+#  define HAVE_PCLMUL_NATIVE	1
+#else
+#  define HAVE_PCLMUL_NATIVE	0
+#endif
+#if HAVE_PCLMUL_NATIVE || (HAVE_TARGET_INTRINSICS && \
+			   (GCC_PREREQ(4, 4) || CLANG_PREREQ(3, 2, 0) || \
+			    defined(_MSC_VER)))
+#  define HAVE_PCLMUL_INTRIN	1
+#else
+#  define HAVE_PCLMUL_INTRIN	0
+#endif
+
+/* AVX */
+#ifdef __AVX__
+#  define HAVE_AVX_NATIVE	1
+#else
+#  define HAVE_AVX_NATIVE	0
+#endif
+#if HAVE_AVX_NATIVE || (HAVE_TARGET_INTRINSICS && \
+			(GCC_PREREQ(4, 6) || CLANG_PREREQ(3, 0, 0) || \
+			 defined(_MSC_VER)))
+#  define HAVE_AVX_INTRIN	1
+#else
+#  define HAVE_AVX_INTRIN	0
+#endif
+
+/* AVX2 */
+#ifdef __AVX2__
+#  define HAVE_AVX2_NATIVE	1
+#else
+#  define HAVE_AVX2_NATIVE	0
+#endif
+#if HAVE_AVX2_NATIVE || (HAVE_TARGET_INTRINSICS && \
+			 (GCC_PREREQ(4, 7) || CLANG_PREREQ(3, 1, 0) || \
+			  defined(_MSC_VER)))
+#  define HAVE_AVX2_INTRIN	1
+#else
+#  define HAVE_AVX2_INTRIN	0
+#endif
+
+/* BMI2 */
+#if defined(__BMI2__) || (defined(_MSC_VER) && defined(__AVX2__))
+#  define HAVE_BMI2_NATIVE	1
+#else
+#  define HAVE_BMI2_NATIVE	0
+#endif
+#if HAVE_BMI2_NATIVE || (HAVE_TARGET_INTRINSICS && \
+			 (GCC_PREREQ(4, 7) || CLANG_PREREQ(3, 1, 0) || \
+			  defined(_MSC_VER)))
+#  define HAVE_BMI2_INTRIN	1
+#else
+#  define HAVE_BMI2_INTRIN	0
+#endif
+
+#endif /* ARCH_X86_32 || ARCH_X86_64 */
+
+#endif /* LIB_X86_CPU_FEATURES_H */
+
+
+/*
+ * If the expression passed to SAFETY_CHECK() evaluates to false, then the
+ * decompression routine immediately returns LIBDEFLATE_BAD_DATA, indicating the
+ * compressed data is invalid.
+ *
+ * Theoretically, these checks could be disabled for specialized applications
+ * where all input to the decompressor will be trusted.
+ */
+#if 0
+#  pragma message("UNSAFE DECOMPRESSION IS ENABLED. THIS MUST ONLY BE USED IF THE DECOMPRESSOR INPUT WILL ALWAYS BE TRUSTED!")
+#  define SAFETY_CHECK(expr)	(void)(expr)
+#else
+#  define SAFETY_CHECK(expr)	if (unlikely(!(expr))) return LIBDEFLATE_BAD_DATA
+#endif
+
+/*****************************************************************************
+ *				Input bitstream                              *
+ *****************************************************************************/
+
+/*
+ * The state of the "input bitstream" consists of the following variables:
+ *
+ *	- in_next: a pointer to the next unread byte in the input buffer
+ *
+ *	- in_end: a pointer to just past the end of the input buffer
+ *
+ *	- bitbuf: a word-sized variable containing bits that have been read from
+ *		  the input buffer or from the implicit appended zero bytes
+ *
+ *	- bitsleft: the number of bits in 'bitbuf' available to be consumed.
+ *		    After REFILL_BITS_BRANCHLESS(), 'bitbuf' can actually
+ *		    contain more bits than this.  However, only the bits counted
+ *		    by 'bitsleft' can actually be consumed; the rest can only be
+ *		    used for preloading.
+ *
+ *		    As a micro-optimization, we allow bits 8 and higher of
+ *		    'bitsleft' to contain garbage.  When consuming the bits
+ *		    associated with a decode table entry, this allows us to do
+ *		    'bitsleft -= entry' instead of 'bitsleft -= (u8)entry'.
+ *		    On some CPUs, this helps reduce instruction dependencies.
+ *		    This does have the disadvantage that 'bitsleft' sometimes
+ *		    needs to be cast to 'u8', such as when it's used as a shift
+ *		    amount in REFILL_BITS_BRANCHLESS().  But that one happens
+ *		    for free since most CPUs ignore high bits in shift amounts.
+ *
+ *	- overread_count: the total number of implicit appended zero bytes that
+ *			  have been loaded into the bitbuffer, including any
+ *			  counted by 'bitsleft' and any already consumed
+ */
+
+/*
+ * The type for the bitbuffer variable ('bitbuf' described above).  For best
+ * performance, this should have size equal to a machine word.
+ *
+ * 64-bit platforms have a significant advantage: they get a bigger bitbuffer
+ * which they don't have to refill as often.
+ */
+typedef machine_word_t bitbuf_t;
+#define BITBUF_NBITS	(8 * (int)sizeof(bitbuf_t))
+
+/* BITMASK(n) returns a bitmask of length 'n'. */
+#define BITMASK(n)	(((bitbuf_t)1 << (n)) - 1)
+
+/*
+ * MAX_BITSLEFT is the maximum number of consumable bits, i.e. the maximum value
+ * of '(u8)bitsleft'.  This is the size of the bitbuffer variable, minus 1 if
+ * the branchless refill method is being used (see REFILL_BITS_BRANCHLESS()).
+ */
+#define MAX_BITSLEFT	\
+	(UNALIGNED_ACCESS_IS_FAST ? BITBUF_NBITS - 1 : BITBUF_NBITS)
+
+/*
+ * CONSUMABLE_NBITS is the minimum number of bits that are guaranteed to be
+ * consumable (counted in 'bitsleft') immediately after refilling the bitbuffer.
+ * Since only whole bytes can be added to 'bitsleft', the worst case is
+ * 'MAX_BITSLEFT - 7': the smallest amount where another byte doesn't fit.
+ */
+#define CONSUMABLE_NBITS	(MAX_BITSLEFT - 7)
+
+/*
+ * FASTLOOP_PRELOADABLE_NBITS is the minimum number of bits that are guaranteed
+ * to be preloadable immediately after REFILL_BITS_IN_FASTLOOP().  (It is *not*
+ * guaranteed after REFILL_BITS(), since REFILL_BITS() falls back to a
+ * byte-at-a-time refill method near the end of input.)  This may exceed the
+ * number of consumable bits (counted by 'bitsleft').  Any bits not counted in
+ * 'bitsleft' can only be used for precomputation and cannot be consumed.
+ */
+#define FASTLOOP_PRELOADABLE_NBITS	\
+	(UNALIGNED_ACCESS_IS_FAST ? BITBUF_NBITS : CONSUMABLE_NBITS)
+
+/*
+ * PRELOAD_SLACK is the minimum number of bits that are guaranteed to be
+ * preloadable but not consumable, following REFILL_BITS_IN_FASTLOOP() and any
+ * subsequent consumptions.  This is 1 bit if the branchless refill method is
+ * being used, and 0 bits otherwise.
+ */
+#define PRELOAD_SLACK	MAX(0, FASTLOOP_PRELOADABLE_NBITS - MAX_BITSLEFT)
+
+/*
+ * CAN_CONSUME(n) is true if it's guaranteed that if the bitbuffer has just been
+ * refilled, then it's always possible to consume 'n' bits from it.  'n' should
+ * be a compile-time constant, to enable compile-time evaluation.
+ */
+#define CAN_CONSUME(n)	(CONSUMABLE_NBITS >= (n))
+
+/*
+ * CAN_CONSUME_AND_THEN_PRELOAD(consume_nbits, preload_nbits) is true if it's
+ * guaranteed that after REFILL_BITS_IN_FASTLOOP(), it's always possible to
+ * consume 'consume_nbits' bits, then preload 'preload_nbits' bits.  The
+ * arguments should be compile-time constants to enable compile-time evaluation.
+ */
+#define CAN_CONSUME_AND_THEN_PRELOAD(consume_nbits, preload_nbits)	\
+	(CONSUMABLE_NBITS >= (consume_nbits) &&				\
+	 FASTLOOP_PRELOADABLE_NBITS >= (consume_nbits) + (preload_nbits))
+
+/*
+ * REFILL_BITS_BRANCHLESS() branchlessly refills the bitbuffer variable by
+ * reading the next word from the input buffer and updating 'in_next' and
+ * 'bitsleft' based on how many bits were refilled -- counting whole bytes only.
+ * This is much faster than reading a byte at a time, at least if the CPU is
+ * little endian and supports fast unaligned memory accesses.
+ *
+ * The simplest way of branchlessly updating 'bitsleft' would be:
+ *
+ *	bitsleft += (MAX_BITSLEFT - bitsleft) & ~7;
+ *
+ * To make it faster, we define MAX_BITSLEFT to be 'WORDBITS - 1' rather than
+ * WORDBITS, so that in binary it looks like 111111 or 11111.  Then, we update
+ * 'bitsleft' by just setting the bits above the low 3 bits:
+ *
+ *	bitsleft |= MAX_BITSLEFT & ~7;
+ *
+ * That compiles down to a single instruction like 'or $0x38, %rbp'.  Using
+ * 'MAX_BITSLEFT == WORDBITS - 1' also has the advantage that refills can be
+ * done when 'bitsleft == MAX_BITSLEFT' without invoking undefined behavior.
+ *
+ * The simplest way of branchlessly updating 'in_next' would be:
+ *
+ *	in_next += (MAX_BITSLEFT - bitsleft) >> 3;
+ *
+ * With 'MAX_BITSLEFT == WORDBITS - 1' we could use an XOR instead, though this
+ * isn't really better:
+ *
+ *	in_next += (MAX_BITSLEFT ^ bitsleft) >> 3;
+ *
+ * An alternative which can be marginally better is the following:
+ *
+ *	in_next += sizeof(bitbuf_t) - 1;
+ *	in_next -= (bitsleft >> 3) & 0x7;
+ *
+ * It seems this would increase the number of CPU instructions from 3 (sub, shr,
+ * add) to 4 (add, shr, and, sub).  However, if the CPU has a bitfield
+ * extraction instruction (e.g. arm's ubfx), it stays at 3, and is potentially
+ * more efficient because the length of the longest dependency chain decreases
+ * from 3 to 2.  This alternative also has the advantage that it ignores the
+ * high bits in 'bitsleft', so it is compatible with the micro-optimization we
+ * use where we let the high bits of 'bitsleft' contain garbage.
+ */
+#define REFILL_BITS_BRANCHLESS()					\
+do {									\
+	bitbuf |= get_unaligned_leword(in_next) << (u8)bitsleft;	\
+	in_next += sizeof(bitbuf_t) - 1;				\
+	in_next -= (bitsleft >> 3) & 0x7;				\
+	bitsleft |= MAX_BITSLEFT & ~7;					\
+} while (0)
+
+/*
+ * REFILL_BITS() loads bits from the input buffer until the bitbuffer variable
+ * contains at least CONSUMABLE_NBITS consumable bits.
+ *
+ * This checks for the end of input, and it doesn't guarantee
+ * FASTLOOP_PRELOADABLE_NBITS, so it can't be used in the fastloop.
+ *
+ * If we would overread the input buffer, we just don't read anything, leaving
+ * the bits zeroed but marking them filled.  This simplifies the decompressor
+ * because it removes the need to always be able to distinguish between real
+ * overreads and overreads caused only by the decompressor's own lookahead.
+ *
+ * We do still keep track of the number of bytes that have been overread, for
+ * two reasons.  First, it allows us to determine the exact number of bytes that
+ * were consumed once the stream ends or an uncompressed block is reached.
+ * Second, it allows us to stop early if the overread amount gets so large (more
+ * than sizeof bitbuf) that it can only be caused by a real overread.  (The
+ * second part is arguably unneeded, since libdeflate is buffer-based; given
+ * infinite zeroes, it will eventually either completely fill the output buffer
+ * or return an error.  However, we do it to be slightly more friendly to the
+ * not-recommended use case of decompressing with an unknown output size.)
+ */
+#define REFILL_BITS()							\
+do {									\
+	if (UNALIGNED_ACCESS_IS_FAST &&					\
+	    likely(in_end - in_next >= sizeof(bitbuf_t))) {		\
+		REFILL_BITS_BRANCHLESS();				\
+	} else {							\
+		while ((u8)bitsleft < CONSUMABLE_NBITS) {		\
+			if (likely(in_next != in_end)) {		\
+				bitbuf |= (bitbuf_t)*in_next++ <<	\
+					  (u8)bitsleft;			\
+			} else {					\
+				overread_count++;			\
+				SAFETY_CHECK(overread_count <=		\
+					     sizeof(bitbuf_t));		\
+			}						\
+			bitsleft += 8;					\
+		}							\
+	}								\
+} while (0)
+
+/*
+ * REFILL_BITS_IN_FASTLOOP() is like REFILL_BITS(), but it doesn't check for the
+ * end of the input.  It can only be used in the fastloop.
+ */
+#define REFILL_BITS_IN_FASTLOOP()					\
+do {									\
+	STATIC_ASSERT(UNALIGNED_ACCESS_IS_FAST ||			\
+		      FASTLOOP_PRELOADABLE_NBITS == CONSUMABLE_NBITS);	\
+	if (UNALIGNED_ACCESS_IS_FAST) {					\
+		REFILL_BITS_BRANCHLESS();				\
+	} else {							\
+		while ((u8)bitsleft < CONSUMABLE_NBITS) {		\
+			bitbuf |= (bitbuf_t)*in_next++ << (u8)bitsleft;	\
+			bitsleft += 8;					\
+		}							\
+	}								\
+} while (0)
+
+/*
+ * This is the worst-case maximum number of output bytes that are written to
+ * during each iteration of the fastloop.  The worst case is 2 literals, then a
+ * match of length DEFLATE_MAX_MATCH_LEN.  Additionally, some slack space must
+ * be included for the intentional overrun in the match copy implementation.
+ */
+#define FASTLOOP_MAX_BYTES_WRITTEN	\
+	(2 + DEFLATE_MAX_MATCH_LEN + (5 * WORDBYTES) - 1)
+
+/*
+ * This is the worst-case maximum number of input bytes that are read during
+ * each iteration of the fastloop.  To get this value, we first compute the
+ * greatest number of bits that can be refilled during a loop iteration.  The
+ * refill at the beginning can add at most MAX_BITSLEFT, and the amount that can
+ * be refilled later is no more than the maximum amount that can be consumed by
+ * 2 literals that don't need a subtable, then a match.  We convert this value
+ * to bytes, rounding up; this gives the maximum number of bytes that 'in_next'
+ * can be advanced.  Finally, we add sizeof(bitbuf_t) to account for
+ * REFILL_BITS_BRANCHLESS() reading a word past 'in_next'.
+ */
+#define FASTLOOP_MAX_BYTES_READ					\
+	(DIV_ROUND_UP(MAX_BITSLEFT + (2 * LITLEN_TABLEBITS) +	\
+		      LENGTH_MAXBITS + OFFSET_MAXBITS, 8) +	\
+	 sizeof(bitbuf_t))
+
+/*****************************************************************************
+ *                              Huffman decoding                             *
+ *****************************************************************************/
+
+/*
+ * The fastest way to decode Huffman-encoded data is basically to use a decode
+ * table that maps the next TABLEBITS bits of data to their symbol.  Each entry
+ * decode_table[i] maps to the symbol whose codeword is a prefix of 'i'.  A
+ * symbol with codeword length 'n' has '2**(TABLEBITS-n)' entries in the table.
+ *
+ * Ideally, TABLEBITS and the maximum codeword length would be the same; some
+ * compression formats are designed with this goal in mind.  Unfortunately, in
+ * DEFLATE, the maximum litlen and offset codeword lengths are 15 bits, which is
+ * too large for a practical TABLEBITS.  It's not *that* much larger, though, so
+ * the workaround is to use a single level of subtables.  In the main table,
+ * entries for prefixes of codewords longer than TABLEBITS contain a "pointer"
+ * to the appropriate subtable along with the number of bits it is indexed with.
+ *
+ * The most efficient way to allocate subtables is to allocate them dynamically
+ * after the main table.  The worst-case number of table entries needed,
+ * including subtables, is precomputable; see the ENOUGH constants below.
+ *
+ * A useful optimization is to store the codeword lengths in the decode table so
+ * that they don't have to be looked up by indexing a separate table that maps
+ * symbols to their codeword lengths.  We basically do this; however, for the
+ * litlen and offset codes we also implement some DEFLATE-specific optimizations
+ * that build in the consideration of the "extra bits" and the
+ * literal/length/end-of-block division.  For the exact decode table entry
+ * format we use, see the definitions of the *_decode_results[] arrays below.
+ */
+
+
+/*
+ * These are the TABLEBITS values we use for each of the DEFLATE Huffman codes,
+ * along with their corresponding ENOUGH values.
+ *
+ * For the precode, we use PRECODE_TABLEBITS == 7 since this is the maximum
+ * precode codeword length.  This avoids ever needing subtables.
+ *
+ * For the litlen and offset codes, we cannot realistically avoid ever needing
+ * subtables, since litlen and offset codewords can be up to 15 bits.  A higher
+ * TABLEBITS reduces the number of lookups that need a subtable, which increases
+ * performance; however, it increases memory usage and makes building the table
+ * take longer, which decreases performance.  We choose values that work well in
+ * practice, making subtables rarely needed without making the tables too large.
+ *
+ * Our choice of OFFSET_TABLEBITS == 8 is a bit low; without any special
+ * considerations, 9 would fit the trade-off curve better.  However, there is a
+ * performance benefit to using exactly 8 bits when it is a compile-time
+ * constant, as many CPUs can take the low byte more easily than the low 9 bits.
+ *
+ * zlib treats its equivalents of TABLEBITS as maximum values; whenever it
+ * builds a table, it caps the actual table_bits to the longest codeword.  This
+ * makes sense in theory, as there's no need for the table to be any larger than
+ * needed to support the longest codeword.  However, having the table bits be a
+ * compile-time constant is beneficial to the performance of the decode loop, so
+ * there is a trade-off.  libdeflate currently uses the dynamic table_bits
+ * strategy for the litlen table only, due to its larger maximum size.
+ * PRECODE_TABLEBITS and OFFSET_TABLEBITS are smaller, so going dynamic there
+ * isn't as useful, and OFFSET_TABLEBITS=8 is useful as mentioned above.
+ *
+ * Each TABLEBITS value has a corresponding ENOUGH value that gives the
+ * worst-case maximum number of decode table entries, including the main table
+ * and all subtables.  The ENOUGH value depends on three parameters:
+ *
+ *	(1) the maximum number of symbols in the code (DEFLATE_NUM_*_SYMS)
+ *	(2) the maximum number of main table bits (*_TABLEBITS)
+ *	(3) the maximum allowed codeword length (DEFLATE_MAX_*_CODEWORD_LEN)
+ *
+ * The ENOUGH values were computed using the utility program 'enough' from zlib.
+ */
+#define PRECODE_TABLEBITS	7
+#define PRECODE_ENOUGH		128	/* enough 19 7 7	*/
+#define LITLEN_TABLEBITS	11
+#define LITLEN_ENOUGH		2342	/* enough 288 11 15	*/
+#define OFFSET_TABLEBITS	8
+#define OFFSET_ENOUGH		402	/* enough 32 8 15	*/
+
+/*
+ * make_decode_table_entry() creates a decode table entry for the given symbol
+ * by combining the static part 'decode_results[sym]' with the dynamic part
+ * 'len', which is the remaining codeword length (the codeword length for main
+ * table entries, or the codeword length minus TABLEBITS for subtable entries).
+ *
+ * In all cases, we add 'len' to each of the two low-order bytes to create the
+ * appropriately-formatted decode table entry.  See the definitions of the
+ * *_decode_results[] arrays below, where the entry format is described.
+ */
+static forceinline u32
+make_decode_table_entry(const u32 decode_results[], u32 sym, u32 len)
+{
+	return decode_results[sym] + (len << 8) + len;
+}
+
+/*
+ * Here is the format of our precode decode table entries.  Bits not explicitly
+ * described contain zeroes:
+ *
+ *	Bit 20-16:  presym
+ *	Bit 10-8:   codeword length [not used]
+ *	Bit 2-0:    codeword length
+ *
+ * The precode decode table never has subtables, since we use
+ * PRECODE_TABLEBITS == DEFLATE_MAX_PRE_CODEWORD_LEN.
+ *
+ * precode_decode_results[] contains the static part of the entry for each
+ * symbol.  make_decode_table_entry() produces the final entries.
+ */
+static const u32 precode_decode_results[] = {
+#define ENTRY(presym)	((u32)presym << 16)
+	ENTRY(0)   , ENTRY(1)   , ENTRY(2)   , ENTRY(3)   ,
+	ENTRY(4)   , ENTRY(5)   , ENTRY(6)   , ENTRY(7)   ,
+	ENTRY(8)   , ENTRY(9)   , ENTRY(10)  , ENTRY(11)  ,
+	ENTRY(12)  , ENTRY(13)  , ENTRY(14)  , ENTRY(15)  ,
+	ENTRY(16)  , ENTRY(17)  , ENTRY(18)  ,
+#undef ENTRY
+};
+
+/* Litlen and offset decode table entry flags */
+
+/* Indicates a literal entry in the litlen decode table */
+#define HUFFDEC_LITERAL			0x80000000
+
+/* Indicates that HUFFDEC_SUBTABLE_POINTER or HUFFDEC_END_OF_BLOCK is set */
+#define HUFFDEC_EXCEPTIONAL		0x00008000
+
+/* Indicates a subtable pointer entry in the litlen or offset decode table */
+#define HUFFDEC_SUBTABLE_POINTER	0x00004000
+
+/* Indicates an end-of-block entry in the litlen decode table */
+#define HUFFDEC_END_OF_BLOCK		0x00002000
+
+/* Maximum number of bits that can be consumed by decoding a match length */
+#define LENGTH_MAXBITS		(DEFLATE_MAX_LITLEN_CODEWORD_LEN + \
+				 DEFLATE_MAX_EXTRA_LENGTH_BITS)
+#define LENGTH_MAXFASTBITS	(LITLEN_TABLEBITS /* no subtable needed */ + \
+				 DEFLATE_MAX_EXTRA_LENGTH_BITS)
+
+/*
+ * Here is the format of our litlen decode table entries.  Bits not explicitly
+ * described contain zeroes:
+ *
+ *	Literals:
+ *		Bit 31:     1 (HUFFDEC_LITERAL)
+ *		Bit 23-16:  literal value
+ *		Bit 15:     0 (!HUFFDEC_EXCEPTIONAL)
+ *		Bit 14:     0 (!HUFFDEC_SUBTABLE_POINTER)
+ *		Bit 13:     0 (!HUFFDEC_END_OF_BLOCK)
+ *		Bit 11-8:   remaining codeword length [not used]
+ *		Bit 3-0:    remaining codeword length
+ *	Lengths:
+ *		Bit 31:     0 (!HUFFDEC_LITERAL)
+ *		Bit 24-16:  length base value
+ *		Bit 15:     0 (!HUFFDEC_EXCEPTIONAL)
+ *		Bit 14:     0 (!HUFFDEC_SUBTABLE_POINTER)
+ *		Bit 13:     0 (!HUFFDEC_END_OF_BLOCK)
+ *		Bit 11-8:   remaining codeword length
+ *		Bit 4-0:    remaining codeword length + number of extra bits
+ *	End of block:
+ *		Bit 31:     0 (!HUFFDEC_LITERAL)
+ *		Bit 15:     1 (HUFFDEC_EXCEPTIONAL)
+ *		Bit 14:     0 (!HUFFDEC_SUBTABLE_POINTER)
+ *		Bit 13:     1 (HUFFDEC_END_OF_BLOCK)
+ *		Bit 11-8:   remaining codeword length [not used]
+ *		Bit 3-0:    remaining codeword length
+ *	Subtable pointer:
+ *		Bit 31:     0 (!HUFFDEC_LITERAL)
+ *		Bit 30-16:  index of start of subtable
+ *		Bit 15:     1 (HUFFDEC_EXCEPTIONAL)
+ *		Bit 14:     1 (HUFFDEC_SUBTABLE_POINTER)
+ *		Bit 13:     0 (!HUFFDEC_END_OF_BLOCK)
+ *		Bit 11-8:   number of subtable bits
+ *		Bit 3-0:    number of main table bits
+ *
+ * This format has several desirable properties:
+ *
+ *	- The codeword length, length slot base, and number of extra length bits
+ *	  are all built in.  This eliminates the need to separately look up this
+ *	  information by indexing separate arrays by symbol or length slot.
+ *
+ *	- The HUFFDEC_* flags enable easily distinguishing between the different
+ *	  types of entries.  The HUFFDEC_LITERAL flag enables a fast path for
+ *	  literals; the high bit is used for this, as some CPUs can test the
+ *	  high bit more easily than other bits.  The HUFFDEC_EXCEPTIONAL flag
+ *	  makes it possible to detect the two unlikely cases (subtable pointer
+ *	  and end of block) in a single bit flag test.
+ *
+ *	- The low byte is the number of bits that need to be removed from the
+ *	  bitstream; this makes this value easily accessible, and it enables the
+ *	  micro-optimization of doing 'bitsleft -= entry' instead of
+ *	  'bitsleft -= (u8)entry'.  It also includes the number of extra bits,
+ *	  so they don't need to be removed separately.
+ *
+ *	- The flags in bits 15-13 are arranged to be 0 when the
+ *	  "remaining codeword length" in bits 11-8 is needed, making this value
+ *	  fairly easily accessible as well via a shift and downcast.
+ *
+ *	- Similarly, bits 13-12 are 0 when the "subtable bits" in bits 11-8 are
+ *	  needed, making it possible to extract this value with '& 0x3F' rather
+ *	  than '& 0xF'.  This value is only used as a shift amount, so this can
+ *	  save an 'and' instruction as the masking by 0x3F happens implicitly.
+ *
+ * litlen_decode_results[] contains the static part of the entry for each
+ * symbol.  make_decode_table_entry() produces the final entries.
+ */
+static const u32 litlen_decode_results[] = {
+
+	/* Literals */
+#define ENTRY(literal)	(HUFFDEC_LITERAL | ((u32)literal << 16))
+	ENTRY(0)   , ENTRY(1)   , ENTRY(2)   , ENTRY(3)   ,
+	ENTRY(4)   , ENTRY(5)   , ENTRY(6)   , ENTRY(7)   ,
+	ENTRY(8)   , ENTRY(9)   , ENTRY(10)  , ENTRY(11)  ,
+	ENTRY(12)  , ENTRY(13)  , ENTRY(14)  , ENTRY(15)  ,
+	ENTRY(16)  , ENTRY(17)  , ENTRY(18)  , ENTRY(19)  ,
+	ENTRY(20)  , ENTRY(21)  , ENTRY(22)  , ENTRY(23)  ,
+	ENTRY(24)  , ENTRY(25)  , ENTRY(26)  , ENTRY(27)  ,
+	ENTRY(28)  , ENTRY(29)  , ENTRY(30)  , ENTRY(31)  ,
+	ENTRY(32)  , ENTRY(33)  , ENTRY(34)  , ENTRY(35)  ,
+	ENTRY(36)  , ENTRY(37)  , ENTRY(38)  , ENTRY(39)  ,
+	ENTRY(40)  , ENTRY(41)  , ENTRY(42)  , ENTRY(43)  ,
+	ENTRY(44)  , ENTRY(45)  , ENTRY(46)  , ENTRY(47)  ,
+	ENTRY(48)  , ENTRY(49)  , ENTRY(50)  , ENTRY(51)  ,
+	ENTRY(52)  , ENTRY(53)  , ENTRY(54)  , ENTRY(55)  ,
+	ENTRY(56)  , ENTRY(57)  , ENTRY(58)  , ENTRY(59)  ,
+	ENTRY(60)  , ENTRY(61)  , ENTRY(62)  , ENTRY(63)  ,
+	ENTRY(64)  , ENTRY(65)  , ENTRY(66)  , ENTRY(67)  ,
+	ENTRY(68)  , ENTRY(69)  , ENTRY(70)  , ENTRY(71)  ,
+	ENTRY(72)  , ENTRY(73)  , ENTRY(74)  , ENTRY(75)  ,
+	ENTRY(76)  , ENTRY(77)  , ENTRY(78)  , ENTRY(79)  ,
+	ENTRY(80)  , ENTRY(81)  , ENTRY(82)  , ENTRY(83)  ,
+	ENTRY(84)  , ENTRY(85)  , ENTRY(86)  , ENTRY(87)  ,
+	ENTRY(88)  , ENTRY(89)  , ENTRY(90)  , ENTRY(91)  ,
+	ENTRY(92)  , ENTRY(93)  , ENTRY(94)  , ENTRY(95)  ,
+	ENTRY(96)  , ENTRY(97)  , ENTRY(98)  , ENTRY(99)  ,
+	ENTRY(100) , ENTRY(101) , ENTRY(102) , ENTRY(103) ,
+	ENTRY(104) , ENTRY(105) , ENTRY(106) , ENTRY(107) ,
+	ENTRY(108) , ENTRY(109) , ENTRY(110) , ENTRY(111) ,
+	ENTRY(112) , ENTRY(113) , ENTRY(114) , ENTRY(115) ,
+	ENTRY(116) , ENTRY(117) , ENTRY(118) , ENTRY(119) ,
+	ENTRY(120) , ENTRY(121) , ENTRY(122) , ENTRY(123) ,
+	ENTRY(124) , ENTRY(125) , ENTRY(126) , ENTRY(127) ,
+	ENTRY(128) , ENTRY(129) , ENTRY(130) , ENTRY(131) ,
+	ENTRY(132) , ENTRY(133) , ENTRY(134) , ENTRY(135) ,
+	ENTRY(136) , ENTRY(137) , ENTRY(138) , ENTRY(139) ,
+	ENTRY(140) , ENTRY(141) , ENTRY(142) , ENTRY(143) ,
+	ENTRY(144) , ENTRY(145) , ENTRY(146) , ENTRY(147) ,
+	ENTRY(148) , ENTRY(149) , ENTRY(150) , ENTRY(151) ,
+	ENTRY(152) , ENTRY(153) , ENTRY(154) , ENTRY(155) ,
+	ENTRY(156) , ENTRY(157) , ENTRY(158) , ENTRY(159) ,
+	ENTRY(160) , ENTRY(161) , ENTRY(162) , ENTRY(163) ,
+	ENTRY(164) , ENTRY(165) , ENTRY(166) , ENTRY(167) ,
+	ENTRY(168) , ENTRY(169) , ENTRY(170) , ENTRY(171) ,
+	ENTRY(172) , ENTRY(173) , ENTRY(174) , ENTRY(175) ,
+	ENTRY(176) , ENTRY(177) , ENTRY(178) , ENTRY(179) ,
+	ENTRY(180) , ENTRY(181) , ENTRY(182) , ENTRY(183) ,
+	ENTRY(184) , ENTRY(185) , ENTRY(186) , ENTRY(187) ,
+	ENTRY(188) , ENTRY(189) , ENTRY(190) , ENTRY(191) ,
+	ENTRY(192) , ENTRY(193) , ENTRY(194) , ENTRY(195) ,
+	ENTRY(196) , ENTRY(197) , ENTRY(198) , ENTRY(199) ,
+	ENTRY(200) , ENTRY(201) , ENTRY(202) , ENTRY(203) ,
+	ENTRY(204) , ENTRY(205) , ENTRY(206) , ENTRY(207) ,
+	ENTRY(208) , ENTRY(209) , ENTRY(210) , ENTRY(211) ,
+	ENTRY(212) , ENTRY(213) , ENTRY(214) , ENTRY(215) ,
+	ENTRY(216) , ENTRY(217) , ENTRY(218) , ENTRY(219) ,
+	ENTRY(220) , ENTRY(221) , ENTRY(222) , ENTRY(223) ,
+	ENTRY(224) , ENTRY(225) , ENTRY(226) , ENTRY(227) ,
+	ENTRY(228) , ENTRY(229) , ENTRY(230) , ENTRY(231) ,
+	ENTRY(232) , ENTRY(233) , ENTRY(234) , ENTRY(235) ,
+	ENTRY(236) , ENTRY(237) , ENTRY(238) , ENTRY(239) ,
+	ENTRY(240) , ENTRY(241) , ENTRY(242) , ENTRY(243) ,
+	ENTRY(244) , ENTRY(245) , ENTRY(246) , ENTRY(247) ,
+	ENTRY(248) , ENTRY(249) , ENTRY(250) , ENTRY(251) ,
+	ENTRY(252) , ENTRY(253) , ENTRY(254) , ENTRY(255) ,
+#undef ENTRY
+
+	/* End of block */
+	HUFFDEC_EXCEPTIONAL | HUFFDEC_END_OF_BLOCK,
+
+	/* Lengths */
+#define ENTRY(length_base, num_extra_bits)	\
+	(((u32)(length_base) << 16) | (num_extra_bits))
+	ENTRY(3  , 0) , ENTRY(4  , 0) , ENTRY(5  , 0) , ENTRY(6  , 0),
+	ENTRY(7  , 0) , ENTRY(8  , 0) , ENTRY(9  , 0) , ENTRY(10 , 0),
+	ENTRY(11 , 1) , ENTRY(13 , 1) , ENTRY(15 , 1) , ENTRY(17 , 1),
+	ENTRY(19 , 2) , ENTRY(23 , 2) , ENTRY(27 , 2) , ENTRY(31 , 2),
+	ENTRY(35 , 3) , ENTRY(43 , 3) , ENTRY(51 , 3) , ENTRY(59 , 3),
+	ENTRY(67 , 4) , ENTRY(83 , 4) , ENTRY(99 , 4) , ENTRY(115, 4),
+	ENTRY(131, 5) , ENTRY(163, 5) , ENTRY(195, 5) , ENTRY(227, 5),
+	ENTRY(258, 0) , ENTRY(258, 0) , ENTRY(258, 0) ,
+#undef ENTRY
+};
+
+/* Maximum number of bits that can be consumed by decoding a match offset */
+#define OFFSET_MAXBITS		(DEFLATE_MAX_OFFSET_CODEWORD_LEN + \
+				 DEFLATE_MAX_EXTRA_OFFSET_BITS)
+#define OFFSET_MAXFASTBITS	(OFFSET_TABLEBITS /* no subtable needed */ + \
+				 DEFLATE_MAX_EXTRA_OFFSET_BITS)
+
+/*
+ * Here is the format of our offset decode table entries.  Bits not explicitly
+ * described contain zeroes:
+ *
+ *	Offsets:
+ *		Bit 31-16:  offset base value
+ *		Bit 15:     0 (!HUFFDEC_EXCEPTIONAL)
+ *		Bit 14:     0 (!HUFFDEC_SUBTABLE_POINTER)
+ *		Bit 11-8:   remaining codeword length
+ *		Bit 4-0:    remaining codeword length + number of extra bits
+ *	Subtable pointer:
+ *		Bit 31-16:  index of start of subtable
+ *		Bit 15:     1 (HUFFDEC_EXCEPTIONAL)
+ *		Bit 14:     1 (HUFFDEC_SUBTABLE_POINTER)
+ *		Bit 11-8:   number of subtable bits
+ *		Bit 3-0:    number of main table bits
+ *
+ * These work the same way as the length entries and subtable pointer entries in
+ * the litlen decode table; see litlen_decode_results[] above.
+ */
+static const u32 offset_decode_results[] = {
+#define ENTRY(offset_base, num_extra_bits)	\
+	(((u32)(offset_base) << 16) | (num_extra_bits))
+	ENTRY(1     , 0)  , ENTRY(2     , 0)  , ENTRY(3     , 0)  , ENTRY(4     , 0)  ,
+	ENTRY(5     , 1)  , ENTRY(7     , 1)  , ENTRY(9     , 2)  , ENTRY(13    , 2) ,
+	ENTRY(17    , 3)  , ENTRY(25    , 3)  , ENTRY(33    , 4)  , ENTRY(49    , 4)  ,
+	ENTRY(65    , 5)  , ENTRY(97    , 5)  , ENTRY(129   , 6)  , ENTRY(193   , 6)  ,
+	ENTRY(257   , 7)  , ENTRY(385   , 7)  , ENTRY(513   , 8)  , ENTRY(769   , 8)  ,
+	ENTRY(1025  , 9)  , ENTRY(1537  , 9)  , ENTRY(2049  , 10) , ENTRY(3073  , 10) ,
+	ENTRY(4097  , 11) , ENTRY(6145  , 11) , ENTRY(8193  , 12) , ENTRY(12289 , 12) ,
+	ENTRY(16385 , 13) , ENTRY(24577 , 13) , ENTRY(24577 , 13) , ENTRY(24577 , 13) ,
+#undef ENTRY
+};
+
+/*
+ * The main DEFLATE decompressor structure.  Since libdeflate only supports
+ * full-buffer decompression, this structure doesn't store the entire
+ * decompression state, most of which is in stack variables.  Instead, this
+ * struct just contains the decode tables and some temporary arrays used for
+ * building them, as these are too large to comfortably allocate on the stack.
+ *
+ * Storing the decode tables in the decompressor struct also allows the decode
+ * tables for the static codes to be reused whenever two static Huffman blocks
+ * are decoded without an intervening dynamic block, even across streams.
+ */
+struct libdeflate_decompressor {
+
+	/*
+	 * The arrays aren't all needed at the same time.  'precode_lens' and
+	 * 'precode_decode_table' are unneeded after 'lens' has been filled.
+	 * Furthermore, 'lens' need not be retained after building the litlen
+	 * and offset decode tables.  In fact, 'lens' can be in union with
+	 * 'litlen_decode_table' provided that 'offset_decode_table' is separate
+	 * and is built first.
+	 */
+
+	union {
+		u8 precode_lens[DEFLATE_NUM_PRECODE_SYMS];
+
+		struct {
+			u8 lens[DEFLATE_NUM_LITLEN_SYMS +
+				DEFLATE_NUM_OFFSET_SYMS +
+				DEFLATE_MAX_LENS_OVERRUN];
+
+			u32 precode_decode_table[PRECODE_ENOUGH];
+		} l;
+
+		u32 litlen_decode_table[LITLEN_ENOUGH];
+	} u;
+
+	u32 offset_decode_table[OFFSET_ENOUGH];
+
+	/* used only during build_decode_table() */
+	u16 sorted_syms[DEFLATE_MAX_NUM_SYMS];
+
+	bool static_codes_loaded;
+	unsigned litlen_tablebits;
+
+	/* The free() function for this struct, chosen at allocation time */
+	free_func_t free_func;
+};
+
+/*
+ * Build a table for fast decoding of symbols from a Huffman code.  As input,
+ * this function takes the codeword length of each symbol which may be used in
+ * the code.  As output, it produces a decode table for the canonical Huffman
+ * code described by the codeword lengths.  The decode table is built with the
+ * assumption that it will be indexed with "bit-reversed" codewords, where the
+ * low-order bit is the first bit of the codeword.  This format is used for all
+ * Huffman codes in DEFLATE.
+ *
+ * @decode_table
+ *	The array in which the decode table will be generated.  This array must
+ *	have sufficient length; see the definition of the ENOUGH numbers.
+ * @lens
+ *	An array which provides, for each symbol, the length of the
+ *	corresponding codeword in bits, or 0 if the symbol is unused.  This may
+ *	alias @decode_table, since nothing is written to @decode_table until all
+ *	@lens have been consumed.  All codeword lengths are assumed to be <=
+ *	@max_codeword_len but are otherwise considered untrusted.  If they do
+ *	not form a valid Huffman code, then the decode table is not built and
+ *	%false is returned.
+ * @num_syms
+ *	The number of symbols in the code, including all unused symbols.
+ * @decode_results
+ *	An array which gives the incomplete decode result for each symbol.  The
+ *	needed values in this array will be combined with codeword lengths to
+ *	make the final decode table entries using make_decode_table_entry().
+ * @table_bits
+ *	The log base-2 of the number of main table entries to use.
+ *	If @table_bits_ret != NULL, then @table_bits is treated as a maximum
+ *	value and it will be decreased if a smaller table would be sufficient.
+ * @max_codeword_len
+ *	The maximum allowed codeword length for this Huffman code.
+ *	Must be <= DEFLATE_MAX_CODEWORD_LEN.
+ * @sorted_syms
+ *	A temporary array of length @num_syms.
+ * @table_bits_ret
+ *	If non-NULL, then the dynamic table_bits is enabled, and the actual
+ *	table_bits value will be returned here.
+ *
+ * Returns %true if successful; %false if the codeword lengths do not form a
+ * valid Huffman code.
+ */
+static bool
+build_decode_table(u32 decode_table[],
+		   const u8 lens[],
+		   const unsigned num_syms,
+		   const u32 decode_results[],
+		   unsigned table_bits,
+		   unsigned max_codeword_len,
+		   u16 *sorted_syms,
+		   unsigned *table_bits_ret)
+{
+	unsigned len_counts[DEFLATE_MAX_CODEWORD_LEN + 1];
+	unsigned offsets[DEFLATE_MAX_CODEWORD_LEN + 1];
+	unsigned sym;		/* current symbol */
+	unsigned codeword;	/* current codeword, bit-reversed */
+	unsigned len;		/* current codeword length in bits */
+	unsigned count;		/* num codewords remaining with this length */
+	u32 codespace_used;	/* codespace used out of '2^max_codeword_len' */
+	unsigned cur_table_end; /* end index of current table */
+	unsigned subtable_prefix; /* codeword prefix of current subtable */
+	unsigned subtable_start;  /* start index of current subtable */
+	unsigned subtable_bits;   /* log2 of current subtable length */
+
+	/* Count how many codewords have each length, including 0. */
+	for (len = 0; len <= max_codeword_len; len++)
+		len_counts[len] = 0;
+	for (sym = 0; sym < num_syms; sym++)
+		len_counts[lens[sym]]++;
+
+	/*
+	 * Determine the actual maximum codeword length that was used, and
+	 * decrease table_bits to it if allowed.
+	 */
+	while (max_codeword_len > 1 && len_counts[max_codeword_len] == 0)
+		max_codeword_len--;
+	if (table_bits_ret != NULL) {
+		table_bits = MIN(table_bits, max_codeword_len);
+		*table_bits_ret = table_bits;
+	}
+
+	/*
+	 * Sort the symbols primarily by increasing codeword length and
+	 * secondarily by increasing symbol value; or equivalently by their
+	 * codewords in lexicographic order, since a canonical code is assumed.
+	 *
+	 * For efficiency, also compute 'codespace_used' in the same pass over
+	 * 'len_counts[]' used to build 'offsets[]' for sorting.
+	 */
+
+	/* Ensure that 'codespace_used' cannot overflow. */
+	STATIC_ASSERT(sizeof(codespace_used) == 4);
+	STATIC_ASSERT(UINT32_MAX / (1U << (DEFLATE_MAX_CODEWORD_LEN - 1)) >=
+		      DEFLATE_MAX_NUM_SYMS);
+
+	offsets[0] = 0;
+	offsets[1] = len_counts[0];
+	codespace_used = 0;
+	for (len = 1; len < max_codeword_len; len++) {
+		offsets[len + 1] = offsets[len] + len_counts[len];
+		codespace_used = (codespace_used << 1) + len_counts[len];
+	}
+	codespace_used = (codespace_used << 1) + len_counts[len];
+
+	for (sym = 0; sym < num_syms; sym++)
+		sorted_syms[offsets[lens[sym]]++] = sym;
+
+	sorted_syms += offsets[0]; /* Skip unused symbols */
+
+	/* lens[] is done being used, so we can write to decode_table[] now. */
+
+	/*
+	 * Check whether the lengths form a complete code (exactly fills the
+	 * codespace), an incomplete code (doesn't fill the codespace), or an
+	 * overfull code (overflows the codespace).  A codeword of length 'n'
+	 * uses proportion '1/(2^n)' of the codespace.  An overfull code is
+	 * nonsensical, so is considered invalid.  An incomplete code is
+	 * considered valid only in two specific cases; see below.
+	 */
+
+	/* overfull code? */
+	if (unlikely(codespace_used > (1U << max_codeword_len)))
+		return false;
+
+	/* incomplete code? */
+	if (unlikely(codespace_used < (1U << max_codeword_len))) {
+		u32 entry;
+		unsigned i;
+
+		if (codespace_used == 0) {
+			/*
+			 * An empty code is allowed.  This can happen for the
+			 * offset code in DEFLATE, since a dynamic Huffman block
+			 * need not contain any matches.
+			 */
+
+			/* sym=0, len=1 (arbitrary) */
+			entry = make_decode_table_entry(decode_results, 0, 1);
+		} else {
+			/*
+			 * Allow codes with a single used symbol, with codeword
+			 * length 1.  The DEFLATE RFC is unclear regarding this
+			 * case.  What zlib's decompressor does is permit this
+			 * for the litlen and offset codes and assume the
+			 * codeword is '0' rather than '1'.  We do the same
+			 * except we allow this for precodes too, since there's
+			 * no convincing reason to treat the codes differently.
+			 * We also assign both codewords '0' and '1' to the
+			 * symbol to avoid having to handle '1' specially.
+			 */
+			if (codespace_used != (1U << (max_codeword_len - 1)) ||
+			    len_counts[1] != 1)
+				return false;
+			entry = make_decode_table_entry(decode_results,
+							*sorted_syms, 1);
+		}
+		/*
+		 * Note: the decode table still must be fully initialized, in
+		 * case the stream is malformed and contains bits from the part
+		 * of the codespace the incomplete code doesn't use.
+		 */
+		for (i = 0; i < (1U << table_bits); i++)
+			decode_table[i] = entry;
+		return true;
+	}
+
+	/*
+	 * The lengths form a complete code.  Now, enumerate the codewords in
+	 * lexicographic order and fill the decode table entries for each one.
+	 *
+	 * First, process all codewords with len <= table_bits.  Each one gets
+	 * '2^(table_bits-len)' direct entries in the table.
+	 *
+	 * Since DEFLATE uses bit-reversed codewords, these entries aren't
+	 * consecutive but rather are spaced '2^len' entries apart.  This makes
+	 * filling them naively somewhat awkward and inefficient, since strided
+	 * stores are less cache-friendly and preclude the use of word or
+	 * vector-at-a-time stores to fill multiple entries per instruction.
+	 *
+	 * To optimize this, we incrementally double the table size.  When
+	 * processing codewords with length 'len', the table is treated as
+	 * having only '2^len' entries, so each codeword uses just one entry.
+	 * Then, each time 'len' is incremented, the table size is doubled and
+	 * the first half is copied to the second half.  This significantly
+	 * improves performance over naively doing strided stores.
+	 *
+	 * Note that some entries copied for each table doubling may not have
+	 * been initialized yet, but it doesn't matter since they're guaranteed
+	 * to be initialized later (because the Huffman code is complete).
+	 */
+	codeword = 0;
+	len = 1;
+	while ((count = len_counts[len]) == 0)
+		len++;
+	cur_table_end = 1U << len;
+	while (len <= table_bits) {
+		/* Process all 'count' codewords with length 'len' bits. */
+		do {
+			unsigned bit;
+
+			/* Fill the first entry for the current codeword. */
+			decode_table[codeword] =
+				make_decode_table_entry(decode_results,
+							*sorted_syms++, len);
+
+			if (codeword == cur_table_end - 1) {
+				/* Last codeword (all 1's) */
+				for (; len < table_bits; len++) {
+					memcpy(&decode_table[cur_table_end],
+					       decode_table,
+					       cur_table_end *
+						sizeof(decode_table[0]));
+					cur_table_end <<= 1;
+				}
+				return true;
+			}
+			/*
+			 * To advance to the lexicographically next codeword in
+			 * the canonical code, the codeword must be incremented,
+			 * then 0's must be appended to the codeword as needed
+			 * to match the next codeword's length.
+			 *
+			 * Since the codeword is bit-reversed, appending 0's is
+			 * a no-op.  However, incrementing it is nontrivial.  To
+			 * do so efficiently, use the 'bsr' instruction to find
+			 * the last (highest order) 0 bit in the codeword, set
+			 * it, and clear any later (higher order) 1 bits.  But
+			 * 'bsr' actually finds the highest order 1 bit, so to
+			 * use it first flip all bits in the codeword by XOR'ing
+			 * it with (1U << len) - 1 == cur_table_end - 1.
+			 */
+			bit = 1U << bsr32(codeword ^ (cur_table_end - 1));
+			codeword &= bit - 1;
+			codeword |= bit;
+		} while (--count);
+
+		/* Advance to the next codeword length. */
+		do {
+			if (++len <= table_bits) {
+				memcpy(&decode_table[cur_table_end],
+				       decode_table,
+				       cur_table_end * sizeof(decode_table[0]));
+				cur_table_end <<= 1;
+			}
+		} while ((count = len_counts[len]) == 0);
+	}
+
+	/* Process codewords with len > table_bits.  These require subtables. */
+	cur_table_end = 1U << table_bits;
+	subtable_prefix = -1;
+	subtable_start = 0;
+	for (;;) {
+		u32 entry;
+		unsigned i;
+		unsigned stride;
+		unsigned bit;
+
+		/*
+		 * Start a new subtable if the first 'table_bits' bits of the
+		 * codeword don't match the prefix of the current subtable.
+		 */
+		if ((codeword & ((1U << table_bits) - 1)) != subtable_prefix) {
+			subtable_prefix = (codeword & ((1U << table_bits) - 1));
+			subtable_start = cur_table_end;
+			/*
+			 * Calculate the subtable length.  If the codeword has
+			 * length 'table_bits + n', then the subtable needs
+			 * '2^n' entries.  But it may need more; if fewer than
+			 * '2^n' codewords of length 'table_bits + n' remain,
+			 * then the length will need to be incremented to bring
+			 * in longer codewords until the subtable can be
+			 * completely filled.  Note that because the Huffman
+			 * code is complete, it will always be possible to fill
+			 * the subtable eventually.
+			 */
+			subtable_bits = len - table_bits;
+			codespace_used = count;
+			while (codespace_used < (1U << subtable_bits)) {
+				subtable_bits++;
+				codespace_used = (codespace_used << 1) +
+					len_counts[table_bits + subtable_bits];
+			}
+			cur_table_end = subtable_start + (1U << subtable_bits);
+
+			/*
+			 * Create the entry that points from the main table to
+			 * the subtable.
+			 */
+			decode_table[subtable_prefix] =
+				((u32)subtable_start << 16) |
+				HUFFDEC_EXCEPTIONAL |
+				HUFFDEC_SUBTABLE_POINTER |
+				(subtable_bits << 8) | table_bits;
+		}
+
+		/* Fill the subtable entries for the current codeword. */
+		entry = make_decode_table_entry(decode_results, *sorted_syms++,
+						len - table_bits);
+		i = subtable_start + (codeword >> table_bits);
+		stride = 1U << (len - table_bits);
+		do {
+			decode_table[i] = entry;
+			i += stride;
+		} while (i < cur_table_end);
+
+		/* Advance to the next codeword. */
+		if (codeword == (1U << len) - 1) /* last codeword (all 1's)? */
+			return true;
+		bit = 1U << bsr32(codeword ^ ((1U << len) - 1));
+		codeword &= bit - 1;
+		codeword |= bit;
+		count--;
+		while (count == 0)
+			count = len_counts[++len];
+	}
+}
+
+/* Build the decode table for the precode.  */
+static bool
+build_precode_decode_table(struct libdeflate_decompressor *d)
+{
+	/* When you change TABLEBITS, you must change ENOUGH, and vice versa! */
+	STATIC_ASSERT(PRECODE_TABLEBITS == 7 && PRECODE_ENOUGH == 128);
+
+	STATIC_ASSERT(ARRAY_LEN(precode_decode_results) ==
+		      DEFLATE_NUM_PRECODE_SYMS);
+
+	return build_decode_table(d->u.l.precode_decode_table,
+				  d->u.precode_lens,
+				  DEFLATE_NUM_PRECODE_SYMS,
+				  precode_decode_results,
+				  PRECODE_TABLEBITS,
+				  DEFLATE_MAX_PRE_CODEWORD_LEN,
+				  d->sorted_syms,
+				  NULL);
+}
+
+/* Build the decode table for the literal/length code.  */
+static bool
+build_litlen_decode_table(struct libdeflate_decompressor *d,
+			  unsigned num_litlen_syms, unsigned num_offset_syms)
+{
+	/* When you change TABLEBITS, you must change ENOUGH, and vice versa! */
+	STATIC_ASSERT(LITLEN_TABLEBITS == 11 && LITLEN_ENOUGH == 2342);
+
+	STATIC_ASSERT(ARRAY_LEN(litlen_decode_results) ==
+		      DEFLATE_NUM_LITLEN_SYMS);
+
+	return build_decode_table(d->u.litlen_decode_table,
+				  d->u.l.lens,
+				  num_litlen_syms,
+				  litlen_decode_results,
+				  LITLEN_TABLEBITS,
+				  DEFLATE_MAX_LITLEN_CODEWORD_LEN,
+				  d->sorted_syms,
+				  &d->litlen_tablebits);
+}
+
+/* Build the decode table for the offset code.  */
+static bool
+build_offset_decode_table(struct libdeflate_decompressor *d,
+			  unsigned num_litlen_syms, unsigned num_offset_syms)
+{
+	/* When you change TABLEBITS, you must change ENOUGH, and vice versa! */
+	STATIC_ASSERT(OFFSET_TABLEBITS == 8 && OFFSET_ENOUGH == 402);
+
+	STATIC_ASSERT(ARRAY_LEN(offset_decode_results) ==
+		      DEFLATE_NUM_OFFSET_SYMS);
+
+	return build_decode_table(d->offset_decode_table,
+				  d->u.l.lens + num_litlen_syms,
+				  num_offset_syms,
+				  offset_decode_results,
+				  OFFSET_TABLEBITS,
+				  DEFLATE_MAX_OFFSET_CODEWORD_LEN,
+				  d->sorted_syms,
+				  NULL);
+}
+
+/*****************************************************************************
+ *                         Main decompression routine
+ *****************************************************************************/
+
+typedef enum libdeflate_result (*decompress_func_t)
+	(struct libdeflate_decompressor * restrict d,
+	 const void * restrict in, size_t in_nbytes,
+	 void * restrict out, size_t out_nbytes_avail,
+	 size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret);
+
+#define FUNCNAME deflate_decompress_default
+#undef ATTRIBUTES
+#undef EXTRACT_VARBITS
+#undef EXTRACT_VARBITS8
+/*
+ * decompress_template.h
+ *
+ * Copyright 2016 Eric Biggers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * This is the actual DEFLATE decompression routine, lifted out of
+ * deflate_decompress.c so that it can be compiled multiple times with different
+ * target instruction sets.
+ */
+
+#ifndef ATTRIBUTES
+#  define ATTRIBUTES
+#endif
+#ifndef EXTRACT_VARBITS
+#  define EXTRACT_VARBITS(word, count)	((word) & BITMASK(count))
+#endif
+#ifndef EXTRACT_VARBITS8
+#  define EXTRACT_VARBITS8(word, count)	((word) & BITMASK((u8)(count)))
+#endif
+
+static enum libdeflate_result ATTRIBUTES MAYBE_UNUSED
+FUNCNAME(struct libdeflate_decompressor * restrict d,
+	 const void * restrict in, size_t in_nbytes,
+	 void * restrict out, size_t out_nbytes_avail,
+	 size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret)
+{
+	u8 *out_next = out;
+	u8 * const out_end = out_next + out_nbytes_avail;
+	u8 * const out_fastloop_end =
+		out_end - MIN(out_nbytes_avail, FASTLOOP_MAX_BYTES_WRITTEN);
+
+	/* Input bitstream state; see deflate_decompress.c for documentation */
+	const u8 *in_next = in;
+	const u8 * const in_end = in_next + in_nbytes;
+	const u8 * const in_fastloop_end =
+		in_end - MIN(in_nbytes, FASTLOOP_MAX_BYTES_READ);
+	bitbuf_t bitbuf = 0;
+	bitbuf_t saved_bitbuf;
+	u32 bitsleft = 0;
+	size_t overread_count = 0;
+
+	bool is_final_block;
+	unsigned block_type;
+	unsigned num_litlen_syms;
+	unsigned num_offset_syms;
+	bitbuf_t litlen_tablemask;
+	u32 entry;
+
+next_block:
+	/* Starting to read the next block */
+	;
+
+	STATIC_ASSERT(CAN_CONSUME(1 + 2 + 5 + 5 + 4 + 3));
+	REFILL_BITS();
+
+	/* BFINAL: 1 bit */
+	is_final_block = bitbuf & BITMASK(1);
+
+	/* BTYPE: 2 bits */
+	block_type = (bitbuf >> 1) & BITMASK(2);
+
+	if (block_type == DEFLATE_BLOCKTYPE_DYNAMIC_HUFFMAN) {
+
+		/* Dynamic Huffman block */
+
+		/* The order in which precode lengths are stored */
+		static const u8 deflate_precode_lens_permutation[DEFLATE_NUM_PRECODE_SYMS] = {
+			16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+		};
+
+		unsigned num_explicit_precode_lens;
+		unsigned i;
+
+		/* Read the codeword length counts. */
+
+		STATIC_ASSERT(DEFLATE_NUM_LITLEN_SYMS == 257 + BITMASK(5));
+		num_litlen_syms = 257 + ((bitbuf >> 3) & BITMASK(5));
+
+		STATIC_ASSERT(DEFLATE_NUM_OFFSET_SYMS == 1 + BITMASK(5));
+		num_offset_syms = 1 + ((bitbuf >> 8) & BITMASK(5));
+
+		STATIC_ASSERT(DEFLATE_NUM_PRECODE_SYMS == 4 + BITMASK(4));
+		num_explicit_precode_lens = 4 + ((bitbuf >> 13) & BITMASK(4));
+
+		d->static_codes_loaded = false;
+
+		/*
+		 * Read the precode codeword lengths.
+		 *
+		 * A 64-bit bitbuffer is just one bit too small to hold the
+		 * maximum number of precode lens, so to minimize branches we
+		 * merge one len with the previous fields.
+		 */
+		STATIC_ASSERT(DEFLATE_MAX_PRE_CODEWORD_LEN == (1 << 3) - 1);
+		if (CAN_CONSUME(3 * (DEFLATE_NUM_PRECODE_SYMS - 1))) {
+			d->u.precode_lens[deflate_precode_lens_permutation[0]] =
+				(bitbuf >> 17) & BITMASK(3);
+			bitbuf >>= 20;
+			bitsleft -= 20;
+			REFILL_BITS();
+			i = 1;
+			do {
+				d->u.precode_lens[deflate_precode_lens_permutation[i]] =
+					bitbuf & BITMASK(3);
+				bitbuf >>= 3;
+				bitsleft -= 3;
+			} while (++i < num_explicit_precode_lens);
+		} else {
+			bitbuf >>= 17;
+			bitsleft -= 17;
+			i = 0;
+			do {
+				if ((u8)bitsleft < 3)
+					REFILL_BITS();
+				d->u.precode_lens[deflate_precode_lens_permutation[i]] =
+					bitbuf & BITMASK(3);
+				bitbuf >>= 3;
+				bitsleft -= 3;
+			} while (++i < num_explicit_precode_lens);
+		}
+		for (; i < DEFLATE_NUM_PRECODE_SYMS; i++)
+			d->u.precode_lens[deflate_precode_lens_permutation[i]] = 0;
+
+		/* Build the decode table for the precode. */
+		SAFETY_CHECK(build_precode_decode_table(d));
+
+		/* Decode the litlen and offset codeword lengths. */
+		i = 0;
+		do {
+			unsigned presym;
+			u8 rep_val;
+			unsigned rep_count;
+
+			if ((u8)bitsleft < DEFLATE_MAX_PRE_CODEWORD_LEN + 7)
+				REFILL_BITS();
+
+			/*
+			 * The code below assumes that the precode decode table
+			 * doesn't have any subtables.
+			 */
+			STATIC_ASSERT(PRECODE_TABLEBITS == DEFLATE_MAX_PRE_CODEWORD_LEN);
+
+			/* Decode the next precode symbol. */
+			entry = d->u.l.precode_decode_table[
+				bitbuf & BITMASK(DEFLATE_MAX_PRE_CODEWORD_LEN)];
+			bitbuf >>= (u8)entry;
+			bitsleft -= entry; /* optimization: subtract full entry */
+			presym = entry >> 16;
+
+			if (presym < 16) {
+				/* Explicit codeword length */
+				d->u.l.lens[i++] = presym;
+				continue;
+			}
+
+			/* Run-length encoded codeword lengths */
+
+			/*
+			 * Note: we don't need to immediately verify that the
+			 * repeat count doesn't overflow the number of elements,
+			 * since we've sized the lens array to have enough extra
+			 * space to allow for the worst-case overrun (138 zeroes
+			 * when only 1 length was remaining).
+			 *
+			 * In the case of the small repeat counts (presyms 16
+			 * and 17), it is fastest to always write the maximum
+			 * number of entries.  That gets rid of branches that
+			 * would otherwise be required.
+			 *
+			 * It is not just because of the numerical order that
+			 * our checks go in the order 'presym < 16', 'presym ==
+			 * 16', and 'presym == 17'.  For typical data this is
+			 * ordered from most frequent to least frequent case.
+			 */
+			STATIC_ASSERT(DEFLATE_MAX_LENS_OVERRUN == 138 - 1);
+
+			if (presym == 16) {
+				/* Repeat the previous length 3 - 6 times. */
+				SAFETY_CHECK(i != 0);
+				rep_val = d->u.l.lens[i - 1];
+				STATIC_ASSERT(3 + BITMASK(2) == 6);
+				rep_count = 3 + (bitbuf & BITMASK(2));
+				bitbuf >>= 2;
+				bitsleft -= 2;
+				d->u.l.lens[i + 0] = rep_val;
+				d->u.l.lens[i + 1] = rep_val;
+				d->u.l.lens[i + 2] = rep_val;
+				d->u.l.lens[i + 3] = rep_val;
+				d->u.l.lens[i + 4] = rep_val;
+				d->u.l.lens[i + 5] = rep_val;
+				i += rep_count;
+			} else if (presym == 17) {
+				/* Repeat zero 3 - 10 times. */
+				STATIC_ASSERT(3 + BITMASK(3) == 10);
+				rep_count = 3 + (bitbuf & BITMASK(3));
+				bitbuf >>= 3;
+				bitsleft -= 3;
+				d->u.l.lens[i + 0] = 0;
+				d->u.l.lens[i + 1] = 0;
+				d->u.l.lens[i + 2] = 0;
+				d->u.l.lens[i + 3] = 0;
+				d->u.l.lens[i + 4] = 0;
+				d->u.l.lens[i + 5] = 0;
+				d->u.l.lens[i + 6] = 0;
+				d->u.l.lens[i + 7] = 0;
+				d->u.l.lens[i + 8] = 0;
+				d->u.l.lens[i + 9] = 0;
+				i += rep_count;
+			} else {
+				/* Repeat zero 11 - 138 times. */
+				STATIC_ASSERT(11 + BITMASK(7) == 138);
+				rep_count = 11 + (bitbuf & BITMASK(7));
+				bitbuf >>= 7;
+				bitsleft -= 7;
+				memset(&d->u.l.lens[i], 0,
+				       rep_count * sizeof(d->u.l.lens[i]));
+				i += rep_count;
+			}
+		} while (i < num_litlen_syms + num_offset_syms);
+
+		/* Unnecessary, but check this for consistency with zlib. */
+		SAFETY_CHECK(i == num_litlen_syms + num_offset_syms);
+
+	} else if (block_type == DEFLATE_BLOCKTYPE_UNCOMPRESSED) {
+		u16 len, nlen;
+
+		/*
+		 * Uncompressed block: copy 'len' bytes literally from the input
+		 * buffer to the output buffer.
+		 */
+
+		bitsleft -= 3; /* for BTYPE and BFINAL */
+
+		/*
+		 * Align the bitstream to the next byte boundary.  This means
+		 * the next byte boundary as if we were reading a byte at a
+		 * time.  Therefore, we have to rewind 'in_next' by any bytes
+		 * that have been refilled but not actually consumed yet (not
+		 * counting overread bytes, which don't increment 'in_next').
+		 */
+		bitsleft = (u8)bitsleft;
+		SAFETY_CHECK(overread_count <= (bitsleft >> 3));
+		in_next -= (bitsleft >> 3) - overread_count;
+		overread_count = 0;
+		bitbuf = 0;
+		bitsleft = 0;
+
+		SAFETY_CHECK(in_end - in_next >= 4);
+		len = get_unaligned_le16(in_next);
+		nlen = get_unaligned_le16(in_next + 2);
+		in_next += 4;
+
+		SAFETY_CHECK(len == (u16)~nlen);
+		if (unlikely(len > out_end - out_next))
+			return LIBDEFLATE_INSUFFICIENT_SPACE;
+		SAFETY_CHECK(len <= in_end - in_next);
+
+		memcpy(out_next, in_next, len);
+		in_next += len;
+		out_next += len;
+
+		goto block_done;
+
+	} else {
+		unsigned i;
+
+		SAFETY_CHECK(block_type == DEFLATE_BLOCKTYPE_STATIC_HUFFMAN);
+
+		/*
+		 * Static Huffman block: build the decode tables for the static
+		 * codes.  Skip doing so if the tables are already set up from
+		 * an earlier static block; this speeds up decompression of
+		 * degenerate input of many empty or very short static blocks.
+		 *
+		 * Afterwards, the remainder is the same as decompressing a
+		 * dynamic Huffman block.
+		 */
+
+		bitbuf >>= 3; /* for BTYPE and BFINAL */
+		bitsleft -= 3;
+
+		if (d->static_codes_loaded)
+			goto have_decode_tables;
+
+		d->static_codes_loaded = true;
+
+		STATIC_ASSERT(DEFLATE_NUM_LITLEN_SYMS == 288);
+		STATIC_ASSERT(DEFLATE_NUM_OFFSET_SYMS == 32);
+
+		for (i = 0; i < 144; i++)
+			d->u.l.lens[i] = 8;
+		for (; i < 256; i++)
+			d->u.l.lens[i] = 9;
+		for (; i < 280; i++)
+			d->u.l.lens[i] = 7;
+		for (; i < 288; i++)
+			d->u.l.lens[i] = 8;
+
+		for (; i < 288 + 32; i++)
+			d->u.l.lens[i] = 5;
+
+		num_litlen_syms = 288;
+		num_offset_syms = 32;
+	}
+
+	/* Decompressing a Huffman block (either dynamic or static) */
+
+	SAFETY_CHECK(build_offset_decode_table(d, num_litlen_syms, num_offset_syms));
+	SAFETY_CHECK(build_litlen_decode_table(d, num_litlen_syms, num_offset_syms));
+have_decode_tables:
+	litlen_tablemask = BITMASK(d->litlen_tablebits);
+
+	/*
+	 * This is the "fastloop" for decoding literals and matches.  It does
+	 * bounds checks on in_next and out_next in the loop conditions so that
+	 * additional bounds checks aren't needed inside the loop body.
+	 *
+	 * To reduce latency, the bitbuffer is refilled and the next litlen
+	 * decode table entry is preloaded before each loop iteration.
+	 */
+	if (in_next >= in_fastloop_end || out_next >= out_fastloop_end)
+		goto generic_loop;
+	REFILL_BITS_IN_FASTLOOP();
+	entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+	do {
+		u32 length, offset, lit;
+		const u8 *src;
+		u8 *dst;
+
+		/*
+		 * Consume the bits for the litlen decode table entry.  Save the
+		 * original bitbuf for later, in case the extra match length
+		 * bits need to be extracted from it.
+		 */
+		saved_bitbuf = bitbuf;
+		bitbuf >>= (u8)entry;
+		bitsleft -= entry; /* optimization: subtract full entry */
+
+		/*
+		 * Begin by checking for a "fast" literal, i.e. a literal that
+		 * doesn't need a subtable.
+		 */
+		if (entry & HUFFDEC_LITERAL) {
+			/*
+			 * On 64-bit platforms, we decode up to 2 extra fast
+			 * literals in addition to the primary item, as this
+			 * increases performance and still leaves enough bits
+			 * remaining for what follows.  We could actually do 3,
+			 * assuming LITLEN_TABLEBITS=11, but that actually
+			 * decreases performance slightly (perhaps by messing
+			 * with the branch prediction of the conditional refill
+			 * that happens later while decoding the match offset).
+			 *
+			 * Note: the definitions of FASTLOOP_MAX_BYTES_WRITTEN
+			 * and FASTLOOP_MAX_BYTES_READ need to be updated if the
+			 * number of extra literals decoded here is changed.
+			 */
+			if (/* enough bits for 2 fast literals + length + offset preload? */
+			    CAN_CONSUME_AND_THEN_PRELOAD(2 * LITLEN_TABLEBITS +
+							 LENGTH_MAXBITS,
+							 OFFSET_TABLEBITS) &&
+			    /* enough bits for 2 fast literals + slow literal + litlen preload? */
+			    CAN_CONSUME_AND_THEN_PRELOAD(2 * LITLEN_TABLEBITS +
+							 DEFLATE_MAX_LITLEN_CODEWORD_LEN,
+							 LITLEN_TABLEBITS)) {
+				/* 1st extra fast literal */
+				lit = entry >> 16;
+				entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+				saved_bitbuf = bitbuf;
+				bitbuf >>= (u8)entry;
+				bitsleft -= entry;
+				*out_next++ = lit;
+				if (entry & HUFFDEC_LITERAL) {
+					/* 2nd extra fast literal */
+					lit = entry >> 16;
+					entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+					saved_bitbuf = bitbuf;
+					bitbuf >>= (u8)entry;
+					bitsleft -= entry;
+					*out_next++ = lit;
+					if (entry & HUFFDEC_LITERAL) {
+						/*
+						 * Another fast literal, but
+						 * this one is in lieu of the
+						 * primary item, so it doesn't
+						 * count as one of the extras.
+						 */
+						lit = entry >> 16;
+						entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+						REFILL_BITS_IN_FASTLOOP();
+						*out_next++ = lit;
+						continue;
+					}
+				}
+			} else {
+				/*
+				 * Decode a literal.  While doing so, preload
+				 * the next litlen decode table entry and refill
+				 * the bitbuffer.  To reduce latency, we've
+				 * arranged for there to be enough "preloadable"
+				 * bits remaining to do the table preload
+				 * independently of the refill.
+				 */
+				STATIC_ASSERT(CAN_CONSUME_AND_THEN_PRELOAD(
+						LITLEN_TABLEBITS, LITLEN_TABLEBITS));
+				lit = entry >> 16;
+				entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+				REFILL_BITS_IN_FASTLOOP();
+				*out_next++ = lit;
+				continue;
+			}
+		}
+
+		/*
+		 * It's not a literal entry, so it can be a length entry, a
+		 * subtable pointer entry, or an end-of-block entry.  Detect the
+		 * two unlikely cases by testing the HUFFDEC_EXCEPTIONAL flag.
+		 */
+		if (unlikely(entry & HUFFDEC_EXCEPTIONAL)) {
+			/* Subtable pointer or end-of-block entry */
+
+			if (unlikely(entry & HUFFDEC_END_OF_BLOCK))
+				goto block_done;
+
+			/*
+			 * A subtable is required.  Load and consume the
+			 * subtable entry.  The subtable entry can be of any
+			 * type: literal, length, or end-of-block.
+			 */
+			entry = d->u.litlen_decode_table[(entry >> 16) +
+				EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+			saved_bitbuf = bitbuf;
+			bitbuf >>= (u8)entry;
+			bitsleft -= entry;
+
+			/*
+			 * 32-bit platforms that use the byte-at-a-time refill
+			 * method have to do a refill here for there to always
+			 * be enough bits to decode a literal that requires a
+			 * subtable, then preload the next litlen decode table
+			 * entry; or to decode a match length that requires a
+			 * subtable, then preload the offset decode table entry.
+			 */
+			if (!CAN_CONSUME_AND_THEN_PRELOAD(DEFLATE_MAX_LITLEN_CODEWORD_LEN,
+							  LITLEN_TABLEBITS) ||
+			    !CAN_CONSUME_AND_THEN_PRELOAD(LENGTH_MAXBITS,
+							  OFFSET_TABLEBITS))
+				REFILL_BITS_IN_FASTLOOP();
+			if (entry & HUFFDEC_LITERAL) {
+				/* Decode a literal that required a subtable. */
+				lit = entry >> 16;
+				entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+				REFILL_BITS_IN_FASTLOOP();
+				*out_next++ = lit;
+				continue;
+			}
+			if (unlikely(entry & HUFFDEC_END_OF_BLOCK))
+				goto block_done;
+			/* Else, it's a length that required a subtable. */
+		}
+
+		/*
+		 * Decode the match length: the length base value associated
+		 * with the litlen symbol (which we extract from the decode
+		 * table entry), plus the extra length bits.  We don't need to
+		 * consume the extra length bits here, as they were included in
+		 * the bits consumed by the entry earlier.  We also don't need
+		 * to check for too-long matches here, as this is inside the
+		 * fastloop where it's already been verified that the output
+		 * buffer has enough space remaining to copy a max-length match.
+		 */
+		length = entry >> 16;
+		length += EXTRACT_VARBITS8(saved_bitbuf, entry) >> (u8)(entry >> 8);
+
+		/*
+		 * Decode the match offset.  There are enough "preloadable" bits
+		 * remaining to preload the offset decode table entry, but a
+		 * refill might be needed before consuming it.
+		 */
+		STATIC_ASSERT(CAN_CONSUME_AND_THEN_PRELOAD(LENGTH_MAXFASTBITS,
+							   OFFSET_TABLEBITS));
+		entry = d->offset_decode_table[bitbuf & BITMASK(OFFSET_TABLEBITS)];
+		if (CAN_CONSUME_AND_THEN_PRELOAD(OFFSET_MAXBITS,
+						 LITLEN_TABLEBITS)) {
+			/*
+			 * Decoding a match offset on a 64-bit platform.  We may
+			 * need to refill once, but then we can decode the whole
+			 * offset and preload the next litlen table entry.
+			 */
+			if (unlikely(entry & HUFFDEC_EXCEPTIONAL)) {
+				/* Offset codeword requires a subtable */
+				if (unlikely((u8)bitsleft < OFFSET_MAXBITS +
+					     LITLEN_TABLEBITS - PRELOAD_SLACK))
+					REFILL_BITS_IN_FASTLOOP();
+				bitbuf >>= OFFSET_TABLEBITS;
+				bitsleft -= OFFSET_TABLEBITS;
+				entry = d->offset_decode_table[(entry >> 16) +
+					EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+			} else if (unlikely((u8)bitsleft < OFFSET_MAXFASTBITS +
+					    LITLEN_TABLEBITS - PRELOAD_SLACK))
+				REFILL_BITS_IN_FASTLOOP();
+		} else {
+			/* Decoding a match offset on a 32-bit platform */
+			REFILL_BITS_IN_FASTLOOP();
+			if (unlikely(entry & HUFFDEC_EXCEPTIONAL)) {
+				/* Offset codeword requires a subtable */
+				bitbuf >>= OFFSET_TABLEBITS;
+				bitsleft -= OFFSET_TABLEBITS;
+				entry = d->offset_decode_table[(entry >> 16) +
+					EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+				REFILL_BITS_IN_FASTLOOP();
+				/* No further refill needed before extra bits */
+				STATIC_ASSERT(CAN_CONSUME(
+					OFFSET_MAXBITS - OFFSET_TABLEBITS));
+			} else {
+				/* No refill needed before extra bits */
+				STATIC_ASSERT(CAN_CONSUME(OFFSET_MAXFASTBITS));
+			}
+		}
+		saved_bitbuf = bitbuf;
+		bitbuf >>= (u8)entry;
+		bitsleft -= entry; /* optimization: subtract full entry */
+		offset = entry >> 16;
+		offset += EXTRACT_VARBITS8(saved_bitbuf, entry) >> (u8)(entry >> 8);
+
+		/* Validate the match offset; needed even in the fastloop. */
+		SAFETY_CHECK(offset <= out_next - (const u8 *)out);
+		src = out_next - offset;
+		dst = out_next;
+		out_next += length;
+
+		/*
+		 * Before starting to issue the instructions to copy the match,
+		 * refill the bitbuffer and preload the litlen decode table
+		 * entry for the next loop iteration.  This can increase
+		 * performance by allowing the latency of the match copy to
+		 * overlap with these other operations.  To further reduce
+		 * latency, we've arranged for there to be enough bits remaining
+		 * to do the table preload independently of the refill, except
+		 * on 32-bit platforms using the byte-at-a-time refill method.
+		 */
+		if (!CAN_CONSUME_AND_THEN_PRELOAD(
+			MAX(OFFSET_MAXBITS - OFFSET_TABLEBITS,
+			    OFFSET_MAXFASTBITS),
+			LITLEN_TABLEBITS) &&
+		    unlikely((u8)bitsleft < LITLEN_TABLEBITS - PRELOAD_SLACK))
+			REFILL_BITS_IN_FASTLOOP();
+		entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+		REFILL_BITS_IN_FASTLOOP();
+
+		/*
+		 * Copy the match.  On most CPUs the fastest method is a
+		 * word-at-a-time copy, unconditionally copying about 5 words
+		 * since this is enough for most matches without being too much.
+		 *
+		 * The normal word-at-a-time copy works for offset >= WORDBYTES,
+		 * which is most cases.  The case of offset == 1 is also common
+		 * and is worth optimizing for, since it is just RLE encoding of
+		 * the previous byte, which is the result of compressing long
+		 * runs of the same byte.
+		 *
+		 * Writing past the match 'length' is allowed here, since it's
+		 * been ensured there is enough output space left for a slight
+		 * overrun.  FASTLOOP_MAX_BYTES_WRITTEN needs to be updated if
+		 * the maximum possible overrun here is changed.
+		 */
+		if (UNALIGNED_ACCESS_IS_FAST && offset >= WORDBYTES) {
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			while (dst < out_next) {
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+			}
+		} else if (UNALIGNED_ACCESS_IS_FAST && offset == 1) {
+			machine_word_t v;
+
+			/*
+			 * This part tends to get auto-vectorized, so keep it
+			 * copying a multiple of 16 bytes at a time.
+			 */
+			v = (machine_word_t)0x0101010101010101 * src[0];
+			store_word_unaligned(v, dst);
+			dst += WORDBYTES;
+			store_word_unaligned(v, dst);
+			dst += WORDBYTES;
+			store_word_unaligned(v, dst);
+			dst += WORDBYTES;
+			store_word_unaligned(v, dst);
+			dst += WORDBYTES;
+			while (dst < out_next) {
+				store_word_unaligned(v, dst);
+				dst += WORDBYTES;
+				store_word_unaligned(v, dst);
+				dst += WORDBYTES;
+				store_word_unaligned(v, dst);
+				dst += WORDBYTES;
+				store_word_unaligned(v, dst);
+				dst += WORDBYTES;
+			}
+		} else if (UNALIGNED_ACCESS_IS_FAST) {
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += offset;
+			dst += offset;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += offset;
+			dst += offset;
+			do {
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += offset;
+				dst += offset;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += offset;
+				dst += offset;
+			} while (dst < out_next);
+		} else {
+			*dst++ = *src++;
+			*dst++ = *src++;
+			do {
+				*dst++ = *src++;
+			} while (dst < out_next);
+		}
+	} while (in_next < in_fastloop_end && out_next < out_fastloop_end);
+
+	/*
+	 * This is the generic loop for decoding literals and matches.  This
+	 * handles cases where in_next and out_next are close to the end of
+	 * their respective buffers.  Usually this loop isn't performance-
+	 * critical, as most time is spent in the fastloop above instead.  We
+	 * therefore omit some optimizations here in favor of smaller code.
+	 */
+generic_loop:
+	for (;;) {
+		u32 length, offset;
+		const u8 *src;
+		u8 *dst;
+
+		REFILL_BITS();
+		entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+		saved_bitbuf = bitbuf;
+		bitbuf >>= (u8)entry;
+		bitsleft -= entry;
+		if (unlikely(entry & HUFFDEC_SUBTABLE_POINTER)) {
+			entry = d->u.litlen_decode_table[(entry >> 16) +
+					EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+			saved_bitbuf = bitbuf;
+			bitbuf >>= (u8)entry;
+			bitsleft -= entry;
+		}
+		length = entry >> 16;
+		if (entry & HUFFDEC_LITERAL) {
+			if (unlikely(out_next == out_end))
+				return LIBDEFLATE_INSUFFICIENT_SPACE;
+			*out_next++ = length;
+			continue;
+		}
+		if (unlikely(entry & HUFFDEC_END_OF_BLOCK))
+			goto block_done;
+		length += EXTRACT_VARBITS8(saved_bitbuf, entry) >> (u8)(entry >> 8);
+		if (unlikely(length > out_end - out_next))
+			return LIBDEFLATE_INSUFFICIENT_SPACE;
+
+		if (!CAN_CONSUME(LENGTH_MAXBITS + OFFSET_MAXBITS))
+			REFILL_BITS();
+		entry = d->offset_decode_table[bitbuf & BITMASK(OFFSET_TABLEBITS)];
+		if (unlikely(entry & HUFFDEC_EXCEPTIONAL)) {
+			bitbuf >>= OFFSET_TABLEBITS;
+			bitsleft -= OFFSET_TABLEBITS;
+			entry = d->offset_decode_table[(entry >> 16) +
+					EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+			if (!CAN_CONSUME(OFFSET_MAXBITS))
+				REFILL_BITS();
+		}
+		offset = entry >> 16;
+		offset += EXTRACT_VARBITS8(bitbuf, entry) >> (u8)(entry >> 8);
+		bitbuf >>= (u8)entry;
+		bitsleft -= entry;
+
+		SAFETY_CHECK(offset <= out_next - (const u8 *)out);
+		src = out_next - offset;
+		dst = out_next;
+		out_next += length;
+
+		STATIC_ASSERT(DEFLATE_MIN_MATCH_LEN == 3);
+		*dst++ = *src++;
+		*dst++ = *src++;
+		do {
+			*dst++ = *src++;
+		} while (dst < out_next);
+	}
+
+block_done:
+	/* Finished decoding a block */
+
+	if (!is_final_block)
+		goto next_block;
+
+	/* That was the last block. */
+
+	bitsleft = (u8)bitsleft;
+
+	/*
+	 * If any of the implicit appended zero bytes were consumed (not just
+	 * refilled) before hitting end of stream, then the data is bad.
+	 */
+	SAFETY_CHECK(overread_count <= (bitsleft >> 3));
+
+	/* Optionally return the actual number of bytes consumed. */
+	if (actual_in_nbytes_ret) {
+		/* Don't count bytes that were refilled but not consumed. */
+		in_next -= (bitsleft >> 3) - overread_count;
+
+		*actual_in_nbytes_ret = in_next - (u8 *)in;
+	}
+
+	/* Optionally return the actual number of bytes written. */
+	if (actual_out_nbytes_ret) {
+		*actual_out_nbytes_ret = out_next - (u8 *)out;
+	} else {
+		if (out_next != out_end)
+			return LIBDEFLATE_SHORT_OUTPUT;
+	}
+	return LIBDEFLATE_SUCCESS;
+}
+
+#undef FUNCNAME
+#undef ATTRIBUTES
+#undef EXTRACT_VARBITS
+#undef EXTRACT_VARBITS8
+
+
+/* Include architecture-specific implementation(s) if available. */
+#undef DEFAULT_IMPL
+#undef arch_select_decompress_func
+#if defined(ARCH_X86_32) || defined(ARCH_X86_64)
+#ifndef LIB_X86_DECOMPRESS_IMPL_H
+#define LIB_X86_DECOMPRESS_IMPL_H
+
+/*
+ * BMI2 optimized version
+ *
+ * FIXME: with MSVC, this isn't actually compiled with BMI2 code generation
+ * enabled yet.  That would require that this be moved to its own .c file.
+ */
+#if HAVE_BMI2_INTRIN
+#  define deflate_decompress_bmi2	deflate_decompress_bmi2
+#  define FUNCNAME			deflate_decompress_bmi2
+#  if !HAVE_BMI2_NATIVE
+#    define ATTRIBUTES			_target_attribute("bmi2")
+#  endif
+   /*
+    * Even with __attribute__((target("bmi2"))), gcc doesn't reliably use the
+    * bzhi instruction for 'word & BITMASK(count)'.  So use the bzhi intrinsic
+    * explicitly.  EXTRACT_VARBITS() is equivalent to 'word & BITMASK(count)';
+    * EXTRACT_VARBITS8() is equivalent to 'word & BITMASK((u8)count)'.
+    * Nevertheless, their implementation using the bzhi intrinsic is identical,
+    * as the bzhi instruction truncates the count to 8 bits implicitly.
+    */
+#  ifndef __clang__
+#    include <immintrin.h>
+#    ifdef ARCH_X86_64
+#      define EXTRACT_VARBITS(word, count)  _bzhi_u64((word), (count))
+#      define EXTRACT_VARBITS8(word, count) _bzhi_u64((word), (count))
+#    else
+#      define EXTRACT_VARBITS(word, count)  _bzhi_u32((word), (count))
+#      define EXTRACT_VARBITS8(word, count) _bzhi_u32((word), (count))
+#    endif
+#  endif
+/*
+ * decompress_template.h
+ *
+ * Copyright 2016 Eric Biggers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * This is the actual DEFLATE decompression routine, lifted out of
+ * deflate_decompress.c so that it can be compiled multiple times with different
+ * target instruction sets.
+ */
+
+#ifndef ATTRIBUTES
+#  define ATTRIBUTES
+#endif
+#ifndef EXTRACT_VARBITS
+#  define EXTRACT_VARBITS(word, count)	((word) & BITMASK(count))
+#endif
+#ifndef EXTRACT_VARBITS8
+#  define EXTRACT_VARBITS8(word, count)	((word) & BITMASK((u8)(count)))
+#endif
+
+static enum libdeflate_result ATTRIBUTES MAYBE_UNUSED
+FUNCNAME(struct libdeflate_decompressor * restrict d,
+	 const void * restrict in, size_t in_nbytes,
+	 void * restrict out, size_t out_nbytes_avail,
+	 size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret)
+{
+	u8 *out_next = out;
+	u8 * const out_end = out_next + out_nbytes_avail;
+	u8 * const out_fastloop_end =
+		out_end - MIN(out_nbytes_avail, FASTLOOP_MAX_BYTES_WRITTEN);
+
+	/* Input bitstream state; see deflate_decompress.c for documentation */
+	const u8 *in_next = in;
+	const u8 * const in_end = in_next + in_nbytes;
+	const u8 * const in_fastloop_end =
+		in_end - MIN(in_nbytes, FASTLOOP_MAX_BYTES_READ);
+	bitbuf_t bitbuf = 0;
+	bitbuf_t saved_bitbuf;
+	u32 bitsleft = 0;
+	size_t overread_count = 0;
+
+	bool is_final_block;
+	unsigned block_type;
+	unsigned num_litlen_syms;
+	unsigned num_offset_syms;
+	bitbuf_t litlen_tablemask;
+	u32 entry;
+
+next_block:
+	/* Starting to read the next block */
+	;
+
+	STATIC_ASSERT(CAN_CONSUME(1 + 2 + 5 + 5 + 4 + 3));
+	REFILL_BITS();
+
+	/* BFINAL: 1 bit */
+	is_final_block = bitbuf & BITMASK(1);
+
+	/* BTYPE: 2 bits */
+	block_type = (bitbuf >> 1) & BITMASK(2);
+
+	if (block_type == DEFLATE_BLOCKTYPE_DYNAMIC_HUFFMAN) {
+
+		/* Dynamic Huffman block */
+
+		/* The order in which precode lengths are stored */
+		static const u8 deflate_precode_lens_permutation[DEFLATE_NUM_PRECODE_SYMS] = {
+			16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+		};
+
+		unsigned num_explicit_precode_lens;
+		unsigned i;
+
+		/* Read the codeword length counts. */
+
+		STATIC_ASSERT(DEFLATE_NUM_LITLEN_SYMS == 257 + BITMASK(5));
+		num_litlen_syms = 257 + ((bitbuf >> 3) & BITMASK(5));
+
+		STATIC_ASSERT(DEFLATE_NUM_OFFSET_SYMS == 1 + BITMASK(5));
+		num_offset_syms = 1 + ((bitbuf >> 8) & BITMASK(5));
+
+		STATIC_ASSERT(DEFLATE_NUM_PRECODE_SYMS == 4 + BITMASK(4));
+		num_explicit_precode_lens = 4 + ((bitbuf >> 13) & BITMASK(4));
+
+		d->static_codes_loaded = false;
+
+		/*
+		 * Read the precode codeword lengths.
+		 *
+		 * A 64-bit bitbuffer is just one bit too small to hold the
+		 * maximum number of precode lens, so to minimize branches we
+		 * merge one len with the previous fields.
+		 */
+		STATIC_ASSERT(DEFLATE_MAX_PRE_CODEWORD_LEN == (1 << 3) - 1);
+		if (CAN_CONSUME(3 * (DEFLATE_NUM_PRECODE_SYMS - 1))) {
+			d->u.precode_lens[deflate_precode_lens_permutation[0]] =
+				(bitbuf >> 17) & BITMASK(3);
+			bitbuf >>= 20;
+			bitsleft -= 20;
+			REFILL_BITS();
+			i = 1;
+			do {
+				d->u.precode_lens[deflate_precode_lens_permutation[i]] =
+					bitbuf & BITMASK(3);
+				bitbuf >>= 3;
+				bitsleft -= 3;
+			} while (++i < num_explicit_precode_lens);
+		} else {
+			bitbuf >>= 17;
+			bitsleft -= 17;
+			i = 0;
+			do {
+				if ((u8)bitsleft < 3)
+					REFILL_BITS();
+				d->u.precode_lens[deflate_precode_lens_permutation[i]] =
+					bitbuf & BITMASK(3);
+				bitbuf >>= 3;
+				bitsleft -= 3;
+			} while (++i < num_explicit_precode_lens);
+		}
+		for (; i < DEFLATE_NUM_PRECODE_SYMS; i++)
+			d->u.precode_lens[deflate_precode_lens_permutation[i]] = 0;
+
+		/* Build the decode table for the precode. */
+		SAFETY_CHECK(build_precode_decode_table(d));
+
+		/* Decode the litlen and offset codeword lengths. */
+		i = 0;
+		do {
+			unsigned presym;
+			u8 rep_val;
+			unsigned rep_count;
+
+			if ((u8)bitsleft < DEFLATE_MAX_PRE_CODEWORD_LEN + 7)
+				REFILL_BITS();
+
+			/*
+			 * The code below assumes that the precode decode table
+			 * doesn't have any subtables.
+			 */
+			STATIC_ASSERT(PRECODE_TABLEBITS == DEFLATE_MAX_PRE_CODEWORD_LEN);
+
+			/* Decode the next precode symbol. */
+			entry = d->u.l.precode_decode_table[
+				bitbuf & BITMASK(DEFLATE_MAX_PRE_CODEWORD_LEN)];
+			bitbuf >>= (u8)entry;
+			bitsleft -= entry; /* optimization: subtract full entry */
+			presym = entry >> 16;
+
+			if (presym < 16) {
+				/* Explicit codeword length */
+				d->u.l.lens[i++] = presym;
+				continue;
+			}
+
+			/* Run-length encoded codeword lengths */
+
+			/*
+			 * Note: we don't need to immediately verify that the
+			 * repeat count doesn't overflow the number of elements,
+			 * since we've sized the lens array to have enough extra
+			 * space to allow for the worst-case overrun (138 zeroes
+			 * when only 1 length was remaining).
+			 *
+			 * In the case of the small repeat counts (presyms 16
+			 * and 17), it is fastest to always write the maximum
+			 * number of entries.  That gets rid of branches that
+			 * would otherwise be required.
+			 *
+			 * It is not just because of the numerical order that
+			 * our checks go in the order 'presym < 16', 'presym ==
+			 * 16', and 'presym == 17'.  For typical data this is
+			 * ordered from most frequent to least frequent case.
+			 */
+			STATIC_ASSERT(DEFLATE_MAX_LENS_OVERRUN == 138 - 1);
+
+			if (presym == 16) {
+				/* Repeat the previous length 3 - 6 times. */
+				SAFETY_CHECK(i != 0);
+				rep_val = d->u.l.lens[i - 1];
+				STATIC_ASSERT(3 + BITMASK(2) == 6);
+				rep_count = 3 + (bitbuf & BITMASK(2));
+				bitbuf >>= 2;
+				bitsleft -= 2;
+				d->u.l.lens[i + 0] = rep_val;
+				d->u.l.lens[i + 1] = rep_val;
+				d->u.l.lens[i + 2] = rep_val;
+				d->u.l.lens[i + 3] = rep_val;
+				d->u.l.lens[i + 4] = rep_val;
+				d->u.l.lens[i + 5] = rep_val;
+				i += rep_count;
+			} else if (presym == 17) {
+				/* Repeat zero 3 - 10 times. */
+				STATIC_ASSERT(3 + BITMASK(3) == 10);
+				rep_count = 3 + (bitbuf & BITMASK(3));
+				bitbuf >>= 3;
+				bitsleft -= 3;
+				d->u.l.lens[i + 0] = 0;
+				d->u.l.lens[i + 1] = 0;
+				d->u.l.lens[i + 2] = 0;
+				d->u.l.lens[i + 3] = 0;
+				d->u.l.lens[i + 4] = 0;
+				d->u.l.lens[i + 5] = 0;
+				d->u.l.lens[i + 6] = 0;
+				d->u.l.lens[i + 7] = 0;
+				d->u.l.lens[i + 8] = 0;
+				d->u.l.lens[i + 9] = 0;
+				i += rep_count;
+			} else {
+				/* Repeat zero 11 - 138 times. */
+				STATIC_ASSERT(11 + BITMASK(7) == 138);
+				rep_count = 11 + (bitbuf & BITMASK(7));
+				bitbuf >>= 7;
+				bitsleft -= 7;
+				memset(&d->u.l.lens[i], 0,
+				       rep_count * sizeof(d->u.l.lens[i]));
+				i += rep_count;
+			}
+		} while (i < num_litlen_syms + num_offset_syms);
+
+		/* Unnecessary, but check this for consistency with zlib. */
+		SAFETY_CHECK(i == num_litlen_syms + num_offset_syms);
+
+	} else if (block_type == DEFLATE_BLOCKTYPE_UNCOMPRESSED) {
+		u16 len, nlen;
+
+		/*
+		 * Uncompressed block: copy 'len' bytes literally from the input
+		 * buffer to the output buffer.
+		 */
+
+		bitsleft -= 3; /* for BTYPE and BFINAL */
+
+		/*
+		 * Align the bitstream to the next byte boundary.  This means
+		 * the next byte boundary as if we were reading a byte at a
+		 * time.  Therefore, we have to rewind 'in_next' by any bytes
+		 * that have been refilled but not actually consumed yet (not
+		 * counting overread bytes, which don't increment 'in_next').
+		 */
+		bitsleft = (u8)bitsleft;
+		SAFETY_CHECK(overread_count <= (bitsleft >> 3));
+		in_next -= (bitsleft >> 3) - overread_count;
+		overread_count = 0;
+		bitbuf = 0;
+		bitsleft = 0;
+
+		SAFETY_CHECK(in_end - in_next >= 4);
+		len = get_unaligned_le16(in_next);
+		nlen = get_unaligned_le16(in_next + 2);
+		in_next += 4;
+
+		SAFETY_CHECK(len == (u16)~nlen);
+		if (unlikely(len > out_end - out_next))
+			return LIBDEFLATE_INSUFFICIENT_SPACE;
+		SAFETY_CHECK(len <= in_end - in_next);
+
+		memcpy(out_next, in_next, len);
+		in_next += len;
+		out_next += len;
+
+		goto block_done;
+
+	} else {
+		unsigned i;
+
+		SAFETY_CHECK(block_type == DEFLATE_BLOCKTYPE_STATIC_HUFFMAN);
+
+		/*
+		 * Static Huffman block: build the decode tables for the static
+		 * codes.  Skip doing so if the tables are already set up from
+		 * an earlier static block; this speeds up decompression of
+		 * degenerate input of many empty or very short static blocks.
+		 *
+		 * Afterwards, the remainder is the same as decompressing a
+		 * dynamic Huffman block.
+		 */
+
+		bitbuf >>= 3; /* for BTYPE and BFINAL */
+		bitsleft -= 3;
+
+		if (d->static_codes_loaded)
+			goto have_decode_tables;
+
+		d->static_codes_loaded = true;
+
+		STATIC_ASSERT(DEFLATE_NUM_LITLEN_SYMS == 288);
+		STATIC_ASSERT(DEFLATE_NUM_OFFSET_SYMS == 32);
+
+		for (i = 0; i < 144; i++)
+			d->u.l.lens[i] = 8;
+		for (; i < 256; i++)
+			d->u.l.lens[i] = 9;
+		for (; i < 280; i++)
+			d->u.l.lens[i] = 7;
+		for (; i < 288; i++)
+			d->u.l.lens[i] = 8;
+
+		for (; i < 288 + 32; i++)
+			d->u.l.lens[i] = 5;
+
+		num_litlen_syms = 288;
+		num_offset_syms = 32;
+	}
+
+	/* Decompressing a Huffman block (either dynamic or static) */
+
+	SAFETY_CHECK(build_offset_decode_table(d, num_litlen_syms, num_offset_syms));
+	SAFETY_CHECK(build_litlen_decode_table(d, num_litlen_syms, num_offset_syms));
+have_decode_tables:
+	litlen_tablemask = BITMASK(d->litlen_tablebits);
+
+	/*
+	 * This is the "fastloop" for decoding literals and matches.  It does
+	 * bounds checks on in_next and out_next in the loop conditions so that
+	 * additional bounds checks aren't needed inside the loop body.
+	 *
+	 * To reduce latency, the bitbuffer is refilled and the next litlen
+	 * decode table entry is preloaded before each loop iteration.
+	 */
+	if (in_next >= in_fastloop_end || out_next >= out_fastloop_end)
+		goto generic_loop;
+	REFILL_BITS_IN_FASTLOOP();
+	entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+	do {
+		u32 length, offset, lit;
+		const u8 *src;
+		u8 *dst;
+
+		/*
+		 * Consume the bits for the litlen decode table entry.  Save the
+		 * original bitbuf for later, in case the extra match length
+		 * bits need to be extracted from it.
+		 */
+		saved_bitbuf = bitbuf;
+		bitbuf >>= (u8)entry;
+		bitsleft -= entry; /* optimization: subtract full entry */
+
+		/*
+		 * Begin by checking for a "fast" literal, i.e. a literal that
+		 * doesn't need a subtable.
+		 */
+		if (entry & HUFFDEC_LITERAL) {
+			/*
+			 * On 64-bit platforms, we decode up to 2 extra fast
+			 * literals in addition to the primary item, as this
+			 * increases performance and still leaves enough bits
+			 * remaining for what follows.  We could actually do 3,
+			 * assuming LITLEN_TABLEBITS=11, but that actually
+			 * decreases performance slightly (perhaps by messing
+			 * with the branch prediction of the conditional refill
+			 * that happens later while decoding the match offset).
+			 *
+			 * Note: the definitions of FASTLOOP_MAX_BYTES_WRITTEN
+			 * and FASTLOOP_MAX_BYTES_READ need to be updated if the
+			 * number of extra literals decoded here is changed.
+			 */
+			if (/* enough bits for 2 fast literals + length + offset preload? */
+			    CAN_CONSUME_AND_THEN_PRELOAD(2 * LITLEN_TABLEBITS +
+							 LENGTH_MAXBITS,
+							 OFFSET_TABLEBITS) &&
+			    /* enough bits for 2 fast literals + slow literal + litlen preload? */
+			    CAN_CONSUME_AND_THEN_PRELOAD(2 * LITLEN_TABLEBITS +
+							 DEFLATE_MAX_LITLEN_CODEWORD_LEN,
+							 LITLEN_TABLEBITS)) {
+				/* 1st extra fast literal */
+				lit = entry >> 16;
+				entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+				saved_bitbuf = bitbuf;
+				bitbuf >>= (u8)entry;
+				bitsleft -= entry;
+				*out_next++ = lit;
+				if (entry & HUFFDEC_LITERAL) {
+					/* 2nd extra fast literal */
+					lit = entry >> 16;
+					entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+					saved_bitbuf = bitbuf;
+					bitbuf >>= (u8)entry;
+					bitsleft -= entry;
+					*out_next++ = lit;
+					if (entry & HUFFDEC_LITERAL) {
+						/*
+						 * Another fast literal, but
+						 * this one is in lieu of the
+						 * primary item, so it doesn't
+						 * count as one of the extras.
+						 */
+						lit = entry >> 16;
+						entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+						REFILL_BITS_IN_FASTLOOP();
+						*out_next++ = lit;
+						continue;
+					}
+				}
+			} else {
+				/*
+				 * Decode a literal.  While doing so, preload
+				 * the next litlen decode table entry and refill
+				 * the bitbuffer.  To reduce latency, we've
+				 * arranged for there to be enough "preloadable"
+				 * bits remaining to do the table preload
+				 * independently of the refill.
+				 */
+				STATIC_ASSERT(CAN_CONSUME_AND_THEN_PRELOAD(
+						LITLEN_TABLEBITS, LITLEN_TABLEBITS));
+				lit = entry >> 16;
+				entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+				REFILL_BITS_IN_FASTLOOP();
+				*out_next++ = lit;
+				continue;
+			}
+		}
+
+		/*
+		 * It's not a literal entry, so it can be a length entry, a
+		 * subtable pointer entry, or an end-of-block entry.  Detect the
+		 * two unlikely cases by testing the HUFFDEC_EXCEPTIONAL flag.
+		 */
+		if (unlikely(entry & HUFFDEC_EXCEPTIONAL)) {
+			/* Subtable pointer or end-of-block entry */
+
+			if (unlikely(entry & HUFFDEC_END_OF_BLOCK))
+				goto block_done;
+
+			/*
+			 * A subtable is required.  Load and consume the
+			 * subtable entry.  The subtable entry can be of any
+			 * type: literal, length, or end-of-block.
+			 */
+			entry = d->u.litlen_decode_table[(entry >> 16) +
+				EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+			saved_bitbuf = bitbuf;
+			bitbuf >>= (u8)entry;
+			bitsleft -= entry;
+
+			/*
+			 * 32-bit platforms that use the byte-at-a-time refill
+			 * method have to do a refill here for there to always
+			 * be enough bits to decode a literal that requires a
+			 * subtable, then preload the next litlen decode table
+			 * entry; or to decode a match length that requires a
+			 * subtable, then preload the offset decode table entry.
+			 */
+			if (!CAN_CONSUME_AND_THEN_PRELOAD(DEFLATE_MAX_LITLEN_CODEWORD_LEN,
+							  LITLEN_TABLEBITS) ||
+			    !CAN_CONSUME_AND_THEN_PRELOAD(LENGTH_MAXBITS,
+							  OFFSET_TABLEBITS))
+				REFILL_BITS_IN_FASTLOOP();
+			if (entry & HUFFDEC_LITERAL) {
+				/* Decode a literal that required a subtable. */
+				lit = entry >> 16;
+				entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+				REFILL_BITS_IN_FASTLOOP();
+				*out_next++ = lit;
+				continue;
+			}
+			if (unlikely(entry & HUFFDEC_END_OF_BLOCK))
+				goto block_done;
+			/* Else, it's a length that required a subtable. */
+		}
+
+		/*
+		 * Decode the match length: the length base value associated
+		 * with the litlen symbol (which we extract from the decode
+		 * table entry), plus the extra length bits.  We don't need to
+		 * consume the extra length bits here, as they were included in
+		 * the bits consumed by the entry earlier.  We also don't need
+		 * to check for too-long matches here, as this is inside the
+		 * fastloop where it's already been verified that the output
+		 * buffer has enough space remaining to copy a max-length match.
+		 */
+		length = entry >> 16;
+		length += EXTRACT_VARBITS8(saved_bitbuf, entry) >> (u8)(entry >> 8);
+
+		/*
+		 * Decode the match offset.  There are enough "preloadable" bits
+		 * remaining to preload the offset decode table entry, but a
+		 * refill might be needed before consuming it.
+		 */
+		STATIC_ASSERT(CAN_CONSUME_AND_THEN_PRELOAD(LENGTH_MAXFASTBITS,
+							   OFFSET_TABLEBITS));
+		entry = d->offset_decode_table[bitbuf & BITMASK(OFFSET_TABLEBITS)];
+		if (CAN_CONSUME_AND_THEN_PRELOAD(OFFSET_MAXBITS,
+						 LITLEN_TABLEBITS)) {
+			/*
+			 * Decoding a match offset on a 64-bit platform.  We may
+			 * need to refill once, but then we can decode the whole
+			 * offset and preload the next litlen table entry.
+			 */
+			if (unlikely(entry & HUFFDEC_EXCEPTIONAL)) {
+				/* Offset codeword requires a subtable */
+				if (unlikely((u8)bitsleft < OFFSET_MAXBITS +
+					     LITLEN_TABLEBITS - PRELOAD_SLACK))
+					REFILL_BITS_IN_FASTLOOP();
+				bitbuf >>= OFFSET_TABLEBITS;
+				bitsleft -= OFFSET_TABLEBITS;
+				entry = d->offset_decode_table[(entry >> 16) +
+					EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+			} else if (unlikely((u8)bitsleft < OFFSET_MAXFASTBITS +
+					    LITLEN_TABLEBITS - PRELOAD_SLACK))
+				REFILL_BITS_IN_FASTLOOP();
+		} else {
+			/* Decoding a match offset on a 32-bit platform */
+			REFILL_BITS_IN_FASTLOOP();
+			if (unlikely(entry & HUFFDEC_EXCEPTIONAL)) {
+				/* Offset codeword requires a subtable */
+				bitbuf >>= OFFSET_TABLEBITS;
+				bitsleft -= OFFSET_TABLEBITS;
+				entry = d->offset_decode_table[(entry >> 16) +
+					EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+				REFILL_BITS_IN_FASTLOOP();
+				/* No further refill needed before extra bits */
+				STATIC_ASSERT(CAN_CONSUME(
+					OFFSET_MAXBITS - OFFSET_TABLEBITS));
+			} else {
+				/* No refill needed before extra bits */
+				STATIC_ASSERT(CAN_CONSUME(OFFSET_MAXFASTBITS));
+			}
+		}
+		saved_bitbuf = bitbuf;
+		bitbuf >>= (u8)entry;
+		bitsleft -= entry; /* optimization: subtract full entry */
+		offset = entry >> 16;
+		offset += EXTRACT_VARBITS8(saved_bitbuf, entry) >> (u8)(entry >> 8);
+
+		/* Validate the match offset; needed even in the fastloop. */
+		SAFETY_CHECK(offset <= out_next - (const u8 *)out);
+		src = out_next - offset;
+		dst = out_next;
+		out_next += length;
+
+		/*
+		 * Before starting to issue the instructions to copy the match,
+		 * refill the bitbuffer and preload the litlen decode table
+		 * entry for the next loop iteration.  This can increase
+		 * performance by allowing the latency of the match copy to
+		 * overlap with these other operations.  To further reduce
+		 * latency, we've arranged for there to be enough bits remaining
+		 * to do the table preload independently of the refill, except
+		 * on 32-bit platforms using the byte-at-a-time refill method.
+		 */
+		if (!CAN_CONSUME_AND_THEN_PRELOAD(
+			MAX(OFFSET_MAXBITS - OFFSET_TABLEBITS,
+			    OFFSET_MAXFASTBITS),
+			LITLEN_TABLEBITS) &&
+		    unlikely((u8)bitsleft < LITLEN_TABLEBITS - PRELOAD_SLACK))
+			REFILL_BITS_IN_FASTLOOP();
+		entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+		REFILL_BITS_IN_FASTLOOP();
+
+		/*
+		 * Copy the match.  On most CPUs the fastest method is a
+		 * word-at-a-time copy, unconditionally copying about 5 words
+		 * since this is enough for most matches without being too much.
+		 *
+		 * The normal word-at-a-time copy works for offset >= WORDBYTES,
+		 * which is most cases.  The case of offset == 1 is also common
+		 * and is worth optimizing for, since it is just RLE encoding of
+		 * the previous byte, which is the result of compressing long
+		 * runs of the same byte.
+		 *
+		 * Writing past the match 'length' is allowed here, since it's
+		 * been ensured there is enough output space left for a slight
+		 * overrun.  FASTLOOP_MAX_BYTES_WRITTEN needs to be updated if
+		 * the maximum possible overrun here is changed.
+		 */
+		if (UNALIGNED_ACCESS_IS_FAST && offset >= WORDBYTES) {
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += WORDBYTES;
+			dst += WORDBYTES;
+			while (dst < out_next) {
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += WORDBYTES;
+				dst += WORDBYTES;
+			}
+		} else if (UNALIGNED_ACCESS_IS_FAST && offset == 1) {
+			machine_word_t v;
+
+			/*
+			 * This part tends to get auto-vectorized, so keep it
+			 * copying a multiple of 16 bytes at a time.
+			 */
+			v = (machine_word_t)0x0101010101010101 * src[0];
+			store_word_unaligned(v, dst);
+			dst += WORDBYTES;
+			store_word_unaligned(v, dst);
+			dst += WORDBYTES;
+			store_word_unaligned(v, dst);
+			dst += WORDBYTES;
+			store_word_unaligned(v, dst);
+			dst += WORDBYTES;
+			while (dst < out_next) {
+				store_word_unaligned(v, dst);
+				dst += WORDBYTES;
+				store_word_unaligned(v, dst);
+				dst += WORDBYTES;
+				store_word_unaligned(v, dst);
+				dst += WORDBYTES;
+				store_word_unaligned(v, dst);
+				dst += WORDBYTES;
+			}
+		} else if (UNALIGNED_ACCESS_IS_FAST) {
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += offset;
+			dst += offset;
+			store_word_unaligned(load_word_unaligned(src), dst);
+			src += offset;
+			dst += offset;
+			do {
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += offset;
+				dst += offset;
+				store_word_unaligned(load_word_unaligned(src), dst);
+				src += offset;
+				dst += offset;
+			} while (dst < out_next);
+		} else {
+			*dst++ = *src++;
+			*dst++ = *src++;
+			do {
+				*dst++ = *src++;
+			} while (dst < out_next);
+		}
+	} while (in_next < in_fastloop_end && out_next < out_fastloop_end);
+
+	/*
+	 * This is the generic loop for decoding literals and matches.  This
+	 * handles cases where in_next and out_next are close to the end of
+	 * their respective buffers.  Usually this loop isn't performance-
+	 * critical, as most time is spent in the fastloop above instead.  We
+	 * therefore omit some optimizations here in favor of smaller code.
+	 */
+generic_loop:
+	for (;;) {
+		u32 length, offset;
+		const u8 *src;
+		u8 *dst;
+
+		REFILL_BITS();
+		entry = d->u.litlen_decode_table[bitbuf & litlen_tablemask];
+		saved_bitbuf = bitbuf;
+		bitbuf >>= (u8)entry;
+		bitsleft -= entry;
+		if (unlikely(entry & HUFFDEC_SUBTABLE_POINTER)) {
+			entry = d->u.litlen_decode_table[(entry >> 16) +
+					EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+			saved_bitbuf = bitbuf;
+			bitbuf >>= (u8)entry;
+			bitsleft -= entry;
+		}
+		length = entry >> 16;
+		if (entry & HUFFDEC_LITERAL) {
+			if (unlikely(out_next == out_end))
+				return LIBDEFLATE_INSUFFICIENT_SPACE;
+			*out_next++ = length;
+			continue;
+		}
+		if (unlikely(entry & HUFFDEC_END_OF_BLOCK))
+			goto block_done;
+		length += EXTRACT_VARBITS8(saved_bitbuf, entry) >> (u8)(entry >> 8);
+		if (unlikely(length > out_end - out_next))
+			return LIBDEFLATE_INSUFFICIENT_SPACE;
+
+		if (!CAN_CONSUME(LENGTH_MAXBITS + OFFSET_MAXBITS))
+			REFILL_BITS();
+		entry = d->offset_decode_table[bitbuf & BITMASK(OFFSET_TABLEBITS)];
+		if (unlikely(entry & HUFFDEC_EXCEPTIONAL)) {
+			bitbuf >>= OFFSET_TABLEBITS;
+			bitsleft -= OFFSET_TABLEBITS;
+			entry = d->offset_decode_table[(entry >> 16) +
+					EXTRACT_VARBITS(bitbuf, (entry >> 8) & 0x3F)];
+			if (!CAN_CONSUME(OFFSET_MAXBITS))
+				REFILL_BITS();
+		}
+		offset = entry >> 16;
+		offset += EXTRACT_VARBITS8(bitbuf, entry) >> (u8)(entry >> 8);
+		bitbuf >>= (u8)entry;
+		bitsleft -= entry;
+
+		SAFETY_CHECK(offset <= out_next - (const u8 *)out);
+		src = out_next - offset;
+		dst = out_next;
+		out_next += length;
+
+		STATIC_ASSERT(DEFLATE_MIN_MATCH_LEN == 3);
+		*dst++ = *src++;
+		*dst++ = *src++;
+		do {
+			*dst++ = *src++;
+		} while (dst < out_next);
+	}
+
+block_done:
+	/* Finished decoding a block */
+
+	if (!is_final_block)
+		goto next_block;
+
+	/* That was the last block. */
+
+	bitsleft = (u8)bitsleft;
+
+	/*
+	 * If any of the implicit appended zero bytes were consumed (not just
+	 * refilled) before hitting end of stream, then the data is bad.
+	 */
+	SAFETY_CHECK(overread_count <= (bitsleft >> 3));
+
+	/* Optionally return the actual number of bytes consumed. */
+	if (actual_in_nbytes_ret) {
+		/* Don't count bytes that were refilled but not consumed. */
+		in_next -= (bitsleft >> 3) - overread_count;
+
+		*actual_in_nbytes_ret = in_next - (u8 *)in;
+	}
+
+	/* Optionally return the actual number of bytes written. */
+	if (actual_out_nbytes_ret) {
+		*actual_out_nbytes_ret = out_next - (u8 *)out;
+	} else {
+		if (out_next != out_end)
+			return LIBDEFLATE_SHORT_OUTPUT;
+	}
+	return LIBDEFLATE_SUCCESS;
+}
+
+#undef FUNCNAME
+#undef ATTRIBUTES
+#undef EXTRACT_VARBITS
+#undef EXTRACT_VARBITS8
+
+#endif /* HAVE_BMI2_INTRIN */
+
+#if defined(deflate_decompress_bmi2) && HAVE_BMI2_NATIVE
+#define DEFAULT_IMPL	deflate_decompress_bmi2
+#else
+static inline decompress_func_t
+arch_select_decompress_func(void)
+{
+#ifdef deflate_decompress_bmi2
+	if (HAVE_BMI2(get_x86_cpu_features()))
+		return deflate_decompress_bmi2;
+#endif
+	return NULL;
+}
+#define arch_select_decompress_func	arch_select_decompress_func
+#endif
+
+#endif /* LIB_X86_DECOMPRESS_IMPL_H */
+
+#endif
+
+#ifndef DEFAULT_IMPL
+#  define DEFAULT_IMPL deflate_decompress_default
+#endif
+
+#ifdef arch_select_decompress_func
+static enum libdeflate_result
+dispatch_decomp(struct libdeflate_decompressor *d,
+		const void *in, size_t in_nbytes,
+		void *out, size_t out_nbytes_avail,
+		size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret);
+
+static volatile decompress_func_t decompress_impl = dispatch_decomp;
+
+/* Choose the best implementation at runtime. */
+static enum libdeflate_result
+dispatch_decomp(struct libdeflate_decompressor *d,
+		const void *in, size_t in_nbytes,
+		void *out, size_t out_nbytes_avail,
+		size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret)
+{
+	decompress_func_t f = arch_select_decompress_func();
+
+	if (f == NULL)
+		f = DEFAULT_IMPL;
+
+	decompress_impl = f;
+	return f(d, in, in_nbytes, out, out_nbytes_avail,
+		 actual_in_nbytes_ret, actual_out_nbytes_ret);
+}
+#else
+/* The best implementation is statically known, so call it directly. */
+#  define decompress_impl DEFAULT_IMPL
+#endif
+
+/*
+ * This is the main DEFLATE decompression routine.  See libdeflate.h for the
+ * documentation.
+ *
+ * Note that the real code is in decompress_template.h.  The part here just
+ * handles calling the appropriate implementation depending on the CPU features
+ * at runtime.
+ */
+LIBDEFLATEAPI enum libdeflate_result
+libdeflate_deflate_decompress_ex(struct libdeflate_decompressor *d,
+				 const void *in, size_t in_nbytes,
+				 void *out, size_t out_nbytes_avail,
+				 size_t *actual_in_nbytes_ret,
+				 size_t *actual_out_nbytes_ret)
+{
+	return decompress_impl(d, in, in_nbytes, out, out_nbytes_avail,
+			       actual_in_nbytes_ret, actual_out_nbytes_ret);
+}
+
+LIBDEFLATEAPI enum libdeflate_result
+libdeflate_deflate_decompress(struct libdeflate_decompressor *d,
+			      const void *in, size_t in_nbytes,
+			      void *out, size_t out_nbytes_avail,
+			      size_t *actual_out_nbytes_ret)
+{
+	return libdeflate_deflate_decompress_ex(d, in, in_nbytes,
+						out, out_nbytes_avail,
+						NULL, actual_out_nbytes_ret);
+}
+
+LIBDEFLATEAPI struct libdeflate_decompressor *
+libdeflate_alloc_decompressor_ex(const struct libdeflate_options *options)
+{
+	struct libdeflate_decompressor *d;
+
+	/*
+	 * Note: if more fields are added to libdeflate_options, this code will
+	 * need to be updated to support both the old and new structs.
+	 */
+	if (options->sizeof_options != sizeof(*options))
+		return NULL;
+
+	d = (options->malloc_func ? options->malloc_func :
+	     libdeflate_default_malloc_func)(sizeof(*d));
+	if (d == NULL)
+		return NULL;
+	/*
+	 * Note that only certain parts of the decompressor actually must be
+	 * initialized here:
+	 *
+	 * - 'static_codes_loaded' must be initialized to false.
+	 *
+	 * - The first half of the main portion of each decode table must be
+	 *   initialized to any value, to avoid reading from uninitialized
+	 *   memory during table expansion in build_decode_table().  (Although,
+	 *   this is really just to avoid warnings with dynamic tools like
+	 *   valgrind, since build_decode_table() is guaranteed to initialize
+	 *   all entries eventually anyway.)
+	 *
+	 * - 'free_func' must be set.
+	 *
+	 * But for simplicity, we currently just zero the whole decompressor.
+	 */
+	memset(d, 0, sizeof(*d));
+	d->free_func = options->free_func ?
+		       options->free_func : libdeflate_default_free_func;
+	return d;
+}
+
+LIBDEFLATEAPI struct libdeflate_decompressor *
+libdeflate_alloc_decompressor(void)
+{
+	static const struct libdeflate_options defaults = {
+		.sizeof_options = sizeof(defaults),
+	};
+	return libdeflate_alloc_decompressor_ex(&defaults);
+}
+
+LIBDEFLATEAPI void
+libdeflate_free_decompressor(struct libdeflate_decompressor *d)
+{
+	if (d)
+		d->free_func(d);
+}
+
+
+/*
+ * utils.c - utility functions for libdeflate
+ *
+ * Copyright 2016 Eric Biggers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifdef FREESTANDING
+#  define malloc NULL
+#  define free NULL
+#else
+#  include <stdlib.h>
+#endif
+
+malloc_func_t libdeflate_default_malloc_func = malloc;
+free_func_t libdeflate_default_free_func = free;
+
+void *
+libdeflate_aligned_malloc(malloc_func_t malloc_func,
+			  size_t alignment, size_t size)
+{
+	void *ptr = (*malloc_func)(sizeof(void *) + alignment - 1 + size);
+
+	if (ptr) {
+		void *orig_ptr = ptr;
+
+		ptr = (void *)ALIGN((uintptr_t)ptr + sizeof(void *), alignment);
+		((void **)ptr)[-1] = orig_ptr;
+	}
+	return ptr;
+}
+
+void
+libdeflate_aligned_free(free_func_t free_func, void *ptr)
+{
+	(*free_func)(((void **)ptr)[-1]);
+}
+
+LIBDEFLATEAPI void
+libdeflate_set_memory_allocator(malloc_func_t malloc_func,
+				free_func_t free_func)
+{
+	libdeflate_default_malloc_func = malloc_func;
+	libdeflate_default_free_func = free_func;
+}
+
+/*
+ * Implementations of libc functions for freestanding library builds.
+ * Normal library builds don't use these.  Not optimized yet; usually the
+ * compiler expands these functions and doesn't actually call them anyway.
+ */
+#ifdef FREESTANDING
+#undef memset
+void * __attribute__((weak))
+memset(void *s, int c, size_t n)
+{
+	u8 *p = s;
+	size_t i;
+
+	for (i = 0; i < n; i++)
+		p[i] = c;
+	return s;
+}
+
+#undef memcpy
+void * __attribute__((weak))
+memcpy(void *dest, const void *src, size_t n)
+{
+	u8 *d = dest;
+	const u8 *s = src;
+	size_t i;
+
+	for (i = 0; i < n; i++)
+		d[i] = s[i];
+	return dest;
+}
+
+#undef memmove
+void * __attribute__((weak))
+memmove(void *dest, const void *src, size_t n)
+{
+	u8 *d = dest;
+	const u8 *s = src;
+	size_t i;
+
+	if (d <= s)
+		return memcpy(d, s, n);
+
+	for (i = n; i > 0; i--)
+		d[i - 1] = s[i - 1];
+	return dest;
+}
+
+#undef memcmp
+int __attribute__((weak))
+memcmp(const void *s1, const void *s2, size_t n)
+{
+	const u8 *p1 = s1;
+	const u8 *p2 = s2;
+	size_t i;
+
+	for (i = 0; i < n; i++) {
+		if (p1[i] != p2[i])
+			return (int)p1[i] - (int)p2[i];
+	}
+	return 0;
+}
+#endif /* FREESTANDING */
+
+#ifdef LIBDEFLATE_ENABLE_ASSERTIONS
+#include <stdio.h>
+#include <stdlib.h>
+void
+libdeflate_assertion_failed(const char *expr, const char *file, int line)
+{
+	fprintf(stderr, "Assertion failed: %s at %s:%d\n", expr, file, line);
+	abort();
+}
+#endif /* LIBDEFLATE_ENABLE_ASSERTIONS */
+
+/*
+ * x86/cpu_features.c - feature detection for x86 CPUs
+ *
+ * Copyright 2016 Eric Biggers
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_DYNAMIC_X86_CPU_FEATURES
+
+/*
+ * With old GCC versions we have to manually save and restore the x86_32 PIC
+ * register (ebx).  See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47602
+ */
+#if defined(ARCH_X86_32) && defined(__PIC__)
+#  define EBX_CONSTRAINT "=&r"
+#else
+#  define EBX_CONSTRAINT "=b"
+#endif
+
+/* Execute the CPUID instruction. */
+static inline void
+cpuid(u32 leaf, u32 subleaf, u32 *a, u32 *b, u32 *c, u32 *d)
+{
+#ifdef _MSC_VER
+	int result[4];
+
+	__cpuidex(result, leaf, subleaf);
+	*a = result[0];
+	*b = result[1];
+	*c = result[2];
+	*d = result[3];
+#else
+	__asm__ volatile(".ifnc %%ebx, %1; mov  %%ebx, %1; .endif\n"
+			 "cpuid                                  \n"
+			 ".ifnc %%ebx, %1; xchg %%ebx, %1; .endif\n"
+			 : "=a" (*a), EBX_CONSTRAINT (*b), "=c" (*c), "=d" (*d)
+			 : "a" (leaf), "c" (subleaf));
+#endif
+}
+
+/* Read an extended control register. */
+static inline u64
+read_xcr(u32 index)
+{
+#ifdef _MSC_VER
+	return _xgetbv(index);
+#else
+	u32 d, a;
+
+	/*
+	 * Execute the "xgetbv" instruction.  Old versions of binutils do not
+	 * recognize this instruction, so list the raw bytes instead.
+	 *
+	 * This must be 'volatile' to prevent this code from being moved out
+	 * from under the check for OSXSAVE.
+	 */
+	__asm__ volatile(".byte 0x0f, 0x01, 0xd0" :
+			 "=d" (d), "=a" (a) : "c" (index));
+
+	return ((u64)d << 32) | a;
+#endif
+}
+
+static const struct cpu_feature x86_cpu_feature_table[] = {
+	{X86_CPU_FEATURE_SSE2,		"sse2"},
+	{X86_CPU_FEATURE_PCLMUL,	"pclmul"},
+	{X86_CPU_FEATURE_AVX,		"avx"},
+	{X86_CPU_FEATURE_AVX2,		"avx2"},
+	{X86_CPU_FEATURE_BMI2,		"bmi2"},
+};
+
+volatile u32 libdeflate_x86_cpu_features = 0;
+
+/* Initialize libdeflate_x86_cpu_features. */
+void libdeflate_init_x86_cpu_features(void)
+{
+	u32 max_leaf, a, b, c, d;
+	u64 xcr0 = 0;
+	u32 features = 0;
+
+	/* EAX=0: Highest Function Parameter and Manufacturer ID */
+	cpuid(0, 0, &max_leaf, &b, &c, &d);
+	if (max_leaf < 1)
+		goto out;
+
+	/* EAX=1: Processor Info and Feature Bits */
+	cpuid(1, 0, &a, &b, &c, &d);
+	if (d & (1 << 26))
+		features |= X86_CPU_FEATURE_SSE2;
+	if (c & (1 << 1))
+		features |= X86_CPU_FEATURE_PCLMUL;
+	if (c & (1 << 27))
+		xcr0 = read_xcr(0);
+	if ((c & (1 << 28)) && ((xcr0 & 0x6) == 0x6))
+		features |= X86_CPU_FEATURE_AVX;
+
+	if (max_leaf < 7)
+		goto out;
+
+	/* EAX=7, ECX=0: Extended Features */
+	cpuid(7, 0, &a, &b, &c, &d);
+	if ((b & (1 << 5)) && ((xcr0 & 0x6) == 0x6))
+		features |= X86_CPU_FEATURE_AVX2;
+	if (b & (1 << 8))
+		features |= X86_CPU_FEATURE_BMI2;
+
+out:
+	disable_cpu_features_for_testing(&features, x86_cpu_feature_table,
+					 ARRAY_LEN(x86_cpu_feature_table));
+
+	libdeflate_x86_cpu_features = features | X86_CPU_FEATURES_KNOWN;
+}
+
+#endif /* HAVE_DYNAMIC_X86_CPU_FEATURES */
diff --git a/source/fbxplugin/libdeflate.h b/source/fbxplugin/libdeflate.h
new file mode 100644
index 0000000000000000000000000000000000000000..382d895de89c237960a6b4d8a5ba32013e67ba53
--- /dev/null
+++ b/source/fbxplugin/libdeflate.h
@@ -0,0 +1,411 @@
+/*
+ * libdeflate.h - public header for libdeflate
+ */
+
+#ifndef LIBDEFLATE_H
+#define LIBDEFLATE_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LIBDEFLATE_VERSION_MAJOR	1
+#define LIBDEFLATE_VERSION_MINOR	18
+#define LIBDEFLATE_VERSION_STRING	"1.18"
+
+/*
+ * Users of libdeflate.dll on Windows can define LIBDEFLATE_DLL to cause
+ * __declspec(dllimport) to be used.  This should be done when it's easy to do.
+ * Otherwise it's fine to skip it, since it is a very minor performance
+ * optimization that is irrelevant for most use cases of libdeflate.
+ */
+#ifndef LIBDEFLATEAPI
+#  if defined(LIBDEFLATE_DLL) && (defined(_WIN32) || defined(__CYGWIN__))
+#    define LIBDEFLATEAPI	__declspec(dllimport)
+#  else
+#    define LIBDEFLATEAPI
+#  endif
+#endif
+
+/* ========================================================================== */
+/*                             Compression                                    */
+/* ========================================================================== */
+
+struct libdeflate_compressor;
+struct libdeflate_options;
+
+/*
+ * libdeflate_alloc_compressor() allocates a new compressor that supports
+ * DEFLATE, zlib, and gzip compression.  'compression_level' is the compression
+ * level on a zlib-like scale but with a higher maximum value (1 = fastest, 6 =
+ * medium/default, 9 = slow, 12 = slowest).  Level 0 is also supported and means
+ * "no compression", specifically "create a valid stream, but only emit
+ * uncompressed blocks" (this will expand the data slightly).
+ *
+ * The return value is a pointer to the new compressor, or NULL if out of memory
+ * or if the compression level is invalid (i.e. outside the range [0, 12]).
+ *
+ * Note: for compression, the sliding window size is defined at compilation time
+ * to 32768, the largest size permissible in the DEFLATE format.  It cannot be
+ * changed at runtime.
+ *
+ * A single compressor is not safe to use by multiple threads concurrently.
+ * However, different threads may use different compressors concurrently.
+ */
+LIBDEFLATEAPI struct libdeflate_compressor *
+libdeflate_alloc_compressor(int compression_level);
+
+/*
+ * Like libdeflate_alloc_compressor(), but adds the 'options' argument.
+ */
+LIBDEFLATEAPI struct libdeflate_compressor *
+libdeflate_alloc_compressor_ex(int compression_level,
+			       const struct libdeflate_options *options);
+
+/*
+ * libdeflate_deflate_compress() performs raw DEFLATE compression on a buffer of
+ * data.  It attempts to compress 'in_nbytes' bytes of data located at 'in' and
+ * write the result to 'out', which has space for 'out_nbytes_avail' bytes.  The
+ * return value is the compressed size in bytes, or 0 if the data could not be
+ * compressed to 'out_nbytes_avail' bytes or fewer (but see note below).
+ *
+ * If compression is successful, then the output data is guaranteed to be a
+ * valid DEFLATE stream that decompresses to the input data.  No other
+ * guarantees are made about the output data.  Notably, different versions of
+ * libdeflate can produce different compressed data for the same uncompressed
+ * data, even at the same compression level.  Do ***NOT*** do things like
+ * writing tests that compare compressed data to a golden output, as this can
+ * break when libdeflate is updated.  (This property isn't specific to
+ * libdeflate; the same is true for zlib and other compression libraries too.)
+ */
+LIBDEFLATEAPI size_t
+libdeflate_deflate_compress(struct libdeflate_compressor *compressor,
+			    const void *in, size_t in_nbytes,
+			    void *out, size_t out_nbytes_avail);
+
+/*
+ * libdeflate_deflate_compress_bound() returns a worst-case upper bound on the
+ * number of bytes of compressed data that may be produced by compressing any
+ * buffer of length less than or equal to 'in_nbytes' using
+ * libdeflate_deflate_compress() with the specified compressor.  This bound will
+ * necessarily be a number greater than or equal to 'in_nbytes'.  It may be an
+ * overestimate of the true upper bound.  The return value is guaranteed to be
+ * the same for all invocations with the same compressor and same 'in_nbytes'.
+ *
+ * As a special case, 'compressor' may be NULL.  This causes the bound to be
+ * taken across *any* libdeflate_compressor that could ever be allocated with
+ * this build of the library, with any options.
+ *
+ * Note that this function is not necessary in many applications.  With
+ * block-based compression, it is usually preferable to separately store the
+ * uncompressed size of each block and to store any blocks that did not compress
+ * to less than their original size uncompressed.  In that scenario, there is no
+ * need to know the worst-case compressed size, since the maximum number of
+ * bytes of compressed data that may be used would always be one less than the
+ * input length.  You can just pass a buffer of that size to
+ * libdeflate_deflate_compress() and store the data uncompressed if
+ * libdeflate_deflate_compress() returns 0, indicating that the compressed data
+ * did not fit into the provided output buffer.
+ */
+LIBDEFLATEAPI size_t
+libdeflate_deflate_compress_bound(struct libdeflate_compressor *compressor,
+				  size_t in_nbytes);
+
+/*
+ * Like libdeflate_deflate_compress(), but uses the zlib wrapper format instead
+ * of raw DEFLATE.
+ */
+LIBDEFLATEAPI size_t
+libdeflate_zlib_compress(struct libdeflate_compressor *compressor,
+			 const void *in, size_t in_nbytes,
+			 void *out, size_t out_nbytes_avail);
+
+/*
+ * Like libdeflate_deflate_compress_bound(), but assumes the data will be
+ * compressed with libdeflate_zlib_compress() rather than with
+ * libdeflate_deflate_compress().
+ */
+LIBDEFLATEAPI size_t
+libdeflate_zlib_compress_bound(struct libdeflate_compressor *compressor,
+			       size_t in_nbytes);
+
+/*
+ * Like libdeflate_deflate_compress(), but uses the gzip wrapper format instead
+ * of raw DEFLATE.
+ */
+LIBDEFLATEAPI size_t
+libdeflate_gzip_compress(struct libdeflate_compressor *compressor,
+			 const void *in, size_t in_nbytes,
+			 void *out, size_t out_nbytes_avail);
+
+/*
+ * Like libdeflate_deflate_compress_bound(), but assumes the data will be
+ * compressed with libdeflate_gzip_compress() rather than with
+ * libdeflate_deflate_compress().
+ */
+LIBDEFLATEAPI size_t
+libdeflate_gzip_compress_bound(struct libdeflate_compressor *compressor,
+			       size_t in_nbytes);
+
+/*
+ * libdeflate_free_compressor() frees a compressor that was allocated with
+ * libdeflate_alloc_compressor().  If a NULL pointer is passed in, no action is
+ * taken.
+ */
+LIBDEFLATEAPI void
+libdeflate_free_compressor(struct libdeflate_compressor *compressor);
+
+/* ========================================================================== */
+/*                             Decompression                                  */
+/* ========================================================================== */
+
+struct libdeflate_decompressor;
+struct libdeflate_options;
+
+/*
+ * libdeflate_alloc_decompressor() allocates a new decompressor that can be used
+ * for DEFLATE, zlib, and gzip decompression.  The return value is a pointer to
+ * the new decompressor, or NULL if out of memory.
+ *
+ * This function takes no parameters, and the returned decompressor is valid for
+ * decompressing data that was compressed at any compression level and with any
+ * sliding window size.
+ *
+ * A single decompressor is not safe to use by multiple threads concurrently.
+ * However, different threads may use different decompressors concurrently.
+ */
+LIBDEFLATEAPI struct libdeflate_decompressor *
+libdeflate_alloc_decompressor(void);
+
+/*
+ * Like libdeflate_alloc_decompressor(), but adds the 'options' argument.
+ */
+LIBDEFLATEAPI struct libdeflate_decompressor *
+libdeflate_alloc_decompressor_ex(const struct libdeflate_options *options);
+
+/*
+ * Result of a call to libdeflate_deflate_decompress(),
+ * libdeflate_zlib_decompress(), or libdeflate_gzip_decompress().
+ */
+enum libdeflate_result {
+	/* Decompression was successful.  */
+	LIBDEFLATE_SUCCESS = 0,
+
+	/* Decompression failed because the compressed data was invalid,
+	 * corrupt, or otherwise unsupported.  */
+	LIBDEFLATE_BAD_DATA = 1,
+
+	/* A NULL 'actual_out_nbytes_ret' was provided, but the data would have
+	 * decompressed to fewer than 'out_nbytes_avail' bytes.  */
+	LIBDEFLATE_SHORT_OUTPUT = 2,
+
+	/* The data would have decompressed to more than 'out_nbytes_avail'
+	 * bytes.  */
+	LIBDEFLATE_INSUFFICIENT_SPACE = 3,
+};
+
+/*
+ * libdeflate_deflate_decompress() decompresses a DEFLATE stream from the buffer
+ * 'in' with compressed size up to 'in_nbytes' bytes.  The uncompressed data is
+ * written to 'out', a buffer with size 'out_nbytes_avail' bytes.  If
+ * decompression succeeds, then 0 (LIBDEFLATE_SUCCESS) is returned.  Otherwise,
+ * a nonzero result code such as LIBDEFLATE_BAD_DATA is returned, and the
+ * contents of the output buffer are undefined.
+ *
+ * Decompression stops at the end of the DEFLATE stream (as indicated by the
+ * BFINAL flag), even if it is actually shorter than 'in_nbytes' bytes.
+ *
+ * libdeflate_deflate_decompress() can be used in cases where the actual
+ * uncompressed size is known (recommended) or unknown (not recommended):
+ *
+ *   - If the actual uncompressed size is known, then pass the actual
+ *     uncompressed size as 'out_nbytes_avail' and pass NULL for
+ *     'actual_out_nbytes_ret'.  This makes libdeflate_deflate_decompress() fail
+ *     with LIBDEFLATE_SHORT_OUTPUT if the data decompressed to fewer than the
+ *     specified number of bytes.
+ *
+ *   - If the actual uncompressed size is unknown, then provide a non-NULL
+ *     'actual_out_nbytes_ret' and provide a buffer with some size
+ *     'out_nbytes_avail' that you think is large enough to hold all the
+ *     uncompressed data.  In this case, if the data decompresses to less than
+ *     or equal to 'out_nbytes_avail' bytes, then
+ *     libdeflate_deflate_decompress() will write the actual uncompressed size
+ *     to *actual_out_nbytes_ret and return 0 (LIBDEFLATE_SUCCESS).  Otherwise,
+ *     it will return LIBDEFLATE_INSUFFICIENT_SPACE if the provided buffer was
+ *     not large enough but no other problems were encountered, or another
+ *     nonzero result code if decompression failed for another reason.
+ */
+LIBDEFLATEAPI enum libdeflate_result
+libdeflate_deflate_decompress(struct libdeflate_decompressor *decompressor,
+			      const void *in, size_t in_nbytes,
+			      void *out, size_t out_nbytes_avail,
+			      size_t *actual_out_nbytes_ret);
+
+/*
+ * Like libdeflate_deflate_decompress(), but adds the 'actual_in_nbytes_ret'
+ * argument.  If decompression succeeds and 'actual_in_nbytes_ret' is not NULL,
+ * then the actual compressed size of the DEFLATE stream (aligned to the next
+ * byte boundary) is written to *actual_in_nbytes_ret.
+ */
+LIBDEFLATEAPI enum libdeflate_result
+libdeflate_deflate_decompress_ex(struct libdeflate_decompressor *decompressor,
+				 const void *in, size_t in_nbytes,
+				 void *out, size_t out_nbytes_avail,
+				 size_t *actual_in_nbytes_ret,
+				 size_t *actual_out_nbytes_ret);
+
+/*
+ * Like libdeflate_deflate_decompress(), but assumes the zlib wrapper format
+ * instead of raw DEFLATE.
+ *
+ * Decompression will stop at the end of the zlib stream, even if it is shorter
+ * than 'in_nbytes'.  If you need to know exactly where the zlib stream ended,
+ * use libdeflate_zlib_decompress_ex().
+ */
+LIBDEFLATEAPI enum libdeflate_result
+libdeflate_zlib_decompress(struct libdeflate_decompressor *decompressor,
+			   const void *in, size_t in_nbytes,
+			   void *out, size_t out_nbytes_avail,
+			   size_t *actual_out_nbytes_ret);
+
+/*
+ * Like libdeflate_zlib_decompress(), but adds the 'actual_in_nbytes_ret'
+ * argument.  If 'actual_in_nbytes_ret' is not NULL and the decompression
+ * succeeds (indicating that the first zlib-compressed stream in the input
+ * buffer was decompressed), then the actual number of input bytes consumed is
+ * written to *actual_in_nbytes_ret.
+ */
+LIBDEFLATEAPI enum libdeflate_result
+libdeflate_zlib_decompress_ex(struct libdeflate_decompressor *decompressor,
+			      const void *in, size_t in_nbytes,
+			      void *out, size_t out_nbytes_avail,
+			      size_t *actual_in_nbytes_ret,
+			      size_t *actual_out_nbytes_ret);
+
+/*
+ * Like libdeflate_deflate_decompress(), but assumes the gzip wrapper format
+ * instead of raw DEFLATE.
+ *
+ * If multiple gzip-compressed members are concatenated, then only the first
+ * will be decompressed.  Use libdeflate_gzip_decompress_ex() if you need
+ * multi-member support.
+ */
+LIBDEFLATEAPI enum libdeflate_result
+libdeflate_gzip_decompress(struct libdeflate_decompressor *decompressor,
+			   const void *in, size_t in_nbytes,
+			   void *out, size_t out_nbytes_avail,
+			   size_t *actual_out_nbytes_ret);
+
+/*
+ * Like libdeflate_gzip_decompress(), but adds the 'actual_in_nbytes_ret'
+ * argument.  If 'actual_in_nbytes_ret' is not NULL and the decompression
+ * succeeds (indicating that the first gzip-compressed member in the input
+ * buffer was decompressed), then the actual number of input bytes consumed is
+ * written to *actual_in_nbytes_ret.
+ */
+LIBDEFLATEAPI enum libdeflate_result
+libdeflate_gzip_decompress_ex(struct libdeflate_decompressor *decompressor,
+			      const void *in, size_t in_nbytes,
+			      void *out, size_t out_nbytes_avail,
+			      size_t *actual_in_nbytes_ret,
+			      size_t *actual_out_nbytes_ret);
+
+/*
+ * libdeflate_free_decompressor() frees a decompressor that was allocated with
+ * libdeflate_alloc_decompressor().  If a NULL pointer is passed in, no action
+ * is taken.
+ */
+LIBDEFLATEAPI void
+libdeflate_free_decompressor(struct libdeflate_decompressor *decompressor);
+
+/* ========================================================================== */
+/*                                Checksums                                   */
+/* ========================================================================== */
+
+/*
+ * libdeflate_adler32() updates a running Adler-32 checksum with 'len' bytes of
+ * data and returns the updated checksum.  When starting a new checksum, the
+ * required initial value for 'adler' is 1.  This value is also returned when
+ * 'buffer' is specified as NULL.
+ */
+LIBDEFLATEAPI uint32_t
+libdeflate_adler32(uint32_t adler, const void *buffer, size_t len);
+
+
+/*
+ * libdeflate_crc32() updates a running CRC-32 checksum with 'len' bytes of data
+ * and returns the updated checksum.  When starting a new checksum, the required
+ * initial value for 'crc' is 0.  This value is also returned when 'buffer' is
+ * specified as NULL.
+ */
+LIBDEFLATEAPI uint32_t
+libdeflate_crc32(uint32_t crc, const void *buffer, size_t len);
+
+/* ========================================================================== */
+/*                           Custom memory allocator                          */
+/* ========================================================================== */
+
+/*
+ * Install a custom memory allocator which libdeflate will use for all memory
+ * allocations by default.  'malloc_func' is a function that must behave like
+ * malloc(), and 'free_func' is a function that must behave like free().
+ *
+ * The per-(de)compressor custom memory allocator that can be specified in
+ * 'struct libdeflate_options' takes priority over this.
+ *
+ * This doesn't affect the free() function that will be used to free
+ * (de)compressors that were already in existence when this is called.
+ */
+LIBDEFLATEAPI void
+libdeflate_set_memory_allocator(void *(*malloc_func)(size_t),
+				void (*free_func)(void *));
+
+/*
+ * Advanced options.  This is the options structure that
+ * libdeflate_alloc_compressor_ex() and libdeflate_alloc_decompressor_ex()
+ * require.  Most users won't need this and should just use the non-"_ex"
+ * functions instead.  If you do need this, it should be initialized like this:
+ *
+ *	struct libdeflate_options options;
+ *
+ *	memset(&options, 0, sizeof(options));
+ *	options.sizeof_options = sizeof(options);
+ *	// Then set the fields that you need to override the defaults for.
+ */
+struct libdeflate_options {
+
+	/*
+	 * This field must be set to the struct size.  This field exists for
+	 * extensibility, so that fields can be appended to this struct in
+	 * future versions of libdeflate while still supporting old binaries.
+	 */
+	size_t sizeof_options;
+
+	/*
+	 * An optional custom memory allocator to use for this (de)compressor.
+	 * 'malloc_func' must be a function that behaves like malloc(), and
+	 * 'free_func' must be a function that behaves like free().
+	 *
+	 * This is useful in cases where a process might have multiple users of
+	 * libdeflate who want to use different memory allocators.  For example,
+	 * a library might want to use libdeflate with a custom memory allocator
+	 * without interfering with user code that might use libdeflate too.
+	 *
+	 * This takes priority over the "global" memory allocator (which by
+	 * default is malloc() and free(), but can be changed by
+	 * libdeflate_set_memory_allocator()).  Moreover, libdeflate will never
+	 * call the "global" memory allocator if a per-(de)compressor custom
+	 * allocator is always given.
+	 */
+	void *(*malloc_func)(size_t);
+	void (*free_func)(void *);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBDEFLATE_H */
diff --git a/source/fbxplugin/plugin_main.cpp b/source/fbxplugin/plugin_main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..08d1cacab6f669d18793e3e7a41e01bbcf0f4283
--- /dev/null
+++ b/source/fbxplugin/plugin_main.cpp
@@ -0,0 +1,51 @@
+#include <xcommon/IXPlugin.h>
+#include "ModelLoader.h"
+
+class CFBXPlugin: public IXUnknownImplementation<IXPlugin>
+{
+public:
+	void XMETHODCALLTYPE startup(IXCore *pCore) override
+	{
+		m_pCore = pCore;
+	}
+
+	void XMETHODCALLTYPE shutdown() override
+	{
+	}
+
+	UINT XMETHODCALLTYPE getInterfaceCount() override
+	{
+		return(1);
+	}
+	const XGUID* XMETHODCALLTYPE getInterfaceGUID(UINT id) override
+	{
+		static XGUID s_guid;
+		switch(id)
+		{
+		case 0:
+			s_guid = IXMODELLOADER_GUID;
+			break;
+
+		default:
+			return(NULL);
+		}
+		return(&s_guid);
+	}
+	void XMETHODCALLTYPE getInterface(UINT id, void **ppOut) override
+	{
+		switch(id)
+		{
+		case 0:
+			*ppOut = new CModelLoader(m_pCore->getFileSystem());
+			break;
+		
+		default:
+			*ppOut = NULL;
+		}
+	}
+
+private:
+	IXCore *m_pCore = NULL;
+};
+
+DECLARE_XPLUGIN(CFBXPlugin);
diff --git a/source/game/BaseAmmo.cpp b/source/game/BaseAmmo.cpp
index 8714102d3348320990b7f1ca63891f49ba742bac..35abac1eea8a590650ec7e3abe5d2d9420f35cb7 100644
--- a/source/game/BaseAmmo.cpp
+++ b/source/game/BaseAmmo.cpp
@@ -9,11 +9,10 @@ See the license in LICENSE
 #include "BaseTool.h"
 #include <particles/sxparticles.h>
 #include <mtrl/IXMaterial.h>
-#include <decals/sxdecals.h>
 //#include <BulletCollision/NarrowPhaseCollision/btRaycastCallback.h>
 
 //! TODO Reimplement me!
-#define SMtrl_MtlGetPhysicMaterial(id) MTLTYPE_PHYSIC_METAL
+#define SMtrl_MtlGetPhysicMaterial(id) MTLTYPE_PHYSIC_CONCRETE
 #define SMtrl_MtlGetDurability(id) 1.0f
 #define SMtrl_MtlGetDensity(id) 1.0f
 #define SMtrl_MtlGetHitChance(id) 1.0f
@@ -107,8 +106,7 @@ void CBaseAmmo::fire(const float3 &_vStart, const float3 &_vDir, CBaseCharacter
 			});
 			for(int i = 0, l = aHitPoints.size(); i < l; ++i)
 			{
-
-				ID idMtl = -1; // SPhysics_GetMtlID(aHitPoints[i].pCollisionObject, &aHitPoints[i].shapeInfo);
+				ID idMtl = 0; // SPhysics_GetMtlID(aHitPoints[i].pCollisionObject, &aHitPoints[i].shapeInfo);
 				if(ID_VALID(idMtl) && !aHitPoints[i].isExit)
 				{
 					float fHitChance = SMtrl_MtlGetHitChance(idMtl);
@@ -184,6 +182,15 @@ void CBaseAmmo::fire(const float3 &_vStart, const float3 &_vDir, CBaseCharacter
 					// restart fire with new dir and speed
 
 					float3 vStart2 = aHitPoints[i].vPosition + SMVector3Normalize(vDir) * 0.001f;
+					for(int j = i + 1; j < l; ++j)
+					{
+						if(aHitPoints[j].isExit)
+						{
+							// restart at exit point
+							vStart2 = aHitPoints[i].vPosition + SMVector3Normalize(vDir) * 0.001f;
+						}
+					}
+
 					vDir = vNewDir;
 					fSpeed = fNewSpeed;
 
@@ -240,26 +247,26 @@ void CBaseAmmo::shootDecal(const float3 &vPos, const float3 &vNormal, ID idMtl)
 	if(ID_VALID(idMtl))
 	{
 		MTLTYPE_PHYSIC type = SMtrl_MtlGetPhysicMaterial(idMtl);
-		DECAL_TYPE decalType = DECAL_TYPE_CONCRETE;
+		XDECAL_TYPE decalType = XDT_CONCRETE;
 		switch(type)
 		{
 		case MTLTYPE_PHYSIC_METAL:
-			decalType = DECAL_TYPE_METAL;
+			decalType = XDT_METAL;
 			break;
 		case MTLTYPE_PHYSIC_FLESH:
-			decalType = DECAL_TYPE_FLESH;
+			decalType = XDT_FLESH;
 			break;
 		case MTLTYPE_PHYSIC_GROUD_SAND:
-			decalType = DECAL_TYPE_EARTH;
+			decalType = XDT_EARTH;
 			break;
 		case MTLTYPE_PHYSIC_PLASTIC:
-			decalType = DECAL_TYPE_PLASTIC;
+			decalType = XDT_PLASTIC;
 			break;
 		case MTLTYPE_PHYSIC_TREE:
-			decalType = DECAL_TYPE_WOOD;
+			decalType = XDT_WOOD;
 			break;
 		}
-		SXDecals_ShootDecal(decalType, vPos, vNormal);
+		SAFE_CALL(GetDecalProvider(), shootDecal, decalType, vPos, vNormal);
 
 		//SPE_EffectPlayByName("create_decal_test", &aHitPoints[i].vPosition, &aHitPoints[i].vNormal);
 	}
@@ -267,7 +274,7 @@ void CBaseAmmo::shootDecal(const float3 &vPos, const float3 &vNormal, ID idMtl)
 
 void CBaseAmmo::shootBlood(const float3 &vPos, const float3 &vNormal)
 {
-	SXDecals_ShootDecal(DECAL_TYPE_BLOOD_BIG, vPos, vNormal);
+	SAFE_CALL(GetDecalProvider(), shootDecal, XDT_BLOOD_BIG, vPos, vNormal);
 }
 
 bool CBaseAmmo::shouldRecochet(const float3 &vPos, const float3 &vNormal, const float3 &vDir, ID idMtl, float fSpeed, float3 *pvNewDir, float *pfNewSpeed)
diff --git a/source/game/BaseAnimating.cpp b/source/game/BaseAnimating.cpp
index 5b37882ff0d5cecd2e175a261753e0af96906e28..c5eb19afb8c2b850724be4d86fd7ac1fe7295491 100644
--- a/source/game/BaseAnimating.cpp
+++ b/source/game/BaseAnimating.cpp
@@ -358,7 +358,10 @@ void CBaseAnimating::initPhysics()
 			}
 		}
 	}
-	pShape->setLocalScaling(m_fBaseScale);
+	if(!SMIsZero(m_fBaseScale - 1.0f))
+	{
+		pShape->setLocalScaling(m_fBaseScale);
+	}
 	pShape->recalculateLocalAabb();
 	m_pCollideShape = pShape;
 	createPhysBody();
diff --git a/source/game/BaseEntity.h b/source/game/BaseEntity.h
index 53ecd6de3f3eb3fbfd47e08a0069ab9b61f2c086..a6bee19631543f2b0abd55b67c9582dcbba2c7cb 100644
--- a/source/game/BaseEntity.h
+++ b/source/game/BaseEntity.h
@@ -36,11 +36,13 @@ See the license in LICENSE
 
 #include <light/IXLightSystem.h>
 
+#include <xcommon/resource/IXDecalProvider.h>
+
 #pragma pointers_to_members(full_generality, virtual_inheritance)
 
 IXRender* GetRender();
 IXParticleSystem* GetParticleSystem();
-
+IXDecalProvider* GetDecalProvider();
 
 
 
@@ -170,6 +172,10 @@ public:
 		return(false);
 	}
 
+	virtual void setSize(const float3_t &vSize)
+	{
+	}
+
 private:
 	void setClassName(const char *name);
 	void setDefaults();
diff --git a/source/game/BaseMover.cpp b/source/game/BaseMover.cpp
index d1eed1100fb19396ccf75ec46eaaea9469b83150..0008c78b676ec18bab8b1c443c1cfb37d3aaec24 100644
--- a/source/game/BaseMover.cpp
+++ b/source/game/BaseMover.cpp
@@ -31,7 +31,7 @@ BEGIN_PROPTABLE(CBaseMover)
 	DEFINE_FLAG(MOVER_NO_AUTOMOUNT, "Disable automount")
 END_PROPTABLE()
 
-REGISTER_ENTITY(CBaseMover, base_mover);
+REGISTER_ENTITY_NOLISTING(CBaseMover, base_mover);
 
 IEventChannel<XEventPhysicsStep> *CBaseMover::m_pTickEventChannel = NULL;
 
diff --git a/source/game/BaseTool.cpp b/source/game/BaseTool.cpp
index 5d84a3a0fa5b3c5dfe5026fd5c4fe95d425a8bfb..ccc15248bd43e6211a5ace7862af189df0c4ef6a 100644
--- a/source/game/BaseTool.cpp
+++ b/source/game/BaseTool.cpp
@@ -7,7 +7,6 @@ See the license in LICENSE
 #include "BaseTool.h"
 
 #include <particles/sxparticles.h>
-#include <decals/sxdecals.h>
 #include "Player.h"
 
 /*! \skydocent base_tool
@@ -138,7 +137,8 @@ void CBaseTool::primaryAction(BOOL st)
 		if(cb.hasHit())
 		{
 			//shoot decal
-			SXDecals_ShootDecal(DECAL_TYPE_CONCRETE, BTVEC_F3(cb.m_hitPointWorld), BTVEC_F3(cb.m_hitNormalWorld));
+			SAFE_CALL(GetDecalProvider(), shootDecal, XDT_CONCRETE, cb.m_result.vHitPoint, cb.m_result.vHitNormal);
+			//SXDecals_ShootDecal(DECAL_TYPE_CONCRETE, BTVEC_F3(cb.m_hitPointWorld), BTVEC_F3(cb.m_hitNormalWorld));
 			SPE_EffectPlayByName("fire", &BTVEC_F3(cb.m_hitPointWorld), &BTVEC_F3(cb.m_hitNormalWorld));
 
 			IXRigidBody *pRB = cb.m_result.pCollisionObject->asRigidBody();
diff --git a/source/game/Editable.cpp b/source/game/Editable.cpp
index d9484c6714bbd3ddf3e66f29daa70a44428a01ba..dc3239c24b6876ba31a7ec1cd237fc0ba6c81dd1 100644
--- a/source/game/Editable.cpp
+++ b/source/game/Editable.cpp
@@ -110,14 +110,15 @@ void CEditable::onSelectionChanged(CEditorObject *pObject)
 
 bool XMETHODCALLTYPE CEditable::canUseModel(const char *szClass)
 {
-	IEntityFactory *pFactory = CEntityFactoryMap::GetInstance()->getFactory(szClass);
+	return(getClassKV(szClass, "model_field") != NULL);
+}
+
+const char* XMETHODCALLTYPE CEditable::getClassKV(const char *szClassName, const char *szKey)
+{
+	IEntityFactory *pFactory = CEntityFactoryMap::GetInstance()->getFactory(szClassName);
 	if(pFactory)
 	{
-		const char *szModelField = pFactory->getKV("model_field");
-		if(szModelField)
-		{
-			return(true);
-		}
+		return(pFactory->getKV(szKey));
 	}
-	return(false);
+	return(NULL);
 }
diff --git a/source/game/Editable.h b/source/game/Editable.h
index 3db86f3e571426fb60a88cd3b09ed1836d3342f3..b2f66e35549c4af59d1f7c199a496c3d6519261d 100644
--- a/source/game/Editable.h
+++ b/source/game/Editable.h
@@ -80,6 +80,8 @@ public:
 		return(m_pDevice);
 	}
 
+	const char* XMETHODCALLTYPE getClassKV(const char *szClassName, const char *szKey) override;
+
 protected:
 	IGXDevice *m_pDevice = NULL;
 	IXCore *m_pCore = NULL;
diff --git a/source/game/EditorObject.cpp b/source/game/EditorObject.cpp
index 944656b71a186737cba9e37b9ab212f9c68ef0af..83da6cb47cfffe04f57985332e1ca7ddc2dd1e01 100644
--- a/source/game/EditorObject.cpp
+++ b/source/game/EditorObject.cpp
@@ -71,6 +71,8 @@ void CEditorObject::_iniFieldList()
 				&& pField->editor.type != PDE_NONE
 				)
 			{
+				xField.pEditorData = NULL;
+
 				switch(pField->editor.type)
 				{
 				case PDE_COMBOBOX:
@@ -79,27 +81,30 @@ void CEditorObject::_iniFieldList()
 					break;
 				case PDE_FILEFIELD:
 					xField.editorType = XPET_FILE;
-					xField.pEditorData = NULL;
 					{
-						editor_kv *pKV = (editor_kv*)pField->editor.pData;
-						if(pKV && pKV[0].value)
+						kv_t *pKV = (kv_t*)pField->editor.pData;
+						if(pKV && pKV[0].szValue)
 						{
-							if(!fstrcmp(pKV[0].value, "dse"))
+							if(!fstrcmp(pKV[0].szValue, "dse"))
 							{
 								xField.pEditorData = "model";
 							}
-							else if(!fstrcmp(pKV[0].value, "ogg"))
+							else if(!fstrcmp(pKV[0].szValue, "ogg"))
 							{
 								xField.pEditorData = "sound";
 							}
-							else if(!fstrcmp(pKV[0].value, "dds"))
+							else if(!fstrcmp(pKV[0].szValue, "dds"))
 							{
 								xField.pEditorData = "texture";
 							}
-							else if(!fstrcmp(pKV[0].value, "eff"))
+							else if(!fstrcmp(pKV[0].szValue, "eff"))
 							{
 								xField.pEditorData = "effect";
 							}
+							else if(!fstrcmp(pKV[0].szValue, "mtl"))
+							{
+								xField.pEditorData = "material";
+							}
 						}
 						//xField.pEditorData
 					}
@@ -119,6 +124,7 @@ void CEditorObject::_iniFieldList()
 					break;
 				case PDE_POINTCOORD:
 					xField.editorType = XPET_POINTCOORD;
+					xField.pEditorData = pField->editor.pData;
 					break;
 				case PDE_RADIUS:
 					xField.editorType = XPET_RADIUS;
@@ -129,6 +135,9 @@ void CEditorObject::_iniFieldList()
 				case PDE_HDRCOLOR:
 					xField.editorType = XPET_HDRCOLOR;
 					break;
+				case PDE_SIZE:
+					xField.editorType = XPET_SIZE;
+					break;
 				}
 				xField.szHelp = "";
 				xField.szKey = pField->szKey;
@@ -218,8 +227,8 @@ void CEditorObject::setPos(const float3_t &pos, bool isSeparate)
 
 void XMETHODCALLTYPE CEditorObject::setSize(const float3_t &vSize)
 {
-	// TODO Implement me
 	//m_vScale = vScale;
+	m_pEntity->setSize(vSize);
 }
 
 /*void CEditorObject::setScale(const float3_t &vScale, bool isSeparate)
diff --git a/source/game/EntityFactory.h b/source/game/EntityFactory.h
index 46ff5c91c3305bdbaa86b8b6eeeedd4507ae70c3..1ec3331aa9c0e3a792d98f708298c4b0b350f746 100644
--- a/source/game/EntityFactory.h
+++ b/source/game/EntityFactory.h
@@ -203,6 +203,9 @@ private:
 #define REC_ICON(s) REC_KV("icon", s)
 #define REC_MODEL(s) REC_KV("model", s)
 #define REC_MODEL_FIELD(s) REC_KV("model_field", s)
+#define REC_NO_HEIGHT_ADJUST() REC_KV("no_height_adj", "1")
+#define REC_ALIGN_WITH_NORMAL(axis) REC_KV("align_with_norm", axis)
+#define REC_MATERIAL_FIELD(s) REC_KV("material_field", s)
 
 #define REGISTER_ENTITY(cls, name, ...) \
 	CEntityFactory<cls> ent_ ## name ## _factory(#name, {__VA_ARGS__})
diff --git a/source/game/EntityManager.cpp b/source/game/EntityManager.cpp
index f7bdfed35d1ef3e3676bd0e6d9c6ab1ce8179b1f..f7a14bf636cc0f33f3d042888fdb676cea6dac76 100644
--- a/source/game/EntityManager.cpp
+++ b/source/game/EntityManager.cpp
@@ -400,14 +400,14 @@ bool CEntityManager::exportList(const char * file)
 	int ic = 0;
 
 	//if(m_isOldImported)
-	{
+	/*{
 		FILE *fp = fopen(file, "w");
 		if(fp)
 		{
 			fclose(fp);
 		}
 		m_isOldImported = false;
-	}
+	}*/
 
 	// conf->set("meta", "count", "0");
 
@@ -463,13 +463,7 @@ bool CEntityManager::import(const char * file, bool shouldSendProgress)
 	CBaseEntity *pEnt = NULL;
 	Array<CBaseEntity*> tmpList;
 
-	char szFullPath[1024];
-	if(!Core_GetIXCore()->getFileSystem()->resolvePath(file, szFullPath, sizeof(szFullPath)))
-	{
-		goto err;
-	}
-
-	if(conf->open(szFullPath))
+	if(conf->open(file))
 	{
 		goto err;
 	}
diff --git a/source/game/GUIInventoryController.cpp b/source/game/GUIInventoryController.cpp
index 354374438694492971ae4bed4216f733e69b8b9d..c06a4fd7bcf5fbaf1b4629a36626f77119140da6 100644
--- a/source/game/GUIInventoryController.cpp
+++ b/source/game/GUIInventoryController.cpp
@@ -38,7 +38,7 @@ CGUIInventoryController::CGUIInventoryController(CCharacterInventory *pInventory
 		{
 			m_aEquipAreas[i].type = (EQUIP_ITEM_TYPE)enumerator.getValue();
 			m_aEquipAreas[i].uIndex = pTypeCounts[enumerator.getValue()]++;
-			m_aEquipAreas[i].pNode->setAttribute(L"equip_index", (unsigned long)m_aEquipAreas[i].uIndex);
+			m_aEquipAreas[i].pNode->setAttribute(L"equip_index", m_aEquipAreas[i].uIndex);
 
 			if(m_aEquipAreas[i].pNode->getAttribute(L"split_container").toBool())
 			{
@@ -52,13 +52,13 @@ CGUIInventoryController::CGUIInventoryController(CCharacterInventory *pInventory
 						if(j == 0 && k == 0)
 						{
 							m_aEquipAreas[i].pCellNode = aCells[k];
-							m_aEquipAreas[i].pCellNode->setAttribute(L"equip_index", (unsigned long)m_aEquipAreas[i].uIndex);
+							m_aEquipAreas[i].pCellNode->setAttribute(L"equip_index", m_aEquipAreas[i].uIndex);
 						}
 						else
 						{
 							newArea.uIndex = pTypeCounts[enumerator.getValue()]++;
 							newArea.pCellNode = aCells[k];
-							newArea.pCellNode->setAttribute(L"equip_index", (unsigned long)newArea.uIndex);
+							newArea.pCellNode->setAttribute(L"equip_index", newArea.uIndex);
 
 							m_aEquipAreas.push_back(newArea);
 						}
diff --git a/source/game/GameData.cpp b/source/game/GameData.cpp
index a759bab3943e33f7e5b6181e9951d5975667c0d7..1f6d4ed04c74d4e943320d065d60fc55a9e820e5 100644
--- a/source/game/GameData.cpp
+++ b/source/game/GameData.cpp
@@ -81,6 +81,7 @@ static IXPhysics *g_pPhysics = NULL;
 static IXPhysicsWorld *g_pPhysWorld = NULL;
 static IXRender *g_pRender = NULL;
 static IXParticleSystem *g_pParticleSystem = NULL;
+static IXDecalProvider *g_pDecalProvider = NULL;
 
 //##########################################################################
 
@@ -114,6 +115,10 @@ IXParticleSystem* GetParticleSystem()
 {
 	return(g_pParticleSystem);
 }
+IXDecalProvider* GetDecalProvider()
+{
+	return(g_pDecalProvider);
+}
 
 //##########################################################################
 
@@ -342,9 +347,12 @@ void XMETHODCALLTYPE CLevelLoadTask::exec()
 	evLevel.szLevelName = m_szLevelName;
 
 	g_levelProgressListener.onLoadBegin();
-	
+
 	evLevel.type = XEventLevel::TYPE_LOAD;
 	g_pLevelChannel->broadcastEvent(&evLevel);
+
+	evLevel.type = XEventLevel::TYPE_LOAD_WAIT_ASYNC_TASKS;
+	g_pLevelChannel->broadcastEvent(&evLevel);
 }
 
 void XMETHODCALLTYPE CLevelLoadTask::onFinished()
@@ -489,6 +497,7 @@ GameData::GameData(HWND hWnd, bool isGame):
 	g_pPhysics = (IXPhysics*)Core_GetIXCore()->getPluginManager()->getInterface(IXPHYSICS_GUID);
 	g_pPhysWorld = g_pPhysics->getWorld();
 	g_pParticleSystem = (IXParticleSystem*)Core_GetIXCore()->getPluginManager()->getInterface(IXPARTICLESYSTEM_GUID);
+	g_pDecalProvider = (IXDecalProvider*)Core_GetIXCore()->getPluginManager()->getInterface(IXDECALPROVIDER_GUID);
 
 	if(m_pLightSystem && false)
 	{
diff --git a/source/game/InfoOverlay.cpp b/source/game/InfoOverlay.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d534185547e5ba48ac82dacbb5350993b783db45
--- /dev/null
+++ b/source/game/InfoOverlay.cpp
@@ -0,0 +1,372 @@
+#include "InfoOverlay.h"
+
+BEGIN_PROPTABLE(CInfoOverlay)
+	//! Значение по умолчанию
+	DEFINE_FIELD_STRINGFN(m_szMaterial, PDFF_NONE, "material", "Overlay material", setMaterial, EDITOR_MATERIAL)
+
+	//! Высота объема декали
+	DEFINE_FIELD_FLOATFN(m_fHeight, PDFF_NONE, "height", "Height", setHeight, EDITOR_TEXTFIELD)
+
+	//! Угол 1
+	DEFINE_FIELD_VECTORFN(m_vCorner0, PDFF_USE_GIZMO, "point0", "Point 1", setCorner0, EDITOR_POINTCOORDEX)
+		EDITOR_KV("lock", "plane")
+		EDITOR_KV("axis", "z")
+		EDITOR_KV("local", "1")
+	EDITOR_END()
+	//! Угол 2
+	DEFINE_FIELD_VECTORFN(m_vCorner1, PDFF_USE_GIZMO, "point1", "Point 2", setCorner1, EDITOR_POINTCOORDEX)
+		EDITOR_KV("lock", "plane")
+		EDITOR_KV("axis", "z")
+		EDITOR_KV("local", "1")
+	EDITOR_END()
+	//! Угол 3
+	DEFINE_FIELD_VECTORFN(m_vCorner2, PDFF_USE_GIZMO, "point2", "Point 3", setCorner2, EDITOR_POINTCOORDEX)
+		EDITOR_KV("lock", "plane")
+		EDITOR_KV("axis", "z")
+		EDITOR_KV("local", "1")
+	EDITOR_END()
+	//! Угол 4
+	DEFINE_FIELD_VECTORFN(m_vCorner3, PDFF_USE_GIZMO, "point3", "Point 4", setCorner3, EDITOR_POINTCOORDEX)
+		EDITOR_KV("lock", "plane")
+		EDITOR_KV("axis", "z")
+		EDITOR_KV("local", "1")
+	EDITOR_END()
+
+	//! Разрешить перетекание на перпендикулярные поверхности
+	DEFINE_FIELD_BOOLFN(m_isLeakAllowed, PDFF_NONE, "allow_leak", "Allow leak to perpendicular surface", setLeakAllowed, EDITOR_YESNO)
+
+	//! Угол 4
+	//DEFINE_FIELD_VECTORFN(m_vSize, PDFF_USE_GIZMO, "size", "Size", setSize, EDITOR_SIZE)
+
+	//! Включает
+	//DEFINE_INPUT(turnOn, "turnOn", "Turn on", PDF_NONE)
+	//! Выключает
+	//DEFINE_INPUT(turnOff, "turnOff", "Turn off", PDF_NONE)
+	//! Переключает состояние
+	//DEFINE_INPUT(toggle, "toggle", "Toggle", PDF_NONE)
+
+	
+
+	//! Изначально выключена
+	//DEFINE_FLAG(MOVER_INITIALLY_DISABLED, "Start disabled")
+	//! Отключить автомонтирование (требуется нажать кнопку взаимодействия)
+	//DEFINE_FLAG(MOVER_NO_AUTOMOUNT, "Disable automount")
+END_PROPTABLE()
+
+REGISTER_ENTITY(CInfoOverlay, info_overlay, REC_NO_HEIGHT_ADJUST(), REC_ALIGN_WITH_NORMAL("z"), REC_MATERIAL_FIELD("material"));
+
+CInfoOverlay::CInfoOverlay()
+{
+	float2_t vSize(1.0f, 1.0f);
+
+	float2_t sBound(-vSize.x * 0.5f, vSize.x * 0.5f);
+	float2_t tBound(-vSize.y * 0.5f, vSize.y * 0.5f);
+
+	m_vCorner0 = float3_t(sBound.x, tBound.x, 0.0f);
+	m_vCorner1 = float3_t(sBound.x, tBound.y, 0.0f);
+	m_vCorner2 = float3_t(sBound.y, tBound.y, 0.0f);
+	m_vCorner3 = float3_t(sBound.y, tBound.x, 0.0f);
+
+	m_fHeight = min(vSize.x, vSize.y) * 0.25f;
+
+	GetDecalProvider()->newDecal(&m_pDecal);
+
+	m_pDecal->setHeight(m_fHeight);
+	
+	m_pDecal->setCorner(0, float2(m_vCorner0));
+	m_pDecal->setCorner(1, float2(m_vCorner1));
+	m_pDecal->setCorner(2, float2(m_vCorner2));
+	m_pDecal->setCorner(3, float2(m_vCorner3));
+
+	updateSize();
+}
+
+CInfoOverlay::~CInfoOverlay()
+{
+	mem_release(m_pDecal);
+}
+
+void CInfoOverlay::setCorner0(const float3 &v)
+{
+	m_vCorner0 = v;
+	m_pDecal->setCorner(0, float2(v));
+	updateSize();
+}
+void CInfoOverlay::setCorner1(const float3 &v)
+{
+	m_vCorner1 = v;
+	m_pDecal->setCorner(1, float2(v));
+	updateSize();
+}
+void CInfoOverlay::setCorner2(const float3 &v)
+{
+	m_vCorner2 = v;
+	m_pDecal->setCorner(2, float2(v));
+	updateSize();
+}
+void CInfoOverlay::setCorner3(const float3 &v)
+{
+	m_vCorner3 = v;
+	m_pDecal->setCorner(3, float2(v));
+	updateSize();
+}
+
+void CInfoOverlay::setPos(const float3 &vPos)
+{
+	BaseClass::setPos(vPos);
+
+	m_pDecal->setPosition(vPos);
+}
+
+void CInfoOverlay::setOrient(const SMQuaternion &qRot)
+{
+	BaseClass::setOrient(qRot);
+
+	m_pDecal->setOrientation(qRot);
+}
+
+void CInfoOverlay::setMaterial(const char *szValue)
+{
+	_setStrVal(&m_szMaterial, szValue);
+
+	m_pDecal->setMaterial(szValue);
+}
+
+void CInfoOverlay::setHeight(float fHeight)
+{
+	m_fHeight = fHeight;
+	m_pDecal->setHeight(fHeight);
+}
+
+void CInfoOverlay::renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer *pRenderer)
+{
+	if(pRenderer)
+	{
+		pRenderer->setLineWidth(is3D ? 0.01f : 1.0f);
+
+		pRenderer->setColor(float4(1.0f, 0.0f, 0.0f, 1.0f));
+		pRenderer->jumpTo(getPos());
+		pRenderer->lineTo(getPos() + getOrient() * float3(0.1f, 0.0f, 0.0f));
+
+		pRenderer->setColor(float4(0.0f, 1.0f, 0.0f, 1.0f));
+		pRenderer->jumpTo(getPos());
+		pRenderer->lineTo(getPos() + getOrient() * float3(0.0f, 0.1f, 0.0f));
+
+		pRenderer->setColor(float4(0.0f, 0.0f, 1.0f, 1.0f));
+		pRenderer->jumpTo(getPos());
+		pRenderer->lineTo(getPos() + getOrient() * float3(0.0f, 0.0f, 0.2f));
+
+		if(bRenderSelection || !is3D)
+		{
+			pRenderer->setLineWidth(is3D ? 0.005f : 1.0f);
+			const float4 c_vLineColor = bRenderSelection ? float4(1.0f, 0.0f, 0.0f, 1.0f) : float4(1.0f, 0.0f, 1.0f, 1.0f);
+			pRenderer->setColor(c_vLineColor);
+
+			float3 vS = getOrient() * float3(1.0f, 0.0f, 0.0f);
+			float3 vT = getOrient() * float3(0.0f, 1.0f, 0.0f);
+			float3 vN = getOrient() * float3(0.0f, 0.0f, 1.0f);
+
+			float3_t vPt0 = getPos() + m_vCorner0.x * vS + m_vCorner0.y * vT;
+			float3_t vPt1 = getPos() + m_vCorner1.x * vS + m_vCorner1.y * vT;
+			float3_t vPt2 = getPos() + m_vCorner2.x * vS + m_vCorner2.y * vT;
+			float3_t vPt3 = getPos() + m_vCorner3.x * vS + m_vCorner3.y * vT;
+
+			float3 vTopOffset = vN * m_fHeight;
+
+			pRenderer->jumpTo(vPt0 - vTopOffset);
+			pRenderer->lineTo(vPt1 - vTopOffset);
+			pRenderer->lineTo(vPt2 - vTopOffset);
+			pRenderer->lineTo(vPt3 - vTopOffset);
+			pRenderer->lineTo(vPt0 - vTopOffset);
+			pRenderer->lineTo(vPt0 + vTopOffset);
+			pRenderer->lineTo(vPt1 + vTopOffset);
+			pRenderer->lineTo(vPt2 + vTopOffset);
+			pRenderer->lineTo(vPt3 + vTopOffset);
+			pRenderer->lineTo(vPt0 + vTopOffset);
+
+			pRenderer->jumpTo(vPt1 - vTopOffset);
+			pRenderer->lineTo(vPt1 + vTopOffset);
+
+			pRenderer->jumpTo(vPt2 - vTopOffset);
+			pRenderer->lineTo(vPt2 + vTopOffset);
+
+			pRenderer->jumpTo(vPt3 - vTopOffset);
+			pRenderer->lineTo(vPt3 + vTopOffset);
+		}
+	}
+}
+
+void CInfoOverlay::getMinMax(float3 *min, float3 *max)
+{
+	float3 vS = getOrient() * float3(1.0f, 0.0f, 0.0f);
+	float3 vT = getOrient() * float3(0.0f, 1.0f, 0.0f);
+	float3 vN = getOrient() * float3(0.0f, 0.0f, 1.0f);
+
+	float3 vTopOffset = vN * m_fHeight;
+
+	float3_t vPt0 = m_vCorner0.x * vS + m_vCorner0.y * vT;
+	float3_t vPt1 = m_vCorner1.x * vS + m_vCorner1.y * vT;
+	float3_t vPt2 = m_vCorner2.x * vS + m_vCorner2.y * vT;
+	float3_t vPt3 = m_vCorner3.x * vS + m_vCorner3.y * vT;
+
+	float3_t vPt10 = vPt0 + vTopOffset;
+	float3_t vPt11 = vPt1 + vTopOffset;
+	float3_t vPt12 = vPt2 + vTopOffset;
+	float3_t vPt13 = vPt3 + vTopOffset;
+
+	vPt0 = vPt0 - vTopOffset;
+	vPt1 = vPt1 - vTopOffset;
+	vPt2 = vPt2 - vTopOffset;
+	vPt3 = vPt3 - vTopOffset;
+
+	if(min)
+	{
+		*min = SMVectorMin(
+			SMVectorMin(SMVectorMin(vPt0, vPt1), SMVectorMin(vPt2, vPt3)),
+			SMVectorMin(SMVectorMin(vPt10, vPt11), SMVectorMin(vPt12, vPt13))
+		);
+	}
+
+	if(max)
+	{
+		*max = SMVectorMax(
+			SMVectorMax(SMVectorMax(vPt0, vPt1), SMVectorMax(vPt2, vPt3)),
+			SMVectorMax(SMVectorMax(vPt10, vPt11), SMVectorMax(vPt12, vPt13))
+		);
+	}
+}
+
+void CInfoOverlay::setSize(const float3_t &vSize)
+{
+	float3 vMin, vMax;
+	getMinMax(&vMin, &vMax);
+	BaseClass::setSize(vSize);
+
+	float3 vRelativeSize = vSize / (vMax - vMin);
+
+	float3 vS = getOrient() * float3(1.0f, 0.0f, 0.0f);
+	float3 vT = getOrient() * float3(0.0f, 1.0f, 0.0f);
+	float3 vN = getOrient() * float3(0.0f, 0.0f, 1.0f);
+
+	float3_t vPt0 = (m_vCorner0.x * vS + m_vCorner0.y * vT) * vRelativeSize;
+	float3_t vPt1 = (m_vCorner1.x * vS + m_vCorner1.y * vT) * vRelativeSize;
+	float3_t vPt2 = (m_vCorner2.x * vS + m_vCorner2.y * vT) * vRelativeSize;
+	float3_t vPt3 = (m_vCorner3.x * vS + m_vCorner3.y * vT) * vRelativeSize;
+
+	m_vCorner0 = float3_t(SMVector3Dot(vPt0, vS), SMVector3Dot(vPt0, vT), 0.0f);
+	m_vCorner1 = float3_t(SMVector3Dot(vPt1, vS), SMVector3Dot(vPt1, vT), 0.0f);
+	m_vCorner2 = float3_t(SMVector3Dot(vPt2, vS), SMVector3Dot(vPt2, vT), 0.0f);
+	m_vCorner3 = float3_t(SMVector3Dot(vPt3, vS), SMVector3Dot(vPt3, vT), 0.0f);
+
+	m_pDecal->setCorner(0, float2(m_vCorner0));
+	m_pDecal->setCorner(1, float2(m_vCorner1));
+	m_pDecal->setCorner(2, float2(m_vCorner2));
+	m_pDecal->setCorner(3, float2(m_vCorner3));
+
+	m_fHeight *= SMVector3Dot(vN, vRelativeSize);
+	m_pDecal->setHeight(m_fHeight);
+
+	updateSize();
+}
+
+bool CInfoOverlay::rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut, float3 *pvNormal, bool isRayInWorldSpace, bool bReturnNearestPoint)
+{
+	if(!isRayInWorldSpace)
+	{
+		float3 vPos = getPos();
+		SMQuaternion qRot = getOrient().Conjugate();
+
+		float3 vRayStart = qRot * vStart - vPos;
+		float3 vRayEnd = qRot * vEnd - vPos;
+		return(m_pDecal->rayTest(vRayStart, vRayEnd, pvOut, pvNormal));
+	}
+
+	return(m_pDecal->rayTest(vStart, vEnd, pvOut, pvNormal));
+}
+
+void CInfoOverlay::updateSize()
+{
+	float3 vMin = SMVectorMin(SMVectorMin(m_vCorner0, m_vCorner1), SMVectorMin(m_vCorner2, m_vCorner3));
+	float3 vMax = SMVectorMax(SMVectorMax(m_vCorner0, m_vCorner1), SMVectorMax(m_vCorner2, m_vCorner3));
+
+	m_vSize = vMax - vMin;
+	m_vSize.z = m_fHeight * 2.0f;
+}
+
+void CInfoOverlay::setSize(const float3 &v)
+{
+}
+
+void CInfoOverlay::setLeakAllowed(bool yesNo)
+{
+	m_isLeakAllowed = yesNo;
+	m_pDecal->setLeakAllowed(yesNo);
+}
+
+#if 0
+
+void CInfoOverlay::updateFlags()
+{
+	BaseClass::updateFlags();
+
+	if(getFlags() & MOVER_INITIALLY_DISABLED)
+	{
+		disable();
+	}
+	else
+	{
+		enable();
+	}
+}
+
+void CInfoOverlay::turnOn(inputdata_t *pInputdata)
+{
+	enable();
+}
+
+void CInfoOverlay::turnOff(inputdata_t *pInputdata)
+{
+	disable();
+}
+
+void CInfoOverlay::toggle(inputdata_t *pInputdata)
+{
+	if(m_isEnabled)
+	{
+		turnOff(pInputdata);
+	}
+	else
+	{
+		turnOn(pInputdata);
+	}
+}
+
+
+
+void CInfoOverlay::enable()
+{
+	if(!m_isEnabled)
+	{
+		if(m_pGhostObject)
+		{
+			GetPhysWorld()->addCollisionObject(m_pGhostObject, CG_LADDER, CG_CHARACTER);
+		}
+		m_pTickEventChannel->addListener(&m_physicsTicker);
+		m_isEnabled = true;
+	}
+}
+
+void CInfoOverlay::disable()
+{
+	if(m_isEnabled)
+	{
+		if(m_pGhostObject)
+		{
+			GetPhysWorld()->removeCollisionObject(m_pGhostObject);
+		}
+		m_pTickEventChannel->removeListener(&m_physicsTicker);
+		m_isEnabled = false;
+	}
+}
+
+#endif
diff --git a/source/game/InfoOverlay.h b/source/game/InfoOverlay.h
new file mode 100644
index 0000000000000000000000000000000000000000..ca46e39c52d3ffeb2fe6891bd5113822b7fa8764
--- /dev/null
+++ b/source/game/InfoOverlay.h
@@ -0,0 +1,71 @@
+#ifndef __INFO_OVERLAY_H
+#define __INFO_OVERLAY_H
+
+#include "PointEntity.h"
+
+//#define MOVER_INITIALLY_DISABLED ENT_FLAG_0
+//#define MOVER_NO_AUTOMOUNT ENT_FLAG_1
+
+class CInfoOverlay: public CPointEntity
+{
+	DECLARE_CLASS(CInfoOverlay, CPointEntity);
+	DECLARE_PROPTABLE();
+public:
+	DECLARE_CONSTRUCTOR();
+	~CInfoOverlay();
+
+	void setPos(const float3 &vPos) override;
+	void setOrient(const SMQuaternion &qRot) override;
+
+	void renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer *pRenderer) override;
+
+	void getMinMax(float3 *min, float3 *max) override;
+
+	
+
+	bool rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut = NULL, float3 *pvNormal = NULL, bool isRayInWorldSpace = true, bool bReturnNearestPoint = false) override;
+
+	void setHeight(float fHeight);
+	void setMaterial(const char *szValue);
+
+	void setSize(const float3_t &vSize) override;
+
+	void setLeakAllowed(bool yesNo);
+
+private:
+	void setCorner0(const float3 &v);
+	void setCorner1(const float3 &v);
+	void setCorner2(const float3 &v);
+	void setCorner3(const float3 &v);
+
+	void setSize(const float3 &v);
+	//void updateFlags() override;
+
+	//void turnOn(inputdata_t *pInputdata);
+	//void turnOff(inputdata_t *pInputdata);
+	//void toggle(inputdata_t *pInputdata);
+	//void enable();
+	//void disable();
+
+	//SMAABB getBound();
+	void updateSize();
+
+private:
+	IXDecal *m_pDecal = NULL;
+	const char *m_szMaterial = NULL;
+
+	float3_t m_vCorner0;
+	float3_t m_vCorner1;
+	float3_t m_vCorner2;
+	float3_t m_vCorner3;
+	
+	float m_fHeight = 0.1f;
+
+	float3_t m_vSize;
+
+	bool m_isLeakAllowed = true;
+
+	//bool m_isEnabled = false;
+};
+
+#endif
diff --git a/source/game/ZombieHands.cpp b/source/game/ZombieHands.cpp
index 19b7df3658635fc40ebad62f6021db8467b8a3ec..4e8e4f1887143866e16efce5465d843585c807ef 100644
--- a/source/game/ZombieHands.cpp
+++ b/source/game/ZombieHands.cpp
@@ -1,7 +1,6 @@
 
 #include "ZombieHands.h"
 #include "BaseCharacter.h"
-#include <decals/sxdecals.h>
 #include "Tracer.h"
 
 /*! \skydocent wpn_zombie_hands
@@ -52,7 +51,7 @@ void CZombieHands::actualShoot(float dt)
 
 	if(cb.hasHit())
 	{
-		SXDecals_ShootDecal(DECAL_TYPE_BLOOD_BIG, BTVEC_F3(cb.m_hitPointWorld), BTVEC_F3(cb.m_hitNormalWorld));
+		SAFE_CALL(GetDecalProvider(), shootDecal, XDT_BLOOD_BIG, cb.m_result.vHitPoint, cb.m_result.vHitNormal);
 
 		if(cb.m_result.pCollisionObject->getUserPointer() && cb.m_result.pCollisionObject->getUserTypeId() == 1)
 		{
diff --git a/source/game/proptable.cpp b/source/game/proptable.cpp
index 619cba20ad883d8889af3b7296df202deaa73c98..57723b117e69977e564bfb0a58cf06182a0c2dec 100644
--- a/source/game/proptable.cpp
+++ b/source/game/proptable.cpp
@@ -11,14 +11,14 @@ See the license in LICENSE
 
 #include "BaseEntity.h"
 
-prop_editor_t _GetEditorCombobox(int start, ...)
+prop_editor_t _GetEditor(PDE_TYPE type, ...)
 {
 	prop_editor_t out;
-	out.type = PDE_COMBOBOX;
-	editor_kv kvs[ED_COMBO_MAXELS];
+	out.type = type;
+	kv_t kvs[ED_COMBO_MAXELS];
 
 	va_list va;
-	va_start(va, start);
+	va_start(va, type);
 
 	const char * el;
 	int i = 0;
@@ -28,56 +28,21 @@ prop_editor_t _GetEditorCombobox(int start, ...)
 		{
 			break;
 		}
-		kvs[i].key = el;
+		kvs[i].szKey = el;
 		if(!(el = va_arg(va, const char *)))
 		{
 			break;
 		}
-		kvs[i].value = el;
+		kvs[i].szValue = el;
 		++i;
 	}
-	kvs[i].value = kvs[i].key = 0;
+	kvs[i].szValue = kvs[i].szKey = 0;
 	++i;
 
 	va_end(va);
 
-	out.pData = new editor_kv[i];
-	memcpy(out.pData, kvs, sizeof(editor_kv)* i);
-	return(out);
-}
-
-prop_editor_t _GetEditorFilefield(int start, ...)
-{
-	prop_editor_t out;
-	out.type = PDE_FILEFIELD;
-	editor_kv kvs[ED_COMBO_MAXELS];
-
-	va_list va;
-	va_start(va, start);
-
-	const char * el;
-	int i = 0;
-	while(i < ED_COMBO_MAXELS)
-	{
-		if(!(el = va_arg(va, const char *)))
-		{
-			break;
-		}
-		kvs[i].key = el;
-		if(!(el = va_arg(va, const char *)))
-		{
-			break;
-		}
-		kvs[i].value = el;
-		++i;
-	}
-	kvs[i].value = kvs[i].key = 0;
-	++i;
-
-	va_end(va);
-
-	out.pData = new editor_kv[i];
-	memcpy(out.pData, kvs, sizeof(editor_kv)* i);
+	out.pData = new kv_t[i];
+	memcpy(out.pData, kvs, sizeof(kv_t)* i);
 	return(out);
 }
 
diff --git a/source/game/proptable.h b/source/game/proptable.h
index b73c430f918522042ca208b713ced369059cd7f1..3f97ecba76de87cf3078cd626635b3999668dc39 100644
--- a/source/game/proptable.h
+++ b/source/game/proptable.h
@@ -74,7 +74,8 @@ enum PDE_TYPE
 	PDE_POINTCOORD,
 	PDE_RADIUS,
 	PDE_COLOR,
-	PDE_HDRCOLOR
+	PDE_HDRCOLOR,
+	PDE_SIZE
 };
 
 enum PDF_FLAG
@@ -170,12 +171,6 @@ union PFNFIELDSET
 	int __;
 };
 
-struct editor_kv
-{
-	const char * key;
-	const char * value;
-};
-
 struct prop_editor_t
 {
 	PDE_TYPE type;
@@ -367,8 +362,7 @@ struct proptable_t
 
 #define ED_COMBO_MAXELS 256
 
-prop_editor_t _GetEditorCombobox(int start, ...);
-prop_editor_t _GetEditorFilefield(int start, ...);
+prop_editor_t _GetEditor(PDE_TYPE type, ...);
 
 #define DECLARE_PROPTABLE() \
 	\
@@ -454,6 +448,10 @@ void cls::InitPropData() \
 
 const char * GetEmptyString();
 
+#define EDITOR_BEGIN(type) _GetEditor(type
+#define EDITOR_KV(name, value) , name, value
+#define EDITOR_END() , NULL)}
+
 #define EDITOR_NONE {PDE_NONE, NULL}}
 #define EDITOR_TEXTFIELD {PDE_TEXTFIELD, NULL}}
 #define EDITOR_TIMEFIELD {PDE_TIME, NULL}}
@@ -463,19 +461,24 @@ const char * GetEmptyString();
 #define EDITOR_FLAGS {PDE_FLAGS, NULL}}
 #define EDITOR_COLOR {PDE_COLOR, NULL}}
 #define EDITOR_HDRCOLOR {PDE_HDRCOLOR, NULL}}
+#define EDITOR_SIZE {PDE_SIZE, NULL}}
+
+#define EDITOR_POINTCOORDEX EDITOR_BEGIN(PDE_POINTCOORD)
+
 
-#define EDITOR_COMBOBOX _GetEditorCombobox(0
-#define COMBO_OPTION(name, value) , name, value
-#define EDITOR_COMBO_END() , NULL)}
+#define EDITOR_COMBOBOX EDITOR_BEGIN(PDE_COMBOBOX)
+#define COMBO_OPTION(name, value) EDITOR_KV(name, value)
+#define EDITOR_COMBO_END() EDITOR_END()
 
-#define EDITOR_FILEFIELD _GetEditorFilefield(0
-#define FILE_OPTION(name, value) , name, value
-#define EDITOR_FILE_END() , NULL)}
+#define EDITOR_FILEFIELD EDITOR_BEGIN(PDE_FILEFIELD)
+#define FILE_OPTION(name, value) EDITOR_KV(name, value)
+#define EDITOR_FILE_END() EDITOR_END()
 
 #define EDITOR_YESNO EDITOR_COMBOBOX COMBO_OPTION("Yes", "1") COMBO_OPTION("No", "0") EDITOR_COMBO_END()
 #define EDITOR_MODEL EDITOR_FILEFIELD FILE_OPTION("Select model", "dse") EDITOR_FILE_END()
 #define EDITOR_SOUND EDITOR_FILEFIELD FILE_OPTION("Select sound", "ogg") EDITOR_FILE_END()
 #define EDITOR_TEXTURE EDITOR_FILEFIELD FILE_OPTION("Select texture", "dds") EDITOR_FILE_END()
+#define EDITOR_MATERIAL EDITOR_FILEFIELD FILE_OPTION("Select material", "mtl") EDITOR_FILE_END()
 #define EDITOR_EFFECT EDITOR_FILEFIELD FILE_OPTION("Select effect", "eff") EDITOR_FILE_END()
 
 #define DEFINE_FIELD_STRING(field, flags, keyname, edname, editor)              , {propdata_t::ToFieldType<const char* DataClass::*>(&DataClass::field),          PDF_STRING,  flags, keyname, edname, editor
diff --git a/source/gdefines.h b/source/gdefines.h
index 30f4bac0d425986a774eff01ce5cf0072086fefc..bc8b37e1f5df43181052d80037318ab599bb27cd 100644
--- a/source/gdefines.h
+++ b/source/gdefines.h
@@ -221,6 +221,12 @@ typedef void(*report_func) (int iLevel, const char *szLibName, const char *szMes
 #define GET_Y_LPARAM(lp)                        ((int)(short)HIWORD(lp))
 #endif
 
+struct kv_t
+{
+	const char *szKey;
+	const char *szValue;
+};
+
 /** \name Уровни критичности сообщений для функции репортов */
 //! @{
 #define REPORT_MSG_LEVEL_NOTICE		0	/*!< заметка */
diff --git a/source/gui/DOMdocument.cpp b/source/gui/DOMdocument.cpp
index 1f99d6373ffea1fbaa3e417429bbd7e3cf0ce67c..ed90d41302f870db794f988d3700c1793e0e4789 100644
--- a/source/gui/DOMdocument.cpp
+++ b/source/gui/DOMdocument.cpp
@@ -133,11 +133,11 @@ namespace gui
 
 			if(cls.length())
 			{
-				UINT pos = 0;
+				size_t pos = 0;
 				while(true)
 				{
 					pos = cls.find(L" ");
-					if(pos != (UINT)(-1))
+					if(pos != StringW::EOS)
 					{
 						StringW c = cls.substr(0, pos);
 						UINT icls = doc->getIndexForClassString(cls);
@@ -1813,14 +1813,14 @@ namespace gui
 			else if(name == L"class")
 			{
 				StringW cls = value+L" ";
-				UINT pos = 0;
+				size_t pos = 0;
 				Array<UINT> vNewCls;
 				Array<UINT> vAddCls;
 				Array<UINT> vRemoveCls;
 				while(true)
 				{
 					pos = cls.find(L" ");
-					if(pos != ~0)
+					if(pos != StringW::EOS)
 					{
 						if(pos != 0)
 						{
@@ -2045,8 +2045,8 @@ namespace gui
 		BOOL CDOMnode::classExists(const StringW &cls)
 		{
 			const StringW &wsClass = getAttribute(L"class");
-			int pos = wsClass.find(cls);
-			if(pos >= 0)
+			size_t pos = wsClass.find(cls);
+			if(pos != StringW::EOS)
 			{
 				wchar_t wc = wsClass[pos + cls.length()];
 				if(wc == 0 || iswspace(wc))
diff --git a/source/gui/Font.cpp b/source/gui/Font.cpp
index 46a55fc536762948d8c38acf96f6beffffdbcfdd..76a0400a464cfc64f35509abf83c3f61a31917e2 100644
--- a/source/gui/Font.cpp
+++ b/source/gui/Font.cpp
@@ -679,7 +679,7 @@ namespace gui
 
 	void CFont::addChar(WCHAR c, bool full)
 	{
-		if(m_szFontChars.find(c) == (UINT)-1)
+		if(m_szFontChars.find(c) == StringW::EOS)
 		{
 			m_szFontChars += c;
 			if(full)
diff --git a/source/gui/ICSS.cpp b/source/gui/ICSS.cpp
index e064c5411db60ea25a1b13638c08cee778fdd709..4d7c1017bd9c872d9be01c5bdff22560a2d529e0 100644
--- a/source/gui/ICSS.cpp
+++ b/source/gui/ICSS.cpp
@@ -235,8 +235,8 @@ namespace gui
 				}
 				if(pseudoclass & (PSEUDOCLASS_NTH_CHILD | PSEUDOCLASS_NTH_LAST_CHILD | PSEUDOCLASS_NTH_OF_TYPE | PSEUDOCLASS_NTH_LAST_OF_TYPE))
 				{
-					UINT pos = str.find(L"(");
-					if(pos != (UINT)(-1))
+					size_t pos = str.find(L"(");
+					if(pos != StringW::EOS)
 					{
 						UINT d = 0, o = 0;
 						WCHAR cn;
diff --git a/source/gui/IHTMLparser.cpp b/source/gui/IHTMLparser.cpp
index 4ca54eaf9441f48f33a3a2ca0cc179334d81d0a9..164cf3b5bcd7a5bf5dfd89d1e83db6524007c817 100644
--- a/source/gui/IHTMLparser.cpp
+++ b/source/gui/IHTMLparser.cpp
@@ -473,7 +473,7 @@ namespace gui
 											}
 											for(AssotiativeArray<StringW, StringW>::Iterator j = attrs.begin(); j; ++j)
 											{
-												pCur->setAttribute(j.first, j.second);
+												pCur->setAttribute(*j.first, *j.second);
 											}
 											attrs.clear();
 											//								wprintf(L"open tag %s\n", tagname);
diff --git a/source/light/LightSystem.cpp b/source/light/LightSystem.cpp
index 94b5e1e66fad7f1338b866bea041f7937f581cce..a69580b4e92b2f350e46460b3394b5dc19a840b6 100644
--- a/source/light/LightSystem.cpp
+++ b/source/light/LightSystem.cpp
@@ -852,7 +852,7 @@ void CLightSystem::renderGI(CGIGraphNodeData *pNodeData, IXRenderTarget *pFinalT
 
 		pCtx->addTimestamp("lpv_inject -");
 
-		const bool *dev_lpv_points = m_pCore->getConsole()->getPCVarBool("dev_lpv_points");
+		static const bool *dev_lpv_points = m_pCore->getConsole()->getPCVarBool("dev_lpv_points");
 		if(*dev_lpv_points)
 		{
 			auto pTarget = pNodeData->m_pGBufferColor->asRenderTarget();
diff --git a/source/physics/PhyWorld.cpp b/source/physics/PhyWorld.cpp
index 0dd249c57282fc0b1fd30fa45d528f3a1bc7590a..c4eb6d186be5e804983271a66b263e81adc9e567 100644
--- a/source/physics/PhyWorld.cpp
+++ b/source/physics/PhyWorld.cpp
@@ -56,7 +56,7 @@ struct XRayResultCallback: public btCollisionWorld::RayResultCallback
 		m_hitPointWorld.setInterpolate3(m_rayFromWorld, m_rayToWorld, rayResult.m_hitFraction);
 
 		m_result.vHitPoint = BTVEC_F3(m_hitPointWorld);
-		m_result.vHitNormal = BTVEC_F3(m_hitPointWorld);
+		m_result.vHitNormal = BTVEC_F3(m_hitNormalWorld);
 		m_result.pCollisionObject = m_collisionObject->getUserIndex() == 2 ? (IXCollisionObject*)m_collisionObject->getUserPointer() : NULL;
 		m_result.fHitFraction = rayResult.m_hitFraction;
 
diff --git a/source/render/Scene.cpp b/source/render/Scene.cpp
index 42e5ad8a8b557b67fe6cb19cc90f7b3a5803fc4e..46d10cb9cc88b4586996f9a5299367e1b7c2cb09 100644
--- a/source/render/Scene.cpp
+++ b/source/render/Scene.cpp
@@ -1287,6 +1287,11 @@ public:
 		return(m_pEditorExt->getRenderer());
 	}
 
+	const char* XMETHODCALLTYPE getClassKV(const char *szClassName, const char *szKey) override
+	{
+		return(NULL);
+	}
+
 private:
 	CEditorExt *m_pEditorExt = NULL;
 };
@@ -1696,3 +1701,8 @@ void CScene::print()
 	m_pRootNode->print(0, &uNodesCount, &uObjectsCount, &uMaxDepth);
 	printf("Total objects: %u, Total nodes: %u, Max depth: %u, avg objects per node: %f\n", uObjectsCount, uNodesCount, uMaxDepth, (float)uObjectsCount / (float)uNodesCount);
 }
+
+bool CScene::hasPendingOps()
+{
+	return(!m_qUpdate.empty());
+}
diff --git a/source/render/Scene.h b/source/render/Scene.h
index 4d810b09aaf95bef82711d954347bdc953ae975b..7b42aeff747a16a0e22954db3f3ce7b83bcc62d2 100644
--- a/source/render/Scene.h
+++ b/source/render/Scene.h
@@ -275,6 +275,8 @@ public:
 	bool validate();
 	void print();
 
+	bool hasPendingOps();
+
 protected:
 	void addObject(CSceneObject *pObject);
 	void removeObject(CSceneObject *pObject);
diff --git a/source/render/ShaderPreprocessor.cpp b/source/render/ShaderPreprocessor.cpp
index 567fae0a834d8dec1e8bff7f678c49070272ff8f..cdfcc149012688ae42f109f01291b015bce9f2e3 100644
--- a/source/render/ShaderPreprocessor.cpp
+++ b/source/render/ShaderPreprocessor.cpp
@@ -545,7 +545,7 @@ String CShaderPreprocessor::process(const char *src, const char *file)
 	String sOut;
 	if(out.size())
 	{
-		sOut.reserve(out.size());
+		sOut.resize(out.size());
 		memcpy((void*)(sOut.c_str()), &(out[0]), out.size() * sizeof(char));
 		((char*)(sOut.c_str()))[out.size()] = 0;
 	}
@@ -1097,7 +1097,7 @@ String CShaderPreprocessor::getInclude(const String &name, const char *szLocalPa
 		{
 			int iSize = (int)pFile->getSize();
 			String s;
-			s.reserve(iSize + 2);
+			s.resize(iSize + 1);
 			pFile->readBin((void*)(s.c_str()), iSize);
 			s[iSize] = '\n';
 			s[iSize + 1] = 0;
@@ -1119,7 +1119,7 @@ String CShaderPreprocessor::getInclude(const String &name, const char *szLocalPa
 			{
 				int iSize = (int)pFile->getSize();
 				String s;
-				s.reserve(iSize + 2);
+				s.resize(iSize + 1);
 				pFile->readBin((void*)(s.c_str()), iSize);
 				s[iSize] = '\n';
 				s[iSize + 1] = 0;
diff --git a/source/render/plugin_main.cpp b/source/render/plugin_main.cpp
index 0e510908d5fba6132a52ba7642596c3f96e1a97a..01d6d674333e70fb40f671902f51f54515d336df 100644
--- a/source/render/plugin_main.cpp
+++ b/source/render/plugin_main.cpp
@@ -15,6 +15,33 @@
 #pragma comment(lib, "sxgame.lib")
 #endif
 
+class CLoadLevelEventListener final: public IEventListener<XEventLevel>
+{
+public:
+	CLoadLevelEventListener(CScene *pScene):
+		m_pScene(pScene)
+	{
+	}
+	void onEvent(const XEventLevel *pData)
+	{
+		switch(pData->type)
+		{
+		case XEventLevel::TYPE_LOAD_WAIT_ASYNC_TASKS:
+			while(m_pScene->hasPendingOps())
+			{
+				Sleep(100);
+			}
+			break;
+
+		default:
+			break;
+		}
+	}
+
+protected:
+	CScene *m_pScene;
+};
+
 class CRenderPlugin: public IXUnknownImplementation<IXPlugin>
 {
 public:
@@ -25,6 +52,10 @@ public:
 
 	void XMETHODCALLTYPE shutdown() override
 	{
+		if(m_pLevelLoadEventListener)
+		{
+			m_pCore->getEventChannel<XEventLevel>(EVENT_LEVEL_GUID)->removeListener(m_pLevelLoadEventListener);
+		}
 	}
 
 	UINT XMETHODCALLTYPE getInterfaceCount() override
@@ -68,6 +99,9 @@ public:
 			if(!m_pScene)
 			{
 				m_pScene = new CScene(m_pCore);
+				m_pLevelLoadEventListener = new CLoadLevelEventListener(m_pScene);
+
+				m_pCore->getEventChannel<XEventLevel>(EVENT_LEVEL_GUID)->addListener(m_pLevelLoadEventListener);
 			}
 			*ppOut = m_pScene;
 			break;
@@ -144,6 +178,7 @@ protected:
 	CScene *m_pScene = NULL;
 	CUpdatable *m_pUpdatable = NULL;
 	CRender *m_pRender = NULL;
+	CLoadLevelEventListener *m_pLevelLoadEventListener = NULL;
 };
 
 DECLARE_XPLUGIN(CRenderPlugin);
diff --git a/source/terrax/CommandBuildModel.cpp b/source/terrax/CommandBuildModel.cpp
index f3ff59d42f8b94b22cca96ca04f36814cf8e700e..30484344f531eb76436cf3aa900bb4d877df50d1 100644
--- a/source/terrax/CommandBuildModel.cpp
+++ b/source/terrax/CommandBuildModel.cpp
@@ -5,7 +5,7 @@ extern AssotiativeArray<AAString, IXEditable*> g_mEditableSystems;
 
 CCommandBuildModel::CCommandBuildModel(const char *szTypeName, const char *szClassName)
 {
-	m_pCommandCreate = new CCommandCreate(float3_t(), szTypeName, szClassName);
+	m_pCommandCreate = new CCommandCreate(float3_t(), float3_t(), szTypeName, szClassName);
 }
 
 CCommandBuildModel::~CCommandBuildModel()
diff --git a/source/terrax/CommandCreate.cpp b/source/terrax/CommandCreate.cpp
index 7dac8882c0c4ba1f15c956157d58bd30376f6040..df0279c186a20a1bd50bb06be8390cf9023337ec 100644
--- a/source/terrax/CommandCreate.cpp
+++ b/source/terrax/CommandCreate.cpp
@@ -3,15 +3,15 @@
 
 extern AssotiativeArray<AAString, IXEditable*> g_mEditableSystems;
 
-CCommandCreate::CCommandCreate(const float3_t &vPos, const char *szTypeName, const char *szClassName, bool useRandomScaleYaw)
+CCommandCreate::CCommandCreate(const float3_t &vPos, const float3_t &vNorm, const char *szTypeName, const char *szClassName, bool useRandomScaleYaw):
+	m_vPos(vPos),
+	m_vNorm(vNorm),
+	m_sClassName(szClassName)
 {
-	m_vPos = vPos;
-	m_sClassName = szClassName;
-
 	if(useRandomScaleYaw)
 	{
 		m_fScale = randf(0.7, 1.3);
-		m_qOrient = SMQuaternion(randf(0, SM_2PI), 'y');
+		m_fRotAngle = randf(0, SM_2PI);
 	}
 
 	const AssotiativeArray<AAString, IXEditable*>::Node *pNode;
@@ -42,10 +42,82 @@ bool XMETHODCALLTYPE CCommandCreate::exec()
 	m_pObject->setPos(m_vPos);
 	//! @TODO Implement random scale?
 	//m_pObject->setScale(float3(m_fScale));
-	m_pObject->setOrient(m_qOrient);
+	const char *szNormalAlign = m_pEditable->getClassKV(m_sClassName.c_str(), "align_with_norm");
+	if(!SMIsZero(SMVector3Length2(m_vNorm)) && szNormalAlign)
+	{
+		char cAxis = szNormalAlign[0];
+		float fVal = 1.0f;
+		if(cAxis == '-')
+		{
+			cAxis = szNormalAlign[1];
+			float fVal = -1.0f;
+		}
+		float3_t vFrom;
+		switch(tolower(cAxis))
+		{
+		case 'x':
+			vFrom.x = fVal;
+			break;
+		case 'y':
+			vFrom.y = fVal;
+			break;
+		case 'z':
+			vFrom.z = fVal;
+			break;
+		}
+		SMQuaternion qRot(vFrom, m_vNorm);
+		m_pObject->setOrient(qRot * SMQuaternion(m_vNorm, m_fRotAngle));
+	}
+	else
+	{
+		m_pObject->setOrient(SMQuaternion(m_fRotAngle, 'y'));
+	}
 	m_pObject->setSelected(true);
 	m_pObject->create();
 
+	if(!SMIsZero(SMVector3Length2(m_vNorm)) && !m_pEditable->getClassKV(m_sClassName.c_str(), "no_height_adj"))
+	{
+		float3 vMin, vMax;
+		m_pObject->getBound(&vMin, &vMax);
+
+		float3 avPoints[] = {
+			float3(vMin),
+			float3(vMin.x, vMin.y, vMax.z),
+			float3(vMin.x, vMax.y, vMin.z),
+			float3(vMin.x, vMax.y, vMax.z),
+			float3(vMax.x, vMin.y, vMin.z),
+			float3(vMax.x, vMin.y, vMax.z),
+			float3(vMax.x, vMax.y, vMin.z),
+			float3(vMax),
+		};
+		//printf("%.2f, %.2f, %.2f\n", vNorm.x, vNorm.y, vNorm.z);
+
+		float fProj = FLT_MAX;
+		for(UINT i = 0, l = ARRAYSIZE(avPoints); i < l; ++i)
+		{
+			float fTmp = SMVector3Dot(avPoints[i], m_vNorm);
+			//printf("%.2f\n", fTmp);
+			if(fTmp < fProj)
+			{
+				fProj = fTmp;
+			}
+		}
+
+		fProj = SMVector3Dot(m_vPos, m_vNorm) - fProj;
+		//printf("%.2f, %.2f, %.2f\n%.2f, %.2f, %.2f\n%.2f, %.2f, %.2f\n%f\n\n", vPos.x, vPos.y, vPos.z, vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z, fProj);
+		if(fProj > 0)
+		{
+			m_pObject->setPos((float3)(m_vPos + m_vNorm * fProj));
+		}
+	}
+
+	const char *szMaterialField = m_pEditable->getClassKV(m_sClassName.c_str(), "material_field");
+	if(szMaterialField)
+	{
+		m_sMaterial = g_pEditor->getMaterialBrowser()->getCurrentMaterial();
+		m_pObject->setKV(szMaterialField, m_sMaterial.c_str());
+	}
+
 	g_pEditor->addObject(m_pObject);
 
 	XUpdatePropWindow();
diff --git a/source/terrax/CommandCreate.h b/source/terrax/CommandCreate.h
index 7b72e90d6951e71d3c9341e4bf8d7b3caa57e828..ba0b86dee61cb72cf1a9bb2e7985b64f7ac614f0 100644
--- a/source/terrax/CommandCreate.h
+++ b/source/terrax/CommandCreate.h
@@ -11,7 +11,7 @@
 class CCommandCreate final: public IXUnknownImplementation<IXEditorCommand>
 {
 public:
-	CCommandCreate(const float3_t &vPos, const char *szTypeName, const char *szClassName, bool useRandomScaleYaw=false);
+	CCommandCreate(const float3_t &vPos, const float3_t &vNorm, const char *szTypeName, const char *szClassName, bool useRandomScaleYaw = false);
 	~CCommandCreate();
 
 	bool XMETHODCALLTYPE exec() override;
@@ -34,11 +34,13 @@ public:
 
 protected:
 	float3_t m_vPos;
-	SMQuaternion m_qOrient;
+	float3_t m_vNorm;
+	float m_fRotAngle = 0.0f;
 	float m_fScale = 1.0f;
 	String m_sClassName;
 	IXEditable *m_pEditable = NULL;
 	IXEditorObject *m_pObject = NULL;
+	String m_sMaterial;
 };
 
 #endif
diff --git a/source/terrax/Editor.cpp b/source/terrax/Editor.cpp
index e17b7ce2adc3b572aa9e263cca13c1cfda303392..51185b8270e370ecc459640a978b514bc7400f05 100644
--- a/source/terrax/Editor.cpp
+++ b/source/terrax/Editor.cpp
@@ -38,6 +38,8 @@ CEditor::CEditor(IXCore *pCore):
 	pCore->getPluginManager()->registerInterface(IXCOLORPICKER_GUID, &m_colorPicker);
 
 	m_pSceneTreeWindow = new CSceneTreeWindow(this, pCore);
+
+	registerResourceBrowser(new CResourceBrowser());
 }
 
 CEditor::~CEditor()
diff --git a/source/terrax/Editor.h b/source/terrax/Editor.h
index fb18b38d0b571fa69c8fce49d0453603c580b7ce..3f15126b872c664856d8b2da1629f4a8945e3cf8 100644
--- a/source/terrax/Editor.h
+++ b/source/terrax/Editor.h
@@ -15,6 +15,7 @@
 #include "ColorGradientEditorDialog.h"
 #include "ColorPicker.h"
 #include "SceneTreeWindow.h"
+#include "ResourceBrowser.h"
 
 #define GIZMO_TYPES() \
 	GTO(Handle)\
diff --git a/source/terrax/GizmoRotate.cpp b/source/terrax/GizmoRotate.cpp
index 763ce2076e5ccc4cefe0995d7f4a877d7f5e7348..f76dba18a05bed8aec9b22bfc12b94902ecdb572 100644
--- a/source/terrax/GizmoRotate.cpp
+++ b/source/terrax/GizmoRotate.cpp
@@ -1,5 +1,6 @@
 #include "GizmoRotate.h"
 #include "Editor.h"
+#include "terrax.h"
 
 CGizmoRotate::CGizmoRotate(CEditor *pEditor):
 	m_pEditor(pEditor)
@@ -266,7 +267,10 @@ void CGizmoRotate::setWorldRay(const float3 &vRayOrigin, const float3 &vRayDir)
 			fAngle = SM_2PI - fAngle;
 		}
 
-		fAngle = round_step(fAngle, 5.0f * SM_PIDIV180);
+		if(g_xConfig.m_bSnapGrid)
+		{
+			fAngle = round_step(fAngle, 5.0f * SM_PIDIV180);
+		}
 
 		SAFE_CALL(m_pCallback, onRotate, m_vCurDir, fAngle, this)
 		else
diff --git a/source/terrax/GroupObject.cpp b/source/terrax/GroupObject.cpp
index 5beb78e13a37a1a825bd2c00fd5406de316446e1..5fa1a1aa1a60ef5e00e2cd63c3326ec7ac12e930 100644
--- a/source/terrax/GroupObject.cpp
+++ b/source/terrax/GroupObject.cpp
@@ -303,7 +303,7 @@ void CGroupObject::addChildObject(IXEditorObject *pObject)
 
 	ICompoundObject *pOldContainer = XTakeObject(pObject, this);
 	assert(pOldContainer == NULL);
-	//add_ref(pObject);
+	add_ref(pObject);
 	m_aObjects.push_back({pObject, m_qOrient.Conjugate() * (pObject->getPos() - m_vPos), pObject->getOrient() * m_qOrient.Conjugate()});
 	
 	g_pEditor->onObjectAdded(pObject);
@@ -323,7 +323,7 @@ void CGroupObject::removeChildObject(IXEditorObject *pObject)
 
 		g_pEditor->onObjectRemoved(pObject);
 
-		//mem_release(pObject);
+		mem_release(pObject);
 
 		if(!m_aObjects.size())
 		{
diff --git a/source/terrax/PropertyWindow.cpp b/source/terrax/PropertyWindow.cpp
index 07f5fd470d3462dbfe258ca4a945ef9f92aac320..475f2471c523e876fa3eba1e55e7639d3c323cc2 100644
--- a/source/terrax/PropertyWindow.cpp
+++ b/source/terrax/PropertyWindow.cpp
@@ -63,6 +63,7 @@ static UINT g_uEditorDlgIds[] = {
 	IDD_PROPEDIT_TEXT,
 	IDD_PROPEDIT_TEXT,
 	IDD_PROPEDIT_TEXT,
+	IDD_PROPEDIT_TEXT,
 };
 
 static_assert(ARRAYSIZE(g_uEditorDlgIds) == XPET__LAST, "g_uEditorDlgIds must match X_PROP_EDITOR_TYPE");
@@ -515,7 +516,7 @@ INT_PTR CALLBACK CPropertyWindow::dlgProc(HWND hWnd, UINT msg, WPARAM wParam, LP
 
 			memset(&ofn, 0, sizeof(OPENFILENAMEW));
 			ofn.lStructSize = sizeof(OPENFILENAMEW);
-			ofn.hwndOwner = NULL;
+			ofn.hwndOwner = m_hDlgWnd;
 			ofn.lpstrFile = szFile;
 			ofn.nMaxFile = sizeof(szFile);
 			ofn.lpstrFilter = szFilter;
@@ -819,7 +820,7 @@ void CPropertyWindow::initEditor(X_PROP_EDITOR_TYPE type, const void *pData, con
 		{
 			HWND hCombo = GetDlgItem(hEditorDlg, IDC_OPE_COMBO);
 			ComboBox_ResetContent(hCombo);
-			edt_kv *pKV = (edt_kv*)pData;
+			kv_t *pKV = (kv_t*)pData;
 			while(pKV->szKey)
 			{
 				ComboBox_AddString(hCombo, pKV->szKey);
diff --git a/source/terrax/PropertyWindow.h b/source/terrax/PropertyWindow.h
index 9047f29ce296a1cbb3b513e0ef1095741b25f932..2f8db9f8154cb4f20569f33891098f0abba36205 100644
--- a/source/terrax/PropertyWindow.h
+++ b/source/terrax/PropertyWindow.h
@@ -105,13 +105,7 @@ protected:
 		X_PROP_FIELD field;
 		String sValue;
 	};
-
-	struct edt_kv
-	{
-		const char *szKey;
-		const char *szValue;
-	};
-
+	
 	AssotiativeArray<AAString, prop_s> m_aPropFields;
 	X_PROP_EDITOR_TYPE m_editorActive = XPET__LAST;
 
diff --git a/source/terrax/ResourceBrowser.cpp b/source/terrax/ResourceBrowser.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5f0be58de59bbfd1f595898efd1bd4e7537348d6
--- /dev/null
+++ b/source/terrax/ResourceBrowser.cpp
@@ -0,0 +1,26 @@
+#include "ResourceBrowser.h"
+#include "terrax.h"
+
+UINT XMETHODCALLTYPE CResourceBrowser::getResourceTypeCount()
+{
+	return(1);
+}
+const char* XMETHODCALLTYPE CResourceBrowser::getResourceType(UINT uId)
+{
+	if(uId == 0)
+	{
+		return("material");
+	}
+
+	return(NULL);
+}
+
+void XMETHODCALLTYPE CResourceBrowser::browse(const char *szType, const char *szOldValue, IXEditorResourceBrowserCallback *pCallback)
+{
+	m_callback.init(pCallback);
+	g_pMaterialBrowser->browse(&m_callback);
+}
+void XMETHODCALLTYPE CResourceBrowser::cancel()
+{
+	g_pMaterialBrowser->abort();
+}
diff --git a/source/terrax/ResourceBrowser.h b/source/terrax/ResourceBrowser.h
new file mode 100644
index 0000000000000000000000000000000000000000..04b530571e612b34d2ab3904d5d6de3979039b22
--- /dev/null
+++ b/source/terrax/ResourceBrowser.h
@@ -0,0 +1,46 @@
+#ifndef __RESOURCEBROWSER_H
+#define __RESOURCEBROWSER_H
+
+#include <xcommon/editor/IXEditorExtension.h>
+#include "MaterialBrowser.h"
+
+class CResourceMaterialBrowserCallback: public IMaterialBrowserCallback
+{
+public:
+	void init(IXEditorResourceBrowserCallback *pCallback)
+	{
+		SAFE_CALL(m_pCallback, onCancelled);
+		m_pCallback = pCallback;
+	}
+	void onSelected(const char *szName) override
+	{
+		SAFE_CALL(m_pCallback, onSelected, szName);
+		m_pCallback = NULL;
+	}
+	void onCancel() override
+	{
+		SAFE_CALL(m_pCallback, onCancelled);
+		m_pCallback = NULL;
+	}
+
+
+private:
+	IXEditorResourceBrowserCallback *m_pCallback = NULL;
+};
+
+//#############################################################################
+
+class CResourceBrowser: public IXUnknownImplementation<IXEditorResourceBrowser>
+{
+public:
+	UINT XMETHODCALLTYPE getResourceTypeCount() override;
+	const char* XMETHODCALLTYPE getResourceType(UINT uId) override;
+
+	void XMETHODCALLTYPE browse(const char *szType, const char *szOldValue, IXEditorResourceBrowserCallback *pCallback) override;
+	void XMETHODCALLTYPE cancel() override;
+
+private:
+	CResourceMaterialBrowserCallback m_callback;
+};
+
+#endif
diff --git a/source/terrax/mainWindow.cpp b/source/terrax/mainWindow.cpp
index 2b9b8270d6d0ea98f04ab754583d675e0cc27b04..763ed59dfcc6e89cb97f7dec07fc4299f6543749 100644
--- a/source/terrax/mainWindow.cpp
+++ b/source/terrax/mainWindow.cpp
@@ -972,7 +972,7 @@ guid = {9D7D2E62-24C7-42B7-8D83-8448FC4604F0}
 	mem_release(pConfig);
 }
 
-static CCommandCreate* CreateObjectAtPosition(const float3 &vPos, bool bDeselectAll)
+static CCommandCreate* CreateObjectAtPosition(const float3 &vPos, const float3 &vNorm, bool bDeselectAll)
 {
 	int iSel1 = ComboBox_GetCurSel(g_hComboTypesWnd);
 	int iLen1 = ComboBox_GetLBTextLen(g_hComboTypesWnd, iSel1);
@@ -989,7 +989,7 @@ static CCommandCreate* CreateObjectAtPosition(const float3 &vPos, bool bDeselect
 		SendMessage(g_hWndMain, WM_COMMAND, MAKEWPARAM(ID_EDIT_CLEARSELECTION, 0), (LPARAM)0);
 	}
 
-	CCommandCreate *pCmd = new CCommandCreate(vPos, szTypeName, szClassName, Button_GetCheck(g_hCheckboxRandomScaleYawWnd));
+	CCommandCreate *pCmd = new CCommandCreate(vPos, vNorm, szTypeName, szClassName, Button_GetCheck(g_hCheckboxRandomScaleYawWnd));
 	g_pUndoManager->execCommand(pCmd);
 
 	return(pCmd);
@@ -1947,7 +1947,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 		case IDC_CTRL_RETURN:
 			if(g_xState.bCreateMode)
 			{
-				CreateObjectAtPosition(g_xState.vCreateOrigin, GetKeyState(VK_CONTROL) >= 0);
+				CreateObjectAtPosition(g_xState.vCreateOrigin, float3(0.0f), GetKeyState(VK_CONTROL) >= 0);
 
 				g_xState.bCreateMode = false;
 			}
@@ -2085,6 +2085,18 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 			XSetXformType(X2DXF_ROTATE);
 			break;
 
+		case ID_XFORM_MODE_CENTER:
+			g_xConfig.m_bUsePivot = false;
+			CheckToolbarButton(ID_XFORM_MODE_PIVOT, g_xConfig.m_bUsePivot);
+			CheckToolbarButton(ID_XFORM_MODE_CENTER, !g_xConfig.m_bUsePivot);
+
+			break;
+		case ID_XFORM_MODE_PIVOT:
+			g_xConfig.m_bUsePivot = true;
+			CheckToolbarButton(ID_XFORM_MODE_PIVOT, g_xConfig.m_bUsePivot);
+			CheckToolbarButton(ID_XFORM_MODE_CENTER, !g_xConfig.m_bUsePivot);
+			break;
+
 		case IDC_TIE_TO_OBJECT:
 			if(IsWindowEnabled(g_hButtonToEntityWnd))
 			{
@@ -3273,40 +3285,8 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP
 					vPos = s_aRaytracedItems[0].vPos;
 					vNorm = SMVector3Normalize(s_aRaytracedItems[0].vNorm);
 
-					IXEditorObject *pObj = CreateObjectAtPosition(vPos, GetKeyState(VK_CONTROL) >= 0)->getObject();
-					float3 vMin, vMax;
-					pObj->getBound(&vMin, &vMax);
-
-					float3 avPoints[] = {
-						float3(vMin),
-						float3(vMin.x, vMin.y, vMax.z),
-						float3(vMin.x, vMax.y, vMin.z),
-						float3(vMin.x, vMax.y, vMax.z),
-						float3(vMax.x, vMin.y, vMin.z),
-						float3(vMax.x, vMin.y, vMax.z),
-						float3(vMax.x, vMax.y, vMin.z),
-						float3(vMax),
-					};
-					//printf("%.2f, %.2f, %.2f\n", vNorm.x, vNorm.y, vNorm.z);
-
-					float fProj = FLT_MAX;
-					for(UINT i = 0, l = ARRAYSIZE(avPoints); i < l; ++i)
-					{
-						float fTmp = SMVector3Dot(avPoints[i], vNorm);
-						//printf("%.2f\n", fTmp);
-						if(fTmp < fProj)
-						{
-							fProj = fTmp;
-						}
-					}
-
-					fProj = SMVector3Dot(vPos, vNorm) - fProj;
-					//printf("%.2f, %.2f, %.2f\n%.2f, %.2f, %.2f\n%.2f, %.2f, %.2f\n%f\n\n", vPos.x, vPos.y, vPos.z, vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z, fProj);
-					if(fProj > 0)
-					{
-						pObj->setPos((float3)(vPos + vNorm * fProj));
-					}
-
+					IXEditorObject *pObj = CreateObjectAtPosition(vPos, vNorm, GetKeyState(VK_CONTROL) >= 0)->getObject();
+					
 					s_aRaytracedItems.clearFast();
 				}
 			}
@@ -3418,7 +3398,7 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP
 							{
 								// create rotate command
 								s_pRotateCmd = new CCommandRotate(GetKeyState(VK_SHIFT) < 0);
-								s_pRotateCmd->setStartOrigin((g_xState.vSelectionBoundMax + g_xState.vSelectionBoundMin) * 0.5f * vMask, float3(1.0f) - vMask);
+								s_pRotateCmd->setStartOrigin((g_xConfig.m_bUsePivot ? g_xState.vSelectionPivot : (g_xState.vSelectionBoundMax + g_xState.vSelectionBoundMin) * 0.5f) * vMask, float3(1.0f) - vMask);
 								s_pRotateCmd->setStartPos(vStartPos);
 								XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){
 									if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/)
@@ -4487,7 +4467,7 @@ case editor_type:
 		if(pItem->m_pObj == pObj && !fstrcmp(pItem->m_field.szKey, pField->szKey))       \
 		{                                                                                \
 			pItem->m_uUpdateRevision = g_uPropGizmoUpdateRevision;                       \
-			pItem->init();                                                               \
+			pItem->init(pField->pEditorData);                                            \
 			return;                                                                      \
 		}                                                                                \
 	}                                                                                    \
@@ -4496,7 +4476,7 @@ case editor_type:
 		pCb->m_pObj = pObj;                                                              \
 		pCb->m_field = *pField;                                                          \
 		pCb->m_uUpdateRevision = g_uPropGizmoUpdateRevision;                             \
-		pCb->init();                                                                     \
+		pCb->init(pField->pEditorData);                                                  \
 		g_aPropGizmo##gizmo##Items.push_back(pCb);                                       \
 	}                                                                                    \
 	break
@@ -4519,7 +4499,7 @@ XDECLARE_PROP_GIZMO(Radius, void XMETHODCALLTYPE onChange(float fNewRadius, IXEd
 	char tmp[16];
 	sprintf(tmp, "%f", fNewRadius);
 	m_pCommand->setKV(m_field.szKey, tmp);
-}, void init()
+}, void init(const void *pEditorData)
 {
 	m_pGizmo->setPos(m_pObj->getPos());
 	float fRadius;
@@ -4536,13 +4516,69 @@ XDECLARE_PROP_GIZMO(Handle, void XMETHODCALLTYPE moveTo(const float3 &vNewPos, I
 	char tmp[64];
 	sprintf(tmp, "%f %f %f", vTmp.x, vTmp.y, vTmp.z);
 	m_pCommand->setKV(m_field.szKey, tmp);
-}, void init()
+}, void init(const void *pEditorData)
 {
 	float3_t vec;
 	if(sscanf(m_pObj->getKV(m_field.szKey), "%f %f %f", &vec.x, &vec.y, &vec.z))
 	{
 		m_pGizmo->setPos(m_pObj->getOrient() * vec + m_pObj->getPos());
 	}
+
+	if(pEditorData)
+	{
+		kv_t *pKV = (kv_t*)pEditorData;
+		bool bLockPlane = false;
+		bool bLockAxis = false;
+		bool bLockLocal = true;
+		char axis = 0;
+		while(pKV->szKey)
+		{
+			if(!strcmp(pKV->szKey, "lock"))
+			{
+				if(!strcmp(pKV->szValue, "plane"))
+				{
+					bLockPlane = true;
+				}
+				else if(!strcmp(pKV->szValue, "axis"))
+				{
+					bLockAxis = true;
+				}
+			}
+			else if(!strcmp(pKV->szKey, "axis"))
+			{
+				axis = pKV->szValue[0];
+			}
+			else if(!strcmp(pKV->szKey, "local"))
+			{
+				if(pKV->szValue[0] != '0')
+				{
+					bLockLocal = true;
+				}
+			}
+			++pKV;
+		}
+
+		if((bLockPlane || bLockAxis) && axis)
+		{
+			float3_t vAxis(axis == 'x' ? 1.0f : 0.0, axis == 'y' ? 1.0f : 0.0, axis == 'z' ? 1.0f : 0.0);
+			if(bLockLocal)
+			{
+				vAxis = m_pObj->getOrient() * vAxis;
+			}
+			if(bLockPlane)
+			{
+				m_pGizmo->lockInPlane(vAxis);
+			}
+			else
+			{
+				m_pGizmo->lockInDir(vAxis);
+			}
+		}
+		else
+		{
+			m_pGizmo->unLock();
+		}
+	}
 });
 
 UINT g_uPropGizmoUpdateRevision = 0;
@@ -4729,7 +4765,7 @@ void XUpdateGizmos()
 {
 	if(Button_GetCheck(g_hABArrowButton) && g_xState.bHasSelection)
 	{
-		float3 vPos = (g_xState.vSelectionBoundMin + g_xState.vSelectionBoundMax) * 0.5f;
+		float3 vPos = g_xConfig.m_bUsePivot ? g_xState.vSelectionPivot : (g_xState.vSelectionBoundMin + g_xState.vSelectionBoundMax) * 0.5f;
 		g_pGizmoMove->setPos(vPos);
 		if(!g_gizmoRotateCallback.isActive())
 		{
@@ -4849,7 +4885,7 @@ HWND CreateToolbar(HWND hWndParent)
 {
 	// Declare and initialize local constants.
 	const int ImageListID = 0;
-	const int numButtons = 8;
+	const int numButtons = 10;
 	const int bitmapSize = 16;
 
 	const DWORD buttonStyles = BTNS_AUTOSIZE;
@@ -4909,6 +4945,9 @@ HWND CreateToolbar(HWND hWndParent)
 		{MAKELONG(1, ImageListID), ID_XFORM_TRANSLATE, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Move [W]"},
 		{MAKELONG(2, ImageListID), ID_XFORM_ROTATE, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Rotate [R]"},
 		{0, 0, TBSTATE_ENABLED, BTNS_SEP, 0L, 0},
+		{MAKELONG(8, ImageListID), ID_XFORM_MODE_CENTER, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Use center"},
+		{MAKELONG(9, ImageListID), ID_XFORM_MODE_PIVOT, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Use pivot"},
+		{0, 0, TBSTATE_ENABLED, BTNS_SEP, 0L, 0},
 		{MAKELONG(6, ImageListID), ID_TOOLS_GROUP, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Group selected [Ctrl+G]"},
 		{MAKELONG(7, ImageListID), ID_TOOLS_UNGROUP, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Ungroup selected [Ctrl+U]"},
 		{MAKELONG(5, ImageListID), ID_IGNORE_GROUPS, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Toggle group ignore [Ctrl+W]"},
diff --git a/source/terrax/resource.h b/source/terrax/resource.h
index bdbd2937659c1c2521c71074b5341ca191f4b3a6..8b0c2331f8a92e4071aab850ebda31aae84af055 100644
Binary files a/source/terrax/resource.h and b/source/terrax/resource.h differ
diff --git a/source/terrax/resource/toolbar1.bmp b/source/terrax/resource/toolbar1.bmp
index dbb1f0b437a3419ade41cafc9834107c00921d22..eb7529bf301a6746730ae4c975acc9601860c34a 100644
Binary files a/source/terrax/resource/toolbar1.bmp and b/source/terrax/resource/toolbar1.bmp differ
diff --git a/source/terrax/resource/toolbar2.bmp b/source/terrax/resource/toolbar2.bmp
index 5051572bc72dbbd0c0b1a1af2b48dcd827182f2d..e581a1f306259eb0cd1bb90037e55cad7a7a2a8e 100644
Binary files a/source/terrax/resource/toolbar2.bmp and b/source/terrax/resource/toolbar2.bmp differ
diff --git a/source/terrax/terrax.cpp b/source/terrax/terrax.cpp
index 3c256fea72a642fd489ff99dbf2b43a3c2ff8ea8..3a73567ac904ab6f2d5fc1e6b3a97cc438d8095a 100644
--- a/source/terrax/terrax.cpp
+++ b/source/terrax/terrax.cpp
@@ -736,6 +736,8 @@ int main(int argc, char **argv)
 	g_pXformEventChannel = pEngine->getCore()->getEventChannel<XEventEditorXformType>(EVENT_EDITOR_XFORM_TYPE_GUID);
 
 	XSetXformType(X2DXF_SCALE);
+	CheckToolbarButton(ID_XFORM_MODE_PIVOT, g_xConfig.m_bUsePivot);
+	CheckToolbarButton(ID_XFORM_MODE_CENTER, !g_xConfig.m_bUsePivot);
 
 	RECT rcTopLeft;
 	GetClientRect(g_hTopLeftWnd, &rcTopLeft);
@@ -825,7 +827,7 @@ int main(int argc, char **argv)
 						g_pEditor->registerResourceBrowser(pResourceBrowser);
 						mem_release(pResourceBrowser);
 						// XInitTool(pResourceBrowser, pEditable);
-			}
+					}
 				}
 			}
 
@@ -932,6 +934,33 @@ int main(int argc, char **argv)
 						}
 					}
 
+					XUpdateStatusGrid();
+					
+					szVal = pCfg->getKey("terrax", "ignore_groups");
+					if(szVal)
+					{
+						int iVal = 0;
+						if(sscanf(szVal, "%d", &iVal))
+						{
+							g_xConfig.m_bIgnoreGroups = iVal != 0;
+							CheckToolbarButton(ID_IGNORE_GROUPS, g_xConfig.m_bIgnoreGroups);
+						}
+					}
+
+					szVal = pCfg->getKey("terrax", "xform_use_pivot");
+					if(szVal)
+					{
+						int iVal = 0;
+						if(sscanf(szVal, "%d", &iVal))
+						{
+							g_xConfig.m_bUsePivot = iVal != 0;
+							CheckToolbarButton(ID_XFORM_MODE_PIVOT, g_xConfig.m_bUsePivot);
+							CheckToolbarButton(ID_XFORM_MODE_CENTER, !g_xConfig.m_bUsePivot);
+						}
+					}
+
+					
+
 					for(UINT i = 0; i < 4; ++i)
 					{
 						float3 vec;
@@ -1079,6 +1108,12 @@ int main(int argc, char **argv)
 				sprintf_s(szVal, "%d", g_xConfig.m_bShowGrid ? 1 : 0);
 				pCfg->set("terrax", "grid_show", szVal);
 
+				sprintf_s(szVal, "%d", g_xConfig.m_bIgnoreGroups ? 1 : 0);
+				pCfg->set("terrax", "ignore_groups", szVal);
+
+				sprintf_s(szVal, "%d", g_xConfig.m_bUsePivot ? 1 : 0);
+				pCfg->set("terrax", "xform_use_pivot", szVal);
+				
 				pCfg->save();
 				mem_release(pCfg);
 
@@ -2617,21 +2652,53 @@ void XUpdateSelectionBound()
 	g_xState.bHasSelection = false;
 	float3 vMin, vMax;
 
+	extern HWND g_hABArrowButton;
+	bool bTrackPivotObject = g_xConfig.m_bUsePivot && g_xState.activeWindow == XWP_TOP_LEFT && Button_GetCheck(g_hABArrowButton);
+	float fBestDist = FLT_MAX;
+
 	XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){
 		if(pObj->isSelected()/* && !(g_xConfig.m_bIgnoreGroups && isProxy)*/)
 		{
 			pObj->getBound(&vMin, &vMax);
+
+			if(bTrackPivotObject)
+			{
+				// raytest
+				g_xState.vWorldRayStart;
+				g_xState.vWorldRayDir;
+				if(pObj->hasVisualModel())
+				{
+					float3 vPos;
+					if(pObj->rayTest(g_xState.vWorldRayStart, g_xState.vWorldRayStart + g_xState.vWorldRayDir * 1000.0f, &vPos))
+					{
+						float fDist2 = SMVector3Length2(g_xState.vWorldRayStart - vPos);
+						if(fDist2 < fBestDist)
+						{
+							fBestDist = fDist2;
+							g_xState.pPivotSource = pObj;
+						}
+					}
+				}
+			}
+
 			if(!g_xState.bHasSelection)
 			{
 				g_xState.bHasSelection = true;
 				g_xState.vSelectionBoundMax = vMax;
 				g_xState.vSelectionBoundMin = vMin;
+				g_xState.vSelectionPivot = pObj->getPos();
 			}
 			else
 			{
 				g_xState.vSelectionBoundMax = (float3)SMVectorMax(g_xState.vSelectionBoundMax, vMax);
 				g_xState.vSelectionBoundMin = (float3)SMVectorMin(g_xState.vSelectionBoundMin, vMin);
 			}
+
+			if(g_xState.pPivotSource == pObj)
+			{
+				g_xState.vSelectionPivot = pObj->getPos();
+			}
+
 			return(XEOR_SKIP_CHILDREN);
 		}
 		return(XEOR_CONTINUE);
diff --git a/source/terrax/terrax.h b/source/terrax/terrax.h
index 0f512ce832315f45f4e01dfa3faa42037ee384f1..0185bbc4a1dc142404940dce31f3b53946d79143 100644
--- a/source/terrax/terrax.h
+++ b/source/terrax/terrax.h
@@ -92,6 +92,7 @@ struct CTerraXConfig
 	bool m_bDottedGrid = false;
 	float m_fGridOpacity = 0.5f;
 	bool m_bIgnoreGroups = false;
+	bool m_bUsePivot = false;
 
 	X_VIEWPORT_LAYOUT m_xViewportLayout = XVIEW_2X2;
 };
@@ -108,6 +109,8 @@ struct CTerraXState: public TerraXState
 	bool bHasSelection = false;
 	float3_t vSelectionBoundMin;
 	float3_t vSelectionBoundMax;
+	float3_t vSelectionPivot;
+	IXEditorObject *pPivotSource = NULL;
 
 	X_2DXFORM_TYPE xformType = X2DXF_SCALE;
 
@@ -299,6 +302,7 @@ bool XIsMouseInBound(X_WINDOW_POS wnd, const float3_t &vBoundMin, const float3_t
 
 void XUpdatePropWindow();
 void XUpdateGizmos();
+void XUpdateStatusGrid();
 
 void XInitTypesCombo();
 
diff --git a/source/xParticles/Editable.h b/source/xParticles/Editable.h
index af162845fe61a18fbfdc016fd118a546abdc6f4c..bbfe486f44d41ad889888c5f3b0df3289089b280 100644
--- a/source/xParticles/Editable.h
+++ b/source/xParticles/Editable.h
@@ -79,6 +79,11 @@ public:
 		return(false);
 	}
 
+	const char* XMETHODCALLTYPE getClassKV(const char *szClassName, const char *szKey) override
+	{
+		return(NULL);
+	}
+
 private:
 	CEditorObject* getObjectByGUID(const XGUID &guid);
 
diff --git a/source/xSound/SoundSystem.cpp b/source/xSound/SoundSystem.cpp
index fbd7d88ca98b20507b44714a95f52f1c2897144d..7a79ed7a4448d0801b231a9ec0ba788c13d00695 100644
--- a/source/xSound/SoundSystem.cpp
+++ b/source/xSound/SoundSystem.cpp
@@ -243,9 +243,9 @@ IXAudioCodecTarget* CSoundSystem::getCodecTarget(const char *szName)
 
 	for(MapCodec::Iterator i = m_mapCodecs.begin(); i; ++i)
 	{
-		if(m_mapCodecs[i.first]->open(szPath, "", &pTarget, false))
+		if(m_mapCodecs[*i.first]->open(szPath, "", &pTarget, false))
 		{
-			pCodec = m_mapCodecs[i.first];
+			pCodec = m_mapCodecs[*i.first];
 			break;
 		}
 	}
@@ -411,8 +411,8 @@ IXAudioCodec* CSoundSystem::getCodecSave()
 {
 	for(MapCodec::Iterator i = m_mapCodecs.begin(); i; ++i)
 	{
-		if(m_mapCodecs[i.first]->canSave())
-			return m_mapCodecs[i.first];
+		if(m_mapCodecs[*i.first]->canSave())
+			return m_mapCodecs[*i.first];
 	}
 
 	return NULL;
diff --git a/source/xUI/UIControl.h b/source/xUI/UIControl.h
index 34803a3db394e2cda656209e536171a1cab9f954..fab4481021018a395ef4510d7d216d83fd7b949f 100644
--- a/source/xUI/UIControl.h
+++ b/source/xUI/UIControl.h
@@ -274,7 +274,7 @@ protected:
 	gui::dom::IDOMnode *m_pNode = NULL;
 	gui::dom::IDOMnode *m_pInputNode = NULL;
 	gui::dom::IDOMnode *m_pContainerNode = NULL;
-	const ULONG m_id;
+	const UINT m_id;
 	const String m_sName;
 
 	friend class CUIWindow;
diff --git a/source/xUI/UIViewport.cpp b/source/xUI/UIViewport.cpp
index 989921e9b303b116db11a8f351c3a6adaac8ec87..495b9560f381daa598a714ad30deb99776ff3455 100644
--- a/source/xUI/UIViewport.cpp
+++ b/source/xUI/UIViewport.cpp
@@ -46,5 +46,5 @@ void XMETHODCALLTYPE CUIViewport::setSize(float fSizeX, float fSizeY)
 {
 	BaseClass::setSize(fSizeX, fSizeY);
 
-	m_pRenderTarget->resize((UINT)fSizeX, (UINT)fSizeY);
+	m_pRenderTarget->resize(fSizeX > 1.0f ? (UINT)fSizeX : 1, fSizeY > 1.0f ? (UINT)fSizeY : 1);
 }
diff --git a/source/xUI/UIWindow.cpp b/source/xUI/UIWindow.cpp
index 4fa6863ed70af72eb8a495facfa4295db91fd1cb..a6ae35a5e2741262a33708894ba7c31cc7700207 100644
--- a/source/xUI/UIWindow.cpp
+++ b/source/xUI/UIWindow.cpp
@@ -108,9 +108,22 @@ CUIWindow::CUIWindow(CXUI *pXUI, const XWINDOW_DESC *pWindowDesc, IUIWindow *pPa
 	m_pXWindowCallback = new CWindowCallback(this, m_pDesktopStack);
 	m_pXWindow = pXUI->getWindowSystem()->createWindow(pWindowDesc, m_pXWindowCallback, pXParent);
 
-	createSwapChain((UINT)pWindowDesc->iSizeX, (UINT)pWindowDesc->iSizeY);
+	float fScale = m_pXWindow->getScale();
+
+	if(fScale != 1.0f)
+	{
+		XWINDOW_DESC newDesc = *pWindowDesc;
+		newDesc.iSizeX *= fScale;
+		newDesc.iSizeY *= fScale;
+		m_pXWindow->update(&newDesc);
+		createSwapChain((UINT)newDesc.iSizeX, (UINT)newDesc.iSizeY);
+	}
+	else
+	{
+		createSwapChain((UINT)pWindowDesc->iSizeX, (UINT)pWindowDesc->iSizeY);
+	}
 
-	m_pDesktopStack->setScale(m_pXWindow->getScale());
+	m_pDesktopStack->setScale(fScale);
 
 	m_pControl = new CUIWindowControl(this, 0);
 }
diff --git a/source/xcommon/IXModelLoader.h b/source/xcommon/IXModelLoader.h
index f6e99f3a7fc81cd1e8341dfd7d6103fac1f9a133..f4bb15d67c9290efd11d93e33808714829bbd16b 100644
--- a/source/xcommon/IXModelLoader.h
+++ b/source/xcommon/IXModelLoader.h
@@ -33,7 +33,7 @@ struct XModelInfo
 	//! количество костей
 	UINT uBoneCount;
 
-	//! количетсов анимаций
+	//! количество анимаций
 	UINT uAnimationCount;
 
 	//! габариты в метрах
diff --git a/source/xcommon/XEvents.h b/source/xcommon/XEvents.h
index fc73870971bcc377ed16e646b9d90a5a76425af9..09b2ae9f8172df10d57b6dccef02e38c1f14e9b7 100644
--- a/source/xcommon/XEvents.h
+++ b/source/xcommon/XEvents.h
@@ -95,6 +95,8 @@ struct XEventLevel
 		TYPE_LOAD_END, //!< Уровень полностью загружен
 		TYPE_UNLOAD_BEGIN, //!< До начала выгрузки уровня
 		TYPE_UNLOAD_END, //!< Уровень полностью выгружен
+
+		TYPE_LOAD_WAIT_ASYNC_TASKS, //!< Ожидание завершения асинхронных задач
 	} type;
 	const char *szLevelName;
 };
diff --git a/source/xcommon/editor/IXEditable.h b/source/xcommon/editor/IXEditable.h
index 17cd062da54204939f4feff6707b9018f3f4d3db..abcd208faa589896dfe7271dd0a4bff7d7c60398 100644
--- a/source/xcommon/editor/IXEditable.h
+++ b/source/xcommon/editor/IXEditable.h
@@ -58,7 +58,7 @@ public:
 	virtual const char* XMETHODCALLTYPE getName() = 0;
 	virtual UINT XMETHODCALLTYPE getClassCount() = 0;
 	virtual const char* XMETHODCALLTYPE getClass(UINT id) = 0;
-
+	
 	virtual void XMETHODCALLTYPE startup(IGXDevice *pDevice) = 0;
 	virtual void XMETHODCALLTYPE shutdown() = 0;
 
@@ -72,6 +72,7 @@ public:
 
 	virtual bool XMETHODCALLTYPE canUseModel(const char *szClass) = 0;
 
+	virtual const char* XMETHODCALLTYPE getClassKV(const char *szClassName, const char *szKey) = 0;
 };
 
 
diff --git a/source/xcommon/editor/IXEditorObject.h b/source/xcommon/editor/IXEditorObject.h
index b1643d970bec41590c397ed6122c0340bdf92191..156c7e2dd35d6a65ba9c4f4ed9564369ea539b15 100644
--- a/source/xcommon/editor/IXEditorObject.h
+++ b/source/xcommon/editor/IXEditorObject.h
@@ -16,6 +16,7 @@ enum X_PROP_EDITOR_TYPE
 	XPET_RADIUS,
 	XPET_COLOR,
 	XPET_HDRCOLOR,
+	XPET_SIZE,
 	//XPET_YESNO,
 
 	XPET__LAST
diff --git a/source/xcommon/resource/IXDecal.h b/source/xcommon/resource/IXDecal.h
new file mode 100644
index 0000000000000000000000000000000000000000..67038ed8f4d4febb2d62e7248b1459d922034050
--- /dev/null
+++ b/source/xcommon/resource/IXDecal.h
@@ -0,0 +1,44 @@
+#ifndef __IXDECAL_H
+#define __IXDECAL_H
+
+#include <gdefines.h>
+#include "IXResourceModel.h"
+
+class IXDecal: public IXUnknown
+{
+public:
+	virtual float3 XMETHODCALLTYPE getPosition() const = 0;
+	virtual void XMETHODCALLTYPE setPosition(const float3 &vPos) = 0;
+
+	virtual SMQuaternion XMETHODCALLTYPE getOrientation() const = 0;
+	virtual void XMETHODCALLTYPE setOrientation(const SMQuaternion &qRot) = 0;
+
+	virtual float3 XMETHODCALLTYPE getLocalBoundMin() const = 0;
+	virtual float3 XMETHODCALLTYPE getLocalBoundMax() const = 0;
+	virtual SMAABB XMETHODCALLTYPE getLocalBound() const = 0;
+
+	virtual bool XMETHODCALLTYPE isEnabled() const = 0;
+	virtual void XMETHODCALLTYPE enable(bool yesNo) = 0;
+
+	virtual bool XMETHODCALLTYPE rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut = NULL, float3 *pvNormal = NULL) = 0;
+
+	virtual void XMETHODCALLTYPE setLayer(UINT uLayer) = 0;
+	virtual UINT XMETHODCALLTYPE getLayer() = 0;
+
+	virtual void XMETHODCALLTYPE setHeight(float fHeight) = 0;
+	virtual float XMETHODCALLTYPE getHeight() = 0;
+
+	virtual void XMETHODCALLTYPE setTextureRangeU(const float2_t &vRange) = 0;
+	virtual float2_t XMETHODCALLTYPE getTextureRangeU() = 0;
+
+	virtual void XMETHODCALLTYPE setTextureRangeV(const float2_t &vRange) = 0;
+	virtual float2_t XMETHODCALLTYPE getTextureRangeV() = 0;
+
+	virtual void XMETHODCALLTYPE setMaterial(const char *szMaterial) = 0;
+
+	virtual void XMETHODCALLTYPE setCorner(UINT uCorner, const float2_t &vCorner) = 0;
+
+	virtual void XMETHODCALLTYPE setLeakAllowed(bool yesNo) = 0;
+};
+
+#endif
diff --git a/source/xcommon/resource/IXDecalProvider.h b/source/xcommon/resource/IXDecalProvider.h
new file mode 100644
index 0000000000000000000000000000000000000000..38420258815f8383c1d60823f6254c6a031263da
--- /dev/null
+++ b/source/xcommon/resource/IXDecalProvider.h
@@ -0,0 +1,34 @@
+#ifndef __IXDECALPROVIDER_H
+#define __IXDECALPROVIDER_H
+
+#include "IXDecal.h"
+
+// {878E7A1E-86D4-4967-9A6D-6054B4363119}
+#define IXDECALPROVIDER_GUID DEFINE_XGUID(0x878e7a1e, 0x86d4, 0x4967, 0x9a, 0x6d, 0x60, 0x54, 0xb4, 0x36, 0x31, 0x19)
+
+enum XDECAL_TYPE
+{
+	XDT_CONCRETE = 0,
+	XDT_METAL,
+	XDT_GLASS,
+	XDT_PLASTIC,
+	XDT_WOOD,
+	XDT_FLESH,
+	XDT_EARTH,
+	XDT_BLOOD_BIG,
+
+	XDT__COUNT
+};
+
+// Implemented in anim plugin
+class IXDecalProvider: public IXUnknown
+{
+public:
+	// create custom decal
+	virtual bool XMETHODCALLTYPE newDecal(IXDecal **ppDecal) = 0;
+	
+	// create temporal standard decal at point/normal
+	virtual void XMETHODCALLTYPE shootDecal(XDECAL_TYPE type, const float3 &vWorldPos, const float3 &vNormal, float fScale = 1.0f, const float3 *pvSAxis = NULL) = 0;
+};
+
+#endif
diff --git a/source/xcsg/BrushMesh.cpp b/source/xcsg/BrushMesh.cpp
index 79a1ee1f1d0f7bf6a4ca164cd4e4205f8e86a4a5..a1a1cba20a0eddd206731d1522bdc051bebf964e 100644
--- a/source/xcsg/BrushMesh.cpp
+++ b/source/xcsg/BrushMesh.cpp
@@ -64,7 +64,7 @@ void CBrushMesh::enable(bool yesNo)
 	}
 }
 
-void CBrushMesh::buildMesh(CMeshBuilder *pBuilder)
+void CBrushMesh::buildMesh(CMeshBuilder *pBuilder, bool bOnlyComputeCounts)
 {
 	Array<UINT> aSubsets(m_aMaterials.size());
 	fora(i, m_aMaterials)
@@ -82,6 +82,14 @@ void CBrushMesh::buildMesh(CMeshBuilder *pBuilder)
 		TextureInfo &texInfo = face.texInfo;
 		Subset &subset = pBuilder->getSubset(aSubsets[texInfo.uMatId]);
 
+		if(bOnlyComputeCounts)
+		{
+			UINT uEdges = face.aEdges.size();
+			subset.uVertexCount += uEdges;
+			subset.uIndexCount += (uEdges - 2) * 3;
+			continue;
+		}
+
 		bool isFirst = true;
 		UINT uFirstIdx;
 		UINT uNextIdx;
@@ -161,7 +169,9 @@ void CBrushMesh::buildModel(bool bBuildPhysbox)
 	}
 
 	CMeshBuilder meshBuilder;
-	buildMesh(&meshBuilder);
+	buildMesh(&meshBuilder, true);
+	meshBuilder.allocArrays();
+	buildMesh(&meshBuilder, false);
 
 	// create model
 	IXResourceManager *pRM = m_pCore->getResourceManager();
@@ -2417,6 +2427,16 @@ UINT CMeshBuilder::getSubsetIndexForMaterial(const char *szMat)
 	return(idx);
 }
 
+void CMeshBuilder::allocArrays()
+{
+	fora(i, m_aSubsets)
+	{
+		Subset &ss = m_aSubsets[i];
+		ss.aIndices.reserve(ss.uIndexCount);
+		ss.aVertices.reserve(ss.uVertexCount);
+	}
+}
+
 UINT CMeshBuilder::getSubsetCount()
 {
 	return(m_aMaterials.size());
diff --git a/source/xcsg/BrushMesh.h b/source/xcsg/BrushMesh.h
index 352c664c13babbabdfd9b977f855d312d13e7bca..39f72d434e2938a725d9cc3558aa89daa86b2e68 100644
--- a/source/xcsg/BrushMesh.h
+++ b/source/xcsg/BrushMesh.h
@@ -34,6 +34,9 @@ struct Subset
 {
 	Array<XResourceModelStaticVertex> aVertices;
 	Array<UINT> aIndices;
+
+	UINT uVertexCount = 0;
+	UINT uIndexCount = 0;
 };
 
 class CMeshBuilder
@@ -48,6 +51,8 @@ public:
 
 	void buildResource(IXResourceModelStatic *pResource, UINT idx = UINT_MAX);
 
+	void allocArrays();
+
 private:
 	Array<Subset> m_aSubsets;
 	Array<String> m_aMaterials;
@@ -101,7 +106,7 @@ public:
 	bool findInternalFace(Array<float3_t> &aDest);
 	bool fillInternalFace(const Array<float3_t> &aSrc);
 
-	void buildMesh(CMeshBuilder *pBuilder);
+	void buildMesh(CMeshBuilder *pBuilder, bool bOnlyComputeCounts);
 	void buildPhysbox(IXResourceModel *pResource);
 
 	void setFinalized(bool set);
diff --git a/source/xcsg/Combiner.cpp b/source/xcsg/Combiner.cpp
index 5ce45e31280fa73e5d34f79ce0d3b6bc1dc5a599..63f0cdad3a07a1f863b7805b74b76f4fcea61f17 100644
--- a/source/xcsg/Combiner.cpp
+++ b/source/xcsg/Combiner.cpp
@@ -33,7 +33,17 @@ void CCombiner::build()
 		if(!aObjects[i]->getModel())
 		{
 			hasObjects = true;
-			aObjects[i]->buildMesh(&meshBuilder);
+			aObjects[i]->buildMesh(&meshBuilder, true);
+		}
+	}
+
+	meshBuilder.allocArrays();
+
+	fora(i, aObjects)
+	{
+		if(!aObjects[i]->getModel())
+		{
+			aObjects[i]->buildMesh(&meshBuilder, false);
 			aObjects[i]->buildPhysbox(pResource);
 		}
 	}
diff --git a/source/xcsg/Editable.h b/source/xcsg/Editable.h
index 8d88c2d9b081b928410d7b1bd7868b17cde75ba1..6df39e2befc5f1b11dd02eb6d368ad5be5c9cddf 100644
--- a/source/xcsg/Editable.h
+++ b/source/xcsg/Editable.h
@@ -103,6 +103,11 @@ public:
 	void onModelDestroy(CEditorModel *pModel);
 	void onModelRestored(CEditorModel *pModel);
 
+	const char* XMETHODCALLTYPE getClassKV(const char *szClassName, const char *szKey) override
+	{
+		return(NULL);
+	}
+
 	SX_ALIGNED_OP_MEM();
 
 	CVertexTool* getVertexTool();
diff --git a/source/xcsg/EditorModel.cpp b/source/xcsg/EditorModel.cpp
index 273ae59f49a0fbd6f64bf562b16800db3234a4df..28fca2c72af565a15fc7f2217158bf330724e45c 100644
--- a/source/xcsg/EditorModel.cpp
+++ b/source/xcsg/EditorModel.cpp
@@ -60,7 +60,14 @@ void XMETHODCALLTYPE CEditorModel::getResource(IXResourceModel **ppOut)
 	CMeshBuilder meshBuilder;
 	fora(i, m_aObjects)
 	{
-		m_aObjects[i]->buildMesh(&meshBuilder);
+		m_aObjects[i]->buildMesh(&meshBuilder, true);
+	}
+
+	meshBuilder.allocArrays();
+
+	fora(i, m_aObjects)
+	{
+		m_aObjects[i]->buildMesh(&meshBuilder, false);
 		m_aObjects[i]->buildPhysbox(pResource);
 	}
 
diff --git a/source/xcsg/EditorObject.cpp b/source/xcsg/EditorObject.cpp
index e168c533cfc99678ed213f1ca686c5f2cfd59edc..47fcd5fd42c778cf08088ea9119d1dd30eb6b4f8 100644
--- a/source/xcsg/EditorObject.cpp
+++ b/source/xcsg/EditorObject.cpp
@@ -144,7 +144,7 @@ bool XMETHODCALLTYPE CEditorObject::rayTest(const float3 &vStart, const float3 &
 		if(bReturnNearestPoint)
 		{
 			float3 vOut, vMinOut, vNormal, *pNormal = NULL;
-			if(pNormal)
+			if(pvNormal)
 			{
 				pNormal = &vNormal;
 			}
@@ -691,11 +691,11 @@ void CEditorObject::removeBrush(UINT idx)
 	m_aBrushes.erase(idx);
 }
 
-void CEditorObject::buildMesh(CMeshBuilder *pBuilder)
+void CEditorObject::buildMesh(CMeshBuilder *pBuilder, bool bOnlyComputeCounts)
 {
 	fora(i, m_aBrushes)
 	{
-		m_aBrushes[i]->buildMesh(pBuilder);
+		m_aBrushes[i]->buildMesh(pBuilder, bOnlyComputeCounts);
 	}
 }
 
diff --git a/source/xcsg/EditorObject.h b/source/xcsg/EditorObject.h
index 938a3fb44a0f6f5a1e8dc9628d944015c03df351..365cf5c270ff22c3825f22d3c32552af246cba47 100644
--- a/source/xcsg/EditorObject.h
+++ b/source/xcsg/EditorObject.h
@@ -103,7 +103,7 @@ public:
 		return(m_pModel);
 	}
 
-	void buildMesh(CMeshBuilder *pBuilder);
+	void buildMesh(CMeshBuilder *pBuilder, bool bOnlyComputeCounts);
 	void buildPhysbox(IXResourceModel *pResource);
 
 	bool isRemoved()