public void StartMission(string missionName)
        {
            var down = Program.Downloader.GetResource(DownloadType.MISSION, missionName);
            if (down == null)
            {
                //okay Mission exist, but lets check for dependency!
                down = Program.Downloader.GetDependenciesOnly(missionName);
            }

            var engine = Program.Downloader.GetResource(DownloadType.ENGINE, Program.TasClient.ServerWelcome.Engine ?? GlobalConst.DefaultEngineOverride);

            var metaWait = new EventWaitHandle(false, EventResetMode.ManualReset);
            Mod modInfo = null;
            Program.MetaData.GetModAsync(missionName,
                mod =>
                {
                    if (!mod.IsMission)
                    {
                        Program.MainWindow.InvokeFunc(() => { WarningBar.DisplayWarning(string.Format("{0} is not a valid mission", missionName)); });
                    }

                    else modInfo = mod;

                    metaWait.Set();
                },
                error =>
                {
                    Program.MainWindow.InvokeFunc(() =>
                    {
                        WarningBar.DisplayWarning(string.Format("Download of metadata failed: {0}", error.Message));
                        //container.btnStop.Enabled = true;
                    });
                    metaWait.Set();
                });

            var downloads = new List<Download>() { down, engine }.Where(x => x != null).ToList();
            if (downloads.Count > 0)
            {
                var dd = new WaitDownloadDialog(downloads);
                if (dd.ShowDialog(Program.MainWindow) == DialogResult.Cancel)
                {
                    Program.MainWindow.InvokeFunc(() => Program.NotifySection.RemoveBar(this));
                    return;
                }
            }
            metaWait.WaitOne();

            var spring = new Spring(Program.SpringPaths);
            spring.RunLocalScriptGame(modInfo.MissionScript, Program.TasClient.ServerWelcome.Engine ?? GlobalConst.DefaultEngineOverride);
            var cs = GlobalConst.GetContentService();
            cs.NotifyMissionRun(Program.Conf.LobbyPlayerName, missionName);
            spring.SpringExited += (o, args) => RecordMissionResult(spring, modInfo);
            Program.MainWindow.InvokeFunc(() => Program.NotifySection.RemoveBar(this));
        }
		public static void StartDownloadedMission(ScriptMissionData profile, string modInternalName)
		{
			var spring = new Spring(Program.SpringPaths);
			var name = Program.Conf.LobbyPlayerName;
			if (string.IsNullOrEmpty(name)) name = "Player";

			if (Utils.VerifySpringInstalled())
			{
				spring.RunLocalScriptGame(profile.StartScript.Replace("%MOD%", modInternalName).Replace("%MAP%", profile.MapName).Replace("%NAME%", name));
                var serv = GlobalConst.GetContentService();
				serv.NotifyMissionRun(Program.Conf.LobbyPlayerName, profile.Name);
			}
		}
        private void MissionBar_Load(object sender, EventArgs e)
        {
            label1.Text = string.Format("Starting mission {0} - please wait", missionName);

            var down = Program.Downloader.GetResource(DownloadType.MOD, missionName);
            if (down==null)
            {   //okay Mission exist, but lets check for dependency!
                down = Program.Downloader.GetDependenciesOnly(missionName);
            }

            var engine = Program.Downloader.GetAndSwitchEngine(Program.SpringPaths.SpringVersion);

            ZkData.Utils.StartAsync(() =>
            {
                var metaWait = new EventWaitHandle(false, EventResetMode.ManualReset);
                Mod modInfo = null;
                Program.SpringScanner.MetaData.GetModAsync(missionName,
                                                           mod =>
                                                           {
                                                               if (!mod.IsMission)
                                                               {
                                                                   Program.MainWindow.InvokeFunc(() =>
                                                                   {
                                                                       label1.Text = string.Format("{0} is not a valid mission", missionName);
                                                                       container.btnStop.Enabled = true;
                                                                   });
                                                               }

                                                               else modInfo = mod;

                                                               metaWait.Set();
                                                           },
                                                           error =>
                                                           {
                                                               Program.MainWindow.InvokeFunc(() =>
                                                               {
                                                                   label1.Text = string.Format("Download of metadata failed: {0}", error.Message);
                                                                   container.btnStop.Enabled = true;
                                                               });
                                                               metaWait.Set();
                                                           });
                //if (down != null) WaitHandle.WaitAll(new WaitHandle[] { down.WaitHandle, metaWait });
                //else metaWait.WaitOne();
                
                var waitHandles = new List<EventWaitHandle>();
                
                waitHandles.Add(metaWait);
                if (down != null)  waitHandles.Add(down.WaitHandle);
                if (engine != null) waitHandles.Add(engine.WaitHandle);
                
                if (waitHandles.Any()) WaitHandle.WaitAll(waitHandles.ToArray());

                if ((down != null && down.IsComplete == false) || (engine != null && engine.IsComplete == false) || modInfo==null)
                {
                    Program.MainWindow.InvokeFunc(() =>
                    {
                        label1.Text = string.Format("Download of {0} failed", missionName);

                        container.btnStop.Enabled = true;
                    });
                }

                if (modInfo != null && (down == null || down.IsComplete == true) && (engine == null || engine.IsComplete == true))
                {
                    if (Utils.VerifySpringInstalled())
                    {
                        var spring = new Spring(Program.SpringPaths);
                        spring.RunLocalScriptGame(modInfo.MissionScript);

                        var cs = GlobalConst.GetContentService();
                        cs.NotifyMissionRun(Program.Conf.LobbyPlayerName, missionName);
                    }
                    Program.MainWindow.InvokeFunc(() => Program.NotifySection.RemoveBar(this));
                }
            });
        }
        public void StartScriptMission(string missionName)
        {
            var serv = GlobalConst.GetContentService();
            var profile = serv.GetScriptMissionData(missionName);
            var downloads = new List<Download>();
            downloads.Add(Program.Downloader.GetResource(DownloadType.RAPID, profile.ModName));
            downloads.Add(Program.Downloader.GetResource(DownloadType.MAP, profile.MapName));
            downloads.Add(Program.Downloader.GetResource(DownloadType.ENGINE, Program.TasClient.ServerWelcome.Engine ?? GlobalConst.DefaultEngineOverride));
            if (profile.ManualDependencies != null) foreach (var entry in profile.ManualDependencies) if (!string.IsNullOrEmpty(entry)) downloads.Add(Program.Downloader.GetResource(DownloadType.NOTKNOWN, entry));

            downloads = downloads.Where(x => x != null).ToList();

            if (downloads.Count > 0)
            {
                var dd = new WaitDownloadDialog(downloads);
                if (dd.ShowDialog(Program.MainWindow) == DialogResult.Cancel) return;
            }

            var spring = new Spring(Program.SpringPaths);
            var name = Program.Conf.LobbyPlayerName;
            if (string.IsNullOrEmpty(name)) name = "Player";

            spring.RunLocalScriptGame(
                profile.StartScript.Replace("%MOD%", profile.ModName).Replace("%MAP%", profile.MapName).Replace("%NAME%", name),
                Program.TasClient.ServerWelcome.Engine ?? GlobalConst.DefaultEngineOverride);
            serv.NotifyMissionRun(name, profile.Name);
        }
        //[STAThread]
        private static void Main(params string[] args) {
            var startupPath = Path.GetDirectoryName(Path.GetFullPath(Application.ExecutablePath));
            var springPaths = new SpringPaths(startupPath);
            Spring runningSpring = null;
            TcpTransport connection = null;

            // speed up spring start
            springPaths.SpringVersionChanged += (sender, engine) =>
            {
                Utils.StartAsync(
                    () =>
                    {
                        UnitSync unitSync = null;
                        try
                        {
                            unitSync = new UnitSync(springPaths, engine); // initialize unitsync to avoid slowdowns when starting

                            if (unitSync.UnitsyncWritableFolder != springPaths.WritableDirectory)
                            {
                                // unitsync created its cache in different folder than is used to start spring -> move it
                                var fi = ArchiveCache.GetCacheFile(unitSync.UnitsyncWritableFolder);
                                if (fi != null) File.Copy(fi.FullName, Path.Combine(springPaths.WritableDirectory, "cache", fi.Name), true);
                            }
                        }
                        finally
                        {
                            unitSync?.Dispose();
                        }
                    });
            };

            Config config = null;
            try { config = JsonConvert.DeserializeObject<Config>(File.ReadAllText(startupPath + "/config.json")); } catch (Exception) { }

            CefWrapper.Initialize(startupPath + "/render", args);


            var springScanner = new SpringScanner(springPaths);
            springScanner.Start();

            EventHandler<ProgressEventArgs> workHandler =
                (s, e) => { CefWrapper.ExecuteJavascript("on_spring_scanner_work(" + JsonConvert.SerializeObject(e) + ");"); };
            springScanner.WorkStarted += workHandler;
            springScanner.WorkProgressChanged += workHandler;
            springScanner.WorkStopped += (s, e) => { CefWrapper.ExecuteJavascript("on_spring_scanner_work(null);"); };
            springScanner.LocalResourceAdded +=
                (s, e) => { CefWrapper.ExecuteJavascript("on_spring_scanner_add(" + JsonConvert.SerializeObject(e.Item) + ")"); };
            springScanner.LocalResourceRemoved +=
                (s, e) => { CefWrapper.ExecuteJavascript("on_spring_scanner_remove(" + JsonConvert.SerializeObject(e.Item) + ")"); };


            var downloader = new PlasmaDownloader.PlasmaDownloader(springScanner, springPaths); //rapid
            downloader.GetResource(DownloadType.ENGINE, GlobalConst.DefaultEngineOverride);

            // ZKL's downloader doesn't send events to monitor download progress, so we have to poll it.
            Timer pollDownloads = new Timer();
            pollDownloads.Interval = 250;
            pollDownloads.Tick += (s, e) => {
                CefWrapper.ExecuteJavascript("on_downloads_change(" + JsonConvert.SerializeObject(downloader.Downloads) + ")");
            };
            // Through some WinAPI dark magic it manages to use the message pump in the window that is run by CEF.
            // Can this be dangerous?
            pollDownloads.Start();


            CefWrapper.RegisterApiFunction(
                "getEngines",
                () => {
                    return new List<string> { "100.0" }; // TODO: stub
                });
            CefWrapper.RegisterApiFunction("getMods", () => { return springScanner.GetAllModResource(); });
            CefWrapper.RegisterApiFunction("getMaps", () => { return springScanner.GetAllMapResource(); });

            CefWrapper.RegisterApiFunction(
                "downloadEngine",
                (string engine) =>
                {
                    // Don't let GetAndSwitchEngine() touch the main SpringPaths.
                    var path = new SpringPaths(springPaths.WritableDirectory);
                    downloader.GetResource(DownloadType.ENGINE, engine);
                });
            CefWrapper.RegisterApiFunction("downloadMod", (string game) => { downloader.GetResource(DownloadType.MOD, game); });
            CefWrapper.RegisterApiFunction("downloadMap", (string map) => { downloader.GetResource(DownloadType.MAP, map); });
            CefWrapper.RegisterApiFunction(
                "abortDownload",
                (string name) =>
                {
                    downloader.Downloads.FirstOrDefault(d => d.Name == name)?.Abort();
                });

            CefWrapper.RegisterApiFunction(
                "startSpringScript",
                (string engineVer, string script) =>
                {
                    if (runningSpring != null) return null;
                    // Ultimately we should get rid of the concept of a "current set engine", but for now let's work around it.
                    var path = new SpringPaths(springPaths.WritableDirectory);
                    runningSpring = new Spring(path);
                    runningSpring.SpringExited += (obj, evt) =>
                    {
                        CefWrapper.ExecuteJavascript("on_spring_exit(" + (evt.IsCrash ? "true" : "false") + ");");
                        runningSpring = null;
                    };
                    try
                    {
                        runningSpring.RunLocalScriptGame(script, engineVer);
                        return null;
                    }
                    catch (Exception e)
                    {
                        runningSpring = null;
                        return e.Message;
                    }
                });

            CefWrapper.RegisterApiFunction(
                "connect",
                (string host, int port) =>
                {
                    if (connection != null) connection.RequestClose();
                    connection = new TcpTransport(host, port);
                    connection.ConnectAndRun(
                        async (s) => CefWrapper.ExecuteJavascript(
                            $"on_lobby_message({CefWrapper.mangleUtf8(JsonConvert.SerializeObject(s))})"),
                        async () => { },
                        async (requested) => CefWrapper.ExecuteJavascript(
                            $"on_connection_closed({CefWrapper.mangleUtf8(JsonConvert.SerializeObject(requested))})")
                        );
                });
            CefWrapper.RegisterApiFunction("disconnect", () => connection?.RequestClose());
            CefWrapper.RegisterApiFunction("sendLobbyMessage", (string msg) => connection?.SendLine(CefWrapper.unmangleUtf8(msg) + '\n'));

            CefWrapper.RegisterApiFunction(
                "readConfig",
                () =>
                {
                    try { return JsonConvert.DeserializeObject(File.ReadAllText(startupPath + "/config.json")); }
                    catch(FileNotFoundException) { return null; }
                });
            CefWrapper.RegisterApiFunction("saveConfig", (object conf) => File.WriteAllText(startupPath + "/config.json",
                JsonConvert.SerializeObject(conf, Formatting.Indented)));

            CefWrapper.RegisterApiFunction("setFullscreen", (bool fullscreen) => CefWrapper.SetFullscreen(fullscreen));

            var fileUrl = new Uri(startupPath + "/zkwl/index.html");
            CefWrapper.StartMessageLoop(fileUrl.AbsoluteUri, "black", !config?.lobbyWindowed ?? true);
            CefWrapper.Deinitialize();

            downloader.Dispose();
            springScanner.Dispose();
        }