<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace UltimateOsuServerSwitcher
{
  public class Settings : Dictionary<string, string>
  {
    /// <summary>
    /// The settings file of this class
    /// </summary>
    public string File { get; }

    public Settings(string file)
    {
      File = file;

      // Create the file if it doesn't exist to prevent bugs when reading from a non-existing file
      if (!System.IO.File.Exists(file))
        System.IO.File.WriteAllText(file, "");
      else
        Load();
    }

    // Handle interaction with the indexer
    public new string this[string key]
    {
      get
      {
        return base[key];
      }
      set
      { 
        // Set the value and immediately save
        base[key] = value;
        Save();
      }
    }

    /// <summary>
    ///  Bind a default value to a key to prevent uninitialized properties
    /// </summary>
    /// <param name="key">The key to initialize</param>
    /// <param name="value">The value</param>
    public void SetDefaultValue(string key, string value)
    {
      // If the variable does not exist yet, initialize and save
      if (!Keys.Contains(key))
      {
        Add(key, value);
        Save();
      }
    }


    private void Save()
    {
      // Parse the dictionary in the key=value format
      string str = "";
      foreach (var kvp in this)
        str += kvp.Key + "=" + kvp.Value + "\r\n";

      // Remove the \r\n at the end and write everything to the settings file
      str = str.Substring(0, str.Length - 2);
      System.IO.File.WriteAllText(File, str);
    }

    private void Load()
    {
      // Clear the dictionary
      Clear();

      // Read the file and get every line, remove all \r to only get the lines without the \r at the end
      string str = System.IO.File.ReadAllText(File);
      List<string> lines = str.Split('\n').Select(x => x.Replace("\r", "")).ToList();

      // Go through every line that contains a = (valid lines) and split it by that,
      // then add the parsed key and value to the dictionary
      foreach (string line in lines.Where(x => x.Contains('=')))
      {
        // Split and limit the elements to 2 to ignore all = after the first one
        string key = line.Split("=".ToCharArray(), 2)[0];
        string value = line.Split("=".ToCharArray(), 2)[1];
        // If the key already exists, throw an exception
        // Otherwise add key and value to the dictionary
        if (ContainsKey(key))
          throw new InvalidOperationException("Tried to set the key '" + key + "' but it was already set.\r\n(Double occurence of the key in the settings file");
        else
          Add(key, value);
      }
    }
  }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  // https://stackoverflow.com/questions/19147/what-is-the-correct-way-to-create-a-single-instance-wpf-application
  public class NativeMethods
  {
    /// <summary>
    /// Broadcast hwnd to send a windows message to all processes
    /// </summary>
    public const int HWND_BROADCAST = 0xffff;

    /// <summary>
    /// Message to "wake up" the running switcher instance
    /// </summary>
    public static readonly int WM_WAKEUP = WinApi.RegisterWindowMessage("WM_WAKEUP");
  }
}
<file_sep>﻿using IWshRuntimeLibrary;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace UltimateOsuServerSwitcher
{
  public static class Switcher
  {
    // The settings for the switcher
    private static Settings m_settings => new Settings(Paths.SettingsFile);

    /// <summary>
    /// The servers that were parsed from the web
    /// </summary>
    public static List<Server> Servers { get; set; }

    /// <summary>
    /// Switch the server
    /// </summary>
    /// <param name="server">The server to switch to</param>
    public static void SwitchServer(Server server)
    {
      // Remember the old server for telemetry
      Server from = GetCurrentServer();

      // Get rid of all uneccessary certificates
      CertificateManager.UninstallAllCertificates(Servers);

      // Clean up the hosts file
      List<string> hosts = HostsUtil.GetHosts().ToList();
      hosts.RemoveAll(x => x.Contains(".ppy.sh"));
      HostsUtil.SetHosts(hosts.ToArray());

      // Edit the hosts file if the server is not bancho
      if (!server.IsBancho)
      {
        // Change the hosts file by adding the server's ip and the osu domain
        hosts = HostsUtil.GetHosts().ToList();
        foreach (string domain in Variables.OsuDomains)
          hosts.Add(server.IP + " " + domain);
        HostsUtil.SetHosts(hosts.ToArray());

        // Install the certificate if needed (not needed for bancho and localhost)
        if (server.HasCertificate)
          CertificateManager.InstallCertificate(server);
      }

      // Send telemetry if enabled
      if (m_settings["sendTelemetry"] == "true")
      {
        SendTelemetry(from, server);
      }
    }

    /// <summary>
    /// Returns the server the user is currently connected to
    /// </summary>
    public static Server GetCurrentServer()
    {
      // Get every line of the hosts file
      string[] hosts = HostsUtil.GetHosts();

      // Check for the first line that contains the osu domain
      for (int i = 0; i < hosts.Length; i++)
        if (hosts[i].Contains(".ppy.sh"))
        {
          // Read the ip that .ppy.sh redirects to
          string ip = hosts[i].Replace("\t", " ").Split(' ')[0];

          // Try to identify and return the server
          return Servers.FirstOrDefault(x => x.IP == ip) ?? Server.UnidentifiedServer;
        }

      // Because after downloading icon etc the bancho server object is not equal to Server.BanchoServer
      // because the Icon property is set then
      return Servers.First(x => x.IsBancho);
    }

    private static void SendTelemetry(Server from, Server to)
    {
      // Get the span between this and the last switching in unix milliseconds
      int span = -1;
      long currentUnixTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
      // If the unix time in the telemetry cache somehow cannot be converted
      // For example if the file was edited or the timestamp not set in the first place
      // Send an undefined timespan (-1)
      if (long.TryParse(TelemetryService.GetTelemetryCache(), out long lastUnixTime))
      {
        span = (int)(currentUnixTime - lastUnixTime);
      }

      // Set the timestamp in the file to the current one for the next switching
      TelemetryService.SetTelemetryCache(currentUnixTime.ToString());

      TelemetryService.SendTelemetry(from.ServerName ?? "Unknown", to.ServerName ?? "Unknown", TelemetryUtils.MillisecondsToString(span), CheckServerAvailability());
    }

    #region QuickSwitch
    /// <summary>
    /// Creates a QuickSwitch shortcut at the given location for the given server
    /// </summary>
    /// <param name="file">The path to the .lnk file</param>
    /// <param name="server">The server the shortcuts lets you switch to</param>
    public static void CreateShortcut(string file, Server server)
    {
      WshShell shell = new WshShell();
      IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(file);
      shortcut.Description = $"Switch to {server.ServerName}";
      if (server.Icon != null)
        shortcut.IconLocation = Paths.IconCacheFolder + $@"\{server.UID}.ico";
      shortcut.TargetPath = "cmd";
      shortcut.Arguments = $"/c call \"{Application.ExecutablePath}\" \"{server.UID}\"";
      shortcut.Save();
    }
    #endregion

    /// <summary>
    /// Checks if the currently redirected server is available (c.ppy.sh:80 ping)
    /// </summary>
    public static bool CheckServerAvailability()
    {
      // Try to build up a connection to the c interface of the osu server
      // to check if the bancho server is running
      try
      {
        using (TcpClient client = new TcpClient("c.ppy.sh", 80))
          return true;
      }
      catch
      {
        return false;
      }
    }
  }
}
<file_sep>﻿using IWshRuntimeLibrary;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace UltimateOsuServerSwitcher
{
  static class Program
  {
    /// <summary>
    /// Der Haupteinstiegspunkt für die Anwendung.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
      // Check if the program was started from a QuickSwitch Shortcut
      if (args.Length == 1)
      {
        MessageBox.Show("QuickSwitch to " + args[0]);
        return;
      }

      // Fix https://github.com/MinisBett/ultimate-osu-server-switcher/issues/9
      // (Windows 7 only)
      ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;


      // https://stackoverflow.com/questions/19147/what-is-the-correct-way-to-create-a-single-instance-wpf-application
      // Create mutex for single instance checks
      Mutex mutex = new Mutex(true, "UltimateOsuServerSwitcher");

      // Check if the mutex is owned by another process
      if (mutex.WaitOne(TimeSpan.Zero, true))
      {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());

        // Release the mutex for clean up
        mutex.ReleaseMutex();
      }
      else
      {
        // If the mutex is owned by another process (a switcher instance is already running)
        // sent the WM_WAKEUP message to all processes to put the current instance in the foreground
        WinApi.SendMessage((IntPtr)NativeMethods.HWND_BROADCAST, NativeMethods.WM_WAKEUP, 0, 0);
      }
    }
  }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public static class CertificateManager
  {
    /// <summary>
    /// Determines whether the certificate of the specified server is installed, or not.
    /// </summary>
    /// <param name="server">The server with the certificate to check</param>
    /// <returns>bool, if the certificate is installed</returns>
    public static Task<bool> CheckCertificateInstalled(Server server)
    {
      // Check if the certificate of the specified server awas found in the x509 store
      return Task.Run(() =>
      {
        X509Store x509Store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
        x509Store.Open(OpenFlags.ReadOnly);
        bool found = x509Store.Certificates.Find(X509FindType.FindByThumbprint, server.CertificateThumbprint, true).Count >= 1;
        x509Store.Close();
        return found;
      });
    }

    /// <summary>
    /// Installs the certificate of the specified server
    /// </summary>
    /// <param name="server">The server which certificate will be installed</param>
    public static void InstallCertificate(Server server)
    {
      // Install the certificate of the specified server
      X509Store x509Store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
      x509Store.Open(OpenFlags.ReadWrite);
      X509Certificate2 certificate = new X509Certificate2(server.Certificate);
      x509Store.Add(certificate);
      x509Store.Close();
    }

    /// <summary>
    /// Uninstalls the certificates of all specified servers
    /// </summary>
    /// <param name="servers">The servers which certificates will be uninstalled</param>
    public static void UninstallAllCertificates(List<Server> servers)
    {
      // Uninstall the certificates of all specified servers that has a certificate
      X509Store x509Store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
      x509Store.Open(OpenFlags.ReadWrite);
      foreach (Server server in servers.Where(x => x.HasCertificate))
        foreach (X509Certificate2 certificate in x509Store.Certificates.Find(X509FindType.FindByThumbprint, server.CertificateThumbprint, true))
        {
          try
          {
            x509Store.Remove(certificate);
          }
          catch (Exception ex)
          {
            throw ex;
          }
        }

      x509Store?.Close();
    }
  }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public static class Variables
  {
    // All osu domains that need to be redirected
    public static string[] OsuDomains => new string[]
    {
          "osu.ppy.sh", "c.ppy.sh", "c1.ppy.sh", "c2.ppy.sh", "c3.ppy.sh",
          "c4.ppy.sh", "c5.ppy.sh", "c6.ppy.sh", "ce.ppy.sh", "a.ppy.sh", "i.ppy.sh"
    };
  }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public static class Urls
  {
    /// <summary>
    /// Url to the json file with all mirrors
    /// </summary>
    public static string Mirrors => "https://raw.githubusercontent.com/MinisBett/ultimate-osu-server-switcher/master/datav2/mirrors.json";

    /// <summary>
    /// Url to the github repository of the switcher
    /// </summary>
    public static string Repository => "https://github.com/minisbett/ultimate-osu-server-switcher";

    /// <summary>
    /// Url to the discord page on the github page, which redirects to the discord invite url
    /// </summary>
    public static string Discord => "https://minisbett.github.io/ultimate-osu-server-switcher/discord.html";

    /// <summary>
    /// Url to a video from discord about discord rich presence for explaination
    /// </summary>
    public static string RichPresenceExplanation => "https://www.youtube.com/watch?v=Ss-IvNjl7JQ";
  }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public static class WebHelper
  {
    // The web client used for every connected in this class
    private static WebClient m_client = new WebClient();

    /// <summary>
    /// Download an image from a given url
    /// </summary>
    /// <param name="url">The given url to the image</param>
    /// <returns>The downloaded image</returns>
    public static async Task<Image> DownloadImageAsync(string url)
    {
      //using (Stream stream = m_client.OpenRead(url))
      //  return Image.FromStream(stream);
      // Download the bytes to the image async and convert it to an image using a memory stream
      using (MemoryStream stream = new MemoryStream(await DownloadBytesAsync(url)))
        return Image.FromStream(stream);
    }

    /// <summary>
    /// Downloads the bytes of a given url
    /// </summary>
    /// <param name="url">The given url</param>
    /// <returns>The byte array</returns>
    public static async Task<byte[]> DownloadBytesAsync(string url)
    {
      // Download the bytes async and return them
      var bytes = await m_client.DownloadDataTaskAsync(url);
      return bytes;
    }

    /// <summary>
    /// Downloads the string of a given url
    /// </summary>
    /// <param name="url">The given url</param>
    /// <returns>The string</returns>
    public static async Task<string> DownloadStringAsync(string url)
    {
      // Download the string and return it
      var str = await m_client.DownloadStringTaskAsync(url);
      return str;
    }
  }
}
<file_sep>﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public class Server
  {
    /// <summary>
    /// A unique id to identify the server
    /// </summary>
    public string UID { get; set; } = null;

    /// <summary>
    /// The name of the server
    /// </summary>
    [JsonProperty("name")]
    public string ServerName { get; private set; } = null;

    /// <summary>
    /// Determines whether the server is featured or not
    /// </summary>
    public bool IsFeatured { get; set; } = false;

    /// <summary>
    /// The IP-Address of the server
    /// </summary>
    [JsonProperty("ip")]
    public string IP { get; private set; } = null;

    /// <summary>
    /// The url to the certificate
    /// </summary>
    [JsonProperty("certificate_url")]
    public string CertificateUrl { get; private set; } = null;

    /// <summary>
    /// The certificate
    /// </summary>
    public byte[] Certificate { get; set; } = null;

    /// <summary>
    /// The thumbprint of the certificate
    /// </summary>
    public string CertificateThumbprint { get; set; } = null;

    /// <summary>
    /// The url to the discord server
    /// </summary>
    [JsonProperty("discord_url")]
    public string DiscordUrl { get; private set; } = null;

    /// <summary>
    /// The url of the server's icon
    /// </summary>
    [JsonProperty("icon_url")]
    public string IconUrl { get; private set; } = null;

    /// <summary>
    /// The icon
    /// </summary>
    public Image Icon { get; set; } = null;

    /// <summary>
    /// Determines whether the server was identified (is in the database) or not
    /// </summary>
    public bool IsUnidentified => UID == null;

    /// <summary>
    /// Determines whether the server is localhost or not
    /// </summary>
    public bool IsLocalhost => UID == "localhost";

    /// <summary>
    /// Determines whether the server is the official osu!bancho server or not
    /// </summary>
    public bool IsBancho => UID == "bancho";

    /// <summary>
    /// Determines whether the server should a have certificate in theory or not
    /// </summary>
    public bool HasCertificate => !IsLocalhost && !IsUnidentified && !IsBancho;

    /// <summary>
    /// The priority of the server
    /// </summary>
    public int Priority
    {
      get
      {
        if (IsBancho)
          return 4;
        else if (IsFeatured)
          return 3;
        else if (IsLocalhost)
          return 1;
        return 2;
      }
    }

    /// <summary>
    /// An unidentified server object
    /// </summary>
    public static Server UnidentifiedServer => new Server();

    /// <summary>
    /// A server object that represents the bancho server
    /// </summary>
    public static Server BanchoServer => new Server() { UID = "bancho", ServerName = "osu!bancho", DiscordUrl = "", CertificateUrl = "", IconUrl = "https://raw.githubusercontent.com/MinisBett/ultimate-osu-server-switcher/master/datav2/osu_256.png" };

    /// <summary>
    /// A server object that represents a localhost server
    /// </summary>
    public static Server LocalhostServer => new Server() { UID = "localhost", ServerName = "localhost", IP = "127.0.0.1", IconUrl = "https://raw.githubusercontent.com/MinisBett/ultimate-osu-server-switcher/master/datav2/osu_256.png" };

    /// <summary>
    /// Returns an array with new instances of all static/hardcoded servers
    /// </summary>
    public static Server[] StaticServers => new Server[] { BanchoServer, LocalhostServer };
  }
}
<file_sep>﻿using DiscordRPC;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public static class Discord
  {

    // The discord RPC client used for the rich presence
    // given the app id (see developer portal) and -1 as the pipe for automatic pipe scan
    private static DiscordRpcClient m_client = new DiscordRpcClient("770355757622755379", -1);


    // Current RichPresence object
    private static RichPresence m_richPresence = null;

    // Determines if a presence is currently being shown
    public static bool IsPrecenseSet { get; private set; } = false;

    public static Server ShownServer { get; private set; } = null;

    public static void SetPresenceServer(Server server)
    {
      if (!m_client.IsInitialized)
        m_client.Initialize();

      if (!IsPrecenseSet)
      {
        m_richPresence = new RichPresence();
        m_richPresence.Timestamps = new Timestamps(DateTime.UtcNow);
        m_richPresence.State = $"Playing on {server.ServerName}";
        m_richPresence.Details = "Ultimate Osu Server Switcher";
        m_richPresence.Assets = new Assets() { LargeImageKey = "osu", LargeImageText = "Download on GitHub!\r\nminisbett/ultimate-osu-server-switcher" };
        m_client.SetPresence(m_richPresence);

        IsPrecenseSet = true;
      }
      else
      {
        m_richPresence.State = "Currently playing on: " + server.ServerName;
        m_client.SetPresence(m_richPresence);
      }

      ShownServer = server;
    }

    public static void RemovePresence()
    {
      if (IsPrecenseSet)
      {
        m_client.ClearPresence();
        IsPrecenseSet = false;
        ShownServer = null;
      }
    }
  }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace UltimateOsuServerSwitcher
{
  public static class HostsUtil
  {
    /// <summary>
    /// Reads all lines of the hosts file and returns them in a string array
    /// </summary>
    /// <returns>The lines of the hosts file</returns>
    public static string[] GetHosts()
    {
      return File.ReadAllLines(Environment.SystemDirectory + @"\drivers\etc\hosts");
    }

    /// <summary>
    /// Overwrites all lines of the hosts file.
    /// Will retry 2 times if failed
    /// </summary>
    /// <param name="hosts">The lines that will be written in the hosts file</param>
    /// <param name="retry">(Internal use only) the amount of retries that are already done</param>
    public static void SetHosts(string[] hosts, int retry = 0)
    {
      // Try to change the hosts file (cannot be successful due to anti virus, file lock, ...)
      try
      {
        File.WriteAllLines(Environment.SystemDirectory + @"\drivers\etc\hosts", hosts);
      }
      catch (Exception ex)
      {
        // Retry it 2 more times
        if (retry < 3)
        {
          SetHosts(hosts, retry++);
        }
        else
        {
          // Show the corresponding error message
          string error = "";

          if (ex is DirectoryNotFoundException)
          {
            error = "The hosts file could not be found. (DirectoryNotFoundException)\r\nPlease visit our discord server so we can provide help to you.";
          }
          else if(ex is IOException)
          {
            error = "(Retry 3/3) Cannot open the hosts file. (IOException)\r\nPlease try to deactivate your antivurs.\r\n\r\nIf this doesn't fix your issue, please visit our discord server so we can provide help to you.";
          }
          else if(ex is UnauthorizedAccessException)
          {
            error = "(Retry 3/3) Cannot access the hosts file. (UnauthorizedAccessException)\r\nPlease try to deactivate your antivurs.\r\n\r\nIf this doesn't fix your issue, please visit our discord server so we can provide help to you.";
          }
          else
          {
            error = "(Retry 3/3) " + ex.Message;
          }

          MessageBox.Show(error, "Ultimate Osu Server Switcher", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
      }
    }
  }
}<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public static class Paths
  {
    /// <summary>
    /// Folder where all files for the switcher are saved
    /// /// </summary>
    public static string AppFolder => Environment.GetEnvironmentVariable("localappdata") + @"\UltimateOsuServerSwitcher";

    /// <summary>
    /// Cache file for the telemetry
    /// </summary>
    public static string TelemetryCacheFile => AppFolder + @"\telemetry_cache";

    /// <summary>
    /// Folder where the .ico icons for the shortcuts are saved
    /// </summary>
    public static string IconCacheFolder => AppFolder + @"\IconCache";

    /// <summary>
    /// Settings file where all switcher settings are saved
    /// </summary>
    public static string SettingsFile => AppFolder + @"\settings";
  }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public static class VersionChecker
  {
    /// <summary>
    /// The current version of this switcher
    /// </summary>
    public static string CurrentVersion = "2.0.0";

    // The web client used for all web connections in this class
    private static WebClient m_client = new WebClient();

    /// <summary>
    /// Gets the current version state of this switcher
    /// </summary>
    /// <returns>The version state of this switcher</returns>
    public async static Task<VersionState> GetCurrentState()
    {
      // Downloads the blacklisted version file and parses them into an array
      string blacklistedVersionsStr = await m_client.DownloadStringTaskAsync("https://raw.githubusercontent.com/MinisBett/ultimate-osu-server-switcher/master/datav2/BLACKLISTED_VERSIONS");
      string[] blacklistedVersions = blacklistedVersionsStr.Replace("\r", "").Split("\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
      // If the blacklist starts with "*", return MAINTENANCE
      if (blacklistedVersions.Length >= 1 && blacklistedVersions[0] == "*")
        return VersionState.MAINTENANCE;
      // If the blacklist contains the version of this switcher, return BLACKLISTED
      if (blacklistedVersions.Contains(CurrentVersion))
        return VersionState.BLACKLISTED;
      // If the newest version is not the one of this switcher, return OUTDATED
      if (await GetNewestVersion() != CurrentVersion)
        return VersionState.OUTDATED;

      // Otherwise, return LATEST
      return VersionState.LATEST;
    }

    /// <summary>
    /// Gets the newest version available of the switcher
    /// </summary>
    /// <returns>The newest available version</returns>
    public async static Task<string> GetNewestVersion()
    {
      // Download the VERSION file, remove the white space and return
      string newestVersion = await m_client.DownloadStringTaskAsync("https://raw.githubusercontent.com/MinisBett/ultimate-osu-server-switcher/master/datav2/VERSION");
      newestVersion = newestVersion.Replace("\n", "");
      return newestVersion;
    }
  }

  public enum VersionState
  {
    LATEST,
    OUTDATED,
    BLACKLISTED,
    MAINTENANCE
  }
}<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public static class WinApi
  {
    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);

    [DllImport("user32.dll")]
    public static extern bool ReleaseCapture();

    [DllImport("user32.dll")]
    public static extern int RegisterWindowMessage(string message);
  }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace UltimateOsuServerSwitcher
{
  public static class TelemetryService
  {
    /// <summary>
    /// Send telemetry to the server
    /// </summary>
    /// <param name="from">The server the user is coming from</param>
    /// <param name="to">The server the user is switching to</param>
    /// <param name="span">The span between this and the last switch</param>
    /// <param name="successful">Connectivity status of the switched to server</param>
    public static void SendTelemetry(string from, string to, string span, bool successful)
    {
      // Send a get request with the arguments as parameters
      string url = "https://uosuss.000webhostapp.com/index.php";
      string get = $"from={from}&to={to}&span={span}&successful={(successful ? 1 : 0)}";
      new WebClient().DownloadData(url + "?" + get);
    }

    /// <summary>
    /// Gets the telemetry cache
    /// </summary>
    /// <returns>The telemetry cache</returns>
    public static string GetTelemetryCache()
    {
      // If the file does not exist yet, return nothing
      if (!File.Exists(Paths.TelemetryCacheFile))
        return "";
      return File.ReadAllText(Paths.TelemetryCacheFile);
    }

    /// <summary>
    /// Sets the telemetry cache
    /// </summary>
    /// <param name="cache">The telemetry cache</param>
    public static void SetTelemetryCache(string cache)
    {
      // Write to the telemetry cache file
      File.WriteAllText(Paths.TelemetryCacheFile, cache);
    }
  }
}
<file_sep>﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace UltimateOsuServerSwitcher
{
  public partial class MainForm : Form
  {
    // The server that is currently selected (not the one the user is connected to)
    private Server m_currentSelectedServer = null;

    // The index of the server above
    private int m_currentSelectedServerIndex => Switcher.Servers.IndexOf(m_currentSelectedServer);

    // Temporary variable to force-close the switcher if minimize to system tray is enabled
    private bool m_forceclose = false;

    // The settings for the switcher
    private Settings m_settings => new Settings(Paths.SettingsFile);

    #region Program initialize

    public MainForm()
    {
      InitializeComponent();

      // Check if the icon cache folder exists
      if (!Directory.Exists(Paths.IconCacheFolder))
        Directory.CreateDirectory(Paths.IconCacheFolder);

      // Set the version label to the current version provided by the version checker
      lblVersion.Text = "Version: " + VersionChecker.CurrentVersion;

      // Initialize all settings with their default values
      m_settings.SetDefaultValue("minimizeToTray", "true");
      m_settings.SetDefaultValue("sendTelemetry", "false");
      m_settings.SetDefaultValue("closeOsuBeforeSwitching", "true");
      m_settings.SetDefaultValue("reopenOsuAfterSwitching", "true");
      m_settings.SetDefaultValue("openOsuAfterQuickSwitching", "true");
      m_settings.SetDefaultValue("useDiscordRichPresence", "true");

      // Set the settings controls to their state from the settings file
      chckbxMinimize.Checked = m_settings["minimizeToTray"] == "true";
      chckbxSendTelemetry.Checked = m_settings["sendTelemetry"] == "true";
      chckbxCloseBeforeSwitching.Checked = m_settings["closeOsuBeforeSwitching"] == "true";
      chckbxReopenAfterSwitching.Checked = m_settings["reopenOsuAfterSwitching"] == "true";
      chckbxOpenAfterQuickSwitching.Checked = m_settings["openOsuAfterQuickSwitching"] == "true";
      chckbxUseDiscordRichPresence.Checked = m_settings["useDiscordRichPresence"] == "true";

      //
      // Telemetry disabled because not finished yet
      //
      m_settings["sendTelemetry"] = "false";
      chckbxSendTelemetry.Checked = false;
    }

    private async void MainForm_Load(object sender, EventArgs e)
    {
      // Hide the connect button, show the loading button to make the user clear that the servers are being fetched
      btnConnect.Visible = false;
      pctrLoading.Visible = true;
      Application.DoEvents();

      await Task.Delay(1);

      // Check the state of the current version
      VersionState vs = await VersionChecker.GetCurrentState();
      if (vs == VersionState.BLACKLISTED)
      {
        // If the current version is blacklisted, prevent the user from using it.
        MessageBox.Show($"Your current version ({VersionChecker.CurrentVersion}) is blacklisted.\r\n\r\nThis can happen when the version contains security flaws or other things that could interrupt a good user experience.\r\n\r\nPlease download the newest version of the switcher from its website. (github.com/minisbett/ultimate-osu-server-switcher/releases).", "Ultimate Osu Server Switcher", MessageBoxButtons.OK, MessageBoxIcon.Error);
        Environment.Exit(0);
        return;
      }
      else if (vs == VersionState.MAINTENANCE)
      {
        // If the switcher is in maintenance, also prevent the user from using it.
        MessageBox.Show("The switcher is currently hold in maintenance mode which means that the switcher is currently not available.\r\n\r\nJoin our discord server for more informations.\r\nThe discord server and be found on our github page.", "Ultimate Osu Server Switcher", MessageBoxButtons.OK, MessageBoxIcon.Error);
        Environment.Exit(0);
        return;
      }
      else if (vs == VersionState.OUTDATED)
      {
        // Show the user a message that a new version is available if the current switcher is outdated.
        MessageBox.Show($"Your switcher version ({VersionChecker.CurrentVersion}) is outdated.\r\nA newer version ({await VersionChecker.GetNewestVersion()}) is available.\r\n\r\nYou can download it from our github page.", "Ultimate Osu Server Switcher", MessageBoxButtons.OK, MessageBoxIcon.Warning);
      }


      lblInfo.Text = "Fetching mirrors...";
      Application.DoEvents();

      // Try to load online data and verify servers
      List<Mirror> mirrors = null;
      try
      {
        // Download the mirror data from github and deserialize it into a mirror list
        mirrors = JsonConvert.DeserializeObject<List<Mirror>>(await WebHelper.DownloadStringAsync(Urls.Mirrors));
      }
      catch
      {
        // If it was not successful, github may cannot currently be reached or I made a mistake in the json data.
        MessageBox.Show("Error whilst parsing the server mirrors from GitHub!\r\nPlease make sure you can connect to www.github.com in your browser.\r\n\r\nIf this issue persists, please visit our discord. You can find the invite link on our GitHub Page (minisbett/ultimate-osu-server-switcher)", "Ultimate Osu Server Switcher", MessageBoxButtons.OK, MessageBoxIcon.Error);
        Application.Exit();
        return;
      }

      imgLoadingBar.Maximum = mirrors.Count;
      // Try to load all servers
      List<Server> servers = new List<Server>();
      foreach (Mirror mirror in mirrors)
      {
        lblInfo.Text = $"Parsing mirror {mirror.Url}";
        Application.DoEvents();
        Server server = null;
        // Try to serialize the mirror into a server
        try
        {
          // Download the data from the mirror and try to parse it into a server object.
          server = JsonConvert.DeserializeObject<Server>(await WebHelper.DownloadStringAsync(mirror.Url));
        }
        catch
        {
          // If the cast was not successful (invalid json) or the mirror could not be reached, skip the mirror
          continue;
        }
        // Forward mirror variables to the server
        server.IsFeatured = mirror.Featured;
        server.UID = mirror.UID;
        lblInfo.Text = $"Parsing mirror {mirror.Url} ({server.ServerName})";
        Application.DoEvents();

        // Check if UID is 6 letters long (If not I made a mistake)
        if (server.UID.Length != 6)
          continue;

        // Check if the UID is really unique (I may accidentally put the same uid for two servers)
        if (servers.Any(x => x.UID == server.UID))
          continue;

        // Check if everything is set
        if (server.ServerName == null ||
            server.IP == null ||
            server.IconUrl == null ||
            server.CertificateUrl == null ||
            server.DiscordUrl == null)
          continue;

        // Check if server name length is valid (between 3 and 24)
        if (server.ServerName.Replace(" ", "").Length < 3 || server.ServerName.Length > 24)
          continue;
        // Check if it neither start with a space, nor end
        if (server.ServerName.StartsWith(" "))
          continue;
        if (server.ServerName.EndsWith(" "))
          continue;
        // // Only a-zA-Z0-9 ! is allowed
        if (!Regex.Match(server.ServerName.Replace("!", "").Replace(" ", ""), "^\\w+$").Success)
          continue;
        // Double spaces are invalid because its hard to tell how many spaces there are
        // (One server could be named test 123 and the other test  123)
        if (server.ServerName.Replace("  ", "") != server.ServerName)
          continue;
        // Check if the server fakes a hardcoded server
        if (Server.StaticServers.Any(x => x.ServerName == server.ServerName))
          continue;

        // Check if the server ip is formatted correctly
        if (!Regex.IsMatch(server.IP, @"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$"))
          continue;

        // Check if that server name already exists (if so, prioritize the first one)
        if (servers.Any(x => x.ServerName.ToLower().Replace(" ", "") == server.ServerName.ToLower().Replace(" ", "")))
          continue;

        // Check if its a real discord invite url
        if (!server.DiscordUrl.Replace("https", "").Replace("http", "").Replace("://", "").StartsWith("discord.gg"))
          continue;

        // Initialize variables like Certificate and Icon that are downloaded from their urls when
        // all checks are done (IconUrl, CertificateUrl)

        try
        {
          // Try to parse the certificate from the given url
          server.Certificate = await WebHelper.DownloadBytesAsync(server.CertificateUrl);
          server.CertificateThumbprint = new X509Certificate2(server.Certificate).Thumbprint;
        }
        catch // Cerfiticate url not valid or certificate type is not cer (base64 encoded)
        {
          continue;
        }

        // Check if icon is valid
        try
        {
          // Download the icon and check if its at least 256x256
          Image icon = await WebHelper.DownloadImageAsync(server.IconUrl);
          if (icon.Width < 256 || icon.Height < 256)
            continue;

          // Scale the image to 256x256
          server.Icon = new Bitmap(icon, new Size(256, 256));

          // Add the server to the servers that were successfully parsed and checked
          servers.Add(server);
        }
        catch // Image could not be downloaded or loaded
        {
          continue;
        }

        imgLoadingBar.Value++;
      }

      // Load bancho and localhost
      try
      {
        // Download the icon and check if its at least 256x256
        Image icon = await WebHelper.DownloadImageAsync(Server.BanchoServer.IconUrl);
        if (icon.Width >= 256 && icon.Height >= 256)
        {
          // Add the bancho server
          Server s = Server.BanchoServer;
          // Scale the image to 256x256
          s.Icon = new Bitmap(icon, new Size(256, 256));
          servers.Add(s);
        }
      }
      catch // Image could not be downloaded or loaded
      {

      }

      try
      {
        // Download the icon and check if its at least 256x256
        Image icon = await WebHelper.DownloadImageAsync(Server.LocalhostServer.IconUrl);
        if (icon.Width >= 256 && icon.Height >= 256)
        {
          // Add the localhost server
          Server s = Server.LocalhostServer;
          // Scale the image to 256x256
          s.Icon = new Bitmap(icon, new Size(256, 256));
          servers.Add(s);
        }
      }
      catch // Image could not be downloaded or loaded
      {

      }

      // Sort the servers by priority (first bancho, then featured, then normal, then localhost)
      servers = servers.OrderByDescending(x => x.Priority).ToList();

      // Create .ico files for shortcuts
      foreach (Server server in servers)
      {
        using (FileStream fs = File.OpenWrite(Paths.IconCacheFolder + $@"\{server.UID}.ico"))
        using (MemoryStream ms = new MemoryStream((byte[])new ImageConverter().ConvertTo(server.Icon, typeof(byte[]))))
          ImagingHelper.ConvertToIcon(ms, fs, server.Icon.Width, true);
      }

      Switcher.Servers = servers;

      // Enable/Disable the timer depending on if useDiscordRichPresence is true or false
      // Set here because the timer needs to have the servers loaded
      richPresenceUpdateTimer.Enabled = m_settings["useDiscordRichPresence"] == "true";

      // Initialize the current selected server variable
      m_currentSelectedServer = Switcher.GetCurrentServer();
      if (m_currentSelectedServer.IsUnidentified)
        m_currentSelectedServer = Switcher.Servers.First(x => x.UID == "bancho");

      // Hide loading button and loading bar after all mirrors are loaded
      pctrLoading.Visible = false;
      imgLoadingBar.Visible = false;

      // Show the account manager button because all servers are loaded now
      btnAccountManager.Visible = true;

      // Update the UI
      UpdateUI();
    }

    #endregion

    #region Click & CheckChanged Events

    private void pctrServerIcon_Click(object sender, EventArgs e)
    {
      // If the server is set has a discord, open the link
      if (m_currentSelectedServer != null && !string.IsNullOrEmpty(m_currentSelectedServer.DiscordUrl))
        Process.Start(m_currentSelectedServer.DiscordUrl);
    }

    private void BtnConnect_Click(object sender, EventArgs e)
    {
      // Change the shown button to the "connecting" one
      btnConnect.Visible = false;
      pctrConnecting.Visible = true;
      Application.DoEvents();

      // Save the osu executable path for the reopen feature later
      string osuExecutablePath = "";
      // Only close osu if the feature is enabled
      if (m_settings["closeOsuBeforeSwitching"] == "true")
      {
        // Find the osu process and, if found, save the executable path and kill it
        Process osu = Process.GetProcessesByName("osu!").FirstOrDefault();
        if (osu != null)
        {
          osuExecutablePath = osu.MainModule.FileName;
          osu.Kill();
          // Wait till osu is completely closed
          osu.WaitForExit();
        }
      }

      // Switch the server to the currently selected one
      Switcher.SwitchServer(m_currentSelectedServer);

      // Check if the server is available (server that has been switched to is reachable)
      if (!Switcher.CheckServerAvailability())
      {
        // If not, show a warning
        MessageBox.Show("The connection test failed. Please restart the switcher and try again.\r\n\r\nIf it's still not working the server either didn't update their mirror yet or their server is currently not running (for example due to maintenance).", "Ultimate Osu Server Switcher", MessageBoxButtons.OK, MessageBoxIcon.Warning);
      }

      // Start osu if the reopen feature is enabled and an osu instance was found before switching
      // osuExecutablePath can only be != "" if close before switching feature is enabled
      // so a check for the closeOsuBeforeSwitching setting is not necessary
      if (m_settings["reopenOsuAfterSwitching"] == "true" && osuExecutablePath != "")
      {
        Process.Start(osuExecutablePath);
      }

      // Hide the "connecting" button and update the UI (update UI will show the already connected button then)
      pctrConnecting.Visible = false;
      UpdateUI();
    }

    private void btnLeft_Click(object sender, EventArgs e)
    {
      // Move the selection 1 to the left and update the UI
      m_currentSelectedServer = Switcher.Servers[m_currentSelectedServerIndex - 1];
      UpdateUI();
    }

    private void btnRight_Click(object sender, EventArgs e)
    {
      // Move the selection 1 to the right and update the UI
      m_currentSelectedServer = Switcher.Servers[m_currentSelectedServerIndex + 1];
      UpdateUI();
    }
    private void lnklblTelemetryLearnMore_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
    {
      // Show informations about telemetry logging
      MessageBox.Show("We would appreciate to log some data to improve the user experience for everyone.\r\n\r\nThe following data will be transmitted to our server:\r\n\r\n- Server you are coming from and switching to\r\n- The time span between switching the server\r\n- Connectivity status of servers\r\n\r\n\r\nNote: No informations that would identify you will be transmitted. All informations are completely anonymous.\r\n\r\nYou can stop sending telemtry data at any time by disabling that option.", "UOSS Telemetry Service", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }

    private void lnklblWhyMinimize_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
    {
      // Show informations about the minimize to tray option
      MessageBox.Show("Minimize to system tray means that if you close the program it just gets minimized to the system tray\r\n(icons next to the clock in the taskbar).\r\n\r\nThis way, the switcher will run in background and, when you open it again, doesn't need to load all servers again.\r\n\r\nThis feature is useful when you switch the server quite often.\r\n\r\nYou can still close the switcher by either disabling this option or right clicking the icon in the system tray, and then 'Exit'.", "Ultimate Osu Server Switcher", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }

    private void lnklblWhatRichPresence_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
    {
      // Open a discord video about discord rich presence for explaination
      Process.Start(Urls.RichPresenceExplanation);
    }

    private void btnExit_Click(object sender, EventArgs e)
    {
      // Close the application
      Application.Exit();
    }

    private void pctrGithub_Click(object sender, EventArgs e)
    {
      // Open the github page
      Process.Start(Urls.Repository);
    }

    private void pctrDiscord_Click(object sender, EventArgs e)
    {
      // Open the page that redirects to the discord invite url
      Process.Start(Urls.Discord);
    }

    private void showToolStripMenuItem_Click(object sender, EventArgs e)
    {
      // If the show tool strip of the context menu from the notify icon is clicked, show the switcher
      // by simulating a click on the notify icon
      notifyIcon_MouseClick(notifyIcon, new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0));
    }

    private void exitToolStripMenuItem_Click(object sender, EventArgs e)
    {
      // If the exit tool strip of the context menu from the notify icon is clicked, force-close the program
      // (force close needed because otherwise the FormClosing event would be cancelled again and the program
      // just minimized to the taskbar again)
      m_forceclose = true;
      Application.Exit();
    }

    private void chckbxMinimize_CheckedChanged(object sender, EventArgs e)
    {
      // Save the minimizeToTray setting
      m_settings["minimizeToTray"] = chckbxMinimize.Checked ? "true" : "false";
    }

    private void chckbxSendTelemetry_CheckedChanged(object sender, EventArgs e)
    {
      // Save the sendTelemetry setting
      m_settings["sendTelemetry"] = chckbxSendTelemetry.Checked ? "true" : "false";
    }

    private void chckbxCloseBeforeSwitching_CheckedChanged(object sender, EventArgs e)
    {
      // Only enable the reopen setting control when close before switching is enabled
      chckbxReopenAfterSwitching.Enabled = chckbxCloseBeforeSwitching.Checked;
      // Save the closeOsuBeforeSwitching setting
      m_settings["closeOsuBeforeSwitching"] = chckbxCloseBeforeSwitching.Checked ? "true" : "false";
    }

    private void chckbxReopenAfterSwitching_CheckedChanged(object sender, EventArgs e)
    {
      // Save the reopenOsuAfterSwitching setting
      m_settings["reopenOsuAfterSwitching"] = chckbxReopenAfterSwitching.Checked ? "true" : "false";
    }

    private void chckbxOpenAfterQuickSwitching_CheckedChanged(object sender, EventArgs e)
    {
      m_settings["openOsuAfterQuickSwitching"] = chckbxOpenAfterQuickSwitching.Checked ? "true" : "false";
    }

    private void chckbxUseDiscordRichPresence_CheckedChanged(object sender, EventArgs e)
    {
      m_settings["useDiscordRichPresence"] = chckbxUseDiscordRichPresence.Checked ? "true" : "false";

      // If the presence feature gets deactivated, remove the precense if needed
      if (!chckbxUseDiscordRichPresence.Checked && Discord.IsPrecenseSet)
        Discord.RemovePresence();
      else
        MessageBox.Show("In order to make this feature run properly, please disable the Discord Rich Presense in your osu! settings.\r\n\r\nIf it still doesn't show up, try to switch the server or restart the switcher in order to reload the Discord Rich Presence.", "Ultimate Osu Server Switcher", MessageBoxButtons.OK, MessageBoxIcon.Information);

      // En/Disable the timer that constantly checks if osu is running
      richPresenceUpdateTimer.Enabled = chckbxUseDiscordRichPresence.Checked;
    }

    private void notifyIcon_MouseClick(object sender, MouseEventArgs e)
    {
      // Only show when left mouse button is used because the right should show the context menu
      if (e.Button == MouseButtons.Left)
      {
        // Hide the notify icon and show the switcher when clicked on the notify icon
        notifyIcon.Visible = false;
        Show();
      }
    }

    #region Tab pages

    private void btnSettings_Click(object sender, EventArgs e)
    {
      btnSwitcher.BackColor = Color.FromArgb(10, 10, 10);
      btnSwitcher.FlatAppearance.BorderSize = 0;
      btnHelp.BackColor = Color.FromArgb(10, 10, 10);
      btnHelp.FlatAppearance.BorderSize = 0;

      btnSettings.BackColor = Color.FromArgb(31, 31, 31);
      btnSettings.FlatAppearance.BorderSize = 2;

      pnlSwitcher.Visible = false;
      pnlHelp.Visible = false;
      pnlSettings.Visible = true;
    }

    private void btnSwitcher_Click(object sender, EventArgs e)
    {
      btnSettings.BackColor = Color.FromArgb(10, 10, 10);
      btnSettings.FlatAppearance.BorderSize = 0;
      btnHelp.BackColor = Color.FromArgb(10, 10, 10);
      btnHelp.FlatAppearance.BorderSize = 0;

      btnSwitcher.BackColor = Color.FromArgb(31, 31, 31);
      btnSwitcher.FlatAppearance.BorderSize = 2;

      pnlSettings.Visible = false;
      pnlHelp.Visible = false;
      pnlSwitcher.Visible = true;
    }
    private void btnHelp_Click(object sender, EventArgs e)
    {
      btnSettings.BackColor = Color.FromArgb(10, 10, 10);
      btnSettings.FlatAppearance.BorderSize = 0;
      btnSwitcher.BackColor = Color.FromArgb(10, 10, 10);
      btnSwitcher.FlatAppearance.BorderSize = 0;

      btnHelp.BackColor = Color.FromArgb(31, 31, 31);
      btnHelp.FlatAppearance.BorderSize = 2;

      pnlSettings.Visible = false;
      pnlSwitcher.Visible = false;
      pnlHelp.Visible = true;
    }

    #endregion

    #endregion

    #region Other events

    private void richPresenceUpdateTimer_Tick(object sender, EventArgs e)
    {
      // Get all osu instances to check if osu is running
      Process[] osuInstances = Process.GetProcessesByName("osu!");
      // If no osu is running but a presence is set, remove that presence
      if (osuInstances.Length == 0 && Discord.IsPrecenseSet)
        Discord.RemovePresence();
      else if (osuInstances.Length > 0)
      {
        // Get the current server
        Server currentServer = Switcher.GetCurrentServer();
        // Check if either no presence is set or the current presence is a different server
        // If the presence is a different server, update the presence (server was switched)
        if (!Discord.IsPrecenseSet || Discord.ShownServer.UID != currentServer.UID)
          Discord.SetPresenceServer(currentServer);
      }
    }

    private void BorderlessDragMouseDown(object sender, MouseEventArgs e)
    {
      // Make borderless window moveable
      if (e.Button == MouseButtons.Left)
      {
        WinApi.ReleaseCapture();
        WinApi.SendMessage(Handle, 0xA1, 0x2, 0);
      }
    }

    private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
    {
      // If the minimize to tray option is enabled, hide the switcher, 
      // show the tray symbol and cancel the exit
      if (chckbxMinimize.Checked && !m_forceclose)
      {
        e.Cancel = true;
        notifyIcon.Visible = true;
        Hide();
      }
    }

    protected override void WndProc(ref Message m)
    {
      // Override the wndproc event to receive messages from other switcher instances (quick switch, multi instance, ...)

      if (m.Msg == NativeMethods.WM_WAKEUP) // Called by a second switcher instance to tell this switcher to get to the foreground
      {
        // If the switcher is not minimized to the system tray, just focus it; otherwise simulate a click on the notify icon
        if (Visible)
          Focus();
        else
          notifyIcon_MouseClick(notifyIcon, new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0));
      }
      else
        base.WndProc(ref m);
    }

    #endregion

    private void UpdateUI()
    {
      // Get the current server to update the UI
      Server currentServer = Switcher.GetCurrentServer();

      // If the server the user is currently connected to unidentified the icon of the server cannot be shown
      // because its unknown. Otherwise show the icon of the current connected server
      pctrCurrentServer.Image = currentServer.IsUnidentified ? null : currentServer.Icon;

      // Show the image of the server that is currently selected
      pctrCurrentSelectedServer.Image = m_currentSelectedServer.Icon;

      // Show the connect / already connected button whether the currently selected server
      // is the one the user is connected to or not
      btnConnect.Visible = m_currentSelectedServer.UID != currentServer.UID;
      pctrAlreadyConnected.Visible = m_currentSelectedServer.UID == currentServer.UID;

      // Show the left and right arrow whether the index allows to go to the left or right
      btnRight.Visible = m_currentSelectedServerIndex < Switcher.Servers.Count - 1;
      btnLeft.Visible = m_currentSelectedServerIndex > 0;

      // Set the info text to the name of the current selected server
      lblInfo.Text = m_currentSelectedServer.ServerName;
      // Change the color of the server name depending on if the server is featured or not
      lblInfo.ForeColor = m_currentSelectedServer.IsFeatured ? Color.Orange : Color.White;
      // Show the verified badge depending on if the server is featured or not
      pctrVerified.Visible = m_currentSelectedServer.IsFeatured;

      // Get a graphics object to measure the size to the info text
      // in order to position the verified badge next to the text
      Graphics g = CreateGraphics();
      // Measure the size of the text
      SizeF textSize = g.MeasureString(m_currentSelectedServer.ServerName, lblInfo.Font);
      // Set the location, x is the middle of the panel + half the text - 5
      // to position the verified badge next to the next (-5 to make the distance less)
      pctrVerified.Location = new Point(pnlSwitcher.Width / 2 + (int)textSize.Width / 2 - 5, pctrVerified.Location.Y);
    }
  }
}<file_sep><a href="https://minisbett.github.io/ultimate-osu-server-switcher/discord.html"><img src="https://discordapp.com/api/guilds/715149105525030932/widget.png"></a>

<a href="https://minisbett.github.io/ultimate-osu-server-switcher"><img width=128 height=128 align="right" src="https://minisbett.github.io/ultimate-osu-server-switcher/images/icon.png"></a>

# Ultimate Osu Server Switcher

The Ultimate Osu Server Switcher is a server switcher that allows you to switch between osu!bancho and a variety of private servers.
You can [add your server](https://minisbett.github.io/ultimate-osu-server-switcher#how-to-add-a-server) and  [get your server featured](https://minisbett.github.io/ultimate-osu-server-switcher/#get-your-server-featured-partnership) in a partnership.

# How it works

The Program fetches all its informations from the data/data.json file, which means its fully expandable even without a software update.

Switching to a different server or even bancho itself is fairly simple. You just choose the server you want to connect to from a list and
click the «Connect» button.

# How to add a server

> **As of the 05/29/2020 every server's owner now needs to be member of our discord server as it is a requirement for our management!**
> **Thank you for your understanding.**

If you wish to add a server, please follow these steps:

1. Ask the server owner (if not you) if it's okay to add their server to our switcher.
2. Collect the following data:
- The server name
- The server's website url
- The server's ip-address
- The certificate
- The discord tag of the server owner (We need to verify that he is on our discord server)
- The certificate thumbprint (can be grabbed with our thumbprint reader, see releases) <br>
  NOTE: The certificate should be base64 encoded (.cer file extension) <br>
  If your certificate has the file extension .crt, use [this tutorial](https://support.comodo.com/index.php?/Knowledgebase/Article/View/361/17/how-do-i-convert-crt-file-into-the-microsoft-cer-format) to convert the .crt certificate to a .cer certificate.
- The server icon (It should be sized 48x48 due to performance)
3. Create an issue **with the label "server request"**. Please do not create a pull request as it would break our workflow. Thanks :)

# Get your server featured (Partnership)

**Currently we do not feature servers as we need to keep this state something special.**

To expand the switcher and to gain popularity, partnerships are welcome!

If you and your private server want to help us please DM us on Discord!

> minisbett#8873
>
> Julaaaan#7635

We will offer a feature icon ⭐ before your server name in the switcher and your server will be put to the top of the list.
In return for that we want you to offer our switcher as a second option on your switcher's download site.

More details can be discussed on Discord.

# In-deep explination

The server you are currently playing on is estimated by the ip that is deposited in the hosts file.
Switching a server includes deinstalling all certificates, modfying the hosts file, followed by installing the corresponding certificate
(if you chose to play on a third-party server).

The certificates will be installed on the local machine, rather than just the local user to avoid a warning prompt the user
has to agree with. This improves the switching agility to garantue the best user experience.

# Current servers
>
> Currently the following servers are listed:
>
> - [osu!bancho](https://osu.ppy.sh/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/bancho.png?raw=true">
> - [Ripple](https://ripple.moe/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/ripple.png?raw=true">
> - [EZPPFarm](https://ez-pp.farm/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/ezppfarm.png?raw=true"><sup>⭐</sup>
> - [osu!Ainu](https://ainu.pw/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/ainu.png?raw=true">
> - [Kurikku](https://kurikku.pw/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/kurikku.png?raw=true"><sup>⭐</sup>
> - [Akatsuki](https://akatsuki.pw/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/akatsuki.png?raw=true">
> - [osu!Gatari](https://gatari.pw/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/gatari.png?raw=true"><sup>⭐</sup>
> - [RealistikOsu!](https://ussr.pl/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/realistikosu.png?raw=true">
> - [Enjuu](https://enjuu.click/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/enjuu.png?raw=true">
> - [Kawata](https://kawata.pw/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/kawata.png?raw=true">
> - [Astellia](https://astellia.club/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/astellia.png?raw=true">
> - [Horizon](https://lemres.de/)<img width="16" height="16" src="https://github.com/MinisBett/ultimate-osu-server-switcher/blob/master/data/icons/horizon.png?raw=true">

# GUI

We have a light theme and a dark theme. You can toggle between them in the bottom right corner of the about tab.

![Light theme](https://minisbett.github.io/ultimate-osu-server-switcher/images/light.png)
![Dark theme](https://minisbett.github.io/ultimate-osu-server-switcher/images/dark.png)
