diff --git a/BrotliBuilder.sln b/BrotliBuilder.sln
index 8b0b8b2..9ca54f4 100644
--- a/BrotliBuilder.sln
+++ b/BrotliBuilder.sln
@@ -2,30 +2,22 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 16
 VisualStudioVersion = 16.0.28729.10
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrotliBuilder", "BrotliBuilder\BrotliBuilder.csproj", "{3684F055-8D45-42D6-AE8B-7F8C895994E0}"
-EndProject
-Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "UnitTests", "UnitTests\UnitTests.fsproj", "{D0854D9E-E442-42B2-922F-0CB1BEAAE8BC}"
-EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrotliLib", "BrotliLib\BrotliLib.csproj", "{09692EDC-DC35-46A9-83C6-746E447F3B86}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrotliCalc", "BrotliCalc\BrotliCalc.csproj", "{6541BA71-D176-48E9-891B-7062E27347E5}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrotliImpl", "BrotliImpl\BrotliImpl.csproj", "{8137DF6E-21F1-445F-B29D-80032DF1D711}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrotliBuilder", "BrotliBuilder\BrotliBuilder.csproj", "{D3E845C9-3599-4020-92B7-B1CB0BFBBF48}"
+EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "UnitTests", "UnitTests\UnitTests.fsproj", "{848155A6-3A83-4022-922E-CEF96BED123D}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Release|Any CPU = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{3684F055-8D45-42D6-AE8B-7F8C895994E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{3684F055-8D45-42D6-AE8B-7F8C895994E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{3684F055-8D45-42D6-AE8B-7F8C895994E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{3684F055-8D45-42D6-AE8B-7F8C895994E0}.Release|Any CPU.Build.0 = Release|Any CPU
-		{D0854D9E-E442-42B2-922F-0CB1BEAAE8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{D0854D9E-E442-42B2-922F-0CB1BEAAE8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{D0854D9E-E442-42B2-922F-0CB1BEAAE8BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{D0854D9E-E442-42B2-922F-0CB1BEAAE8BC}.Release|Any CPU.Build.0 = Release|Any CPU
 		{09692EDC-DC35-46A9-83C6-746E447F3B86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{09692EDC-DC35-46A9-83C6-746E447F3B86}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{09692EDC-DC35-46A9-83C6-746E447F3B86}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -38,6 +30,14 @@ Global
 		{8137DF6E-21F1-445F-B29D-80032DF1D711}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{8137DF6E-21F1-445F-B29D-80032DF1D711}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{8137DF6E-21F1-445F-B29D-80032DF1D711}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D3E845C9-3599-4020-92B7-B1CB0BFBBF48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D3E845C9-3599-4020-92B7-B1CB0BFBBF48}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D3E845C9-3599-4020-92B7-B1CB0BFBBF48}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D3E845C9-3599-4020-92B7-B1CB0BFBBF48}.Release|Any CPU.Build.0 = Release|Any CPU
+		{848155A6-3A83-4022-922E-CEF96BED123D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{848155A6-3A83-4022-922E-CEF96BED123D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{848155A6-3A83-4022-922E-CEF96BED123D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{848155A6-3A83-4022-922E-CEF96BED123D}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/BrotliBuilder/BrotliBuilder.csproj b/BrotliBuilder/BrotliBuilder.csproj
index 68fec90..93acc86 100644
--- a/BrotliBuilder/BrotliBuilder.csproj
+++ b/BrotliBuilder/BrotliBuilder.csproj
@@ -1,183 +1,120 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
+
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{3684F055-8D45-42D6-AE8B-7F8C895994E0}</ProjectGuid>
     <OutputType>WinExe</OutputType>
-    <RootNamespace>BrotliBuilder</RootNamespace>
-    <AssemblyName>BrotliBuilder</AssemblyName>
-    <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-    <TargetFrameworkProfile />
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <UseWindowsForms>true</UseWindowsForms>
+    <Copyright>Daniel Chýlek</Copyright>
+    <RepositoryUrl>https://github.com/chylex/Brotli-Builder</RepositoryUrl>
+    <Authors>Daniel Chýlek</Authors>
   </PropertyGroup>
-  <PropertyGroup>
-    <StartupObject>BrotliBuilder.Program</StartupObject>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
-    <DebugSymbols>true</DebugSymbols>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
     <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
-    <DebugType>full</DebugType>
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <ErrorReport>prompt</ErrorReport>
-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
-    <LangVersion>latest</LangVersion>
-    <Prefer32Bit>false</Prefer32Bit>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
-    <Optimize>true</Optimize>
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <ErrorReport>prompt</ErrorReport>
-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
-    <LangVersion>latest</LangVersion>
-    <Prefer32Bit>false</Prefer32Bit>
   </PropertyGroup>
+
   <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="System.Drawing" />
-    <Reference Include="System.Windows.Forms" />
-    <Reference Include="System.Xml" />
+    <ContentWithTargetPath Include="Resources\LICENSE-BROTLI.txt">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <TargetPath>LICENSE-BROTLI.txt</TargetPath>
+    </ContentWithTargetPath>
+    <ContentWithTargetPath Include="Resources\LICENSE-FCTB.txt">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <TargetPath>LICENSE-FCTB.txt</TargetPath>
+    </ContentWithTargetPath>
   </ItemGroup>
+
   <ItemGroup>
-    <Compile Include="Blocks\Structure\BuildEmptyMetaBlock.cs">
+    <PackageReference Include="FCTB" Version="2.16.24">
+      <NoWarn>NU1701</NoWarn>
+    </PackageReference>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\BrotliImpl\BrotliImpl.csproj" />
+    <ProjectReference Include="..\BrotliLib\BrotliLib.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Update="Blocks\BuildFileStructure.cs">
       <SubType>UserControl</SubType>
     </Compile>
-    <Compile Include="Blocks\Structure\BuildEmptyMetaBlock.Designer.cs">
-      <DependentUpon>BuildEmptyMetaBlock.cs</DependentUpon>
-    </Compile>
-    <Compile Include="Blocks\Structure\BuildUncompressedMetaBlock.cs">
-      <SubType>UserControl</SubType>
-    </Compile>
-    <Compile Include="Blocks\Structure\BuildUncompressedMetaBlock.Designer.cs">
-      <DependentUpon>BuildUncompressedMetaBlock.cs</DependentUpon>
-    </Compile>
-    <Compile Include="Blocks\Structure\BuildWindowSize.cs">
-      <SubType>UserControl</SubType>
-    </Compile>
-    <Compile Include="Blocks\BuildFileStructure.cs">
-      <SubType>UserControl</SubType>
-    </Compile>
-    <Compile Include="Blocks\BuildFileStructure.Designer.cs">
+    <Compile Update="Blocks\BuildFileStructure.Designer.cs">
       <DependentUpon>BuildFileStructure.cs</DependentUpon>
     </Compile>
-    <Compile Include="Blocks\Structure\BuildWindowSize.Designer.cs">
+    <Compile Update="Blocks\Structure\BuildEmptyMetaBlock.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Update="Blocks\Structure\BuildEmptyMetaBlock.Designer.cs">
+      <DependentUpon>BuildEmptyMetaBlock.cs</DependentUpon>
+    </Compile>
+    <Compile Update="Blocks\Structure\BuildUncompressedMetaBlock.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Update="Blocks\Structure\BuildUncompressedMetaBlock.Designer.cs">
+      <DependentUpon>BuildUncompressedMetaBlock.cs</DependentUpon>
+    </Compile>
+    <Compile Update="Blocks\Structure\BuildWindowSize.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Update="Blocks\Structure\BuildWindowSize.Designer.cs">
       <DependentUpon>BuildWindowSize.cs</DependentUpon>
     </Compile>
-    <Compile Include="Blocks\IBuildingBlockContext.cs" />
-    <Compile Include="State\BrotliFileController.cs" />
-    <Compile Include="Components\BrotliFilePanel.cs">
+    <Compile Update="Components\BrotliFilePanel.cs">
       <SubType>UserControl</SubType>
     </Compile>
-    <Compile Include="Components\BrotliFilePanel.Designer.cs">
+    <Compile Update="Components\BrotliFilePanel.Designer.cs">
       <DependentUpon>BrotliFilePanel.cs</DependentUpon>
     </Compile>
-    <Compile Include="State\BrotliFileState.cs" />
-    <Compile Include="Components\BrotliMarkerInfoPanel.cs">
+    <Compile Update="Components\BrotliMarkerInfoPanel.cs">
       <SubType>UserControl</SubType>
     </Compile>
-    <Compile Include="Components\BrotliMarkerInfoPanel.Designer.cs">
+    <Compile Update="Components\BrotliMarkerInfoPanel.Designer.cs">
       <DependentUpon>BrotliMarkerInfoPanel.cs</DependentUpon>
     </Compile>
-    <Compile Include="Components\MarkedTextBox.cs">
+    <Compile Update="Components\MarkedTextBox.cs">
       <SubType>UserControl</SubType>
     </Compile>
-    <Compile Include="Dialogs\FormStaticDictionary.cs">
+    <Compile Update="Dialogs\FormStaticDictionary.cs">
       <SubType>Form</SubType>
     </Compile>
-    <Compile Include="Dialogs\FormStaticDictionary.Designer.cs">
+    <Compile Update="Dialogs\FormStaticDictionary.Designer.cs">
       <DependentUpon>FormStaticDictionary.cs</DependentUpon>
     </Compile>
-    <Compile Include="FormMain.cs">
+    <Compile Update="FormMain.cs">
       <SubType>Form</SubType>
     </Compile>
-    <Compile Include="FormMain.Designer.cs">
+    <Compile Update="FormMain.Designer.cs">
       <DependentUpon>FormMain.cs</DependentUpon>
     </Compile>
-    <Compile Include="Program.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="State\ErrorType.cs" />
-    <Compile Include="State\StateChangedEventArgs.cs" />
-    <Compile Include="Utils\AsyncWorker.cs" />
-    <Compile Include="Utils\Colors.cs" />
-    <Compile Include="Utils\ControlExtensions.cs" />
-    <Compile Include="Utils\NativeMethods.cs" />
-    <Compile Include="Utils\WinMerge.cs" />
   </ItemGroup>
+
   <ItemGroup>
-    <BootstrapperPackage Include=".NETFramework,Version=v4.7.1">
-      <Visible>False</Visible>
-      <ProductName>Microsoft .NET Framework 4.7.1 %28x86 and x64%29</ProductName>
-      <Install>true</Install>
-    </BootstrapperPackage>
-    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
-      <Visible>False</Visible>
-      <ProductName>.NET Framework 3.5 SP1</ProductName>
-      <Install>false</Install>
-    </BootstrapperPackage>
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\BrotliImpl\BrotliImpl.csproj">
-      <Project>{8137df6e-21f1-445f-b29d-80032df1d711}</Project>
-      <Name>BrotliImpl</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\BrotliLib\BrotliLib.csproj">
-      <Project>{09692edc-dc35-46a9-83c6-746e447f3b86}</Project>
-      <Name>BrotliLib</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <ItemGroup>
-    <PackageReference Include="FCTB">
-      <Version>2.16.24</Version>
-    </PackageReference>
-    <PackageReference Include="Microsoft.Net.Compilers">
-      <Version>2.9.0</Version>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
-      <PrivateAssets>all</PrivateAssets>
-    </PackageReference>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Blocks\BuildFileStructure.resx">
+    <EmbeddedResource Update="Blocks\BuildFileStructure.resx">
       <DependentUpon>BuildFileStructure.cs</DependentUpon>
     </EmbeddedResource>
-    <EmbeddedResource Include="Blocks\Structure\BuildEmptyMetaBlock.resx">
+    <EmbeddedResource Update="Blocks\Structure\BuildEmptyMetaBlock.resx">
       <DependentUpon>BuildEmptyMetaBlock.cs</DependentUpon>
     </EmbeddedResource>
-    <EmbeddedResource Include="Blocks\Structure\BuildUncompressedMetaBlock.resx">
+    <EmbeddedResource Update="Blocks\Structure\BuildUncompressedMetaBlock.resx">
       <DependentUpon>BuildUncompressedMetaBlock.cs</DependentUpon>
     </EmbeddedResource>
-    <EmbeddedResource Include="Blocks\Structure\BuildWindowSize.resx">
+    <EmbeddedResource Update="Blocks\Structure\BuildWindowSize.resx">
       <DependentUpon>BuildWindowSize.cs</DependentUpon>
     </EmbeddedResource>
-    <EmbeddedResource Include="Components\BrotliFilePanel.resx">
-      <DependentUpon>BrotliFilePanel.cs</DependentUpon>
+    <EmbeddedResource Update="Components\BrotliFilePanel.resx">
       <SubType>Designer</SubType>
+      <DependentUpon>BrotliFilePanel.cs</DependentUpon>
     </EmbeddedResource>
-    <EmbeddedResource Include="Components\BrotliMarkerInfoPanel.resx">
+    <EmbeddedResource Update="Components\BrotliMarkerInfoPanel.resx">
       <DependentUpon>BrotliMarkerInfoPanel.cs</DependentUpon>
     </EmbeddedResource>
-    <EmbeddedResource Include="Dialogs\FormStaticDictionary.resx">
+    <EmbeddedResource Update="Dialogs\FormStaticDictionary.resx">
       <DependentUpon>FormStaticDictionary.cs</DependentUpon>
     </EmbeddedResource>
-    <EmbeddedResource Include="FormMain.resx">
+    <EmbeddedResource Update="FormMain.resx">
       <DependentUpon>FormMain.cs</DependentUpon>
     </EmbeddedResource>
   </ItemGroup>
-  <ItemGroup>
-    <Content Include="Resources\LICENSE-BROTLI.txt" />
-    <Content Include="Resources\LICENSE-FCTB.txt" />
-  </ItemGroup>
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-  <PropertyGroup>
-    <PostBuildEvent>xcopy /Y "$(ProjectDir)Resources\*" "$(TargetDir)"</PostBuildEvent>
-  </PropertyGroup>
+
 </Project>
\ No newline at end of file
diff --git a/BrotliBuilder/Program.cs b/BrotliBuilder/Program.cs
index aa83f12..0b19f8a 100644
--- a/BrotliBuilder/Program.cs
+++ b/BrotliBuilder/Program.cs
@@ -10,6 +10,7 @@ namespace BrotliBuilder{
             Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
             Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
 
+            Application.SetHighDpiMode(HighDpiMode.SystemAware);
             Application.EnableVisualStyles();
             Application.SetCompatibleTextRenderingDefault(false);
             Application.Run(new FormMain());
diff --git a/BrotliBuilder/Properties/AssemblyInfo.cs b/BrotliBuilder/Properties/AssemblyInfo.cs
deleted file mode 100644
index 7b73171..0000000
--- a/BrotliBuilder/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("BrotliBuilder")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("BrotliBuilder")]
-[assembly: AssemblyCopyright("Daniel Ch�lek")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components.  If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("3684f055-8d45-42d6-ae8b-7f8c895994e0")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BrotliCalc/BrotliCalc.csproj b/BrotliCalc/BrotliCalc.csproj
index 9a5173d..f0b009d 100644
--- a/BrotliCalc/BrotliCalc.csproj
+++ b/BrotliCalc/BrotliCalc.csproj
@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>netcoreapp2.0</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <LangVersion>latest</LangVersion>
     <ApplicationIcon />
     <StartupObject>BrotliCalc.Program</StartupObject>
diff --git a/BrotliCalc/Commands/CmdBenchReserializeRebuild.cs b/BrotliCalc/Commands/CmdBenchReserializeRebuild.cs
index c1f3bf9..f75ae3d 100644
--- a/BrotliCalc/Commands/CmdBenchReserializeRebuild.cs
+++ b/BrotliCalc/Commands/CmdBenchReserializeRebuild.cs
@@ -13,7 +13,7 @@ namespace BrotliCalc.Commands{
         public string ShortName => "brr";
 
         public string ArgumentDesc => "<source-path> <output-file>";
-        public Range ArgumentCount => Range.Only(2);
+        public IntRange ArgumentCount => IntRange.Only(2);
 
         public string Process(string[] args){
             int totalFiles = 0;
diff --git a/BrotliCalc/Commands/CmdCompress.cs b/BrotliCalc/Commands/CmdCompress.cs
index 078a60b..1fb6535 100644
--- a/BrotliCalc/Commands/CmdCompress.cs
+++ b/BrotliCalc/Commands/CmdCompress.cs
@@ -10,7 +10,7 @@ namespace BrotliCalc.Commands{
         public string ShortName => "c";
 
         public string ArgumentDesc => "<source-path> <quality|all> [window-size]";
-        public Range ArgumentCount => new Range(2, 3);
+        public IntRange ArgumentCount => new IntRange(2, 3);
 
         public string Process(string[] args){
             var qualities = args[1] == "all" ? Enumerable.Range(0, 12) : Enumerable.Range(int.Parse(args[1]), 1);
diff --git a/BrotliCalc/Commands/CmdCompressStats.cs b/BrotliCalc/Commands/CmdCompressStats.cs
index 42085a2..afa088f 100644
--- a/BrotliCalc/Commands/CmdCompressStats.cs
+++ b/BrotliCalc/Commands/CmdCompressStats.cs
@@ -8,7 +8,7 @@ namespace BrotliCalc.Commands{
         public string ShortName => "cs";
 
         public string ArgumentDesc => "<source-path> <output-file>";
-        public Range ArgumentCount => Range.Only(2);
+        public IntRange ArgumentCount => IntRange.Only(2);
 
         public string Process(string[] args){
             int totalFiles = 0;
diff --git a/BrotliCalc/Commands/CmdTestEncoder.cs b/BrotliCalc/Commands/CmdTestEncoder.cs
index 1e07d39..b83ec0b 100644
--- a/BrotliCalc/Commands/CmdTestEncoder.cs
+++ b/BrotliCalc/Commands/CmdTestEncoder.cs
@@ -20,7 +20,7 @@ namespace BrotliCalc.Commands{
         public string ShortName => "te";
 
         public string ArgumentDesc => "<{" + string.Join('|', Encoders.Keys) + "}> <source-path> <output-file>";
-        public Range ArgumentCount => Range.Only(3);
+        public IntRange ArgumentCount => IntRange.Only(3);
 
         public string Process(string[] args){
             if (!Encoders.TryGetValue(args[0], out var encoder)){
diff --git a/BrotliCalc/Commands/CmdTestReserializeRebuild.cs b/BrotliCalc/Commands/CmdTestReserializeRebuild.cs
index 18ee9fe..d84f060 100644
--- a/BrotliCalc/Commands/CmdTestReserializeRebuild.cs
+++ b/BrotliCalc/Commands/CmdTestReserializeRebuild.cs
@@ -10,7 +10,7 @@ namespace BrotliCalc.Commands{
         public string ShortName => "trr";
 
         public string ArgumentDesc => "<source-path> <output-file>";
-        public Range ArgumentCount => Range.Only(2);
+        public IntRange ArgumentCount => IntRange.Only(2);
 
         public string Process(string[] args){
             int totalFiles = 0;
diff --git a/BrotliCalc/Commands/CmdTestTransformer.cs b/BrotliCalc/Commands/CmdTestTransformer.cs
index 0888647..6c0f815 100644
--- a/BrotliCalc/Commands/CmdTestTransformer.cs
+++ b/BrotliCalc/Commands/CmdTestTransformer.cs
@@ -18,7 +18,7 @@ namespace BrotliCalc.Commands{
         public string ShortName => "tt";
 
         public string ArgumentDesc => "<{" + string.Join('|', Transformers.Keys) + "}> <source-path> <output-file>";
-        public Range ArgumentCount => Range.Only(3);
+        public IntRange ArgumentCount => IntRange.Only(3);
 
         public string Process(string[] args){
             if (!Transformers.TryGetValue(args[0], out var transformer)){
diff --git a/BrotliCalc/Helpers/Brotli.cs b/BrotliCalc/Helpers/Brotli.cs
index 0ddb36f..6913321 100644
--- a/BrotliCalc/Helpers/Brotli.cs
+++ b/BrotliCalc/Helpers/Brotli.cs
@@ -14,7 +14,7 @@ namespace BrotliCalc.Helpers{
         private const string CompressedFileExtension = ".br";
         
         private static readonly Regex RegexCompressionIdentifier = new Regex(@"\.([^.]+)\.br$");
-        private static readonly Range QualityRange = new Range(0, 11);
+        private static readonly IntRange QualityRange = new IntRange(0, 11);
 
         private static string GetUncompressedName(string path){
             return Path.GetExtension(path) == CompressedFileExtension ? RegexCompressionIdentifier.Replace(path, "") : path;
diff --git a/BrotliCalc/ICommand.cs b/BrotliCalc/ICommand.cs
index 777eb80..1edd93b 100644
--- a/BrotliCalc/ICommand.cs
+++ b/BrotliCalc/ICommand.cs
@@ -6,7 +6,7 @@ namespace BrotliCalc{
         string ShortName { get; }
 
         string ArgumentDesc { get; }
-        Range ArgumentCount { get; }
+        IntRange ArgumentCount { get; }
 
         string Process(string[] args);
     }
diff --git a/BrotliCalc/Program.cs b/BrotliCalc/Program.cs
index ca876d1..37670b9 100644
--- a/BrotliCalc/Program.cs
+++ b/BrotliCalc/Program.cs
@@ -72,7 +72,7 @@ namespace BrotliCalc{
                     continue;
                 }
 
-                Range range = command.ArgumentCount;
+                IntRange range = command.ArgumentCount;
                 string[] args = ParseCommandArguments(input.ElementAtOrDefault(1) ?? string.Empty);
 
                 if (!range.Contains(args.Length)){
diff --git a/BrotliImpl/BrotliImpl.csproj b/BrotliImpl/BrotliImpl.csproj
index ad23852..fe1f0d3 100644
--- a/BrotliImpl/BrotliImpl.csproj
+++ b/BrotliImpl/BrotliImpl.csproj
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <Platforms>AnyCPU</Platforms>
     <LangVersion>latest</LangVersion>
     <Authors>Daniel Chýlek</Authors>
diff --git a/BrotliLib/Brotli/Components/Data/BlockLengthCode.cs b/BrotliLib/Brotli/Components/Data/BlockLengthCode.cs
index c1b02f9..efdf182 100644
--- a/BrotliLib/Brotli/Components/Data/BlockLengthCode.cs
+++ b/BrotliLib/Brotli/Components/Data/BlockLengthCode.cs
@@ -34,7 +34,7 @@ namespace BrotliLib.Brotli.Components.Data{
             369, 497, 753, 1265, 2289, 4337, 8433, 16625,
         };
 
-        private static readonly Range[] BlockLengthRanges = BlockLengthOffsets.Zip(BlockLengthExtraBits, Range.FromOffsetBitPair).ToArray();
+        private static readonly IntRange[] BlockLengthRanges = BlockLengthOffsets.Zip(BlockLengthExtraBits, IntRange.FromOffsetBitPair).ToArray();
 
         public static BlockLengthCode MakeCode(int length){
             return new BlockLengthCode(Array.FindIndex(BlockLengthRanges, range => range.Contains(length)));
diff --git a/BrotliLib/Brotli/Components/Data/InsertCopyLengthCode.cs b/BrotliLib/Brotli/Components/Data/InsertCopyLengthCode.cs
index 1c1fc3e..c3481f7 100644
--- a/BrotliLib/Brotli/Components/Data/InsertCopyLengthCode.cs
+++ b/BrotliLib/Brotli/Components/Data/InsertCopyLengthCode.cs
@@ -24,7 +24,7 @@ namespace BrotliLib.Brotli.Components.Data{
             0, 8, 0, 8, 0, 8, 16, 0, 16, 8, 16
         };
 
-        private static readonly (Range i, Range c)[] PairedCellOffsets = InsertCellOffsets.Zip(CopyCellOffsets, (i, c) => (new Range(i, i + 7), new Range(c, c + 7))).ToArray();
+        private static readonly (IntRange i, IntRange c)[] PairedCellOffsets = InsertCellOffsets.Zip(CopyCellOffsets, (i, c) => (new IntRange(i, i + 7), new IntRange(c, c + 7))).ToArray();
 
         // Data
 
diff --git a/BrotliLib/Brotli/Components/Data/InsertCopyLengths.cs b/BrotliLib/Brotli/Components/Data/InsertCopyLengths.cs
index 3c749f6..16a00e2 100644
--- a/BrotliLib/Brotli/Components/Data/InsertCopyLengths.cs
+++ b/BrotliLib/Brotli/Components/Data/InsertCopyLengths.cs
@@ -41,7 +41,7 @@ namespace BrotliLib.Brotli.Components.Data{
             130, 194, 322, 578, 1090, 2114, 6210, 22594,
         };
 
-        private static readonly Range[] InsertCodeRanges = InsertCodeValueOffsets.Zip(InsertCodeExtraBits, Range.FromOffsetBitPair).ToArray();
+        private static readonly IntRange[] InsertCodeRanges = InsertCodeValueOffsets.Zip(InsertCodeExtraBits, IntRange.FromOffsetBitPair).ToArray();
 
         // Copy code tables
 
@@ -57,7 +57,7 @@ namespace BrotliLib.Brotli.Components.Data{
             70, 102, 134, 198, 326, 582, 1094, 2118,
         };
 
-        private static readonly Range[] CopyCodeRanges = CopyCodeValueOffsets.Zip(CopyCodeExtraBits, Range.FromOffsetBitPair).ToArray();
+        private static readonly IntRange[] CopyCodeRanges = CopyCodeValueOffsets.Zip(CopyCodeExtraBits, IntRange.FromOffsetBitPair).ToArray();
 
         // Data
 
diff --git a/BrotliLib/Brotli/Components/Header/ContextMap.cs b/BrotliLib/Brotli/Components/Header/ContextMap.cs
index cd4da00..cc9b790 100644
--- a/BrotliLib/Brotli/Components/Header/ContextMap.cs
+++ b/BrotliLib/Brotli/Components/Header/ContextMap.cs
@@ -78,7 +78,7 @@ namespace BrotliLib.Brotli.Components.Header{
                 return this;
             }
 
-            public Builder Set(Range range, byte value){
+            public Builder Set(IntRange range, byte value){
                 for(int index = range.First; index <= range.Last; index++){
                     contextMap[index] = value;
                 }
diff --git a/BrotliLib/BrotliLib.csproj b/BrotliLib/BrotliLib.csproj
index 710e93e..d0925ec 100644
--- a/BrotliLib/BrotliLib.csproj
+++ b/BrotliLib/BrotliLib.csproj
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <Platforms>AnyCPU</Platforms>
     <LangVersion>latest</LangVersion>
     <Authors>Daniel Chýlek</Authors>
diff --git a/BrotliLib/BrotliLib.csproj.DotSettings b/BrotliLib/BrotliLib.csproj.DotSettings
deleted file mode 100644
index 96331d1..0000000
--- a/BrotliLib/BrotliLib.csproj.DotSettings
+++ /dev/null
@@ -1,2 +0,0 @@
-<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
-	<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp72</s:String></wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/BrotliLib/Numbers/Range.cs b/BrotliLib/Numbers/IntRange.cs
similarity index 78%
rename from BrotliLib/Numbers/Range.cs
rename to BrotliLib/Numbers/IntRange.cs
index 1a0dab4..d5fa2f6 100644
--- a/BrotliLib/Numbers/Range.cs
+++ b/BrotliLib/Numbers/IntRange.cs
@@ -2,40 +2,40 @@
 
 namespace BrotliLib.Numbers{
     /// <summary>
-    /// Represents a range of integers. When not initialized (using <code>default</code> or <code>new Range()</code>), the range will contain all 32-bit integers.
+    /// Represents a range of integers. When not initialized (using <code>default</code> or <code>new IntRange()</code>), the range will contain all 32-bit integers.
     /// </summary>
-    public readonly struct Range{
+    public readonly struct IntRange{
         /// <summary>
         /// Range containing all signed 32-bit integers.
         /// </summary>
-        public static Range Any => default;
+        public static IntRange Any => default;
 
         /// <summary>
         /// Returns a range containing all values which can be encoded with the specified amount of <paramref name="bits"/>, with an <paramref name="offset"/> applied to both ends of the range.
         /// </summary>
-        public static Range FromOffsetBitPair(int offset, int bits){
-            return new Range(offset, offset + (1 << bits) - 1);
+        public static IntRange FromOffsetBitPair(int offset, int bits){
+            return new IntRange(offset, offset + (1 << bits) - 1);
         }
 
         /// <summary>
         /// Returns a range between <paramref name="minimum"/> and <see cref="int.MaxValue"/>.
         /// </summary>
-        public static Range AtLeast(int minimum){
-            return new Range(minimum, int.MaxValue);
+        public static IntRange AtLeast(int minimum){
+            return new IntRange(minimum, int.MaxValue);
         }
         
         /// <summary>
         /// Returns a range between <see cref="int.MinValue"/> and <paramref name="maximum"/>.
         /// </summary>
-        public static Range AtMost(int maximum){
-            return new Range(int.MinValue, maximum);
+        public static IntRange AtMost(int maximum){
+            return new IntRange(int.MinValue, maximum);
         }
         
         /// <summary>
         /// Returns a range only containing the specified <paramref name="value"/>.
         /// </summary>
-        public static Range Only(int value){
-            return new Range(value, value);
+        public static IntRange Only(int value){
+            return new IntRange(value, value);
         }
 
         // Data
@@ -57,7 +57,7 @@ namespace BrotliLib.Numbers{
         /// <summary>
         /// Initializes the range with the provided lower and upper bound (both inclusive).
         /// </summary>
-        public Range(int first, int last){
+        public IntRange(int first, int last){
             if (last < first){
                 throw new ArgumentOutOfRangeException(nameof(last), "last must be >= first");
             }
@@ -74,7 +74,7 @@ namespace BrotliLib.Numbers{
         // Object
 
         public override bool Equals(object obj){
-            return obj is Range range &&
+            return obj is IntRange range &&
                    initialized == range.initialized &&
                    first == range.first &&
                    last == range.last;
diff --git a/README.md b/README.md
index d1a568f..694e591 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,10 @@ Before documentation and the APIs themselves reach a more finished state, I woul
 
 | Project | Type | Framework | Description |
 | ------- | :--: | --------- | ----------- |
-| BrotliLib | Library | .NET&nbsp;Standard&nbsp;2.0 | APIs for Brotli structure and serialization; main dependency |
-| BrotliImpl | Library | .NET&nbsp;Standard&nbsp;2.0 | Example implementations of *encoders* and *transformers* |
-| BrotliBuilder | WinForms&nbsp;App | .NET&nbsp;Framework&nbsp;4.7.1 | GUI for analysis of Brotli-compressed files & static dictionary |
-| BrotliCalc | Console&nbsp;App | .NET&nbsp;Core&nbsp;2.0 | CLI for batch file processing, analysis, and statistics |
+| BrotliLib | Library | .NET&nbsp;Standard&nbsp;2.1 | APIs for Brotli structure and serialization; main dependency |
+| BrotliImpl | Library | .NET&nbsp;Standard&nbsp;2.1 | Example implementations of *encoders* and *transformers* |
+| BrotliBuilder | WinForms&nbsp;App | .NET&nbsp;Core&nbsp;3.0 | GUI for analysis of Brotli-compressed files & static dictionary |
+| BrotliCalc | Console&nbsp;App | .NET&nbsp;Core&nbsp;3.0 | CLI for batch file processing, analysis, and statistics |
 
 The key principle behind *BrotliLib* is representing the structure of a compressed file using a structure of objects, which is easy to analyze and manipulate.
 
@@ -39,6 +39,5 @@ The bit stream text fields let you navigate the color-coded bit groups:
 
 The project is written primarily in C# and built using Visual Studio 2019. Make sure you have the following components:
 
- - **.NET Framework 4.7.1 SDK**
- - **[.NET Core 2.0 SDK](https://dotnet.microsoft.com/download)**
+ - **[.NET Core 3.0 SDK](https://dotnet.microsoft.com/download)** (included in Visual Studio v16.3)
  - **F# desktop language support** (optional, only required for unit tests)
diff --git a/UnitTests/EntryPoint.fs b/UnitTests/EntryPoint.fs
new file mode 100644
index 0000000..1d11a29
--- /dev/null
+++ b/UnitTests/EntryPoint.fs
@@ -0,0 +1,6 @@
+module EntryPoint
+
+let _ = 0
+
+// F# adds an entry point for executable projects to the last file.
+// If the last file is a test module, it messes up static initializers.
diff --git a/UnitTests/IO/TestBitStream.fs b/UnitTests/IO/TestBitStream.fs
index e44d2ba..5233bc1 100644
--- a/UnitTests/IO/TestBitStream.fs
+++ b/UnitTests/IO/TestBitStream.fs
@@ -29,9 +29,14 @@ module Representations =
     [<InlineData("0000_1111")>]
     let ``constructing from string with invalid characters throws exception`` (bits: string) =
         Assert.Throws<ArgumentOutOfRangeException>(fun () -> BitStream(bits) |> ignore)
+    
+    [<Fact>]
+    let ``constructing from empty byte array yields same length and byte array representation`` () =
+        let stream = BitStream()
+        Assert.Equal(0, stream.Length)
+        Assert.Equal<byte array>([||], stream.ToByteArray())
         
     [<Theory>]
-    [<InlineData()>]
     [<InlineData(0b00000000uy)>]
     [<InlineData(0b11100110uy)>]
     [<InlineData(0b00000000uy, 0b11111111uy)>]
diff --git a/UnitTests/IO/TestBitWriter.fs b/UnitTests/IO/TestBitWriter.fs
index 544c9e6..1de1f2d 100644
--- a/UnitTests/IO/TestBitWriter.fs
+++ b/UnitTests/IO/TestBitWriter.fs
@@ -92,7 +92,6 @@ module AlignToByteBoundary =
 
 module WriteAlignedBytes =
     let bytes : obj array seq = seq {
-        yield [||]
         yield Array.map box [| 0b0uy |]
         yield Array.map box [| 0b1uy |]
         yield Array.map box [| 120uy; 0uy; 255uy; 33uy |]
@@ -101,6 +100,14 @@ module WriteAlignedBytes =
         yield Array.map box [| 1uy; 2uy; 3uy; 4uy; 5uy; 6uy; 7uy; 8uy; 9uy; 10uy; 11uy; 12uy; 13uy; 14uy; 15uy; 16uy; 17uy |]
     }
 
+    [<Fact>]
+    let ``writing empty byte array into stream yields correct byte array representation`` () =
+        let stream = BitStream()
+        let writer = stream.GetWriter()
+
+        writer.WriteAlignedBytes([||])
+        Assert.Equal<byte array>([||], stream.ToByteArray())
+
     [<Theory>]
     [<MemberData("bytes")>]
     let ``writing bytes into stream yields correct byte array representation`` ([<ParamArray>] bytes: byte array) =
@@ -109,6 +116,15 @@ module WriteAlignedBytes =
 
         writer.WriteAlignedBytes(bytes)
         Assert.Equal<byte array>(bytes, stream.ToByteArray())
+
+    [<Fact>]
+    let ``writing empty byte array into unaligned stream skips to next boundary and then writes correct byte sequence with correct final alignment`` () =
+        let stream = BitStream("1")
+        let writer = stream.GetWriter()
+
+        writer.WriteAlignedBytes([||])
+        writer.WriteBit(true)
+        Assert.Equal<byte array>(Array.concat [ [| 0b1uy |]; [| 0b1uy |] ], stream.ToByteArray())
         
     [<Theory>]
     [<MemberData("bytes")>]
diff --git a/UnitTests/UnitTests.fsproj b/UnitTests/UnitTests.fsproj
index b640fe7..76a1373 100644
--- a/UnitTests/UnitTests.fsproj
+++ b/UnitTests/UnitTests.fsproj
@@ -1,105 +1,33 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" />
-  <Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" />
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
+
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>d0854d9e-e442-42b2-922f-0cb1beaae8bc</ProjectGuid>
-    <OutputType>Library</OutputType>
-    <RootNamespace>UnitTests</RootNamespace>
-    <AssemblyName>UnitTests</AssemblyName>
-    <UseStandardResourceNames>True</UseStandardResourceNames>
-    <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-    <Name>UnitTests</Name>
-    <NuGetPackageImportStamp>
-    </NuGetPackageImportStamp>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+
+    <IsPackable>false</IsPackable>
+    <GenerateProgramFile>false</GenerateProgramFile>
   </PropertyGroup>
-  <PropertyGroup>
-    <MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(FSharpTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets') ">
-    <FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <Tailcalls>false</Tailcalls>
-    <OutputPath>bin\$(Configuration)\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <WarningLevel>3</WarningLevel>
-    <DocumentationFile>bin\$(Configuration)\$(AssemblyName).XML</DocumentationFile>
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <Tailcalls>true</Tailcalls>
-    <OutputPath>bin\$(Configuration)\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <WarningLevel>3</WarningLevel>
-    <DocumentationFile>bin\$(Configuration)\$(AssemblyName).XML</DocumentationFile>
-    <PlatformTarget>AnyCPU</PlatformTarget>
-  </PropertyGroup>
-  <Import Project="$(FSharpTargetsPath)" />
-  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
-    <PropertyGroup>
-      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
-    </PropertyGroup>
-    <Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
-    <Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
-    <Error Condition="!Exists('..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props'))" />
-  </Target>
-  <Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" />
+
   <ItemGroup>
     <Compile Include="Brotli\TestElements.fs" />
     <Compile Include="Brotli\TestUtils.fs" />
     <Compile Include="Collections\TestRingBuffer.fs" />
-    <Compile Include="Huffman\TestHuffmanNode.fs" />
     <Compile Include="Huffman\TestHuffmanGenerator.fs" />
-    <Compile Include="IO\TestBitStream.fs" />
+    <Compile Include="Huffman\TestHuffmanNode.fs" />
     <Compile Include="IO\TestBitReader.fs" />
+    <Compile Include="IO\TestBitStream.fs" />
     <Compile Include="IO\TestBitWriter.fs" />
-    <Content Include="packages.config" />
+    <Compile Include="EntryPoint.fs" />
   </ItemGroup>
+
   <ItemGroup>
-    <Reference Include="FSharp.Core">
-      <HintPath>..\packages\FSharp.Core.4.5.2\lib\net45\FSharp.Core.dll</HintPath>
-    </Reference>
-    <Reference Include="mscorlib" />
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Numerics" />
-    <Reference Include="System.ValueTuple">
-      <Private>True</Private>
-    </Reference>
-    <Reference Include="xunit.abstractions">
-      <HintPath>..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll</HintPath>
-    </Reference>
-    <Reference Include="xunit.assert">
-      <HintPath>..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
-    </Reference>
-    <Reference Include="xunit.core">
-      <HintPath>..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll</HintPath>
-    </Reference>
-    <Reference Include="xunit.execution.desktop">
-      <HintPath>..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll</HintPath>
-    </Reference>
-    <ProjectReference Include="..\BrotliLib\BrotliLib.csproj">
-      <Name>BrotliLib</Name>
-      <Project>{09692edc-dc35-46a9-83c6-746e447f3b86}</Project>
-      <Private>True</Private>
-    </ProjectReference>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
+    <PackageReference Include="xunit" Version="2.4.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
+    <PackageReference Include="coverlet.collector" Version="1.0.1" />
   </ItemGroup>
-  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
-       Other similar extension points exist, see Microsoft.Common.targets.
-  <Target Name="BeforeBuild">
-  </Target>
-  <Target Name="AfterBuild">
-  </Target>
-  -->
-</Project>
\ No newline at end of file
+
+  <ItemGroup>
+    <ProjectReference Include="..\BrotliLib\BrotliLib.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/UnitTests/packages.config b/UnitTests/packages.config
deleted file mode 100644
index 34d9755..0000000
--- a/UnitTests/packages.config
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
-  <package id="FSharp.Core" version="4.5.2" targetFramework="net471" />
-  <package id="System.ValueTuple" version="4.5.0" targetFramework="net471" />
-  <package id="xunit" version="2.4.1" targetFramework="net471" />
-  <package id="xunit.abstractions" version="2.0.3" targetFramework="net471" />
-  <package id="xunit.analyzers" version="0.10.0" targetFramework="net471" />
-  <package id="xunit.assert" version="2.4.1" targetFramework="net471" />
-  <package id="xunit.core" version="2.4.1" targetFramework="net471" />
-  <package id="xunit.extensibility.core" version="2.4.1" targetFramework="net471" />
-  <package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net471" />
-  <package id="xunit.runner.visualstudio" version="2.4.1" targetFramework="net471" developmentDependency="true" />
-</packages>
\ No newline at end of file