////////////////

        /// <summary>
        /// Sends a POST request to a website asynchronously.
        /// </summary>
        /// <param name="url">Website URL.</param>
        /// <param name="jsonData">JSON-formatted string data.</param>
        /// <param name="onError">Called on error. Receives an `Exception`.</param>
        /// <param name="onCompletion">Called regardless of success. Receives a boolean indicating if the site request succeeded,
        /// and the output (if any).</param>
        public static void MakePostRequestAsync(
            string url,
            string jsonData,
            Action <Exception> onError,
            Action <bool, string> onCompletion = null)
        {
            TaskLauncher.Run((token) => {
                try {
                    ServicePointManager.Expect100Continue = false;
                    //var values = new NameValueCollection {
                    //	{ "modloaderversion", ModLoader.versionedName },
                    //	{ "platform", ModLoader.CompressedPlatformRepresentation },
                    //	{ "netversion", FrameworkVersion.Version.ToString() }
                    //};

                    using (var client = new WebClient()) {
                        ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, policyErrors) => {
                            return(true);
                        };

                        client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
                        client.Headers.Add(HttpRequestHeader.UserAgent, "tModLoader " + ModLoader.version.ToString());
                        //client.Headers["UserAgent"] = "tModLoader " + ModLoader.version.ToString();
                        client.UploadStringAsync(new Uri(url), "POST", jsonData);                          //UploadValuesAsync( new Uri( url ), "POST", values );
                        client.UploadStringCompleted += (sender, e) => {
                            if (token.IsCancellationRequested)
                            {
                                return;
                            }
                            WebConnectionLibraries.HandleResponse(sender, e, onError, onCompletion);
                        };
                    }
                } catch (WebException e) {
                    if (e.Status == WebExceptionStatus.Timeout)
                    {
                        onCompletion?.Invoke(false, "Timeout.");
                        return;
                    }

                    if (e.Status == WebExceptionStatus.ProtocolError)
                    {
                        var resp = (HttpWebResponse)e.Response;
                        if (resp.StatusCode == HttpStatusCode.NotFound)
                        {
                            onCompletion?.Invoke(false, "Not found.");
                            return;
                        }

                        onCompletion?.Invoke(false, "Bad protocol.");
                    }
                } catch (Exception e) {
                    onError?.Invoke(e);
                    //LogLibraries.Warn( e.ToString() );
                }
            });
        }
        ////////////////

        /// <summary>
        /// Makes a GET request to a website.
        /// </summary>
        /// <param name="url">Website URL.</param>
        /// <param name="onError">Called on error. Receives an `Exception`.</param>
        /// <param name="onCompletion">Called regardless of success. Receives a boolean indicating if the site request succeeded,
        /// and the output (if any).</param>
        public static void MakeGetRequestAsync(
            string url,
            Action <Exception> onError,
            Action <bool, string> onCompletion = null)
        {
            TaskLauncher.Run((token) => {
                try {
                    ServicePointManager.Expect100Continue = false;

                    using (var client = new WebClient()) {
                        ServicePointManager.ServerCertificateValidationCallback =
                            (sender, certificate, chain, policyErrors) => { return(true); };

                        client.Headers.Add(HttpRequestHeader.UserAgent, "tModLoader " + ModLoader.version.ToString());
                        client.DownloadStringAsync(new Uri(url));
                        client.DownloadStringCompleted += (sender, e) => {
                            if (token.IsCancellationRequested)
                            {
                                return;
                            }
                            WebConnectionLibraries.HandleResponse(sender, e, onError, onCompletion);
                        };
                        //client.UploadStringAsync( new Uri(url), "GET", "" );//UploadValuesAsync( new Uri( url ), "POST", values );
                    }
                } catch (WebException e) {
                    if (e.Status == WebExceptionStatus.Timeout)
                    {
                        onCompletion?.Invoke(false, "Timeout.");
                        return;
                    }

                    if (e.Status == WebExceptionStatus.ProtocolError)
                    {
                        var resp = (HttpWebResponse)e.Response;
                        if (resp.StatusCode == HttpStatusCode.NotFound)
                        {
                            onCompletion?.Invoke(false, "Not found.");
                            return;
                        }

                        onCompletion?.Invoke(false, "Bad protocol.");
                    }
                } catch (Exception e) {
                    onError?.Invoke(e);
                    //LogLibraries.Warn( e.ToString() );
                }
            });             //, cts.Token );
        }