context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Diagnostics;
  using System.IO;
  using System.IO.Compression;
  using System.Linq;
  using System.Net;
  using System.Net.Http;
  using System.Net.Http.Headers;
  using System.Runtime.InteropServices;
  using System.Text;
  using System.Threading;
  using System.Threading.Tasks;
  using System.Web.Script.Serialization;
  using System.Windows.Forms;
  using Org.BouncyCastle.Crypto.Engines;
  using Org.BouncyCastle.Crypto.Modes;
  using Org.BouncyCastle.Crypto.Parameters;
  
  namespace CrashHandler
  {
      public sealed class CrashDumper : IDisposable
      {
          public Dictionary<string, string> PretendFiles => _lstPretendFilePaths;
          public Dictionary<string, string> Attributes => _attributes;
          public CrashDumperProgress Progress => _progress;
  
          public event Action<object, CrashDumperProgressChangedEventArgs> CrashDumperProgressChanged;
  
          public string WorkingDirectory { get; }
          public Process Process { get; private set; }
          public bool DoCleanUp { get; set; } = true;
  
          private readonly List<string> _lstFilePaths;
          private readonly Dictionary<string, string> _lstPretendFilePaths;
          private readonly Dictionary<string, string> _attributes;
          private readonly short _procId;
          private readonly IntPtr _exceptionPrt;
          private readonly uint _threadId;
          private volatile CrashDumperProgress _progress;
          private readonly BackgroundWorker _worker = new BackgroundWorker();
          private readonly ManualResetEventSlim _startSendEvent = new ManualResetEventSlim(false);
          private string _strLatestDumpName = string.Empty;
          private readonly Random _objRandom = new Random();
  
          private readonly StreamWriter CrashLogWriter;
  
          /// <summary>
          ///
          /// </summary>
          /// <param name="b64Json">String path of the text file that contains our JSON package.</param>
          public CrashDumper(string b64Json)
          {
              CrashLogWriter = new StreamWriter(
                  Path.ChangeExtension(
                      Path.Combine(
                          Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
                          GenerateFolderName()
                      ),
                      "txt"), false, Encoding.UTF8);
  
              CrashLogWriter.WriteLine("This file contains information on a crash report for Chummer5A.");
              CrashLogWriter.WriteLine("You can safely delete this file, but a developer might want to look at it.");
  
              if (!Deserialize(b64Json, out _procId, out _lstFilePaths, out _lstPretendFilePaths, out _attributes,
                  out _threadId, out _exceptionPrt))
              {
                  throw new ArgumentException("Could not deserialize");
              }
  
              if (_lstPretendFilePaths.TryGetValue("exception.txt", out string exception))
              {
                  CrashLogWriter.WriteLine(exception);
                  CrashLogWriter.Flush();
              }
  
              CrashLogWriter.WriteLine("Crash id is " + _attributes["visible-crash-id"]);
              CrashLogWriter.Flush();
  
              //		    _filesList = new List<string>();
              //		    _attributes = new Dictionary<string, string>()
              //		    {
              //		        {"visible-error-friendly", "FAKE"},
              //		        {"visible-crash-id", Guid.NewGuid().ToString()}
              //		    };
              //		    _pretendFiles = new Dictionary<string, string>();
  
              WorkingDirectory = Path.Combine(Path.GetTempPath(), GenerateFolderName());
              Directory.CreateDirectory(WorkingDirectory);
              Attributes["visible-crashhandler-major-minor"] = "v3_0";
  
              CrashLogWriter.WriteLine("Crash working directory is " + WorkingDirectory);
              CrashLogWriter.Flush();
  
              _worker.WorkerReportsProgress = false;
              _worker.WorkerSupportsCancellation = false;
              _worker.DoWork += CollectCrashDump;
          }
  
          private void AttemptDebug(Process process)
          {
              bool blnSuccess = NativeMethods.DebugActiveProcess(new IntPtr(process.Id));
              int intLastError = Marshal.GetLastWin32Error();
              if (blnSuccess)
              {
                  Attributes["debugger-attached-success"] = bool.TrueString;
              }
              else
              {
                  Attributes["debugger-attached-error"] = new Win32Exception(intLastError).Message;
              }
          }
  
          public void AllowSending()
          {
              _startSendEvent.Set();
          }
  
          private static string GenerateFolderName()
          {
              return $"chummer_crash_{DateTime.UtcNow.ToFileTimeUtc()}";
          }
  
          private void SetProgress(CrashDumperProgress progress)
          {
              _progress = progress;
              CrashDumperProgressChanged?.Invoke(this, new CrashDumperProgressChangedEventArgs(progress));
          }
  
          public void StartCollecting()
          {
              if (!_worker.IsBusy)
                  _worker.RunWorkerAsync();
          }
  
          private async void CollectCrashDump(object sender, DoWorkEventArgs e)
          {
              SetProgress(CrashDumperProgress.Started);
              try
              {
                  Process = Process.GetProcessById(_procId);
  
                  SetProgress(CrashDumperProgress.Debugger);
                  AttemptDebug(Process);
  
                  SetProgress(CrashDumperProgress.CreateDmp);
                  if (CreateDump(Process, _exceptionPrt, _threadId, Attributes.ContainsKey("debugger-attached-success")))
                  {
                      Process.Kill();
                      SetProgress(CrashDumperProgress.Error);
                      return;
                  }
  
                  SetProgress(CrashDumperProgress.CreateFiles);
                  GetValue();
  
                  SetProgress(CrashDumperProgress.CopyFiles);
                  CopyFiles();
  
                  SetProgress(CrashDumperProgress.FinishedCollecting);
  
                  _startSendEvent.Wait();
  
                  await CrashLogWriter.WriteLineAsync("Files collected");
                  await CrashLogWriter.FlushAsync();
  
                  SetProgress(CrashDumperProgress.Compressing);
                  byte[] zip = GetZip();
  
                  SetProgress(CrashDumperProgress.Encrypting);
                  byte[] encrypted = Encrypt(zip, out byte[] iv, out byte[] key);
  
                  SetProgress(CrashDumperProgress.Uploading);
                  string location = await Upload(encrypted);
  
                  await CrashLogWriter.WriteLineAsync("Files uploaded");
                  await CrashLogWriter.FlushAsync();
  
                  SetProgress(CrashDumperProgress.Saving);
                  Attributes["visible-key"] = MakeStringKey(iv, key);
                  Attributes["visible-location"] = location;
  
                  Task tskUpload = UploadToAws();
  
                  await CrashLogWriter.WriteLineAsync("Key saved");
                  await CrashLogWriter.FlushAsync();
  
                  SetProgress(CrashDumperProgress.Cleanup);
  
                  if (DoCleanUp)
                  {
                      Clean();
  
                      await CrashLogWriter.WriteLineAsync("Cleanup done");
                      await CrashLogWriter.FlushAsync();
                  }
  
                  await tskUpload;
  
                  SetProgress(CrashDumperProgress.FinishedSending);
                  Process.Kill();
              }
              catch (WebException rex)
              {
                  SetProgress(CrashDumperProgress.Error);
  
                  MessageBox.Show("Upload service had an error."
                                  + Environment.NewLine + "Reason: " + rex.Message
                                  + Environment.NewLine + Environment.NewLine +
                                  "Please manually submit an issue to https://github.com/chummer5a/chummer5a/issues and attach the file \"" +
                                  _strLatestDumpName + "\" found in \"" + WorkingDirectory + "\".");
                  Process?.Kill();
              }
              catch (RemoteServiceException rex)
              {
                  SetProgress(CrashDumperProgress.Error);
  
                  MessageBox.Show("Upload service had an error."
                                  + Environment.NewLine + "Reason: " + rex.Message
                                  + Environment.NewLine + Environment.NewLine +
                                  "Please manually submit an issue to https://github.com/chummer5a/chummer5a/issues and attach the file \"" +
                                  _strLatestDumpName + "\" found in \"" + WorkingDirectory + "\".");
                  Process?.Kill();
              }
              catch (Exception ex)
              {
                  SetProgress(CrashDumperProgress.Error);
                  MessageBox.Show(ex.ToString());
  
                  Process?.Kill();
              }
          }
  
          private bool CreateDump(Process process, IntPtr exceptionInfo, uint threadId, bool debugger)
          {
              bool ret;
              _strLatestDumpName = "crashdump-" + DateTime.Now.ToFileTimeUtc().ToString() + ".dmp";
              using (FileStream file = File.Create(Path.Combine(WorkingDirectory, _strLatestDumpName)))
              {
                  MiniDumpExceptionInformation info = new MiniDumpExceptionInformation
                  {
                      ClientPointers = true,
                      ExceptionPointers = exceptionInfo,
                      ThreadId = threadId
                  };
  
                  const MINIDUMP_TYPE dtype = MINIDUMP_TYPE.MiniDumpWithPrivateReadWriteMemory |
                                              MINIDUMP_TYPE.MiniDumpWithDataSegs |
                                              MINIDUMP_TYPE.MiniDumpWithHandleData |
                                              MINIDUMP_TYPE.MiniDumpWithFullMemoryInfo |
                                              MINIDUMP_TYPE.MiniDumpWithThreadInfo |
                                              MINIDUMP_TYPE.MiniDumpWithUnloadedModules;
  
                  bool extraInfo = !(exceptionInfo == IntPtr.Zero || threadId == 0 || !debugger);
  
                  if (extraInfo)
                  {
                      ret = !NativeMethods.MiniDumpWriteDump(process.Handle, _procId,
                          file.SafeFileHandle?.DangerousGetHandle() ?? IntPtr.Zero,
                          dtype, ref info, IntPtr.Zero, IntPtr.Zero);
                  }
                  else if (NativeMethods.MiniDumpWriteDump(process.Handle, _procId,
                      file.SafeFileHandle?.DangerousGetHandle() ?? IntPtr.Zero,
                      dtype, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero))
                  {
                      ret = false;
  
                      //Might solve the problem if crashhandler stops working on remote (hah)
                      Attributes["debug-debug-exception-info"] = exceptionInfo.ToString();
                      Attributes["debug-debug-thread-id"] = threadId.ToString();
                  }
                  else
                  {
                      ret = true;
                  }
  
                  file.Flush();
              }
  
              return ret;
          }
  
          private void GetValue()
          {
  
gt:             StringBuilder sb = new StringBuilder(byte.MaxValue);