From 4496c5f2caf4b9dfc0740032002a43becc81a9c8 Mon Sep 17 00:00:00 2001
From: Nemesh <prevter@gmail.com>
Date: Tue, 25 Apr 2023 22:53:42 +0300
Subject: [PATCH] NextCombination V2, Parallel.For, namespace sync

- Remade NextCombination method to actually support skipping, instead of running in a loop. A bit complicated code if you consider it uses `goto`.
- Added toggle to use Parallel.For instead of task pools. Testing shows a slight improvement on AMD CPUs.
- Synced all namespaces to organize codebase
---
 FloatTool/App.xaml.cs                      |    3 +-
 FloatTool/AppHelpers.cs                    |    3 +-
 FloatTool/Common/Calculations.cs           |  117 ++-
 FloatTool/Common/Logger.cs                 |   72 +-
 FloatTool/Common/RelayCommand.cs           |   56 +-
 FloatTool/Common/Settings.cs               |  374 +++----
 FloatTool/Common/Skin.cs                   |  280 ++---
 FloatTool/Common/Steam/SteamApi.cs         |   34 -
 FloatTool/Common/Utils.cs                  |  533 +++++-----
 FloatTool/Languages/Lang.ru.xaml           |    2 +-
 FloatTool/Languages/Lang.xaml              |    1 +
 FloatTool/ViewModels/BenchmarkViewModel.cs |  409 ++++----
 FloatTool/ViewModels/MainViewModel.cs      | 1081 ++++++++++----------
 FloatTool/ViewModels/SettingsViewModel.cs  |  453 ++++----
 FloatTool/Views/BenchmarkWindow.xaml.cs    |  103 +-
 FloatTool/Views/MainWindow.xaml            |    7 +-
 FloatTool/Views/MainWindow.xaml.cs         | 1007 +++++++++---------
 FloatTool/Views/SettingsWindow.xaml        |    6 +-
 FloatTool/Views/SettingsWindow.xaml.cs     |    4 +-
 FloatTool/Views/UpdateWindow.xaml.cs       |    3 +-
 20 files changed, 2316 insertions(+), 2232 deletions(-)
 delete mode 100644 FloatTool/Common/Steam/SteamApi.cs

diff --git a/FloatTool/App.xaml.cs b/FloatTool/App.xaml.cs
index bd1d346..e57d634 100644
--- a/FloatTool/App.xaml.cs
+++ b/FloatTool/App.xaml.cs
@@ -16,6 +16,7 @@
 */
 
 using DiscordRPC;
+using FloatTool.Common;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -31,7 +32,7 @@
 
 namespace FloatTool
 {
-    public partial class App : Application
+	public partial class App : Application
     {
         public static ResourceDictionary ThemeDictionary { get; set; }
 
diff --git a/FloatTool/AppHelpers.cs b/FloatTool/AppHelpers.cs
index 60a927a..1050353 100644
--- a/FloatTool/AppHelpers.cs
+++ b/FloatTool/AppHelpers.cs
@@ -16,12 +16,13 @@
 */
 
 using DiscordRPC;
+using FloatTool.Common;
 using System.Collections.Generic;
 using System.IO;
 
 namespace FloatTool
 {
-    internal static class AppHelpers
+	internal static class AppHelpers
     {
         public static FileSystemWatcher Watcher;
         public static DiscordRpcClient DiscordClient;
diff --git a/FloatTool/Common/Calculations.cs b/FloatTool/Common/Calculations.cs
index 1296ca0..ceb5486 100644
--- a/FloatTool/Common/Calculations.cs
+++ b/FloatTool/Common/Calculations.cs
@@ -15,56 +15,81 @@
 - along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
 
-using System.Collections.Generic;
 using System.Numerics;
 
-namespace FloatTool
+namespace FloatTool.Common
 {
-    static public class Calculations
-    {
-        public static double Craft(InputSkin[] ingridients, double minFloat, double floatRange)
-        {
-            return floatRange * (ingridients[0].WearValue
-                + ingridients[1].WearValue
-                + ingridients[2].WearValue
-                + ingridients[3].WearValue
-                + ingridients[4].WearValue
-                + ingridients[5].WearValue
-                + ingridients[6].WearValue
-                + ingridients[7].WearValue
-                + ingridients[8].WearValue
-                + ingridients[9].WearValue) + minFloat;
-        }
+	static public class Calculations
+	{
+		public static double Craft(InputSkin[] ingridients, double minFloat, double floatRange)
+		{
+			return floatRange * (ingridients[0].WearValue
+				+ ingridients[1].WearValue
+				+ ingridients[2].WearValue
+				+ ingridients[3].WearValue
+				+ ingridients[4].WearValue
+				+ ingridients[5].WearValue
+				+ ingridients[6].WearValue
+				+ ingridients[7].WearValue
+				+ ingridients[8].WearValue
+				+ ingridients[9].WearValue) + minFloat;
+		}
 
-        public static bool NextCombination(int[] num, int n)
-        {
-            bool finished = false;
-            for (int i = 9; !finished; --i)
-            {
-                if (num[i] < n + i)
-                {
-                    ++num[i];
-                    if (i < 9)
-                        for (int j = i + 1; j < 10; ++j)
-                            num[j] = num[j - 1] + 1;
-                    return true;
-                }
-                finished = i == 0;
-            }
-            return false;
-        }
-        
-        public static long GetCombinationsCount(int poolSize)
-        {
-            BigInteger fact1 = poolSize;
-            for (int i = poolSize - 1; i > 10; i--)
-                fact1 *= i;
+		public static bool NextCombination(int[] num, int n)
+		{
+			bool finished = false;
+			for (int i = 9; !finished; --i)
+			{
+				if (num[i] < n + i)
+				{
+					++num[i];
+					if (i < 9)
+						for (int j = i + 1; j < 10; ++j)
+							num[j] = num[j - 1] + 1;
+					return true;
+				}
+				finished = i == 0;
+			}
+			return false;
+		}
 
-            BigInteger fact2 = poolSize - 10;
-            for (int i = poolSize - 11; i > 1; i--)
-                fact2 *= i;
+		public static bool NextCombination(int[] arr, int n, int step)
+		{
+			arr[9] += step;
+			if (arr[9] < n + 10) return true;
+			else
+			{
+			fix_loop:
+				int overflow = arr[9] - (n + 10);
+				for (int i = 9; i >= 0; --i)
+				{
+					if (arr[i] < n + i)
+					{
+						++arr[i];
+						for (int j = i + 1; j < 10; ++j)
+							arr[j] = arr[j - 1] + 1;
 
-            return (long)(fact1 / fact2);
-        }
-    }
+						arr[9] += overflow;
+
+						if (arr[9] < n + 10) return true;
+						else goto fix_loop;
+					}
+				}
+				return false;
+			}
+		}
+
+		public static long GetCombinationsCount(int poolSize)
+		{
+			BigInteger fact1 = poolSize;
+			for (int i = poolSize - 1; i > 10; i--)
+				fact1 *= i;
+
+			BigInteger fact2 = poolSize - 10;
+			for (int i = poolSize - 11; i > 1; i--)
+				fact2 *= i;
+
+			return (long)(fact1 / fact2);
+		}
+	}
 }
diff --git a/FloatTool/Common/Logger.cs b/FloatTool/Common/Logger.cs
index 358bb45..edb57fd 100644
--- a/FloatTool/Common/Logger.cs
+++ b/FloatTool/Common/Logger.cs
@@ -22,47 +22,47 @@
 using log4net.Repository.Hierarchy;
 using System.Reflection;
 
-namespace FloatTool
+namespace FloatTool.Common
 {
-    public sealed class Logger
-    {
-        public static ILog Log { get; } = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+	public sealed class Logger
+	{
+		public static ILog Log { get; } = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
 
-        public static void Initialize()
-        {
-            Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
+		public static void Initialize()
+		{
+			Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
 
-            PatternLayout patternLayout = new()
-            {
-                ConversionPattern = "%date [%thread] %-5level - %message%newline"
-            };
-            patternLayout.ActivateOptions();
+			PatternLayout patternLayout = new()
+			{
+				ConversionPattern = "%date [%thread] %-5level - %message%newline"
+			};
+			patternLayout.ActivateOptions();
 
-            RollingFileAppender roller = new()
-            {
-                AppendToFile = false,
-                File = @"logs/log.txt",
-                Layout = patternLayout,
-                MaxSizeRollBackups = 5,
-                MaximumFileSize = "250KB",
-                RollingStyle = RollingFileAppender.RollingMode.Size,
-                StaticLogFileName = true
-            };
-            roller.ActivateOptions();
-            hierarchy.Root.AddAppender(roller);
+			RollingFileAppender roller = new()
+			{
+				AppendToFile = false,
+				File = @"logs/log.txt",
+				Layout = patternLayout,
+				MaxSizeRollBackups = 5,
+				MaximumFileSize = "250KB",
+				RollingStyle = RollingFileAppender.RollingMode.Size,
+				StaticLogFileName = true
+			};
+			roller.ActivateOptions();
+			hierarchy.Root.AddAppender(roller);
 
-            MemoryAppender memory = new();
-            memory.ActivateOptions();
-            hierarchy.Root.AddAppender(memory);
+			MemoryAppender memory = new();
+			memory.ActivateOptions();
+			hierarchy.Root.AddAppender(memory);
 
-            hierarchy.Root.Level = Level.Info;
-            hierarchy.Configured = true;
-        }
+			hierarchy.Root.Level = Level.Info;
+			hierarchy.Configured = true;
+		}
 
-        public static void Debug(object message) => Log.Debug(message);
-        public static void Info(object message) => Log.Info(message);
-        public static void Warn(object message) => Log.Warn(message);
-        public static void Error(object message) => Log.Error(message);
-        public static void Fatal(object message) => Log.Fatal(message);
-    }
+		public static void Debug(object message) => Log.Debug(message);
+		public static void Info(object message) => Log.Info(message);
+		public static void Warn(object message) => Log.Warn(message);
+		public static void Error(object message) => Log.Error(message);
+		public static void Fatal(object message) => Log.Fatal(message);
+	}
 }
diff --git a/FloatTool/Common/RelayCommand.cs b/FloatTool/Common/RelayCommand.cs
index d63e42d..00a5740 100644
--- a/FloatTool/Common/RelayCommand.cs
+++ b/FloatTool/Common/RelayCommand.cs
@@ -18,38 +18,38 @@
 using System;
 using System.Windows.Input;
 
-namespace FloatTool
+namespace FloatTool.Common
 {
-    public sealed class RelayCommand : ICommand
-    {
-        readonly Action<object> _execute;
-        readonly Predicate<object> _canExecute;
+	public sealed class RelayCommand : ICommand
+	{
+		readonly Action<object> _execute;
+		readonly Predicate<object> _canExecute;
 
-        public RelayCommand(Action<object> execute)
-            : this(execute, null)
-        {
-        }
+		public RelayCommand(Action<object> execute)
+			: this(execute, null)
+		{
+		}
 
-        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
-        {
-            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
-            _canExecute = canExecute;
-        }
+		public RelayCommand(Action<object> execute, Predicate<object> canExecute)
+		{
+			_execute = execute ?? throw new ArgumentNullException(nameof(execute));
+			_canExecute = canExecute;
+		}
 
-        public bool CanExecute(object parameters)
-        {
-            return _canExecute == null || _canExecute(parameters);
-        }
+		public bool CanExecute(object parameters)
+		{
+			return _canExecute == null || _canExecute(parameters);
+		}
 
-        public event EventHandler CanExecuteChanged
-        {
-            add { CommandManager.RequerySuggested += value; }
-            remove { CommandManager.RequerySuggested -= value; }
-        }
+		public event EventHandler CanExecuteChanged
+		{
+			add { CommandManager.RequerySuggested += value; }
+			remove { CommandManager.RequerySuggested -= value; }
+		}
 
-        public void Execute(object parameters)
-        {
-            _execute(parameters);
-        }
-    }
+		public void Execute(object parameters)
+		{
+			_execute(parameters);
+		}
+	}
 }
diff --git a/FloatTool/Common/Settings.cs b/FloatTool/Common/Settings.cs
index 99a0968..ed49b15 100644
--- a/FloatTool/Common/Settings.cs
+++ b/FloatTool/Common/Settings.cs
@@ -22,191 +22,193 @@
 using System.IO;
 using System.Threading;
 
-namespace FloatTool
+namespace FloatTool.Common
 {
-    public enum Currency
-    {
-        USD = 1, GBP = 2,
-        EUR = 3, CHF = 4,
-        RUB = 5, PLN = 6,
-        BRL = 7, JPY = 8,
-        NOK = 9, IDR = 10,
-        MYR = 11, PHP = 12,
-        SGD = 13, THB = 14,
-        VND = 15, KRW = 16,
-        TRY = 17, UAH = 18,
-        MXN = 19, CAD = 20,
-        AUD = 21, NZD = 22,
-        CNY = 23, INR = 24,
-        CLP = 25, PEN = 26,
-        COP = 27, ZAR = 28,
-        HKD = 29, TWD = 30,
-        SAR = 31, AED = 32,
-        ARS = 34, ILS = 35,
-        BYN = 36, KZT = 37,
-        KWD = 38, QAR = 39,
-        CRC = 40, UYU = 41,
-        RMB = 9000, NXP = 9001
-    }
-    
-    // These are currently the same, but that can change later.
-    public enum FloatAPI
-    {
-        CSGOFloat,
-        SteamInventoryHelper
-    }
-
-    public enum ExtensionType
-    {
-        CSGOFloat,
-        SteamInventoryHelper
-    }
-
-    public static class CurrencyHelper
-    {
-        //Method to get the currency name from the enum
-        public static Currency GetCurrencyByIndex(int index)
-        {
-            return (Currency)(Enum.GetValues(typeof(Currency))).GetValue(index);
-        }
-
-        //Method to get index of a currency
-        public static int GetIndexFromCurrency(Currency currency)
-        {
-            return Array.IndexOf(Enum.GetValues(typeof(Currency)), currency);
-        }
-    }
-
-    public sealed class Settings
-    {
-        public string LanguageCode { get; set; } = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
-        public Currency Currency { get; set; } = Currency.USD;
-        public string ThemeURI { get; set; } = "/Theme/Schemes/Dark.xaml";
-        public bool Sound { get; set; } = true;
-        public bool CheckForUpdates { get; set; } = true;
-        public bool DiscordRPC { get; set; } = true;
-        public int ThreadCount { get; set; } = Environment.ProcessorCount;
-        public FloatAPI FloatAPI { get; set; } = FloatAPI.CSGOFloat;
-        public ExtensionType ExtensionType { get; set; } = ExtensionType.CSGOFloat;
-
-        public void Load()
-        {
-            try
-            {
-                string settingsPath = Path.Join(AppHelpers.AppDirectory, "settings.json");
-                if (File.Exists(settingsPath))
-                {
-                    var settings = File.ReadAllText(settingsPath);
-                    var tmpSettings = JsonConvert.DeserializeObject<Settings>(settings);
-                    LanguageCode = tmpSettings.LanguageCode;
-                    Currency = tmpSettings.Currency;
-                    ThemeURI = tmpSettings.ThemeURI;
-                    Sound = tmpSettings.Sound;
-                    CheckForUpdates = tmpSettings.CheckForUpdates;
-                    DiscordRPC = tmpSettings.DiscordRPC;
-                    ThreadCount = tmpSettings.ThreadCount;
-                    FloatAPI = tmpSettings.FloatAPI;
-                    ExtensionType = tmpSettings.ExtensionType;
-                }
-                else
-                {
-                    LoadOld();
-                    Save();
-                }
-            }
-            catch (Exception ex)
-            {
-                Logger.Log.Error("Error loading settings", ex);
-            }
-        }
-
-        public void LoadOld()
-        {
-            // Load settings from registry
-            const string userRoot = "HKEY_CURRENT_USER";
-            const string subkey = "Software\\FloatTool";
-            const string keyName = userRoot + "\\" + subkey;
-
-            try
-            {
-                using var hkcu = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
-                var key = hkcu.OpenSubKey(@"SOFTWARE\FloatTool");
-
-                if (key == null) return;
-
-                LanguageCode = (string)Registry.GetValue(keyName, "languageCode", LanguageCode);
-                Currency = (Currency)Registry.GetValue(keyName, "currency", Currency);
-                ThemeURI = (string)Registry.GetValue(keyName, "themeURI", ThemeURI);
-                ThreadCount = (int)Registry.GetValue(keyName, "lastThreads", ThreadCount);
-                Sound = (string)Registry.GetValue(keyName, "sound", Sound ? "True" : "False") == "True";
-                CheckForUpdates = (string)Registry.GetValue(keyName, "updateCheck", CheckForUpdates ? "True" : "False") == "True";
-                DiscordRPC = (string)Registry.GetValue(keyName, "discordRPC", DiscordRPC ? "True" : "False") == "True";
-
-                key.Close();
-                Logger.Log.Info("Loaded settings from registry");
-
-                MigrateFromOldVersion();
-            }
-            catch (Exception ex)
-            {
-                Logger.Log.Error("Error loading settings from registry", ex);
-            }
-        }
-
-        public void Save()
-        {
-            try
-            {
-                if (!Directory.Exists(AppHelpers.AppDirectory))
-                    Directory.CreateDirectory(AppHelpers.AppDirectory);
-
-                var settings = JsonConvert.SerializeObject(this, Formatting.Indented);
-                File.WriteAllText(Path.Join(AppHelpers.AppDirectory, "settings.json"), settings);
-            }
-            catch (Exception ex)
-            {
-                Logger.Log.Error("Error saving settings", ex);
-            }
-        }
-
-        public void MigrateFromOldVersion()
-        {
-            // This method cleans up old settings and data
-            try
-            {
-                RegistryKey regkeySoftware = Registry.CurrentUser.OpenSubKey("SOFTWARE", true);
-                regkeySoftware.DeleteSubKeyTree("FloatTool");
-                regkeySoftware.Close();
-            }
-            catch (Exception ex)
-            {
-                Logger.Log.Error("Error saving settings", ex);
-            }
-
-            List<string> oldFiles = new()
-            {
-                "debug.log",
-                "FloatCore.dll",
-                "FloatTool.exe.config",
-                "FloatTool.pdb",
-                "itemData.json",
-                "theme.json",
-                "Updater.exe"
-            };
-
-            foreach (var file in oldFiles)
-            {
-                if (File.Exists(file))
-                    File.Delete(file);
-            }
-
-            App.CleanOldFiles();
-            Save();
-        }
-
-        public override string ToString()
-        {
-            return JsonConvert.SerializeObject(this, Formatting.Indented);
-        }
-    }
+	public enum Currency
+	{
+		USD = 1, GBP = 2,
+		EUR = 3, CHF = 4,
+		RUB = 5, PLN = 6,
+		BRL = 7, JPY = 8,
+		NOK = 9, IDR = 10,
+		MYR = 11, PHP = 12,
+		SGD = 13, THB = 14,
+		VND = 15, KRW = 16,
+		TRY = 17, UAH = 18,
+		MXN = 19, CAD = 20,
+		AUD = 21, NZD = 22,
+		CNY = 23, INR = 24,
+		CLP = 25, PEN = 26,
+		COP = 27, ZAR = 28,
+		HKD = 29, TWD = 30,
+		SAR = 31, AED = 32,
+		ARS = 34, ILS = 35,
+		BYN = 36, KZT = 37,
+		KWD = 38, QAR = 39,
+		CRC = 40, UYU = 41,
+		RMB = 9000, NXP = 9001
+	}
+
+	// These are currently the same, but that can change later.
+	public enum FloatAPI
+	{
+		CSGOFloat,
+		SteamInventoryHelper
+	}
+
+	public enum ExtensionType
+	{
+		CSGOFloat,
+		SteamInventoryHelper
+	}
+
+	public static class CurrencyHelper
+	{
+		//Method to get the currency name from the enum
+		public static Currency GetCurrencyByIndex(int index)
+		{
+			return (Currency)Enum.GetValues(typeof(Currency)).GetValue(index);
+		}
+
+		//Method to get index of a currency
+		public static int GetIndexFromCurrency(Currency currency)
+		{
+			return Array.IndexOf(Enum.GetValues(typeof(Currency)), currency);
+		}
+	}
+
+	public sealed class Settings
+	{
+		public string LanguageCode { get; set; } = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
+		public Currency Currency { get; set; } = Currency.USD;
+		public string ThemeURI { get; set; } = "/Theme/Schemes/Dark.xaml";
+		public bool Sound { get; set; } = true;
+		public bool CheckForUpdates { get; set; } = true;
+		public bool DiscordRPC { get; set; } = true;
+		public bool UseParallel { get; set; } = false;
+		public int ThreadCount { get; set; } = Environment.ProcessorCount;
+		public FloatAPI FloatAPI { get; set; } = FloatAPI.CSGOFloat;
+		public ExtensionType ExtensionType { get; set; } = ExtensionType.CSGOFloat;
+
+		public void Load()
+		{
+			try
+			{
+				string settingsPath = Path.Join(AppHelpers.AppDirectory, "settings.json");
+				if (File.Exists(settingsPath))
+				{
+					var settings = File.ReadAllText(settingsPath);
+					var tmpSettings = JsonConvert.DeserializeObject<Settings>(settings);
+					LanguageCode = tmpSettings.LanguageCode;
+					Currency = tmpSettings.Currency;
+					ThemeURI = tmpSettings.ThemeURI;
+					Sound = tmpSettings.Sound;
+					CheckForUpdates = tmpSettings.CheckForUpdates;
+					DiscordRPC = tmpSettings.DiscordRPC;
+					UseParallel = tmpSettings.UseParallel;
+					ThreadCount = tmpSettings.ThreadCount;
+					FloatAPI = tmpSettings.FloatAPI;
+					ExtensionType = tmpSettings.ExtensionType;
+				}
+				else
+				{
+					LoadOld();
+					Save();
+				}
+			}
+			catch (Exception ex)
+			{
+				Logger.Log.Error("Error loading settings", ex);
+			}
+		}
+
+		public void LoadOld()
+		{
+			// Load settings from registry
+			const string userRoot = "HKEY_CURRENT_USER";
+			const string subkey = "Software\\FloatTool";
+			const string keyName = userRoot + "\\" + subkey;
+
+			try
+			{
+				using var hkcu = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
+				var key = hkcu.OpenSubKey(@"SOFTWARE\FloatTool");
+
+				if (key == null) return;
+
+				LanguageCode = (string)Registry.GetValue(keyName, "languageCode", LanguageCode);
+				Currency = (Currency)Registry.GetValue(keyName, "currency", Currency);
+				ThemeURI = (string)Registry.GetValue(keyName, "themeURI", ThemeURI);
+				ThreadCount = (int)Registry.GetValue(keyName, "lastThreads", ThreadCount);
+				Sound = (string)Registry.GetValue(keyName, "sound", Sound ? "True" : "False") == "True";
+				CheckForUpdates = (string)Registry.GetValue(keyName, "updateCheck", CheckForUpdates ? "True" : "False") == "True";
+				DiscordRPC = (string)Registry.GetValue(keyName, "discordRPC", DiscordRPC ? "True" : "False") == "True";
+
+				key.Close();
+				Logger.Log.Info("Loaded settings from registry");
+
+				MigrateFromOldVersion();
+			}
+			catch (Exception ex)
+			{
+				Logger.Log.Error("Error loading settings from registry", ex);
+			}
+		}
+
+		public void Save()
+		{
+			try
+			{
+				if (!Directory.Exists(AppHelpers.AppDirectory))
+					Directory.CreateDirectory(AppHelpers.AppDirectory);
+
+				var settings = JsonConvert.SerializeObject(this, Formatting.Indented);
+				File.WriteAllText(Path.Join(AppHelpers.AppDirectory, "settings.json"), settings);
+			}
+			catch (Exception ex)
+			{
+				Logger.Log.Error("Error saving settings", ex);
+			}
+		}
+
+		public void MigrateFromOldVersion()
+		{
+			// This method cleans up old settings and data
+			try
+			{
+				RegistryKey regkeySoftware = Registry.CurrentUser.OpenSubKey("SOFTWARE", true);
+				regkeySoftware.DeleteSubKeyTree("FloatTool");
+				regkeySoftware.Close();
+			}
+			catch (Exception ex)
+			{
+				Logger.Log.Error("Error saving settings", ex);
+			}
+
+			List<string> oldFiles = new()
+			{
+				"debug.log",
+				"FloatCore.dll",
+				"FloatTool.exe.config",
+				"FloatTool.pdb",
+				"itemData.json",
+				"theme.json",
+				"Updater.exe"
+			};
+
+			foreach (var file in oldFiles)
+			{
+				if (File.Exists(file))
+					File.Delete(file);
+			}
+
+			App.CleanOldFiles();
+			Save();
+		}
+
+		public override string ToString()
+		{
+			return JsonConvert.SerializeObject(this, Formatting.Indented);
+		}
+	}
 }
diff --git a/FloatTool/Common/Skin.cs b/FloatTool/Common/Skin.cs
index 951e7d5..a9639ac 100644
--- a/FloatTool/Common/Skin.cs
+++ b/FloatTool/Common/Skin.cs
@@ -17,145 +17,145 @@
 
 using System.Collections.Generic;
 
-namespace FloatTool
+namespace FloatTool.Common
 {
-    public sealed class Collection
-    {
-        public string Name;
-        public bool CanBeStattrak;
-        public string LowestRarity;
-        public string HighestRarity;
-        public string Link;
-        public List<SkinModel> Skins;
-    }
-
-    public struct SkinModel
-    {
-        public string Name;
-        public string Rarity;
-        public double MinWear;
-        public double MaxWear;
-
-        public bool IsQualityInRange(string quality)
-        {
-            var range = Skin.GetFloatRangeForQuality(quality);
-            return new FloatRange(MinWear, MaxWear).IsOverlapped(range);
-        }
-    }
-
-    public struct Skin
-    {
-        public enum Quality
-        {
-            Consumer,
-            Industrial,
-            MilSpec,
-            Restricted,
-            Classified,
-            Covert
-        }
-
-        public string Name;
-        public double MinFloat;
-        public double MaxFloat;
-        public double FloatRange;
-        public Quality Rarity;
-
-        public Skin(string name, double minWear, double maxWear, Quality rarity)
-        {
-            Name = name;
-            MinFloat = minWear;
-            MaxFloat = maxWear;
-            FloatRange = (MaxFloat - MinFloat) / 10;
-            Rarity = rarity;
-        }
-
-        public Skin(SkinModel model) : this(model.Name, model.MinWear, model.MaxWear, FromString(model.Rarity)) { }
-
-        public static string NextRarity(string rarity)
-        {
-            return rarity switch
-            {
-                "Consumer" => "Industrial",
-                "Industrial" => "Mil-Spec",
-                "Mil-Spec" => "Restricted",
-                "Restricted" => "Classified",
-                "Classified" => "Covert",
-                _ => "Nothing",
-            };
-        }
-
-        public static Quality FromString(string value)
-        {
-            return value switch
-            {
-                "Consumer" => Quality.Consumer,
-                "Industrial" => Quality.Industrial,
-                "Mil-Spec" => Quality.MilSpec,
-                "Restricted" => Quality.Restricted,
-                "Classified" => Quality.Classified,
-                _ => Quality.Covert,
-            };
-        }
-
-        public static FloatRange GetFloatRangeForQuality(string quality)
-        {
-            float lowestWear;
-            float highestWear;
-
-            switch (quality)
-            {
-                case "Factory New":
-                    lowestWear = 0f;
-                    highestWear = 0.07f;
-                    break;
-                case "Minimal Wear":
-                    lowestWear = 0.07f;
-                    highestWear = 0.15f;
-                    break;
-                case "Field-Tested":
-                    lowestWear = 0.15f;
-                    highestWear = 0.38f;
-                    break;
-                case "Well-Worn":
-                    lowestWear = 0.38f;
-                    highestWear = 0.45f;
-                    break;
-                case "Battle-Scarred":
-                    lowestWear = 0.45f;
-                    highestWear = 1f;
-                    break;
-                default:
-                    lowestWear = 0f;
-                    highestWear = 1f;
-                    break;
-            }
-
-            return new FloatRange(lowestWear, highestWear);
-        }
-
-    }
-
-    public sealed class InputSkin
-    {
-        public double WearValue;
-        public float Price;
-        public Currency SkinCurrency;
-        public string ListingID;
-
-        public double GetWearValue => WearValue;
-
-        public InputSkin(double wear, float price, Currency currency, string listingId = "")
-        {
-            WearValue = wear;
-            Price = price;
-            SkinCurrency = currency;
-            ListingID = listingId;
-        }
-
-        internal int CompareTo(InputSkin b)
-        {
-            return WearValue > b.WearValue ? 1 : (WearValue < b.WearValue ? -1 : 0);
-        }
-    }
+	public sealed class Collection
+	{
+		public string Name;
+		public bool CanBeStattrak;
+		public string LowestRarity;
+		public string HighestRarity;
+		public string Link;
+		public List<SkinModel> Skins;
+	}
+
+	public struct SkinModel
+	{
+		public string Name;
+		public string Rarity;
+		public double MinWear;
+		public double MaxWear;
+
+		public bool IsQualityInRange(string quality)
+		{
+			var range = Skin.GetFloatRangeForQuality(quality);
+			return new FloatRange(MinWear, MaxWear).IsOverlapped(range);
+		}
+	}
+
+	public enum Quality
+	{
+		Consumer,
+		Industrial,
+		MilSpec,
+		Restricted,
+		Classified,
+		Covert
+	}
+
+	public struct Skin
+	{
+		public string Name;
+		public double MinFloat;
+		public double MaxFloat;
+		public double FloatRange;
+		public Quality Rarity;
+
+		public Skin(string name, double minWear, double maxWear, Quality rarity)
+		{
+			Name = name;
+			MinFloat = minWear;
+			MaxFloat = maxWear;
+			FloatRange = (MaxFloat - MinFloat) / 10;
+			Rarity = rarity;
+		}
+
+		public Skin(SkinModel model) : this(model.Name, model.MinWear, model.MaxWear, FromString(model.Rarity)) { }
+
+		public static string NextRarity(string rarity)
+		{
+			return rarity switch
+			{
+				"Consumer" => "Industrial",
+				"Industrial" => "Mil-Spec",
+				"Mil-Spec" => "Restricted",
+				"Restricted" => "Classified",
+				"Classified" => "Covert",
+				_ => "Nothing",
+			};
+		}
+
+		public static Quality FromString(string value)
+		{
+			return value switch
+			{
+				"Consumer" => Quality.Consumer,
+				"Industrial" => Quality.Industrial,
+				"Mil-Spec" => Quality.MilSpec,
+				"Restricted" => Quality.Restricted,
+				"Classified" => Quality.Classified,
+				_ => Quality.Covert,
+			};
+		}
+
+		public static FloatRange GetFloatRangeForQuality(string quality)
+		{
+			float lowestWear;
+			float highestWear;
+
+			switch (quality)
+			{
+				case "Factory New":
+					lowestWear = 0f;
+					highestWear = 0.07f;
+					break;
+				case "Minimal Wear":
+					lowestWear = 0.07f;
+					highestWear = 0.15f;
+					break;
+				case "Field-Tested":
+					lowestWear = 0.15f;
+					highestWear = 0.38f;
+					break;
+				case "Well-Worn":
+					lowestWear = 0.38f;
+					highestWear = 0.45f;
+					break;
+				case "Battle-Scarred":
+					lowestWear = 0.45f;
+					highestWear = 1f;
+					break;
+				default:
+					lowestWear = 0f;
+					highestWear = 1f;
+					break;
+			}
+
+			return new FloatRange(lowestWear, highestWear);
+		}
+
+	}
+
+	public struct InputSkin
+	{
+		public double WearValue;
+		public float Price;
+		public Currency SkinCurrency;
+		public string ListingID;
+
+		public double GetWearValue => WearValue;
+
+		public InputSkin(double wear, float price, Currency currency, string listingId = "")
+		{
+			WearValue = wear;
+			Price = price;
+			SkinCurrency = currency;
+			ListingID = listingId;
+		}
+
+		internal int CompareTo(InputSkin b)
+		{
+			return WearValue.CompareTo(b.WearValue);
+		}
+	}
 }
diff --git a/FloatTool/Common/Steam/SteamApi.cs b/FloatTool/Common/Steam/SteamApi.cs
deleted file mode 100644
index b42e760..0000000
--- a/FloatTool/Common/Steam/SteamApi.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
-using System.Runtime.Serialization.Formatters.Binary;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace FloatTool.Steam
-{
-    /*public static class SteamApi
-    {
-        public readonly static HttpClient httpClient = new();
-
-        public struct RequestDefinition
-        {
-            public string ApiInterface;
-            public string ApiMethod;
-            public string ApiVersion;
-            public dynamic Data;
-            public string? AccessToken;
-        }
-
-        public static async Task<dynamic> SendRequest(RequestDefinition request)
-        {
-            string url = $"https://api.steampowered.com/{request.ApiInterface}/{request.ApiMethod}/v{request.ApiVersion}/";
-            var content = new FormUrlEncodedContent(request.Data);
-            var result = await httpClient.PostAsync(url, content);
-            
-            return null;
-        }
-    }*/
-}
\ No newline at end of file
diff --git a/FloatTool/Common/Utils.cs b/FloatTool/Common/Utils.cs
index 2957fd8..fa73af0 100644
--- a/FloatTool/Common/Utils.cs
+++ b/FloatTool/Common/Utils.cs
@@ -15,6 +15,7 @@
 - along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
 
+using FloatTool.ViewModels;
 using Newtonsoft.Json;
 using System;
 using System.Collections.Generic;
@@ -28,272 +29,272 @@
 using System.Threading.Tasks;
 using System.Windows;
 
-namespace FloatTool
+namespace FloatTool.Common
 {
-    // Taken from here:
-    // https://stackoverflow.com/a/46497896/16349466
-    public static class StreamExtensions
-    {
-        public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default)
-        {
-            if (source == null)
-                throw new ArgumentNullException(nameof(source));
-            if (!source.CanRead)
-                throw new ArgumentException("Has to be readable", nameof(source));
-            if (destination == null)
-                throw new ArgumentNullException(nameof(destination));
-            if (!destination.CanWrite)
-                throw new ArgumentException("Has to be writable", nameof(destination));
-            if (bufferSize < 0)
-                throw new ArgumentOutOfRangeException(nameof(bufferSize));
-
-            var buffer = new byte[bufferSize];
-            long totalBytesRead = 0;
-            int bytesRead;
-            while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
-            {
-                await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
-                totalBytesRead += bytesRead;
-                progress?.Report(totalBytesRead);
-            }
-        }
-    }
-
-    public static class HttpClientExtensions
-    {
-        public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default)
-        {
-            // Get the http headers first to examine the content length
-            using var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
-            var contentLength = response.Content.Headers.ContentLength;
-
-            using var download = await response.Content.ReadAsStreamAsync(cancellationToken);
-
-            // Ignore progress reporting when no progress reporter was 
-            // passed or when the content length is unknown
-            if (progress == null || !contentLength.HasValue)
-            {
-                await download.CopyToAsync(destination, cancellationToken);
-                return;
-            }
-
-            // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
-            var relativeProgress = new Progress<long>(totalBytes => progress.Report((float)totalBytes / contentLength.Value));
-            // Use extension method to report progress while downloading
-            await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
-            progress.Report(1);
-        }
-    }
-
-    public static class StringExtensions
-    {
-        public static string FirstCharToUpper(this string input) =>
-            input switch
-            {
-                null => throw new ArgumentNullException(nameof(input)),
-                "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)),
-                _ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1))
-            };
-    }
-
-    public static class Utils
-    {
-        public const string API_URL = "https://prevter.ml/api/floattool";
-        private static readonly HttpClient Client = new();
-
-        public static async Task<double> GetWearFromInspectURL(string inspect_url)
-        {
-            string url = AppHelpers.Settings.FloatAPI switch
-            {
-                FloatAPI.SteamInventoryHelper => "https://floats.steaminventoryhelper.com/?url=",
-                _ => "https://api.csgofloat.com/?url=",
-            };
-
-            var result = await Client.GetAsync(url + inspect_url);
-            result.EnsureSuccessStatusCode();
-            string response = await result.Content.ReadAsStringAsync();
-            dynamic json = JsonConvert.DeserializeObject(response);
-            return Convert.ToDouble(json["iteminfo"]["floatvalue"]);
-        }
-
-        public static async Task<UpdateResult> CheckForUpdates()
-        {
-            try
-            {
-                HttpClient client = new();
-                client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36");
-                var result = await client.GetAsync("https://api.github.com/repos/prevter/floattool/releases/latest");
-                result.EnsureSuccessStatusCode();
-                string response = await result.Content.ReadAsStringAsync();
-                return JsonConvert.DeserializeObject<UpdateResult>(response);
-            }
-            catch (Exception ex)
-            {
-                Logger.Log.Error("Failed to get latest version", ex);
-                return null;
-            }
-        }
-
-        public static string ShortCpuName(string cpu)
-        {
-            cpu = cpu.Replace("(R)", "");
-            cpu = cpu.Replace("(TM)", "");
-            cpu = cpu.Replace("(tm)", "");
-            cpu = cpu.Replace(" with Radeon Graphics", "");
-            cpu = cpu.Replace(" with Radeon Vega Mobile Gfx", "");
-            cpu = cpu.Replace(" CPU", "");
-            cpu = cpu.Replace(" Processor", "");
-
-            Regex regex = new(@"( \S{1,}-Core)");
-            MatchCollection matches = regex.Matches(cpu);
-            if (matches.Count > 0)
-                foreach (Match match in matches.Cast<Match>())
-                    cpu = cpu.Replace(match.Value, "");
-
-            var index = cpu.IndexOf('@');
-            if (index != -1)
-                cpu = cpu[..index];
-
-            index = cpu.IndexOf('(');
-            if (index != -1)
-                cpu = cpu[..index];
-
-            return cpu.Trim();
-        }
-
-        public static string EscapeLocalization(string input)
-        {
-            string regex = @"%(m_[^%]{1,})%";
-            var matches = Regex.Matches(input, regex);
-
-            foreach (Match m in matches.Cast<Match>())
-            {
-                string key = m.Groups[1].Value;
-                string localization = Application.Current.Resources[key] as string;
-                input = input.Replace(m.Value, localization);
-            }
-
-            return input;
-        }
-
-        public static void Sort<T>(this ObservableCollection<T> collection, Comparison<T> comparison)
-        {
-            var sortableList = new List<T>(collection);
-            sortableList.Sort(comparison);
-
-            for (int i = 0; i < sortableList.Count; i++)
-            {
-                collection.Move(collection.IndexOf(sortableList[i]), i);
-            }
-        }
-
-        private const long TicksPerMillisecond = 10000;
-        private const long TicksPerSecond = TicksPerMillisecond * 1000;
-        private static readonly double s_tickFrequency = (double)TicksPerSecond / Stopwatch.Frequency;
-
-        public static TimeSpan GetTimePassed(long starttime)
-        {
-            long endtime = Stopwatch.GetTimestamp();
-            return GetTimePassed(starttime, endtime);
-        }
-
-        public static TimeSpan GetTimePassed(long starttime, long endtime)
-        {
-            return new TimeSpan((long)((endtime - starttime) * s_tickFrequency));
-        }
-
-    }
-
-    public sealed class UpdateResult
-    {
-        public class Asset
-        {
-            [JsonRequired]
-            [JsonProperty("browser_download_url")]
-            public string BrowserDownloadUrl { get; set; }
-        }
-
-        [JsonRequired]
-        [JsonProperty("tag_name")]
-        public string TagName { get; set; }
-        [JsonRequired]
-        [JsonProperty("name")]
-        public string Name { get; set; }
-        [JsonRequired]
-        [JsonProperty("assets")]
-        public List<Asset> Assets { get; set; }
-        [JsonRequired]
-        [JsonProperty("body")]
-        public string Body { get; set; }
-    }
-
-    public struct CraftSearchSetup
-    {
-        public double SearchTarget;
-        public double TargetPrecision;
-        public string SearchFilter;
-
-        public Skin[] Outcomes;
-        public InputSkin[] SkinPool;
-
-        public SearchMode SearchMode;
-
-        public int ThreadID;
-        public int ThreadCount;
-    }
-
-    public struct FloatRange
-    {
-        readonly double min;
-        readonly double max;
-
-        public FloatRange(double min, double max)
-        {
-            this.min = min;
-            this.max = max;
-        }
-
-        public bool IsOverlapped(FloatRange other)
-        {
-            return Min <= other.Max && other.Min <= Max;
-        }
-
-        public double Min { get { return min; } }
-        public double Max { get { return max; } }
-    }
-
-    /// <summary>
-    /// Used to store current Discord Presense and update language if needed
-    /// </summary>
-    public sealed class RPCSettingsPersist
-    {
-        public string Details { get; set; }
-        public string State { get; set; }
-
-        public DiscordRPC.Timestamps Timestamp { get; set; }
-        public bool ShowTime { get; set; }
-
-        public DiscordRPC.RichPresence GetPresense()
-        {
-            string details = Utils.EscapeLocalization(Details);
-            string state = Utils.EscapeLocalization(State);
-
-            var rpc = new DiscordRPC.RichPresence()
-            {
-                Details = details,
-                State = state,
-                Assets = new DiscordRPC.Assets()
-                {
-                    LargeImageKey = "icon_new",
-                    LargeImageText = $"FloatTool {AppHelpers.VersionCode}",
-                },
-            };
-
-            if (ShowTime)
-                rpc.Timestamps = Timestamp;
-
-            return rpc;
-        }
-    }
+	// Taken from here:
+	// https://stackoverflow.com/a/46497896/16349466
+	public static class StreamExtensions
+	{
+		public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default)
+		{
+			if (source == null)
+				throw new ArgumentNullException(nameof(source));
+			if (!source.CanRead)
+				throw new ArgumentException("Has to be readable", nameof(source));
+			if (destination == null)
+				throw new ArgumentNullException(nameof(destination));
+			if (!destination.CanWrite)
+				throw new ArgumentException("Has to be writable", nameof(destination));
+			if (bufferSize < 0)
+				throw new ArgumentOutOfRangeException(nameof(bufferSize));
+
+			var buffer = new byte[bufferSize];
+			long totalBytesRead = 0;
+			int bytesRead;
+			while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
+			{
+				await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
+				totalBytesRead += bytesRead;
+				progress?.Report(totalBytesRead);
+			}
+		}
+	}
+
+	public static class HttpClientExtensions
+	{
+		public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default)
+		{
+			// Get the http headers first to examine the content length
+			using var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
+			var contentLength = response.Content.Headers.ContentLength;
+
+			using var download = await response.Content.ReadAsStreamAsync(cancellationToken);
+
+			// Ignore progress reporting when no progress reporter was 
+			// passed or when the content length is unknown
+			if (progress == null || !contentLength.HasValue)
+			{
+				await download.CopyToAsync(destination, cancellationToken);
+				return;
+			}
+
+			// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
+			var relativeProgress = new Progress<long>(totalBytes => progress.Report((float)totalBytes / contentLength.Value));
+			// Use extension method to report progress while downloading
+			await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
+			progress.Report(1);
+		}
+	}
+
+	public static class StringExtensions
+	{
+		public static string FirstCharToUpper(this string input) =>
+			input switch
+			{
+				null => throw new ArgumentNullException(nameof(input)),
+				"" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)),
+				_ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1))
+			};
+	}
+
+	public static class Utils
+	{
+		public const string API_URL = "https://prevter.ml/api/floattool";
+		private static readonly HttpClient Client = new();
+
+		public static async Task<double> GetWearFromInspectURL(string inspect_url)
+		{
+			string url = AppHelpers.Settings.FloatAPI switch
+			{
+				FloatAPI.SteamInventoryHelper => "https://floats.steaminventoryhelper.com/?url=",
+				_ => "https://api.csgofloat.com/?url=",
+			};
+
+			var result = await Client.GetAsync(url + inspect_url);
+			result.EnsureSuccessStatusCode();
+			string response = await result.Content.ReadAsStringAsync();
+			dynamic json = JsonConvert.DeserializeObject(response);
+			return Convert.ToDouble(json["iteminfo"]["floatvalue"]);
+		}
+
+		public static async Task<UpdateResult> CheckForUpdates()
+		{
+			try
+			{
+				HttpClient client = new();
+				client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36");
+				var result = await client.GetAsync("https://api.github.com/repos/prevter/floattool/releases/latest");
+				result.EnsureSuccessStatusCode();
+				string response = await result.Content.ReadAsStringAsync();
+				return JsonConvert.DeserializeObject<UpdateResult>(response);
+			}
+			catch (Exception ex)
+			{
+				Logger.Log.Error("Failed to get latest version", ex);
+				return null;
+			}
+		}
+
+		public static string ShortCpuName(string cpu)
+		{
+			cpu = cpu.Replace("(R)", "");
+			cpu = cpu.Replace("(TM)", "");
+			cpu = cpu.Replace("(tm)", "");
+			cpu = cpu.Replace(" with Radeon Graphics", "");
+			cpu = cpu.Replace(" with Radeon Vega Mobile Gfx", "");
+			cpu = cpu.Replace(" CPU", "");
+			cpu = cpu.Replace(" Processor", "");
+
+			Regex regex = new(@"( \S{1,}-Core)");
+			MatchCollection matches = regex.Matches(cpu);
+			if (matches.Count > 0)
+				foreach (Match match in matches.Cast<Match>())
+					cpu = cpu.Replace(match.Value, "");
+
+			var index = cpu.IndexOf('@');
+			if (index != -1)
+				cpu = cpu[..index];
+
+			index = cpu.IndexOf('(');
+			if (index != -1)
+				cpu = cpu[..index];
+
+			return cpu.Trim();
+		}
+
+		public static string EscapeLocalization(string input)
+		{
+			string regex = @"%(m_[^%]{1,})%";
+			var matches = Regex.Matches(input, regex);
+
+			foreach (Match m in matches.Cast<Match>())
+			{
+				string key = m.Groups[1].Value;
+				string localization = Application.Current.Resources[key] as string;
+				input = input.Replace(m.Value, localization);
+			}
+
+			return input;
+		}
+
+		public static void Sort<T>(this ObservableCollection<T> collection, Comparison<T> comparison)
+		{
+			var sortableList = new List<T>(collection);
+			sortableList.Sort(comparison);
+
+			for (int i = 0; i < sortableList.Count; i++)
+			{
+				collection.Move(collection.IndexOf(sortableList[i]), i);
+			}
+		}
+
+		private const long TicksPerMillisecond = 10000;
+		private const long TicksPerSecond = TicksPerMillisecond * 1000;
+		private static readonly double s_tickFrequency = (double)TicksPerSecond / Stopwatch.Frequency;
+
+		public static TimeSpan GetTimePassed(long starttime)
+		{
+			long endtime = Stopwatch.GetTimestamp();
+			return GetTimePassed(starttime, endtime);
+		}
+
+		public static TimeSpan GetTimePassed(long starttime, long endtime)
+		{
+			return new TimeSpan((long)((endtime - starttime) * s_tickFrequency));
+		}
+
+	}
+
+	public sealed class UpdateResult
+	{
+		public class Asset
+		{
+			[JsonRequired]
+			[JsonProperty("browser_download_url")]
+			public string BrowserDownloadUrl { get; set; }
+		}
+
+		[JsonRequired]
+		[JsonProperty("tag_name")]
+		public string TagName { get; set; }
+		[JsonRequired]
+		[JsonProperty("name")]
+		public string Name { get; set; }
+		[JsonRequired]
+		[JsonProperty("assets")]
+		public List<Asset> Assets { get; set; }
+		[JsonRequired]
+		[JsonProperty("body")]
+		public string Body { get; set; }
+	}
+
+	public struct CraftSearchSetup
+	{
+		public double SearchTarget;
+		public double TargetPrecision;
+		public string SearchFilter;
+
+		public Skin[] Outcomes;
+		public InputSkin[] SkinPool;
+
+		public SearchMode SearchMode;
+
+		public int ThreadID;
+		public int ThreadCount;
+	}
+
+	public readonly struct FloatRange
+	{
+		readonly double min;
+		readonly double max;
+
+		public FloatRange(double min, double max)
+		{
+			this.min = min;
+			this.max = max;
+		}
+
+		public bool IsOverlapped(FloatRange other)
+		{
+			return Min <= other.Max && other.Min <= Max;
+		}
+
+		public double Min { get { return min; } }
+		public double Max { get { return max; } }
+	}
+
+	/// <summary>
+	/// Used to store current Discord Presense and update language if needed
+	/// </summary>
+	public sealed class RPCSettingsPersist
+	{
+		public string Details { get; set; }
+		public string State { get; set; }
+
+		public DiscordRPC.Timestamps Timestamp { get; set; }
+		public bool ShowTime { get; set; }
+
+		public DiscordRPC.RichPresence GetPresense()
+		{
+			string details = Utils.EscapeLocalization(Details);
+			string state = Utils.EscapeLocalization(State);
+
+			var rpc = new DiscordRPC.RichPresence()
+			{
+				Details = details,
+				State = state,
+				Assets = new DiscordRPC.Assets()
+				{
+					LargeImageKey = "icon_new",
+					LargeImageText = $"FloatTool {AppHelpers.VersionCode}",
+				},
+			};
+
+			if (ShowTime)
+				rpc.Timestamps = Timestamp;
+
+			return rpc;
+		}
+	}
 
 }
diff --git a/FloatTool/Languages/Lang.ru.xaml b/FloatTool/Languages/Lang.ru.xaml
index d578672..c4bb6d0 100644
--- a/FloatTool/Languages/Lang.ru.xaml
+++ b/FloatTool/Languages/Lang.ru.xaml
@@ -73,7 +73,7 @@
     <v:String x:Key="m_Searching">Поиск</v:String>
 
     <!-- Status bar -->
-    <v:String x:Key="m_SearchingSkin">Ищю скин на торговой площадке</v:String>
+    <v:String x:Key="m_SearchingSkin">Ищу скин на торговой площадке</v:String>
     <v:String x:Key="m_GettingFloats">Получаю флоаты з торговой площадки</v:String>
     <v:String x:Key="m_ErrorCouldntGetFloats">Ошибка! Не удалось получить флоаты с торговой площадки</v:String>
     <v:String x:Key="m_ErrorLessThan10">Ошибка! Не удалось получить больше 10-ти флоатов</v:String>
diff --git a/FloatTool/Languages/Lang.xaml b/FloatTool/Languages/Lang.xaml
index 11e181a..cb9ff5f 100644
--- a/FloatTool/Languages/Lang.xaml
+++ b/FloatTool/Languages/Lang.xaml
@@ -53,6 +53,7 @@
     <v:String x:Key="m_OpenAdvancedSettings">Show advanced</v:String>
     <v:String x:Key="m_FloatAPI">Float fetcher API:</v:String>
     <v:String x:Key="m_CopyFormat">Format for:</v:String>
+    <v:String x:Key="m_UseParallel">Use "Parallel.For"</v:String>
     
     <!-- Benchmark window -->
     <v:String x:Key="m_Benchmark">Benchmark</v:String>
diff --git a/FloatTool/ViewModels/BenchmarkViewModel.cs b/FloatTool/ViewModels/BenchmarkViewModel.cs
index 1d57235..a3b771b 100644
--- a/FloatTool/ViewModels/BenchmarkViewModel.cs
+++ b/FloatTool/ViewModels/BenchmarkViewModel.cs
@@ -15,6 +15,7 @@
 - along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
 
+using FloatTool.Common;
 using Microsoft.Win32;
 using Newtonsoft.Json;
 using System;
@@ -26,232 +27,232 @@
 using System.Windows;
 using System.Windows.Media;
 
-namespace FloatTool
+namespace FloatTool.ViewModels
 {
-    public sealed class BenchmarkViewModel : INotifyPropertyChanged
-    {
-        public sealed class BenchmarkResult
-        {
-            public string CpuName { get; set; }
-            public string ThreadCount { get; set; }
-            public string MultithreadedScore { get; set; }
-            public string SinglethreadedScore { get; set; }
-            public LinearGradientBrush FillBrush { get; set; }
-            public GridLength FillSize { get; set; }
-            public GridLength EmptySize { get; set; }
-        }
-        public long TotalCombinations { get; set; }
+	public sealed class BenchmarkViewModel : INotifyPropertyChanged
+	{
+		public sealed class BenchmarkResult
+		{
+			public string CpuName { get; set; }
+			public string ThreadCount { get; set; }
+			public string MultithreadedScore { get; set; }
+			public string SinglethreadedScore { get; set; }
+			public LinearGradientBrush FillBrush { get; set; }
+			public GridLength FillSize { get; set; }
+			public GridLength EmptySize { get; set; }
+		}
+		public long TotalCombinations { get; set; }
 
-        private int progressPercentage;
-        private bool buttonsEnabled = true;
-        private bool canPublish = false;
-        private bool showOnlyCurrent = false;
-        private int multithreadedSpeed = 0;
-        private int threadCount = Environment.ProcessorCount;
-        public int ThreadCountTested = 0;
-        private int singlethreadedSpeed = 0;
-        private bool isUpdatingEnabled;
+		private int progressPercentage;
+		private bool buttonsEnabled = true;
+		private bool canPublish = false;
+		private bool showOnlyCurrent = false;
+		private int multithreadedSpeed = 0;
+		private int threadCount = Environment.ProcessorCount;
+		public int ThreadCountTested = 0;
+		private int singlethreadedSpeed = 0;
+		private bool isUpdatingEnabled;
 
-        private static readonly LinearGradientBrush AMDBrush = Application.Current.Resources["AmdBenchmarkFill"] as LinearGradientBrush;
-        private static readonly LinearGradientBrush IntelBrush = Application.Current.Resources["IntelBenchmarkFill"] as LinearGradientBrush;
-        private static readonly LinearGradientBrush CurrentBrush = Application.Current.Resources["CurrentBenchmarkFill"] as LinearGradientBrush;
+		private static readonly LinearGradientBrush AMDBrush = Application.Current.Resources["AmdBenchmarkFill"] as LinearGradientBrush;
+		private static readonly LinearGradientBrush IntelBrush = Application.Current.Resources["IntelBenchmarkFill"] as LinearGradientBrush;
+		private static readonly LinearGradientBrush CurrentBrush = Application.Current.Resources["CurrentBenchmarkFill"] as LinearGradientBrush;
 
-        public int ProgressPercentage
-        {
-            get { return progressPercentage; }
-            set
-            {
-                progressPercentage = value;
-                OnPropertyChanged();
-            }
-        }
+		public int ProgressPercentage
+		{
+			get { return progressPercentage; }
+			set
+			{
+				progressPercentage = value;
+				OnPropertyChanged();
+			}
+		}
 
-        public bool ButtonsEnabled
-        {
-            get { return buttonsEnabled; }
-            set
-            {
-                buttonsEnabled = value;
-                OnPropertyChanged();
-            }
-        }
-        public bool CanPublish
-        {
-            get { return canPublish; }
-            set
-            {
-                canPublish = value;
-                OnPropertyChanged();
-            }
-        }
+		public bool ButtonsEnabled
+		{
+			get { return buttonsEnabled; }
+			set
+			{
+				buttonsEnabled = value;
+				OnPropertyChanged();
+			}
+		}
+		public bool CanPublish
+		{
+			get { return canPublish; }
+			set
+			{
+				canPublish = value;
+				OnPropertyChanged();
+			}
+		}
 
-        public int ThreadCount
-        {
-            get
-            {
-                return threadCount;
-            }
-            set
-            {
-                threadCount = value;
-                OnPropertyChanged();
-            }
-        }
-        public bool ShowOnlyCurrent
-        {
-            get
-            {
-                return showOnlyCurrent;
-            }
-            set
-            {
-                showOnlyCurrent = value;
-                PollBenchmarkResults();
-                OnPropertyChanged();
-            }
-        }
+		public int ThreadCount
+		{
+			get
+			{
+				return threadCount;
+			}
+			set
+			{
+				threadCount = value;
+				OnPropertyChanged();
+			}
+		}
+		public bool ShowOnlyCurrent
+		{
+			get
+			{
+				return showOnlyCurrent;
+			}
+			set
+			{
+				showOnlyCurrent = value;
+				PollBenchmarkResults();
+				OnPropertyChanged();
+			}
+		}
 
-        public int MultithreadedSpeed
-        {
-            get { return multithreadedSpeed; }
-            set
-            {
-                multithreadedSpeed = value;
-                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MultithreadedSpeedText)));
-            }
-        }
-        public string MultithreadedSpeedText { get { return $"{multithreadedSpeed:n0}"; } }
+		public int MultithreadedSpeed
+		{
+			get { return multithreadedSpeed; }
+			set
+			{
+				multithreadedSpeed = value;
+				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MultithreadedSpeedText)));
+			}
+		}
+		public string MultithreadedSpeedText { get { return $"{multithreadedSpeed:n0}"; } }
 
-        public int SinglethreadedSpeed
-        {
-            get { return singlethreadedSpeed; }
-            set
-            {
-                singlethreadedSpeed = value;
-                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SinglethreadedSpeedText)));
-            }
-        }
-        public string SinglethreadedSpeedText { get { return $"{singlethreadedSpeed:n0}"; } }
+		public int SinglethreadedSpeed
+		{
+			get { return singlethreadedSpeed; }
+			set
+			{
+				singlethreadedSpeed = value;
+				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SinglethreadedSpeedText)));
+			}
+		}
+		public string SinglethreadedSpeedText { get { return $"{singlethreadedSpeed:n0}"; } }
 
-        public ObservableCollection<BenchmarkResult> BenchmarkResults { get; set; }
-        public string CurrentCpuName { get; set; }
-        public static string CurrentCpuThreads { get { return $"{Environment.ProcessorCount}"; } }
+		public ObservableCollection<BenchmarkResult> BenchmarkResults { get; set; }
+		public string CurrentCpuName { get; set; }
+		public static string CurrentCpuThreads { get { return $"{Environment.ProcessorCount}"; } }
 
-        public bool IsUpdatingEnabled
-        {
-            get { return isUpdatingEnabled; }
-            set { isUpdatingEnabled = value; OnPropertyChanged(); }
-        }
+		public bool IsUpdatingEnabled
+		{
+			get { return isUpdatingEnabled; }
+			set { isUpdatingEnabled = value; OnPropertyChanged(); }
+		}
 
-        public async void PollBenchmarkResults()
-        {
-            var url = Utils.API_URL + "/load";
-            if (ShowOnlyCurrent) url += $"?version={AppHelpers.VersionCode}";
-            Logger.Log.Info($"Getting benchmark results from {url}");
-            IsUpdatingEnabled = false;
-            try
-            {
-                BenchmarkResults.Clear();
-                using var client = new HttpClient();
+		public async void PollBenchmarkResults()
+		{
+			var url = Utils.API_URL + "/load";
+			if (ShowOnlyCurrent) url += $"?version={AppHelpers.VersionCode}";
+			Logger.Log.Info($"Getting benchmark results from {url}");
+			IsUpdatingEnabled = false;
+			try
+			{
+				BenchmarkResults.Clear();
+				using var client = new HttpClient();
 
-                HttpResponseMessage response = await client.GetAsync(url);
-                response.EnsureSuccessStatusCode();
-                string responseBody = await response.Content.ReadAsStringAsync();
-                dynamic result = JsonConvert.DeserializeObject(responseBody);
+				HttpResponseMessage response = await client.GetAsync(url);
+				response.EnsureSuccessStatusCode();
+				string responseBody = await response.Content.ReadAsStringAsync();
+				dynamic result = JsonConvert.DeserializeObject(responseBody);
 
-                if (result["status"] == 200 && result["count"] > 0)
-                {
-                    float maxspeed = result.items[0].multithread;
+				if (result["status"] == 200 && result["count"] > 0)
+				{
+					float maxspeed = result.items[0].multithread;
 
-                    foreach (var benchmark in result.items)
-                    {
-                        float percentage = (float)benchmark.multithread / maxspeed;
-                        float reverse = 1 - percentage;
+					foreach (var benchmark in result.items)
+					{
+						float percentage = (float)benchmark.multithread / maxspeed;
+						float reverse = 1 - percentage;
 
-                        string cpuName = Utils.ShortCpuName((string)benchmark.name);
-                        var currentFill = AMDBrush;
-                        if (cpuName == CurrentCpuName)
-                        {
-                            currentFill = CurrentBrush;
-                            MultithreadedSpeed = Math.Max((int)benchmark.multithread, MultithreadedSpeed);
-                            SinglethreadedSpeed = Math.Max((int)benchmark.singlethread, SinglethreadedSpeed);
-                        }
-                        else if (cpuName.StartsWith("Intel"))
-                            currentFill = IntelBrush;
+						string cpuName = Utils.ShortCpuName((string)benchmark.name);
+						var currentFill = AMDBrush;
+						if (cpuName == CurrentCpuName)
+						{
+							currentFill = CurrentBrush;
+							MultithreadedSpeed = Math.Max((int)benchmark.multithread, MultithreadedSpeed);
+							SinglethreadedSpeed = Math.Max((int)benchmark.singlethread, SinglethreadedSpeed);
+						}
+						else if (cpuName.StartsWith("Intel"))
+							currentFill = IntelBrush;
 
-                        BenchmarkResults.Add(new BenchmarkResult
-                        {
-                            CpuName = cpuName,
-                            ThreadCount = Utils.EscapeLocalization($"{benchmark.threads} {((int)benchmark.threads == 1 ? "%m_Thread%" : "%m_Threads%")} [{benchmark.version}]"),
-                            MultithreadedScore = $"{(int)benchmark.multithread:n0}",
-                            SinglethreadedScore = $"{(int)benchmark.singlethread:n0}",
-                            FillSize = new GridLength(percentage, GridUnitType.Star),
-                            EmptySize = new GridLength(reverse, GridUnitType.Star),
-                            FillBrush = currentFill
-                        });
-                    }
-                }
+						BenchmarkResults.Add(new BenchmarkResult
+						{
+							CpuName = cpuName,
+							ThreadCount = Utils.EscapeLocalization($"{benchmark.threads} {((int)benchmark.threads == 1 ? "%m_Thread%" : "%m_Threads%")} [{benchmark.version}]"),
+							MultithreadedScore = $"{(int)benchmark.multithread:n0}",
+							SinglethreadedScore = $"{(int)benchmark.singlethread:n0}",
+							FillSize = new GridLength(percentage, GridUnitType.Star),
+							EmptySize = new GridLength(reverse, GridUnitType.Star),
+							FillBrush = currentFill
+						});
+					}
+				}
 
-                Logger.Log.Info($"Benchmark results loaded: {BenchmarkResults.Count} items");
+				Logger.Log.Info($"Benchmark results loaded: {BenchmarkResults.Count} items");
 
-                if (BenchmarkResults.Count == 0)
-                    throw new Exception("0 results");
-            }
-            catch (Exception ex)
-            {
-                BenchmarkResults.Add(new BenchmarkResult
-                {
-                    CpuName = "Error loading benchmark table: " + ex.Message,
-                    FillSize = new GridLength(0, GridUnitType.Star),
-                    EmptySize = new GridLength(1, GridUnitType.Star),
-                });
-                Logger.Log.Error("Error getting benchmark results", ex);
-            }
-            IsUpdatingEnabled = true;
-        }
+				if (BenchmarkResults.Count == 0)
+					throw new Exception("0 results");
+			}
+			catch (Exception ex)
+			{
+				BenchmarkResults.Add(new BenchmarkResult
+				{
+					CpuName = "Error loading benchmark table: " + ex.Message,
+					FillSize = new GridLength(0, GridUnitType.Star),
+					EmptySize = new GridLength(1, GridUnitType.Star),
+				});
+				Logger.Log.Error("Error getting benchmark results", ex);
+			}
+			IsUpdatingEnabled = true;
+		}
 
-        public async void PushBenchmarkResults()
-        {
-            Logger.Log.Info("Sending benchmark result");
-            try
-            {
-                BenchmarkResults.Clear();
-                using var client = new HttpClient();
-                var version = Assembly.GetExecutingAssembly().GetName().Version;
-                client.DefaultRequestHeaders.Add("User-Agent", $"FloatTool/{AppHelpers.VersionCode}");
-                string paramedURL = $"/submit?cpu={CurrentCpuName}&threads={ThreadCountTested}&multicore={MultithreadedSpeed}&singlecore={SinglethreadedSpeed}";
-                HttpResponseMessage response = await client.GetAsync(Utils.API_URL + paramedURL);
-                response.EnsureSuccessStatusCode();
-                string responseBody = await response.Content.ReadAsStringAsync();
+		public async void PushBenchmarkResults()
+		{
+			Logger.Log.Info("Sending benchmark result");
+			try
+			{
+				BenchmarkResults.Clear();
+				using var client = new HttpClient();
+				var version = Assembly.GetExecutingAssembly().GetName().Version;
+				client.DefaultRequestHeaders.Add("User-Agent", $"FloatTool/{AppHelpers.VersionCode}");
+				string paramedURL = $"/submit?cpu={CurrentCpuName}&threads={ThreadCountTested}&multicore={MultithreadedSpeed}&singlecore={SinglethreadedSpeed}";
+				HttpResponseMessage response = await client.GetAsync(Utils.API_URL + paramedURL);
+				response.EnsureSuccessStatusCode();
+				string responseBody = await response.Content.ReadAsStringAsync();
 
-                Logger.Log.Info("Sended benchmark result");
-            }
-            catch (Exception ex)
-            {
-                Logger.Log.Error("Error sending benchmark result", ex);
-            }
-            CanPublish = false;
-            PollBenchmarkResults();
-        }
+				Logger.Log.Info("Sended benchmark result");
+			}
+			catch (Exception ex)
+			{
+				Logger.Log.Error("Error sending benchmark result", ex);
+			}
+			CanPublish = false;
+			PollBenchmarkResults();
+		}
 
-        public BenchmarkViewModel()
-        {
-            ThreadCount = AppHelpers.Settings.ThreadCount;
-            string path = @"HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0";
-            string cpu = (string)Registry.GetValue(path, "ProcessorNameString", "Unknown");
-            CurrentCpuName = Utils.ShortCpuName(cpu);
+		public BenchmarkViewModel()
+		{
+			ThreadCount = AppHelpers.Settings.ThreadCount;
+			string path = @"HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0";
+			string cpu = (string)Registry.GetValue(path, "ProcessorNameString", "Unknown");
+			CurrentCpuName = Utils.ShortCpuName(cpu);
 
-            Logger.Log.Info($"CPU: {CurrentCpuName}");
-            Logger.Log.Info($"Threads: {CurrentCpuThreads}");
+			Logger.Log.Info($"CPU: {CurrentCpuName}");
+			Logger.Log.Info($"Threads: {CurrentCpuThreads}");
 
-            BenchmarkResults = new ObservableCollection<BenchmarkResult>();
-            PollBenchmarkResults();
-        }
+			BenchmarkResults = new ObservableCollection<BenchmarkResult>();
+			PollBenchmarkResults();
+		}
 
-        public event PropertyChangedEventHandler PropertyChanged;
-        private void OnPropertyChanged([CallerMemberName] string name = null)
-        {
-            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
-        }
-    }
+		public event PropertyChangedEventHandler PropertyChanged;
+		private void OnPropertyChanged([CallerMemberName] string name = null)
+		{
+			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+		}
+	}
 }
diff --git a/FloatTool/ViewModels/MainViewModel.cs b/FloatTool/ViewModels/MainViewModel.cs
index 4e154d2..3a03ae2 100644
--- a/FloatTool/ViewModels/MainViewModel.cs
+++ b/FloatTool/ViewModels/MainViewModel.cs
@@ -15,6 +15,7 @@
 - along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
 
+using FloatTool.Common;
 using Newtonsoft.Json;
 using System;
 using System.Collections.Generic;
@@ -27,546 +28,544 @@
 using System.Windows;
 using System.Windows.Controls;
 
-namespace FloatTool
+namespace FloatTool.ViewModels
 {
-    public enum SearchMode
-    {
-        Less, Equal, Greater
-    }
-
-    public sealed class Combination
-    {
-        public double Wear { get; set; }
-        public string Wear32Bit { get; set; }
-        public string Wear128Bit { get; set; }
-        public InputSkin[] Inputs { get; set; }
-        public string OutcomeName { get; set; }
-        public Currency Currency { get; set; }
-        public float Price { get; set; }
-        public DateTime Time { get; set; } = DateTime.Now;
-    }
-
-    public sealed class MainViewModel : INotifyPropertyChanged
-    {
-        private bool isSearching;
-
-        private bool isStatTrak;
-        private string weaponName;
-        private string skinName;
-        private string skinQuality;
-        private string fullSkinName;
-        private int outcomeIndex;
-        private string floatRange;
-
-        private SearchMode searchMode;
-        private string searchFilter;
-        private int skinCount;
-        private int skinSkipCount;
-        private Visibility isError = Visibility.Hidden;
-        private Visibility isErrorFloat = Visibility.Hidden;
-        private readonly Viewbox errorMessage;
-        private readonly Viewbox errorMessageFloat;
-        private bool sort;
-        private bool sortDescending;
-        private float progressPercentage;
-        private string currentSpeed = "0";
-
-        private readonly List<string> qualityList = new()
-        {
-            "Factory New",
-            "Minimal Wear",
-            "Field-Tested",
-            "Well-Worn",
-            "Battle-Scarred"
-        };
-
-        private readonly List<string> weaponList = new()
-        {
-            "AK-47",
-            "AUG",
-            "AWP",
-            "CZ75-Auto",
-            "Desert Eagle",
-            "Dual Berettas",
-            "FAMAS",
-            "Five-SeveN",
-            "G3SG1",
-            "Galil AR",
-            "Glock-18",
-            "M249",
-            "M4A1-S",
-            "M4A4",
-            "MAC-10",
-            "MAG-7",
-            "MP5-SD",
-            "MP7",
-            "MP9",
-            "Negev",
-            "Nova",
-            "P2000",
-            "P250",
-            "P90",
-            "PP-Bizon",
-            "R8 Revolver",
-            "SCAR-20",
-            "SG 553",
-            "SSG 08",
-            "Sawed-Off",
-            "Tec-9",
-            "UMP-45",
-            "USP-S",
-            "XM1014"
-        };
-
-        private List<string> skinList = new();
-        private List<string> outcomeList = new();
-        public Dictionary<Tuple<double, double>, List<Skin>> Outcomes = new();
-
-        public List<Collection> SkinsDatabase;
-        
-        #region Properties
-
-        public ObservableCollection<Combination> FoundCombinations { get; set; }
-
-        public static string CurrentVersionSubtitle
-        {
-            get { return $"{AppHelpers.VersionCode} by Prevter"; }
-        }
-
-        public int ThreadCount
-        {
-            get { return AppHelpers.Settings.ThreadCount; }
-            set { AppHelpers.Settings.ThreadCount = value; OnPropertyChanged(); }
-        }
-
-        public string CombinationsLabel
-        {
-            get { return $"{ParsedCombinations:n0}/{TotalCombinations}"; }
-            set { OnPropertyChanged(); }
-        }
-
-        public string CurrentSpeedLabel
-        {
-            get { return currentSpeed; }
-            set { currentSpeed = value; OnPropertyChanged(); }
-        }
-
-        public string SearchFilter
-        {
-            get { return searchFilter; }
-            set
-            {
-                searchFilter = value;
-                UpdateFloatError();
-                OnPropertyChanged();
-            }
-        }
-
-        public int SkinCount
-        {
-            get { return skinCount; }
-            set
-            {
-                skinCount = value;
-                OnPropertyChanged();
-            }
-        }
-
-        public int SkinSkipCount
-        {
-            get { return skinSkipCount; }
-            set
-            {
-                skinSkipCount = value;
-                OnPropertyChanged();
-            }
-        }
-
-        public bool Sort
-        {
-            get { return sort; }
-            set { sort = value; OnPropertyChanged(); }
-        }
-
-        public bool SortDescending
-        {
-            get { return sortDescending; }
-            set { sortDescending = value; OnPropertyChanged(); }
-        }
-
-        public SearchMode SearchModeSelected
-        {
-            get { return searchMode; }
-            set { searchMode = value; OnPropertyChanged(); }
-        }
-
-        public string FloatRange
-        {
-            get { return floatRange; }
-            set { floatRange = value; OnPropertyChanged(); }
-        }
-
-        public int OutcomeIndex
-        {
-            get { return outcomeIndex; }
-            set
-            {
-                outcomeIndex = value;
-                UpdateFloatRange();
-                OnPropertyChanged();
-            }
-        }
-
-        public bool CanEditSettings
-        {
-            get { return !isSearching; }
-            set { isSearching = !value; OnPropertyChanged(); }
-        }
-
-        public string WeaponName
-        {
-            get { return weaponName; }
-            set
-            {
-                weaponName = value;
-                UpdateFullSkinName();
-                UpdateOutcomes();
-                UpdateSkinList();
-                OnPropertyChanged();
-            }
-        }
-
-        public string SkinName
-        {
-            get { return skinName; }
-            set
-            {
-                skinName = value;
-                UpdateFullSkinName();
-                UpdateOutcomes();
-                OnPropertyChanged();
-            }
-        }
-
-        public string SkinQuality
-        {
-            get { return skinQuality; }
-            set
-            {
-                skinQuality = value;
-                UpdateFullSkinName();
-                UpdateFloatRange();
-                OnPropertyChanged();
-            }
-        }
-
-        public bool IsStatTrak
-        {
-            get { return isStatTrak; }
-            set
-            {
-                isStatTrak = value;
-                UpdateFullSkinName();
-                OnPropertyChanged();
-            }
-        }
-
-        public string FullSkinName
-        {
-            get { return fullSkinName; }
-            set { fullSkinName = value; OnPropertyChanged(); }
-        }
-
-        public List<string> WeaponList { get { return weaponList; } }
-
-        public List<string> SkinList
-        {
-            get { return skinList; }
-            set { skinList = value; OnPropertyChanged(); }
-        }
-
-        public List<string> QualityList { get { return qualityList; } }
-
-        public List<string> OutcomeList
-        {
-            get { return outcomeList; }
-            set { outcomeList = value; OnPropertyChanged(); }
-        }
-
-        public Visibility IsError
-        {
-            get { return isError; }
-            set { isError = value; OnPropertyChanged(); }
-        }
-
-        public Visibility IsErrorFloat
-        {
-            get { return isErrorFloat; }
-            set { isErrorFloat = value; OnPropertyChanged(); }
-        }
-
-        public float ProgressPercentage
-        {
-            get { return progressPercentage; }
-            set { progressPercentage = value; OnPropertyChanged(); }
-        }
-
-        public long ParsedCombinations { get; internal set; }
-        public long TotalCombinations { get; internal set; }
-
-        private RelayCommand copyCommand;
-        public RelayCommand CopyCommand
-        {
-            get
-            {
-                return copyCommand ??= new RelayCommand(field =>
-                {
-                    var textbox = field as TextBox;
-                    Clipboard.SetText(textbox.Text);
-                    textbox?.Focus();
-                    textbox?.SelectAll();
-                });
-            }
-        }
-
-        #endregion
-
-        private void UpdateFullSkinName()
-        {
-            FullSkinName = $"{(IsStatTrak ? "StatTrak™ " : "")}{WeaponName} | {SkinName} ({SkinQuality})";
-
-            if (SkinsDatabase is null)
-                return;
-
-            foreach (var collection in SkinsDatabase)
-            {
-                foreach (var skin in collection.Skins)
-                {
-                    if (skin.Name == $"{WeaponName} | {SkinName}")
-                    {
-                        if (!skin.IsQualityInRange(SkinQuality))
-                        {
-                            errorMessage.SetResourceReference(Viewbox.ToolTipProperty, "m_SkinNotFound");
-                            IsError = Visibility.Visible;
-                        }
-                        else if (IsStatTrak && !collection.CanBeStattrak)
-                        {
-                            errorMessage.SetResourceReference(Viewbox.ToolTipProperty, "m_CantBeStattrak");
-                            IsError = Visibility.Visible;
-                        }
-                        else
-                        {
-                            IsError = Visibility.Hidden;
-                        }
-                    }
-                }
-            }
-        }
-
-        private float minCraftWear, maxCraftWear;
-
-        private void UpdateFloatRange()
-        {
-            if (OutcomeIndex > Outcomes.Count - 1)
-            {
-                FloatRange = "No data";
-                return;
-            }
-
-            var range = Skin.GetFloatRangeForQuality(SkinQuality);
-
-            List<InputSkin> lowest = new();
-            for (int i = 0; i < 10; i++)
-                lowest.Add(new InputSkin(range.Min, 0, Currency.USD));
-
-            List<InputSkin> highest = new();
-            for (int i = 0; i < 10; i++)
-                highest.Add(new InputSkin(range.Max, 0, Currency.USD));
-
-            int index = 0;
-            minCraftWear = 0;
-            maxCraftWear = 0;
-            foreach (var outcome in Outcomes.Values)
-            {
-                if (index++ == OutcomeIndex)
-                {
-                    var currSkin = outcome[0];
-                    minCraftWear = Convert.ToSingle(
-                        Calculations.Craft(
-                            lowest.ToArray(),
-                            currSkin.MinFloat,
-                            currSkin.FloatRange
-                        ),
-                        CultureInfo.InvariantCulture
-                    );
-                    maxCraftWear = Convert.ToSingle(
-                        Calculations.Craft(
-                            highest.ToArray(),
-                            currSkin.MinFloat,
-                            currSkin.FloatRange
-                        ),
-                        CultureInfo.InvariantCulture
-                    );
-                    break;
-                }
-            }
-
-            UpdateFloatError();
-            FloatRange = $"{minCraftWear.ToString("0.00", CultureInfo.InvariantCulture)} - {maxCraftWear.ToString("0.00", CultureInfo.InvariantCulture)}";
-        }
-
-        public void UpdateFloatError()
-        {
-            try
-            {
-                decimal searchFilterDecimal = decimal.Parse(searchFilter, CultureInfo.InvariantCulture);
-                if (searchFilterDecimal < (decimal)minCraftWear || searchFilterDecimal > (decimal)maxCraftWear)
-                {
-                    IsErrorFloat = Visibility.Visible;
-                    errorMessageFloat.SetResourceReference(Viewbox.ToolTipProperty, "m_OutOfBounds");
-                }
-                else
-                {
-                    IsErrorFloat = Visibility.Hidden;
-                }
-            }
-            catch (FormatException)
-            {
-                IsErrorFloat = Visibility.Visible;
-                errorMessageFloat.SetResourceReference(Viewbox.ToolTipProperty, "m_CantParse");
-            }
-        }
-
-
-        public Collection FindSkinCollection(string skin)
-        {
-            if (SkinsDatabase is null)
-                return null;
-
-            foreach (var collection in SkinsDatabase)
-            {
-                foreach (var skinObj in collection.Skins)
-                {
-                    if (skinObj.Name == skin)
-                    {
-                        return collection;
-                    }
-
-                }
-            }
-
-            return null;
-        }
-
-        public void UpdateOutcomes()
-        {
-            if (SkinsDatabase is null)
-                return;
-
-            string skin = $"{WeaponName} | {SkinName}";
-            var skinlist = new List<SkinModel>();
-
-            var collection = FindSkinCollection(skin);
-            if (collection is null)
-                return;
-
-            foreach (var skinObj in collection.Skins)
-            {
-                if (skinObj.Name == skin)
-                {
-                    foreach (var otherSkinModel in collection.Skins)
-                    {
-                        if (Skin.NextRarity(skinObj.Rarity) == otherSkinModel.Rarity)
-                        {
-                            skinlist.Add(otherSkinModel);
-                        }
-                    }
-                    break;
-                }
-            }
-            Outcomes.Clear();
-
-            for (int i = 0; i < skinlist.Count; i++)
-            {
-                var range = new Tuple<double, double>(skinlist[i].MinWear, skinlist[i].MaxWear);
-                if (Outcomes.ContainsKey(range))
-                    Outcomes[range].Add(new Skin(skinlist[i]));
-                else
-                    Outcomes[range] = new List<Skin> { new Skin(skinlist[i]) };
-            }
-
-            int totalSkins = 0;
-            foreach (var skinRange in Outcomes.Values)
-                totalSkins += skinRange.Count;
-
-            var list = new List<string>();
-
-            foreach (var skinRange in Outcomes.Values)
-            {
-                string tmp = (skinRange.Count > 1) ? $" + {(skinRange.Count - 1)}" : "";
-                list.Add($"{((float)skinRange.Count) / totalSkins * 100}% ({skinRange[0].Name}{tmp})");
-            }
-
-            list.Add("* Search all *");
-            OutcomeIndex = 0;
-            OutcomeList = list;
-        }
-
-        public void UpdateSkinList()
-        {
-            if (SkinsDatabase is null)
-                return;
-
-            var list = new List<string>();
-
-            foreach (var collection in SkinsDatabase)
-            {
-                foreach (var skin in collection.Skins)
-                {
-                    if (skin.Name.Contains(WeaponName) && collection.HighestRarity != skin.Rarity)
-                    {
-                        list.Add(skin.Name.Split(" | ")[1]);
-                    }
-                }
-            }
-
-            list.Sort();
-            SkinList = list;
-        }
-
-        public MainViewModel(string weapon, string skin, string quality, string filter, int count, int skip, Viewbox errorTooltip, Viewbox errorTooltipFloat)
-        {
-            errorMessage = errorTooltip;
-            errorMessageFloat = errorTooltipFloat;
-            WeaponName = weapon;
-            SkinName = skin;
-            SkinQuality = quality;
-
-            SearchModeSelected = SearchMode.Equal;
-            SearchFilter = filter;
-            SkinCount = count;
-            SkinSkipCount = skip;
-
-            FoundCombinations = new();
-
-            Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FloatTool.Assets.SkinList.json")!;
-            if (stream is null)
-                throw new NullReferenceException("Could not find SkinList.json");
-
-            using (StreamReader reader = new(stream))
-            {
-                var jsonFileContent = reader.ReadToEnd();
-                SkinsDatabase = JsonConvert.DeserializeObject<List<Collection>>(jsonFileContent)!;
-            }
-
-            UpdateSkinList();
-            UpdateOutcomes();
-        }
-
-        public event PropertyChangedEventHandler PropertyChanged;
-        private void OnPropertyChanged([CallerMemberName] string name = null)
-        {
-            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
-        }
-    }
+	public enum SearchMode
+	{
+		Less, Equal, Greater
+	}
+
+	public sealed class Combination
+	{
+		public double Wear { get; set; }
+		public string Wear32Bit { get; set; }
+		public string Wear128Bit { get; set; }
+		public InputSkin[] Inputs { get; set; }
+		public string OutcomeName { get; set; }
+		public Currency Currency { get; set; }
+		public float Price { get; set; }
+		public DateTime Time { get; set; } = DateTime.Now;
+	}
+
+	public sealed class MainViewModel : INotifyPropertyChanged
+	{
+		private bool isSearching;
+
+		private bool isStatTrak;
+		private string weaponName;
+		private string skinName;
+		private string skinQuality;
+		private string fullSkinName;
+		private int outcomeIndex;
+		private string floatRange;
+
+		private SearchMode searchMode;
+		private string searchFilter;
+		private int skinCount;
+		private int skinSkipCount;
+		private Visibility isError = Visibility.Hidden;
+		private Visibility isErrorFloat = Visibility.Hidden;
+		private readonly Viewbox errorMessage;
+		private readonly Viewbox errorMessageFloat;
+		private bool sort;
+		private bool sortDescending;
+		private float progressPercentage;
+		private string currentSpeed = "0";
+
+		private readonly List<string> qualityList = new()
+		{
+			"Factory New",
+			"Minimal Wear",
+			"Field-Tested",
+			"Well-Worn",
+			"Battle-Scarred"
+		};
+
+		private readonly List<string> weaponList = new()
+		{
+			"AK-47",
+			"AUG",
+			"AWP",
+			"CZ75-Auto",
+			"Desert Eagle",
+			"Dual Berettas",
+			"FAMAS",
+			"Five-SeveN",
+			"G3SG1",
+			"Galil AR",
+			"Glock-18",
+			"M249",
+			"M4A1-S",
+			"M4A4",
+			"MAC-10",
+			"MAG-7",
+			"MP5-SD",
+			"MP7",
+			"MP9",
+			"Negev",
+			"Nova",
+			"P2000",
+			"P250",
+			"P90",
+			"PP-Bizon",
+			"R8 Revolver",
+			"SCAR-20",
+			"SG 553",
+			"SSG 08",
+			"Sawed-Off",
+			"Tec-9",
+			"UMP-45",
+			"USP-S",
+			"XM1014"
+		};
+
+		private List<string> skinList = new();
+		private List<string> outcomeList = new();
+		public Dictionary<Tuple<double, double>, List<Skin>> Outcomes = new();
+
+		public List<Collection> SkinsDatabase;
+
+		#region Properties
+
+		public ObservableCollection<Combination> FoundCombinations { get; set; }
+
+		public static string CurrentVersionSubtitle
+		{
+			get { return $"{AppHelpers.VersionCode} by Prevter"; }
+		}
+
+		public int ThreadCount
+		{
+			get { return AppHelpers.Settings.ThreadCount; }
+			set { AppHelpers.Settings.ThreadCount = value; OnPropertyChanged(); }
+		}
+
+		public string CombinationsLabel
+		{
+			get { return $"{ParsedCombinations:n0}/{TotalCombinations}"; }
+			set { OnPropertyChanged(); }
+		}
+
+		public string CurrentSpeedLabel
+		{
+			get { return currentSpeed; }
+			set { currentSpeed = value; OnPropertyChanged(); }
+		}
+
+		public string SearchFilter
+		{
+			get { return searchFilter; }
+			set
+			{
+				searchFilter = value;
+				UpdateFloatError();
+				OnPropertyChanged();
+			}
+		}
+
+		public int SkinCount
+		{
+			get { return skinCount; }
+			set
+			{
+				skinCount = value;
+				OnPropertyChanged();
+			}
+		}
+
+		public int SkinSkipCount
+		{
+			get { return skinSkipCount; }
+			set
+			{
+				skinSkipCount = value;
+				OnPropertyChanged();
+			}
+		}
+
+		public bool Sort
+		{
+			get { return sort; }
+			set { sort = value; OnPropertyChanged(); }
+		}
+
+		public bool SortDescending
+		{
+			get { return sortDescending; }
+			set { sortDescending = value; OnPropertyChanged(); }
+		}
+
+		public SearchMode SearchModeSelected
+		{
+			get { return searchMode; }
+			set { searchMode = value; OnPropertyChanged(); }
+		}
+
+		public string FloatRange
+		{
+			get { return floatRange; }
+			set { floatRange = value; OnPropertyChanged(); }
+		}
+
+		public int OutcomeIndex
+		{
+			get { return outcomeIndex; }
+			set
+			{
+				outcomeIndex = value;
+				UpdateFloatRange();
+				OnPropertyChanged();
+			}
+		}
+
+		public bool CanEditSettings
+		{
+			get { return !isSearching; }
+			set { isSearching = !value; OnPropertyChanged(); }
+		}
+
+		public string WeaponName
+		{
+			get { return weaponName; }
+			set
+			{
+				weaponName = value;
+				UpdateFullSkinName();
+				UpdateOutcomes();
+				UpdateSkinList();
+				OnPropertyChanged();
+			}
+		}
+
+		public string SkinName
+		{
+			get { return skinName; }
+			set
+			{
+				skinName = value;
+				UpdateFullSkinName();
+				UpdateOutcomes();
+				OnPropertyChanged();
+			}
+		}
+
+		public string SkinQuality
+		{
+			get { return skinQuality; }
+			set
+			{
+				skinQuality = value;
+				UpdateFullSkinName();
+				UpdateFloatRange();
+				OnPropertyChanged();
+			}
+		}
+
+		public bool IsStatTrak
+		{
+			get { return isStatTrak; }
+			set
+			{
+				isStatTrak = value;
+				UpdateFullSkinName();
+				OnPropertyChanged();
+			}
+		}
+
+		public string FullSkinName
+		{
+			get { return fullSkinName; }
+			set { fullSkinName = value; OnPropertyChanged(); }
+		}
+
+		public List<string> WeaponList { get { return weaponList; } }
+
+		public List<string> SkinList
+		{
+			get { return skinList; }
+			set { skinList = value; OnPropertyChanged(); }
+		}
+
+		public List<string> QualityList { get { return qualityList; } }
+
+		public List<string> OutcomeList
+		{
+			get { return outcomeList; }
+			set { outcomeList = value; OnPropertyChanged(); }
+		}
+
+		public Visibility IsError
+		{
+			get { return isError; }
+			set { isError = value; OnPropertyChanged(); }
+		}
+
+		public Visibility IsErrorFloat
+		{
+			get { return isErrorFloat; }
+			set { isErrorFloat = value; OnPropertyChanged(); }
+		}
+
+		public float ProgressPercentage
+		{
+			get { return progressPercentage; }
+			set { progressPercentage = value; OnPropertyChanged(); }
+		}
+
+		public long ParsedCombinations { get; internal set; }
+		public long TotalCombinations { get; internal set; }
+
+		private RelayCommand copyCommand;
+		public RelayCommand CopyCommand
+		{
+			get
+			{
+				return copyCommand ??= new RelayCommand(field =>
+				{
+					var textbox = field as TextBox;
+					Clipboard.SetText(textbox.Text);
+					textbox?.Focus();
+					textbox?.SelectAll();
+				});
+			}
+		}
+
+		#endregion
+
+		private void UpdateFullSkinName()
+		{
+			FullSkinName = $"{(IsStatTrak ? "StatTrak™ " : "")}{WeaponName} | {SkinName} ({SkinQuality})";
+
+			if (SkinsDatabase is null)
+				return;
+
+			foreach (var collection in SkinsDatabase)
+			{
+				foreach (var skin in collection.Skins)
+				{
+					if (skin.Name == $"{WeaponName} | {SkinName}")
+					{
+						if (!skin.IsQualityInRange(SkinQuality))
+						{
+							errorMessage.SetResourceReference(FrameworkElement.ToolTipProperty, "m_SkinNotFound");
+							IsError = Visibility.Visible;
+						}
+						else if (IsStatTrak && !collection.CanBeStattrak)
+						{
+							errorMessage.SetResourceReference(FrameworkElement.ToolTipProperty, "m_CantBeStattrak");
+							IsError = Visibility.Visible;
+						}
+						else
+						{
+							IsError = Visibility.Hidden;
+						}
+					}
+				}
+			}
+		}
+
+		private float minCraftWear, maxCraftWear;
+
+		private void UpdateFloatRange()
+		{
+			if (OutcomeIndex > Outcomes.Count - 1)
+			{
+				FloatRange = "No data";
+				return;
+			}
+
+			var range = Skin.GetFloatRangeForQuality(SkinQuality);
+
+			List<InputSkin> lowest = new();
+			for (int i = 0; i < 10; i++)
+				lowest.Add(new InputSkin(range.Min, 0, Currency.USD));
+
+			List<InputSkin> highest = new();
+			for (int i = 0; i < 10; i++)
+				highest.Add(new InputSkin(range.Max, 0, Currency.USD));
+
+			int index = 0;
+			minCraftWear = 0;
+			maxCraftWear = 0;
+			foreach (var outcome in Outcomes.Values)
+			{
+				if (index++ == OutcomeIndex)
+				{
+					var currSkin = outcome[0];
+					minCraftWear = Convert.ToSingle(
+						Calculations.Craft(
+							lowest.ToArray(),
+							currSkin.MinFloat,
+							currSkin.FloatRange
+						),
+						CultureInfo.InvariantCulture
+					);
+					maxCraftWear = Convert.ToSingle(
+						Calculations.Craft(
+							highest.ToArray(),
+							currSkin.MinFloat,
+							currSkin.FloatRange
+						),
+						CultureInfo.InvariantCulture
+					);
+					break;
+				}
+			}
+
+			UpdateFloatError();
+			FloatRange = $"{minCraftWear.ToString("0.00", CultureInfo.InvariantCulture)} - {maxCraftWear.ToString("0.00", CultureInfo.InvariantCulture)}";
+		}
+
+		public void UpdateFloatError()
+		{
+			try
+			{
+				decimal searchFilterDecimal = decimal.Parse(searchFilter, CultureInfo.InvariantCulture);
+				if (searchFilterDecimal < (decimal)minCraftWear || searchFilterDecimal > (decimal)maxCraftWear)
+				{
+					IsErrorFloat = Visibility.Visible;
+					errorMessageFloat.SetResourceReference(FrameworkElement.ToolTipProperty, "m_OutOfBounds");
+				}
+				else
+				{
+					IsErrorFloat = Visibility.Hidden;
+				}
+			}
+			catch (FormatException)
+			{
+				IsErrorFloat = Visibility.Visible;
+				errorMessageFloat.SetResourceReference(FrameworkElement.ToolTipProperty, "m_CantParse");
+			}
+		}
+
+
+		public Collection FindSkinCollection(string skin)
+		{
+			if (SkinsDatabase is null)
+				return null;
+
+			foreach (var collection in SkinsDatabase)
+			{
+				foreach (var skinObj in collection.Skins)
+				{
+					if (skinObj.Name == skin)
+					{
+						return collection;
+					}
+
+				}
+			}
+
+			return null;
+		}
+
+		public void UpdateOutcomes()
+		{
+			if (SkinsDatabase is null)
+				return;
+
+			string skin = $"{WeaponName} | {SkinName}";
+			var skinlist = new List<SkinModel>();
+
+			var collection = FindSkinCollection(skin);
+			if (collection is null)
+				return;
+
+			foreach (var skinObj in collection.Skins)
+			{
+				if (skinObj.Name == skin)
+				{
+					foreach (var otherSkinModel in collection.Skins)
+					{
+						if (Skin.NextRarity(skinObj.Rarity) == otherSkinModel.Rarity)
+						{
+							skinlist.Add(otherSkinModel);
+						}
+					}
+					break;
+				}
+			}
+			Outcomes.Clear();
+
+			for (int i = 0; i < skinlist.Count; i++)
+			{
+				var range = new Tuple<double, double>(skinlist[i].MinWear, skinlist[i].MaxWear);
+				if (Outcomes.TryGetValue(range, out List<Skin> value))
+					value.Add(new Skin(skinlist[i]));
+				else
+					Outcomes[range] = new List<Skin> { new Skin(skinlist[i]) };
+			}
+
+			int totalSkins = 0;
+			foreach (var skinRange in Outcomes.Values)
+				totalSkins += skinRange.Count;
+
+			var list = new List<string>();
+
+			foreach (var skinRange in Outcomes.Values)
+			{
+				string tmp = skinRange.Count > 1 ? $" + {skinRange.Count - 1}" : "";
+				list.Add($"{(float)skinRange.Count / totalSkins * 100}% ({skinRange[0].Name}{tmp})");
+			}
+
+			list.Add("* Search all *");
+			OutcomeIndex = 0;
+			OutcomeList = list;
+		}
+
+		public void UpdateSkinList()
+		{
+			if (SkinsDatabase is null)
+				return;
+
+			var list = new List<string>();
+
+			foreach (var collection in SkinsDatabase)
+			{
+				foreach (var skin in collection.Skins)
+				{
+					if (skin.Name.Contains(WeaponName) && collection.HighestRarity != skin.Rarity)
+					{
+						list.Add(skin.Name.Split(" | ")[1]);
+					}
+				}
+			}
+
+			list.Sort();
+			SkinList = list;
+		}
+
+		public MainViewModel(string weapon, string skin, string quality, string filter, int count, int skip, Viewbox errorTooltip, Viewbox errorTooltipFloat)
+		{
+			errorMessage = errorTooltip;
+			errorMessageFloat = errorTooltipFloat;
+			WeaponName = weapon;
+			SkinName = skin;
+			SkinQuality = quality;
+
+			SearchModeSelected = SearchMode.Equal;
+			SearchFilter = filter;
+			SkinCount = count;
+			SkinSkipCount = skip;
+
+			FoundCombinations = new();
+
+			Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FloatTool.Assets.SkinList.json")!
+				?? throw new NullReferenceException("Could not find SkinList.json");
+			using (StreamReader reader = new(stream))
+			{
+				var jsonFileContent = reader.ReadToEnd();
+				SkinsDatabase = JsonConvert.DeserializeObject<List<Collection>>(jsonFileContent)!;
+			}
+
+			UpdateSkinList();
+			UpdateOutcomes();
+		}
+
+		public event PropertyChangedEventHandler PropertyChanged;
+		private void OnPropertyChanged([CallerMemberName] string name = null)
+		{
+			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+		}
+	}
 }
diff --git a/FloatTool/ViewModels/SettingsViewModel.cs b/FloatTool/ViewModels/SettingsViewModel.cs
index 1eefa90..b21e637 100644
--- a/FloatTool/ViewModels/SettingsViewModel.cs
+++ b/FloatTool/ViewModels/SettingsViewModel.cs
@@ -15,233 +15,240 @@
 - along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
 
+using FloatTool.Common;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.IO;
 using System.Runtime.CompilerServices;
 
-namespace FloatTool
+namespace FloatTool.ViewModels
 {
-    public sealed class SettingsViewModel : INotifyPropertyChanged
-    {
-        public static List<string> ThemesList
-        {
-            get
-            {
-                var tmpList = new List<string>();
-                foreach (var theme in AppHelpers.ThemesFound)
-                    tmpList.Add(Path.GetFileNameWithoutExtension(theme));
-                return tmpList;
-            }
-        }
-
-        public int ThemeIndex
-        {
-            get { return AppHelpers.ThemesFound.IndexOf(AppHelpers.Settings.ThemeURI); }
-            set
-            {
-                AppHelpers.Settings.ThemeURI = AppHelpers.ThemesFound[value];
-                App.SelectTheme(AppHelpers.Settings.ThemeURI);
-                OnPropertyChanged();
-            }
-        }
-
-        public bool EnableSound
-        {
-            get { return AppHelpers.Settings.Sound; }
-            set { AppHelpers.Settings.Sound = value; OnPropertyChanged(); }
-        }
-
-        public bool CheckUpdates
-        {
-            get { return AppHelpers.Settings.CheckForUpdates; }
-            set { AppHelpers.Settings.CheckForUpdates = value; OnPropertyChanged(); }
-        }
-
-        public bool DiscordRPC
-        {
-            get { return AppHelpers.Settings.DiscordRPC; }
-            set
-            {
-                AppHelpers.Settings.DiscordRPC = value;
-                OnPropertyChanged();
-
-                // Re-enabling does not work. Probably bug in the library
-                if (!value)
-                    AppHelpers.DiscordClient.Deinitialize();
-                else
-                    AppHelpers.DiscordClient.Initialize();
-            }
-        }
-
-        public int CurrentCurrencyIndex
-        {
-            get
-            {
-                return CurrencyHelper.GetIndexFromCurrency(AppHelpers.Settings.Currency);
-            }
-            set
-            {
-                AppHelpers.Settings.Currency = CurrencyHelper.GetCurrencyByIndex(value);
-                OnPropertyChanged();
-            }
-        }
-
-        public List<string> CurrencyNames { get; private set; } = new List<string>
-        {
-            "USD / United States Dollar",
-            "GBP / United Kingdom Pound",
-            "EUR / European Union Euro",
-            "CHF / Swiss Francs",
-            "RUB / Russian Rouble",
-            "PLN / Polish Złoty",
-            "BRL / Brazilian Reals",
-            "JPY / Japanese Yen",
-            "NOK / Norwegian Krone",
-            "IDR / Indonesian Rupiah",
-            "MYR / Malaysian Ringgit",
-            "PHP / Philippine Peso",
-            "SGD / Singapore Dollar",
-            "THB / Thai Baht",
-            "VND / Vietnamese Dong",
-            "KRW / South Korean Won",
-            "TRY / Turkish Lira",
-            "UAH / Ukrainian Hryvnia",
-            "MXN / Mexican Peso",
-            "CAD / Canadian Dollar",
-            "AUD / Australian Dollar",
-            "NZD / New Zealand Dollar",
-            "CNY / Chinese Yuan",
-            "INR / Indian Rupee",
-            "CLP / Chilean Peso",
-            "PEN / Peruvian Nuevo Sol",
-            "COP / Colombian Peso",
-            "ZAR / South African Rand",
-            "HKD / Hong Kong Dollar",
-            "TWD / Taiwan Dollar",
-            "SAR / Saudi Riyal",
-            "AED / United Arab Emirates Dirham",
-            "ARS / Argentine Peso",
-            "ILS / Israeli New Shekel",
-            "BYN / Belarusian Ruble",
-            "KZT / Kazakhstani Tenge",
-            "KWD / Kuwaiti Dinar",
-            "QAR / Qatari Riyal",
-            "CRC / Costa Rican Colón",
-            "UYU / Uruguayan Peso",
-            "RMB / Chinese Yuan",
-            "NXP / NXP",
-        };
-
-        public int CurrentLanguage
-        {
-            get
-            {
-                return LanguageCodes.IndexOf(AppHelpers.Settings.LanguageCode);
-            }
-            set
-            {
-                AppHelpers.Settings.LanguageCode = LanguageCodes[value];
-                App.SelectCulture(AppHelpers.Settings.LanguageCode);
-                OnPropertyChanged();
-            }
-        }
-
-        public static List<string> Languages { get; private set; }
-        public readonly static List<string> LanguageCodes = new()
-        {
-            "cs",
-            "da",
-            "de",
-            "en",
-            "es",
-            "fi",
-            "fr",
-            "ga",
-            "he",
-            "hr",
-            "it",
-            "ja",
-            "ka",
-            "lt",
-            "lv",
-            "pl",
-            "pt",
-            "uk",
-            "tr",
-            "ru",
-            "zh",
-        };
-
-        public List<string> FloatAPIList { get; private set; } = new()
-        {
-            "CSGOFloat (api.csgofloat.com)",
-            "SIH (floats.steaminventoryhelper.com)"
-        };
-
-        public List<string> ExtensionNames { get; private set; } = new()
-        {
-            "CSGOFloat Market Checker",
-            "Steam Inventory Helper"
-        };
-
-        public int SelectedFloatAPI
-        {
-            get { return (int)AppHelpers.Settings.FloatAPI; }
-            set
-            {
-                AppHelpers.Settings.FloatAPI = (FloatAPI)value;
-                OnPropertyChanged();
-            }
-        }
-
-        public int SelectedExtension
-        {
-            get { return (int)AppHelpers.Settings.ExtensionType; }
-            set
-            {
-                AppHelpers.Settings.ExtensionType = (ExtensionType)value;
-                OnPropertyChanged();
-            }
-        }
-        
-        private bool isShowingAdvanced = false;
-        private RelayCommand showAdvanced;
-        public RelayCommand ShowAdvancedCommand
-        {
-            get
-            {
-                return showAdvanced ??= new RelayCommand(obj =>
-                {
-                    isShowingAdvanced = !isShowingAdvanced;
-
-                    if (isShowingAdvanced)
-                        window.Height = 370;
-                    else
-                        window.Height = 310;
-
-                });
-            }
-        }
-        
-        private readonly SettingsWindow window;
-
-        public SettingsViewModel(SettingsWindow window)
-        {
-            Languages = new List<string>();
-            this.window = window;
-
-            foreach (var lang in LanguageCodes)
-            {
-                var locale = new System.Globalization.CultureInfo(lang);
-                Languages.Add($"{locale.NativeName.FirstCharToUpper()} ({locale.EnglishName})");
-            }
-        }
-
-        public event PropertyChangedEventHandler PropertyChanged;
-        private void OnPropertyChanged([CallerMemberName] string name = null)
-        {
-            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
-        }
-    }
+	public sealed class SettingsViewModel : INotifyPropertyChanged
+	{
+		public static List<string> ThemesList
+		{
+			get
+			{
+				var tmpList = new List<string>();
+				foreach (var theme in AppHelpers.ThemesFound)
+					tmpList.Add(Path.GetFileNameWithoutExtension(theme));
+				return tmpList;
+			}
+		}
+
+		public int ThemeIndex
+		{
+			get { return AppHelpers.ThemesFound.IndexOf(AppHelpers.Settings.ThemeURI); }
+			set
+			{
+				AppHelpers.Settings.ThemeURI = AppHelpers.ThemesFound[value];
+				App.SelectTheme(AppHelpers.Settings.ThemeURI);
+				OnPropertyChanged();
+			}
+		}
+
+		public bool EnableSound
+		{
+			get { return AppHelpers.Settings.Sound; }
+			set { AppHelpers.Settings.Sound = value; OnPropertyChanged(); }
+		}
+
+		public bool CheckUpdates
+		{
+			get { return AppHelpers.Settings.CheckForUpdates; }
+			set { AppHelpers.Settings.CheckForUpdates = value; OnPropertyChanged(); }
+		}
+
+		public bool UseParallel
+		{
+			get { return AppHelpers.Settings.UseParallel; }
+			set { AppHelpers.Settings.UseParallel = value; OnPropertyChanged(); }
+		}
+
+		public bool DiscordRPC
+		{
+			get { return AppHelpers.Settings.DiscordRPC; }
+			set
+			{
+				AppHelpers.Settings.DiscordRPC = value;
+				OnPropertyChanged();
+
+				// Re-enabling does not work. Probably bug in the library
+				if (!value)
+					AppHelpers.DiscordClient.Deinitialize();
+				else
+					AppHelpers.DiscordClient.Initialize();
+			}
+		}
+
+		public int CurrentCurrencyIndex
+		{
+			get
+			{
+				return CurrencyHelper.GetIndexFromCurrency(AppHelpers.Settings.Currency);
+			}
+			set
+			{
+				AppHelpers.Settings.Currency = CurrencyHelper.GetCurrencyByIndex(value);
+				OnPropertyChanged();
+			}
+		}
+
+		public List<string> CurrencyNames { get; private set; } = new List<string>
+		{
+			"USD / United States Dollar",
+			"GBP / United Kingdom Pound",
+			"EUR / European Union Euro",
+			"CHF / Swiss Francs",
+			"RUB / Russian Rouble",
+			"PLN / Polish Złoty",
+			"BRL / Brazilian Reals",
+			"JPY / Japanese Yen",
+			"NOK / Norwegian Krone",
+			"IDR / Indonesian Rupiah",
+			"MYR / Malaysian Ringgit",
+			"PHP / Philippine Peso",
+			"SGD / Singapore Dollar",
+			"THB / Thai Baht",
+			"VND / Vietnamese Dong",
+			"KRW / South Korean Won",
+			"TRY / Turkish Lira",
+			"UAH / Ukrainian Hryvnia",
+			"MXN / Mexican Peso",
+			"CAD / Canadian Dollar",
+			"AUD / Australian Dollar",
+			"NZD / New Zealand Dollar",
+			"CNY / Chinese Yuan",
+			"INR / Indian Rupee",
+			"CLP / Chilean Peso",
+			"PEN / Peruvian Nuevo Sol",
+			"COP / Colombian Peso",
+			"ZAR / South African Rand",
+			"HKD / Hong Kong Dollar",
+			"TWD / Taiwan Dollar",
+			"SAR / Saudi Riyal",
+			"AED / United Arab Emirates Dirham",
+			"ARS / Argentine Peso",
+			"ILS / Israeli New Shekel",
+			"BYN / Belarusian Ruble",
+			"KZT / Kazakhstani Tenge",
+			"KWD / Kuwaiti Dinar",
+			"QAR / Qatari Riyal",
+			"CRC / Costa Rican Colón",
+			"UYU / Uruguayan Peso",
+			"RMB / Chinese Yuan",
+			"NXP / NXP",
+		};
+
+		public int CurrentLanguage
+		{
+			get
+			{
+				return LanguageCodes.IndexOf(AppHelpers.Settings.LanguageCode);
+			}
+			set
+			{
+				AppHelpers.Settings.LanguageCode = LanguageCodes[value];
+				App.SelectCulture(AppHelpers.Settings.LanguageCode);
+				OnPropertyChanged();
+			}
+		}
+
+		public static List<string> Languages { get; private set; }
+		public readonly static List<string> LanguageCodes = new()
+		{
+			"cs",
+			"da",
+			"de",
+			"en",
+			"es",
+			"fi",
+			"fr",
+			"ga",
+			"he",
+			"hr",
+			"it",
+			"ja",
+			"ka",
+			"lt",
+			"lv",
+			"pl",
+			"pt",
+			"uk",
+			"tr",
+			"ru",
+			"zh",
+		};
+
+		public List<string> FloatAPIList { get; private set; } = new()
+		{
+			"CSGOFloat (api.csgofloat.com)",
+			"SIH (floats.steaminventoryhelper.com)"
+		};
+
+		public List<string> ExtensionNames { get; private set; } = new()
+		{
+			"CSGOFloat Market Checker",
+			"Steam Inventory Helper"
+		};
+
+		public int SelectedFloatAPI
+		{
+			get { return (int)AppHelpers.Settings.FloatAPI; }
+			set
+			{
+				AppHelpers.Settings.FloatAPI = (FloatAPI)value;
+				OnPropertyChanged();
+			}
+		}
+
+		public int SelectedExtension
+		{
+			get { return (int)AppHelpers.Settings.ExtensionType; }
+			set
+			{
+				AppHelpers.Settings.ExtensionType = (ExtensionType)value;
+				OnPropertyChanged();
+			}
+		}
+
+		private bool isShowingAdvanced = false;
+		private RelayCommand showAdvanced;
+		public RelayCommand ShowAdvancedCommand
+		{
+			get
+			{
+				return showAdvanced ??= new RelayCommand(obj =>
+				{
+					isShowingAdvanced = !isShowingAdvanced;
+
+					if (isShowingAdvanced)
+						window.Height = 400;
+					else
+						window.Height = 310;
+
+				});
+			}
+		}
+
+		private readonly SettingsWindow window;
+
+		public SettingsViewModel(SettingsWindow window)
+		{
+			Languages = new List<string>();
+			this.window = window;
+
+			foreach (var lang in LanguageCodes)
+			{
+				var locale = new System.Globalization.CultureInfo(lang);
+				Languages.Add($"{locale.NativeName.FirstCharToUpper()} ({locale.EnglishName})");
+			}
+		}
+
+		public event PropertyChangedEventHandler PropertyChanged;
+		private void OnPropertyChanged([CallerMemberName] string name = null)
+		{
+			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+		}
+	}
 }
diff --git a/FloatTool/Views/BenchmarkWindow.xaml.cs b/FloatTool/Views/BenchmarkWindow.xaml.cs
index 7afdbaa..b5beab3 100644
--- a/FloatTool/Views/BenchmarkWindow.xaml.cs
+++ b/FloatTool/Views/BenchmarkWindow.xaml.cs
@@ -15,6 +15,8 @@
 - along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
 
+using FloatTool.Common;
+using FloatTool.ViewModels;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -27,7 +29,7 @@
 
 namespace FloatTool
 {
-    public sealed partial class BenchmarkWindow : Window
+	public sealed partial class BenchmarkWindow : Window
     {
         public BenchmarkViewModel Context;
         private static long PassedCombinations;
@@ -83,8 +85,7 @@ private static void FloatCraftWorkerThread(CraftSearchSetup options)
             InputSkin[] resultList = new InputSkin[10];
             bool running = true;
 
-            for (int i = 0; i < options.ThreadID; i++)
-                running = Calculations.NextCombination(numbers, size);
+            running = Calculations.NextCombination(numbers, size, options.ThreadID);
 
             while (running)
             {
@@ -129,8 +130,7 @@ private static void FloatCraftWorkerThread(CraftSearchSetup options)
                 Interlocked.Increment(ref PassedCombinations);
 
                 // Get next combination
-                for (int i = 0; i < options.ThreadCount; i++)
-                    running = Calculations.NextCombination(numbers, size);
+                running = Calculations.NextCombination(numbers, size, options.ThreadCount);
             }
 
         }
@@ -142,7 +142,7 @@ private void StartBenchmark_Click(object sender, RoutedEventArgs e)
             new Thread(() =>
             {
                 Skin[] outcomes = new Skin[] {
-                    new Skin("AK-47 | Safari Mesh", 0.06, 0.8, Skin.Quality.Industrial)
+                    new Skin("AK-47 | Safari Mesh", 0.06, 0.8, Quality.Industrial)
                 };
 
                 double[] pool = {
@@ -155,8 +155,9 @@ private void StartBenchmark_Click(object sender, RoutedEventArgs e)
                     0.157685652375221, 0.217334255576134, 0.217334255576134,
                     0.157685652375221, 0.217334255576134, 0.217334255576134,
                     0.157685652375221, 0.217334255576134, 0.217334255576134,
-                    0.157685652375221, 0.217334255576134, 0.217334255576134
-                };
+                    0.157685652375221, 0.217334255576134, 0.217334255576134,
+					0.157685652375221, 0.217334255576134, 0.217334255576134,
+				};
 
                 List<InputSkin> inputSkinsList = new();
                 foreach (double f in pool)
@@ -182,27 +183,51 @@ private void StartBenchmark_Click(object sender, RoutedEventArgs e)
                 int threads = Context.ThreadCount;
 
                 long startTime = Stopwatch.GetTimestamp();
+				ParallelLoopResult? parallel = null;
 
-                try
-                {
-                    for (int i = 0; i < threads; i++)
+				try
+				{
+                    if (AppHelpers.Settings.UseParallel)
                     {
-                        int startIndex = i;
-                        Task newThread = Task.Factory.StartNew(() => FloatCraftWorkerThread(
-                            new CraftSearchSetup
+                        Task.Run(() =>
+                        {
+                            parallel = Parallel.For(0, threads, i =>
                             {
-                                SkinPool = inputSkins,
-                                Outcomes = outcomes,
-                                SearchTarget = searched,
-                                TargetPrecision = precission,
-                                SearchFilter = searchFilter,
-                                SearchMode = SearchMode.Equal,
-                                ThreadID = startIndex,
-                                ThreadCount = threads,
-                            }
-                        ));
-                        threadPool.Add(newThread);
+                                FloatCraftWorkerThread(new CraftSearchSetup
+                                {
+                                    SkinPool = inputSkins,
+                                    Outcomes = outcomes,
+                                    SearchTarget = searched,
+                                    TargetPrecision = precission,
+                                    SearchFilter = searchFilter,
+                                    SearchMode = SearchMode.Equal,
+                                    ThreadID = i,
+                                    ThreadCount = threads,
+                                });
+                            });
+                        });
                     }
+                    else
+                    {
+						for (int i = 0; i < threads; i++)
+						{
+							int startIndex = i;
+							Task newThread = Task.Factory.StartNew(() => FloatCraftWorkerThread(
+								new CraftSearchSetup
+								{
+									SkinPool = inputSkins,
+									Outcomes = outcomes,
+									SearchTarget = searched,
+									TargetPrecision = precission,
+									SearchFilter = searchFilter,
+									SearchMode = SearchMode.Equal,
+									ThreadID = startIndex,
+									ThreadCount = threads,
+								}
+							));
+							threadPool.Add(newThread);
+						}
+					}
                 }
                 catch (Exception ex)
                 {
@@ -211,15 +236,27 @@ private void StartBenchmark_Click(object sender, RoutedEventArgs e)
 
                 while (true)
                 {
-                    bool isAnyRunning = false;
-                    foreach (Task t in CollectionsMarshal.AsSpan(threadPool))
+                    bool isAnyRunning;
+					if (AppHelpers.Settings.UseParallel)
                     {
-                        if (t.Status != TaskStatus.RanToCompletion)
-                        {
-                            isAnyRunning = true;
-                            break;
-                        }
-                    }
+						isAnyRunning = true;
+						if (parallel.HasValue)
+						{
+							isAnyRunning = !parallel.Value.IsCompleted;
+						}
+					}
+                    else
+                    {
+						isAnyRunning = false;
+						foreach (Task t in CollectionsMarshal.AsSpan(threadPool))
+						{
+							if (t.Status != TaskStatus.RanToCompletion)
+							{
+								isAnyRunning = true;
+								break;
+							}
+						}
+					}
 
                     Context.ProgressPercentage = (int)(PassedCombinations * 100 / Context.TotalCombinations);
 
diff --git a/FloatTool/Views/MainWindow.xaml b/FloatTool/Views/MainWindow.xaml
index d76e80f..8abaef3 100644
--- a/FloatTool/Views/MainWindow.xaml
+++ b/FloatTool/Views/MainWindow.xaml
@@ -21,6 +21,7 @@
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:local="clr-namespace:FloatTool"
+    xmlns:models="clr-namespace:FloatTool.ViewModels"
     xmlns:theme="clr-namespace:FloatTool.Theme" 
     mc:Ignorable="d" KeyUp="Window_KeyUp"
     MinWidth="875" MinHeight="420" 
@@ -168,9 +169,9 @@
                             <ColumnDefinition/>
                             <ColumnDefinition/>
                         </Grid.ColumnDefinitions>
-                        <RadioButton IsEnabled="{Binding CanEditSettings}" Content="&lt;" Height="30" Margin="1,0" Style="{DynamicResource MainToggleButtonStyle}" FontSize="18" VerticalContentAlignment="Top" VerticalAlignment="Top" IsChecked="{Binding SearchModeSelected, Converter={StaticResource enumToBoolConverter}, ConverterParameter={x:Static local:SearchMode.Less}}" />
-                        <RadioButton IsEnabled="{Binding CanEditSettings}" Content="=" Height="30" Margin="1,0" Style="{DynamicResource MainToggleButtonStyle}" FontSize="18" BorderThickness="1" VerticalContentAlignment="Top" VerticalAlignment="Top" IsChecked="{Binding SearchModeSelected, Converter={StaticResource enumToBoolConverter}, ConverterParameter={x:Static local:SearchMode.Equal}}" Grid.Column="1"/>
-                        <RadioButton IsEnabled="{Binding CanEditSettings}" Content="&gt;" Height="30" Margin="1,0" Style="{DynamicResource MainToggleButtonStyle}" FontSize="18" VerticalContentAlignment="Top" VerticalAlignment="Top" IsChecked="{Binding SearchModeSelected, Converter={StaticResource enumToBoolConverter}, ConverterParameter={x:Static local:SearchMode.Greater}}" Grid.Column="2"/>
+                        <RadioButton IsEnabled="{Binding CanEditSettings}" Content="&lt;" Height="30" Margin="1,0" Style="{DynamicResource MainToggleButtonStyle}" FontSize="18" VerticalContentAlignment="Top" VerticalAlignment="Top" IsChecked="{Binding SearchModeSelected, Converter={StaticResource enumToBoolConverter}, ConverterParameter={x:Static models:SearchMode.Less}}" />
+                        <RadioButton IsEnabled="{Binding CanEditSettings}" Content="=" Height="30" Margin="1,0" Style="{DynamicResource MainToggleButtonStyle}" FontSize="18" BorderThickness="1" VerticalContentAlignment="Top" VerticalAlignment="Top" IsChecked="{Binding SearchModeSelected, Converter={StaticResource enumToBoolConverter}, ConverterParameter={x:Static models:SearchMode.Equal}}" Grid.Column="1"/>
+                        <RadioButton IsEnabled="{Binding CanEditSettings}" Content="&gt;" Height="30" Margin="1,0" Style="{DynamicResource MainToggleButtonStyle}" FontSize="18" VerticalContentAlignment="Top" VerticalAlignment="Top" IsChecked="{Binding SearchModeSelected, Converter={StaticResource enumToBoolConverter}, ConverterParameter={x:Static models:SearchMode.Greater}}" Grid.Column="2"/>
                     </Grid>
 
 
diff --git a/FloatTool/Views/MainWindow.xaml.cs b/FloatTool/Views/MainWindow.xaml.cs
index b978038..e5aefc1 100644
--- a/FloatTool/Views/MainWindow.xaml.cs
+++ b/FloatTool/Views/MainWindow.xaml.cs
@@ -15,6 +15,8 @@
 - along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
 
+using FloatTool.Common;
+using FloatTool.ViewModels;
 using Newtonsoft.Json;
 using System;
 using System.Collections.Concurrent;
@@ -37,490 +39,523 @@
 
 namespace FloatTool
 {
-    public sealed partial class MainWindow : Window
-    {
-        public MainViewModel ViewModel;
-        private readonly RPCSettingsPersist RPCSettings = new();
-
-        private static long PassedCombinations;
-        private static List<Task> ThreadPool;
-        private static CancellationTokenSource TokenSource = new();
-        public CancellationToken CancellationToken;
-        private static SoundPlayer CombinationFoundSound;
-
-        public void UpdateRichPresence(bool clear = false)
-        {
-            if (clear)
-            {
-                RPCSettings.Details = "%m_SettingUpSearch%";
-                RPCSettings.State = "";
-                RPCSettings.ShowTime = false;
-            }
-
-            if (AppHelpers.Settings.DiscordRPC)
-            {
-                AppHelpers.DiscordClient.SetPresence(RPCSettings.GetPresense());
-            }
-        }
-
-        public MainWindow()
-        {
-            InitializeComponent();
-            using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FloatTool.Assets.Found.wav"))
-            {
-                CombinationFoundSound = new SoundPlayer(stream);
-                CombinationFoundSound.Load();
-            }
-
-            App.SelectCulture(AppHelpers.Settings.LanguageCode);
-            App.SelectTheme(AppHelpers.Settings.ThemeURI);
-
-            ViewModel = new MainViewModel("Nova", "Predator", "Field-Tested", "0.250000000", 100, 20, ErrorTooltip, ErrorTooltipFloat);
-
-            MaxHeight = SystemParameters.WorkArea.Height + 12;
-            MaxWidth = SystemParameters.WorkArea.Width + 12;
-
-            UpdateRichPresence(true);
-            DataContext = ViewModel;
-
-            Logger.Log.Info("Main window started");
-
-            if (AppHelpers.Settings.CheckForUpdates)
-            {
-                Task.Factory.StartNew(() =>
-                {
-                    var update = Utils.CheckForUpdates().Result;
-                    if (update != null && update.TagName != AppHelpers.VersionCode)
-                    {
-                        Dispatcher.Invoke(new Action(() =>
-                        {
-                            Logger.Log.Info("New version available");
-                            var updateWindow = new UpdateWindow(update)
-                            {
-                                Owner = this
-                            };
-                            updateWindow.ShowDialog();
-                        }));
-                    }
-                });
-            }
-        }
-
-        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
-        {
-            base.OnMouseLeftButtonDown(e);
-            if (e.GetPosition(this).Y < 40) DragMove();
-        }
-
-        private async void Window_KeyUp(object sender, KeyEventArgs e)
-        {
-            switch (e.Key)
-            {
-                case Key.F1:
-                    Process.Start(new ProcessStartInfo { FileName = "https://prevter.ml/floattool/table", UseShellExecute = true });
-                    break;
-                case Key.F2:
-                    Process.Start(new ProcessStartInfo { FileName = "https://prevter.ml/floattool/tools", UseShellExecute = true });
-                    break;
-                case Key.F3:
-                    string skin = $"{ViewModel.WeaponName} | {ViewModel.SkinName}";
-                    var collection = ViewModel.FindSkinCollection(skin);
-                    Process.Start(new ProcessStartInfo { FileName = collection.Link, UseShellExecute = true });
-                    break;
-                case Key.F4:
-                    var link = $"https://steamcommunity.com/market/listings/730/{ViewModel.FullSkinName}";
-                    Process.Start(new ProcessStartInfo { FileName = link, UseShellExecute = true });
-                    break;
-                case Key.F5:
-                    ViewModel.FoundCombinations.Sort((a, b) => a.Price.CompareTo(b.Price));
-                    break;
-                case Key.F12:
-                    break;
-            }
-        }
-
-        private void WindowButton_Click(object sender, RoutedEventArgs e)
-        {
-            switch (((Button)sender).Name)
-            {
-                case "CloseButton":
-                    Logger.Log.Info("Closing");
-                    Environment.Exit(Environment.ExitCode);
-                    break;
-                case "MaximizeButton":
-                    WindowState = (WindowState == WindowState.Maximized) ? WindowState.Normal : WindowState.Maximized;
-                    break;
-                case "MinimizeButton":
-                    WindowState = WindowState.Minimized;
-                    break;
-                case "DiscordButton":
-                    Process.Start(new ProcessStartInfo { FileName = "https://discord.gg/RM9VrzMfhP", UseShellExecute = true });
-                    break;
-                case "BenchmarkButton":
-                    new BenchmarkWindow().ShowDialog();
-                    break;
-                case "SettingsButton":
-                    new SettingsWindow().ShowDialog();
-                    break;
-            }
-
-            // This will return rich presense to last state and update the language
-            UpdateRichPresence();
-        }
-
-        private void SetStatus(string stringCode)
-        {
-            if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
-                StatusBar.SetResourceReference(TextBlock.TextProperty, stringCode);
-            else
-                Dispatcher.Invoke(new Action(() => { SetStatus(stringCode); }));
-        }
-
-        private void FloatCraftWorkerThread(CraftSearchSetup options)
-        {
-            int size = options.SkinPool.Length - 10;
-            int[] numbers = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
-            InputSkin[] resultList = new InputSkin[10];
-            bool running = true;
-
-            for (int i = 0; i < options.ThreadID; i++)
-                running = Calculations.NextCombination(numbers, size);
-
-            while (running)
-            {
-                for (int i = 0; i < 10; ++i)
-                    resultList[i] = options.SkinPool[numbers[i]];
-
-                // Check if the combination is valid
-
-                for (int i = 0; i < options.Outcomes.Length; ++i)
-                {
-                    double resultFloat = Calculations.Craft(
-                        resultList, options.Outcomes[i].MinFloat, options.Outcomes[i].FloatRange
-                    );
-
-                    bool gotResult = false;
-
-                    switch (options.SearchMode)
-                    {
-                        case SearchMode.Equal:
-                            gotResult = Math.Abs(resultFloat - options.SearchTarget) < options.TargetPrecision;
-                            break;
-                        case SearchMode.Less:
-                            gotResult = resultFloat < options.SearchTarget;
-                            break;
-                        case SearchMode.Greater:
-                            gotResult = resultFloat > options.SearchTarget;
-                            break;
-                    }
-
-                    if (gotResult)
-                    {
-                        if (Math.Round(resultFloat, 14, MidpointRounding.AwayFromZero)
-                            .ToString(CultureInfo.InvariantCulture)
-                            .StartsWith(options.SearchFilter, StringComparison.Ordinal)
-                            || options.SearchMode != SearchMode.Equal)
-                        {
-                            InputSkin[] result = (InputSkin[])resultList.Clone();
-                            float price = 0;
-                            float ieeesum = 0;
-                            foreach (var skin in result)
-                            {
-                                price += skin.Price;
-                                ieeesum += (float)skin.WearValue;
-                            }
-                            ieeesum /= 10;
-                            float ieee = ((float)options.Outcomes[i].MaxFloat - (float)options.Outcomes[i].MinFloat) * ieeesum + (float)options.Outcomes[i].MinFloat;
-
-                            Dispatcher.Invoke(new Action(() =>
-                            {
-                                ViewModel.FoundCombinations.Add(new Combination
-                                {
-                                    Wear = resultFloat,
-                                    OutcomeName = options.Outcomes[i].Name,
-                                    Inputs = result,
-                                    Currency = result[0].SkinCurrency,
-                                    Price = price,
-                                    Wear32Bit = ((double)ieee).ToString("0.000000000000000", CultureInfo.InvariantCulture),
-                                });
-                                if (AppHelpers.Settings.Sound)
-                                    CombinationFoundSound.Play();
-                            }));
-                        }
-                    }
-                }
-
-                Interlocked.Increment(ref PassedCombinations);
-
-                if (CancellationToken.IsCancellationRequested)
-                    break;
-
-                // Get next combination
-
-                for (int i = 0; i < options.ThreadCount; i++)
-                    running = Calculations.NextCombination(numbers, size);
-            }
-        }
-
-        private void StartSearchButton_Click(object sender, RoutedEventArgs e)
-        {
-            bool stopAfterHit = (sender as Button).Name == "FindOneButton";
-
-            if (ViewModel.CanEditSettings)
-            {
-                ViewModel.FoundCombinations.Clear();
-                StartButton.SetResourceReference(ContentProperty, "m_Stop");
-
-                TokenSource.Dispose();
-                TokenSource = new CancellationTokenSource();
-                CancellationToken = TokenSource.Token;
-
-                if (AppHelpers.Settings.DiscordRPC)
-                {
-                    RPCSettings.Details = $"%m_Searching% {ViewModel.FullSkinName}";
-                    RPCSettings.State = $"%m_DesiredFloat% {ViewModel.SearchFilter}";
-                    RPCSettings.Timestamp = DiscordRPC.Timestamps.Now;
-                    RPCSettings.ShowTime = true;
-
-                    UpdateRichPresence();
-                }
-
-                new Thread(() =>
-                {
-                    Logger.Log.Info("Starting up search");
-                    SetStatus("m_SearchingSkin");
-                    PassedCombinations = 0;
-                    ViewModel.CanEditSettings = false;
-                    ViewModel.ProgressPercentage = 0;
-                    ViewModel.TotalCombinations = Calculations.GetCombinationsCount(ViewModel.SkinCount);
-
-                    int index = 0;
-                    Skin[] outcomes = Array.Empty<Skin>();
-                    bool found = false;
-                    foreach (var outcome in ViewModel.Outcomes.Values)
-                    {
-                        if (index++ == ViewModel.OutcomeIndex)
-                        {
-                            outcomes = new Skin[] { outcome[0] };
-                            found = true;
-                            break;
-                        }
-                    }
-                    if (!found)
-                    {
-                        List<Skin> everything = new();
-                        foreach (var outcome in ViewModel.Outcomes.Values)
-                            everything.Add(outcome[0]);
-                        outcomes = everything.ToArray();
-                    }
-
-                    ConcurrentBag<InputSkin> inputSkinBag = new();
-
-                    string url = $"https://steamcommunity.com/market/listings/730/{ViewModel.FullSkinName}/render/?count={ViewModel.SkinCount}&start={ViewModel.SkinSkipCount}&currency={(int)AppHelpers.Settings.Currency}";
-                    try
-                    {
-                        using var client = new HttpClient();
-                        HttpResponseMessage response = client.GetAsync(url).Result;
-                        response.EnsureSuccessStatusCode();
-                        string responseBody = response.Content.ReadAsStringAsync().Result;
-                        dynamic r = JsonConvert.DeserializeObject(responseBody);
-
-                        if (r["success"] == false)
-                        {
-                            Logger.Log.Error($"Steam haven't returned a success code\n{r.ToString()}");
-                            throw new ValueUnavailableException("Steam server returned error.");
-                        }
-
-                        SetStatus("m_GettingFloats");
-
-                        Dictionary<Task<double>, (string, float)> floatTasks = new();
-                        foreach (var skin in r["listinginfo"])
-                        {
-                            string lid = r["listinginfo"][skin.Name]["listingid"].ToString();
-                            string aid = r["listinginfo"][skin.Name]["asset"]["id"].ToString();
-                            string link = r["listinginfo"][skin.Name]["asset"]["market_actions"][0]["link"].ToString();
-                            link = link.Replace("%assetid%", aid).Replace("%listingid%", lid);
-                            try
-                            {
-                                floatTasks.Add(
-                                    Utils.GetWearFromInspectURL(link),
-                                    (lid, (float.Parse(r["listinginfo"][skin.Name]["converted_price"].ToString()) +
-                                    float.Parse(r["listinginfo"][skin.Name]["converted_fee"].ToString())) / 100)
-                                );
-                            }
-                            catch (Exception ex)
-                            {
-                                Logger.Log.Error($"Error getting float from link {link}", ex);
-                            }
-                        }
-
-                        foreach (var task in floatTasks.Keys)
-                        {
-                            try
-                            {
-                                inputSkinBag.Add(new InputSkin(
-                                    task.Result,
-                                    floatTasks[task].Item2,
-                                    AppHelpers.Settings.Currency,
-                                    floatTasks[task].Item1
-                                ));
-
-                                ViewModel.ProgressPercentage = (float)inputSkinBag.Count * 100 / floatTasks.Count;
-                            }
-                            catch (Exception ex)
-                            {
-                                Logger.Log.Error($"Error getting float from task {task.Id}", ex);
-                            }
-                        }
-                    }
-                    catch (ValueUnavailableException)
-                    {
-                        SetStatus("m_ErrorCouldntGetFloats");
-                        Dispatcher.Invoke(
-                            new Action(() =>
-                            {
-                                StartButton.SetResourceReference(ContentProperty, "m_Start");
-                                ViewModel.CanEditSettings = true;
-                            })
-                        );
-                        return;
-                    }
-                    catch (Exception ex)
-                    {
-                        Logger.Log.Error("Error getting floats from marketplace", ex);
-                        SetStatus("m_ErrorCouldntGetFloats");
-                    }
-
-                    if (inputSkinBag.Count < 10)
-                    {
-                        Logger.Log.Error("Couldn't get more than 10 floats from marketplace");
-                        SetStatus("m_ErrorLessThan10");
-                        Dispatcher.Invoke(
-                            new Action(() =>
-                            {
-                                StartButton.SetResourceReference(ContentProperty, "m_Start");
-                                ViewModel.CanEditSettings = true;
-                            })
-                        );
-                        return;
-                    }
-
-                    List<InputSkin> inputSkinList = inputSkinBag.ToList();
-
-                    // sort skins by price in ascending order
-                    inputSkinList.Sort((a, b) => a.Price.CompareTo(b.Price));
-
-                    if (ViewModel.Sort)
-                    {
-                        if (ViewModel.SortDescending)
-                            inputSkinList.Sort((a, b) => b.CompareTo(a));
-                        else
-                            inputSkinList.Sort((a, b) => a.CompareTo(b));
-                    }
-                    InputSkin[] inputSkins = inputSkinList.ToArray();
-                    ViewModel.TotalCombinations = Calculations.GetCombinationsCount(inputSkinBag.Count);
-
-                    SetStatus("m_StartingUpSearch");
-
-                    string searchFilter = ViewModel.SearchFilter;
-
-                    double searched = double.Parse(searchFilter, CultureInfo.InvariantCulture);
-                    double precission = Math.Pow(0.1, searchFilter.Length - 2);
-
-                    // Create thread pool
-                    ThreadPool = new();
-                    int threads = ViewModel.ThreadCount;
-
-                    try
-                    {
-                        for (int i = 0; i < threads; i++)
-                        {
-                            var startIndex = i;
-                            var newThread = Task.Factory.StartNew(() => FloatCraftWorkerThread(
-                                new CraftSearchSetup
-                                {
-                                    SkinPool = inputSkins,
-                                    Outcomes = outcomes,
-                                    SearchTarget = searched,
-                                    TargetPrecision = precission,
-                                    SearchFilter = searchFilter,
-                                    SearchMode = ViewModel.SearchModeSelected,
-                                    ThreadID = startIndex,
-                                    ThreadCount = threads,
-                                }
-                            ));
-                            ThreadPool.Add(newThread);
-                        }
-                    }
-                    catch (Exception ex)
-                    {
-                        Logger.Log.Error("Error starting up thread pool", ex);
-                    }
-
-                    SetStatus("m_Searching");
-                    var startTime = Stopwatch.GetTimestamp();
-                    long LastCombinationCount = 0;
-
-                    while (true)
-                    {
-                        long newTime = Stopwatch.GetTimestamp();
-                        double timeSinceLast = Utils.GetTimePassed(startTime, newTime).TotalMilliseconds;
-                        startTime = newTime;
-
-                        bool isAnyRunning = false;
-                        foreach (Task t in CollectionsMarshal.AsSpan(ThreadPool))
-                        {
-                            if (t.Status != TaskStatus.RanToCompletion)
-                            {
-                                isAnyRunning = true;
-                                break;
-                            }
-                        }
-
-                        if (timeSinceLast > 0)
-                            ViewModel.CurrentSpeedLabel =
-                            (
-                                (PassedCombinations - LastCombinationCount)
-                                * 1000
-                                / timeSinceLast
-                            ).ToString("n0");
-
-                        LastCombinationCount = PassedCombinations;
-                        ViewModel.ProgressPercentage = (float)PassedCombinations * 100 / ViewModel.TotalCombinations;
-                        ViewModel.ParsedCombinations = PassedCombinations;
-                        ViewModel.CombinationsLabel = string.Empty;
-
-                        if (!isAnyRunning)
-                            break;
-
-                        if (stopAfterHit && ViewModel.FoundCombinations.Count >= 1)
-                        {
-                            TokenSource.Cancel();
-                            break;
-                        }
-
-                        Thread.Sleep(100);
-                    }
-
-                    UpdateRichPresence(true);
-                    Logger.Log.Info("Finished searching");
-                    Dispatcher.Invoke(
-                        new Action(() =>
-                        {
-                            StartButton.SetResourceReference(ContentProperty, "m_Start");
-                            ViewModel.CanEditSettings = true;
-                            SetStatus("m_FinishedSearching");
-                        })
-                    );
-
-                }).Start();
-            }
-            else
-            {
-                Logger.Log.Info("Canceling task");
-                TokenSource.Cancel();
-                UpdateRichPresence(true);
-            }
-        }
-    }
+	public sealed partial class MainWindow : Window
+	{
+		public MainViewModel ViewModel;
+		private readonly RPCSettingsPersist RPCSettings = new();
+
+		private static long PassedCombinations;
+		private static List<Task> ThreadPool;
+		private static CancellationTokenSource TokenSource = new();
+		public CancellationToken CancellationToken;
+		private static SoundPlayer CombinationFoundSound;
+
+		public void UpdateRichPresence(bool clear = false)
+		{
+			if (clear)
+			{
+				RPCSettings.Details = "%m_SettingUpSearch%";
+				RPCSettings.State = "";
+				RPCSettings.ShowTime = false;
+			}
+
+			if (AppHelpers.Settings.DiscordRPC)
+			{
+				AppHelpers.DiscordClient.SetPresence(RPCSettings.GetPresense());
+			}
+		}
+
+		public MainWindow()
+		{
+			InitializeComponent();
+			using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FloatTool.Assets.Found.wav"))
+			{
+				CombinationFoundSound = new SoundPlayer(stream);
+				CombinationFoundSound.Load();
+			}
+
+			App.SelectCulture(AppHelpers.Settings.LanguageCode);
+			App.SelectTheme(AppHelpers.Settings.ThemeURI);
+
+			ViewModel = new MainViewModel("Nova", "Predator", "Field-Tested", "0.250000000", 100, 20, ErrorTooltip, ErrorTooltipFloat);
+
+			MaxHeight = SystemParameters.WorkArea.Height + 12;
+			MaxWidth = SystemParameters.WorkArea.Width + 12;
+
+			UpdateRichPresence(true);
+			DataContext = ViewModel;
+
+			Logger.Info("Main window started");
+
+			if (AppHelpers.Settings.CheckForUpdates)
+			{
+				Task.Factory.StartNew(() =>
+				{
+					var update = Utils.CheckForUpdates().Result;
+					if (update != null && update.TagName != AppHelpers.VersionCode)
+					{
+						Dispatcher.Invoke(new Action(() =>
+						{
+							Logger.Info("New version available");
+							var updateWindow = new UpdateWindow(update)
+							{
+								Owner = this
+							};
+							updateWindow.ShowDialog();
+						}));
+					}
+				});
+			}
+		}
+
+		protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
+		{
+			base.OnMouseLeftButtonDown(e);
+			if (e.GetPosition(this).Y < 40) DragMove();
+		}
+
+		private void Window_KeyUp(object sender, KeyEventArgs e)
+		{
+			switch (e.Key)
+			{
+				case Key.F1:
+					Process.Start(new ProcessStartInfo { FileName = "https://prevter.ml/floattool/table", UseShellExecute = true });
+					break;
+				case Key.F2:
+					Process.Start(new ProcessStartInfo { FileName = "https://prevter.ml/floattool/tools", UseShellExecute = true });
+					break;
+				case Key.F3:
+					string skin = $"{ViewModel.WeaponName} | {ViewModel.SkinName}";
+					var collection = ViewModel.FindSkinCollection(skin);
+					Process.Start(new ProcessStartInfo { FileName = collection.Link, UseShellExecute = true });
+					break;
+				case Key.F4:
+					var link = $"https://steamcommunity.com/market/listings/730/{ViewModel.FullSkinName}";
+					Process.Start(new ProcessStartInfo { FileName = link, UseShellExecute = true });
+					break;
+				case Key.F5:
+					ViewModel.FoundCombinations.Sort((a, b) => a.Price.CompareTo(b.Price));
+					break;
+				case Key.F12:
+					break;
+			}
+		}
+
+		private void WindowButton_Click(object sender, RoutedEventArgs e)
+		{
+			switch (((Button)sender).Name)
+			{
+				case "CloseButton":
+					Logger.Info("Closing");
+					Environment.Exit(Environment.ExitCode);
+					break;
+				case "MaximizeButton":
+					WindowState = (WindowState == WindowState.Maximized) ? WindowState.Normal : WindowState.Maximized;
+					break;
+				case "MinimizeButton":
+					WindowState = WindowState.Minimized;
+					break;
+				case "DiscordButton":
+					Process.Start(new ProcessStartInfo { FileName = "https://discord.gg/RM9VrzMfhP", UseShellExecute = true });
+					break;
+				case "BenchmarkButton":
+					new BenchmarkWindow().ShowDialog();
+					break;
+				case "SettingsButton":
+					new SettingsWindow().ShowDialog();
+					break;
+			}
+
+			// This will return rich presense to last state and update the language
+			UpdateRichPresence();
+		}
+
+		private void SetStatus(string stringCode)
+		{
+			if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
+				StatusBar.SetResourceReference(TextBlock.TextProperty, stringCode);
+			else
+				Dispatcher.Invoke(new Action(() => { SetStatus(stringCode); }));
+		}
+
+		private void FloatCraftWorkerThread(CraftSearchSetup options)
+		{
+			int size = options.SkinPool.Length - 10;
+			int[] numbers = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+			InputSkin[] resultList = new InputSkin[10];
+			bool running = true;
+
+			running = Calculations.NextCombination(numbers, size, options.ThreadID);
+
+			while (running)
+			{
+				for (int i = 0; i < 10; ++i)
+					resultList[i] = options.SkinPool[numbers[i]];
+
+				// Check if the combination is valid
+
+				for (int i = 0; i < options.Outcomes.Length; ++i)
+				{
+					double resultFloat = Calculations.Craft(
+						resultList, options.Outcomes[i].MinFloat, options.Outcomes[i].FloatRange
+					);
+
+					bool gotResult = false;
+
+					switch (options.SearchMode)
+					{
+						case SearchMode.Equal:
+							gotResult = Math.Abs(resultFloat - options.SearchTarget) < options.TargetPrecision;
+							break;
+						case SearchMode.Less:
+							gotResult = resultFloat < options.SearchTarget;
+							break;
+						case SearchMode.Greater:
+							gotResult = resultFloat > options.SearchTarget;
+							break;
+					}
+
+					if (gotResult)
+					{
+						if (Math.Round(resultFloat, 14, MidpointRounding.AwayFromZero)
+							.ToString(CultureInfo.InvariantCulture)
+							.StartsWith(options.SearchFilter, StringComparison.Ordinal)
+							|| options.SearchMode != SearchMode.Equal)
+						{
+							InputSkin[] result = (InputSkin[])resultList.Clone();
+							float price = 0;
+							float ieeesum = 0;
+							foreach (var skin in result)
+							{
+								price += skin.Price;
+								ieeesum += (float)skin.WearValue;
+							}
+							ieeesum /= 10;
+							float ieee = ((float)options.Outcomes[i].MaxFloat - (float)options.Outcomes[i].MinFloat) * ieeesum + (float)options.Outcomes[i].MinFloat;
+
+							Dispatcher.Invoke(new Action(() =>
+							{
+								ViewModel.FoundCombinations.Add(new Combination
+								{
+									Wear = resultFloat,
+									OutcomeName = options.Outcomes[i].Name,
+									Inputs = result,
+									Currency = result[0].SkinCurrency,
+									Price = price,
+									Wear32Bit = ((double)ieee).ToString("0.000000000000000", CultureInfo.InvariantCulture),
+								});
+								if (AppHelpers.Settings.Sound)
+									CombinationFoundSound.Play();
+							}));
+						}
+					}
+				}
+
+				Interlocked.Increment(ref PassedCombinations);
+
+				if (CancellationToken.IsCancellationRequested)
+					break;
+
+				// Get next combination
+
+				running = Calculations.NextCombination(numbers, size, options.ThreadCount);
+			}
+		}
+
+		private void StartSearchButton_Click(object sender, RoutedEventArgs e)
+		{
+			bool stopAfterHit = (sender as Button).Name == "FindOneButton";
+
+			if (ViewModel.CanEditSettings)
+			{
+				ViewModel.FoundCombinations.Clear();
+				StartButton.SetResourceReference(ContentProperty, "m_Stop");
+
+				TokenSource.Dispose();
+				TokenSource = new CancellationTokenSource();
+				CancellationToken = TokenSource.Token;
+
+				if (AppHelpers.Settings.DiscordRPC)
+				{
+					RPCSettings.Details = $"%m_Searching% {ViewModel.FullSkinName}";
+					RPCSettings.State = $"%m_DesiredFloat% {ViewModel.SearchFilter}";
+					RPCSettings.Timestamp = DiscordRPC.Timestamps.Now;
+					RPCSettings.ShowTime = true;
+
+					UpdateRichPresence();
+				}
+
+				new Thread(() =>
+				{
+					Logger.Info("Starting up search");
+					SetStatus("m_SearchingSkin");
+					PassedCombinations = 0;
+					ViewModel.CanEditSettings = false;
+					ViewModel.ProgressPercentage = 0;
+					ViewModel.TotalCombinations = Calculations.GetCombinationsCount(ViewModel.SkinCount);
+
+					int index = 0;
+					Skin[] outcomes = Array.Empty<Skin>();
+					bool found = false;
+					foreach (var outcome in ViewModel.Outcomes.Values)
+					{
+						if (index++ == ViewModel.OutcomeIndex)
+						{
+							outcomes = new Skin[] { outcome[0] };
+							found = true;
+							break;
+						}
+					}
+					if (!found)
+					{
+						List<Skin> everything = new();
+						foreach (var outcome in ViewModel.Outcomes.Values)
+							everything.Add(outcome[0]);
+						outcomes = everything.ToArray();
+					}
+
+					ConcurrentBag<InputSkin> inputSkinBag = new();
+
+					string url = $"https://steamcommunity.com/market/listings/730/{ViewModel.FullSkinName}/render/?count={ViewModel.SkinCount}&start={ViewModel.SkinSkipCount}&currency={(int)AppHelpers.Settings.Currency}";
+					try
+					{
+						using var client = new HttpClient();
+						HttpResponseMessage response = client.GetAsync(url).Result;
+						response.EnsureSuccessStatusCode();
+						string responseBody = response.Content.ReadAsStringAsync().Result;
+						dynamic r = JsonConvert.DeserializeObject(responseBody);
+
+						if (r["success"] == false)
+						{
+							Logger.Error($"Steam haven't returned a success code\n{r.ToString()}");
+							throw new ValueUnavailableException("Steam server returned error.");
+						}
+
+						SetStatus("m_GettingFloats");
+
+						Dictionary<Task<double>, (string, float)> floatTasks = new();
+						foreach (var skin in r["listinginfo"])
+						{
+							string lid = r["listinginfo"][skin.Name]["listingid"].ToString();
+							string aid = r["listinginfo"][skin.Name]["asset"]["id"].ToString();
+							string link = r["listinginfo"][skin.Name]["asset"]["market_actions"][0]["link"].ToString();
+							link = link.Replace("%assetid%", aid).Replace("%listingid%", lid);
+							try
+							{
+								floatTasks.Add(
+									Utils.GetWearFromInspectURL(link),
+									(lid, (float.Parse(r["listinginfo"][skin.Name]["converted_price"].ToString()) +
+									float.Parse(r["listinginfo"][skin.Name]["converted_fee"].ToString())) / 100)
+								);
+							}
+							catch (Exception ex)
+							{
+								Logger.Log.Error($"Error getting float from link {link}", ex);
+							}
+						}
+
+						foreach (var task in floatTasks.Keys)
+						{
+							try
+							{
+								inputSkinBag.Add(new InputSkin(
+									task.Result,
+									floatTasks[task].Item2,
+									AppHelpers.Settings.Currency,
+									floatTasks[task].Item1
+								));
+
+								ViewModel.ProgressPercentage = (float)inputSkinBag.Count * 100 / floatTasks.Count;
+							}
+							catch (Exception ex)
+							{
+								Logger.Log.Error($"Error getting float from task {task.Id}", ex);
+							}
+						}
+					}
+					catch (ValueUnavailableException)
+					{
+						SetStatus("m_ErrorCouldntGetFloats");
+						Dispatcher.Invoke(
+							new Action(() =>
+							{
+								StartButton.SetResourceReference(ContentProperty, "m_Start");
+								ViewModel.CanEditSettings = true;
+							})
+						);
+						return;
+					}
+					catch (Exception ex)
+					{
+						Logger.Log.Error("Error getting floats from marketplace", ex);
+						SetStatus("m_ErrorCouldntGetFloats");
+					}
+
+					if (inputSkinBag.Count < 10)
+					{
+						Logger.Error("Couldn't get more than 10 floats from marketplace");
+						SetStatus("m_ErrorLessThan10");
+						Dispatcher.Invoke(
+							new Action(() =>
+							{
+								StartButton.SetResourceReference(ContentProperty, "m_Start");
+								ViewModel.CanEditSettings = true;
+							})
+						);
+						return;
+					}
+
+					List<InputSkin> inputSkinList = inputSkinBag.ToList();
+
+					// sort skins by price in ascending order
+					inputSkinList.Sort((a, b) => a.Price.CompareTo(b.Price));
+
+					if (ViewModel.Sort)
+					{
+						if (ViewModel.SortDescending)
+							inputSkinList.Sort((a, b) => b.CompareTo(a));
+						else
+							inputSkinList.Sort((a, b) => a.CompareTo(b));
+					}
+					InputSkin[] inputSkins = inputSkinList.ToArray();
+					ViewModel.TotalCombinations = Calculations.GetCombinationsCount(inputSkinBag.Count);
+
+					SetStatus("m_StartingUpSearch");
+
+					string searchFilter = ViewModel.SearchFilter;
+
+					double searched = double.Parse(searchFilter, CultureInfo.InvariantCulture);
+					double precission = Math.Pow(0.1, searchFilter.Length - 2);
+
+					// Create thread pool
+					ThreadPool = new();
+					int threads = ViewModel.ThreadCount;
+
+					ParallelLoopResult? parallel = null;
+
+					try
+					{
+						if (AppHelpers.Settings.UseParallel)
+						{
+							Task.Run(() =>
+							{
+								parallel = Parallel.For(0, threads, i =>
+								{
+									FloatCraftWorkerThread(new CraftSearchSetup
+									{
+										SkinPool = inputSkins,
+										Outcomes = outcomes,
+										SearchTarget = searched,
+										TargetPrecision = precission,
+										SearchFilter = searchFilter,
+										SearchMode = ViewModel.SearchModeSelected,
+										ThreadID = i,
+										ThreadCount = threads,
+									});
+								});
+							});
+						}
+						else
+						{
+							for (int i = 0; i < threads; i++)
+							{
+								var startIndex = i;
+								var newThread = Task.Factory.StartNew(() => FloatCraftWorkerThread(
+									new CraftSearchSetup
+									{
+										SkinPool = inputSkins,
+										Outcomes = outcomes,
+										SearchTarget = searched,
+										TargetPrecision = precission,
+										SearchFilter = searchFilter,
+										SearchMode = ViewModel.SearchModeSelected,
+										ThreadID = startIndex,
+										ThreadCount = threads,
+									}
+								));
+								ThreadPool.Add(newThread);
+							}
+						}
+					}
+					catch (Exception ex)
+					{
+						Logger.Log.Error("Error starting up thread pool", ex);
+					}
+
+					SetStatus("m_Searching");
+					var startTime = Stopwatch.GetTimestamp();
+					long LastCombinationCount = 0;
+
+					while (true)
+					{
+						long newTime = Stopwatch.GetTimestamp();
+						double timeSinceLast = Utils.GetTimePassed(startTime, newTime).TotalMilliseconds;
+						startTime = newTime;
+
+						bool isAnyRunning;
+						if (AppHelpers.Settings.UseParallel)
+						{
+							isAnyRunning = true;
+							if (parallel.HasValue)
+								isAnyRunning = !parallel.Value.IsCompleted;
+						}
+						else
+						{
+							isAnyRunning = false;
+							foreach (Task t in CollectionsMarshal.AsSpan(ThreadPool))
+							{
+								if (t.Status != TaskStatus.RanToCompletion)
+								{
+									isAnyRunning = true;
+									break;
+								}
+							}
+						}
+
+						if (timeSinceLast > 0)
+							ViewModel.CurrentSpeedLabel =
+							(
+								(PassedCombinations - LastCombinationCount)
+								* 1000
+								/ timeSinceLast
+							).ToString("n0");
+
+						LastCombinationCount = PassedCombinations;
+						ViewModel.ProgressPercentage = (float)PassedCombinations * 100 / ViewModel.TotalCombinations;
+						ViewModel.ParsedCombinations = PassedCombinations;
+						ViewModel.CombinationsLabel = string.Empty;
+
+						if (!isAnyRunning)
+							break;
+
+						if (stopAfterHit && ViewModel.FoundCombinations.Count >= 1)
+						{
+							TokenSource.Cancel();
+							break;
+						}
+
+						Thread.Sleep(100);
+					}
+
+					UpdateRichPresence(true);
+					Logger.Info("Finished searching");
+					Dispatcher.Invoke(
+						new Action(() =>
+						{
+							StartButton.SetResourceReference(ContentProperty, "m_Start");
+							ViewModel.CanEditSettings = true;
+							SetStatus("m_FinishedSearching");
+						})
+					);
+
+				}).Start();
+			}
+			else
+			{
+				Logger.Info("Canceling task");
+				TokenSource.Cancel();
+				UpdateRichPresence(true);
+			}
+		}
+	}
 }
diff --git a/FloatTool/Views/SettingsWindow.xaml b/FloatTool/Views/SettingsWindow.xaml
index 36f64aa..19dcc0f 100644
--- a/FloatTool/Views/SettingsWindow.xaml
+++ b/FloatTool/Views/SettingsWindow.xaml
@@ -25,7 +25,7 @@
         WindowStartupLocation="CenterScreen" 
         WindowStyle="None" 
         AllowsTransparency="True" ResizeMode="NoResize"
-        Background="Transparent" d:DesignHeight="370">
+        Background="Transparent" d:DesignHeight="400">
     <Window.Effect>
         <DropShadowEffect BlurRadius="3" ShadowDepth="3" Opacity="0.5"/>
     </Window.Effect>
@@ -149,6 +149,10 @@
                               Grid.Column="1" Grid.Row="1" Height="24"
                               ItemsSource="{Binding ExtensionNames}" SelectedIndex="{Binding SelectedExtension}"/>
                 </Grid>
+                <StackPanel Orientation="Horizontal" Margin="0,4,0,0">
+                    <ToggleButton Style="{DynamicResource SwitchButton}" Width="55" HorizontalAlignment="Left" IsChecked="{Binding UseParallel}"/>
+                    <TextBlock Text="{DynamicResource m_UseParallel}" FontSize="14" Margin="8,0" VerticalAlignment="Center" Foreground="{DynamicResource SettingsMainForeground}"/>
+                </StackPanel>
             </StackPanel>
 
             
diff --git a/FloatTool/Views/SettingsWindow.xaml.cs b/FloatTool/Views/SettingsWindow.xaml.cs
index bd10716..231151c 100644
--- a/FloatTool/Views/SettingsWindow.xaml.cs
+++ b/FloatTool/Views/SettingsWindow.xaml.cs
@@ -15,6 +15,8 @@
 - along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
 
+using FloatTool.Common;
+using FloatTool.ViewModels;
 using System;
 using System.Diagnostics;
 using System.IO;
@@ -23,7 +25,7 @@
 
 namespace FloatTool
 {
-    public sealed partial class SettingsWindow : Window
+	public sealed partial class SettingsWindow : Window
     {
         public SettingsWindow()
         {
diff --git a/FloatTool/Views/UpdateWindow.xaml.cs b/FloatTool/Views/UpdateWindow.xaml.cs
index a27f363..7cc5c6d 100644
--- a/FloatTool/Views/UpdateWindow.xaml.cs
+++ b/FloatTool/Views/UpdateWindow.xaml.cs
@@ -15,6 +15,7 @@
 - along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
 
+using FloatTool.Common;
 using System;
 using System.Diagnostics;
 using System.IO;
@@ -27,7 +28,7 @@
 
 namespace FloatTool
 {
-    public sealed partial class UpdateWindow : Window
+	public sealed partial class UpdateWindow : Window
     {
         public UpdateResult UpdateResult;